v0.4.8-pre.014

This commit is contained in:
2026-08-09 12:19:06 +02:00
parent 8b831f8692
commit aec7b4b76a
13 changed files with 1938 additions and 408 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml # file: Cargo.toml
# version: 36 # version: 37
[workspace] [workspace]
resolver = "3" resolver = "3"
@@ -18,7 +18,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.4.8-pre.13" version = "0.4.8-pre.14"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3"

View File

@@ -1,11 +1,11 @@
<!-- file: docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md --> <!-- file: docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md -->
<!-- version: 55 --> <!-- version: 59 -->
# Plan `0.4.8` — Solana Program Metadata et complétude Token-2022 # Plan `0.4.8` — Solana Program Metadata et complétude Token-2022
## 1. Statut et rôle ## 1. Statut et rôle
Ce document est le livrable principal de `0.4.8-pre.001`, réconcilié après laudit contractuel de `0.4.8-pre.002`, les modèles de `pre.003`, les décodeurs de comptes de `pre.004`, le décodeur dinstructions de `pre.005`, la matérialisation de `pre.006`, lexécuteur de `pre.007`, lorchestration généraliste de `pre.008`, les scénarios réutilisables de `pre.009`, leur intégration desktop et leur campagne Devnet de `pre.010`, puis la complétude Token-2022 Token Metadata de `pre.011` et la préparation de sa campagne Devnet dédiée en `pre.012`. Il constitue le plan vivant de la version et reste modifiable lorsque laudit du code, des interfaces officielles, de lIDL ou des validations Devnet impose un ajustement. Ce document est le livrable principal de `0.4.8-pre.001`, réconcilié après laudit contractuel de `0.4.8-pre.002`, les modèles de `pre.003`, les décodeurs de comptes de `pre.004`, le décodeur dinstructions de `pre.005`, la matérialisation de `pre.006`, lexécuteur de `pre.007`, lorchestration généraliste de `pre.008`, les scénarios réutilisables de `pre.009`, leur intégration desktop et leur campagne Devnet de `pre.010`, puis la complétude Token-2022 Token Metadata de `pre.011`, la préparation de sa campagne Devnet dédiée en `pre.012` et la clôture réseau Metaplex de `pre.013`. Il constitue le plan vivant de la version et reste modifiable lorsque laudit du code, des interfaces officielles, de lIDL ou des validations Devnet impose un ajustement.
Il doit être maintenu pendant chaque prerelease, puis archivé pendant la dernière prerelease après transfert des décisions durables vers le ROADMAP, les matrices, les guides, les rapports, les TODO et les changelogs concernés. Il doit être maintenu pendant chaque prerelease, puis archivé pendant la dernière prerelease après transfert des décisions durables vers le ROADMAP, les matrices, les guides, les rapports, les TODO et les changelogs concernés.
@@ -361,23 +361,141 @@ Résultat durable : la campagne réelle du 7 août 2026 confirme les cinq scéna
Les validations suivantes ferment ensuite `Create -> Mint` sur les cinq familles et `Verify -> Unverify` sur collection. Après `fix-019`, la matrice v3 compte 4 opérations `confirmed` et 16 `not_run`. `fix-020` corrige la fixture `Print` en laissant mint et ATA dédition absents avant linstruction. La tentative suivante confirme réellement `Print` avec la signature `2199PTvKyTAfnGRHVaaCceW4u8phdJSu5ULkceD9uJeQyjjenEupK9osRZu1Pzx2NuQaJnAwngSAXMvr5oNtf5y6` au slot `482068400`, puis sarrête sur la lecture stateful du compte `Edition` compact. `fix-021` corrige cette frontière 41 octets sérialisés / 42 octets alloués, ajoute une régression stateful et préserve `Sync` sur les références de signataires additionnels ; la validation suivante montre toutefois qu'une conversion locale en `Vec<&dyn Signer>` efface encore cette propriété dans l'état du futur Tauri. `Print` et `Burn` restent `not_run` jusquà un rerun complet du pipeline de preuve. Les validations suivantes ferment ensuite `Create -> Mint` sur les cinq familles et `Verify -> Unverify` sur collection. Après `fix-019`, la matrice v3 compte 4 opérations `confirmed` et 16 `not_run`. `fix-020` corrige la fixture `Print` en laissant mint et ATA dédition absents avant linstruction. La tentative suivante confirme réellement `Print` avec la signature `2199PTvKyTAfnGRHVaaCceW4u8phdJSu5ULkceD9uJeQyjjenEupK9osRZu1Pzx2NuQaJnAwngSAXMvr5oNtf5y6` au slot `482068400`, puis sarrête sur la lecture stateful du compte `Edition` compact. `fix-021` corrige cette frontière 41 octets sérialisés / 42 octets alloués, ajoute une régression stateful et préserve `Sync` sur les références de signataires additionnels ; la validation suivante montre toutefois qu'une conversion locale en `Vec<&dyn Signer>` efface encore cette propriété dans l'état du futur Tauri. `Print` et `Burn` restent `not_run` jusquà un rerun complet du pipeline de preuve.
#### Chronologie détaillée de qualification réseau
Les jalons ci-dessous conservent les décisions et preuves intermédiaires de la seconde moitié de `pre.013`. Ils restent rattachés à cette prerelease afin que le plan ne crée pas une seconde chronologie après les critères de clôture.
##### Consolidation `pre.013-delta-fix-013`
La campagne SFT `Create -> Mint` est confirmée avec preuves machine-readable complètes : `Create` signature `2s81m6k9HYDLmCy7oXKaqBvtb62iyX4bJmij2f2VjHuoRQBBA6YqRuAsjyaM7JCaJXasbU3WCfiko91cZcDLLdxV` slot `481908028`, puis `Mint` signature `4pAemhEJewuLAeqLkqwzPvVmsCRbkyLiN6uAUWBA5RMsofaEDt5Xr8YGiBEvLWQM7Qvxnd81mmCHpandmfmWuyAm` slot `481908039`. `Mint` fait passer supply et amount de `0` à `10`, et les deux étapes terminent hydratation canonique, extraction Core, replay, matérialisation et idempotence. La matrice Devnet passe en version 3 afin de conserver un bundle de preuves indépendant par famille ; `Create` et `Mint` sont donc confirmés sur NFT et SFT sans fusionner leurs signatures ou slots. Fungible, collection et pNFT restent les trois variantes `Create -> Mint` à exécuter avant les fixtures spécialisées Print/Burn, collection parent-membre, pNFT, uses et escrow.
##### Consolidation `pre.013-delta-fix-014`
La campagne fungible `Create -> Mint` est confirmée avec un mint à 9 décimales : `Create` signature `3J1Ebq9DVkupVFQmqQ84QaNeztwMuRwbtXj7CiyUWwZPj4KCAJpKLAbPvpYss5cdiY33foXXScWWZE98H7tvbZed` slot `481910724`, puis `Mint` signature `2GKiY9foLmioYtUHttA2D6nCSmsftRL1JDKxENHmCnb5w38FGSxwBiRNMfF7mH8htv3G45irmge6DVtr6VN359j2` slot `481910734`. `Mint` fait passer supply et amount de `0` à `1_000_000_000`; les deux étapes terminent le pipeline de preuve complet. La matrice v3 contient désormais des bundles indépendants `nft`, `sft` et `fungible` pour `Create` et `Mint`. Collection et pNFT restent les deux dernières variantes `Create -> Mint` avant le passage aux fixtures spécialisées Print/Burn, collection parent-membre, pNFT lifecycle, uses et escrow.
##### État après `pre.013-delta-fix-015`
Les campagnes `Create -> Mint` NFT, SFT, fungible et collection sont maintenant confirmées sur Devnet avec bundles de preuve séparés par famille. Collection valide explicitement metadata + Master Edition, deux snapshots stateful par étape et la transition supply/amount `0 -> 1`. La variante `programmable_nft` est la dernière à exécuter pour fermer ce cycle multi-famille ; elle devra en plus prouver l'apparition du token record après `Mint`. Une fois cette variante qualifiée, le travail `pre.013` bascule vers les fixtures spécialisées collection parent-membre, Print/Burn/éditions, lifecycle pNFT, uses et escrow.
##### État après `pre.013-delta-fix-016`
La première tentative `programmable_nft` atteint et confirme `Mint` (`Gkg5A6ehuSzkU1N318kYoRHm5RdxV2yBRpaK9Gwyt6vN1ToYwAS6Vzpk2rQYWS9NJz1rnUpypLCn7DqWFzQzxFc`, slot `481913765`) puis échoue uniquement sur une postcondition SPL commune qui exigeait `Initialized=1`. LATA observé possède le mint, lowner et lamount exacts mais `state=2`, soit `Frozen`, état attendu pour la couche SPL dun pNFT. `fix-016` distingue donc létat SPL par famille et conserve désormais `tokenAccountStateBeforeMint/AfterMint` dans la preuve machine-readable. Le bundle pNFT nest pas encore promu : un rerun complet doit retourner `ok`, valider le Token Record et démontrer explicitement `Initialized(1) -> Frozen(2)`.
##### Avancement `pre.013-delta-fix-017`
- `Create -> Mint` est qualifié sur les cinq familles : NFT, SFT, fungible, collection et pNFT.
- `Create` et `Mint` restent les deux seules opérations Metaplex `confirmed`; chacune possède cinq bundles `familyEvidence` complets.
- le premier lot spécialisé suivant est la collection parent-membre `Verify -> Unverify`; son runner est préparé mais reste sans preuve réseau tant que le test Devnet opt-in na pas terminé `ok`.
- la postcondition de collection utilise létat autoritatif du programme (`verified` et `CollectionDetails::V1.size`) et ne réintroduit pas `SetCollectionSize`.
- après qualification de `Verify`/`Unverify`, poursuivre vers les fixtures `Print/Burn`, puis les cycles pNFT delegate/revoke/lock/unlock/transfer, avant uses/escrow/maintenance.
##### Avancement `pre.013-delta-fix-018`
La première exécution collection spécialisée atteint et confirme `Verify(CollectionV1)` au slot `481931061`, puis sarrête pendant le decode replay. Lautorité étant également fee payer, ses privilèges Core globaux sont `signer + writable`; le décodeur exigeait à tort légalité locale `signer + readonly`. Le correctif applique à `Verify`/`Unverify` la règle déjà validée pour `Create`/`Mint` : seuls les privilèges requis constituent des minima. Il corrige en outre larité de `Unverify` de 8 à 7 positions conformément au wrapper courant.
Aucune promotion réseau nest faite à partir de cette preuve partielle. Le prochain jalon reste le rerun intégral `Verify -> Unverify`, avec postconditions membre `false -> true -> false` et taille parent `0 -> 1 -> 0`, avant de passer aux fixtures Print/Burn.
##### Avancement `pre.013-delta-fix-019`
Le rerun collection est complètement qualifié : `Verify` (`39JwUoxF2WWYWvEEWzFxSmKZVwoqXD1wH78sgGaoXv2vZKYUsXDNNnKikeuyQcfCqDftEMHSKFDHnTeSQ3bRpFcJ`, slot `481936730`) puis `Unverify` (`4Kfv42BGZtuBEwqTie5RWwdQ772hyxKiU9d3F3gfRnwVNrxCs2iZMvwL17Rwmuyr3XCVoHdaJVgaqCwv8y7bKoZY`, slot `481936742`) terminent le pipeline complet et prouvent `verified false -> true -> false` ainsi que `size 0 -> 1 -> 0`. La matrice Devnet passe à 4 opérations courantes `confirmed` (`Create`, `Mint`, `Verify`, `Unverify`) et 16 `not_run`.
Le prochain sous-lot est `Print -> Burn` : master NFT imprimable `Limited(1)`, mint dédition frais, Edition Marker stateful et support explicite dun signataire supplémentaire déjà déclaré par la requête. La postcondition `Burn` est bornée par la fixture fraîche : supply imprimée `1 -> 0`, master supply `1 -> 0`, fermeture du token account et de l'Edition imprimée, fermeture sémantique de Metadata et fermeture de lEdition Marker redevenu vide. Les preuves réseau restent fermées : ni `Print` ni `Burn` ne sont promus avant un test Devnet complet. Après cette campagne, poursuivre vers le lifecycle pNFT courant (`Delegate/Revoke/Lock/Unlock/Transfer/Burn` selon les préconditions exactes), puis uses, escrow et opérations de maintenance restantes.
##### Avancement `pre.013-delta-fix-020`
La première exécution réelle de `Print -> Burn` sarrête pendant la simulation exacte de `Print`, avant toute soumission. Le programme retourne `MetadataError::NotEnoughTokens` (`0x20`). Le réaudit du chemin courant identifie la cause dans la fixture `fix-019` : elle précréait le mint dédition **et son ATA vide**. Or `Print` exige `amount=1` lorsquun token account dédition existe déjà ; lorsquils sont absents, il crée lui-même le mint, crée lATA et frappe exactement un token. `fix-020` conserve donc uniquement le keypair signataire frais et exige labsence on-chain du mint et de lATA avant `Print`.
Le runner valide séparément lATA du master à `amount=1` avant simulation afin quun défaut de précondition master produise un diagnostic distinct. Il réembarque également le contrat stateful public `EditionMarker` et ajoute une régression dAPI externe, car le workspace local ayant reçu `fix-019` contenait le nouveau scénario mais pas ce variant `kb-pipeline`. `Print` et `Burn` restent tous deux `not_run`; la matrice demeure à 4 opérations `confirmed` et 16 `not_run` jusquau rerun complet.
##### Avancement `pre.013-delta-fix-021`
Le compte `Edition` imprimé est désormais décodé avec les allocations Metaplex bornées : 41 octets sérialisés, 42 octets compacts ou 241 octets historiques avec padding nul. Le rerun franchit alors la validation post-exécution de `Print`, exécute `Burn` et atteint sa dernière postcondition locale. En parallèle, `cargo check --workspace` révèle que la voie multi-signataires reste non-`Send` : le `Signer + Sync` public est effacé dans une `Vec<&dyn Signer>` conservée par le futur. `kb-lib` révèle aussi un seul test `Print` incorrect qui déclare readonly un `edition_token_record` réellement writable.
##### Avancement `pre.013-delta-fix-022`
Le prochain rerun part désormais d'un contrat plus exact : la conversion vers `&dyn Signer` est enfermée dans une fonction synchrone de signature, la régression `Print` conserve le writable obligatoire du token record, et la fermeture Metadata n'est plus assimilée à une absence RPC stricte. Pour `MetadataV1`, le programme peut conserver les fees résiduels et laisser un compte d'un octet `0x00` `Uninitialized`; seul ce tombstone exact ou l'absence complète est accepté comme fermeture. Le token account imprimé, l'Edition et l'Edition Marker vide doivent toujours disparaître. `Print` et `Burn` restent `not_run` jusqu'à la sortie complète `METAPLEX_PRINT_BURN_*` avec preuves opérateur.
##### Consolidation `pre.013-delta-fix-023`
Le rerun `fix-022` ferme entièrement `Print -> Burn`. `Print` est simulé au slot `482087795`, confirmé par `REHAxUZrSdKDj8F8k27Amrg7hwuJFhVfstjc9xjn2XGfTX38fH4tFwPeNrF5zeMN5pc21PpUPSgaKXMMfTFd7TE` au slot `482087799`, puis `Burn` est simulé au slot `482087813` et confirmé par `4cNUxGGSWifuAtqdPJAtM3Cjpi4JATitRDqrvEAe85NLpMWcgWmC27bsfjNUmK6sEBDovNPtkPV3wDH1LpfGbhd1` au slot `482087818`. `Print` matérialise une instruction et cinq snapshots ; `Burn` une instruction et deux snapshots. Les deux étapes terminent hydratation canonique, extraction Core, replay et seconde passe idempotente. La supply du master suit `0 -> 1 -> 0`, le mint imprimé `0 -> 1 -> 0`, lEdition et lEdition Marker sont créés puis fermés, et Metadata termine sous le tombstone fee exact dun octet `0x00`. La matrice v3 passe donc à 6 opérations `confirmed` et 14 `not_run`.
Le lot prépare ensuite un parcours pNFT unique et cohérent couvrant cinq opérations encore non qualifiées : `Delegate(StakingV1) -> Lock -> Unlock -> Revoke(StakingV1) -> Delegate(TransferV1) -> Transfer`. Le premier delegate prouve le cycle de lock du Token Record ; le second prouve le transfert délégué vers une ATA et un Token Record destination frais. Le transfert doit vider lATA source, remplir lATA destination, conserver les comptes SPL pNFT gelés et supprimer la délégation sur le Token Record destination. Aucun Rule Set nest fabriqué artificiellement : la fixture pNFT sans rule set suffit pour qualifier les wrappers et les transitions de délégation. `Migrate` reste séparé car il concerne la conversion dun actif legacy vers pNFT et ne peut pas être exercé sur cette fixture déjà programmable.
##### Avancement `pre.013-delta-fix-029`
Le cycle pNFT est maintenant qualifié avec six preuves transactionnelles complètes : deux variantes `Delegate` (`StakingV1`, `TransferV1`) et les opérations `Lock`, `Unlock`, `Revoke`, `Transfer`. Les cinq opérations canoniques correspondantes passent à `confirmed` sur `programmable_nft`. Avec `Create`, `Mint`, `Verify`, `Unverify`, `Print` et `Burn`, la matrice compte désormais **11/20 opérations confirmées**, **1 `unavailable` (`Use`)** et **8 `not_run`**.
Le probe `Use` est désormais exécuté et classé : Devnet rejette linstruction courante de discriminant 51 avec `InvalidInstructionData`, aucune transaction nest soumise et lopération passe à `unavailable`. Le SDK et le runtime restent donc explicitement divergents sur cette surface. `Migrate` est par ailleurs explicitement `Removed` dans le routeur courant et devra être classé avec la même discipline de preuve.
Le prochain jalon est le graphe escrow (`CreateEscrowAccount -> TransferOutOfEscrow -> CloseEscrowAccount`) avec un NFT contrôlé, un Token Owned Escrow et un token attribut SPL contrôlé. Ensuite réconcilier `Update`, `Collect`, `Resize`, `CloseAccounts` et `Migrate` avec leur disponibilité réelle avant consolidation de `pre.013`.
##### Avancement `pre.013-delta-fix-033`
Token Owned Escrow est entièrement qualifié sur Devnet. Les trois transactions Metaplex `CreateEscrowAccount`, `TransferOutOfEscrow`, `CloseEscrowAccount` sont confirmées avec replay et matérialisation complets ; lunité SPL déposée dans lATA du PDA revient exactement à lopérateur, lATA vide est fermée, puis lescrow est fermé sans modifier le NFT parent. La matrice est désormais à **14 `confirmed`, 1 `unavailable`, 5 `not_run`**.
La première tentative de la dernière campagne montre que les créations actuelles sont déjà au layout cible : après un `Update` validé en interne, `Resize` atteint `IX: Resize` mais la simulation retourne `AlreadyResized(201)` et aucune soumission na lieu. Une réussite positive exigerait un compte legacy sous-dimensionné contrôlé, non constructible par la fixture courante. `Resize` passe donc à `unavailable`, portant la matrice à **14 `confirmed`, 2 `unavailable`, 4 `not_run`**.
Le rerun final soumet uniquement `Update`. `Resize`, `Migrate`, `Collect` et `CloseAccounts` sont des probes simulation-only, avec refus attendus `AlreadyResized(201)`, `Removed(75)`, `UpdateAuthorityIncorrect(7)` et `InvalidCloseAuthority(188)`. Si le bundle est conforme, la matrice pourra se fermer à **15 `confirmed`, 5 `unavailable`, 0 `not_run`** puis passer à la consolidation finale de `pre.013`.
##### Clôture de la matrice Metaplex `pre.013-delta-fix-036`
La dernière campagne maintenance est qualifiée. `Update` passe à `confirmed` sur NFT et les probes `Resize`, `Migrate`, `Collect`, `CloseAccounts` produisent exactement les refus runtime attendus sans soumission. Avec `Use` déjà classé, la matrice des 20 opérations courantes atteint **15 `confirmed`, 5 `unavailable`, 0 `not_run`**.
Les cinq indisponibilités sont distinctes et ne doivent pas être amalgamées : `Use` diverge du SDK courant sur Devnet ; `Resize` exige un compte legacy sous-dimensionné contrôlé ; `Migrate` est retiré du runtime ; `Collect` et `CloseAccounts` exigent des autorités Metaplex fixes hors du profil opérateur contrôlé.
Le prochain delta de `pre.013` doit être une **consolidation**, pas une nouvelle campagne fonctionnelle : audit des statuts finaux, réconciliation des rapports/TODO/changelogs et vérification que toutes les matrices et guides reflètent 0 ligne `not_run`. Après cette consolidation et les tests de conformité locaux, le développement peut passer à `0.4.8-pre.014`.
#### Clôture de `pre.013` #### Clôture de `pre.013`
La consolidation suivant `fix-036` confirme que la matrice Metaplex est intégralement classée et que les contrôles locaux sont propres : formatage, `cargo check --workspace`, Clippy sans warning, audit workspace, puis 105 tests unitaires + 1 CLI + 1 API externe de `kb-pipeline-demo-scenarios`. Les guides et TODO actifs sont réconciliés avec cet état. `pre.014` peut donc commencer directement sur la finalisation desktop metadata ; aucune nouvelle campagne Metaplex fondamentale n'est attendue sauf régression observée. La consolidation suivant `fix-036` confirme que la matrice Metaplex est intégralement classée et que les contrôles locaux sont propres : formatage, `cargo check --workspace`, Clippy sans warning, audit workspace, puis 105 tests unitaires + 1 CLI + 1 API externe de `kb-pipeline-demo-scenarios`. Les guides et TODO actifs sont réconciliés avec cet état. `pre.014` peut donc commencer directement sur la finalisation desktop metadata ; aucune nouvelle campagne Metaplex fondamentale n'est attendue sauf régression observée.
### `0.4.8-pre.014` — finalisation desktop metadata ### `0.4.8-pre.014` — finalisation desktop Metadata — terminée et validée
- compléter le sous-panneau Token-2022 après les travaux de `pre.011` et `pre.012` et intégrer les éventuels compléments Metaplex de `pre.013` ; Objectif : réunir dans `demo_execution_metadata` les trois domaines Metadata sans dupliquer les orchestrations réseau déjà qualifiées, tout en conservant des frontières UI et des modules desktop explicites.
- conserver des accordéons ou sous-panneaux séparés pour `ProgM6…`, Token-2022 et Metaplex ;
- compléter le sous-panneau Token-2022 après `pre.011` et `pre.012` ;
- intégrer les campagnes Metaplex qualifiées de `pre.013` plutôt quun parcours Devnet générique incomplet ;
- conserver des accordéons distincts pour `ProgM6…`, Token-2022 et Metaplex ;
- réutiliser le JsonViewer commun pour les résultats intégralement JSON ; - réutiliser le JsonViewer commun pour les résultats intégralement JSON ;
- réconcilier préparation, simulation, soumission, lectures et preuves des trois domaines ; - réconcilier préparation, simulation, soumission, lectures et preuves des trois domaines ;
- aucun bouton de fetch URI. - ne fournir aucun bouton de fetch URI.
#### `pre.014-delta` — ouverture du jalon
Le workspace passe à `0.4.8-pre.14` tandis que `package.json` et `tauri.conf.json` restent à `0.4.8`. Token-2022 Token Metadata est ajouté au panneau en appelant directement `execute_devnet_token_2022_metadata_campaign`. Les trois domaines partagent le profil Devnet préparé et les trois JsonViewer communs. Le journal dexécution est sorti de la grille `5/7` et occupe toute la largeur du `container-fluid`.
La validation Tauri réelle confirme ensuite les campagnes desktop Token-2022 Token Metadata et Solana Program Metadata sur Devnet.
#### `pre.014-delta-fix-001` — campagnes Metaplex qualifiées
Lancien chemin Metaplex `scénario -> étape -> intent JSON` sarrêtait après la préparation de fixture et ne lançait aucune campagne spécialisée. Le parcours Devnet principal est remplacé par onze campagnes réutilisables : cinq `Create -> Mint`, collection `Verify -> Unverify`, `Print -> Burn`, lifecycle pNFT, Token Owned Escrow, maintenance et probe Use. Les opérations `unavailable` restent confinées aux probes simulation-only et ne deviennent jamais des soumissions isolées. La carte **Exigences** est déplacée en bas de la colonne droite.
#### `pre.014-delta-fix-002` — stabilité du dispatch et validation réseau desktop
Deux essais initiaux de `Create -> Mint — NFT` montrent un arrêt brutal après confirmation de `Create`, pendant lhydratation canonique. Le dispatch des onze campagnes est ensuite boxé avant son attente par Tauri et reçoit des marqueurs `campaign_start`, `campaign_completed` et `campaign_failed`. Le test historique des contrôles frontend est réaligné sur la nouvelle UI.
Le rerun du 9 août 2026 ferme cette régression : les onze campagnes Metaplex atteignent toutes `campaign_completed`, sans `campaign_failed` ni fichier `error*.jsonl` non vide. Maintenance conserve `3 confirmed / 4 unavailable` et Use `2 confirmed / 1 unavailable`, conformément aux qualifications de `pre.013`. Les 144 tests desktop sont verts.
#### `pre.014-delta-fix-003` — consolidation structurelle et UX
Les deux modules Metaplex desktop sont fusionnés dans `demo_execution_metadata_metaplex_token_metadata.rs`, afin de suivre le même modèle de module unique que Token-2022 Token Metadata et Solana Program Metadata. Les commandes Tauri et les contrats TS-RS restent inchangés.
Les blocs **Préflight et opérations**, **Simulation et confirmation** et **Postconditions et preuves** deviennent trois accordéons Bootstrap utilisant les JsonViewer existants. **Exigences** reste sous ces résultats et le journal reste en pleine largeur.
La validation finale confirme `cargo fmt --all`, `cargo check --workspace`, `cargo clippy --all-targets`, laudit workspace et les 144 tests desktop. La disposition est également validée visuellement dans Tauri. `pre.014` est clôturée ; aucune nouvelle campagne Devnet nest requise avant `pre.015`.
### `0.4.8-pre.015` — stockage, matrices et réconciliation transversale ### `0.4.8-pre.015` — stockage, matrices et réconciliation transversale
- stockage si de nouveaux contrats persistants sont requis ; Objectif : réconcilier les trois surfaces Metadata désormais implémentées et validées avant la clôture documentaire de `pre.016`, sans ouvrir de nouvelle capacité réseau.
- exports, registres, matrices, nomenclature IDL et guides ;
- retrait des statuts `reserved` uniquement lorsque limplémentation correspondante est réelle. - auditer dabord les besoins de stockage réellement introduits par Solana Program Metadata, Token-2022 Token Metadata et Metaplex Token Metadata ; ne modifier le schéma que si un contrat persistant manque effectivement ;
- recouper les matrices de comptes, instructions, exécution, pipeline et validation Devnet avec les inventaires compilés ;
- vérifier les registres runtime de décodeurs et matérialiseurs, les exports publics et les tests dAPI externe ;
- réconcilier `docs/IDL_AUDIT.md`, `docs/IDL_TO_KB_LIB_NOMENCLATURE.md` et les statuts `reserved` avec limplémentation réelle ;
- vérifier README, USAGE, TODO et changelogs des crates concernées uniquement lorsque leur état courant est faux ou incomplet ;
- conserver les preuves Devnet existantes comme références : aucune nouvelle campagne réseau nest requise sauf régression détectée par la réconciliation.
Critère : aucune divergence non expliquée entre code, matrices, registres, IDL, stockage et documentation active ; les éventuelles dettes restantes sont explicitement reportées avant `pre.016`.
### `0.4.8-pre.016` — clôture obligatoire ### `0.4.8-pre.016` — clôture obligatoire
@@ -427,83 +545,3 @@ python3 scripts/audit_rust_workspace_rules.py
cargo test --workspace cargo test --workspace
cargo tauri dev -c kb-app-demo-desktop/tauri.conf.json cargo tauri dev -c kb-app-demo-desktop/tauri.conf.json
``` ```
### Consolidation `pre.013-delta-fix-013`
La campagne SFT `Create -> Mint` est confirmée avec preuves machine-readable complètes : `Create` signature `2s81m6k9HYDLmCy7oXKaqBvtb62iyX4bJmij2f2VjHuoRQBBA6YqRuAsjyaM7JCaJXasbU3WCfiko91cZcDLLdxV` slot `481908028`, puis `Mint` signature `4pAemhEJewuLAeqLkqwzPvVmsCRbkyLiN6uAUWBA5RMsofaEDt5Xr8YGiBEvLWQM7Qvxnd81mmCHpandmfmWuyAm` slot `481908039`. `Mint` fait passer supply et amount de `0` à `10`, et les deux étapes terminent hydratation canonique, extraction Core, replay, matérialisation et idempotence. La matrice Devnet passe en version 3 afin de conserver un bundle de preuves indépendant par famille ; `Create` et `Mint` sont donc confirmés sur NFT et SFT sans fusionner leurs signatures ou slots. Fungible, collection et pNFT restent les trois variantes `Create -> Mint` à exécuter avant les fixtures spécialisées Print/Burn, collection parent-membre, pNFT, uses et escrow.
### Consolidation `pre.013-delta-fix-014`
La campagne fungible `Create -> Mint` est confirmée avec un mint à 9 décimales : `Create` signature `3J1Ebq9DVkupVFQmqQ84QaNeztwMuRwbtXj7CiyUWwZPj4KCAJpKLAbPvpYss5cdiY33foXXScWWZE98H7tvbZed` slot `481910724`, puis `Mint` signature `2GKiY9foLmioYtUHttA2D6nCSmsftRL1JDKxENHmCnb5w38FGSxwBiRNMfF7mH8htv3G45irmge6DVtr6VN359j2` slot `481910734`. `Mint` fait passer supply et amount de `0` à `1_000_000_000`; les deux étapes terminent le pipeline de preuve complet. La matrice v3 contient désormais des bundles indépendants `nft`, `sft` et `fungible` pour `Create` et `Mint`. Collection et pNFT restent les deux dernières variantes `Create -> Mint` avant le passage aux fixtures spécialisées Print/Burn, collection parent-membre, pNFT lifecycle, uses et escrow.
### État après `pre.013-delta-fix-015`
Les campagnes `Create -> Mint` NFT, SFT, fungible et collection sont maintenant confirmées sur Devnet avec bundles de preuve séparés par famille. Collection valide explicitement metadata + Master Edition, deux snapshots stateful par étape et la transition supply/amount `0 -> 1`. La variante `programmable_nft` est la dernière à exécuter pour fermer ce cycle multi-famille ; elle devra en plus prouver l'apparition du token record après `Mint`. Une fois cette variante qualifiée, le travail `pre.013` bascule vers les fixtures spécialisées collection parent-membre, Print/Burn/éditions, lifecycle pNFT, uses et escrow.
### État après `pre.013-delta-fix-016`
La première tentative `programmable_nft` atteint et confirme `Mint` (`Gkg5A6ehuSzkU1N318kYoRHm5RdxV2yBRpaK9Gwyt6vN1ToYwAS6Vzpk2rQYWS9NJz1rnUpypLCn7DqWFzQzxFc`, slot `481913765`) puis échoue uniquement sur une postcondition SPL commune qui exigeait `Initialized=1`. LATA observé possède le mint, lowner et lamount exacts mais `state=2`, soit `Frozen`, état attendu pour la couche SPL dun pNFT. `fix-016` distingue donc létat SPL par famille et conserve désormais `tokenAccountStateBeforeMint/AfterMint` dans la preuve machine-readable. Le bundle pNFT nest pas encore promu : un rerun complet doit retourner `ok`, valider le Token Record et démontrer explicitement `Initialized(1) -> Frozen(2)`.
## Avancement `pre.013-delta-fix-017`
- `Create -> Mint` est qualifié sur les cinq familles : NFT, SFT, fungible, collection et pNFT.
- `Create` et `Mint` restent les deux seules opérations Metaplex `confirmed`; chacune possède cinq bundles `familyEvidence` complets.
- le premier lot spécialisé suivant est la collection parent-membre `Verify -> Unverify`; son runner est préparé mais reste sans preuve réseau tant que le test Devnet opt-in na pas terminé `ok`.
- la postcondition de collection utilise létat autoritatif du programme (`verified` et `CollectionDetails::V1.size`) et ne réintroduit pas `SetCollectionSize`.
- après qualification de `Verify`/`Unverify`, poursuivre vers les fixtures `Print/Burn`, puis les cycles pNFT delegate/revoke/lock/unlock/transfer, avant uses/escrow/maintenance.
### Avancement `pre.013-delta-fix-018`
La première exécution collection spécialisée atteint et confirme `Verify(CollectionV1)` au slot `481931061`, puis sarrête pendant le decode replay. Lautorité étant également fee payer, ses privilèges Core globaux sont `signer + writable`; le décodeur exigeait à tort légalité locale `signer + readonly`. Le correctif applique à `Verify`/`Unverify` la règle déjà validée pour `Create`/`Mint` : seuls les privilèges requis constituent des minima. Il corrige en outre larité de `Unverify` de 8 à 7 positions conformément au wrapper courant.
Aucune promotion réseau nest faite à partir de cette preuve partielle. Le prochain jalon reste le rerun intégral `Verify -> Unverify`, avec postconditions membre `false -> true -> false` et taille parent `0 -> 1 -> 0`, avant de passer aux fixtures Print/Burn.
### Avancement `pre.013-delta-fix-019`
Le rerun collection est complètement qualifié : `Verify` (`39JwUoxF2WWYWvEEWzFxSmKZVwoqXD1wH78sgGaoXv2vZKYUsXDNNnKikeuyQcfCqDftEMHSKFDHnTeSQ3bRpFcJ`, slot `481936730`) puis `Unverify` (`4Kfv42BGZtuBEwqTie5RWwdQ772hyxKiU9d3F3gfRnwVNrxCs2iZMvwL17Rwmuyr3XCVoHdaJVgaqCwv8y7bKoZY`, slot `481936742`) terminent le pipeline complet et prouvent `verified false -> true -> false` ainsi que `size 0 -> 1 -> 0`. La matrice Devnet passe à 4 opérations courantes `confirmed` (`Create`, `Mint`, `Verify`, `Unverify`) et 16 `not_run`.
Le prochain sous-lot est `Print -> Burn` : master NFT imprimable `Limited(1)`, mint dédition frais, Edition Marker stateful et support explicite dun signataire supplémentaire déjà déclaré par la requête. La postcondition `Burn` est bornée par la fixture fraîche : supply imprimée `1 -> 0`, master supply `1 -> 0`, fermeture du token account et de l'Edition imprimée, fermeture sémantique de Metadata et fermeture de lEdition Marker redevenu vide. Les preuves réseau restent fermées : ni `Print` ni `Burn` ne sont promus avant un test Devnet complet. Après cette campagne, poursuivre vers le lifecycle pNFT courant (`Delegate/Revoke/Lock/Unlock/Transfer/Burn` selon les préconditions exactes), puis uses, escrow et opérations de maintenance restantes.
### Avancement `pre.013-delta-fix-020`
La première exécution réelle de `Print -> Burn` sarrête pendant la simulation exacte de `Print`, avant toute soumission. Le programme retourne `MetadataError::NotEnoughTokens` (`0x20`). Le réaudit du chemin courant identifie la cause dans la fixture `fix-019` : elle précréait le mint dédition **et son ATA vide**. Or `Print` exige `amount=1` lorsquun token account dédition existe déjà ; lorsquils sont absents, il crée lui-même le mint, crée lATA et frappe exactement un token. `fix-020` conserve donc uniquement le keypair signataire frais et exige labsence on-chain du mint et de lATA avant `Print`.
Le runner valide séparément lATA du master à `amount=1` avant simulation afin quun défaut de précondition master produise un diagnostic distinct. Il réembarque également le contrat stateful public `EditionMarker` et ajoute une régression dAPI externe, car le workspace local ayant reçu `fix-019` contenait le nouveau scénario mais pas ce variant `kb-pipeline`. `Print` et `Burn` restent tous deux `not_run`; la matrice demeure à 4 opérations `confirmed` et 16 `not_run` jusquau rerun complet.
### Avancement `pre.013-delta-fix-021`
Le compte `Edition` imprimé est désormais décodé avec les allocations Metaplex bornées : 41 octets sérialisés, 42 octets compacts ou 241 octets historiques avec padding nul. Le rerun franchit alors la validation post-exécution de `Print`, exécute `Burn` et atteint sa dernière postcondition locale. En parallèle, `cargo check --workspace` révèle que la voie multi-signataires reste non-`Send` : le `Signer + Sync` public est effacé dans une `Vec<&dyn Signer>` conservée par le futur. `kb-lib` révèle aussi un seul test `Print` incorrect qui déclare readonly un `edition_token_record` réellement writable.
### Avancement `pre.013-delta-fix-022`
Le prochain rerun part désormais d'un contrat plus exact : la conversion vers `&dyn Signer` est enfermée dans une fonction synchrone de signature, la régression `Print` conserve le writable obligatoire du token record, et la fermeture Metadata n'est plus assimilée à une absence RPC stricte. Pour `MetadataV1`, le programme peut conserver les fees résiduels et laisser un compte d'un octet `0x00` `Uninitialized`; seul ce tombstone exact ou l'absence complète est accepté comme fermeture. Le token account imprimé, l'Edition et l'Edition Marker vide doivent toujours disparaître. `Print` et `Burn` restent `not_run` jusqu'à la sortie complète `METAPLEX_PRINT_BURN_*` avec preuves opérateur.
## Consolidation `pre.013-delta-fix-023`
Le rerun `fix-022` ferme entièrement `Print -> Burn`. `Print` est simulé au slot `482087795`, confirmé par `REHAxUZrSdKDj8F8k27Amrg7hwuJFhVfstjc9xjn2XGfTX38fH4tFwPeNrF5zeMN5pc21PpUPSgaKXMMfTFd7TE` au slot `482087799`, puis `Burn` est simulé au slot `482087813` et confirmé par `4cNUxGGSWifuAtqdPJAtM3Cjpi4JATitRDqrvEAe85NLpMWcgWmC27bsfjNUmK6sEBDovNPtkPV3wDH1LpfGbhd1` au slot `482087818`. `Print` matérialise une instruction et cinq snapshots ; `Burn` une instruction et deux snapshots. Les deux étapes terminent hydratation canonique, extraction Core, replay et seconde passe idempotente. La supply du master suit `0 -> 1 -> 0`, le mint imprimé `0 -> 1 -> 0`, lEdition et lEdition Marker sont créés puis fermés, et Metadata termine sous le tombstone fee exact dun octet `0x00`. La matrice v3 passe donc à 6 opérations `confirmed` et 14 `not_run`.
Le lot prépare ensuite un parcours pNFT unique et cohérent couvrant cinq opérations encore non qualifiées : `Delegate(StakingV1) -> Lock -> Unlock -> Revoke(StakingV1) -> Delegate(TransferV1) -> Transfer`. Le premier delegate prouve le cycle de lock du Token Record ; le second prouve le transfert délégué vers une ATA et un Token Record destination frais. Le transfert doit vider lATA source, remplir lATA destination, conserver les comptes SPL pNFT gelés et supprimer la délégation sur le Token Record destination. Aucun Rule Set nest fabriqué artificiellement : la fixture pNFT sans rule set suffit pour qualifier les wrappers et les transitions de délégation. `Migrate` reste séparé car il concerne la conversion dun actif legacy vers pNFT et ne peut pas être exercé sur cette fixture déjà programmable.
### Avancement `pre.013-delta-fix-029`
Le cycle pNFT est maintenant qualifié avec six preuves transactionnelles complètes : deux variantes `Delegate` (`StakingV1`, `TransferV1`) et les opérations `Lock`, `Unlock`, `Revoke`, `Transfer`. Les cinq opérations canoniques correspondantes passent à `confirmed` sur `programmable_nft`. Avec `Create`, `Mint`, `Verify`, `Unverify`, `Print` et `Burn`, la matrice compte désormais **11/20 opérations confirmées**, **1 `unavailable` (`Use`)** et **8 `not_run`**.
Le probe `Use` est désormais exécuté et classé : Devnet rejette linstruction courante de discriminant 51 avec `InvalidInstructionData`, aucune transaction nest soumise et lopération passe à `unavailable`. Le SDK et le runtime restent donc explicitement divergents sur cette surface. `Migrate` est par ailleurs explicitement `Removed` dans le routeur courant et devra être classé avec la même discipline de preuve.
Le prochain jalon est le graphe escrow (`CreateEscrowAccount -> TransferOutOfEscrow -> CloseEscrowAccount`) avec un NFT contrôlé, un Token Owned Escrow et un token attribut SPL contrôlé. Ensuite réconcilier `Update`, `Collect`, `Resize`, `CloseAccounts` et `Migrate` avec leur disponibilité réelle avant consolidation de `pre.013`.
### Avancement `pre.013-delta-fix-033`
Token Owned Escrow est entièrement qualifié sur Devnet. Les trois transactions Metaplex `CreateEscrowAccount`, `TransferOutOfEscrow`, `CloseEscrowAccount` sont confirmées avec replay et matérialisation complets ; lunité SPL déposée dans lATA du PDA revient exactement à lopérateur, lATA vide est fermée, puis lescrow est fermé sans modifier le NFT parent. La matrice est désormais à **14 `confirmed`, 1 `unavailable`, 5 `not_run`**.
La première tentative de la dernière campagne montre que les créations actuelles sont déjà au layout cible : après un `Update` validé en interne, `Resize` atteint `IX: Resize` mais la simulation retourne `AlreadyResized(201)` et aucune soumission na lieu. Une réussite positive exigerait un compte legacy sous-dimensionné contrôlé, non constructible par la fixture courante. `Resize` passe donc à `unavailable`, portant la matrice à **14 `confirmed`, 2 `unavailable`, 4 `not_run`**.
Le rerun final soumet uniquement `Update`. `Resize`, `Migrate`, `Collect` et `CloseAccounts` sont des probes simulation-only, avec refus attendus `AlreadyResized(201)`, `Removed(75)`, `UpdateAuthorityIncorrect(7)` et `InvalidCloseAuthority(188)`. Si le bundle est conforme, la matrice pourra se fermer à **15 `confirmed`, 5 `unavailable`, 0 `not_run`** puis passer à la consolidation finale de `pre.013`.
### Clôture de la matrice Metaplex `pre.013-delta-fix-036`
La dernière campagne maintenance est qualifiée. `Update` passe à `confirmed` sur NFT et les probes `Resize`, `Migrate`, `Collect`, `CloseAccounts` produisent exactement les refus runtime attendus sans soumission. Avec `Use` déjà classé, la matrice des 20 opérations courantes atteint **15 `confirmed`, 5 `unavailable`, 0 `not_run`**.
Les cinq indisponibilités sont distinctes et ne doivent pas être amalgamées : `Use` diverge du SDK courant sur Devnet ; `Resize` exige un compte legacy sous-dimensionné contrôlé ; `Migrate` est retiré du runtime ; `Collect` et `CloseAccounts` exigent des autorités Metaplex fixes hors du profil opérateur contrôlé.
Le prochain delta de `pre.013` doit être une **consolidation**, pas une nouvelle campagne fonctionnelle : audit des statuts finaux, réconciliation des rapports/TODO/changelogs et vérification que toutes les matrices et guides reflètent 0 ligne `not_run`. Après cette consolidation et les tests de conformité locaux, le développement peut passer à `0.4.8-pre.014`.

View File

@@ -0,0 +1,209 @@
<!-- file: docs/validation/V0_4_8_PRE_014_DESKTOP_METADATA_VALIDATION_REPORT.md -->
<!-- version: 4 -->
# Validation desktop Metadata `0.4.8-pre.014`
## 1. Objet
Ce rapport consolide la première validation réelle du panneau `demo_execution_metadata` après louverture de `0.4.8-pre.014`, puis motive le correctif de câblage Metaplex du delta suivant.
Il ne remplace pas les matrices réseau spécialisées. Il qualifie uniquement lintégration desktop et la correspondance entre lUI Tauri et les campagnes réutilisables déjà validées.
## 2. Contrôles locaux
Le 8 août 2026, après application du premier delta `pre.014`, les contrôles suivants réussissent :
```text
cargo fmt --all OK
cargo check --workspace OK
cargo clippy --all-targets OK, aucun warning
scripts/audit_rust_workspace_rules.py clean
kb-pipeline-demo-scenarios 105 unitaires + 1 CLI + 1 API externe
kb-app-demo-desktop 139 unitaires
cargo tauri dev démarrage OK
```
La validation visuelle confirme :
- trois accordéons distincts pour Metaplex Token Metadata, Token-2022 Token Metadata et Solana Program Metadata ;
- synchronisation du profil sélectionné entre les trois domaines ;
- journal dexécution placé sous la grille et utilisant toute la largeur disponible.
## 3. Token-2022 Token Metadata
Les logs fournis montrent une campagne desktop complète sur Devnet, sans entrée dans les fichiers `error*.jsonl`.
| Opération | Signature | Slot |
|-------------------|--------------------------------------------------------------------------------------------|------------:|
| `Initialize` | `23aXutFc25Ncn4XPSGdaaEzvUpe9s57gYunmMq1asmFRK9xe1tAYVCnV3dePUn2a7qqq51JDsPKp5ep5d1DLypNv` | `482188064` |
| `UpdateField` | `Uws9Laene33LxYet4562LKXMkKjAhW5FPweQSnFJv7NG6s4u7LqB7zbppQgQgaNFE52d9UVZKLmjZoK9zb4xdHK` | `482188079` |
| `Emit` | `2uNggBbSvvj7Cqdddt1KtFgFhTJ9jsyvobeGUaAU6kPNY9o4hJ6BVHEhpELe1sRSyrMrgbeGLoRiWAMU2UrisMcE` | `482188091` |
| `RemoveKey` | `3xUUnjQvWzSkMQPe2VFDyYX14K3STxBzFjzWEawrK28UsoJzXTPYA61nzkL5JvLwCfhNRg7YpBcsSSjhTJqtqNYT` | `482188102` |
| `UpdateAuthority` | `2U9zrLL9NusrhpW9Dx15SqZ8c4d6zbcgSyM4hBcewr1Ar7JwLYwvBduLKs4HKEgZP9i5YQQ9cyTC977mjJGLWqXW` | `482188113` |
Les traces `kb-pipeline` et `kb-lib` montrent le décodage Token-2022, la matérialisation `materializer.metadata.token_2022` et la seconde passe de replay/idempotence. La fixture a été préparée auparavant par la signature `LNwzGfvCgZdumbYXfAecCxU6hfHYGa6XRwC7E71jc4JTDUj5QyVBVV5Sd9KLWmX2t72XfKBiYUEV6WYCvPqC4wd`, slot `482188053`.
**Statut desktop : validé.**
## 4. Solana Program Metadata
La campagne desktop `ProgM6…` traverse les deux parcours complets après préparation des PDA frais `buffer=6bs5XpPsNUqw8FatFJTJu81ZAUBCvxmyxptNyUY4RnDU` et `metadata=7fTTM3KCtFMjxmTaWa6xJ3nSbK9Yi7MdVXP3dSa8ahPB`.
Les neuf opérations atteignent toutes létat `Confirmed` dans les contrôles post-exécution :
```text
Buffer : Allocate -> Extend -> Write -> SetAuthority -> Trim -> Close
Metadata : Initialize -> SetData -> SetImmutable
```
Signatures observées :
```text
Allocate Je6SLx7wfNqEJ8Huo64r177Sm9bda4uFXUPwuxfZG89jvb1zKMKLnzTJEL5DFHxftMq38FsLNF3TZ14kaWSZ7Hb
Extend LYgf8ac4CKYdY2xkw9bCS8J9cvkphWA8ueCUJ5CBKZ11jEdS2NZLykF4jwmSA5AnVYWxzNABeXhGV4d1dFvzMnM
Write 4p7XEXeGgWQjJkWTfrBB5rL9XwDadxzoCPreLfaB3fBGACyrPqZYmnBGqVYVgyR9zDZNxuKhimEwMtmiGdJseKa2
SetAuthority 5kPi3eBqbq92kV5ZFWPur9afAokGxhgr84PPQh7s5btyQYayC3TDuaH9TCcspxbd4YEYDD4icFYnSWX2Ej9XdLgA
Trim 5YG7Yp6x1waZZKoRjb37q7QUNJL5CMEUgu8gs7ehDwHUx1TXvV5BaeUJFT1Pb58z4hSo4woey2qaKQggAzaNtYEG
Close 2jyZzegSM2v7LJ15nF3r9zXsLXd7ZDsWsCEWHLXQdoBrLkrbuSuwVrGx1jTqYejw195FVhWHeJQYY6BaN98s5R6
Initialize 2kzvpRi8KwRhaQPiwtR8PCQGTuQ3e3kUNSi7EVsMXqJzhQkZanjNwxsL2sGeB8Xs56N22TaaYbS6LJimQpHZWk6J
SetData 3WZHzYno9M6uHLMiXsNBtBHoN3yAp7ftd3pSNWpuj2jATk1g29nvUccgUu6wuRijv9oBo2Vi1dHKury2DFfDQ1ib
SetImmutable XTAj5NNVXRkhKe4DxsXGm2xtxocrwHwkkpEun4ib6h4qs6DX5f2K6gfgQ2jppkTWoC83MyoPgrpvdKes1b72gbJ
```
**Statut desktop : validé.**
## 5. Metaplex Token Metadata : écart constaté
Les logs de la même session ne montrent pas une campagne Metaplex spécialisée. Le desktop prépare uniquement une fixture classique avec :
```text
operation = metadata.metaplex_token_metadata.prepare_native_mint_and_ata
signature = 5YdSQHZ5T3VV8zefmQS4GVHNXPbvCijiLyHdgwvqcmGMMpobAJcM4HvdeQ7vex9qDqcYtAVUtj4U3bDCeQ2Hfzvv
slot = 482188642
```
Aucune séquence qualifiée `Create -> Mint`, collection, Print/Burn, lifecycle pNFT, escrow, maintenance ou Use ne suit cette préparation avant la fin des logs.
La cause est un écart de câblage desktop : le panneau utilisait encore le workflow générique `scénario -> étape -> intent JSON`, alors que `pre.013` a qualifié des campagnes spécialisées cohérentes et indivisibles.
## 6. Correctif `pre.014-delta-fix-001`
Le correctif conserve les scénarios synthétiques pour linspection de contrats mais remplace le parcours Devnet principal par 11 campagnes spécialisées :
1. `Create -> Mint` NFT ;
2. `Create -> Mint` SFT ;
3. `Create -> Mint` fungible ;
4. `Create -> Mint` collection ;
5. `Create -> Mint` pNFT ;
6. collection `Verify -> Unverify` ;
7. `Print -> Burn` ;
8. lifecycle pNFT ;
9. Token Owned Escrow ;
10. maintenance ;
11. probe Use.
Maintenance et Use conservent les opérations `unavailable` comme probes simulation-only ; elles ne sont pas converties en soumissions isolées.
La carte **Exigences** est également déplacée en bas de la colonne droite, après **Postconditions et preuves**. Le journal reste en pleine largeur sous la grille.
## 7. Validation de `fix-001`
Après application de `fix-001` :
- les 11 campagnes Metaplex sont bien exposées par le nouveau contrat desktop ;
- les contrôles statiques `fmt`, `check`, Clippy et audit workspace sont propres ;
- `kb-pipeline-demo-scenarios` conserve 105 tests unitaires + 1 CLI + 1 API externe réussis ;
- `kb-app-demo-desktop` exécute 144 tests, avec 143 réussites et un échec du test historique qui cherche encore les anciens IDs de contrôles supprimés ;
- deux tentatives réelles de `Create -> Mint — NFT` provoquent un arrêt brutal du processus pendant la post-validation du `Create`.
## 8. Correctif `pre.014-delta-fix-002`
Après application de `fix-001`, deux essais successifs de `Create -> Mint — NFT` provoquent un arrêt brutal de l'application au même endroit. Le second essai est effectué après purge des logs.
Le journal réseau prouve que la fixture SPL classique est créée, puis que l'opération Metaplex `Create` est simulée, envoyée et confirmée :
```text
fixture signature : 64kLtKRiCewywr1CdNbcznHZS7PtNnfh5vxzZLte2VoUexkoyGtRdVozMxsATpP97Sp1fjv2fmPwGUj9hM6VFH8d
fixture slot : 482323101
Create signature : NAXZephC3aPqe8KA4Ly81o56xkhUEEMMLdZKnYEqiifvRGuQmf997nM29EtWZESyC97qu75v4UzyRWt4aqxRN1d
Create slot : 482323110
```
Le dernier événement écrit est le lancement de l'hydratation canonique de cette signature puis l'envoi de `getTransaction`. Aucun panic Rust, aucune erreur applicative structurée et aucune erreur JSONL ne sont enregistrés avant la disparition du processus. Le défaut n'est donc pas classé comme un échec Metaplex de la transaction : il appartient à l'intégration desktop/runtime du nouveau dispatch de campagnes.
`fix-002` :
- conserve les campagnes réutilisables intactes ;
- boxe explicitement le futur du dispatch Metaplex avant l'attente Tauri, afin d'éviter de conserver le gros state machine des onze branches directement dans le futur de commande ;
- journalise `campaign_start`, `campaign_completed` et `campaign_failed` ;
- réaligne le test frontend historique sur les contrôles de campagne qualifiés introduits par `fix-001`.
La validation Tauri doit reprendre par `Create -> Mint — NFT`. Si un arrêt brutal subsiste, les nouveaux marqueurs et la dernière étape réseau permettront de distinguer un défaut du runtime desktop d'un défaut d'hydratation HTTP.
## 9. Validation réelle de `fix-002`
La validation locale du 9 août 2026 est propre :
```text
cargo fmt --all OK
cargo check --workspace OK
cargo clippy --all-targets OK, aucun warning
scripts/audit_rust_workspace_rules.py clean
kb-app-demo-desktop 144 tests unitaires, tous OK
cargo tauri dev démarrage OK
```
Le bundle de logs du second essai confirme que le boxing du dispatch Metaplex supprime larrêt brutal observé avec `fix-001`. Les onze campagnes exposées par le desktop ont toutes un marqueur `campaign_start` suivi dun marqueur `campaign_completed`, sans `campaign_failed` :
| Campagne | Exécutions/probes projetés | Statut desktop |
|--------------------------------|---------------------------:|---------------------------------------------|
| `create_mint_nft` | 2 | terminé |
| `create_mint_sft` | 2 | terminé |
| `create_mint_fungible` | 2 | terminé |
| `create_mint_collection` | 2 | terminé |
| `create_mint_programmable_nft` | 2 | terminé |
| `collection_verify` | 6 | terminé |
| `print_burn` | 4 | terminé |
| `pnft_lifecycle` | 8 | terminé |
| `escrow` | 7 | terminé |
| `maintenance` | 7 | terminé, dont 4 indisponibilités qualifiées |
| `use_probe` | 3 | terminé, dont 1 indisponibilité qualifiée |
Les journaux UI confirment notamment `maintenance : confirmed=3, unavailable=4, evidence=7` et `use_probe : confirmed=2, unavailable=1, evidence=3`. Les fichiers `error*.jsonl` du bundle fourni sont tous vides.
**Statut desktop Metaplex : validé pour les onze campagnes qualifiées.**
## 10. Consolidation structurelle `fix-003`
Le correctif suivant ne change aucune campagne réseau. Il simplifie seulement le desktop :
- `demo_execution_metadata_metaplex_campaign.rs` est fusionné dans `demo_execution_metadata_metaplex_token_metadata.rs` ;
- les commandes Tauri et les contrats TS-RS restent inchangés ;
- Metaplex suit désormais le même modèle de module unique que Token-2022 Token Metadata et Solana Program Metadata ;
- les trois blocs de résultats deviennent un accordéon Bootstrap commun, sur le modèle de `demo_execution_spl` ;
- la carte **Exigences** reste sous laccordéon dans la colonne droite ;
- le journal dexécution reste pleine largeur sous la grille.
Après application, la validation requise est locale (`fmt`, `check`, Clippy, audit, 144 tests desktop) puis visuelle dans Tauri pour louverture/fermeture des trois JsonViewer. Aucune nouvelle campagne Devnet nest requise pour requalifier Metaplex.
## 11. Clôture de `pre.014`
La validation finale confirme que `fix-003` ne réintroduit aucune régression :
```text
cargo fmt --all OK
cargo check --workspace OK
cargo clippy --all-targets OK, aucun warning
scripts/audit_rust_workspace_rules.py clean
kb-app-demo-desktop 144 tests unitaires, tous OK
```
La vérification visuelle Tauri confirme également :
- les trois accordéons de résultats souvrent et se ferment correctement ;
- les JsonViewer restent lisibles dans les accordéons ;
- la carte **Exigences** reste sous les résultats dans la colonne droite ;
- le journal dexécution reste en pleine largeur sous la grille ;
- les trois domaines Metaplex Token Metadata, Token-2022 Token Metadata et Solana Program Metadata restent distincts.
Les campagnes réseau ayant déjà été qualifiées avant le refactor structurel de `fix-003`, aucun rerun Devnet supplémentaire nest requis. **Statut de `0.4.8-pre.014` : terminé et validé.**

View File

@@ -1,8 +1,31 @@
<!-- file: kb-app-demo-desktop/CHANGELOG.md --> <!-- file: kb-app-demo-desktop/CHANGELOG.md -->
<!-- version: 33 --> <!-- version: 38 -->
# CHANGELOG — kb-app-demo-desktop # CHANGELOG — kb-app-demo-desktop
## 0.4.8-pre.014
- `pre.014-delta-fix-004` clôture la finalisation desktop Metadata après validation de `fix-003` : formatage, `cargo check --workspace`, Clippy sans warning, audit workspace et 144 tests desktop sont propres ; la disposition finale des trois accordéons de résultats, de la carte **Exigences** et du journal pleine largeur est validée visuellement ;
- réconcilie le rapport et le TODO avec cet état final, sans nouvelle campagne réseau ni changement fonctionnel ;
- `pre.014-delta-fix-003` consolide lintégration Metadata après validation réelle de `fix-002` : les 11 campagnes Metaplex qualifiées atteignent toutes `campaign_completed`, les campagnes Maintenance et Use conservent leurs probes indisponibles sans soumission et aucun fichier `error*.jsonl` fourni nest non vide ;
- fusionne `demo_execution_metadata_metaplex_campaign.rs` dans `demo_execution_metadata_metaplex_token_metadata.rs` afin que Metaplex suive le même modèle de module unique que Token-2022 Token Metadata et Solana Program Metadata, sans modifier les commandes Tauri ni les bindings TS-RS ;
- transforme les blocs `Préflight et opérations`, `Simulation et confirmation` et `Postconditions et preuves` en accordéons Bootstrap, sur le modèle de `demo_execution_spl`, tout en conservant `Exigences` sous les détails et le journal dexécution en pleine largeur ;
- `pre.014-delta-fix-002` réaligne le test de contrôles Metaplex sur la nouvelle UI qualifiée (`metadataMetaplexCampaignSelect` + confirmation + exécution) et retire les assertions sur les anciens contrôles Devnet supprimés par `fix-001` ;
- après deux crashs reproductibles du desktop pendant `Create -> Mint — NFT`, les logs montrent `Create` simulé, soumis et confirmé, puis un arrêt brutal pendant le `getTransaction` de l'hydratation canonique, sans panic Rust ni erreur JSONL ;
- boxe explicitement le futur volumineux du dispatch des 11 campagnes Metaplex avant son exécution dans le runtime Tauri afin de réduire l'état async conservé par la commande desktop et ajoute des marqueurs backend `campaign_start`, `campaign_completed` et `campaign_failed` ; cette correction reste volontairement limitée à l'adaptateur desktop et ne modifie aucune campagne Devnet déjà qualifiée ;
- `pre.014-delta-fix-001` remplace le chemin Devnet Metaplex générique du panneau par 11 campagnes qualifiées réutilisant directement les runners spécialisés de `kb-pipeline-demo-scenarios` : cinq `Create -> Mint`, collection, Print/Burn, lifecycle pNFT, escrow, maintenance et probe Use ;
- conserve les scénarios Metaplex synthétiques pour linspection de contrats, mais ne présente plus `scenario + step + intent JSON` comme parcours Devnet principal ;
- confine `Use`, `Resize`, `Migrate`, `Collect` et `CloseAccounts` aux probes simulation-only de leurs campagnes qualifiées et ne les expose jamais comme soumissions isolées ;
- déplace la carte **Exigences** en bas de la colonne droite, après les postconditions, tout en conservant le journal dexécution en pleine largeur sous la grille ;
- enregistre la première validation Tauri de `pre.014` : `cargo check --workspace`, Clippy et laudit workspace sont propres, `kb-pipeline-demo-scenarios` réussit 105 tests unitaires + 1 CLI + 1 API externe, et `kb-app-demo-desktop` réussit 139 tests unitaires ;
- les logs fournis confirment lexécution desktop complète de Token-2022 Token Metadata et Solana Program Metadata sur Devnet, tandis que lancien parcours Metaplex sarrêtait après la seule préparation de fixture, défaut de câblage corrigé par ce delta ;
- ajoute le troisième sous-panneau **Token-2022 Token Metadata** dans `demo_execution_metadata` ;
- appelle directement la campagne réutilisable de cinq opérations `Initialize -> UpdateField -> Emit -> RemoveKey -> UpdateAuthority` sur un mint Token-2022 frais ;
- exige la préparation PostgreSQL, un profil Devnet autorisé et une confirmation explicite avant la fixture et les cinq mutations ;
- affiche la fixture, les simulations, confirmations, snapshots stateful, return data `Emit`, matérialisations et postconditions avec le JsonViewer commun ;
- synchronise le profil entre Metaplex, Token-2022 et Solana Program Metadata ;
- déplace le journal dexécution hors de la colonne gauche afin quil occupe toute la largeur du panneau.
## 0.4.8-pre.013 ## 0.4.8-pre.013
- `pre.013-delta-fix-022` restaure la compatibilité `Future + Send` de la commande Tauri Metaplex en corrigeant le runner partagé : l'effacement temporaire `Signer + Sync -> &dyn Signer` est limité à un helper synchrone de signature et n'est plus conservé au travers des `await` ; aucun assouplissement de la commande desktop n'est nécessaire ; - `pre.013-delta-fix-022` restaure la compatibilité `Future + Send` de la commande Tauri Metaplex en corrigeant le runner partagé : l'effacement temporaire `Signer + Sync -> &dyn Signer` est limité à un helper synchrone de signature et n'est plus conservé au travers des `await` ; aucun assouplissement de la commande desktop n'est nécessaire ;

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/README.md --> <!-- file: kb-app-demo-desktop/README.md -->
<!-- version: 10 --> <!-- version: 12 -->
# kb-app-demo-desktop # kb-app-demo-desktop
@@ -12,7 +12,7 @@
- diagnostics PostgreSQL ; - diagnostics PostgreSQL ;
- backfill, extraction Core et replay, avec enregistrement générique de Solana Program Metadata ; - backfill, extraction Core et replay, avec enregistrement générique de Solana Program Metadata ;
- démonstrations Solana Core, SPL Memo, SPL Token, ATA et Token-2022 ; - démonstrations Solana Core, SPL Memo, SPL Token, ATA et Token-2022 ;
- panneau Metadata avec parcours séparés Metaplex Token Metadata et Solana Program Metadata, préparation du profil et de la base, simulation RPC réelle, soumission explicitement confirmée et affichage des postconditions ; - panneau Metadata avec parcours séparés Metaplex Token Metadata, Token-2022 Token Metadata et Solana Program Metadata, préparation du profil et de la base, simulation RPC réelle, soumission explicitement confirmée et affichage des postconditions ;
- progression, annulation et restauration de létat des fenêtres. - progression, annulation et restauration de létat des fenêtres.
## Frontière architecturale ## Frontière architecturale
@@ -23,11 +23,15 @@ Les scénarios UI Devnet/Testnet restent dans le desktop. Des tests automatisés
## Exécution Metaplex Token Metadata ## Exécution Metaplex Token Metadata
Le panneau conserve les scénarios synthétiques et appelle directement le runner réutilisable de `kb-pipeline-demo-scenarios` pour les opérations Metaplex courantes. La simulation RPC est réelle ; la soumission exige deux autorisations distinctes dans la requête desktop. Après une confirmation, ladaptateur réutilise PostgreSQL pour conserver lhydratation canonique, lextraction Core, le replay, les matérialisations et la preuve didempotence produites par le runner. Le panneau conserve les scénarios synthétiques pour inspecter les contrats, mais son parcours Devnet principal expose désormais 11 campagnes qualifiées de `pre.013` et appelle directement leurs runners spécialisés de `kb-pipeline-demo-scenarios`. Les cinq variantes `Create -> Mint`, collection, Print/Burn, lifecycle pNFT et escrow soumettent uniquement les opérations autorisées ; Maintenance et Use conservent les opérations `unavailable` dans des probes simulation-only. Le desktop ne reconstruit ni les builders ni les postconditions et présente la fixture, les exécutions/probes et les preuves produites par les runners.
## Exécution Token-2022 Token Metadata
Le sous-panneau dédié appelle directement la campagne réutilisable de `kb-pipeline-demo-scenarios`; son parcours desktop complet a été confirmé sur Devnet pendant `pre.014`. Il crée un mint Token-2022 frais avec Metadata Pointer auto-référencé puis exécute, dans lordre, `Initialize`, `UpdateField`, `Emit`, `RemoveKey` et `UpdateAuthority`. Chaque étape passe par simulation, confirmation, replay canonique, matérialisation lorsque requise, lecture stateful et postcondition ; `Emit` conserve en plus sa return data validée.
## Exécution Solana Program Metadata ## Exécution Solana Program Metadata
Le même panneau expose séparément les deux parcours `Buffer` et `Metadata` de `ProgM6…`. La campagne complète crée deux PDA frais, confirme leurs préfinancements, exécute les neuf opérations stables, vérifie les lectures avant/après et affiche les snapshots matérialisés. La logique réseau reste entièrement dans `kb-pipeline-demo-scenarios` ; le desktop ne fait que sélectionner le profil, demander la confirmation et présenter les preuves. Le même panneau expose séparément les deux parcours `Buffer` et `Metadata` de `ProgM6…`; sa campagne desktop complète a été confirmée sur Devnet pendant `pre.014`. La campagne complète crée deux PDA frais, confirme leurs préfinancements, exécute les neuf opérations stables, vérifie les lectures avant/après et affiche les snapshots matérialisés. La logique réseau reste entièrement dans `kb-pipeline-demo-scenarios` ; le desktop ne fait que sélectionner le profil, demander la confirmation et présenter les preuves.
## Documentation ## Documentation

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/TODO.md --> <!-- file: kb-app-demo-desktop/TODO.md -->
<!-- version: 10 --> <!-- version: 14 -->
# TODO — kb-app-demo-desktop # TODO — kb-app-demo-desktop
@@ -8,16 +8,6 @@
- [ ] UX — empêcher les doubles déclenchements pendant une préparation, simulation ou soumission active. - [ ] UX — empêcher les doubles déclenchements pendant une préparation, simulation ou soumission active.
- [ ] Observabilité — afficher explicitement les comptes relus et les projections matérialisées. - [ ] Observabilité — afficher explicitement les comptes relus et les projections matérialisées.
## Validation Devnet Solana Program Metadata
- [ ] Exécuter la campagne desktop complète sur Devnet et archiver les signatures, confirmations, postconditions et snapshots dans la preuve `0.4.8`.
- [ ] Renseigner la matrice réseau uniquement après validation des neuf étapes.
## Validation Devnet complémentaire Metaplex Token Metadata
- [ ] Ajouter les parcours spécialisés Print/Burn, collection avancée et pNFT uniquement lorsque leurs fixtures réutilisables sont disponibles.
- [ ] Conserver simulation et soumission comme actions distinctes avec confirmation opérateur.
## Report conditionnel — registre ElGamal ## Report conditionnel — registre ElGamal
- [ ] Confirmer le déploiement et les preuves réseau avant de rendre le panneau exécutable. - [ ] Confirmer le déploiement et les preuves réseau avant de rendre le panneau exécutable.

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/USAGE.md --> <!-- file: kb-app-demo-desktop/USAGE.md -->
<!-- version: 10 --> <!-- version: 13 -->
# Utilisation de kb-app-demo-desktop # Utilisation de kb-app-demo-desktop
@@ -207,35 +207,32 @@ La simulation et lenvoi doivent rester explicitement distingués dans les req
### Exécution Metadata Devnet ### Exécution Metadata Devnet
Le backend distingue le module commun Metadata, Metaplex Token Metadata et Solana Program Metadata. Le backend distingue le module commun Metadata, Metaplex Token Metadata, Token-2022 Token Metadata et Solana Program Metadata. Metaplex est porté par un seul module Rust desktop, comme les deux autres domaines : ce module contient à la fois les contrats génériques conservés pour les tests/outils avancés et les campagnes Devnet qualifiées. Les commandes génériques Metaplex (`current_operations`, `operation_template`, `prepare_step`, `execute`) restent disponibles, mais le panneau Devnet nutilise plus ce chemin comme workflow opérateur principal.
Le panneau Metadata charge les opérations courantes depuis `demo_execution_metadata_metaplex_token_metadata_current_operations` et peut demander un modèle avec `demo_execution_metadata_metaplex_token_metadata_operation_template`. Le panneau charge désormais linventaire borné avec `demo_execution_metadata_metaplex_campaigns`, puis exécute une campagne complète avec `demo_execution_metadata_metaplex_execute_campaign`.
```typescript ```typescript
const operationJson = await invoke( const campaigns = await invoke(
"demo_execution_metadata_metaplex_token_metadata_operation_template", "demo_execution_metadata_metaplex_campaigns"
{ operation: "verify" }
); );
const summary = await invoke( const summary = await invoke(
"demo_execution_metadata_metaplex_token_metadata_execute", "demo_execution_metadata_metaplex_execute_campaign",
{ {
request: { request: {
profileName: "devnet", profileName: "devnet",
intentId: `desktop-metaplex-${Date.now()}`, campaignId: "create_mint_nft",
operationJson, operatorConfirmed: true
preflightReadsJson: "[]",
postconditionReadsJson: "[]",
submit: false,
operatorConfirmed: false
} }
} }
); );
console.log(summary); console.log(campaigns, summary);
``` ```
Les valeurs `<...>` dun modèle doivent être remplacées par des comptes réels avant exécution. `submit: true` exige simultanément `operatorConfirmed: true`. Le résultat distingue le plan, le préflight stateful, la simulation RPC, la signature éventuelle, la confirmation et les états avant/après. Après soumission confirmée, `evidenceJson` contient également lhydratation canonique, lextraction Core, le replay, la seconde passe didempotence, les matérialisations instructionnelles et le diagnostic post-exécution. Linventaire contient cinq variantes `Create -> Mint`, puis collection `Verify -> Unverify`, `Print -> Burn`, lifecycle pNFT, Token Owned Escrow, Maintenance et le probe Use. Maintenance et Use sont les seules campagnes mixtes/probe : `Use`, `Resize`, `Migrate`, `Collect` et `CloseAccounts` y restent simulation-only lorsquelles rencontrent leur indisponibilité qualifiée et ne sont jamais exposées comme boutons de soumission isolés.
Dans la colonne de résultats, `Préflight et opérations`, `Simulation et confirmation` et `Postconditions et preuves` sont présentés dans un accordéon commun. La carte `Exigences` reste sous cet accordéon et le journal dexécution reste sous la grille, sur toute la largeur.
### Diagnostics PostgreSQL ### Diagnostics PostgreSQL
@@ -290,7 +287,7 @@ Le nombre de tests évolue avec les surfaces raccordées ; la référence de val
## Panneau Exécution Metadata ## Panneau Exécution Metadata
Depuis la fenêtre principale, choisir **Exécution Devnet → Exécution Metadata**. Le panneau permet actuellement : Depuis la fenêtre principale, choisir **Exécution Devnet → Exécution Metadata**. Le panneau sépare Metaplex Token Metadata, Token-2022 Token Metadata et Solana Program Metadata. Il permet actuellement :
- de choisir un scénario synthétique ou un contrat de campagne Devnet ; - de choisir un scénario synthétique ou un contrat de campagne Devnet ;
- de sélectionner le profil Devnet ; - de sélectionner le profil Devnet ;
@@ -300,11 +297,30 @@ Depuis la fenêtre principale, choisir **Exécution Devnet → Exécution Metada
Un contrat marqué Devnet nest pas, à lui seul, une exécution réseau. Les champs `networkExecutionPerformed` et `networkEvidenceCollected` doivent rester faux tant quaucun appel RPC réel et aucune preuve correspondante nont été produits. Un contrat marqué Devnet nest pas, à lui seul, une exécution réseau. Les champs `networkExecutionPerformed` et `networkEvidenceCollected` doivent rester faux tant quaucun appel RPC réel et aucune preuve correspondante nont été produits.
Le panneau distingue préparation de létape, simulation exacte et soumission explicitement confirmée. Les lectures de postcondition et la matérialisation sont dérivées du scénario préparé. Les scénarios synthétiques restent disponibles pour inspecter les contrats sans réseau. Le parcours Devnet Metaplex sélectionne au contraire une campagne qualifiée complète ; fixture, simulation, soumission autorisée, replay, matérialisation, idempotence et postconditions restent la responsabilité du runner spécialisé.
### Préparation de la fixture Metaplex `Create` ### Campagnes Metaplex qualifiées
Le panneau desktop peut créer ou réutiliser un mint SPL classique Devnet à zéro décimale. Le préparateur dérive ensuite automatiquement les PDA `metadata` et `master edition` et génère un intent `Create` typé. Les valeurs entre chevrons des modèles génériques restent des placeholders non exécutables. Ouvrir **Metaplex Token Metadata -> Campagnes Devnet qualifiées**, sélectionner un profil persistant et préparer PostgreSQL. Le sélecteur présente 11 campagnes : cinq familles `Create -> Mint`, collection, Print/Burn, lifecycle pNFT, escrow, maintenance et Use. Après confirmation opérateur, le desktop appelle le runner correspondant et affiche :
- la fixture et son setup ;
- les exécutions confirmées et les probes indisponibles dans leur ordre réel ;
- les postconditions de campagne ;
- les compteurs `confirmed` et `unavailable`.
La surface générique par opération reste une API avancée et ne doit pas être utilisée pour reconstruire manuellement ces campagnes dans lUI.
### Campagne Token-2022 Token Metadata
Ouvrir laccordéon **Token-2022 Token Metadata**, sélectionner un profil Devnet persistant et préparer sa base PostgreSQL. Le panneau affiche le contrat fixe de cinq opérations :
```text
Initialize -> UpdateField -> Emit -> RemoveKey -> UpdateAuthority
```
Après confirmation explicite, **Exécuter la campagne Token-2022 Metadata** crée un mint frais avec Metadata Pointer vers lui-même, puis appelle le runner réutilisable pour les cinq opérations. La campagne conserve les simulations, signatures, confirmations, snapshots stateful, postconditions et matérialisations ; `Emit` expose également la return data validée. La fixture et les preuves sont affichées avec le JsonViewer commun.
La carte **Exigences** est placée en bas de la colonne droite, après les postconditions. Le journal dexécution est placé sous les deux colonnes principales et occupe toute la largeur disponible de la page.
### Campagne Solana Program Metadata ### Campagne Solana Program Metadata

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/demo_execution_metadata.html --> <!-- file: kb-app-demo-desktop/frontend/demo_execution_metadata.html -->
<!-- version: 10 --> <!-- version: 13 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -23,7 +23,7 @@
<main class="app-main"> <main class="app-main">
<div class="osb-scrollable pt-1 pb-4" data-simplebar> <div class="osb-scrollable pt-1 pb-4" data-simplebar>
<div class="container-fluid px-4 py-4"> <div class="container-fluid px-4 py-4">
<div class="alert alert-info">Cette fenêtre sépare les familles metadata. Metaplex Token Metadata conserve ses scénarios propres ; Solana Program Metadata exécute les neuf opérations stables de <code>ProgM6…</code> via deux parcours Devnet. Token-2022 Token Metadata sera ajouté dans une étape ultérieure.</div> <div class="alert alert-info">Cette fenêtre sépare les trois familles metadata. Metaplex Token Metadata conserve ses scénarios synthétiques mais exécute sur Devnet les campagnes qualifiées de <code>pre.013</code> ; Token-2022 Token Metadata exécute sa campagne native de cinq opérations ; Solana Program Metadata exécute les neuf opérations stables de <code>ProgM6…</code> via deux parcours Devnet.</div>
<div class="accordion shadow-sm mb-4" id="metadataGuideAccordion"> <div class="accordion shadow-sm mb-4" id="metadataGuideAccordion">
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header"><button class="accordion-button collapsed" data-bs-target="#metadataGuideCollapse" data-bs-toggle="collapse" type="button">Guide dexécution Metadata Devnet</button></h2> <h2 class="accordion-header"><button class="accordion-button collapsed" data-bs-target="#metadataGuideCollapse" data-bs-toggle="collapse" type="button">Guide dexécution Metadata Devnet</button></h2>
@@ -31,10 +31,17 @@
<div class="accordion-body"> <div class="accordion-body">
<h3 class="h6">Metaplex Token Metadata</h3> <h3 class="h6">Metaplex Token Metadata</h3>
<ol class="mb-3"> <ol class="mb-3">
<li>Choisir un profil Devnet puis préparer sa base PostgreSQL.</li> <li>Utiliser les scénarios synthétiques uniquement pour inspecter les contrats et exigences.</li>
<li>Choisir le parcours Devnet puis son étape.</li> <li>Pour Devnet, choisir et préparer un profil persistant puis sélectionner une campagne qualifiée de <code>pre.013</code>.</li>
<li>Préparer létape afin de générer ensemble lintent, les lectures stateful et les postconditions.</li> <li>Confirmer explicitement la création des fixtures et les soumissions autorisées du runner spécialisé.</li>
<li>Lancer dabord une simulation. La soumission exige une confirmation explicite.</li> <li>Contrôler la fixture, les exécutions/probes et les postconditions dans les trois JsonViewer communs.</li>
</ol>
<h3 class="h6">Token-2022 Token Metadata</h3>
<ol class="mb-3">
<li>Choisir et préparer un profil Devnet persistant, autorisé à soumettre.</li>
<li>Contrôler les cinq opérations natives : Initialize, UpdateField, Emit, RemoveKey et UpdateAuthority.</li>
<li>Confirmer la création dun mint frais et lexécution complète de la campagne.</li>
<li>Contrôler les simulations, signatures, confirmations, snapshots stateful, return data Emit et matérialisations.</li>
</ol> </ol>
<h3 class="h6">Solana Program Metadata</h3> <h3 class="h6">Solana Program Metadata</h3>
<ol class="mb-2"> <ol class="mb-2">
@@ -68,46 +75,26 @@
</div> </div>
<div class="accordion-item"> <div class="accordion-item">
<h3 class="accordion-header"> <h3 class="accordion-header">
<button class="accordion-button collapsed" data-bs-target="#metaplexDevnetCollapse" data-bs-toggle="collapse" type="button">Scénarios Devnet</button> <button class="accordion-button collapsed" data-bs-target="#metaplexDevnetCollapse" data-bs-toggle="collapse" type="button">Campagnes Devnet qualifiées</button>
</h3> </h3>
<div class="accordion-collapse collapse" id="metaplexDevnetCollapse" data-bs-parent="#metaplexScenarioModeAccordion"> <div class="accordion-collapse collapse" id="metaplexDevnetCollapse" data-bs-parent="#metaplexScenarioModeAccordion">
<div class="accordion-body"> <div class="accordion-body">
<label class="form-label" for="metadataProfileSelect">Profil Devnet</label> <label class="form-label" for="metadataProfileSelect">Profil Devnet</label>
<select class="form-select" id="metadataProfileSelect"></select> <select class="form-select" id="metadataProfileSelect"></select>
<div class="form-text mb-3" id="metadataProfileHelp">Chargement du profil Devnet.</div> <div class="form-text mb-3" id="metadataProfileHelp">Chargement du profil Devnet.</div>
<label class="form-label" for="metadataDevnetScenarioSelect">Contrat de scénario</label> <div class="d-grid"><button class="btn btn-outline-primary" id="prepareMetadataProfileButton" type="button">Charger le profil et préparer la base</button></div>
<select class="form-select" id="metadataDevnetScenarioSelect"></select>
<div class="d-grid mt-3"><button class="btn btn-outline-primary" id="prepareMetadataProfileButton" type="button">Charger le profil et préparer la base</button></div>
<div class="d-flex justify-content-between align-items-center mt-3"><span class="small text-muted">État du profil</span><span class="badge text-bg-secondary" id="metadataProfileStatusBadge">Non chargé</span></div> <div class="d-flex justify-content-between align-items-center mt-3"><span class="small text-muted">État du profil</span><span class="badge text-bg-secondary" id="metadataProfileStatusBadge">Non chargé</span></div>
<div class="mt-3" id="metadataProfileOutput"></div> <div class="mt-3" id="metadataProfileOutput"></div>
<label class="form-label mt-3" for="metadataStepSelect">Étape du parcours</label> <label class="form-label mt-3" for="metadataMetaplexCampaignSelect">Campagne qualifiée</label>
<select class="form-select" id="metadataStepSelect"></select> <select class="form-select" id="metadataMetaplexCampaignSelect"></select>
<div class="d-flex gap-2 mt-2"> <div class="mt-3" id="metadataMetaplexCampaignOutput"></div>
<button class="btn btn-outline-secondary flex-fill" id="previousMetadataStepButton" type="button">Étape précédente</button> <div class="alert alert-warning mt-3">Les campagnes Devnet réutilisent directement les runners qualifiés de <code>pre.013</code>. Les opérations classées <code>unavailable</code> restent confinées aux probes simulation-only de Maintenance ou Use ; elles ne sont jamais exposées comme soumissions isolées.</div>
<button class="btn btn-outline-secondary flex-fill" id="nextMetadataStepButton" type="button">Étape suivante</button>
</div>
<div class="d-grid mt-2"><button class="btn btn-outline-success" id="prepareMetadataScenarioStepButton" type="button">Préparer létape courante</button></div>
<div class="form-text">Le scénario remplit ensemble lintent, les lectures avant/après et les cibles de matérialisation.</div>
<label class="form-label mt-3" for="metadataOperationJson">Intent Metaplex typé</label>
<textarea class="form-control font-monospace" id="metadataOperationJson" rows="12" spellcheck="false"></textarea>
<label class="form-label mt-3" for="metadataPreflightReadsJson">Lectures stateful avant simulation</label>
<textarea class="form-control font-monospace" id="metadataPreflightReadsJson" rows="4" spellcheck="false">[]</textarea>
<label class="form-label mt-3" for="metadataPostconditionReadsJson">Lectures stateful après confirmation</label>
<textarea class="form-control font-monospace" id="metadataPostconditionReadsJson" rows="4" spellcheck="false">[]</textarea>
<div class="form-check mt-3"> <div class="form-check mt-3">
<input class="form-check-input" id="metadataMaterializeInput" type="checkbox" checked> <input class="form-check-input" id="metadataMetaplexCampaignOperatorConfirmed" type="checkbox">
<label class="form-check-label" for="metadataMaterializeInput">Matérialiser les snapshots Metaplex confirmés</label> <label class="form-check-label" for="metadataMetaplexCampaignOperatorConfirmed">Je confirme la création des fixtures et toutes les soumissions autorisées de la campagne</label>
<div class="form-text">Après confirmation, relit les comptes déclarés, les décode canoniquement et produit les projections détat. Aucun fetch URI off-chain.</div>
</div> </div>
<div class="form-check mt-3"> <div class="d-grid mt-3"><button class="btn btn-danger" id="executeMetadataMetaplexCampaignButton" type="button">Exécuter la campagne Metaplex</button></div>
<input class="form-check-input" id="metadataOperatorConfirmed" type="checkbox"> <div class="form-text">Le runner arrête la campagne au premier échec et conserve simulation, signature, confirmation, postconditions, matérialisation et idempotence selon son contrat.</div>
<label class="form-check-label" for="metadataOperatorConfirmed">Je confirme explicitement la soumission</label>
</div>
<div class="d-grid gap-2 mt-3">
<button class="btn btn-outline-primary" id="simulateMetadataButton" type="button">Simuler sur Devnet</button>
<button class="btn btn-primary" id="submitMetadataButton" type="button">Soumettre sur Devnet</button>
</div>
<div class="alert alert-warning mt-3 mb-0">La simulation appelle réellement le RPC Devnet. La soumission reste désactivée tant que lopérateur ne lautorise et ne la confirme pas explicitement.</div>
</div> </div>
</div> </div>
</div> </div>
@@ -118,6 +105,29 @@
</div> </div>
</div> </div>
</div> </div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" data-bs-target="#token2022MetadataCollapse" data-bs-toggle="collapse" type="button">Token-2022 Token Metadata</button>
</h2>
<div class="accordion-collapse collapse" id="token2022MetadataCollapse" data-bs-parent="#metadataExecutionAccordion">
<div class="accordion-body">
<label class="form-label" for="token2022MetadataProfileSelect">Profil Devnet</label>
<select class="form-select" id="token2022MetadataProfileSelect"></select>
<div class="form-text mb-3" id="token2022MetadataProfileHelp">Chargement du profil Devnet.</div>
<div class="d-grid"><button class="btn btn-outline-primary" id="prepareToken2022MetadataProfileButton" type="button">Charger le profil et préparer la base</button></div>
<div class="d-flex justify-content-between align-items-center mt-3"><span class="small text-muted">État du profil</span><span class="badge text-bg-secondary" id="token2022MetadataProfileStatusBadge">Non chargé</span></div>
<div class="mt-3" id="token2022MetadataProfileOutput"></div>
<div class="mt-3" id="token2022MetadataCampaignOutput"></div>
<div class="alert alert-warning mt-3">Cette campagne crée un mint Token-2022 frais avec Metadata Pointer, puis soumet exactement cinq opérations Token Metadata. Chaque mutation est simulée avant soumission et la campagne sarrête à la première preuve manquante.</div>
<div class="form-check mt-3">
<input class="form-check-input" id="token2022MetadataOperatorConfirmed" type="checkbox">
<label class="form-check-label" for="token2022MetadataOperatorConfirmed">Je confirme la création de la fixture et les cinq mutations Devnet</label>
</div>
<div class="d-grid mt-3"><button class="btn btn-danger" id="executeToken2022MetadataCampaignButton" type="button">Exécuter la campagne Token-2022 Metadata</button></div>
<div class="form-text">Le résultat réutilise les trois JsonViewer communs pour la fixture, les confirmations et les preuves ordonnées.</div>
</div>
</div>
</div>
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header"> <h2 class="accordion-header">
<button class="accordion-button collapsed" data-bs-target="#solanaProgramMetadataCollapse" data-bs-toggle="collapse" type="button">Solana Program Metadata</button> <button class="accordion-button collapsed" data-bs-target="#solanaProgramMetadataCollapse" data-bs-toggle="collapse" type="button">Solana Program Metadata</button>
@@ -144,10 +154,42 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card shadow-sm border-0 mt-4"> </div>
<div class="card-body"> <div class="col-12 col-xl-7">
<h2 class="h5 mb-2">Journal dexécution</h2> <div class="d-flex align-items-center justify-content-between mb-2">
<textarea class="form-control font-monospace" id="metadataExecutionLogOutput" readonly rows="12" spellcheck="false"></textarea> <h2 class="h5 mb-0">Résultats détaillés</h2>
<small class="text-body-secondary">Préflight, simulation, confirmation et preuves</small>
</div>
<div class="accordion shadow-sm mt-4" id="metadataExecutionDetailsAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="metadataPreflightHeading">
<button aria-controls="metadataPreflightCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#metadataPreflightCollapse" data-bs-toggle="collapse" type="button">Préflight et opérations</button>
</h2>
<div aria-labelledby="metadataPreflightHeading" class="accordion-collapse collapse" data-bs-parent="#metadataExecutionDetailsAccordion" id="metadataPreflightCollapse">
<div class="accordion-body">
<textarea class="form-control font-monospace" data-json-viewer="true" id="metadataPreflightOutput" readonly rows="14">{"title":"Préflight et opérations","status":"idle","message":"Aucun résultat."}</textarea>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="metadataSimulationHeading">
<button aria-controls="metadataSimulationCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#metadataSimulationCollapse" data-bs-toggle="collapse" type="button">Simulation et confirmation</button>
</h2>
<div aria-labelledby="metadataSimulationHeading" class="accordion-collapse collapse" data-bs-parent="#metadataExecutionDetailsAccordion" id="metadataSimulationCollapse">
<div class="accordion-body">
<textarea class="form-control font-monospace" data-json-viewer="true" id="metadataSimulationOutput" readonly rows="16">{"title":"Simulation et confirmation","status":"idle","message":"Aucune simulation."}</textarea>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="metadataPostconditionsHeading">
<button aria-controls="metadataPostconditionsCollapse" aria-expanded="false" class="accordion-button collapsed" data-bs-target="#metadataPostconditionsCollapse" data-bs-toggle="collapse" type="button">Postconditions et preuves</button>
</h2>
<div aria-labelledby="metadataPostconditionsHeading" class="accordion-collapse collapse" data-bs-parent="#metadataExecutionDetailsAccordion" id="metadataPostconditionsCollapse">
<div class="accordion-body">
<textarea class="form-control font-monospace" data-json-viewer="true" id="metadataPostconditionsOutput" readonly rows="16">{"title":"Postconditions et preuves","status":"idle","message":"Aucune preuve."}</textarea>
</div>
</div>
</div> </div>
</div> </div>
<div class="card shadow-sm border-0 mt-4"> <div class="card shadow-sm border-0 mt-4">
@@ -157,25 +199,11 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-xl-7"> </div>
<div class="card shadow-sm border-0 mb-4"> <div class="card shadow-sm border-0 mt-4">
<div class="card-body"> <div class="card-body">
<h2 class="h5">Préflight et opérations</h2> <h2 class="h5 mb-2">Journal dexécution</h2>
<textarea class="form-control font-monospace" data-json-viewer="true" id="metadataPreflightOutput" readonly rows="14">{"title":"Préflight et opérations","status":"idle","message":"Aucun résultat."}</textarea> <textarea class="form-control font-monospace" id="metadataExecutionLogOutput" readonly rows="12" spellcheck="false"></textarea>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h2 class="h5">Simulation et confirmation</h2>
<textarea class="form-control font-monospace" data-json-viewer="true" id="metadataSimulationOutput" readonly rows="16">{"title":"Simulation et confirmation","status":"idle","message":"Aucune simulation."}</textarea>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-body">
<h2 class="h5">Postconditions et preuves</h2>
<textarea class="form-control font-monospace" data-json-viewer="true" id="metadataPostconditionsOutput" readonly rows="16">{"title":"Postconditions et preuves","status":"idle","message":"Aucune preuve."}</textarea>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -184,4 +212,4 @@
<script type="module" src="ts/demo_execution_metadata.ts"></script> <script type="module" src="ts/demo_execution_metadata.ts"></script>
</body> </body>
</html> </html>

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts // file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
// version: 12 // version: 14
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
@@ -7,30 +7,32 @@ import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts"; import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
import { renderJsonViewer } from "./json_viewer.ts"; import { renderJsonViewer } from "./json_viewer.ts";
import type { DemoExecutionMetadataMetaplexCampaignOptionPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexCampaignOptionPayload.ts";
import type { DemoExecutionMetadataMetaplexCampaignRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexCampaignRequest.ts";
import type { DemoExecutionMetadataMetaplexCampaignSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexCampaignSummaryPayload.ts";
import type { DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload.ts"; import type { DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload.ts";
import type { DemoExecutionMetadataProgressPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataProgressPayload.ts"; import type { DemoExecutionMetadataProgressPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataProgressPayload.ts";
import type { DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload.ts";
import type { DemoExecutionMetadataMetaplexTokenMetadataRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataRequest.ts";
import type { DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload.ts";
import type { DemoExecutionMetadataSolanaProgramCampaignRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramCampaignRequest.ts"; import type { DemoExecutionMetadataSolanaProgramCampaignRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramCampaignRequest.ts";
import type { DemoExecutionMetadataSolanaProgramCampaignSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramCampaignSummaryPayload.ts"; import type { DemoExecutionMetadataSolanaProgramCampaignSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramCampaignSummaryPayload.ts";
import type { DemoExecutionMetadataSolanaProgramScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramScenarioPayload.ts"; import type { DemoExecutionMetadataSolanaProgramScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramScenarioPayload.ts";
import type { DemoExecutionMetadataToken2022CampaignRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataToken2022CampaignRequest.ts";
import type { DemoExecutionMetadataToken2022CampaignSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataToken2022CampaignSummaryPayload.ts";
import type { DemoExecutionDevnetStoreReadinessPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionDevnetStoreReadinessPayload.ts"; import type { DemoExecutionDevnetStoreReadinessPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionDevnetStoreReadinessPayload.ts";
import type { DemoExecutionSolanaCoreOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts"; import type { 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 { DemoExecutionSolanaCoreProfileOption } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProfileOption.ts";
const storageKey = "kb-app-demo-desktop.metadata.selected-scenario"; const storageKey = "kb-app-demo-desktop.metadata.selected-scenario";
const metaplexCampaignStorageKey = "kb-app-demo-desktop.metadata.metaplex.selected-campaign";
const solanaProgramMetadataStorageKey = "kb-app-demo-desktop.metadata.solana-program.selected-scenario"; const solanaProgramMetadataStorageKey = "kb-app-demo-desktop.metadata.solana-program.selected-scenario";
const profileStorageKey = "kb-app-demo-desktop.metadata.selected-profile"; const profileStorageKey = "kb-app-demo-desktop.metadata.selected-profile";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap; (window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
let scenarios: DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload[] = []; let scenarios: DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload[] = [];
let metaplexCampaigns: DemoExecutionMetadataMetaplexCampaignOptionPayload[] = [];
let solanaProgramMetadataScenarios: DemoExecutionMetadataSolanaProgramScenarioPayload[] = []; let solanaProgramMetadataScenarios: DemoExecutionMetadataSolanaProgramScenarioPayload[] = [];
let selectedProfileName: string | null = null; let selectedProfileName: string | null = null;
let executionRunning = false; let executionRunning = false;
let profileStoreReady = false; let profileStoreReady = false;
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = []; let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
let preparedScenarioId: string | null = null;
let preparedStepIndex: number | null = null;
const metadataLogLines: string[] = []; const metadataLogLines: string[] = [];
const maximumMetadataLogLines = 1200; const maximumMetadataLogLines = 1200;
@@ -57,10 +59,10 @@ function element<T extends HTMLElement>(selector: string): T {
return value; return value;
} }
const profileSelectSelectors = ["#metadataProfileSelect", "#solanaProgramMetadataProfileSelect"] as const; const profileSelectSelectors = ["#metadataProfileSelect", "#token2022MetadataProfileSelect", "#solanaProgramMetadataProfileSelect"] as const;
const profileHelpSelectors = ["#metadataProfileHelp", "#solanaProgramMetadataProfileHelp"] as const; const profileHelpSelectors = ["#metadataProfileHelp", "#token2022MetadataProfileHelp", "#solanaProgramMetadataProfileHelp"] as const;
const profileStatusSelectors = ["#metadataProfileStatusBadge", "#solanaProgramMetadataProfileStatusBadge"] as const; const profileStatusSelectors = ["#metadataProfileStatusBadge", "#token2022MetadataProfileStatusBadge", "#solanaProgramMetadataProfileStatusBadge"] as const;
const profileOutputSelectors = ["#metadataProfileOutput", "#solanaProgramMetadataProfileOutput"] as const; const profileOutputSelectors = ["#metadataProfileOutput", "#token2022MetadataProfileOutput", "#solanaProgramMetadataProfileOutput"] as const;
function synchronizeProfileSelections(profileName: string): void { function synchronizeProfileSelections(profileName: string): void {
for (const selector of profileSelectSelectors) { for (const selector of profileSelectSelectors) {
@@ -80,50 +82,19 @@ function updateProfileStatus(text: string, className: string): void {
} }
} }
function activeSelect(): HTMLSelectElement {
return document.querySelector("#metaplexDevnetCollapse.show") !== null ? element<HTMLSelectElement>("#metadataDevnetScenarioSelect") : element<HTMLSelectElement>("#metadataSyntheticScenarioSelect");
}
function selectedScenario(): DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload | null { function selectedScenario(): DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload | null {
const id = activeSelect().value; const id = element<HTMLSelectElement>("#metadataSyntheticScenarioSelect").value;
return scenarios.find(scenario => scenario.id === id) ?? null; return scenarios.find(scenario => scenario.id === id) ?? null;
} }
function fillSelect(selector: string, mode: string): void { function selectedMetaplexCampaign(): DemoExecutionMetadataMetaplexCampaignOptionPayload | null {
const select = element<HTMLSelectElement>(selector); const id = element<HTMLSelectElement>("#metadataMetaplexCampaignSelect").value;
select.replaceChildren(); return metaplexCampaigns.find(campaign => campaign.id === id) ?? null;
for (const scenario of scenarios.filter(candidate => candidate.mode === mode)) {
const option = document.createElement("option");
option.value = scenario.id;
option.textContent = `${scenario.assetFamily}${scenario.label}`;
select.append(option);
}
} }
function operationName(operationCode: string): string { function selectedSolanaProgramMetadataScenario(): DemoExecutionMetadataSolanaProgramScenarioPayload | null {
const parts = operationCode.split("."); const id = element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect").value;
return parts.length === 0 ? operationCode : parts[parts.length - 1] ?? operationCode; return solanaProgramMetadataScenarios.find(scenario => scenario.id === id) ?? null;
}
function renderScenarioSteps(scenario: DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload): void {
const select = element<HTMLSelectElement>("#metadataStepSelect");
const previousValue = Number.parseInt(select.value, 10);
select.replaceChildren();
scenario.operationCodes.forEach((operationCode, index) => {
const option = document.createElement("option");
option.value = String(index);
option.textContent = `${index + 1}. ${operationName(operationCode)}`;
select.append(option);
});
if (Number.isInteger(previousValue) && previousValue >= 0 && previousValue < scenario.operationCodes.length) {
select.value = String(previousValue);
}
const index = Number.parseInt(select.value, 10);
element<HTMLButtonElement>("#previousMetadataStepButton").disabled = !Number.isInteger(index) || index <= 0;
element<HTMLButtonElement>("#nextMetadataStepButton").disabled = !Number.isInteger(index) || index >= scenario.operationCodes.length - 1;
element<HTMLButtonElement>("#prepareMetadataScenarioStepButton").textContent = Number.isInteger(index) && scenario.operationCodes[index] !== undefined
? `Préparer létape ${index + 1}${operationName(scenario.operationCodes[index])}`
: "Préparer létape courante";
} }
function renderSelectedScenario(): void { function renderSelectedScenario(): void {
@@ -132,95 +103,62 @@ function renderSelectedScenario(): void {
return; return;
} }
window.localStorage.setItem(storageKey, scenario.id); window.localStorage.setItem(storageKey, scenario.id);
const networkMode = scenario.mode !== "synthetic"; element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Parcours</dt><dd class="col-6">${scenario.label}</dd><dt class="col-6">Famille</dt><dd class="col-6"><code>${scenario.assetFamily}</code></dd><dt class="col-6">Fixture</dt><dd class="col-6"><code>${scenario.fixtureKind}</code></dd><dt class="col-6">État initial</dt><dd class="col-6"><code>${scenario.initialState}</code></dd><dt class="col-6">État attendu</dt><dd class="col-6"><code>${scenario.resultingState}</code></dd><dt class="col-6">Mode</dt><dd class="col-6"><code>${scenario.mode}</code></dd><dt class="col-6">Collection</dt><dd class="col-6">${scenario.requiresCollection ? "requise" : "non requise"}</dd><dt class="col-6">Rule set</dt><dd class="col-6">${scenario.requiresProgrammableRules ? "requis" : "non requis"}</dd><dt class="col-6">Validation</dt><dd class="col-6"><code>${scenario.validationStatus}</code></dd>`;
renderScenarioSteps(scenario); renderJsonViewer("#metadataPreflightOutput", titled("Contrat synthétique — opérations", {
preparedScenarioId = null;
preparedStepIndex = null;
element<HTMLTextAreaElement>("#metadataOperationJson").value = "";
element<HTMLTextAreaElement>("#metadataPreflightReadsJson").value = "[]";
element<HTMLTextAreaElement>("#metadataPostconditionReadsJson").value = "[]";
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Parcours</dt><dd class="col-6">${scenario.label}</dd><dt class="col-6">Famille</dt><dd class="col-6"><code>${scenario.assetFamily}</code></dd><dt class="col-6">Fixture</dt><dd class="col-6"><code>${scenario.fixtureKind}</code></dd><dt class="col-6">État initial</dt><dd class="col-6"><code>${scenario.initialState}</code></dd><dt class="col-6">État attendu</dt><dd class="col-6"><code>${scenario.resultingState}</code></dd><dt class="col-6">Mode</dt><dd class="col-6"><code>${scenario.mode}</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${networkMode ? selectedProfileName ?? "non chargé" : "non applicable"}</code></dd><dt class="col-6">Collection</dt><dd class="col-6">${scenario.requiresCollection ? "requise" : "non requise"}</dd><dt class="col-6">Rule set</dt><dd class="col-6">${scenario.requiresProgrammableRules ? "requis" : "non requis"}</dd><dt class="col-6">Validation</dt><dd class="col-6"><code>${scenario.validationStatus}</code></dd>`;
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", {
scenarioId: scenario.id, scenarioId: scenario.id,
operationCodes: scenario.operationCodes, operationCodes: scenario.operationCodes,
fixtureKind: scenario.fixtureKind,
checks: ["program_id", "owner", "pda_and_seeds", "required_accounts", "authorities", "stateful_correlations"], checks: ["program_id", "owner", "pda_and_seeds", "required_accounts", "authorities", "stateful_correlations"],
}));
renderJsonViewer("#metadataSimulationOutput", titled("Contrat synthétique — politique", {
mode: scenario.mode,
networkExecutionPerformed: false,
automaticSubmission: false,
validationStatus: scenario.validationStatus,
note: "Ce mode inspecte le contrat. Les exécutions Devnet utilisent exclusivement les campagnes qualifiées de pre.013.",
}));
renderJsonViewer("#metadataPostconditionsOutput", titled("Contrat synthétique — preuves", {
required: scenario.requiresPostcondition,
evidenceKinds: scenario.requiredEvidence,
materializationTargets: scenario.materializationTargets,
networkEvidenceCollected: false,
}));
}
function renderMetaplexCampaign(): void {
const campaign = selectedMetaplexCampaign();
if (campaign === null) {
return;
}
window.localStorage.setItem(metaplexCampaignStorageKey, campaign.id);
renderJsonViewer("#metadataMetaplexCampaignOutput", titled("Campagne Metaplex qualifiée", campaign));
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.metaplex_token_metadata</code></dd><dt class="col-6">Campagne</dt><dd class="col-6">${campaign.label}</dd><dt class="col-6">Asset</dt><dd class="col-6"><code>${campaign.assetFamily}</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${selectedProfileName ?? "non chargé"}</code></dd><dt class="col-6">Qualification</dt><dd class="col-6"><code>${campaign.qualification}</code></dd><dt class="col-6">Opérations cibles</dt><dd class="col-6">${campaign.operationNames.length}</dd>`;
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", {
campaignId: campaign.id,
label: campaign.label,
assetFamily: campaign.assetFamily,
setup: campaign.setup,
targetOperations: campaign.operationNames,
runner: "qualified_pre_013_specialized_campaign",
networkExecutionPerformed: false, networkExecutionPerformed: false,
})); }));
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", { renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", {
mode: scenario.mode, policy: "specialized_campaign_simulation_first",
policy: "simulation_first",
networkExecutionPerformed: false,
automaticSubmission: false,
operatorConfirmationRequired: true, operatorConfirmationRequired: true,
expectedState: scenario.validationStatus, stopOnFirstFailure: true,
note: networkMode ? "Renseignez un intent typé puis lancez une simulation RPC réelle." : "Scénario synthétique déterministe.", isolatedUnavailableSubmission: false,
qualification: campaign.qualification,
networkExecutionPerformed: false,
})); }));
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", { renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", {
required: scenario.requiresPostcondition, campaignId: campaign.id,
evidenceKinds: scenario.requiredEvidence, qualification: campaign.qualification,
requiredEvidence: ["rpc_simulation", "confirmed_signature_when_submitted", "canonical_replay", "stateful_postconditions", "materialization", "idempotence"],
unavailablePolicy: "simulation_only_probe_without_submission",
networkEvidenceCollected: false, networkEvidenceCollected: false,
materializationTargets: scenario.materializationTargets,
rule: "Aucune validation réseau n'est déclarée sans simulation RPC réelle, signature éventuelle, slot et postconditions.",
})); }));
} }
async function prepareScenarioStep(): Promise<void> {
const profileName = element<HTMLSelectElement>("#metadataProfileSelect").value;
const scenario = selectedScenario();
const stepIndex = Number.parseInt(element<HTMLSelectElement>("#metadataStepSelect").value, 10);
if (profileName.length === 0) { throw new Error("Sélectionnez un profil Devnet."); }
if (scenario === null || scenario.mode === "synthetic") { throw new Error("Sélectionnez un scénario Devnet."); }
if (!Number.isInteger(stepIndex) || stepIndex < 0) { throw new Error("Sélectionnez une étape valide."); }
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "fixture", message: `Préparation de ${scenario.id} étape ${stepIndex + 1}` });
const prepared = await invoke<DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload>("demo_execution_metadata_metaplex_token_metadata_prepare_step", {
profileName,
scenarioId: scenario.id,
stepIndex,
previousOperationJson: element<HTMLTextAreaElement>("#metadataOperationJson").value.trim().length === 0
? null
: element<HTMLTextAreaElement>("#metadataOperationJson").value,
});
element<HTMLTextAreaElement>("#metadataOperationJson").value = prepared.operationJson;
element<HTMLTextAreaElement>("#metadataPreflightReadsJson").value = prepared.preflightReadsJson;
element<HTMLTextAreaElement>("#metadataPostconditionReadsJson").value = prepared.postconditionReadsJson;
preparedScenarioId = prepared.scenarioId;
preparedStepIndex = prepared.stepIndex;
renderJsonViewer("#metadataPreflightOutput", titled("Étape de scénario préparée", {
scenarioId: prepared.scenarioId,
stepIndex: prepared.stepIndex,
operation: prepared.operation,
fixtureState: prepared.fixtureState,
preflightReads: JSON.parse(prepared.preflightReadsJson),
postconditionReads: JSON.parse(prepared.postconditionReadsJson),
simulationReady: prepared.simulationReady,
submissionReady: prepared.submissionReady,
}));
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "fixture", message: `${prepared.label} prête (${prepared.fixtureState})` });
setExecutionRunning(executionRunning);
}
function moveScenarioStep(offset: number): void {
const select = element<HTMLSelectElement>("#metadataStepSelect");
const current = Number.parseInt(select.value, 10);
const next = current + offset;
if (Number.isInteger(next) && next >= 0 && next < select.options.length) {
select.value = String(next);
const scenario = selectedScenario();
if (scenario !== null) { renderScenarioSteps(scenario); }
}
}
function parseJsonText(value: string, label: string): unknown {
try {
return JSON.parse(value);
} catch (caught) {
const message = caught instanceof Error ? caught.message : String(caught);
throw new Error(`${label} invalide : ${message}`);
}
}
function selectedProfile(): DemoExecutionSolanaCoreProfileOption | null { function selectedProfile(): DemoExecutionSolanaCoreProfileOption | null {
if (selectedProfileName === null) { if (selectedProfileName === null) {
return null; return null;
@@ -241,92 +179,158 @@ function updateProfileHelp(): void {
function setExecutionRunning(value: boolean): void { function setExecutionRunning(value: boolean): void {
executionRunning = value; executionRunning = value;
const profile = selectedProfile(); const profile = selectedProfile();
const missingOperation = element<HTMLTextAreaElement>("#metadataOperationJson").value.trim().length === 0; const metaplexConfirmed = element<HTMLInputElement>("#metadataMetaplexCampaignOperatorConfirmed").checked;
const unavailable = value || profile === null || !profileStoreReady || missingOperation; element<HTMLButtonElement>("#executeMetadataMetaplexCampaignButton").disabled = value || profile === null || !profileStoreReady || !profile.sendEnabled || !metaplexConfirmed || selectedMetaplexCampaign() === null;
element<HTMLButtonElement>("#simulateMetadataButton").disabled = unavailable; const token2022Confirmed = element<HTMLInputElement>("#token2022MetadataOperatorConfirmed").checked;
element<HTMLButtonElement>("#submitMetadataButton").disabled = unavailable || !profile?.sendEnabled; element<HTMLButtonElement>("#executeToken2022MetadataCampaignButton").disabled = value || profile === null || !profileStoreReady || !profile.sendEnabled || !token2022Confirmed;
element<HTMLButtonElement>("#prepareMetadataScenarioStepButton").disabled = value || profile === null || !profileStoreReady;
const solanaProgramConfirmed = element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").checked; const solanaProgramConfirmed = element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").checked;
element<HTMLButtonElement>("#executeSolanaProgramMetadataCampaignButton").disabled = value || profile === null || !profileStoreReady || !profile.sendEnabled || !solanaProgramConfirmed; element<HTMLButtonElement>("#executeSolanaProgramMetadataCampaignButton").disabled = value || profile === null || !profileStoreReady || !profile.sendEnabled || !solanaProgramConfirmed;
for (const selector of ["#prepareMetadataProfileButton", "#prepareToken2022MetadataProfileButton", "#prepareSolanaProgramMetadataProfileButton", "#refreshMetadataScenariosButton"]) {
element<HTMLButtonElement>(selector).disabled = value;
}
} }
async function executeMetadata(submit: boolean): Promise<void> { async function executeMetaplexCampaign(): Promise<void> {
if (executionRunning) { if (executionRunning) {
return; return;
} }
const profileName = element<HTMLSelectElement>("#metadataProfileSelect").value; const profile = selectedProfile();
if (profileName.length === 0 || !profileStoreReady) { const campaign = selectedMetaplexCampaign();
if (profile === null || selectedProfileName === null || !profileStoreReady) {
throw new Error("Sélectionnez et préparez un profil Devnet."); throw new Error("Sélectionnez et préparez un profil Devnet.");
} }
const operationJson = element<HTMLTextAreaElement>("#metadataOperationJson").value; if (campaign === null) {
const scenario = selectedScenario(); throw new Error("Sélectionnez une campagne Metaplex qualifiée.");
const stepIndex = Number.parseInt(element<HTMLSelectElement>("#metadataStepSelect").value, 10);
if (operationJson.trim().length === 0) {
throw new Error("Préparez létape courante avant lexécution.");
} }
if (scenario === null || preparedScenarioId !== scenario.id || preparedStepIndex !== stepIndex) { if (!profile.sendEnabled) {
throw new Error("Préparez de nouveau létape après toute modification du scénario ou de létape."); throw new Error("Le profil sélectionné nautorise pas les soumissions Devnet requises par les fixtures Metaplex.");
} }
const preflightReadsJson = element<HTMLTextAreaElement>("#metadataPreflightReadsJson").value; const operatorConfirmed = element<HTMLInputElement>("#metadataMetaplexCampaignOperatorConfirmed").checked;
const postconditionReadsJson = element<HTMLTextAreaElement>("#metadataPostconditionReadsJson").value; if (!operatorConfirmed) {
parseJsonText(operationJson, "Intent Metaplex"); throw new Error("La campagne Metaplex exige la confirmation explicite de lopérateur.");
parseJsonText(preflightReadsJson, "Lectures de préflight");
parseJsonText(postconditionReadsJson, "Lectures de postcondition");
const operatorConfirmed = element<HTMLInputElement>("#metadataOperatorConfirmed").checked;
if (submit && !operatorConfirmed) {
throw new Error("La soumission exige la confirmation explicite de lopérateur.");
} }
const request: DemoExecutionMetadataMetaplexTokenMetadataRequest = { const request: DemoExecutionMetadataMetaplexCampaignRequest = {
profileName, profileName: selectedProfileName,
intentId: `desktop-metaplex-${Date.now()}`, campaignId: campaign.id,
operationJson,
preflightReadsJson,
postconditionReadsJson,
submit,
operatorConfirmed, operatorConfirmed,
materializeAfterConfirmation: element<HTMLInputElement>("#metadataMaterializeInput").checked,
}; };
setExecutionRunning(true); setExecutionRunning(true);
const badge = element<HTMLElement>("#metadataStatusBadge"); const badge = element<HTMLElement>("#metadataStatusBadge");
badge.textContent = submit ? "Soumission" : "Simulation"; badge.textContent = "Campagne Metaplex";
badge.className = "badge text-bg-warning"; badge.className = "badge text-bg-warning";
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: submit ? "submit" : "simulation", message: submit ? "Démarrage du parcours Devnet complet." : "Démarrage de la simulation RPC exacte." }); appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "metaplex_campaign", message: `Démarrage de la campagne qualifiée ${campaign.id}${campaign.label}.` });
try { try {
const summary = await invoke<DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload>("demo_execution_metadata_metaplex_token_metadata_execute", { request }); const summary = await invoke<DemoExecutionMetadataMetaplexCampaignSummaryPayload>("demo_execution_metadata_metaplex_execute_campaign", { request });
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", JSON.parse(summary.preflightJson))); const executions = JSON.parse(summary.executionsJson) as unknown;
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", { const postconditions = JSON.parse(summary.postconditionsJson) as unknown;
renderJsonViewer("#metadataPreflightOutput", titled("Fixture et setup Metaplex", JSON.parse(summary.fixtureJson)));
renderJsonViewer("#metadataSimulationOutput", titled("Synthèse de campagne Metaplex", {
profileName: summary.profileName, profileName: summary.profileName,
cluster: summary.cluster, campaignId: summary.campaignId,
genesisHash: summary.genesisHash, label: summary.label,
walletPublicKey: summary.walletPublicKey, operationCount: summary.operationCount,
balanceLamports: summary.balanceLamports, confirmedOperationCount: summary.confirmedOperationCount,
simulationSuccess: summary.simulationSuccess, unavailableOperationCount: summary.unavailableOperationCount,
transactionSignature: summary.transactionSignature, completed: summary.completed,
confirmationStatus: summary.confirmationStatus,
materializationRequested: summary.materializationRequested,
materializedSnapshotCount: summary.materializedSnapshotCount,
plan: JSON.parse(summary.planJson),
simulation: JSON.parse(summary.simulationJson),
})); }));
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", JSON.parse(summary.evidenceJson))); renderJsonViewer("#metadataPostconditionsOutput", titled("Exécutions et postconditions Metaplex", { executions, postconditions }));
badge.textContent = summary.transactionSignature === null ? "Simulé" : "Confirmé"; element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.metaplex_token_metadata</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${summary.profileName}</code></dd><dt class="col-6">Campagne</dt><dd class="col-6">${summary.label}</dd><dt class="col-6">Qualification</dt><dd class="col-6"><code>${campaign.qualification}</code></dd><dt class="col-6">Confirmées</dt><dd class="col-6">${summary.confirmedOperationCount}</dd><dt class="col-6">Indisponibles</dt><dd class="col-6">${summary.unavailableOperationCount}</dd><dt class="col-6">Éléments de preuve</dt><dd class="col-6">${summary.operationCount}</dd>`;
badge.className = summary.simulationSuccess ? "badge text-bg-success" : "badge text-bg-danger"; badge.textContent = summary.completed ? "Campagne qualifiée" : "Campagne incomplète";
badge.className = summary.completed ? "badge text-bg-success" : "badge text-bg-danger";
appendMetadataLog({ timestamp: new Date().toISOString(), level: summary.completed ? "info" : "error", stage: "metaplex_campaign", message: `Campagne terminée : confirmed=${summary.confirmedOperationCount}, unavailable=${summary.unavailableOperationCount}, evidence=${summary.operationCount}.` });
} finally { } finally {
setExecutionRunning(false); setExecutionRunning(false);
} }
} }
function selectedSolanaProgramMetadataScenario(): DemoExecutionMetadataSolanaProgramScenarioPayload | null { function renderToken2022MetadataCampaign(): void {
const id = element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect").value; const operations = [
return solanaProgramMetadataScenarios.find(scenario => scenario.id === id) ?? null; "initialize_token_metadata",
"update_token_metadata_field",
"emit_token_metadata",
"remove_token_metadata_key",
"update_token_metadata_authority",
];
renderJsonViewer("#token2022MetadataCampaignOutput", titled("Campagne Token-2022 Token Metadata", {
family: "metadata.token_2022",
operations,
fixture: "fresh_token_2022_mint_with_self_metadata_pointer",
transactionCount: 6,
policy: "simulation_first_then_confirmed_submission",
desktopValidation: "confirmed_pre_014",
}));
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.token_2022</code></dd><dt class="col-6">Programme</dt><dd class="col-6"><code>TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb</code></dd><dt class="col-6">Opérations</dt><dd class="col-6">${operations.length}</dd><dt class="col-6">Transactions</dt><dd class="col-6">6</dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${selectedProfileName ?? "non chargé"}</code></dd><dt class="col-6">Validation desktop</dt><dd class="col-6"><code>confirmed_pre_014</code></dd>`;
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", {
family: "metadata.token_2022",
operations,
fixturePreparation: ["fresh_mint", "metadata_pointer_to_self", "rent_budget_for_metadata_growth"],
desktopValidation: "confirmed_pre_014",
networkExecutionPerformed: false,
}));
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", {
policy: "simulation_first",
submission: "required",
operatorConfirmationRequired: true,
stopOnFirstFailure: true,
fixtureTransactionCount: 1,
metadataTransactionCount: operations.length,
desktopValidation: "confirmed_pre_014",
networkExecutionPerformed: false,
}));
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", {
requiredEvidence: ["metadata_pointer", "stateful_snapshot", "rpc_simulation", "confirmed_signature", "canonical_replay", "materialization", "idempotence", "emit_return_data"],
operationCount: operations.length,
desktopValidation: "confirmed_pre_014",
networkEvidenceCollected: false,
}));
} }
function renderActiveMetadataSelection(): void { async function executeToken2022MetadataCampaign(): Promise<void> {
if (document.querySelector("#solanaProgramMetadataCollapse.show") !== null) { if (executionRunning) {
renderSolanaProgramMetadataScenario();
return; return;
} }
renderSelectedScenario(); const profile = selectedProfile();
if (profile === null || selectedProfileName === null || !profileStoreReady) {
throw new Error("Sélectionnez et préparez un profil Devnet.");
}
if (!profile.sendEnabled) {
throw new Error("Le profil sélectionné nautorise pas les soumissions Devnet.");
}
const operatorConfirmed = element<HTMLInputElement>("#token2022MetadataOperatorConfirmed").checked;
if (!operatorConfirmed) {
throw new Error("La campagne exige la confirmation explicite de la fixture et des cinq mutations.");
}
const request: DemoExecutionMetadataToken2022CampaignRequest = {
profileName: selectedProfileName,
operatorConfirmed,
};
setExecutionRunning(true);
const badge = element<HTMLElement>("#metadataStatusBadge");
badge.textContent = "Campagne Token-2022";
badge.className = "badge text-bg-warning";
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "token_2022_metadata_campaign", message: "Démarrage : création de la fixture puis cinq opérations Token Metadata." });
try {
const summary = await invoke<DemoExecutionMetadataToken2022CampaignSummaryPayload>("demo_execution_metadata_token_2022_execute_campaign", { request });
renderJsonViewer("#metadataPreflightOutput", titled("Fixture Token-2022 Metadata", JSON.parse(summary.fixtureJson)));
renderJsonViewer("#metadataSimulationOutput", titled("Synthèse de campagne", {
profileName: summary.profileName,
mint: summary.mint,
initialAuthority: summary.initialAuthority,
finalAuthority: summary.finalAuthority,
preparationSignature: summary.preparationSignature,
stepCount: summary.stepCount,
confirmedStepCount: summary.confirmedStepCount,
materializationCount: summary.materializationCount,
completed: summary.completed,
}));
renderJsonViewer("#metadataPostconditionsOutput", titled("Preuves ordonnées des cinq opérations", JSON.parse(summary.stepsJson)));
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.token_2022</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${summary.profileName}</code></dd><dt class="col-6">Mint</dt><dd class="col-6"><code>${summary.mint}</code></dd><dt class="col-6">Confirmées</dt><dd class="col-6">${summary.confirmedStepCount}/${summary.stepCount}</dd><dt class="col-6">Matérialisations</dt><dd class="col-6">${summary.materializationCount}</dd><dt class="col-6">Autorité finale</dt><dd class="col-6"><code>${summary.finalAuthority}</code></dd>`;
badge.textContent = summary.completed ? "Campagne confirmée" : "Campagne incomplète";
badge.className = summary.completed ? "badge text-bg-success" : "badge text-bg-danger";
appendMetadataLog({ timestamp: new Date().toISOString(), level: summary.completed ? "info" : "error", stage: "token_2022_metadata_campaign", message: `Campagne terminée : confirmed=${summary.confirmedStepCount}/${summary.stepCount}, materializations=${summary.materializationCount}` });
} finally {
setExecutionRunning(false);
}
} }
function renderSolanaProgramMetadataScenario(): void { function renderSolanaProgramMetadataScenario(): void {
@@ -340,26 +344,29 @@ function renderSolanaProgramMetadataScenario(): void {
label: scenario.label, label: scenario.label,
fixtureAccount: scenario.fixtureAccount, fixtureAccount: scenario.fixtureAccount,
steps: scenario.steps, steps: scenario.steps,
desktopValidation: "confirmed_pre_014",
})); }));
const allSteps = solanaProgramMetadataScenarios.flatMap(candidate => candidate.steps); const allSteps = solanaProgramMetadataScenarios.flatMap(candidate => candidate.steps);
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.solana_program_metadata</code></dd><dt class="col-6">Program ID</dt><dd class="col-6"><code>ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S</code></dd><dt class="col-6">Parcours affiché</dt><dd class="col-6">${scenario.label}</dd><dt class="col-6">Étapes totales</dt><dd class="col-6">${allSteps.length}</dd><dt class="col-6">Transactions</dt><dd class="col-6">11 minimum</dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${selectedProfileName ?? "non chargé"}</code></dd>`; element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.solana_program_metadata</code></dd><dt class="col-6">Program ID</dt><dd class="col-6"><code>ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S</code></dd><dt class="col-6">Parcours affiché</dt><dd class="col-6">${scenario.label}</dd><dt class="col-6">Étapes totales</dt><dd class="col-6">${allSteps.length}</dd><dt class="col-6">Transactions</dt><dd class="col-6">11 minimum</dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${selectedProfileName ?? "non chargé"}</code></dd><dt class="col-6">Validation desktop</dt><dd class="col-6"><code>confirmed_pre_014</code></dd>`;
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", { renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", {
family: "metadata.solana_program_metadata", family: "metadata.solana_program_metadata",
journeys: solanaProgramMetadataScenarios, journeys: solanaProgramMetadataScenarios,
operationCount: allSteps.length, operationCount: allSteps.length,
fixtureAccounts: ["buffer", "metadata"], fixtureAccounts: ["buffer", "metadata"],
prefundingTransactionCount: 2, prefundingTransactionCount: 2,
desktopValidation: "confirmed_pre_014",
})); }));
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", { renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", {
policy: "simulation_first", policy: "simulation_first",
submission: "required", submission: "required",
operatorConfirmationRequired: true, operatorConfirmationRequired: true,
stopOnFirstFailure: true, stopOnFirstFailure: true,
desktopValidation: "confirmed_pre_014",
networkExecutionPerformed: false, networkExecutionPerformed: false,
})); }));
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", { renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", {
requiredEvidence: ["stateful_preflight", "rpc_simulation", "confirmed_signature", "stateful_postcondition", "materialized_snapshot_or_account_absence"], requiredEvidence: ["stateful_preflight", "rpc_simulation", "confirmed_signature", "stateful_postcondition", "materialized_snapshot_or_account_absence"],
currentStatus: "not_run", desktopValidation: "confirmed_pre_014",
networkEvidenceCollected: false, networkEvidenceCollected: false,
})); }));
} }
@@ -414,6 +421,22 @@ async function executeSolanaProgramMetadataCampaign(): Promise<void> {
} }
} }
function renderActiveMetadataSelection(): void {
if (document.querySelector("#token2022MetadataCollapse.show") !== null) {
renderToken2022MetadataCampaign();
return;
}
if (document.querySelector("#solanaProgramMetadataCollapse.show") !== null) {
renderSolanaProgramMetadataScenario();
return;
}
if (document.querySelector("#metaplexDevnetCollapse.show") !== null) {
renderMetaplexCampaign();
return;
}
renderSelectedScenario();
}
async function loadProfiles(): Promise<void> { async function loadProfiles(): Promise<void> {
const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_metadata_options"); const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_metadata_options");
profileOptions = options.profiles; profileOptions = options.profiles;
@@ -481,15 +504,31 @@ async function loadScenarios(): Promise<void> {
badge.className = "badge text-bg-warning"; badge.className = "badge text-bg-warning";
try { try {
scenarios = await invoke<DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload[]>("demo_execution_metadata_metaplex_token_metadata_scenarios"); scenarios = await invoke<DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload[]>("demo_execution_metadata_metaplex_token_metadata_scenarios");
metaplexCampaigns = await invoke<DemoExecutionMetadataMetaplexCampaignOptionPayload[]>("demo_execution_metadata_metaplex_campaigns");
solanaProgramMetadataScenarios = await invoke<DemoExecutionMetadataSolanaProgramScenarioPayload[]>("demo_execution_metadata_solana_program_scenarios"); solanaProgramMetadataScenarios = await invoke<DemoExecutionMetadataSolanaProgramScenarioPayload[]>("demo_execution_metadata_solana_program_scenarios");
fillSelect("#metadataSyntheticScenarioSelect", "synthetic"); const syntheticSelect = element<HTMLSelectElement>("#metadataSyntheticScenarioSelect");
fillSelect("#metadataDevnetScenarioSelect", "network_simulation"); syntheticSelect.replaceChildren();
for (const scenario of scenarios.filter(candidate => candidate.mode === "synthetic")) {
const option = document.createElement("option");
option.value = scenario.id;
option.textContent = `${scenario.assetFamily}${scenario.label}`;
syntheticSelect.append(option);
}
const restored = window.localStorage.getItem(storageKey); const restored = window.localStorage.getItem(storageKey);
for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) { if (restored !== null && Array.from(syntheticSelect.options).some(option => option.value === restored)) {
const select = element<HTMLSelectElement>(selector); syntheticSelect.value = restored;
if (restored !== null && Array.from(select.options).some(option => option.value === restored)) { }
select.value = restored; const metaplexSelect = element<HTMLSelectElement>("#metadataMetaplexCampaignSelect");
} metaplexSelect.replaceChildren();
for (const campaign of metaplexCampaigns) {
const option = document.createElement("option");
option.value = campaign.id;
option.textContent = `${campaign.assetFamily}${campaign.label}`;
metaplexSelect.append(option);
}
const restoredMetaplex = window.localStorage.getItem(metaplexCampaignStorageKey);
if (restoredMetaplex !== null && Array.from(metaplexSelect.options).some(option => option.value === restoredMetaplex)) {
metaplexSelect.value = restoredMetaplex;
} }
const solanaSelect = element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect"); const solanaSelect = element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect");
solanaSelect.replaceChildren(); solanaSelect.replaceChildren();
@@ -504,8 +543,10 @@ async function loadScenarios(): Promise<void> {
solanaSelect.value = restoredSolana; solanaSelect.value = restoredSolana;
} }
renderActiveMetadataSelection(); renderActiveMetadataSelection();
badge.textContent = `${scenarios.length + solanaProgramMetadataScenarios.length} contrats`; const syntheticCount = scenarios.filter(candidate => candidate.mode === "synthetic").length;
badge.textContent = `${syntheticCount + metaplexCampaigns.length + solanaProgramMetadataScenarios.length + 1} contrats`;
badge.className = "badge text-bg-success"; badge.className = "badge text-bg-success";
setExecutionRunning(executionRunning);
} catch (caught) { } catch (caught) {
const message = caught instanceof Error ? caught.message : String(caught); const message = caught instanceof Error ? caught.message : String(caught);
profileStoreReady = false; profileStoreReady = false;
@@ -518,12 +559,14 @@ async function loadScenarios(): Promise<void> {
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.execution_metadata"); installFrontendConsoleBridge("kb-app-demo-desktop.frontend.execution_metadata");
for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) { element<HTMLSelectElement>("#metadataSyntheticScenarioSelect").addEventListener("change", renderSelectedScenario);
element<HTMLSelectElement>(selector).addEventListener("change", renderSelectedScenario); element<HTMLElement>("#metaplexSyntheticCollapse").addEventListener("shown.bs.collapse", renderSelectedScenario);
} element<HTMLElement>("#metaplexDevnetCollapse").addEventListener("shown.bs.collapse", renderMetaplexCampaign);
for (const id of ["metaplexSyntheticCollapse", "metaplexDevnetCollapse"]) { element<HTMLSelectElement>("#metadataMetaplexCampaignSelect").addEventListener("change", () => {
element<HTMLElement>(`#${id}`).addEventListener("shown.bs.collapse", renderSelectedScenario); renderMetaplexCampaign();
} setExecutionRunning(executionRunning);
});
element<HTMLElement>("#token2022MetadataCollapse").addEventListener("shown.bs.collapse", renderToken2022MetadataCampaign);
element<HTMLElement>("#solanaProgramMetadataCollapse").addEventListener("shown.bs.collapse", renderSolanaProgramMetadataScenario); element<HTMLElement>("#solanaProgramMetadataCollapse").addEventListener("shown.bs.collapse", renderSolanaProgramMetadataScenario);
element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect").addEventListener("change", renderSolanaProgramMetadataScenario); element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect").addEventListener("change", renderSolanaProgramMetadataScenario);
for (const selector of profileSelectSelectors) { for (const selector of profileSelectSelectors) {
@@ -538,27 +581,18 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
} }
element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile()); element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile());
element<HTMLButtonElement>("#prepareToken2022MetadataProfileButton").addEventListener("click", () => void prepareProfile());
element<HTMLButtonElement>("#prepareSolanaProgramMetadataProfileButton").addEventListener("click", () => void prepareProfile()); element<HTMLButtonElement>("#prepareSolanaProgramMetadataProfileButton").addEventListener("click", () => void prepareProfile());
element<HTMLButtonElement>("#refreshMetadataScenariosButton").addEventListener("click", () => void loadScenarios()); element<HTMLButtonElement>("#refreshMetadataScenariosButton").addEventListener("click", () => void loadScenarios());
element<HTMLSelectElement>("#metadataStepSelect").addEventListener("change", () => {
const scenario = selectedScenario();
if (scenario !== null) { renderScenarioSteps(scenario); }
});
element<HTMLButtonElement>("#previousMetadataStepButton").addEventListener("click", () => moveScenarioStep(-1));
element<HTMLButtonElement>("#nextMetadataStepButton").addEventListener("click", () => moveScenarioStep(1));
element<HTMLTextAreaElement>("#metadataOperationJson").addEventListener("input", () => {
preparedScenarioId = null;
preparedStepIndex = null;
setExecutionRunning(executionRunning);
});
element<HTMLButtonElement>("#prepareMetadataScenarioStepButton").addEventListener("click", () => { void prepareScenarioStep().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); });
element<HTMLTextAreaElement>("#metadataExecutionLogOutput").addEventListener("app-log-clear", () => { metadataLogLines.length = 0; }); element<HTMLTextAreaElement>("#metadataExecutionLogOutput").addEventListener("app-log-clear", () => { metadataLogLines.length = 0; });
void listen<DemoExecutionMetadataProgressPayload>("demo-execution-metadata-progress", event => appendMetadataLog(event.payload)); void listen<DemoExecutionMetadataProgressPayload>("demo-execution-metadata-progress", event => appendMetadataLog(event.payload));
element<HTMLButtonElement>("#simulateMetadataButton").addEventListener("click", () => { element<HTMLInputElement>("#metadataMetaplexCampaignOperatorConfirmed").addEventListener("change", () => setExecutionRunning(executionRunning));
void executeMetadata(false).catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); element<HTMLButtonElement>("#executeMetadataMetaplexCampaignButton").addEventListener("click", () => {
void executeMetaplexCampaign().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught)));
}); });
element<HTMLButtonElement>("#submitMetadataButton").addEventListener("click", () => { element<HTMLInputElement>("#token2022MetadataOperatorConfirmed").addEventListener("change", () => setExecutionRunning(executionRunning));
void executeMetadata(true).catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); element<HTMLButtonElement>("#executeToken2022MetadataCampaignButton").addEventListener("click", () => {
void executeToken2022MetadataCampaign().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught)));
}); });
element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").addEventListener("change", () => setExecutionRunning(executionRunning)); element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").addEventListener("change", () => setExecutionRunning(executionRunning));
element<HTMLButtonElement>("#executeSolanaProgramMetadataCampaignButton").addEventListener("click", () => { element<HTMLButtonElement>("#executeSolanaProgramMetadataCampaignButton").addEventListener("click", () => {

View File

@@ -1,7 +1,7 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_metaplex_token_metadata.rs // file: kb-app-demo-desktop/src/demo_execution_metadata_metaplex_token_metadata.rs
// version: 4 // version: 6
//! Desktop adapters for Metaplex Token Metadata execution demo scenarios. //! Desktop adapters for generic and qualified Metaplex Token Metadata execution workflows.
/// UI-safe Metaplex scenario row. /// UI-safe Metaplex scenario row.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)] #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)]
@@ -815,6 +815,876 @@ fn validation_status_code(
}; };
} }
// Qualified Metaplex Devnet campaigns.
/// UI-safe descriptor for one qualified Metaplex Devnet campaign.
#[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_metadata/DemoExecutionMetadataMetaplexCampaignOptionPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexCampaignOptionPayload {
/// Stable campaign identifier accepted by the desktop executor.
pub id: std::string::String,
/// Operator-visible campaign label.
pub label: std::string::String,
/// Primary asset family exercised by the campaign.
pub asset_family: std::string::String,
/// Target operations qualified by the campaign after any fixture setup.
pub operation_names: std::vec::Vec<std::string::String>,
/// Stable qualification code shown by the desktop.
pub qualification: std::string::String,
/// Human-readable fixture/setup contract.
pub setup: std::string::String,
}
/// Request for one complete qualified Metaplex Devnet campaign.
#[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_metadata/DemoExecutionMetadataMetaplexCampaignRequest.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexCampaignRequest {
/// Selected Devnet profile.
pub profile_name: std::string::String,
/// Stable campaign identifier returned by the campaign inventory.
pub campaign_id: std::string::String,
/// Explicit authorization for fixture creation and every campaign submission.
pub operator_confirmed: bool,
}
/// UI-safe result of one qualified Metaplex Devnet campaign.
#[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_metadata/DemoExecutionMetadataMetaplexCampaignSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexCampaignSummaryPayload {
/// Profile used by the campaign.
pub profile_name: std::string::String,
/// Stable campaign identifier.
pub campaign_id: std::string::String,
/// Operator-visible campaign label.
pub label: std::string::String,
/// Number of Metaplex operation executions or probes retained in this run.
pub operation_count: usize,
/// Number of Metaplex operations reaching confirmed or finalized state.
pub confirmed_operation_count: usize,
/// Number of simulation-only runtime-unavailable probes retained in this run.
pub unavailable_operation_count: usize,
/// Whether the complete campaign reached its expected terminal contract.
pub completed: bool,
/// Fixture/setup description as formatted JSON.
pub fixture_json: std::string::String,
/// Ordered Metaplex execution evidence as formatted JSON.
pub executions_json: std::string::String,
/// Exact campaign postconditions as formatted JSON.
pub postconditions_json: std::string::String,
}
struct CampaignProjection {
label: std::string::String,
fixture: serde_json::Value,
executions: std::vec::Vec<serde_json::Value>,
postconditions: serde_json::Value,
completed: bool,
}
type CampaignProjectionFuture<'a> = std::pin::Pin<
std::boxed::Box<
dyn std::future::Future<Output = kb_core::Result<CampaignProjection>>
+ std::marker::Send
+ 'a,
>,
>;
/// Returns the qualified Metaplex Devnet campaign inventory exposed by the desktop.
pub(crate) fn demo_execution_metadata_metaplex_campaigns()
-> std::vec::Vec<crate::DemoExecutionMetadataMetaplexCampaignOptionPayload> {
let mut values = std::vec::Vec::new();
for (id, label, family) in [
("create_mint_nft", "Create → Mint — NFT", "nft"),
("create_mint_sft", "Create → Mint — SFT", "sft"),
("create_mint_fungible", "Create → Mint — Fungible", "fungible"),
("create_mint_collection", "Create → Mint — Collection", "collection"),
("create_mint_programmable_nft", "Create → Mint — pNFT", "programmable_nft"),
] {
values.push(crate::DemoExecutionMetadataMetaplexCampaignOptionPayload {
id: id.to_string(),
label: label.to_string(),
asset_family: family.to_string(),
operation_names:
kb_pipeline_demo_scenarios::metaplex_create_mint_campaign_operation_names()
.iter()
.map(|value| return (*value).to_string())
.collect(),
qualification: "confirmed".to_string(),
setup: "fresh classic SPL mint + operator ATA".to_string(),
});
}
values.extend([
campaign_option(
"collection_verify",
"Collection — Verify → Unverify",
"collection",
kb_pipeline_demo_scenarios::metaplex_collection_verify_campaign_operation_names(),
"confirmed",
"fresh collection parent + fresh unverified member",
),
campaign_option(
"print_burn",
"NFT imprimable — Print → Burn",
"nft",
kb_pipeline_demo_scenarios::metaplex_print_burn_campaign_operation_names(),
"confirmed",
"fresh limited-supply master NFT + fresh printed edition",
),
campaign_option(
"pnft_lifecycle",
"pNFT — Delegate → Lock → Unlock → Revoke → Delegate → Transfer",
"programmable_nft",
kb_pipeline_demo_scenarios::metaplex_pnft_lifecycle_campaign_operation_names(),
"confirmed",
"fresh pNFT + delegate wallet + destination owner",
),
campaign_option(
"escrow",
"Token Owned Escrow — Create → TransferOut → Close",
"nft",
kb_pipeline_demo_scenarios::metaplex_escrow_campaign_operation_names(),
"confirmed",
"fresh NFT parent + fresh fungible attribute + escrow ATA deposit",
),
campaign_option(
"maintenance",
"Maintenance — Update + probes réservés/legacy",
"nft",
kb_pipeline_demo_scenarios::metaplex_maintenance_campaign_operation_names(),
"mixed_confirmed_unavailable",
"fresh NFT; Update submitted; Resize/Migrate/Collect/CloseAccounts simulation-only probes",
),
campaign_option(
"use_probe",
"Use — probe runtime",
"nft",
kb_pipeline_demo_scenarios::metaplex_use_probe_operation_names(),
"runtime_unavailable_probe",
"fresh NFT with two bounded Multiple uses; Use is simulation-only when rejected",
),
]);
return values;
}
fn campaign_option<const N: usize>(
id: &str,
label: &str,
asset_family: &str,
operation_names: &'static [&'static str; N],
qualification: &str,
setup: &str,
) -> crate::DemoExecutionMetadataMetaplexCampaignOptionPayload {
return crate::DemoExecutionMetadataMetaplexCampaignOptionPayload {
id: id.to_string(),
label: label.to_string(),
asset_family: asset_family.to_string(),
operation_names: operation_names.iter().map(|value| return (*value).to_string()).collect(),
qualification: qualification.to_string(),
setup: setup.to_string(),
};
}
/// Executes one complete qualified Metaplex Devnet campaign.
pub(crate) async fn demo_execution_metadata_metaplex_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataMetaplexCampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexCampaignSummaryPayload,
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::DemoExecutionRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
if !request.operator_confirmed {
return std::result::Result::Err(
"Metaplex qualified campaign requires explicit operator confirmation".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),
};
if !profile.wallet.devnet_send_enabled {
return std::result::Result::Err(
"selected profile does not authorize Devnet submissions required by Metaplex fixtures"
.to_string(),
);
}
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 workspace_root = crate::workspace_root_dir();
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir
} else {
workspace_root.join(configured_wallet_dir)
};
let observer = crate::DemoExecutionMetadataObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
tracing::info!(
target: crate::TRACING_TARGET,
action = "execute_metadata_metaplex_campaign",
campaign_id = %request.campaign_id,
phase = "campaign_start",
"start qualified Metaplex desktop campaign"
);
let projection = match execute_campaign_projection(
request.campaign_id.as_str(),
wallet_dir,
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
tracing::error!(
target: crate::TRACING_TARGET,
action = "execute_metadata_metaplex_campaign",
campaign_id = %request.campaign_id,
phase = "campaign_failed",
error = %error,
"qualified Metaplex desktop campaign failed"
);
return std::result::Result::Err(error.to_string());
},
};
tracing::info!(
target: crate::TRACING_TARGET,
action = "execute_metadata_metaplex_campaign",
campaign_id = %request.campaign_id,
phase = "campaign_completed",
completed = projection.completed,
execution_count = projection.executions.len(),
"qualified Metaplex desktop campaign completed"
);
let confirmed_operation_count = projection
.executions
.iter()
.filter(|value| {
return value.get("classification").and_then(serde_json::Value::as_str)
== std::option::Option::Some("confirmed");
})
.count();
let unavailable_operation_count = projection
.executions
.iter()
.filter(|value| {
return value.get("classification").and_then(serde_json::Value::as_str)
== std::option::Option::Some("unavailable");
})
.count();
return std::result::Result::Ok(crate::DemoExecutionMetadataMetaplexCampaignSummaryPayload {
profile_name: request.profile_name,
campaign_id: request.campaign_id,
label: projection.label,
operation_count: projection.executions.len(),
confirmed_operation_count,
unavailable_operation_count,
completed: projection.completed,
fixture_json: crate::pretty_json(&projection.fixture),
executions_json: crate::pretty_json(&projection.executions),
postconditions_json: crate::pretty_json(&projection.postconditions),
});
}
fn execute_campaign_projection<'a, S, O>(
campaign_id: &'a str,
wallet_dir: std::path::PathBuf,
http_pool: &'a kb_onchain_transport::HttpEndpointPool,
store: &'a S,
profile: &'a kb_config::ProfileConfig,
workspace_root: &'a std::path::Path,
observer: &'a O,
) -> CampaignProjectionFuture<'a>
where
S: kb_store::RawTransactionStore
+ kb_store::CoreExtractionStore
+ kb_store::DecodePipelineStore
+ Sync
+ 'a,
O: kb_pipeline_demo_scenarios::SolanaExecutionObserver + 'a,
{
return std::boxed::Box::pin(execute_campaign_projection_unboxed(
campaign_id,
wallet_dir,
http_pool,
store,
profile,
workspace_root,
observer,
));
}
async fn execute_campaign_projection_unboxed<S, O>(
campaign_id: &str,
wallet_dir: std::path::PathBuf,
http_pool: &kb_onchain_transport::HttpEndpointPool,
store: &S,
profile: &kb_config::ProfileConfig,
workspace_root: &std::path::Path,
observer: &O,
) -> kb_core::Result<CampaignProjection>
where
S: kb_store::RawTransactionStore
+ kb_store::CoreExtractionStore
+ kb_store::DecodePipelineStore
+ Sync,
O: kb_pipeline_demo_scenarios::SolanaExecutionObserver,
{
let base_options =
kb_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir);
return match campaign_id {
"create_mint_nft" => {
execute_create_mint_projection(
"Create → Mint — NFT",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"create_mint_sft" => {
execute_create_mint_projection(
"Create → Mint — SFT",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"create_mint_fungible" => {
execute_create_mint_projection(
"Create → Mint — Fungible",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"create_mint_collection" => {
execute_create_mint_projection(
"Create → Mint — Collection",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"create_mint_programmable_nft" => {
execute_create_mint_projection(
"Create → Mint — pNFT",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"collection_verify" => {
let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_collection_verify_campaign(
http_pool,
store,
profile,
workspace_root,
&base_options,
observer,
).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("parent.create", &summary.parent.create),
metaplex_execution_json("parent.mint", &summary.parent.mint),
metaplex_execution_json("member.create", &summary.member.create),
metaplex_execution_json("member.mint", &summary.member.mint),
metaplex_execution_json("verify", &summary.verify),
metaplex_execution_json("unverify", &summary.unverify),
];
std::result::Result::Ok(CampaignProjection {
label: "Collection — Verify → Unverify".to_string(),
fixture: serde_json::json!({
"parent": &summary.parent.fixture,
"member": &summary.member.fixture
}),
completed: executions.iter().all(execution_is_confirmed_json),
executions,
postconditions: serde_json::json!({
"verifiedBefore": summary.state.verified_before,
"verifiedAfterVerify": summary.state.verified_after_verify,
"verifiedAfterUnverify": summary.state.verified_after_unverify,
"collectionSizeBefore": summary.state.collection_size_before,
"collectionSizeAfterVerify": summary.state.collection_size_after_verify,
"collectionSizeAfterUnverify": summary.state.collection_size_after_unverify
}),
})
},
"print_burn" => {
let summary =
match kb_pipeline_demo_scenarios::execute_devnet_metaplex_print_burn_campaign(
http_pool,
store,
profile,
workspace_root,
&base_options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("master.create", &summary.master.create),
metaplex_execution_json("master.mint", &summary.master.mint),
metaplex_execution_json("print", &summary.print),
metaplex_execution_json("burn", &summary.burn),
];
std::result::Result::Ok(CampaignProjection {
label: "NFT imprimable — Print → Burn".to_string(),
fixture: serde_json::json!({
"master": &summary.master.fixture,
"editionMint": &summary.edition_mint,
"editionMetadata": &summary.edition_metadata,
"edition": &summary.edition,
"editionTokenAccount": &summary.edition_token_account,
"editionMarker": &summary.edition_marker
}),
completed: executions.iter().all(execution_is_confirmed_json)
&& summary.state.edition_metadata_closed_after_burn
&& !summary.state.edition_exists_after_burn
&& !summary.state.edition_token_exists_after_burn,
executions,
postconditions: print_burn_state_json(&summary.state),
})
},
"pnft_lifecycle" => {
let options = base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
);
let summary =
match kb_pipeline_demo_scenarios::execute_devnet_metaplex_pnft_lifecycle_campaign(
http_pool,
store,
profile,
workspace_root,
&options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("pnft.create", &summary.pnft.create),
metaplex_execution_json("pnft.mint", &summary.pnft.mint),
metaplex_execution_json("delegate_staking", &summary.delegate_staking),
metaplex_execution_json("lock", &summary.lock),
metaplex_execution_json("unlock", &summary.unlock),
metaplex_execution_json("revoke", &summary.revoke),
metaplex_execution_json("delegate_transfer", &summary.delegate_transfer),
metaplex_execution_json("transfer", &summary.transfer),
];
std::result::Result::Ok(CampaignProjection {
label: "pNFT lifecycle".to_string(),
fixture: serde_json::json!({"pnft": &summary.pnft.fixture}),
completed: executions.iter().all(execution_is_confirmed_json)
&& summary.state.staking_delegate_revoked
&& summary.state.destination_delegate_cleared,
executions,
postconditions: pnft_state_json(&summary.state),
})
},
"escrow" => {
let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_escrow_campaign(
http_pool,
store,
profile,
workspace_root,
&base_options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("parent.create", &summary.parent_fixture.create),
metaplex_execution_json("parent.mint", &summary.parent_fixture.mint),
metaplex_execution_json("attribute.create", &summary.attribute_fixture.create),
metaplex_execution_json("attribute.mint", &summary.attribute_fixture.mint),
metaplex_execution_json("create_escrow", &summary.create_escrow),
metaplex_execution_json("transfer_out", &summary.transfer_out),
metaplex_execution_json("close_escrow", &summary.close_escrow),
];
std::result::Result::Ok(CampaignProjection {
label: "Token Owned Escrow".to_string(),
fixture: serde_json::json!({
"parent": &summary.parent_fixture.fixture,
"attribute": &summary.attribute_fixture.fixture,
"escrow": &summary.escrow,
"escrowAttributeTokenAccount": &summary.escrow_attribute_token_account,
"setup": {
"ataCreationConfirmed": execution_confirmation_is_confirmed(&summary.escrow_attribute_ata_creation.confirmation),
"attributeDepositConfirmed": execution_confirmation_is_confirmed(&summary.attribute_deposit.confirmation),
"ataMaterializations": summary.escrow_attribute_ata_creation.materializations.len(),
"depositMaterializations": summary.attribute_deposit.materializations.len()
}
}),
completed: executions.iter().all(execution_is_confirmed_json)
&& summary.state.escrow_attribute_account_closed
&& summary.state.escrow_account_closed
&& summary.state.parent_token_amount_after_close == 1,
executions,
postconditions: escrow_state_json(&summary.state),
})
},
"maintenance" => {
let options = base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
);
let summary =
match kb_pipeline_demo_scenarios::execute_devnet_metaplex_maintenance_campaign(
http_pool,
store,
profile,
workspace_root,
&options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("fixture.create", &summary.fixture.create),
metaplex_execution_json("fixture.mint", &summary.fixture.mint),
metaplex_execution_json("update", &summary.update),
metaplex_execution_json("resize_probe", &summary.resize_probe),
metaplex_execution_json("migrate_probe", &summary.migrate_probe),
metaplex_execution_json("collect_probe", &summary.collect_probe),
metaplex_execution_json("close_accounts_probe", &summary.close_accounts_probe),
];
let classifications = executions
.iter()
.filter_map(|value| {
return value.get("classification").and_then(serde_json::Value::as_str);
})
.collect::<std::vec::Vec<&str>>();
std::result::Result::Ok(CampaignProjection {
label: "Maintenance — Update + probes".to_string(),
fixture: serde_json::json!({"fixture": &summary.fixture.fixture}),
completed: classifications
== vec![
"confirmed",
"confirmed",
"confirmed",
"unavailable",
"unavailable",
"unavailable",
"unavailable",
]
&& !summary.primary_sale_before_update
&& summary.primary_sale_after_update,
executions,
postconditions: serde_json::json!({
"primarySaleBeforeUpdate": summary.primary_sale_before_update,
"primarySaleAfterUpdate": summary.primary_sale_after_update,
"probePolicy": "simulation_only_no_submission_on_failure"
}),
})
},
"use_probe" => {
let options = base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
);
let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_use_probe(
http_pool,
store,
profile,
workspace_root,
&options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut executions = vec![
metaplex_execution_json("fixture.create", &summary.fixture.create),
metaplex_execution_json("fixture.mint", &summary.fixture.mint),
metaplex_execution_json("use_probe", &summary.simulation),
];
if let std::option::Option::Some(execution) = summary.execution.as_ref() {
executions.push(metaplex_execution_json("use_execution", execution));
}
let status = format!("{:?}", summary.status).to_lowercase();
std::result::Result::Ok(CampaignProjection {
label: "Use — probe runtime".to_string(),
fixture: serde_json::json!({"fixture": &summary.fixture.fixture}),
completed: executions[0..2].iter().all(execution_is_confirmed_json)
&& (status == "runtimeunavailable" || status == "confirmed"),
executions,
postconditions: serde_json::json!({
"status": status,
"usesBefore": summary.uses_before,
"usesAfter": summary.uses_after,
"unavailableReason": summary.unavailable_reason
}),
})
},
_ => std::result::Result::Err(kb_core::Error::config(format!(
"unknown qualified Metaplex desktop campaign `{campaign_id}`"
))),
};
}
async fn execute_create_mint_projection<S, O>(
label: &str,
options: kb_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions,
http_pool: &kb_onchain_transport::HttpEndpointPool,
store: &S,
profile: &kb_config::ProfileConfig,
workspace_root: &std::path::Path,
observer: &O,
) -> kb_core::Result<CampaignProjection>
where
S: kb_store::RawTransactionStore
+ kb_store::CoreExtractionStore
+ kb_store::DecodePipelineStore
+ Sync,
O: kb_pipeline_demo_scenarios::SolanaExecutionObserver,
{
let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_create_mint_campaign(
http_pool,
store,
profile,
workspace_root,
&options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("create", &summary.create),
metaplex_execution_json("mint", &summary.mint),
];
return std::result::Result::Ok(CampaignProjection {
label: label.to_string(),
fixture: serde_json::json!({"fixture": &summary.fixture}),
completed: executions.iter().all(execution_is_confirmed_json)
&& summary.token_state.supply_after_mint == summary.token_state.expected_amount_raw
&& summary.token_state.token_amount_after_mint
== summary.token_state.expected_amount_raw,
executions,
postconditions: create_mint_state_json(&summary.token_state),
});
}
fn metaplex_execution_json(
label: &str,
summary: &kb_pipeline_demo_scenarios::DevnetMetaplexTokenMetadataExecutionSummary,
) -> serde_json::Value {
let classification = if execution_confirmation_is_confirmed(&summary.confirmation) {
"confirmed"
} else if !summary.simulation.success && summary.send_result.is_none() {
"unavailable"
} else {
"incomplete"
};
return serde_json::json!({
"label": label,
"classification": classification,
"profileName": &summary.profile_name,
"cluster": format!("{:?}", summary.cluster).to_lowercase(),
"genesisHash": &summary.genesis_hash,
"walletPublicKey": &summary.wallet.public_key,
"balanceLamports": summary.balance_lamports.to_string(),
"plan": &summary.plan,
"statefulPreflight": &summary.stateful_preflight,
"simulation": &summary.simulation,
"simulationContextSlot": summary.simulation_context_slot,
"readiness": &summary.readiness,
"sendResult": &summary.send_result,
"confirmation": &summary.confirmation,
"before": &summary.before,
"after": &summary.after,
"materializationRequested": summary.materialization_requested,
"materializedSnapshots": &summary.materialized_snapshots,
"instructionMaterializations": &summary.materializations,
"postExecution": &summary.post_execution,
"pipelineEvidence": {
"canonicalHydration": summary.backfill.is_some(),
"coreExtraction": summary.core_extraction.is_some(),
"decodeReplay": summary.decode_replay.is_some(),
"idempotenceReplay": summary.idempotence_replay.is_some()
}
});
}
fn execution_confirmation_is_confirmed(
confirmation: &std::option::Option<kb_lib::ExApiExecutionConfirmationResult>,
) -> bool {
return confirmation.as_ref().is_some_and(|value| {
return matches!(
value.status,
kb_lib::ExApiExecutionConfirmationStatus::Confirmed
| kb_lib::ExApiExecutionConfirmationStatus::Finalized
);
});
}
fn execution_is_confirmed_json(value: &serde_json::Value) -> bool {
return value.get("classification").and_then(serde_json::Value::as_str)
== std::option::Option::Some("confirmed");
}
fn create_mint_state_json(
state: &kb_pipeline_demo_scenarios::DevnetMetaplexCreateMintTokenState,
) -> serde_json::Value {
return serde_json::json!({
"expectedAmountRaw": state.expected_amount_raw,
"supplyBeforeMint": state.supply_before_mint,
"tokenAmountBeforeMint": state.token_amount_before_mint,
"tokenAccountStateBeforeMint": state.token_account_state_before_mint,
"supplyAfterMint": state.supply_after_mint,
"tokenAmountAfterMint": state.token_amount_after_mint,
"tokenAccountStateAfterMint": state.token_account_state_after_mint
});
}
fn print_burn_state_json(
state: &kb_pipeline_demo_scenarios::DevnetMetaplexPrintBurnState,
) -> serde_json::Value {
return serde_json::json!({
"editionNumber": state.edition_number,
"masterSupplyBeforePrint": state.master_supply_before_print,
"masterSupplyAfterPrint": state.master_supply_after_print,
"masterSupplyAfterBurn": state.master_supply_after_burn,
"masterMaxSupply": state.master_max_supply,
"editionSupplyAfterPrint": state.edition_supply_after_print,
"editionSupplyAfterBurn": state.edition_supply_after_burn,
"editionTokenAmountAfterPrint": state.edition_token_amount_after_print,
"editionTokenStateAfterPrint": state.edition_token_state_after_print,
"editionTakenAfterPrint": state.edition_taken_after_print,
"editionMetadataExistsAfterBurn": state.edition_metadata_exists_after_burn,
"editionMetadataFeeTombstoneAfterBurn": state.edition_metadata_fee_tombstone_after_burn,
"editionMetadataClosedAfterBurn": state.edition_metadata_closed_after_burn,
"editionExistsAfterBurn": state.edition_exists_after_burn,
"editionTokenExistsAfterBurn": state.edition_token_exists_after_burn,
"editionMarkerExistsAfterBurn": state.edition_marker_exists_after_burn
});
}
fn pnft_state_json(
state: &kb_pipeline_demo_scenarios::DevnetMetaplexPnftLifecycleState,
) -> serde_json::Value {
return serde_json::json!({
"delegate": &state.delegate,
"destinationOwner": &state.destination_owner,
"destinationTokenAccount": &state.destination_token_account,
"destinationTokenRecord": &state.destination_token_record,
"initialState": &state.initial_state,
"stakingDelegateRole": &state.staking_delegate_role,
"lockedState": &state.locked_state,
"unlockedState": &state.unlocked_state,
"stakingDelegateRevoked": state.staking_delegate_revoked,
"transferDelegateRole": &state.transfer_delegate_role,
"sourceTokenAmountAfterTransfer": state.source_token_amount_after_transfer,
"sourceTokenStateAfterTransfer": &state.source_token_state_after_transfer,
"destinationTokenAmountAfterTransfer": state.destination_token_amount_after_transfer,
"destinationTokenStateAfterTransfer": &state.destination_token_state_after_transfer,
"destinationTokenRecordState": &state.destination_token_record_state,
"destinationDelegateCleared": state.destination_delegate_cleared
});
}
fn escrow_state_json(
state: &kb_pipeline_demo_scenarios::DevnetMetaplexEscrowCampaignState,
) -> serde_json::Value {
return serde_json::json!({
"depositAmountRaw": state.deposit_amount_raw,
"operatorAttributeAmountBeforeDeposit": state.operator_attribute_amount_before_deposit,
"operatorAttributeAmountAfterDeposit": state.operator_attribute_amount_after_deposit,
"escrowAttributeAmountAfterDeposit": state.escrow_attribute_amount_after_deposit,
"operatorAttributeAmountAfterTransferOut": state.operator_attribute_amount_after_transfer_out,
"escrowAttributeAccountClosed": state.escrow_attribute_account_closed,
"escrowAccountClosed": state.escrow_account_closed,
"parentTokenAmountAfterClose": state.parent_token_amount_after_close
});
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[test] #[test]
@@ -865,13 +1735,16 @@ mod tests {
let source = std::include_str!("../frontend/demo_execution_metadata.html"); let source = std::include_str!("../frontend/demo_execution_metadata.html");
for id in [ for id in [
"metadataProfileSelect", "metadataProfileSelect",
"metadataDevnetScenarioSelect", "metadataSyntheticScenarioSelect",
"metadataStepSelect", "metadataMetaplexCampaignSelect",
"prepareMetadataScenarioStepButton", "metadataMetaplexCampaignOperatorConfirmed",
"executeMetadataMetaplexCampaignButton",
"metadataExecutionLogOutput", "metadataExecutionLogOutput",
"metadataProfileHelp", "metadataProfileHelp",
"simulateMetadataButton", "metadataExecutionDetailsAccordion",
"submitMetadataButton", "metadataPreflightCollapse",
"metadataSimulationCollapse",
"metadataPostconditionsCollapse",
] { ] {
assert!(source.contains(format!("id=\"{id}\"").as_str())); assert!(source.contains(format!("id=\"{id}\"").as_str()));
} }
@@ -890,3 +1763,42 @@ mod tests {
assert!(payloads.iter().any(|payload| return payload.requires_programmable_rules)); assert!(payloads.iter().any(|payload| return payload.requires_programmable_rules));
} }
} }
#[cfg(test)]
mod campaign_tests {
#[test]
fn qualified_campaign_inventory_is_exact_and_keeps_unavailable_operations_inside_probes() {
let campaigns = crate::demo_execution_metadata_metaplex_campaigns();
assert_eq!(campaigns.len(), 11);
let ids = campaigns
.iter()
.map(|value| return value.id.as_str())
.collect::<std::collections::BTreeSet<&str>>();
assert_eq!(ids.len(), campaigns.len());
let maintenance = campaigns.iter().find(|value| return value.id == "maintenance");
assert!(
maintenance
.is_some_and(|value| return value.qualification == "mixed_confirmed_unavailable")
);
let use_probe = campaigns.iter().find(|value| return value.id == "use_probe");
assert!(
use_probe
.is_some_and(|value| return value.qualification == "runtime_unavailable_probe")
);
let source = std::include_str!("demo_execution_metadata_metaplex_token_metadata.rs");
assert!(source.contains("Box::pin(execute_campaign_projection_unboxed("));
}
#[test]
fn metadata_frontend_uses_qualified_metaplex_campaign_controls() {
let source = std::include_str!("../frontend/demo_execution_metadata.html");
for id in [
"metadataMetaplexCampaignSelect",
"metadataMetaplexCampaignOutput",
"metadataMetaplexCampaignOperatorConfirmed",
"executeMetadataMetaplexCampaignButton",
] {
assert!(source.contains(format!("id=\"{id}\"").as_str()));
}
}
}

View File

@@ -0,0 +1,224 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_token_2022.rs
// version: 1
//! Thin desktop adapter for the complete Token-2022 Token Metadata Devnet campaign.
/// Request for one complete Token-2022 Token Metadata Devnet campaign.
#[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_metadata/DemoExecutionMetadataToken2022CampaignRequest.ts"
)]
pub(crate) struct DemoExecutionMetadataToken2022CampaignRequest {
/// Selected Devnet profile.
pub profile_name: std::string::String,
/// Explicit authorization for fixture creation and all five metadata mutations.
pub operator_confirmed: bool,
}
/// UI-safe result of one complete Token-2022 Token Metadata Devnet campaign.
#[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_metadata/DemoExecutionMetadataToken2022CampaignSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataToken2022CampaignSummaryPayload {
/// Profile used by the campaign.
pub profile_name: std::string::String,
/// Fresh Token-2022 mint containing the embedded metadata extension.
pub mint: std::string::String,
/// Initial update authority, equal to the selected profile wallet.
pub initial_authority: std::string::String,
/// Fresh authority installed by the final campaign step.
pub final_authority: std::string::String,
/// Confirmed signature that created the Token-2022 fixture mint.
pub preparation_signature: std::string::String,
/// Number of Token Metadata interface operations executed.
pub step_count: usize,
/// Number of operations reaching confirmed or finalized state.
pub confirmed_step_count: usize,
/// Total number of instruction materializations produced by the five operations.
pub materialization_count: usize,
/// Whether the complete five-step campaign and every postcondition succeeded.
pub completed: bool,
/// Complete fixture description as formatted JSON.
pub fixture_json: std::string::String,
/// Ordered execution and postcondition evidence as formatted JSON.
pub steps_json: std::string::String,
}
/// Executes the complete Token-2022 Token Metadata Devnet campaign.
pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataToken2022CampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataToken2022CampaignSummaryPayload,
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::DemoExecutionRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
if !request.operator_confirmed {
return std::result::Result::Err(
"Token-2022 Token Metadata campaign requires explicit operator confirmation"
.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 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 workspace_root = crate::workspace_root_dir();
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir
} else {
workspace_root.join(configured_wallet_dir)
};
let mut options =
kb_pipeline_demo_scenarios::Token2022MetadataFixturePreparationOptions::new(wallet_dir);
options.operator_confirmed = true;
let observer = crate::DemoExecutionMetadataObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let summary = match kb_pipeline_demo_scenarios::execute_devnet_token_2022_metadata_campaign(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&options,
&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(campaign_summary_payload(request.profile_name, summary));
}
fn campaign_summary_payload(
profile_name: std::string::String,
summary: kb_pipeline_demo_scenarios::DevnetToken2022MetadataCampaignSummary,
) -> crate::DemoExecutionMetadataToken2022CampaignSummaryPayload {
let mut confirmed_step_count = 0_usize;
let mut materialization_count = 0_usize;
let mut step_values = std::vec::Vec::with_capacity(summary.steps.len());
for step in &summary.steps {
let confirmed = match step.execution.confirmation.as_ref() {
std::option::Option::Some(value) => matches!(
value.status,
kb_lib::ExApiExecutionConfirmationStatus::Confirmed
| kb_lib::ExApiExecutionConfirmationStatus::Finalized
),
std::option::Option::None => false,
};
if confirmed {
confirmed_step_count = confirmed_step_count.saturating_add(1);
}
materialization_count =
materialization_count.saturating_add(step.execution.materializations.len());
step_values.push(serde_json::json!({
"stepId": &step.step_id,
"operationCode": &step.operation_code,
"simulation": &step.execution.simulation,
"sendResult": &step.execution.send_result,
"confirmation": &step.execution.confirmation,
"statefulPreflight": &step.execution.stateful_preflight,
"statefulSnapshot": &step.stateful_snapshot,
"emitEvidence": &step.emit_evidence,
"postcondition": &step.postcondition,
"postExecution": &step.execution.post_execution,
"materializations": &step.execution.materializations
}));
}
let expected_step_count =
kb_pipeline_demo_scenarios::token_2022_metadata_campaign_operation_names().len();
let completed = summary.steps.len() == expected_step_count
&& confirmed_step_count == expected_step_count
&& summary.steps.iter().all(|step| {
return step.postcondition.status
== kb_pipeline::Token2022ExecutionPostconditionStatus::Confirmed;
});
let fixture_json = crate::pretty_json(&serde_json::json!({
"mint": &summary.fixture.mint,
"initialAuthority": &summary.fixture.initial_authority,
"finalAuthority": &summary.fixture.final_authority,
"mintSpace": summary.fixture.mint_space,
"rentBudgetSpace": summary.fixture.rent_budget_space,
"rentReserveLamports": summary.fixture.rent_reserve_lamports.to_string(),
"preparationSignature": &summary.fixture.preparation_signature,
"initialSnapshot": &summary.fixture.initial_snapshot
}));
return crate::DemoExecutionMetadataToken2022CampaignSummaryPayload {
profile_name,
mint: summary.fixture.mint.0.clone(),
initial_authority: summary.fixture.initial_authority.0.clone(),
final_authority: summary.fixture.final_authority.0.clone(),
preparation_signature: summary.fixture.preparation_signature.clone(),
step_count: summary.steps.len(),
confirmed_step_count,
materialization_count,
completed,
fixture_json,
steps_json: crate::pretty_json(&step_values),
};
}
#[cfg(test)]
mod tests {
#[test]
fn desktop_campaign_exposes_the_exact_five_token_metadata_operations() {
assert_eq!(
kb_pipeline_demo_scenarios::token_2022_metadata_campaign_operation_names(),
&[
"initialize_token_metadata",
"update_token_metadata_field",
"emit_token_metadata",
"remove_token_metadata_key",
"update_token_metadata_authority",
]
);
}
#[test]
fn metadata_frontend_contains_token_2022_campaign_controls() {
let source = std::include_str!("../frontend/demo_execution_metadata.html");
for id in [
"token2022MetadataProfileSelect",
"token2022MetadataCampaignOutput",
"token2022MetadataOperatorConfirmed",
"executeToken2022MetadataCampaignButton",
] {
assert!(source.contains(format!("id=\"{id}\"").as_str()));
}
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/lib.rs // file: kb-app-demo-desktop/src/lib.rs
// version: 31 // version: 34
//! Tauri desktop demo application for `khadhroony-bot3`. //! Tauri desktop demo application for `khadhroony-bot3`.
@@ -17,6 +17,7 @@ mod demo_devnet_common;
mod demo_execution_metadata; mod demo_execution_metadata;
mod demo_execution_metadata_metaplex_token_metadata; mod demo_execution_metadata_metaplex_token_metadata;
mod demo_execution_metadata_solana_program; mod demo_execution_metadata_solana_program;
mod demo_execution_metadata_token_2022;
mod demo_execution_solana_core; mod demo_execution_solana_core;
mod demo_execution_spl; mod demo_execution_spl;
mod demo_http; mod demo_http;
@@ -137,6 +138,12 @@ pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataObserver;
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataProgressPayload; pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataProgressPayload;
/// Returns Devnet profile options shared by the Metadata execution panel. /// Returns Devnet profile options shared by the Metadata execution panel.
pub(crate) use self::demo_execution_metadata::demo_execution_metadata_options; pub(crate) use self::demo_execution_metadata::demo_execution_metadata_options;
/// UI-safe descriptor for one qualified Metaplex Devnet campaign.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexCampaignOptionPayload;
/// Request for one complete qualified Metaplex Devnet campaign.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexCampaignRequest;
/// UI-safe result of one complete qualified Metaplex Devnet campaign.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexCampaignSummaryPayload;
/// UI-safe result of one prepared Metaplex Token Metadata Create fixture. /// UI-safe result of one prepared Metaplex Token Metadata Create fixture.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload; pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload;
/// Prepared coherent Metaplex Token Metadata scenario step. /// Prepared coherent Metaplex Token Metadata scenario step.
@@ -147,6 +154,10 @@ pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecut
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload; pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload;
/// UI-safe result of one real Devnet Metaplex Token Metadata execution. /// UI-safe result of one real Devnet Metaplex Token Metadata execution.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload; pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload;
/// Returns the qualified Metaplex Devnet campaign inventory.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_campaigns;
/// Executes one complete qualified Metaplex Devnet campaign.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_execute_campaign;
/// Returns current Metaplex Token Metadata operation names accepted by Devnet campaigns. /// Returns current Metaplex Token Metadata operation names accepted by Devnet campaigns.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_token_metadata_current_operations; pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_token_metadata_current_operations;
/// Executes one real Devnet Metaplex Token Metadata simulation or submission. /// Executes one real Devnet Metaplex Token Metadata simulation or submission.
@@ -171,6 +182,12 @@ pub(crate) use self::demo_execution_metadata_solana_program::DemoExecutionMetada
pub(crate) use self::demo_execution_metadata_solana_program::demo_execution_metadata_solana_program_execute_campaign; pub(crate) use self::demo_execution_metadata_solana_program::demo_execution_metadata_solana_program_execute_campaign;
/// Returns the two canonical Solana Program Metadata Devnet journeys. /// Returns the two canonical Solana Program Metadata Devnet journeys.
pub(crate) use self::demo_execution_metadata_solana_program::demo_execution_metadata_solana_program_scenarios; pub(crate) use self::demo_execution_metadata_solana_program::demo_execution_metadata_solana_program_scenarios;
/// Request for the complete Token-2022 Token Metadata Devnet campaign.
pub(crate) use self::demo_execution_metadata_token_2022::DemoExecutionMetadataToken2022CampaignRequest;
/// UI-safe result of the complete Token-2022 Token Metadata Devnet campaign.
pub(crate) use self::demo_execution_metadata_token_2022::DemoExecutionMetadataToken2022CampaignSummaryPayload;
/// Executes the complete Token-2022 Token Metadata Devnet campaign.
pub(crate) use self::demo_execution_metadata_token_2022::demo_execution_metadata_token_2022_execute_campaign;
/// Request sent by the Memo v4 Devnet execution panel. /// Request sent by the Memo v4 Devnet execution panel.
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoRequest; pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoRequest;
/// UI-safe result of one Memo v4 Devnet execution orchestration. /// UI-safe result of one Memo v4 Devnet execution orchestration.

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/tauri.rs // file: kb-app-demo-desktop/src/tauri.rs
// version: 32 // version: 34
//! Tauri runtime assembly and private command wrappers. //! Tauri runtime assembly and private command wrappers.
@@ -90,6 +90,8 @@ pub fn run() -> kb_core::Result<()> {
demo_spl_token_2022_fixture, demo_spl_token_2022_fixture,
open_demo_execution_metadata_window, open_demo_execution_metadata_window,
demo_execution_metadata_metaplex_token_metadata_scenarios, demo_execution_metadata_metaplex_token_metadata_scenarios,
demo_execution_metadata_metaplex_campaigns,
demo_execution_metadata_metaplex_execute_campaign,
demo_execution_metadata_options, demo_execution_metadata_options,
demo_execution_metadata_metaplex_token_metadata_current_operations, demo_execution_metadata_metaplex_token_metadata_current_operations,
demo_execution_metadata_metaplex_token_metadata_operation_template, demo_execution_metadata_metaplex_token_metadata_operation_template,
@@ -98,6 +100,7 @@ pub fn run() -> kb_core::Result<()> {
demo_execution_metadata_metaplex_token_metadata_execute, demo_execution_metadata_metaplex_token_metadata_execute,
demo_execution_metadata_solana_program_scenarios, demo_execution_metadata_solana_program_scenarios,
demo_execution_metadata_solana_program_execute_campaign, demo_execution_metadata_solana_program_execute_campaign,
demo_execution_metadata_token_2022_execute_campaign,
]); ]);
builder = builder.on_window_event(|window, event| { builder = builder.on_window_event(|window, event| {
if window.label() != "main" || !matches!(event, tauri::WindowEvent::Destroyed) { if window.label() != "main" || !matches!(event, tauri::WindowEvent::Destroyed) {
@@ -638,6 +641,25 @@ fn demo_execution_metadata_metaplex_token_metadata_scenarios() -> std::result::R
return crate::demo_execution_metadata_metaplex_token_metadata_scenarios(); return crate::demo_execution_metadata_metaplex_token_metadata_scenarios();
} }
#[tauri::command]
fn demo_execution_metadata_metaplex_campaigns()
-> std::vec::Vec<crate::DemoExecutionMetadataMetaplexCampaignOptionPayload> {
return crate::demo_execution_metadata_metaplex_campaigns();
}
#[tauri::command]
async fn demo_execution_metadata_metaplex_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataMetaplexCampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexCampaignSummaryPayload,
std::string::String,
> {
return crate::demo_execution_metadata_metaplex_execute_campaign(app_handle, state, request)
.await;
}
#[tauri::command] #[tauri::command]
fn demo_execution_metadata_metaplex_token_metadata_current_operations() fn demo_execution_metadata_metaplex_token_metadata_current_operations()
-> std::vec::Vec<std::string::String> { -> std::vec::Vec<std::string::String> {
@@ -723,6 +745,19 @@ async fn demo_execution_metadata_solana_program_execute_campaign(
.await; .await;
} }
#[tauri::command]
async fn demo_execution_metadata_token_2022_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataToken2022CampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataToken2022CampaignSummaryPayload,
std::string::String,
> {
return crate::demo_execution_metadata_token_2022_execute_campaign(app_handle, state, request)
.await;
}
#[tauri::command] #[tauri::command]
async fn demo_execution_spl_token_execute( async fn demo_execution_spl_token_execute(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,