v0.5.2-pre.006-fix-010

This commit is contained in:
2026-08-11 12:03:35 +02:00
parent 1ee1d0297d
commit 56572cec40
67 changed files with 2484 additions and 539 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml # file: Cargo.toml
# version: 63 # version: 64
[workspace] [workspace]
resolver = "3" resolver = "3"
@@ -14,6 +14,7 @@ members = [
"ks-onchain-transport", "ks-onchain-transport",
"ks-store", "ks-store",
"ks-wallet", "ks-wallet",
"ks-wallet-demo-scenarios",
"kb-app-demo-desktop", "kb-app-demo-desktop",
] ]

View File

@@ -1,5 +1,5 @@
<!-- file: README.md --> <!-- file: README.md -->
<!-- version: 24 --> <!-- version: 25 -->
# Khadhroony Bot3 # Khadhroony Bot3
@@ -7,7 +7,7 @@
## Architecture ## Architecture
Le workspace contient onze crates : Le workspace contient douze crates :
- `ks-core` : erreurs et primitives transversales ; - `ks-core` : erreurs et primitives transversales ;
- `ks-config` : chargement et validation de la configuration ; - `ks-config` : chargement et validation de la configuration ;
@@ -19,9 +19,10 @@ Le workspace contient onze crates :
- `ks-onchain-transport` : transports RPC HTTP et WebSocket ; - `ks-onchain-transport` : transports RPC HTTP et WebSocket ;
- `ks-store` : contrats de stockage et adaptateur PostgreSQL ; - `ks-store` : contrats de stockage et adaptateur PostgreSQL ;
- `ks-wallet` : frontière de wallet et signataires ; - `ks-wallet` : frontière de wallet et signataires ;
- `ks-wallet-demo-scenarios` : scénarios réutilisables de validation du cycle de vie et des formats wallet ;
- `kb-app-demo-desktop` : application Tauri de démonstration et validation opérateur. - `kb-app-demo-desktop` : application Tauri de démonstration et validation opérateur.
La série `0.5.x` prépare la séparation explicite entre le domaine applicatif `khadhroony-bot` et les bibliothèques généralistes `khadhroony-solana`. Depuis `0.5.1`, les dix crates généralistes utilisent les noms `ks-*` / `ks_*`, tandis que `kb-app-demo-desktop` conserve son nom et reste une application du workspace Bot. Le workspace, le dépôt et le répertoire racine restent nommés `khadhroony-bot3` pendant cette migration et ne doivent pas être renommés avant `1.0` ou une version ultérieure explicitement dédiée. La politique de migration est définie dans [`docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md`](docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md). La série `0.5.x` prépare la séparation explicite entre le domaine applicatif `khadhroony-bot` et les bibliothèques généralistes `khadhroony-solana`. Depuis `0.5.1`, les dix crates généralistes issues de la migration utilisent les noms `ks-*` / `ks_*`. `0.5.2` ajoute `ks-wallet-demo-scenarios` comme onzième crate généraliste, tandis que `kb-app-demo-desktop` conserve son nom et reste une application du workspace Bot. Le workspace, le dépôt et le répertoire racine restent nommés `khadhroony-bot3` pendant cette migration et ne doivent pas être renommés avant `1.0` ou une version ultérieure explicitement dédiée. La politique de migration est définie dans [`docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md`](docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md).
Depuis `0.5.1`, les contrats de configuration source/runtime susceptibles de contenir des secrets restent backend-only et ne sont ni sérialisables ni `Debug` par défaut. Les crates `ks-config` et `ks-lib` ne génèrent plus de bindings TS-RS ; les surfaces TypeScript/Tauri sont possédées par les applications via des DTO explicites. Depuis `0.5.1`, les contrats de configuration source/runtime susceptibles de contenir des secrets restent backend-only et ne sont ni sérialisables ni `Debug` par défaut. Les crates `ks-config` et `ks-lib` ne génèrent plus de bindings TS-RS ; les surfaces TypeScript/Tauri sont possédées par les applications via des DTO explicites.

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md --> <!-- file: ROADMAP.md -->
<!-- version: 38 --> <!-- version: 39 -->
# ROADMAP — khadhroony-bot3 # ROADMAP — khadhroony-bot3
@@ -102,6 +102,7 @@ Les préfixes SQL historiques `kb_sol_*` restent volontairement inchangés jusqu
- définir explicitement la migration du format keypair JSON `0.4.8` si un nouveau format persistant est adopté ; - définir explicitement la migration du format keypair JSON `0.4.8` si un nouveau format persistant est adopté ;
- empêcher toute fuite de secret vers la configuration, les logs, Tauri ou les DTO publics ; - empêcher toute fuite de secret vers la configuration, les logs, Tauri ou les DTO publics ;
- conserver une API de signature réutilisable par les futurs exécuteurs sans couplage à un protocole particulier. - conserver une API de signature réutilisable par les futurs exécuteurs sans couplage à un protocole particulier.
- isoler les validations intégrées réutilisables dans `ks-wallet-demo-scenarios`, en commençant par le cycle de mot de passe A → B → rejet de A → ouverture/signature avec B → restauration vers A, puis réutiliser cette crate pour les futurs scénarios dimport/export et migration.
### 0.5.3 — audit et normalisation de `ks-store` ### 0.5.3 — audit et normalisation de `ks-store`

View File

@@ -465,6 +465,7 @@
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"wallet_dir", "wallet_dir",
"temporary_wallet_dir",
"cluster", "cluster",
"temporary_wallet_enabled", "temporary_wallet_enabled",
"temporary_wallet_alias", "temporary_wallet_alias",
@@ -474,6 +475,9 @@
"wallet_dir": { "wallet_dir": {
"$ref": "#/$defs/non_empty_string" "$ref": "#/$defs/non_empty_string"
}, },
"temporary_wallet_dir": {
"$ref": "#/$defs/non_empty_string"
},
"cluster": { "cluster": {
"$ref": "#/$defs/non_empty_string" "$ref": "#/$defs/non_empty_string"
}, },

View File

@@ -11,7 +11,8 @@
], ],
"properties": { "properties": {
"wallets_directory": { "wallets_directory": {
"$ref": "#/$defs/non_empty_string" "$ref": "#/$defs/non_empty_string",
"description": "Root directory dedicated to persistent native .kswallet containers."
}, },
"default_profile": { "default_profile": {
"$ref": "#/$defs/non_empty_string" "$ref": "#/$defs/non_empty_string"
@@ -45,7 +46,8 @@
"$ref": "#/$defs/non_empty_string" "$ref": "#/$defs/non_empty_string"
}, },
"directory": { "directory": {
"$ref": "#/$defs/non_empty_string" "$ref": "#/$defs/non_empty_string",
"description": "Profile-specific temporary wallet and fixture directory, relative to wallets_directory unless absolute."
}, },
"cluster": { "cluster": {
"$ref": "#/$defs/non_empty_string" "$ref": "#/$defs/non_empty_string"

View File

@@ -1,5 +1,5 @@
<!-- file: docs/README.md --> <!-- file: docs/README.md -->
<!-- version: 35 --> <!-- version: 36 -->
# Documentation active de Khadhroony Bot3 # Documentation active de Khadhroony Bot3
@@ -70,7 +70,7 @@ Elles peuvent être chargées directement par les tests unitaires ou dintégr
## 7. Documentation par crate ## 7. Documentation par crate
Les onze crates possèdent `README.md`, `TODO.md`, `USAGE.md` et `CHANGELOG.md` : Les douze crates possèdent `README.md`, `TODO.md`, `USAGE.md` et `CHANGELOG.md` :
- [`ks-core`](../ks-core/README.md) ; - [`ks-core`](../ks-core/README.md) ;
- [`ks-config`](../ks-config/README.md) ; - [`ks-config`](../ks-config/README.md) ;
@@ -82,6 +82,7 @@ Les onze crates possèdent `README.md`, `TODO.md`, `USAGE.md` et `CHANGELOG.md`
- [`ks-onchain-transport`](../ks-onchain-transport/README.md) ; - [`ks-onchain-transport`](../ks-onchain-transport/README.md) ;
- [`ks-store`](../ks-store/README.md) ; - [`ks-store`](../ks-store/README.md) ;
- [`ks-wallet`](../ks-wallet/README.md) ; - [`ks-wallet`](../ks-wallet/README.md) ;
- [`ks-wallet-demo-scenarios`](../ks-wallet-demo-scenarios/README.md) ;
- [`kb-app-demo-desktop`](../kb-app-demo-desktop/README.md). - [`kb-app-demo-desktop`](../kb-app-demo-desktop/README.md).
## 8. Guides ## 8. Guides

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/CRATE_MAP.md --> <!-- file: docs/architecture/CRATE_MAP.md -->
<!-- version: 7 --> <!-- version: 8 -->
# Carte des crates # Carte des crates
@@ -17,9 +17,10 @@
| `ks-onchain-transport` | bibliothèque | transports RPC HTTP/WebSocket et pools dendpoints | README/TODO/USAGE/CHANGELOG présents | | `ks-onchain-transport` | bibliothèque | transports RPC HTTP/WebSocket et pools dendpoints | README/TODO/USAGE/CHANGELOG présents |
| `ks-store` | bibliothèque | contrats de stockage et adaptateur PostgreSQL | nom `ks-*` actif ; normalisation `0.5.3` | | `ks-store` | bibliothèque | contrats de stockage et adaptateur PostgreSQL | nom `ks-*` actif ; normalisation `0.5.3` |
| `ks-wallet` | bibliothèque | wallet temporaire et frontière de signataire | nom `ks-*` actif ; refonte `0.5.2` | | `ks-wallet` | bibliothèque | wallet temporaire et frontière de signataire | nom `ks-*` actif ; refonte `0.5.2` |
| `ks-wallet-demo-scenarios` | bibliothèque | scénarios de validation réutilisables du cycle de vie et des formats wallet | ajoutée en `0.5.2` |
| `kb-app-demo-desktop` | bibliothèque + binaire | application de démonstration Tauri | documentée ; réconciliation en `0.5.4` | | `kb-app-demo-desktop` | bibliothèque + binaire | application de démonstration Tauri | documentée ; réconciliation en `0.5.4` |
La table décrit les noms physiques stabilisés par `0.5.1`. La migration a renommé les dix crates généralistes en `ks-core`, `ks-config`, `ks-lib`, `ks-logging`, `ks-program-ids`, `ks-pipeline`, `ks-pipeline-demo-scenarios`, `ks-onchain-transport`, `ks-store` et `ks-wallet`. `kb-app-demo-desktop` conserve son nom parce quil appartient au domaine applicatif Bot. Voir [`../decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md`](../decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md). La table décrit les noms physiques stabilisés par `0.5.1`. La migration a renommé les dix crates généralistes en `ks-core`, `ks-config`, `ks-lib`, `ks-logging`, `ks-program-ids`, `ks-pipeline`, `ks-pipeline-demo-scenarios`, `ks-onchain-transport`, `ks-store` et `ks-wallet`. `0.5.2` ajoute ensuite `ks-wallet-demo-scenarios` sans modifier cette histoire de migration. `kb-app-demo-desktop` conserve son nom parce quil appartient au domaine applicatif Bot. Voir [`../decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md`](../decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md).
## 2. Consolidations principales depuis bot2 ## 2. Consolidations principales depuis bot2
@@ -30,7 +31,8 @@ La migration a regroupé de nombreuses anciennes crates dans des frontières plu
- les responsabilités de pipeline ont été réunies dans `ks-pipeline` ; - les responsabilités de pipeline ont été réunies dans `ks-pipeline` ;
- les transports Solana sont réunis dans `ks-onchain-transport` ; - les transports Solana sont réunis dans `ks-onchain-transport` ;
- lapplication et sa bibliothèque sont réunies dans `kb-app-demo-desktop` ; - lapplication et sa bibliothèque sont réunies dans `kb-app-demo-desktop` ;
- les scénarios réutilisables ont été extraits dans `ks-pipeline-demo-scenarios`. - les scénarios réutilisables ont été extraits dans `ks-pipeline-demo-scenarios` ;
- les validations wallet réutilisables sont isolées dans `ks-wallet-demo-scenarios` à partir de `0.5.2`.
Cette carte nest pas une table de compatibilité exhaustive des anciennes crates. Les correspondances historiques détaillées seront synthétisées dans la documentation de migration et laudit dalignement. Cette carte nest pas une table de compatibilité exhaustive des anciennes crates. Les correspondances historiques détaillées seront synthétisées dans la documentation de migration et laudit dalignement.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/V0_5_2_KS_WALLET_RESTRUCTURING_PLAN.md --> <!-- file: docs/plans/V0_5_2_KS_WALLET_RESTRUCTURING_PLAN.md -->
<!-- version: 9 --> <!-- version: 11 -->
# Plan `0.5.2` — restructuration de `ks-wallet` # Plan `0.5.2` — restructuration de `ks-wallet`
@@ -30,7 +30,7 @@ Le plan doit rester centré sur ce besoin. Les détails cryptographiques ou de c
Un consommateur ne doit pas avoir besoin de connaître le chemin réel du fichier ou son format interne pour travailler avec un wallet. Un consommateur ne doit pas avoir besoin de connaître le chemin réel du fichier ou son format interne pour travailler avec un wallet.
La découverte des wallets persistants doit être faite par `ks-wallet` dans le répertoire de wallet résolu qu'il possède. Avec la configuration actuelle, la racine globale provient de `wallet.config.json.wallets_directory`, surchargeable par `KS_WALLETS_DIRECTORY` et valant `wallets` par défaut ; un profil peut encore résoudre un sous-répertoire. La découverte des wallets persistants doit être faite par `ks-wallet` directement dans la racine globale `wallet.config.json.wallets_directory`, surchargeable par `KS_WALLETS_DIRECTORY` et valant `wallets` par défaut. Les sous-répertoires propres aux profils sont réservés aux wallets/keypairs temporaires et sont exposés séparément par `temporary_wallet_dir`.
Le store doit donc pouvoir scanner son répertoire résolu à la recherche des fichiers : Le store doit donc pouvoir scanner son répertoire résolu à la recherche des fichiers :
@@ -543,9 +543,15 @@ La tranche `pre.003` reste structurelle ; le cycle de vie protégé est activé
- [x] DTO desktop possédés par `kb-app-demo-desktop` pour liste/inspection : alias, pubkey déclarée, version et sélection uniquement ; - [x] DTO desktop possédés par `kb-app-demo-desktop` pour liste/inspection : alias, pubkey déclarée, version et sélection uniquement ;
- [x] aucune commande Tauri de password/unlock et aucun type runtime sensible `ks-wallet` sérialisé vers le frontend ; - [x] aucune commande Tauri de password/unlock et aucun type runtime sensible `ks-wallet` sérialisé vers le frontend ;
- [x] workflow CLI Token-2022 explicitement limité au format Solana CLI JSON et rejet des `.kswallet` ; - [x] workflow CLI Token-2022 explicitement limité au format Solana CLI JSON et rejet des `.kswallet` ;
- [x] tests de configuration, sélection persistante, unlock explicite, refus du fallback et canaris de non-divulgation. - [x] tests de configuration, sélection persistante, unlock explicite, refus du fallback et canaris de non-divulgation ;
- [x] séparation de `wallet_dir` persistant (`wallets_directory`, donc `wallets/` par défaut) et `temporary_wallet_dir` propre au profil (`wallets/temporary/<profil>` par défaut) ;
- [x] résolution d'une capacité `DevnetExecutionWallet` commune : `.kswallet` authentifié quand `wallet_alias` est défini, wallet temporaire uniquement quand aucune sélection persistante n'existe ;
- [x] propagation de cette capacité aux démos System, Memo, ATA, SPL Token, Token-2022, Solana Program Metadata, Token-2022 Metadata et à toutes les campagnes Metaplex qualifiées ;
- [x] sélection session-only d'un alias `.kswallet` par profil Devnet depuis `demo_wallet`, prioritaire sur `wallet_alias` sans réécriture de la configuration et authentifiée avant activation ;
- [x] export secret desktop contraint au répertoire applicatif `data/wallets/`, avec nom de fichier borné et permissions privées ;
- [x] conservation des keypairs réellement secondaires et jetables — mints, delegates, destinations et authorities de fixtures — dans le répertoire temporaire.
Cette tranche ne devient pas la refonte générale des scénarios prévue pour `0.5.4`. Les campagnes existantes restent temporaires lorsque `wallet_alias = null`; leur migration fonctionnelle vers un wallet persistant avec acquisition sécurisée du password appartient au consommateur qui choisit d'activer cette sélection. Cette tranche ne devient pas la refonte générale des scénarios prévue pour `0.5.4`. Lorsque `wallet_alias = null`, les campagnes conservent leur comportement temporaire historique ; lorsqu'il est défini, le signer opérateur/payer doit provenir du `.kswallet` et aucun fallback JSON n'est autorisé.
### `0.5.2-pre.007` — finalisation ### `0.5.2-pre.007` — finalisation

View File

@@ -1,8 +1,26 @@
<!-- file: kb-app-demo-desktop/CHANGELOG.md --> <!-- file: kb-app-demo-desktop/CHANGELOG.md -->
<!-- version: 57 --> <!-- version: 60 -->
# CHANGELOG — kb-app-demo-desktop # CHANGELOG — kb-app-demo-desktop
## `0.5.2-pre.006-delta-fix-009`
- corrige les adaptateurs Tauri Devnet qui transmettaient directement `tauri::State<AppState>` aux helpers wallet/projection attendant `&AppState`; les commandes empruntent désormais explicitement `&state` pour System, SPL et les trois familles Metadata ;
- ne modifie ni la sélection runtime du `.kswallet`, ni les chemins dexport `data/wallets/`, ni les contrats de configuration introduits par les fixes précédents.
## `0.5.2-pre.006-delta-fix-008`
- ajoute dans `demo_wallet` une sélection session-only du `.kswallet` d'exécution par profil Devnet ; l'alias est authentifié côté backend avant activation, ne réécrit pas `wallet.config.json` et devient prioritaire sur `wallet_alias` pour les démos suivantes ;
- propage cette sélection runtime à tous les adaptateurs desktop System, Memo, ATA, SPL Token, Token-2022 et Metadata, tout en conservant le fallback temporaire uniquement lorsqu'aucun alias runtime ou configuré n'existe ;
- contraint les exports secrets de la démo à `./data/wallets/`, remplace le chemin arbitraire par un nom de fichier borné, impose l'extension du format et sécurise le répertoire en `0700` sur Unix ;
- affiche explicitement les alias configuré, runtime et effectif afin que la validation Devnet puisse confirmer le passage de `persistence="temporary"` à `persistence="persistent"`.
## `0.5.2-pre.006-delta-fix-005`
- résout côté backend le signer Devnet sélectionné avant les démos System, Memo, ATA, SPL Token, Token-2022, Solana Program Metadata, Token-2022 Metadata ainsi que toutes les campagnes Metaplex qualifiées, y compris leurs parcours multi-signers ;
- utilise `KB_SECRET_DEMO_WALLET_PASSWORD` uniquement lorsque `wallet_alias` sélectionne un `.kswallet`, sans transmettre le password à Tauri ou TypeScript ;
- conserve les répertoires de fixtures sous `temporary_wallet_dir` tandis que `demo_wallet` inventorie et importe désormais dans la racine persistante `wallet_dir`.
## `0.5.2-pre.006-delta-fix-004` ## `0.5.2-pre.006-delta-fix-004`
- complète la fenêtre `Wallets` avec inspection publique, import et export des deux formats `ks-wallet` réellement supportés : Solana CLI JSON 64 octets et keypair Base58 64 octets ; - complète la fenêtre `Wallets` avec inspection publique, import et export des deux formats `ks-wallet` réellement supportés : Solana CLI JSON 64 octets et keypair Base58 64 octets ;

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/README.md --> <!-- file: kb-app-demo-desktop/README.md -->
<!-- version: 20 --> <!-- version: 21 -->
# kb-app-demo-desktop # kb-app-demo-desktop
@@ -8,7 +8,7 @@
## Fonctionnalités ## Fonctionnalités
- configuration et diagnostics de logging ; - configuration et diagnostics de logging ;
- fenêtre Wallets dédiée : inventaire sûr des identités `.kswallet`, création dun conteneur persistant via un secret backend-only, inspection/import/export des formats keypair supportés, extraction sûre de pubkey depuis un keypair legacy, inspection explicite dun `.kswallet` externe et exploration publique du solde SOL, des comptes SPL Token / Token-2022 et des transactions récentes sur un profil RPC explicitement sélectionnable ; - fenêtre Wallets dédiée : inventaire sûr des identités `.kswallet`, création dun conteneur persistant via un secret backend-only, sélection session-only du wallet d'exécution Devnet, inspection/import/export des formats keypair supportés sous `data/wallets/`, extraction sûre de pubkey depuis un keypair legacy, inspection explicite dun `.kswallet` externe et exploration publique du solde SOL, des comptes SPL Token / Token-2022 et des transactions récentes sur un profil RPC explicitement sélectionnable ;
- transport HTTP JSON-RPC et WebSocket ; - transport HTTP JSON-RPC et WebSocket ;
- 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 ;

View File

@@ -1,11 +1,11 @@
<!-- file: kb-app-demo-desktop/TODO.md --> <!-- file: kb-app-demo-desktop/TODO.md -->
<!-- version: 17 --> <!-- version: 19 -->
# TODO — kb-app-demo-desktop # TODO — kb-app-demo-desktop
## Évolutions générales ## Évolutions générales
- [ ] `0.5.2` — valider les démos Devnet Solana Core/SPL/Metadata avec un `.kswallet` sélectionné et déverrouillé côté backend, sans transmettre le password au frontend. - [ ] `0.5.2` — valider sur Devnet la sélection session-only dun `.kswallet` dans `demo_wallet`, confirmer `persistence="persistent"` dans les logs puis retrouver les signatures produites depuis lexplorateur.
- [ ] `0.5.4` — réduire les panneaux d'exécution à l'adaptation UI/Tauri en déplaçant toute orchestration de campagne encore réutilisable vers `ks-pipeline-demo-scenarios`. - [ ] `0.5.4` — réduire les panneaux d'exécution à l'adaptation UI/Tauri en déplaçant toute orchestration de campagne encore réutilisable vers `ks-pipeline-demo-scenarios`.
- [ ] 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.

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/USAGE.md --> <!-- file: kb-app-demo-desktop/USAGE.md -->
<!-- version: 20 --> <!-- version: 21 -->
# Utilisation de kb-app-demo-desktop # Utilisation de kb-app-demo-desktop
@@ -11,9 +11,10 @@ Lapplication expose une interface opérateur pour diagnostiquer, simuler et v
Le backend desktop peut lister les `.kswallet` du répertoire configuré, créer un wallet natif protégé, inspecter un fichier sélectionné explicitement ailleurs et explorer létat on-chain dune adresse publique. Les DTO frontend ne contiennent jamais de matériau secret. Aucun password, chemin interne du store, sel, nonce, ciphertext ou byte privé nest renvoyé par Tauri. Le backend desktop peut lister les `.kswallet` du répertoire configuré, créer un wallet natif protégé, inspecter un fichier sélectionné explicitement ailleurs et explorer létat on-chain dune adresse publique. Les DTO frontend ne contiennent jamais de matériau secret. Aucun password, chemin interne du store, sel, nonce, ciphertext ou byte privé nest renvoyé par Tauri.
La fenêtre `Wallets`, accessible depuis le menu `Démos` de `main`, fournit maintenant six surfaces : La fenêtre `Wallets`, accessible depuis le menu `Démos` de `main`, fournit maintenant sept surfaces :
- inventaire des wallets natifs du profil actif ; - inventaire des wallets natifs du profil actif ;
- sélection session-only du `.kswallet` utilisé par les démos Devnet, sans réécrire la configuration ;
- création dun nouveau `.kswallet` dans le store du profil actif ; - création dun nouveau `.kswallet` dans le store du profil actif ;
- inspection et import d'un keypair externe dans un format supporté ; - inspection et import d'un keypair externe dans un format supporté ;
- export authentifié d'un `.kswallet` vers un format supporté ; - export authentifié d'un `.kswallet` vers un format supporté ;
@@ -42,10 +43,22 @@ Les sélecteurs de format sont alimentés par `ks-wallet` et exposent actuelleme
L'action **Inspecter la keypair** ne crée aucun `.kswallet` : le backend valide le fichier avec le codec choisi et ne renvoie que la pubkey. Cette pubkey peut être envoyée directement à l'explorateur on-chain, ce qui permet par exemple de contrôler un ancien `wallets/temporary/local_devnet/*.json` avant de décider de le migrer. L'action **Inspecter la keypair** ne crée aucun `.kswallet` : le backend valide le fichier avec le codec choisi et ne renvoie que la pubkey. Cette pubkey peut être envoyée directement à l'explorateur on-chain, ce qui permet par exemple de contrôler un ancien `wallets/temporary/local_devnet/*.json` avant de décider de le migrer.
L'action **Importer en .kswallet** demande en plus un alias destination et utilise `KB_SECRET_DEMO_WALLET_PASSWORD` uniquement côté backend. La source reste inchangée. L'export fonctionne dans le sens inverse : choix d'un alias natif, du format et d'un chemin destination explicite ; le backend authentifie le `.kswallet` avec le même secret de démonstration et refuse d'écraser un fichier existant. L'action **Importer en .kswallet** demande en plus un alias destination et utilise `KB_SECRET_DEMO_WALLET_PASSWORD` uniquement côté backend. La source reste inchangée. L'export fonctionne dans le sens inverse : choix d'un alias natif, du format et d'un nom de fichier ; le backend authentifie le `.kswallet` avec le même secret de démonstration, refuse d'écraser un fichier existant et contraint la destination à `./data/wallets/`. Le répertoire d'export privé est créé avec des permissions `0700` sur Unix et `ks-wallet` conserve les fichiers exportés en `0600`.
Un keypair JSON de 64 octets ne contient pas son rôle métier. L'inspection peut dire « keypair Solana valide » et donner sa pubkey, mais elle ne peut pas décider localement si cette clé a servi de wallet opérateur, mint, authority ou autre fixture. Cette classification nécessite le contexte du scénario ou une lecture on-chain séparée. Un keypair JSON de 64 octets ne contient pas son rôle métier. L'inspection peut dire « keypair Solana valide » et donner sa pubkey, mais elle ne peut pas décider localement si cette clé a servi de wallet opérateur, mint, authority ou autre fixture. Cette classification nécessite le contexte du scénario ou une lecture on-chain séparée.
### Sélectionner le `.kswallet` utilisé par les démos Devnet
La configuration persistante continue d'utiliser `wallet_alias`, mais la fenêtre Wallets permet également de choisir un alias pour un profil Devnet uniquement pendant la session desktop. Cette sélection est authentifiée immédiatement avec `KB_SECRET_DEMO_WALLET_PASSWORD`, ne réécrit aucun JSON et s'applique aux exécutions System, Memo, ATA, SPL Token, Token-2022 et Metadata suivantes.
Le resolver applique l'ordre suivant :
1. override session desktop pour le profil, lorsqu'il existe ;
2. `wallet_alias` du profil ;
3. wallet temporaire historique uniquement si les deux précédents sont absents.
Un override valide ne peut donc pas retomber silencieusement sur `temporary_wallet_alias`. Le log `ks-pipeline-demo-scenarios.wallet` doit alors afficher `persistence="persistent"` avec l'alias et la pubkey sélectionnés.
### Explorer une adresse sur un profil RPC sélectionné ### Explorer une adresse sur un profil RPC sélectionné
Lexplorateur on-chain ne déverrouille aucun wallet : seule la pubkey publique est nécessaire. Le bouton `Explorer` dun wallet inventorié remplit ladresse automatiquement ; toute autre pubkey Solana peut également être saisie. Lexplorateur on-chain ne déverrouille aucun wallet : seule la pubkey publique est nécessaire. Le bouton `Explorer` dun wallet inventorié remplit ladresse automatiquement ; toute autre pubkey Solana peut également être saisie.

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/demo_wallet.html --> <!-- file: kb-app-demo-desktop/frontend/demo_wallet.html -->
<!-- version: 4 --> <!-- version: 5 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -49,6 +49,33 @@
<dt class="col-sm-3">Wallets trouvés</dt> <dt class="col-sm-3">Wallets trouvés</dt>
<dd class="col-sm-9"><code id="walletInventoryCount">0</code></dd> <dd class="col-sm-9"><code id="walletInventoryCount">0</code></dd>
</dl> </dl>
<div class="border rounded p-3 mb-3">
<h2 class="h6 mb-2">Wallet d'exécution des démos Devnet</h2>
<p class="small text-body-secondary mb-3">La sélection est propre à cette session desktop. Elle ne modifie pas <code>wallet.config.json</code> et remplace <code>wallet_alias</code> uniquement pour les démos du profil Devnet choisi.</p>
<div class="row g-3 align-items-end">
<div class="col-12 col-lg-4">
<label for="walletExecutionProfileSelect" class="form-label">Profil Devnet</label>
<select id="walletExecutionProfileSelect" class="form-select"></select>
</div>
<div class="col-12 col-lg-5">
<label for="walletExecutionAliasSelect" class="form-label">Wallet d'exécution</label>
<select id="walletExecutionAliasSelect" class="form-select"></select>
</div>
<div class="col-12 col-lg-3">
<button id="applyWalletExecutionSelectionButton" class="btn btn-outline-primary w-100" type="button">Appliquer</button>
</div>
</div>
<dl class="row small mb-0 mt-3">
<dt class="col-sm-4">Configuration</dt>
<dd class="col-sm-8"><code id="walletExecutionConfiguredAlias"></code></dd>
<dt class="col-sm-4">Override session</dt>
<dd class="col-sm-8"><code id="walletExecutionRuntimeAlias"></code></dd>
<dt class="col-sm-4">Effectif</dt>
<dd class="col-sm-8"><code id="walletExecutionEffectiveAlias">temporaire</code></dd>
<dt class="col-sm-4">Statut</dt>
<dd class="col-sm-8"><code id="walletExecutionSelectionStatus">En attente</code></dd>
</dl>
</div>
<div class="table-responsive app-table-full-width"> <div class="table-responsive app-table-full-width">
<table class="table table-sm align-middle mb-0" id="walletInventoryTable"> <table class="table table-sm align-middle mb-0" id="walletInventoryTable">
<thead> <thead>
@@ -56,7 +83,7 @@
<th>Alias</th> <th>Alias</th>
<th>Pubkey déclarée</th> <th>Pubkey déclarée</th>
<th>Format</th> <th>Format</th>
<th>Sélection</th> <th>Exécution</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
@@ -128,13 +155,16 @@
<div class="card shadow-sm border-0 h-100"> <div class="card shadow-sm border-0 h-100">
<div class="card-body"> <div class="card-body">
<h2 class="h5 card-title mb-2">Exporter un wallet natif</h2> <h2 class="h5 card-title mb-2">Exporter un wallet natif</h2>
<p class="text-body-secondary">L'export exige le même secret backend que la création et refuse d'écraser un fichier existant.</p> <p class="text-body-secondary">L'export exige le même secret backend que la création, refuse d'écraser un fichier existant et écrit uniquement dans <code>./data/wallets/</code>.</p>
<label for="walletExportAliasSelect" class="form-label">Wallet</label> <label for="walletExportAliasSelect" class="form-label">Wallet</label>
<select id="walletExportAliasSelect" class="form-select"></select> <select id="walletExportAliasSelect" class="form-select"></select>
<label for="walletExportFormatSelect" class="form-label mt-3">Format d'export</label> <label for="walletExportFormatSelect" class="form-label mt-3">Format d'export</label>
<select id="walletExportFormatSelect" class="form-select"></select> <select id="walletExportFormatSelect" class="form-select"></select>
<label for="walletExportDestinationPathInput" class="form-label mt-3">Chemin destination</label> <label for="walletExportFileNameInput" class="form-label mt-3">Nom du fichier</label>
<input id="walletExportDestinationPathInput" class="form-control font-monospace" type="text" autocomplete="off" placeholder="/chemin/vers/export.json"> <div class="input-group">
<span id="walletExportDirectory" class="input-group-text font-monospace">data/wallets/</span>
<input id="walletExportFileNameInput" class="form-control font-monospace" type="text" autocomplete="off" maxlength="128" placeholder="operator.json">
</div>
<div class="d-flex flex-wrap gap-2 mt-3"> <div class="d-flex flex-wrap gap-2 mt-3">
<button id="exportWalletTransferButton" class="btn btn-primary" type="button">Exporter</button> <button id="exportWalletTransferButton" class="btn btn-primary" type="button">Exporter</button>
</div> </div>

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_wallet.ts // file: kb-app-demo-desktop/frontend/ts/demo_wallet.ts
// version: 4 // version: 5
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
import "simplebar"; import "simplebar";
@@ -10,6 +10,8 @@ import { renderJsonViewer } from "./json_viewer.ts";
import type { DemoWalletBalancePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletBalancePayload.ts"; import type { DemoWalletBalancePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletBalancePayload.ts";
import type { DemoWalletCreateRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletCreateRequest.ts"; import type { DemoWalletCreateRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletCreateRequest.ts";
import type { DemoWalletExportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExportRequest.ts"; import type { DemoWalletExportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExportRequest.ts";
import type { DemoWalletExecutionProfilePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionProfilePayload.ts";
import type { DemoWalletExecutionSelectionRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionSelectionRequest.ts";
import type { DemoWalletIdentityPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletIdentityPayload.ts"; import type { DemoWalletIdentityPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletIdentityPayload.ts";
import type { DemoWalletImportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletImportRequest.ts"; import type { DemoWalletImportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletImportRequest.ts";
import type { DemoWalletInventoryPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletInventoryPayload.ts"; import type { DemoWalletInventoryPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletInventoryPayload.ts";
@@ -96,6 +98,102 @@ function renderOnchainProfiles(profiles: ReadonlyArray<DemoWalletOnchainProfileP
updateOnchainClusterLabel(); updateOnchainClusterLabel();
} }
function selectedExecutionProfilePayload(): DemoWalletExecutionProfilePayload | null {
const profile = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.value.trim() ?? "";
if (!walletInventory || profile.length === 0) {
return null;
}
return walletInventory.execution_profiles.find(candidate => candidate.name === profile) ?? null;
}
function renderExecutionProfiles(profiles: ReadonlyArray<DemoWalletExecutionProfilePayload>): void {
const select = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect");
if (!select) {
return;
}
const previous = select.value;
select.replaceChildren();
for (const profile of profiles) {
const option = document.createElement("option");
option.value = profile.name;
option.textContent = profile.name;
select.appendChild(option);
}
if (profiles.some(profile => profile.name === previous)) {
select.value = previous;
} else if (profiles.length > 0) {
select.value = profiles[0].name;
}
}
function renderExecutionWalletAliases(wallets: ReadonlyArray<DemoWalletIdentityPayload>): void {
const select = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect");
if (!select) {
return;
}
select.replaceChildren();
const configuredOption = document.createElement("option");
configuredOption.value = "";
configuredOption.textContent = "Utiliser la configuration du profil";
select.appendChild(configuredOption);
for (const wallet of wallets) {
const option = document.createElement("option");
option.value = wallet.alias;
option.textContent = `${wallet.alias}${wallet.public_key}`;
select.appendChild(option);
}
}
function updateExecutionSelectionDisplay(): void {
const profile = selectedExecutionProfilePayload();
const aliasSelect = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect");
if (!profile) {
setText("#walletExecutionConfiguredAlias", "—");
setText("#walletExecutionRuntimeAlias", "—");
setText("#walletExecutionEffectiveAlias", "temporaire");
if (aliasSelect) {
aliasSelect.value = "";
}
return;
}
setText("#walletExecutionConfiguredAlias", profile.configured_alias ?? "—");
setText("#walletExecutionRuntimeAlias", profile.runtime_alias ?? "—");
setText("#walletExecutionEffectiveAlias", profile.effective_alias ?? "temporaire");
if (aliasSelect) {
const requested = profile.runtime_alias ?? "";
if (Array.from(aliasSelect.options).some(option => option.value === requested)) {
aliasSelect.value = requested;
} else {
aliasSelect.value = "";
}
}
}
async function applyExecutionWalletSelection(): Promise<void> {
const profile = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.value.trim() ?? "";
const aliasText = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect")?.value.trim() ?? "";
if (profile.length === 0) {
setText("#walletExecutionSelectionStatus", "Profil Devnet requis");
return;
}
const request: DemoWalletExecutionSelectionRequest = {
profile,
alias: aliasText.length === 0 ? null : aliasText,
};
setText("#walletExecutionSelectionStatus", "Validation...");
try {
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_select_execution_wallet", { request });
applyWalletInventoryPayload(payload);
const effective = payload.execution_profiles.find(candidate => candidate.name === profile)?.effective_alias ?? null;
setText("#walletExecutionSelectionStatus", effective ? `Persistant : ${effective}` : "Configuration du profil");
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `execution wallet selection updated for ${profile}`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletExecutionSelectionStatus", `Erreur : ${message}`);
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Execution wallet selection failed: ${message}`);
}
}
async function executeWalletRpc(profile: string, method: string, params: unknown[]): Promise<DemoWalletRpcExecutionPayload> { async function executeWalletRpc(profile: string, method: string, params: unknown[]): Promise<DemoWalletRpcExecutionPayload> {
const request: DemoWalletRpcRequest = { const request: DemoWalletRpcRequest = {
profile, profile,
@@ -159,6 +257,28 @@ function selectedTransferFormat(selector: string): string {
return document.querySelector<HTMLSelectElement>(selector)?.value.trim() ?? ""; return document.querySelector<HTMLSelectElement>(selector)?.value.trim() ?? "";
} }
function selectedExportFormatPayload(): DemoWalletTransferFormatPayload | null {
const code = selectedTransferFormat("#walletExportFormatSelect");
if (!walletInventory || code.length === 0) {
return null;
}
return walletInventory.transfer_formats.find(format => format.code === code) ?? null;
}
function updateExportFileName(force: boolean): void {
const input = document.querySelector<HTMLInputElement>("#walletExportFileNameInput");
const alias = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.value.trim() ?? "";
const format = selectedExportFormatPayload();
if (!input || alias.length === 0 || !format) {
return;
}
const generated = `${alias}.${format.default_extension}`;
if (force || input.value.trim().length === 0 || input.dataset.autoGenerated === "true") {
input.value = generated;
input.dataset.autoGenerated = "true";
}
}
function setTransferPublicKey(publicKey: string): void { function setTransferPublicKey(publicKey: string): void {
inspectedTransferPublicKey = publicKey; inspectedTransferPublicKey = publicKey;
setText("#walletTransferPublicKey", publicKey.length > 0 ? publicKey : "—"); setText("#walletTransferPublicKey", publicKey.length > 0 ? publicKey : "—");
@@ -196,7 +316,8 @@ function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): vo
publicKeyCell.textContent = wallet.public_key; publicKeyCell.textContent = wallet.public_key;
publicKeyCell.className = "font-monospace text-break"; publicKeyCell.className = "font-monospace text-break";
formatCell.textContent = `v${wallet.format_version}`; formatCell.textContent = `v${wallet.format_version}`;
selectedCell.textContent = wallet.selected ? "Oui" : "Non"; const executionSelected = selectedExecutionProfilePayload()?.effective_alias === wallet.alias;
selectedCell.textContent = executionSelected ? "Oui" : "Non";
exploreButton.type = "button"; exploreButton.type = "button";
exploreButton.className = "btn btn-sm btn-outline-primary"; exploreButton.className = "btn btn-sm btn-outline-primary";
exploreButton.textContent = "Explorer"; exploreButton.textContent = "Explorer";
@@ -208,7 +329,7 @@ function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): vo
document.querySelector<HTMLElement>("#walletAddressInput")?.scrollIntoView({ behavior: "smooth", block: "center" }); document.querySelector<HTMLElement>("#walletAddressInput")?.scrollIntoView({ behavior: "smooth", block: "center" });
}); });
actionCell.appendChild(exploreButton); actionCell.appendChild(exploreButton);
if (wallet.selected) { if (executionSelected) {
row.classList.add("table-primary"); row.classList.add("table-primary");
} }
row.append(aliasCell, publicKeyCell, formatCell, selectedCell, actionCell); row.append(aliasCell, publicKeyCell, formatCell, selectedCell, actionCell);
@@ -216,21 +337,30 @@ function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): vo
} }
} }
async function refreshWalletInventory(): Promise<void> { function applyWalletInventoryPayload(payload: DemoWalletInventoryPayload): void {
setText("#walletInventoryStatus", "Chargement...");
try {
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_inventory");
walletInventory = payload; walletInventory = payload;
setText("#walletProfile", payload.profile); setText("#walletProfile", payload.profile);
setText("#walletCluster", payload.cluster); setText("#walletCluster", payload.cluster);
renderOnchainProfiles(payload.onchain_profiles, payload.profile); renderOnchainProfiles(payload.onchain_profiles, payload.profile);
renderExecutionProfiles(payload.execution_profiles);
renderTransferFormats(payload.transfer_formats); renderTransferFormats(payload.transfer_formats);
renderExportWalletAliases(payload.wallets); renderExportWalletAliases(payload.wallets);
renderExecutionWalletAliases(payload.wallets);
setText("#walletCreateSecretStatus", payload.create_password_configured ? "configuré" : "absent"); setText("#walletCreateSecretStatus", payload.create_password_configured ? "configuré" : "absent");
setText("#walletSelectedAlias", payload.selected_alias ?? "—"); setText("#walletSelectedAlias", payload.selected_alias ?? "—");
setText("#walletInventoryCount", String(payload.wallets.length)); setText("#walletInventoryCount", String(payload.wallets.length));
setText("#walletExportDirectory", `${payload.export_directory}/`);
setText("#walletInventoryStatus", "OK"); setText("#walletInventoryStatus", "OK");
renderWalletRows(payload.wallets); renderWalletRows(payload.wallets);
updateExecutionSelectionDisplay();
updateExportFileName(false);
}
async function refreshWalletInventory(): Promise<void> {
setText("#walletInventoryStatus", "Chargement...");
try {
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_inventory");
applyWalletInventoryPayload(payload);
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", "wallet inventory refreshed"); frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", "wallet inventory refreshed");
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
@@ -238,6 +368,7 @@ async function refreshWalletInventory(): Promise<void> {
setText("#walletInventoryCount", "0"); setText("#walletInventoryCount", "0");
renderWalletRows([]); renderWalletRows([]);
renderExportWalletAliases([]); renderExportWalletAliases([]);
renderExecutionWalletAliases([]);
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Wallet inventory loading failed: ${message}`); frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Wallet inventory loading failed: ${message}`);
} }
} }
@@ -324,16 +455,16 @@ async function importTransferFile(): Promise<void> {
async function exportWallet(): Promise<void> { async function exportWallet(): Promise<void> {
const alias = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.value.trim() ?? ""; const alias = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.value.trim() ?? "";
const format = selectedTransferFormat("#walletExportFormatSelect"); const format = selectedTransferFormat("#walletExportFormatSelect");
const destinationPath = inputValue("#walletExportDestinationPathInput"); const fileName = inputValue("#walletExportFileNameInput");
if (alias.length === 0 || format.length === 0 || destinationPath.length === 0) { if (alias.length === 0 || format.length === 0 || fileName.length === 0) {
setText("#walletExportStatus", "Wallet, format et destination requis"); setText("#walletExportStatus", "Wallet, format et nom de fichier requis");
return; return;
} }
const request: DemoWalletExportRequest = { alias, destination_path: destinationPath, format }; const request: DemoWalletExportRequest = { alias, file_name: fileName, format };
setText("#walletExportStatus", "Export..."); setText("#walletExportStatus", "Export...");
try { try {
await invoke<void>("demo_wallet_export_file", { request }); const exportedPath = await invoke<string>("demo_wallet_export_file", { request });
setText("#walletExportStatus", `Exporté : ${alias}`); setText("#walletExportStatus", `Exporté : ${exportedPath}`);
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `native wallet exported: ${alias}`); frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `native wallet exported: ${alias}`);
} catch (caughtError) { } catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError); const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
@@ -647,6 +778,16 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector<HTMLButtonElement>("#refreshWalletInventoryButton")?.addEventListener("click", () => { document.querySelector<HTMLButtonElement>("#refreshWalletInventoryButton")?.addEventListener("click", () => {
void refreshWalletInventory(); void refreshWalletInventory();
}); });
document.querySelector<HTMLButtonElement>("#applyWalletExecutionSelectionButton")?.addEventListener("click", () => {
void applyExecutionWalletSelection();
});
document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.addEventListener("change", () => {
updateExecutionSelectionDisplay();
if (walletInventory) {
renderWalletRows(walletInventory.wallets);
}
setText("#walletExecutionSelectionStatus", "En attente");
});
document.querySelector<HTMLButtonElement>("#createWalletButton")?.addEventListener("click", () => { document.querySelector<HTMLButtonElement>("#createWalletButton")?.addEventListener("click", () => {
void createWallet(); void createWallet();
}); });
@@ -668,6 +809,16 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector<HTMLButtonElement>("#exportWalletTransferButton")?.addEventListener("click", () => { document.querySelector<HTMLButtonElement>("#exportWalletTransferButton")?.addEventListener("click", () => {
void exportWallet(); void exportWallet();
}); });
document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.addEventListener("change", () => {
updateExportFileName(false);
});
document.querySelector<HTMLSelectElement>("#walletExportFormatSelect")?.addEventListener("change", () => {
updateExportFileName(false);
});
document.querySelector<HTMLInputElement>("#walletExportFileNameInput")?.addEventListener("input", event => {
const input = event.currentTarget as HTMLInputElement;
input.dataset.autoGenerated = "false";
});
document.querySelector<HTMLInputElement>("#walletImportSourcePathInput")?.addEventListener("input", () => { document.querySelector<HTMLInputElement>("#walletImportSourcePathInput")?.addEventListener("input", () => {
setTransferPublicKey(""); setTransferPublicKey("");
setText("#walletTransferStatus", "En attente"); setText("#walletTransferStatus", "En attente");

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/app_state.rs // file: kb-app-demo-desktop/src/app_state.rs
// version: 16 // version: 17
//! Shared Tauri application state and startup initialization. //! Shared Tauri application state and startup initialization.
@@ -30,6 +30,8 @@ pub(crate) struct AppState {
demo_decode_replay_campaign_id: std::sync::Mutex<std::option::Option<std::string::String>>, demo_decode_replay_campaign_id: std::sync::Mutex<std::option::Option<std::string::String>>,
demo_execution_solana_core_running: std::sync::atomic::AtomicBool, demo_execution_solana_core_running: std::sync::atomic::AtomicBool,
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool, demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool,
demo_execution_wallet_alias_overrides:
std::sync::Mutex<std::collections::BTreeMap<std::string::String, std::string::String>>,
startup_sequence_started: std::sync::atomic::AtomicBool, startup_sequence_started: std::sync::atomic::AtomicBool,
} }
@@ -115,6 +117,9 @@ impl crate::AppState {
demo_decode_replay_campaign_id: std::sync::Mutex::new(std::option::Option::None), demo_decode_replay_campaign_id: std::sync::Mutex::new(std::option::Option::None),
demo_execution_solana_core_running: std::sync::atomic::AtomicBool::new(false), demo_execution_solana_core_running: std::sync::atomic::AtomicBool::new(false),
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool::new(false), demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool::new(false),
demo_execution_wallet_alias_overrides: std::sync::Mutex::new(
std::collections::BTreeMap::new(),
),
startup_sequence_started: std::sync::atomic::AtomicBool::new(false), startup_sequence_started: std::sync::atomic::AtomicBool::new(false),
}); });
} }
@@ -169,6 +174,46 @@ impl crate::AppState {
return &self.active_profile; return &self.active_profile;
} }
/// Returns the session-only execution-wallet alias override for one profile.
pub(crate) fn demo_execution_wallet_alias_override(
&self,
profile_name: &str,
) -> std::result::Result<std::option::Option<std::string::String>, std::string::String> {
let lock_result = self.demo_execution_wallet_alias_overrides.lock();
let guard = match lock_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
"demo execution wallet selection lock is poisoned".to_string(),
);
},
};
return std::result::Result::Ok(guard.get(profile_name).cloned());
}
/// Sets or clears the session-only execution-wallet alias override for one profile.
pub(crate) fn set_demo_execution_wallet_alias_override(
&self,
profile_name: std::string::String,
alias: std::option::Option<std::string::String>,
) -> std::result::Result<(), std::string::String> {
let lock_result = self.demo_execution_wallet_alias_overrides.lock();
let mut guard = match lock_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
"demo execution wallet selection lock is poisoned".to_string(),
);
},
};
if let std::option::Option::Some(alias) = alias {
guard.insert(profile_name, alias);
} else {
guard.remove(profile_name.as_str());
}
return std::result::Result::Ok(());
}
/// Returns the configured HTTP endpoint pool. /// Returns the configured HTTP endpoint pool.
pub(crate) fn http_pool(&self) -> &ks_onchain_transport::HttpEndpointPool { pub(crate) fn http_pool(&self) -> &ks_onchain_transport::HttpEndpointPool {
return &self.http_pool; return &self.http_pool;

View File

@@ -1,10 +1,12 @@
// file: kb-app-demo-desktop/src/demo_devnet_common.rs // file: kb-app-demo-desktop/src/demo_devnet_common.rs
// version: 4 // version: 6
//! Shared Devnet demo UI contracts. //! Shared Devnet demo UI contracts.
use ts_rs::TS; // rust-rules: derive-import use ts_rs::TS; // rust-rules: derive-import
const DEMO_WALLET_PASSWORD_ENV: &str = "KB_SECRET_DEMO_WALLET_PASSWORD";
/// Readiness report for one Devnet profile PostgreSQL store. /// Readiness report for one Devnet profile PostgreSQL store.
#[derive(Clone, Debug, serde::Serialize, TS)] #[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@@ -73,3 +75,58 @@ fn bounded_u32(value: usize) -> u32 {
std::result::Result::Err(_) => u32::MAX, std::result::Result::Err(_) => u32::MAX,
}; };
} }
/// Returns whether the backend-only demo wallet password is configured.
pub(crate) fn demo_wallet_password_configured() -> bool {
return std::env::var_os(DEMO_WALLET_PASSWORD_ENV).is_some();
}
/// Loads the backend-only demo wallet password without exposing it to Tauri DTOs.
pub(crate) fn demo_wallet_password()
-> std::result::Result<ks_wallet::WalletPassword, std::string::String> {
let password_text = match std::env::var(DEMO_WALLET_PASSWORD_ENV) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(format!(
"backend secret {DEMO_WALLET_PASSWORD_ENV} is not configured as UTF-8"
));
},
};
return match ks_wallet::WalletPassword::new(password_text) {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
/// Resolves the temporary or persistent signer selected by one Devnet profile.
pub(crate) async fn resolve_devnet_execution_wallet(
state: &crate::AppState,
profile: &ks_config::ProfileConfig,
) -> std::result::Result<ks_pipeline_demo_scenarios::DevnetExecutionWallet, std::string::String> {
let alias_override = match state.demo_execution_wallet_alias_override(profile.name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut effective_profile = profile.clone();
if let std::option::Option::Some(alias) = alias_override {
effective_profile.wallet.wallet_alias = std::option::Option::Some(alias);
}
let password = if effective_profile.wallet.wallet_alias.is_some() {
match demo_wallet_password() {
std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
} else {
std::option::Option::None
};
return match ks_pipeline_demo_scenarios::resolve_devnet_execution_wallet(
&effective_profile,
crate::workspace_root_dir().as_path(),
password,
)
.await
{
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}

View File

@@ -1,5 +1,5 @@
// 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: 8 // version: 11
//! Desktop adapters for generic and qualified Metaplex Token Metadata execution workflows. //! Desktop adapters for generic and qualified Metaplex Token Metadata execution workflows.
@@ -235,12 +235,16 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
app_handle, app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(), cancel_requested: state.demo_execution_solana_core_cancel_requested(),
}; };
let workspace_root = crate::workspace_root_dir(); let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_token_metadata( std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let summary =
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_token_metadata_with_wallet(
&http_pool, &http_pool,
&store, &store,
&profile, &profile,
workspace_root.as_path(), &execution_wallet,
&execution_request, &execution_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -417,7 +421,7 @@ async fn prepare_create_fixture_for_family(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured = std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured.is_absolute() { let wallet_dir = if configured.is_absolute() {
configured configured
} else { } else {
@@ -1048,7 +1052,8 @@ pub(crate) async fn demo_execution_metadata_metaplex_execute_campaign(
return std::result::Result::Err(error.to_string()); return std::result::Result::Err(error.to_string());
} }
let workspace_root = crate::workspace_root_dir(); let workspace_root = crate::workspace_root_dir();
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {
@@ -1070,8 +1075,8 @@ pub(crate) async fn demo_execution_metadata_metaplex_execute_campaign(
wallet_dir, wallet_dir,
&http_pool, &http_pool,
&store, &store,
&state,
&profile, &profile,
workspace_root.as_path(),
&observer, &observer,
) )
.await .await
@@ -1133,8 +1138,8 @@ fn execute_campaign_projection<'a, S, O>(
wallet_dir: std::path::PathBuf, wallet_dir: std::path::PathBuf,
http_pool: &'a ks_onchain_transport::HttpEndpointPool, http_pool: &'a ks_onchain_transport::HttpEndpointPool,
store: &'a S, store: &'a S,
state: &'a crate::AppState,
profile: &'a ks_config::ProfileConfig, profile: &'a ks_config::ProfileConfig,
workspace_root: &'a std::path::Path,
observer: &'a O, observer: &'a O,
) -> CampaignProjectionFuture<'a> ) -> CampaignProjectionFuture<'a>
where where
@@ -1150,8 +1155,8 @@ where
wallet_dir, wallet_dir,
http_pool, http_pool,
store, store,
state,
profile, profile,
workspace_root,
observer, observer,
)); ));
} }
@@ -1161,8 +1166,8 @@ async fn execute_campaign_projection_unboxed<S, O>(
wallet_dir: std::path::PathBuf, wallet_dir: std::path::PathBuf,
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S, store: &S,
state: &crate::AppState,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path,
observer: &O, observer: &O,
) -> ks_core::Result<CampaignProjection> ) -> ks_core::Result<CampaignProjection>
where where
@@ -1174,6 +1179,15 @@ where
{ {
let base_options = let base_options =
ks_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir); ks_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir);
let execution_wallet = match crate::resolve_devnet_execution_wallet(state, profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"demo_wallet_resolution_failed",
error,
));
},
};
return match campaign_id { return match campaign_id {
"create_mint_nft" => { "create_mint_nft" => {
execute_create_mint_projection( execute_create_mint_projection(
@@ -1184,7 +1198,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
observer, observer,
) )
.await .await
@@ -1198,7 +1212,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
observer, observer,
) )
.await .await
@@ -1212,7 +1226,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
observer, observer,
) )
.await .await
@@ -1226,7 +1240,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
observer, observer,
) )
.await .await
@@ -1240,17 +1254,17 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
observer, observer,
) )
.await .await
}, },
"collection_verify" => { "collection_verify" => {
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_collection_verify_campaign( let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_collection_verify_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
&base_options, &base_options,
observer, observer,
).await { ).await {
@@ -1285,11 +1299,11 @@ where
}, },
"print_burn" => { "print_burn" => {
let summary = let summary =
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_print_burn_campaign( match ks_pipeline_demo_scenarios::execute_devnet_metaplex_print_burn_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
&base_options, &base_options,
observer, observer,
) )
@@ -1327,11 +1341,11 @@ where
ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft, ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
); );
let summary = let summary =
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_pnft_lifecycle_campaign( match ks_pipeline_demo_scenarios::execute_devnet_metaplex_pnft_lifecycle_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
&options, &options,
observer, observer,
) )
@@ -1361,11 +1375,11 @@ where
}) })
}, },
"escrow" => { "escrow" => {
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_escrow_campaign( let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_escrow_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
&base_options, &base_options,
observer, observer,
) )
@@ -1410,11 +1424,11 @@ where
ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft, ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
); );
let summary = let summary =
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_maintenance_campaign( match ks_pipeline_demo_scenarios::execute_devnet_metaplex_maintenance_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
&options, &options,
observer, observer,
) )
@@ -1465,11 +1479,12 @@ where
let options = base_options.clone().with_asset_family( let options = base_options.clone().with_asset_family(
ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft, ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
); );
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_use_probe( let summary =
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_use_probe_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &execution_wallet,
&options, &options,
observer, observer,
) )
@@ -1513,7 +1528,7 @@ async fn execute_create_mint_projection<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S, store: &S,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &ks_pipeline_demo_scenarios::DevnetExecutionWallet,
observer: &O, observer: &O,
) -> ks_core::Result<CampaignProjection> ) -> ks_core::Result<CampaignProjection>
where where
@@ -1523,13 +1538,9 @@ where
+ Sync, + Sync,
O: ks_pipeline_demo_scenarios::SolanaExecutionObserver, O: ks_pipeline_demo_scenarios::SolanaExecutionObserver,
{ {
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_create_mint_campaign( let summary =
http_pool, match ks_pipeline_demo_scenarios::execute_devnet_metaplex_create_mint_campaign_with_wallet(
store, http_pool, store, profile, wallet, &options, observer,
profile,
workspace_root,
&options,
observer,
) )
.await .await
{ {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_solana_program.rs // file: kb-app-demo-desktop/src/demo_execution_metadata_solana_program.rs
// version: 3 // version: 6
//! Thin desktop adapter for complete Solana Program Metadata Devnet campaigns. //! Thin desktop adapter for complete Solana Program Metadata Devnet campaigns.
@@ -170,11 +170,14 @@ pub(crate) async fn demo_execution_metadata_solana_program_execute_campaign(
app_handle, app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(), cancel_requested: state.demo_execution_solana_core_cancel_requested(),
}; };
let workspace_root = crate::workspace_root_dir(); let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
let summary = match ks_pipeline_demo_scenarios::execute_devnet_solana_program_metadata_campaign( std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let summary = match ks_pipeline_demo_scenarios::execute_devnet_solana_program_metadata_campaign_with_wallet(
&http_pool, &http_pool,
&profile, &profile,
workspace_root.as_path(), &execution_wallet,
&options, &options,
&observer, &observer,
) )

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_token_2022.rs // file: kb-app-demo-desktop/src/demo_execution_metadata_token_2022.rs
// version: 2 // version: 5
//! Thin desktop adapter for the complete Token-2022 Token Metadata Devnet campaign. //! Thin desktop adapter for the complete Token-2022 Token Metadata Devnet campaign.
@@ -96,7 +96,8 @@ pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
return std::result::Result::Err(error.to_string()); return std::result::Result::Err(error.to_string());
} }
let workspace_root = crate::workspace_root_dir(); let workspace_root = crate::workspace_root_dir();
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {
@@ -109,11 +110,16 @@ pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
app_handle, app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(), cancel_requested: state.demo_execution_solana_core_cancel_requested(),
}; };
let summary = match ks_pipeline_demo_scenarios::execute_devnet_token_2022_metadata_campaign( let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let summary =
match ks_pipeline_demo_scenarios::execute_devnet_token_2022_metadata_campaign_with_wallet(
&http_pool, &http_pool,
&store, &store,
&profile, &profile,
workspace_root.as_path(), &execution_wallet,
&options, &options,
&observer, &observer,
) )

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs // file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
// version: 17 // version: 20
//! Tauri adapter for bounded Solana Core execution on Devnet. //! Tauri adapter for bounded Solana Core execution on Devnet.
@@ -509,12 +509,15 @@ pub(crate) async fn demo_execution_solana_core_execute(
app_handle, app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(), cancel_requested: state.demo_execution_solana_core_cancel_requested(),
}; };
let workspace_root = crate::workspace_root_dir(); let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
let summary = match ks_pipeline_demo_scenarios::execute_devnet_system_transfer( std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let summary = match ks_pipeline_demo_scenarios::execute_devnet_system_transfer_with_wallet(
&http_pool, &http_pool,
&store, &store,
&profile, &profile,
workspace_root.as_path(), &execution_wallet,
&pipeline_request, &pipeline_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -582,12 +585,15 @@ pub(crate) async fn demo_execution_spl_memo_execute(
app_handle, app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(), cancel_requested: state.demo_execution_solana_core_cancel_requested(),
}; };
let workspace_root = crate::workspace_root_dir(); let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
let summary = match ks_pipeline_demo_scenarios::execute_devnet_memo( std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let summary = match ks_pipeline_demo_scenarios::execute_devnet_memo_with_wallet(
&http_pool, &http_pool,
&store, &store,
&profile, &profile,
workspace_root.as_path(), &execution_wallet,
&pipeline_request, &pipeline_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_ata.rs // file: kb-app-demo-desktop/src/demo_spl_ata.rs
// version: 13 // version: 16
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads. //! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
@@ -279,11 +279,16 @@ pub(crate) async fn demo_execution_spl_ata_execute(
app_handle, app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(), cancel_requested: state.demo_execution_solana_core_cancel_requested(),
}; };
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_associated_token_account( let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let summary =
match ks_pipeline_demo_scenarios::execute_devnet_spl_associated_token_account_with_wallet(
&http_pool, &http_pool,
&store, &store,
&profile, &profile,
crate::workspace_root_dir().as_path(), &execution_wallet,
&pipeline_request, &pipeline_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -346,7 +351,7 @@ pub(crate) async fn demo_spl_ata_journal(
async fn load_profile_wallet( async fn load_profile_wallet(
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
) -> std::result::Result<ks_wallet::TemporaryWallet, std::string::String> { ) -> std::result::Result<ks_wallet::TemporaryWallet, std::string::String> {
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured = std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let directory = if configured.is_absolute() { let directory = if configured.is_absolute() {
configured configured
} else { } else {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token.rs // file: kb-app-demo-desktop/src/demo_spl_token.rs
// version: 10 // version: 13
//! Thin Tauri adapter for classic SPL Token execution and materialized journals. //! Thin Tauri adapter for classic SPL Token execution and materialized journals.
@@ -223,12 +223,15 @@ pub(crate) async fn demo_execution_spl_token_execute(
app_handle, app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(), cancel_requested: state.demo_execution_solana_core_cancel_requested(),
}; };
let workspace_root = crate::workspace_root_dir(); let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token( std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token_with_wallet(
&http_pool, &http_pool,
&store, &store,
&profile, &profile,
workspace_root.as_path(), &execution_wallet,
&pipeline_request, &pipeline_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token_2022.rs // file: kb-app-demo-desktop/src/demo_spl_token_2022.rs
// version: 15 // version: 18
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios. //! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
@@ -192,12 +192,15 @@ pub(crate) async fn demo_execution_spl_token_2022_execute(
app_handle, app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(), cancel_requested: state.demo_execution_solana_core_cancel_requested(),
}; };
let workspace_root = crate::workspace_root_dir(); let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token_2022( std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token_2022_with_wallet(
&http_pool, &http_pool,
&store, &store,
&profile, &profile,
workspace_root.as_path(), &execution_wallet,
&pipeline_request, &pipeline_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -220,7 +223,7 @@ pub(crate) fn demo_spl_token_2022_fixture(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured = std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured.is_absolute() { let wallet_dir = if configured.is_absolute() {
configured configured
} else { } else {

View File

@@ -1,11 +1,12 @@
// file: kb-app-demo-desktop/src/demo_wallet.rs // file: kb-app-demo-desktop/src/demo_wallet.rs
// version: 5 // version: 7
//! UI-safe wallet management and public on-chain exploration. //! UI-safe wallet management and public on-chain exploration.
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import use ts_rs::TS; // rust-rules: derive-import
const DEMO_WALLET_PASSWORD_ENV: &str = "KB_SECRET_DEMO_WALLET_PASSWORD";
const LAMPORTS_PER_SOL: u64 = 1_000_000_000; const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
/// UI-safe inventory of native wallets visible to the active profile. /// UI-safe inventory of native wallets visible to the active profile.
@@ -21,6 +22,10 @@ pub(crate) struct DemoWalletInventoryPayload {
pub(crate) cluster: std::string::String, pub(crate) cluster: std::string::String,
/// Profiles available for explicit read-only on-chain exploration. /// Profiles available for explicit read-only on-chain exploration.
pub(crate) onchain_profiles: std::vec::Vec<DemoWalletOnchainProfilePayload>, pub(crate) onchain_profiles: std::vec::Vec<DemoWalletOnchainProfilePayload>,
/// Devnet profiles and their session/config execution-wallet selections.
pub(crate) execution_profiles: std::vec::Vec<DemoWalletExecutionProfilePayload>,
/// Workspace-relative directory used for secret exports from this demo.
pub(crate) export_directory: std::string::String,
/// Secret transfer formats supported by the reusable wallet boundary. /// Secret transfer formats supported by the reusable wallet boundary.
pub(crate) transfer_formats: std::vec::Vec<DemoWalletTransferFormatPayload>, pub(crate) transfer_formats: std::vec::Vec<DemoWalletTransferFormatPayload>,
/// Canonical classic SPL Token program identifier used by the explorer. /// Canonical classic SPL Token program identifier used by the explorer.
@@ -119,7 +124,7 @@ pub(crate) struct DemoWalletImportRequest {
pub(crate) format: std::string::String, pub(crate) format: std::string::String,
} }
/// Request to export one native wallet to an explicit external file. /// Request to export one native wallet to the application-owned export directory.
#[derive(Clone, Debug, serde::Deserialize, TS)] #[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts( #[ts(
export, export,
@@ -128,12 +133,42 @@ pub(crate) struct DemoWalletImportRequest {
pub(crate) struct DemoWalletExportRequest { pub(crate) struct DemoWalletExportRequest {
/// Alias of the native wallet to export. /// Alias of the native wallet to export.
pub(crate) alias: std::string::String, pub(crate) alias: std::string::String,
/// Explicit destination path created without overwrite. /// File name created inside the application-owned wallet export directory.
pub(crate) destination_path: std::string::String, pub(crate) file_name: std::string::String,
/// Stable transfer format code selected by the operator. /// Stable transfer format code selected by the operator.
pub(crate) format: std::string::String, pub(crate) format: std::string::String,
} }
/// One Devnet profile and its effective desktop-session execution-wallet selection.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionProfilePayload.ts"
)]
pub(crate) struct DemoWalletExecutionProfilePayload {
/// Resolved Devnet profile name.
pub(crate) name: std::string::String,
/// Alias persisted in wallet configuration, when present.
pub(crate) configured_alias: std::option::Option<std::string::String>,
/// Session-only desktop override, when present.
pub(crate) runtime_alias: std::option::Option<std::string::String>,
/// Effective alias used by execution demos, or `None` for the temporary fallback.
pub(crate) effective_alias: std::option::Option<std::string::String>,
}
/// Request to set or clear one session-only execution-wallet selection.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionSelectionRequest.ts"
)]
pub(crate) struct DemoWalletExecutionSelectionRequest {
/// Devnet profile whose execution wallet is selected.
pub(crate) profile: std::string::String,
/// Native wallet alias, or `None` to return to profile configuration.
pub(crate) alias: std::option::Option<std::string::String>,
}
/// Exact SOL balance returned for one public wallet address. /// Exact SOL balance returned for one public wallet address.
#[derive(Clone, Debug, serde::Serialize, TS)] #[derive(Clone, Debug, serde::Serialize, TS)]
#[ts( #[ts(
@@ -226,19 +261,87 @@ pub(crate) async fn demo_wallet_inventory(
for handle in handles { for handle in handles {
wallets.push(wallet_identity_payload(&handle, selected_alias.as_deref())); wallets.push(wallet_identity_payload(&handle, selected_alias.as_deref()));
} }
let execution_profiles = match execution_profile_payloads(state) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(DemoWalletInventoryPayload { return std::result::Result::Ok(DemoWalletInventoryPayload {
profile: profile.name.clone(), profile: profile.name.clone(),
cluster: profile.wallet.cluster.clone(), cluster: profile.wallet.cluster.clone(),
onchain_profiles: onchain_profile_payloads(state), onchain_profiles: onchain_profile_payloads(state),
execution_profiles,
export_directory: "data/wallets".to_string(),
transfer_formats: wallet_transfer_format_payloads(), transfer_formats: wallet_transfer_format_payloads(),
token_program_id: ks_program_ids::SPL_TOKEN_PROGRAM_ID.to_string(), token_program_id: ks_program_ids::SPL_TOKEN_PROGRAM_ID.to_string(),
token_2022_program_id: ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string(), token_2022_program_id: ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string(),
create_password_configured: std::env::var_os(DEMO_WALLET_PASSWORD_ENV).is_some(), create_password_configured: crate::demo_wallet_password_configured(),
selected_alias, selected_alias,
wallets, wallets,
}); });
} }
/// Sets or clears the session-only native wallet used by Devnet execution demos.
pub(crate) async fn demo_wallet_select_execution_wallet(
state: &crate::AppState,
request: crate::DemoWalletExecutionSelectionRequest,
) -> std::result::Result<DemoWalletInventoryPayload, std::string::String> {
let profile = match devnet_execution_profile(state, request.profile.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let profile_name = profile.name.clone();
if let std::option::Option::Some(alias_text) = request.alias {
let alias = match ks_wallet::WalletAlias::parse(alias_text.trim().to_string()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let password = match crate::demo_wallet_password() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let manager = match wallet_manager_for_profile(profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = match manager.unlock(&alias, password).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let public_key = wallet.public_key();
wallet.lock();
if let std::result::Result::Err(error) = state.set_demo_execution_wallet_alias_override(
profile_name.clone(),
std::option::Option::Some(alias.as_str().to_string()),
) {
return std::result::Result::Err(error);
}
tracing::info!(
target: crate::TRACING_TARGET,
action = "select_demo_execution_wallet",
profile = profile_name.as_str(),
wallet_alias = alias.as_str(),
public_key = %public_key,
selection_source = "runtime",
"selected authenticated native wallet for desktop execution demos"
);
} else {
if let std::result::Result::Err(error) = state.set_demo_execution_wallet_alias_override(
profile_name.clone(),
std::option::Option::None,
) {
return std::result::Result::Err(error);
}
tracing::info!(
target: crate::TRACING_TARGET,
action = "select_demo_execution_wallet",
profile = profile_name.as_str(),
selection_source = "configuration",
"cleared desktop execution-wallet override"
);
}
return demo_wallet_inventory(state).await;
}
/// Creates one password-protected native wallet without sending the password through Tauri IPC. /// Creates one password-protected native wallet without sending the password through Tauri IPC.
pub(crate) async fn demo_wallet_create_native( pub(crate) async fn demo_wallet_create_native(
state: &crate::AppState, state: &crate::AppState,
@@ -248,7 +351,7 @@ pub(crate) async fn demo_wallet_create_native(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
let password = match demo_wallet_password() { let password = match crate::demo_wallet_password() {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -309,7 +412,7 @@ pub(crate) async fn demo_wallet_import_file(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let password = match demo_wallet_password() { let password = match crate::demo_wallet_password() {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -341,7 +444,7 @@ pub(crate) async fn demo_wallet_import_file(
pub(crate) async fn demo_wallet_export_file( pub(crate) async fn demo_wallet_export_file(
state: &crate::AppState, state: &crate::AppState,
request: crate::DemoWalletExportRequest, request: crate::DemoWalletExportRequest,
) -> std::result::Result<(), std::string::String> { ) -> std::result::Result<std::string::String, std::string::String> {
let alias = match ks_wallet::WalletAlias::parse(request.alias.trim().to_string()) { let alias = match ks_wallet::WalletAlias::parse(request.alias.trim().to_string()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
@@ -350,7 +453,15 @@ pub(crate) async fn demo_wallet_export_file(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let password = match demo_wallet_password() { let file_name = match validated_wallet_export_file_name(request.file_name.as_str(), format) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let export_directory = match prepare_wallet_export_directory().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let password = match crate::demo_wallet_password() {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -358,13 +469,21 @@ pub(crate) async fn demo_wallet_export_file(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
return match manager let destination = export_directory.join(file_name.as_str());
.export_file(&alias, password, std::path::PathBuf::from(request.destination_path), format) if let std::result::Result::Err(error) =
.await manager.export_file(&alias, password, destination, format).await
{ {
std::result::Result::Ok(()) => std::result::Result::Ok(()), return std::result::Result::Err(error.to_string());
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()), }
}; tracing::info!(
target: crate::TRACING_TARGET,
action = "export_wallet_secret",
wallet_alias = alias.as_str(),
transfer_format = format.code(),
export_file_name = file_name.as_str(),
"exported native wallet secret to application-owned data directory"
);
return std::result::Result::Ok(format!("data/wallets/{file_name}"));
} }
/// Inspects one explicitly selected native wallet file and returns only its safe identity. /// Inspects one explicitly selected native wallet file and returns only its safe identity.
@@ -489,21 +608,6 @@ pub(crate) async fn demo_wallet_rpc_execute(
}); });
} }
fn demo_wallet_password() -> std::result::Result<ks_wallet::WalletPassword, std::string::String> {
let password_text = match std::env::var(DEMO_WALLET_PASSWORD_ENV) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(format!(
"backend secret {DEMO_WALLET_PASSWORD_ENV} is not configured as UTF-8"
));
},
};
return match ks_wallet::WalletPassword::new(password_text) {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
fn wallet_transfer_format_payloads() -> std::vec::Vec<crate::DemoWalletTransferFormatPayload> { fn wallet_transfer_format_payloads() -> std::vec::Vec<crate::DemoWalletTransferFormatPayload> {
let mut formats = std::vec::Vec::new(); let mut formats = std::vec::Vec::new();
for format in ks_wallet::WalletTransferFormat::supported() { for format in ks_wallet::WalletTransferFormat::supported() {
@@ -528,6 +632,127 @@ fn wallet_transfer_format_from_code(
return std::result::Result::Err(format!("wallet transfer format '{code}' is not supported")); return std::result::Result::Err(format!("wallet transfer format '{code}' is not supported"));
} }
fn execution_profile_payloads(
state: &crate::AppState,
) -> std::result::Result<std::vec::Vec<crate::DemoWalletExecutionProfilePayload>, std::string::String>
{
let mut profiles = std::vec::Vec::new();
for profile in &state.app_config().profiles {
if profile.wallet.cluster != "devnet" {
continue;
}
let runtime_alias = match state.demo_execution_wallet_alias_override(profile.name.as_str())
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let effective_alias = match runtime_alias.as_ref() {
std::option::Option::Some(value) => std::option::Option::Some(value.clone()),
std::option::Option::None => profile.wallet.wallet_alias.clone(),
};
profiles.push(crate::DemoWalletExecutionProfilePayload {
name: profile.name.clone(),
configured_alias: profile.wallet.wallet_alias.clone(),
runtime_alias,
effective_alias,
});
}
return std::result::Result::Ok(profiles);
}
fn devnet_execution_profile<'a>(
state: &'a crate::AppState,
profile_name: &str,
) -> std::result::Result<&'a ks_config::ProfileConfig, std::string::String> {
let profile = match onchain_profile(state, profile_name) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if profile.wallet.cluster != "devnet" {
return std::result::Result::Err(
"wallet execution selection requires a Devnet profile".to_string(),
);
}
return std::result::Result::Ok(profile);
}
fn validated_wallet_export_file_name(
file_name: &str,
format: ks_wallet::WalletTransferFormat,
) -> std::result::Result<std::string::String, std::string::String> {
let file_name = file_name.trim();
if file_name.is_empty() || file_name.len() > 128 {
return std::result::Result::Err(
"wallet export file name must contain between 1 and 128 bytes".to_string(),
);
}
if file_name == "." || file_name == ".." || file_name.starts_with('.') {
return std::result::Result::Err("wallet export file name is invalid".to_string());
}
if !file_name.chars().all(|value| {
return value.is_ascii_alphanumeric() || value == '-' || value == '_' || value == '.';
}) {
return std::result::Result::Err(
"wallet export file name contains unsupported characters".to_string(),
);
}
let expected_extension = format.default_extension();
let path = std::path::Path::new(file_name);
if path.extension().is_none() {
return std::result::Result::Ok(format!("{file_name}.{expected_extension}"));
}
if path.extension().and_then(std::ffi::OsStr::to_str)
!= std::option::Option::Some(expected_extension)
{
return std::result::Result::Err(format!(
"wallet export file extension must be '.{expected_extension}' for format '{}'",
format.code()
));
}
return std::result::Result::Ok(file_name.to_string());
}
async fn prepare_wallet_export_directory()
-> std::result::Result<std::path::PathBuf, std::string::String> {
let data_directory = crate::workspace_root_dir().join("data");
if let std::result::Result::Ok(metadata) = tokio::fs::symlink_metadata(&data_directory).await {
if metadata.file_type().is_symlink() || !metadata.is_dir() {
return std::result::Result::Err(
"wallet export data directory must be a regular directory".to_string(),
);
}
}
if let std::result::Result::Err(error) = tokio::fs::create_dir_all(&data_directory).await {
return std::result::Result::Err(format!(
"cannot create wallet export data directory: {error}"
));
}
let export_directory = data_directory.join("wallets");
if let std::result::Result::Ok(metadata) = tokio::fs::symlink_metadata(&export_directory).await
{
if metadata.file_type().is_symlink() || !metadata.is_dir() {
return std::result::Result::Err(
"wallet export directory must be a regular directory".to_string(),
);
}
}
if let std::result::Result::Err(error) = tokio::fs::create_dir_all(&export_directory).await {
return std::result::Result::Err(format!("cannot create wallet export directory: {error}"));
}
#[cfg(unix)]
{
let permissions = std::fs::Permissions::from_mode(0o700);
if let std::result::Result::Err(error) =
tokio::fs::set_permissions(&export_directory, permissions).await
{
return std::result::Result::Err(format!(
"cannot secure wallet export directory permissions: {error}"
));
}
}
return std::result::Result::Ok(export_directory);
}
fn onchain_profile<'a>( fn onchain_profile<'a>(
state: &'a crate::AppState, state: &'a crate::AppState,
profile_name: &str, profile_name: &str,
@@ -676,14 +901,19 @@ mod tests {
"importWalletTransferButton", "importWalletTransferButton",
"walletExportAliasSelect", "walletExportAliasSelect",
"walletExportFormatSelect", "walletExportFormatSelect",
"walletExportDestinationPathInput", "walletExportFileNameInput",
"walletExportDirectory",
"exportWalletTransferButton", "exportWalletTransferButton",
"walletExecutionProfileSelect",
"walletExecutionAliasSelect",
"applyWalletExecutionSelectionButton",
"walletExternalPathInput", "walletExternalPathInput",
"inspectWalletFileButton", "inspectWalletFileButton",
"walletInspectionJson", "walletInspectionJson",
] { ] {
assert!(html.contains(required)); assert!(html.contains(required));
} }
assert!(script.contains("demo_wallet_select_execution_wallet"));
assert!(script.contains("demo_wallet_create_native")); assert!(script.contains("demo_wallet_create_native"));
assert!(script.contains("demo_wallet_inspect_transfer_file")); assert!(script.contains("demo_wallet_inspect_transfer_file"));
assert!(script.contains("demo_wallet_import_file")); assert!(script.contains("demo_wallet_import_file"));
@@ -700,6 +930,7 @@ mod tests {
assert!(main_html.contains("openDemoWalletLink")); assert!(main_html.contains("openDemoWalletLink"));
assert!(main_script.contains("open_demo_wallet_window")); assert!(main_script.contains("open_demo_wallet_window"));
assert!(tauri_runtime.contains("open_demo_wallet_window")); assert!(tauri_runtime.contains("open_demo_wallet_window"));
assert!(tauri_runtime.contains("demo_wallet_select_execution_wallet"));
assert!(tauri_runtime.contains("demo_wallet_create_native")); assert!(tauri_runtime.contains("demo_wallet_create_native"));
assert!(tauri_runtime.contains("demo_wallet_inspect_transfer_file")); assert!(tauri_runtime.contains("demo_wallet_inspect_transfer_file"));
assert!(tauri_runtime.contains("demo_wallet_import_file")); assert!(tauri_runtime.contains("demo_wallet_import_file"));
@@ -721,6 +952,13 @@ mod tests {
cluster: "devnet".to_string(), cluster: "devnet".to_string(),
active: false, active: false,
}], }],
execution_profiles: std::vec![super::DemoWalletExecutionProfilePayload {
name: "local_devnet".to_string(),
configured_alias: std::option::Option::None,
runtime_alias: std::option::Option::Some("selected-wallet".to_string()),
effective_alias: std::option::Option::Some("selected-wallet".to_string()),
}],
export_directory: "data/wallets".to_string(),
transfer_formats: super::wallet_transfer_format_payloads(), transfer_formats: super::wallet_transfer_format_payloads(),
token_program_id: ks_program_ids::SPL_TOKEN_PROGRAM_ID.to_string(), token_program_id: ks_program_ids::SPL_TOKEN_PROGRAM_ID.to_string(),
token_2022_program_id: ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string(), token_2022_program_id: ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string(),
@@ -752,6 +990,34 @@ mod tests {
} }
} }
#[test]
fn wallet_export_file_name_is_bounded_and_format_specific() {
let json = super::validated_wallet_export_file_name(
"operator",
ks_wallet::WalletTransferFormat::SolanaCliJson,
);
assert_eq!(json, std::result::Result::Ok("operator.json".to_string()));
let base58 = super::validated_wallet_export_file_name(
"operator.txt",
ks_wallet::WalletTransferFormat::SolanaPrivateKeyBase58,
);
assert_eq!(base58, std::result::Result::Ok("operator.txt".to_string()));
assert!(
super::validated_wallet_export_file_name(
"../operator.json",
ks_wallet::WalletTransferFormat::SolanaCliJson,
)
.is_err()
);
assert!(
super::validated_wallet_export_file_name(
"operator.txt",
ks_wallet::WalletTransferFormat::SolanaCliJson,
)
.is_err()
);
}
#[test] #[test]
fn wallet_rpc_surface_is_read_only_and_bounded() { fn wallet_rpc_surface_is_read_only_and_bounded() {
for allowed in ["getTokenAccountsByOwner", "getSignaturesForAddress", "getTransaction"] { for allowed in ["getTokenAccountsByOwner", "getSignaturesForAddress", "getTransaction"] {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/lib.rs // file: kb-app-demo-desktop/src/lib.rs
// version: 41 // version: 43
//! Tauri desktop demo application for `khadhroony-bot3`. //! Tauri desktop demo application for `khadhroony-bot3`.
@@ -151,10 +151,16 @@ pub(crate) use self::demo_decode_replay::demo_decode_replay_options;
pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayload; pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayload;
/// Restores the shared single-execution state flag when a Devnet run finishes. /// Restores the shared single-execution state flag when a Devnet run finishes.
pub(crate) use self::demo_devnet_common::DemoExecutionRunGuard; pub(crate) use self::demo_devnet_common::DemoExecutionRunGuard;
/// Loads the backend-only demo wallet password.
pub(crate) use self::demo_devnet_common::demo_wallet_password;
/// Returns whether the backend-only demo wallet password is configured.
pub(crate) use self::demo_devnet_common::demo_wallet_password_configured;
/// Converts the reusable scenario readiness report to the desktop TS-RS payload. /// Converts the reusable scenario readiness report to the desktop TS-RS payload.
pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload; pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload;
/// Serializes a diagnostic value to indented JSON without panicking. /// Serializes a diagnostic value to indented JSON without panicking.
pub(crate) use self::demo_devnet_common::pretty_json; pub(crate) use self::demo_devnet_common::pretty_json;
/// Resolves the temporary or persistent signer selected by one Devnet profile.
pub(crate) use self::demo_devnet_common::resolve_devnet_execution_wallet;
/// Bridges Metadata execution progress to the Metadata desktop window. /// Bridges Metadata execution progress to the Metadata desktop window.
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataObserver; pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataObserver;
/// Progress event emitted by Metadata execution workflows. /// Progress event emitted by Metadata execution workflows.
@@ -367,6 +373,10 @@ pub(crate) use self::demo_transport::DemoEndpointRolePayload;
pub(crate) use self::demo_wallet::DemoWalletBalancePayload; pub(crate) use self::demo_wallet::DemoWalletBalancePayload;
/// Request to create one native wallet from the backend-only demo secret. /// Request to create one native wallet from the backend-only demo secret.
pub(crate) use self::demo_wallet::DemoWalletCreateRequest; pub(crate) use self::demo_wallet::DemoWalletCreateRequest;
/// One Devnet profile and its effective execution-wallet selection.
pub(crate) use self::demo_wallet::DemoWalletExecutionProfilePayload;
/// Request to set or clear one session-only execution-wallet selection.
pub(crate) use self::demo_wallet::DemoWalletExecutionSelectionRequest;
/// Request to export one native wallet secret through the backend-only demo secret. /// Request to export one native wallet secret through the backend-only demo secret.
pub(crate) use self::demo_wallet::DemoWalletExportRequest; pub(crate) use self::demo_wallet::DemoWalletExportRequest;
/// UI-safe native wallet identity exposed by the desktop adapter. /// UI-safe native wallet identity exposed by the desktop adapter.
@@ -403,6 +413,8 @@ pub(crate) use self::demo_wallet::demo_wallet_inspect_transfer_file;
pub(crate) use self::demo_wallet::demo_wallet_inventory; pub(crate) use self::demo_wallet::demo_wallet_inventory;
/// Executes one bounded read-only wallet explorer RPC through an explicitly selected profile. /// Executes one bounded read-only wallet explorer RPC through an explicitly selected profile.
pub(crate) use self::demo_wallet::demo_wallet_rpc_execute; pub(crate) use self::demo_wallet::demo_wallet_rpc_execute;
/// Sets or clears the session-only native wallet used by Devnet execution demos.
pub(crate) use self::demo_wallet::demo_wallet_select_execution_wallet;
/// UI-safe WebSocket endpoint snapshot. /// UI-safe WebSocket endpoint snapshot.
pub(crate) use self::demo_ws::DemoWsEndpointPayload; pub(crate) use self::demo_ws::DemoWsEndpointPayload;
/// WebSocket execution response payload. /// WebSocket execution response payload.

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/tauri.rs // file: kb-app-demo-desktop/src/tauri.rs
// version: 43 // version: 44
//! Tauri runtime assembly and private command wrappers. //! Tauri runtime assembly and private command wrappers.
@@ -38,6 +38,7 @@ pub fn run() -> ks_core::Result<()> {
load_demo_config, load_demo_config,
open_demo_wallet_window, open_demo_wallet_window,
demo_wallet_inventory, demo_wallet_inventory,
demo_wallet_select_execution_wallet,
demo_wallet_create_native, demo_wallet_create_native,
demo_wallet_inspect_transfer_file, demo_wallet_inspect_transfer_file,
demo_wallet_import_file, demo_wallet_import_file,
@@ -258,6 +259,14 @@ async fn demo_wallet_inventory(
return crate::demo_wallet_inventory(state.inner()).await; return crate::demo_wallet_inventory(state.inner()).await;
} }
#[tauri::command]
async fn demo_wallet_select_execution_wallet(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoWalletExecutionSelectionRequest,
) -> std::result::Result<crate::DemoWalletInventoryPayload, std::string::String> {
return crate::demo_wallet_select_execution_wallet(state.inner(), request).await;
}
#[tauri::command] #[tauri::command]
async fn demo_wallet_create_native( async fn demo_wallet_create_native(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
@@ -285,7 +294,7 @@ async fn demo_wallet_import_file(
async fn demo_wallet_export_file( async fn demo_wallet_export_file(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
request: crate::DemoWalletExportRequest, request: crate::DemoWalletExportRequest,
) -> std::result::Result<(), std::string::String> { ) -> std::result::Result<std::string::String, std::string::String> {
return crate::demo_wallet_export_file(state.inner(), request).await; return crate::demo_wallet_export_file(state.inner(), request).await;
} }

View File

@@ -1,8 +1,23 @@
<!-- file: ks-config/CHANGELOG.md --> <!-- file: ks-config/CHANGELOG.md -->
<!-- version: 21 --> <!-- version: 24 -->
# CHANGELOG — ks-config # CHANGELOG — ks-config
## `0.5.2-pre.006-delta-fix-007`
- corrige le test de séparation des répertoires wallet afin quil résolve dabord `${KS_WALLETS_DIRECTORY:-wallets}` comme le chemin de chargement runtime ;
- rend le test indépendant dun éventuel override `KS_WALLETS_DIRECTORY` en vérifiant la relation `temporary_wallet_dir = wallet_dir/temporary/local_devnet` plutôt quune racine littérale.
## `0.5.2-pre.006-delta-fix-006`
- réinclut dans le delta `config/wallet.config.json` et `config/exemples/example.wallet.config.json`, omis par erreur dans `fix-005`, afin que leurs sous-répertoires temporaires `temporary/...` correspondent au contrat runtime résolu ;
- précise dans le schéma wallet que `wallets_directory` est la racine persistante `.kswallet` et que `profile.directory` désigne les wallets/fixtures temporaires du profil.
## `0.5.2-pre.006-delta-fix-005`
- sépare le store natif persistant `wallet_dir` de `temporary_wallet_dir` : les `.kswallet` utilisent désormais directement la racine `wallets_directory`, tandis que les wallets et keypairs de fixtures restent sous le sous-répertoire temporaire du profil ;
- propage les deux chemins dans le contrat runtime résolu et son schéma sans déplacer automatiquement les fichiers existants.
## `0.5.2-pre.006` ## `0.5.2-pre.006`
- ajoute `wallet_alias` aux profils du document wallet comme sélection optionnelle/nullable d'un wallet natif persistant, sans password ni matériau secret dans `ks-config` ; - ajoute `wallet_alias` aux profils du document wallet comme sélection optionnelle/nullable d'un wallet natif persistant, sans password ni matériau secret dans `ks-config` ;

View File

@@ -1,5 +1,5 @@
<!-- file: ks-config/USAGE.md --> <!-- file: ks-config/USAGE.md -->
<!-- version: 11 --> <!-- version: 12 -->
# Utilisation de ks-config # Utilisation de ks-config
@@ -92,9 +92,9 @@ Les endpoints WebSocket source sélectionnent une classe `ws_endpoint_defaults`;
## Wallet ## Wallet
`wallet.config.json` porte `wallets_directory` hors profils. Chaque profil définit un chemin relatif, le cluster, l'alias et la politique temporaire/persistante. `resolved_wallet_profile` construit le chemin runtime final. `wallet.config.json` porte `wallets_directory` hors profils. Cette valeur résout directement `wallet.wallet_dir`, racine commune des `.kswallet` persistants. Chaque profil conserve `directory` uniquement pour ses wallets/keypairs temporaires ; `resolved_wallet_profile` le résout séparément dans `wallet.temporary_wallet_dir` (par défaut `wallets/temporary/<profil>`).
Les autorisations de soumission ne font plus partie du wallet. Un changement de profil ne déplace donc pas les `.kswallet` persistants. Les fixtures temporaires restent isolées par profil et les autorisations de soumission ne font plus partie du wallet.
## Exécution ## Exécution

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/settings.rs // file: ks-config/src/settings.rs
// version: 24 // version: 25
//! Resolved runtime configuration models retained during the `0.5.1` source-document split. //! Resolved runtime configuration models retained during the `0.5.1` source-document split.
@@ -222,8 +222,10 @@ pub struct AccountListenerConfig {
/// Wallet configuration. /// Wallet configuration.
#[derive(Clone, serde::Deserialize, Eq, PartialEq)] #[derive(Clone, serde::Deserialize, Eq, PartialEq)]
pub struct WalletConfig { pub struct WalletConfig {
/// Wallet directory path. /// Persistent native wallet directory path.
pub wallet_dir: std::string::String, pub wallet_dir: std::string::String,
/// Profile-specific temporary wallet and fixture directory path.
pub temporary_wallet_dir: std::string::String,
/// Selected cluster name. /// Selected cluster name.
pub cluster: std::string::String, pub cluster: std::string::String,
/// Optional persistent native wallet alias selected by this profile. /// Optional persistent native wallet alias selected by this profile.
@@ -734,6 +736,10 @@ pub(crate) fn validate_wallet(config: &WalletConfig) -> ks_core::Result<()> {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
} }
match require_non_empty(&config.temporary_wallet_dir, "wallet.temporary_wallet_dir") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match require_non_empty(&config.cluster, "wallet.cluster") { match require_non_empty(&config.cluster, "wallet.cluster") {
std::result::Result::Ok(()) => (), std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/wallet.rs // file: ks-config/src/wallet.rs
// version: 4 // version: 6
//! Independent wallet configuration document loading and validation. //! Independent wallet configuration document loading and validation.
@@ -21,7 +21,7 @@ pub struct WalletConfigDocument {
pub struct WalletProfileConfig { pub struct WalletProfileConfig {
/// Profile code. /// Profile code.
pub name: std::string::String, pub name: std::string::String,
/// Wallet subdirectory or absolute directory. /// Temporary wallet/fixture subdirectory or absolute directory.
pub directory: std::string::String, pub directory: std::string::String,
/// Selected cluster name. /// Selected cluster name.
pub cluster: std::string::String, pub cluster: std::string::String,
@@ -143,14 +143,16 @@ pub fn resolved_wallet_profile(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let configured = std::path::PathBuf::from(profile.directory.as_str()); let persistent_wallet_dir = std::path::PathBuf::from(document.wallets_directory.as_str());
let wallet_dir = if configured.is_absolute() { let configured_temporary = std::path::PathBuf::from(profile.directory.as_str());
configured let temporary_wallet_dir = if configured_temporary.is_absolute() {
configured_temporary
} else { } else {
std::path::PathBuf::from(document.wallets_directory.as_str()).join(configured) persistent_wallet_dir.join(configured_temporary)
}; };
let resolved = crate::WalletConfig { let resolved = crate::WalletConfig {
wallet_dir: wallet_dir.display().to_string(), wallet_dir: persistent_wallet_dir.display().to_string(),
temporary_wallet_dir: temporary_wallet_dir.display().to_string(),
cluster: profile.cluster.clone(), cluster: profile.cluster.clone(),
wallet_alias: profile.wallet_alias.clone(), wallet_alias: profile.wallet_alias.clone(),
temporary_wallet_enabled: profile.temporary_wallet_enabled, temporary_wallet_enabled: profile.temporary_wallet_enabled,
@@ -233,6 +235,25 @@ mod tests {
); );
} }
#[test]
fn resolved_profile_separates_persistent_and_temporary_wallet_directories() {
let resolved_raw = crate::resolve_environment_placeholders(DEFAULT_WALLET);
let document = match super::parse_wallet_json(resolved_raw.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("resolved wallet config failed: {error}"),
};
let resolved = match super::resolved_wallet_profile(&document, "local_devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet profile resolution failed: {error}"),
};
assert!(!resolved.wallet_dir.contains("${"));
let expected_temporary = std::path::PathBuf::from(resolved.wallet_dir.as_str())
.join("temporary/local_devnet")
.display()
.to_string();
assert_eq!(resolved.temporary_wallet_dir, expected_temporary);
}
#[test] #[test]
fn wallet_profile_without_persistent_alias_remains_backward_compatible() { fn wallet_profile_without_persistent_alias_remains_backward_compatible() {
let mut value = match serde_json::from_str::<serde_json::Value>(DEFAULT_WALLET) { let mut value = match serde_json::from_str::<serde_json::Value>(DEFAULT_WALLET) {

View File

@@ -1,8 +1,20 @@
<!-- file: ks-pipeline-demo-scenarios/CHANGELOG.md --> <!-- file: ks-pipeline-demo-scenarios/CHANGELOG.md -->
<!-- version: 79 --> <!-- version: 81 -->
# CHANGELOG — ks-pipeline-demo-scenarios # CHANGELOG — ks-pipeline-demo-scenarios
## `0.5.2-pre.006-delta-fix-006`
- corrige le wrapper pNFT legacy : il reprend son paramètre `workspace_root` historique et construit le wallet temporaire uniquement dans ce wrapper, tandis que la variante `_with_wallet` conserve le signer explicite ;
- retire l'initialisation du champ inexistant `wallet` dans `PreparedToken2022Execution`, le signer restant fourni explicitement par l'appelant lors de la signature après simulation.
## `0.5.2-pre.006-delta-fix-005`
- introduit `DevnetExecutionWallet` comme capacité de signature commune aux wallets temporaires et aux `.kswallet` authentifiés, sans accès aux bytes privés ;
- résout explicitement `wallet_alias` vers un `.kswallet` déverrouillé et interdit toujours tout fallback silencieux vers le JSON temporaire ;
- sépare les chemins persistants et temporaires dans les scénarios/fixtures, puis branche System, Memo, ATA, SPL Token, Token-2022, Solana Program Metadata et Token-2022 Metadata sur un signer explicitement résolu ;
- propage le même signer persistant à toutes les campagnes Metaplex du desktop, y compris les parcours multi-signers ; les mints, delegates et destinations réellement jetables restent des keypairs temporaires distinctes.
## `0.5.2-pre.006` ## `0.5.2-pre.006`
- ajoute l'inspection et l'unlock explicites du wallet natif sélectionné par `profile.wallet.wallet_alias`, avec `WalletPassword` fourni uniquement au runtime ; - ajoute l'inspection et l'unlock explicites du wallet natif sélectionné par `profile.wallet.wallet_alias`, avec `WalletPassword` fourni uniquement au runtime ;

View File

@@ -1,11 +1,11 @@
<!-- file: ks-pipeline-demo-scenarios/TODO.md --> <!-- file: ks-pipeline-demo-scenarios/TODO.md -->
<!-- version: 44 --> <!-- version: 45 -->
# TODO — ks-pipeline-demo-scenarios # TODO — ks-pipeline-demo-scenarios
## Évolutions générales ## Évolutions générales
- [ ] `0.5.2`refactorer les runners Devnet qui appellent encore directement `load_profile_temporary_wallet()` afin qu'ils puissent recevoir une capacité signer issue d'un `.kswallet` explicitement déverrouillé, sans fallback silencieux. - [ ] `0.5.4`inventorier/classifier les keypairs de fixtures historiques sous `wallets/temporary/**` et préparer leur migration sélective lorsque leur rôle doit devenir persistant.
- [ ] `0.5.4` — centraliser les dispatchs, compositions decoder/materializer et critères de complétion encore réutilisables depuis le desktop. - [ ] `0.5.4` — centraliser les dispatchs, compositions decoder/materializer et critères de complétion encore réutilisables depuis le desktop.
- [ ] `0.5.4` — produire la matrice active decoder/materializer/executor/scénario/preuve sans transformer les surfaces réservées en dettes implicites. - [ ] `0.5.4` — produire la matrice active decoder/materializer/executor/scénario/preuve sans transformer les surfaces réservées en dettes implicites.
- [ ] Ajouter au CLI uniquement les préparations de fixtures explicitement justifiées. - [ ] Ajouter au CLI uniquement les préparations de fixtures explicitement justifiées.

View File

@@ -1,5 +1,5 @@
<!-- file: ks-pipeline-demo-scenarios/USAGE.md --> <!-- file: ks-pipeline-demo-scenarios/USAGE.md -->
<!-- version: 32 --> <!-- version: 33 -->
# Utilisation de ks-pipeline-demo-scenarios # Utilisation de ks-pipeline-demo-scenarios
@@ -312,7 +312,7 @@ cargo test -p ks-pipeline-demo-scenarios optional_devnet_current_operation_from_
Variables optionnelles : Variables optionnelles :
- `KS_DEVNET_WALLET_DIR` pour sélectionner le répertoire du wallet persistant ; - `KS_DEVNET_WALLET_DIR` pour surcharger le répertoire legacy de fixtures/wallets temporaires du test opt-in ;
- `KS_SECRET_POSTGRES_TEST_URL` est obligatoire pour le test opt-in afin de permettre lhydratation canonique, lextraction Core, le replay et la vérification didempotence après une soumission ; - `KS_SECRET_POSTGRES_TEST_URL` est obligatoire pour le test opt-in afin de permettre lhydratation canonique, lextraction Core, le replay et la vérification didempotence après une soumission ;
- `KS_DEVNET_METAPLEX_PREFLIGHT_READS_JSON` pour fournir les lectures stateful avant simulation ; - `KS_DEVNET_METAPLEX_PREFLIGHT_READS_JSON` pour fournir les lectures stateful avant simulation ;
- `KS_DEVNET_METAPLEX_POSTCONDITION_READS_JSON` pour fournir les lectures après confirmation ; - `KS_DEVNET_METAPLEX_POSTCONDITION_READS_JSON` pour fournir les lectures après confirmation ;

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/lib.rs // file: ks-pipeline-demo-scenarios/src/lib.rs
// version: 30 // version: 31
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -12,6 +12,7 @@ mod environment;
mod metadata; mod metadata;
mod solana; mod solana;
mod spl; mod spl;
mod wallet;
/// Readiness report for one Devnet profile PostgreSQL store. /// Readiness report for one Devnet profile PostgreSQL store.
pub use self::environment::DevnetProfileStoreReadiness; pub use self::environment::DevnetProfileStoreReadiness;
@@ -29,6 +30,8 @@ pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::Dev
pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::DevnetMetaplexCollectionVerifyState; pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::DevnetMetaplexCollectionVerifyState;
/// Executes one fresh collection parent/member verification campaign on Devnet. /// Executes one fresh collection parent/member verification campaign on Devnet.
pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::execute_devnet_metaplex_collection_verify_campaign; pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::execute_devnet_metaplex_collection_verify_campaign;
/// Executes one collection verification campaign with an explicit operator wallet.
pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::execute_devnet_metaplex_collection_verify_campaign_with_wallet;
/// Returns the exact current operations qualified by the collection verification campaign. /// Returns the exact current operations qualified by the collection verification campaign.
pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::metaplex_collection_verify_campaign_operation_names; pub use self::metadata::metaplex_token_metadata::collection_verify_campaign::metaplex_collection_verify_campaign_operation_names;
/// Complete evidence produced by one family-specific Metaplex `Create -> Mint` campaign. /// Complete evidence produced by one family-specific Metaplex `Create -> Mint` campaign.
@@ -37,6 +40,8 @@ pub use self::metadata::metaplex_token_metadata::create_mint_campaign::DevnetMet
pub use self::metadata::metaplex_token_metadata::create_mint_campaign::DevnetMetaplexCreateMintTokenState; pub use self::metadata::metaplex_token_metadata::create_mint_campaign::DevnetMetaplexCreateMintTokenState;
/// Executes one fresh family-specific Metaplex `Create -> Mint` campaign on Devnet. /// Executes one fresh family-specific Metaplex `Create -> Mint` campaign on Devnet.
pub use self::metadata::metaplex_token_metadata::create_mint_campaign::execute_devnet_metaplex_create_mint_campaign; pub use self::metadata::metaplex_token_metadata::create_mint_campaign::execute_devnet_metaplex_create_mint_campaign;
/// Executes one family-specific Metaplex `Create -> Mint` campaign with an explicit operator wallet.
pub use self::metadata::metaplex_token_metadata::create_mint_campaign::execute_devnet_metaplex_create_mint_campaign_with_wallet;
/// Returns the exact ordered operations covered by the `Create -> Mint` campaign. /// Returns the exact ordered operations covered by the `Create -> Mint` campaign.
pub use self::metadata::metaplex_token_metadata::create_mint_campaign::metaplex_create_mint_campaign_operation_names; pub use self::metadata::metaplex_token_metadata::create_mint_campaign::metaplex_create_mint_campaign_operation_names;
/// Complete request for one real Devnet Metaplex Token Metadata execution. /// Complete request for one real Devnet Metaplex Token Metadata execution.
@@ -51,18 +56,26 @@ pub use self::metadata::metaplex_token_metadata::devnet_execution::devnet_metapl
pub use self::metadata::metaplex_token_metadata::devnet_execution::execute_devnet_metaplex_token_metadata; pub use self::metadata::metaplex_token_metadata::devnet_execution::execute_devnet_metaplex_token_metadata;
/// Executes one real Devnet Metaplex submission with explicitly authorized extra signers. /// Executes one real Devnet Metaplex submission with explicitly authorized extra signers.
pub use self::metadata::metaplex_token_metadata::devnet_execution::execute_devnet_metaplex_token_metadata_with_signers; pub use self::metadata::metaplex_token_metadata::devnet_execution::execute_devnet_metaplex_token_metadata_with_signers;
/// Executes one real Devnet Metaplex operation with an explicitly resolved wallet.
pub use self::metadata::metaplex_token_metadata::devnet_execution::execute_devnet_metaplex_token_metadata_with_wallet;
/// Executes one real Devnet Metaplex submission with an explicit wallet and extra signers.
pub use self::metadata::metaplex_token_metadata::devnet_execution::execute_devnet_metaplex_token_metadata_with_wallet_and_signers;
/// Returns a formatted JSON template for one named current operation. /// Returns a formatted JSON template for one named current operation.
pub use self::metadata::metaplex_token_metadata::devnet_execution::metaplex_token_metadata_current_operation_json_template; pub use self::metadata::metaplex_token_metadata::devnet_execution::metaplex_token_metadata_current_operation_json_template;
/// Returns the current operation names accepted by automatic Devnet campaigns. /// Returns the current operation names accepted by automatic Devnet campaigns.
pub use self::metadata::metaplex_token_metadata::devnet_execution::metaplex_token_metadata_current_operation_names; pub use self::metadata::metaplex_token_metadata::devnet_execution::metaplex_token_metadata_current_operation_names;
/// Simulates one current Metaplex operation against a real Devnet endpoint. /// Simulates one current Metaplex operation against a real Devnet endpoint.
pub use self::metadata::metaplex_token_metadata::devnet_execution::simulate_devnet_metaplex_token_metadata; pub use self::metadata::metaplex_token_metadata::devnet_execution::simulate_devnet_metaplex_token_metadata;
/// Simulates one current Metaplex operation with an explicitly resolved wallet.
pub use self::metadata::metaplex_token_metadata::devnet_execution::simulate_devnet_metaplex_token_metadata_with_wallet;
/// Exact SPL state transitions exercised around the Token Owned Escrow operations. /// Exact SPL state transitions exercised around the Token Owned Escrow operations.
pub use self::metadata::metaplex_token_metadata::escrow_campaign::DevnetMetaplexEscrowCampaignState; pub use self::metadata::metaplex_token_metadata::escrow_campaign::DevnetMetaplexEscrowCampaignState;
/// Complete evidence retained by one Token Owned Escrow Devnet campaign. /// Complete evidence retained by one Token Owned Escrow Devnet campaign.
pub use self::metadata::metaplex_token_metadata::escrow_campaign::DevnetMetaplexEscrowCampaignSummary; pub use self::metadata::metaplex_token_metadata::escrow_campaign::DevnetMetaplexEscrowCampaignSummary;
/// Executes a fresh Token Owned Escrow lifecycle on Devnet. /// Executes a fresh Token Owned Escrow lifecycle on Devnet.
pub use self::metadata::metaplex_token_metadata::escrow_campaign::execute_devnet_metaplex_escrow_campaign; pub use self::metadata::metaplex_token_metadata::escrow_campaign::execute_devnet_metaplex_escrow_campaign;
/// Executes a Token Owned Escrow lifecycle with an explicit operator wallet.
pub use self::metadata::metaplex_token_metadata::escrow_campaign::execute_devnet_metaplex_escrow_campaign_with_wallet;
/// Returns the exact ordered Metaplex operations qualified by the escrow campaign. /// Returns the exact ordered Metaplex operations qualified by the escrow campaign.
pub use self::metadata::metaplex_token_metadata::escrow_campaign::metaplex_escrow_campaign_operation_names; pub use self::metadata::metaplex_token_metadata::escrow_campaign::metaplex_escrow_campaign_operation_names;
/// Options for one Metaplex Create fixture preparation. /// Options for one Metaplex Create fixture preparation.
@@ -77,6 +90,8 @@ pub(crate) use self::metadata::metaplex_token_metadata::fixture::classic_fixture
pub(crate) use self::metadata::metaplex_token_metadata::fixture::classic_fixture_token_amount; pub(crate) use self::metadata::metaplex_token_metadata::fixture::classic_fixture_token_amount;
/// Creates or reuses a classic SPL mint and derives its Metaplex PDAs. /// Creates or reuses a classic SPL mint and derives its Metaplex PDAs.
pub use self::metadata::metaplex_token_metadata::fixture::prepare_metaplex_create_fixture; pub use self::metadata::metaplex_token_metadata::fixture::prepare_metaplex_create_fixture;
/// Creates a classic SPL mint fixture using an explicitly resolved operator wallet.
pub use self::metadata::metaplex_token_metadata::fixture::prepare_metaplex_create_fixture_with_wallet;
/// Reads one classic SPL mint account at or after a minimum context slot. /// Reads one classic SPL mint account at or after a minimum context slot.
pub(crate) use self::metadata::metaplex_token_metadata::fixture::read_mint_account_at_or_after; pub(crate) use self::metadata::metaplex_token_metadata::fixture::read_mint_account_at_or_after;
/// Reads one classic SPL token account at or after a minimum context slot. /// Reads one classic SPL token account at or after a minimum context slot.
@@ -89,6 +104,8 @@ pub(crate) use self::metadata::metaplex_token_metadata::fixture::validate_classi
pub use self::metadata::metaplex_token_metadata::maintenance_campaign::DevnetMetaplexMaintenanceCampaignSummary; pub use self::metadata::metaplex_token_metadata::maintenance_campaign::DevnetMetaplexMaintenanceCampaignSummary;
/// Executes the final bounded current maintenance campaign on Devnet. /// Executes the final bounded current maintenance campaign on Devnet.
pub use self::metadata::metaplex_token_metadata::maintenance_campaign::execute_devnet_metaplex_maintenance_campaign; pub use self::metadata::metaplex_token_metadata::maintenance_campaign::execute_devnet_metaplex_maintenance_campaign;
/// Executes the bounded maintenance campaign with an explicit operator wallet.
pub use self::metadata::metaplex_token_metadata::maintenance_campaign::execute_devnet_metaplex_maintenance_campaign_with_wallet;
/// Returns the exact ordered operations exercised by the maintenance campaign. /// Returns the exact ordered operations exercised by the maintenance campaign.
pub use self::metadata::metaplex_token_metadata::maintenance_campaign::metaplex_maintenance_campaign_operation_names; pub use self::metadata::metaplex_token_metadata::maintenance_campaign::metaplex_maintenance_campaign_operation_names;
/// Complete evidence produced by one pNFT lifecycle campaign. /// Complete evidence produced by one pNFT lifecycle campaign.
@@ -97,6 +114,8 @@ pub use self::metadata::metaplex_token_metadata::pnft_lifecycle_campaign::Devnet
pub use self::metadata::metaplex_token_metadata::pnft_lifecycle_campaign::DevnetMetaplexPnftLifecycleState; pub use self::metadata::metaplex_token_metadata::pnft_lifecycle_campaign::DevnetMetaplexPnftLifecycleState;
/// Executes one fresh pNFT delegate/lock/unlock/revoke/transfer campaign on Devnet. /// Executes one fresh pNFT delegate/lock/unlock/revoke/transfer campaign on Devnet.
pub use self::metadata::metaplex_token_metadata::pnft_lifecycle_campaign::execute_devnet_metaplex_pnft_lifecycle_campaign; pub use self::metadata::metaplex_token_metadata::pnft_lifecycle_campaign::execute_devnet_metaplex_pnft_lifecycle_campaign;
/// Executes one pNFT lifecycle campaign with an explicit operator wallet.
pub use self::metadata::metaplex_token_metadata::pnft_lifecycle_campaign::execute_devnet_metaplex_pnft_lifecycle_campaign_with_wallet;
/// Returns the exact ordered current operations exercised by the pNFT lifecycle campaign. /// Returns the exact ordered current operations exercised by the pNFT lifecycle campaign.
pub use self::metadata::metaplex_token_metadata::pnft_lifecycle_campaign::metaplex_pnft_lifecycle_campaign_operation_names; pub use self::metadata::metaplex_token_metadata::pnft_lifecycle_campaign::metaplex_pnft_lifecycle_campaign_operation_names;
/// Complete evidence produced by one printable master NFT `Print -> Burn` campaign. /// Complete evidence produced by one printable master NFT `Print -> Burn` campaign.
@@ -105,6 +124,8 @@ pub use self::metadata::metaplex_token_metadata::print_burn_campaign::DevnetMeta
pub use self::metadata::metaplex_token_metadata::print_burn_campaign::DevnetMetaplexPrintBurnState; pub use self::metadata::metaplex_token_metadata::print_burn_campaign::DevnetMetaplexPrintBurnState;
/// Executes one printable master NFT `Print -> Burn` campaign on Devnet. /// Executes one printable master NFT `Print -> Burn` campaign on Devnet.
pub use self::metadata::metaplex_token_metadata::print_burn_campaign::execute_devnet_metaplex_print_burn_campaign; pub use self::metadata::metaplex_token_metadata::print_burn_campaign::execute_devnet_metaplex_print_burn_campaign;
/// Executes one printable master NFT campaign with an explicit operator wallet.
pub use self::metadata::metaplex_token_metadata::print_burn_campaign::execute_devnet_metaplex_print_burn_campaign_with_wallet;
/// Returns the exact ordered current operations qualified by the print/burn campaign. /// Returns the exact ordered current operations qualified by the print/burn campaign.
pub use self::metadata::metaplex_token_metadata::print_burn_campaign::metaplex_print_burn_campaign_operation_names; pub use self::metadata::metaplex_token_metadata::print_burn_campaign::metaplex_print_burn_campaign_operation_names;
/// Stable asset family covered by one Metaplex scenario. /// Stable asset family covered by one Metaplex scenario.
@@ -127,6 +148,8 @@ pub use self::metadata::metaplex_token_metadata::use_campaign::DevnetMetaplexUse
pub use self::metadata::metaplex_token_metadata::use_campaign::DevnetMetaplexUseProbeSummary; pub use self::metadata::metaplex_token_metadata::use_campaign::DevnetMetaplexUseProbeSummary;
/// Executes one bounded current `Use` availability probe on Devnet. /// Executes one bounded current `Use` availability probe on Devnet.
pub use self::metadata::metaplex_token_metadata::use_campaign::execute_devnet_metaplex_use_probe; pub use self::metadata::metaplex_token_metadata::use_campaign::execute_devnet_metaplex_use_probe;
/// Executes one bounded current `Use` probe with an explicit operator wallet.
pub use self::metadata::metaplex_token_metadata::use_campaign::execute_devnet_metaplex_use_probe_with_wallet;
/// Returns the exact setup and target operations exercised by the `Use` probe. /// Returns the exact setup and target operations exercised by the `Use` probe.
pub use self::metadata::metaplex_token_metadata::use_campaign::metaplex_use_probe_operation_names; pub use self::metadata::metaplex_token_metadata::use_campaign::metaplex_use_probe_operation_names;
/// Maximum evidence entries accepted by one Metaplex validation scenario. /// Maximum evidence entries accepted by one Metaplex validation scenario.
@@ -173,14 +196,20 @@ pub use self::metadata::solana_program::campaign::DevnetSolanaProgramMetadataCam
pub use self::metadata::solana_program::campaign::DevnetSolanaProgramMetadataCampaignSummary; pub use self::metadata::solana_program::campaign::DevnetSolanaProgramMetadataCampaignSummary;
/// Prepares fresh accounts and executes the nine stable operations on Devnet. /// Prepares fresh accounts and executes the nine stable operations on Devnet.
pub use self::metadata::solana_program::campaign::execute_devnet_solana_program_metadata_campaign; pub use self::metadata::solana_program::campaign::execute_devnet_solana_program_metadata_campaign;
/// Executes the complete Solana Program Metadata campaign with an explicitly resolved wallet.
pub use self::metadata::solana_program::campaign::execute_devnet_solana_program_metadata_campaign_with_wallet;
/// Complete request for one Solana Program Metadata Devnet execution. /// Complete request for one Solana Program Metadata Devnet execution.
pub use self::metadata::solana_program::devnet_execution::DevnetSolanaProgramMetadataExecutionRequest; pub use self::metadata::solana_program::devnet_execution::DevnetSolanaProgramMetadataExecutionRequest;
/// Complete evidence produced by one Solana Program Metadata Devnet execution. /// Complete evidence produced by one Solana Program Metadata Devnet execution.
pub use self::metadata::solana_program::devnet_execution::DevnetSolanaProgramMetadataExecutionSummary; pub use self::metadata::solana_program::devnet_execution::DevnetSolanaProgramMetadataExecutionSummary;
/// Executes one Solana Program Metadata simulation or authorized submission. /// Executes one Solana Program Metadata simulation or authorized submission.
pub use self::metadata::solana_program::devnet_execution::execute_devnet_solana_program_metadata; pub use self::metadata::solana_program::devnet_execution::execute_devnet_solana_program_metadata;
/// Executes one Solana Program Metadata operation with an explicitly resolved wallet.
pub use self::metadata::solana_program::devnet_execution::execute_devnet_solana_program_metadata_with_wallet;
/// Simulates one stable Solana Program Metadata operation against Devnet. /// Simulates one stable Solana Program Metadata operation against Devnet.
pub use self::metadata::solana_program::devnet_execution::simulate_devnet_solana_program_metadata; pub use self::metadata::solana_program::devnet_execution::simulate_devnet_solana_program_metadata;
/// Simulates one Solana Program Metadata operation with an explicitly resolved wallet.
pub use self::metadata::solana_program::devnet_execution::simulate_devnet_solana_program_metadata_with_wallet;
/// Bytes reserved for the public Buffer journey before `Trim`. /// Bytes reserved for the public Buffer journey before `Trim`.
pub use self::metadata::solana_program::fixture::SOLANA_PROGRAM_METADATA_FIXTURE_BUFFER_DATA_BYTES; pub use self::metadata::solana_program::fixture::SOLANA_PROGRAM_METADATA_FIXTURE_BUFFER_DATA_BYTES;
/// Options used to prepare one complete Solana Program Metadata fixture. /// Options used to prepare one complete Solana Program Metadata fixture.
@@ -191,6 +220,8 @@ pub use self::metadata::solana_program::fixture::SolanaProgramMetadataFixturePre
pub use self::metadata::solana_program::fixture::SolanaProgramMetadataPreparedStep; pub use self::metadata::solana_program::fixture::SolanaProgramMetadataPreparedStep;
/// Creates two unique pre-funded PDAs and the nine typed journey steps. /// Creates two unique pre-funded PDAs and the nine typed journey steps.
pub use self::metadata::solana_program::fixture::prepare_solana_program_metadata_fixture; pub use self::metadata::solana_program::fixture::prepare_solana_program_metadata_fixture;
/// Creates the Solana Program Metadata fixture with an explicitly resolved wallet.
pub use self::metadata::solana_program::fixture::prepare_solana_program_metadata_fixture_with_wallet;
/// Stable lifecycle state consumed or produced by one scenario step. /// Stable lifecycle state consumed or produced by one scenario step.
pub use self::metadata::solana_program::scenarios::SolanaProgramMetadataFixtureState; pub use self::metadata::solana_program::scenarios::SolanaProgramMetadataFixtureState;
/// One ordered multi-step Solana Program Metadata journey. /// One ordered multi-step Solana Program Metadata journey.
@@ -231,6 +262,8 @@ pub(crate) use self::solana::emit;
pub(crate) use self::solana::ensure_not_cancelled; pub(crate) use self::solana::ensure_not_cancelled;
/// Executes one bounded System Program transfer on Devnet. /// Executes one bounded System Program transfer on Devnet.
pub use self::solana::execute_devnet_system_transfer; pub use self::solana::execute_devnet_system_transfer;
/// Executes one bounded System transfer with an explicitly resolved wallet capability.
pub use self::solana::execute_devnet_system_transfer_with_wallet;
/// Inspects the persistent native wallet alias selected by one execution profile. /// Inspects the persistent native wallet alias selected by one execution profile.
pub use self::solana::inspect_selected_profile_wallet; pub use self::solana::inspect_selected_profile_wallet;
/// Loads the managed temporary wallet used by legacy Devnet scenarios. /// Loads the managed temporary wallet used by legacy Devnet scenarios.
@@ -247,6 +280,8 @@ pub use self::spl::associated_token_account::DevnetSplAssociatedTokenAccountExec
pub use self::spl::associated_token_account::DevnetSplAssociatedTokenAccountExecutionSummary; pub use self::spl::associated_token_account::DevnetSplAssociatedTokenAccountExecutionSummary;
/// Executes one Devnet Associated Token Account simulation or authorized submission. /// Executes one Devnet Associated Token Account simulation or authorized submission.
pub use self::spl::associated_token_account::execute_devnet_spl_associated_token_account; pub use self::spl::associated_token_account::execute_devnet_spl_associated_token_account;
/// Executes one ATA operation with an explicitly resolved wallet capability.
pub use self::spl::associated_token_account::execute_devnet_spl_associated_token_account_with_wallet;
/// Simulates one Devnet Associated Token Account operation after stateful preflight. /// Simulates one Devnet Associated Token Account operation after stateful preflight.
pub use self::spl::associated_token_account::simulate_devnet_spl_associated_token_account; pub use self::spl::associated_token_account::simulate_devnet_spl_associated_token_account;
/// Complete request for one SPL Memo v4 Devnet execution. /// Complete request for one SPL Memo v4 Devnet execution.
@@ -255,6 +290,8 @@ pub use self::spl::memo::DevnetMemoExecutionRequest;
pub use self::spl::memo::DevnetMemoExecutionSummary; pub use self::spl::memo::DevnetMemoExecutionSummary;
/// Executes one SPL Memo v4 Devnet simulation or explicitly authorized submission. /// Executes one SPL Memo v4 Devnet simulation or explicitly authorized submission.
pub use self::spl::memo::execute_devnet_memo; pub use self::spl::memo::execute_devnet_memo;
/// Executes one Memo operation with an explicitly resolved wallet capability.
pub use self::spl::memo::execute_devnet_memo_with_wallet;
/// Complete request for one Devnet classic SPL Token execution. /// Complete request for one Devnet classic SPL Token execution.
pub use self::spl::token::execution::DevnetSplTokenExecutionRequest; pub use self::spl::token::execution::DevnetSplTokenExecutionRequest;
/// Complete result of one Devnet classic SPL Token execution. /// Complete result of one Devnet classic SPL Token execution.
@@ -265,6 +302,8 @@ pub(crate) use self::spl::token::execution::canonical_available;
pub(crate) use self::spl::token::execution::decode_completed; pub(crate) use self::spl::token::execution::decode_completed;
/// Executes one Devnet classic SPL Token simulation or authorized submission. /// Executes one Devnet classic SPL Token simulation or authorized submission.
pub use self::spl::token::execution::execute_devnet_spl_token; pub use self::spl::token::execution::execute_devnet_spl_token;
/// Executes one SPL Token operation with an explicitly resolved wallet capability.
pub use self::spl::token::execution::execute_devnet_spl_token_with_wallet;
/// Canonical signature hydration helper shared by Token lifecycle orchestration. /// Canonical signature hydration helper shared by Token lifecycle orchestration.
pub(crate) use self::spl::token::execution::hydrate_signature; pub(crate) use self::spl::token::execution::hydrate_signature;
/// Targeted program replay helper shared by Token lifecycle orchestration. /// Targeted program replay helper shared by Token lifecycle orchestration.
@@ -293,6 +332,8 @@ pub use self::spl::token_2022::devnet_execution::DevnetSplToken2022ExecutionRequ
pub use self::spl::token_2022::devnet_execution::DevnetSplToken2022ExecutionSummary; pub use self::spl::token_2022::devnet_execution::DevnetSplToken2022ExecutionSummary;
/// Executes one Devnet Token-2022 simulation or authorized submission. /// Executes one Devnet Token-2022 simulation or authorized submission.
pub use self::spl::token_2022::devnet_execution::execute_devnet_spl_token_2022; pub use self::spl::token_2022::devnet_execution::execute_devnet_spl_token_2022;
/// Executes one Token-2022 operation with an explicitly resolved wallet capability.
pub use self::spl::token_2022::devnet_execution::execute_devnet_spl_token_2022_with_wallet;
/// Simulates one Devnet Token-2022 operation after stateful preflight. /// Simulates one Devnet Token-2022 operation after stateful preflight.
pub use self::spl::token_2022::devnet_execution::simulate_devnet_spl_token_2022; pub use self::spl::token_2022::devnet_execution::simulate_devnet_spl_token_2022;
/// Stable category of one independent Devnet validation scenario. /// Stable category of one independent Devnet validation scenario.
@@ -323,6 +364,8 @@ pub use self::spl::token_2022::metadata::campaign::DevnetToken2022MetadataCampai
pub use self::spl::token_2022::metadata::campaign::DevnetToken2022MetadataCampaignSummary; pub use self::spl::token_2022::metadata::campaign::DevnetToken2022MetadataCampaignSummary;
/// Executes all five Token Metadata interface instructions on one fresh Devnet mint. /// Executes all five Token Metadata interface instructions on one fresh Devnet mint.
pub use self::spl::token_2022::metadata::campaign::execute_devnet_token_2022_metadata_campaign; pub use self::spl::token_2022::metadata::campaign::execute_devnet_token_2022_metadata_campaign;
/// Executes the Token Metadata campaign with an explicitly resolved wallet.
pub use self::spl::token_2022::metadata::campaign::execute_devnet_token_2022_metadata_campaign_with_wallet;
/// Returns the exact ordered operation names covered by the Token Metadata campaign. /// Returns the exact ordered operation names covered by the Token Metadata campaign.
pub use self::spl::token_2022::metadata::campaign::token_2022_metadata_campaign_operation_names; pub use self::spl::token_2022::metadata::campaign::token_2022_metadata_campaign_operation_names;
/// Additional metadata key created and removed by the campaign. /// Additional metadata key created and removed by the campaign.
@@ -341,6 +384,8 @@ pub use self::spl::token_2022::metadata::fixture::Token2022MetadataFixturePrepar
pub use self::spl::token_2022::metadata::fixture::Token2022MetadataFixturePreparationSummary; pub use self::spl::token_2022::metadata::fixture::Token2022MetadataFixturePreparationSummary;
/// Creates one fresh Token-2022 mint with a self-referential Metadata Pointer. /// Creates one fresh Token-2022 mint with a self-referential Metadata Pointer.
pub use self::spl::token_2022::metadata::fixture::prepare_token_2022_metadata_fixture; pub use self::spl::token_2022::metadata::fixture::prepare_token_2022_metadata_fixture;
/// Creates the Token-2022 metadata fixture with an explicitly resolved wallet.
pub use self::spl::token_2022::metadata::fixture::prepare_token_2022_metadata_fixture_with_wallet;
/// Maximum evidence entries retained by one Token Metadata validation scenario. /// Maximum evidence entries retained by one Token Metadata validation scenario.
pub use self::spl::token_2022::metadata::validation::MAX_TOKEN_2022_METADATA_VALIDATION_EVIDENCE; pub use self::spl::token_2022::metadata::validation::MAX_TOKEN_2022_METADATA_VALIDATION_EVIDENCE;
/// One bounded evidence item retained by the Token Metadata validation matrix. /// One bounded evidence item retained by the Token Metadata validation matrix.
@@ -377,6 +422,10 @@ pub use self::spl::token_2022::validation::load_token_2022_validation_matrix;
pub use self::spl::token_2022::validation::validate_token_2022_validation_matrix; pub use self::spl::token_2022::validation::validate_token_2022_validation_matrix;
/// Validates one bounded Token-2022 milestone report. /// Validates one bounded Token-2022 milestone report.
pub use self::spl::token_2022::validation::validate_token_2022_validation_report; pub use self::spl::token_2022::validation::validate_token_2022_validation_report;
/// Resolved temporary or persistent signer capability used by Devnet scenarios.
pub use self::wallet::DevnetExecutionWallet;
/// Resolves the wallet selected by one Devnet execution profile.
pub use self::wallet::resolve_devnet_execution_wallet;
/// Total transactions reserved by one complete Solana Program Metadata campaign. /// Total transactions reserved by one complete Solana Program Metadata campaign.
pub(crate) use self::constants::SOLANA_PROGRAM_METADATA_CAMPAIGN_TRANSACTION_COUNT; pub(crate) use self::constants::SOLANA_PROGRAM_METADATA_CAMPAIGN_TRANSACTION_COUNT;

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/collection_verify_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/collection_verify_campaign.rs
// version: 6 // version: 7
//! Confirmed Devnet collection parent/member `Verify -> Unverify` campaign. //! Confirmed Devnet collection parent/member `Verify -> Unverify` campaign.
@@ -50,6 +50,34 @@ pub async fn execute_devnet_metaplex_collection_verify_campaign<S, O>(
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexCollectionVerifyCampaignSummary> ) -> ks_core::Result<crate::DevnetMetaplexCollectionVerifyCampaignSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_metaplex_collection_verify_campaign_with_wallet(
http_pool, store, profile, &wallet, options, observer,
)
.await;
}
/// Executes one collection verification campaign with an explicit operator wallet.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_metaplex_collection_verify_campaign_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexCollectionVerifyCampaignSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -65,11 +93,11 @@ where
let mut parent_options = options.clone(); let mut parent_options = options.clone();
parent_options.asset_family = crate::MetaplexTokenMetadataAssetFamily::Collection; parent_options.asset_family = crate::MetaplexTokenMetadataAssetFamily::Collection;
parent_options.collection_mint = std::option::Option::None; parent_options.collection_mint = std::option::Option::None;
let parent = match crate::execute_devnet_metaplex_create_mint_campaign( let parent = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&parent_options, &parent_options,
observer, observer,
) )
@@ -81,11 +109,11 @@ where
let mut member_options = options.clone(); let mut member_options = options.clone();
member_options.asset_family = crate::MetaplexTokenMetadataAssetFamily::Nft; member_options.asset_family = crate::MetaplexTokenMetadataAssetFamily::Nft;
member_options.collection_mint = std::option::Option::Some(parent.fixture.mint.clone()); member_options.collection_mint = std::option::Option::Some(parent.fixture.mint.clone());
let member = match crate::execute_devnet_metaplex_create_mint_campaign( let member = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&member_options, &member_options,
observer, observer,
) )
@@ -168,11 +196,11 @@ where
options.query_role.as_str(), options.query_role.as_str(),
std::option::Option::Some(fixture_slot), std::option::Option::Some(fixture_slot),
); );
let verify = match crate::execute_devnet_metaplex_token_metadata( let verify = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&verify_request, &verify_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -240,11 +268,11 @@ where
options.query_role.as_str(), options.query_role.as_str(),
std::option::Option::Some(verify_slot), std::option::Option::Some(verify_slot),
); );
let unverify = match crate::execute_devnet_metaplex_token_metadata( let unverify = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&unverify_request, &unverify_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -667,7 +695,8 @@ mod tests {
if let std::result::Result::Err(error) = store.initialize_store_schema().await { if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}"); panic!("PostgreSQL schema initialization failed: {error}");
} }
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/create_mint_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/create_mint_campaign.rs
// version: 14 // version: 15
//! Confirmed Devnet `Create -> Mint` campaign for one Metaplex asset family. //! Confirmed Devnet `Create -> Mint` campaign for one Metaplex asset family.
@@ -52,6 +52,34 @@ pub async fn execute_devnet_metaplex_create_mint_campaign<S, O>(
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexCreateMintCampaignSummary> ) -> ks_core::Result<crate::DevnetMetaplexCreateMintCampaignSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_metaplex_create_mint_campaign_with_wallet(
http_pool, store, profile, &wallet, options, observer,
)
.await;
}
/// Executes one fresh family-specific `Create -> Mint` campaign with an explicit operator wallet.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_metaplex_create_mint_campaign_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexCreateMintCampaignSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -64,8 +92,9 @@ where
"Metaplex Create -> Mint Devnet campaign requires devnet_send_enabled=true", "Metaplex Create -> Mint Devnet campaign requires devnet_send_enabled=true",
)); ));
} }
let fixture = let fixture = match crate::prepare_metaplex_create_fixture_with_wallet(
match crate::prepare_metaplex_create_fixture(http_pool, profile, workspace_root, options) http_pool, profile, wallet, options,
)
.await .await
{ {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -100,11 +129,11 @@ where
std::option::Option::None, std::option::Option::None,
false, false,
); );
let create = match crate::execute_devnet_metaplex_token_metadata( let create = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&create_request, &create_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -239,11 +268,11 @@ where
std::option::Option::Some(create_slot), std::option::Option::Some(create_slot),
true, true,
); );
let mint = match crate::execute_devnet_metaplex_token_metadata( let mint = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&mint_request, &mint_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -832,7 +861,8 @@ mod tests {
if let std::result::Result::Err(error) = store.initialize_store_schema().await { if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}"); panic!("PostgreSQL schema initialization failed: {error}");
} }
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs
// version: 23 // version: 24
//! Real Devnet simulation and submission for current Metaplex Token Metadata operations. //! Real Devnet simulation and submission for current Metaplex Token Metadata operations.
@@ -363,7 +363,6 @@ pub struct DevnetMetaplexTokenMetadataExecutionSummary {
} }
struct PreparedMetaplexExecution { struct PreparedMetaplexExecution {
wallet: ks_wallet::TemporaryWallet,
unsigned: ks_lib::ExSolanaUnsignedTransaction, unsigned: ks_lib::ExSolanaUnsignedTransaction,
evidence: ks_lib::ExSolanaSimulationEvidence, evidence: ks_lib::ExSolanaSimulationEvidence,
summary: crate::DevnetMetaplexTokenMetadataExecutionSummary, summary: crate::DevnetMetaplexTokenMetadataExecutionSummary,
@@ -377,6 +376,28 @@ pub async fn simulate_devnet_metaplex_token_metadata<O>(
request: &crate::DevnetMetaplexTokenMetadataExecutionRequest, request: &crate::DevnetMetaplexTokenMetadataExecutionRequest,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary> ) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary>
where
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return simulate_devnet_metaplex_token_metadata_with_wallet(
http_pool, profile, &wallet, request, observer,
)
.await;
}
/// Simulates one current Metaplex operation with an explicitly resolved wallet.
pub async fn simulate_devnet_metaplex_token_metadata_with_wallet<O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetMetaplexTokenMetadataExecutionRequest,
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary>
where where
O: crate::SolanaExecutionObserver, O: crate::SolanaExecutionObserver,
{ {
@@ -385,8 +406,8 @@ where
"simulate_devnet_metaplex_token_metadata requires submit=false", "simulate_devnet_metaplex_token_metadata requires submit=false",
)); ));
} }
let prepared = let prepared = match prepare_execution(http_pool, profile, wallet, request, &[], observer).await
match prepare_execution(http_pool, profile, workspace_root, request, &[], observer).await { {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -412,11 +433,48 @@ where
+ Sync, + Sync,
O: crate::SolanaExecutionObserver, O: crate::SolanaExecutionObserver,
{ {
return crate::execute_devnet_metaplex_token_metadata_with_signers( let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, &wallet,
request,
decoders,
materializers,
observer,
)
.await;
}
/// Executes one real Devnet Metaplex operation with an explicitly resolved wallet.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_metaplex_token_metadata_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetMetaplexTokenMetadataExecutionRequest,
decoders: &[std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>],
materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>],
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
return execute_devnet_metaplex_token_metadata_with_wallet_and_signers(
http_pool,
store,
profile,
wallet,
request, request,
decoders, decoders,
materializers, materializers,
@@ -446,14 +504,47 @@ where
+ Sync, + Sync,
O: crate::SolanaExecutionObserver, O: crate::SolanaExecutionObserver,
{ {
let prepared = match prepare_execution( let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_metaplex_token_metadata_with_wallet_and_signers(
http_pool, http_pool,
store,
profile, profile,
workspace_root, &wallet,
request, request,
decoders,
materializers,
additional_signers, additional_signers,
observer, observer,
) )
.await;
}
/// Executes one real Devnet Metaplex submission with an explicit wallet and extra signers.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_metaplex_token_metadata_with_wallet_and_signers<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetMetaplexTokenMetadataExecutionRequest,
decoders: &[std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>],
materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>],
additional_signers: &[&(dyn solana_signer::Signer + std::marker::Sync)],
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let prepared =
match prepare_execution(http_pool, profile, wallet, request, additional_signers, observer)
.await .await
{ {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -483,7 +574,7 @@ where
let signed = match sign_prepared_transaction( let signed = match sign_prepared_transaction(
prepared.unsigned, prepared.unsigned,
&prepared.evidence, &prepared.evidence,
prepared.wallet.as_sync_signer(), wallet.as_sync_signer(),
additional_signers, additional_signers,
) { ) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -805,7 +896,7 @@ fn replay_summary_diagnostic(
async fn prepare_execution<O>( async fn prepare_execution<O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetMetaplexTokenMetadataExecutionRequest, request: &crate::DevnetMetaplexTokenMetadataExecutionRequest,
additional_signers: &[&(dyn solana_signer::Signer + std::marker::Sync)], additional_signers: &[&(dyn solana_signer::Signer + std::marker::Sync)],
observer: &O, observer: &O,
@@ -839,11 +930,7 @@ where
), ),
)); ));
} }
let wallet = match crate::load_profile_temporary_wallet(profile, workspace_root).await { let wallet_summary = wallet.identity();
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_summary = wallet.summary();
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone()); let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
let balance = match http_pool let balance = match http_pool
.get_balance_for_role( .get_balance_for_role(
@@ -1000,7 +1087,6 @@ where
}; };
let evidence = unsigned.bind_simulation(simulation.clone()); let evidence = unsigned.bind_simulation(simulation.clone());
return std::result::Result::Ok(PreparedMetaplexExecution { return std::result::Result::Ok(PreparedMetaplexExecution {
wallet,
unsigned, unsigned,
evidence, evidence,
summary: crate::DevnetMetaplexTokenMetadataExecutionSummary { summary: crate::DevnetMetaplexTokenMetadataExecutionSummary {
@@ -1175,9 +1261,11 @@ fn validate_profile(
"Metaplex Devnet orchestration requires a Devnet wallet profile", "Metaplex Devnet orchestration requires a Devnet wallet profile",
)); ));
} }
if !profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist { if profile.wallet.wallet_alias.is_none()
&& (!profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist)
{
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
"Metaplex Devnet orchestration requires an enabled persistent temporary wallet", "Metaplex Devnet orchestration requires an enabled persistent temporary wallet when no native wallet alias is selected",
)); ));
} }
if !profile.execution.require_simulation { if !profile.execution.require_simulation {
@@ -1416,7 +1504,7 @@ mod tests {
}; };
let mut profile = example_devnet_profile(); let mut profile = example_devnet_profile();
if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") { if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") {
profile.wallet.wallet_dir = directory; profile.wallet.temporary_wallet_dir = directory;
} }
let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) { let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/escrow_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/escrow_campaign.rs
// version: 6 // version: 7
//! Confirmed Devnet Token Owned Escrow campaign for current Metaplex escrow operations. //! Confirmed Devnet Token Owned Escrow campaign for current Metaplex escrow operations.
@@ -64,6 +64,34 @@ pub async fn execute_devnet_metaplex_escrow_campaign<S, O>(
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexEscrowCampaignSummary> ) -> ks_core::Result<crate::DevnetMetaplexEscrowCampaignSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_metaplex_escrow_campaign_with_wallet(
http_pool, store, profile, &wallet, options, observer,
)
.await;
}
/// Executes a fresh Token Owned Escrow lifecycle with an explicit operator wallet.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_metaplex_escrow_campaign_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexEscrowCampaignSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -80,11 +108,11 @@ where
clean_fixture_options(options, crate::MetaplexTokenMetadataAssetFamily::Nft); clean_fixture_options(options, crate::MetaplexTokenMetadataAssetFamily::Nft);
let attribute_options = let attribute_options =
clean_fixture_options(options, crate::MetaplexTokenMetadataAssetFamily::Fungible); clean_fixture_options(options, crate::MetaplexTokenMetadataAssetFamily::Fungible);
let parent_fixture = match crate::execute_devnet_metaplex_create_mint_campaign( let parent_fixture = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&parent_options, &parent_options,
observer, observer,
) )
@@ -93,11 +121,11 @@ where
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let attribute_fixture = match crate::execute_devnet_metaplex_create_mint_campaign( let attribute_fixture = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&attribute_options, &attribute_options,
observer, observer,
) )
@@ -148,11 +176,11 @@ where
parent_options.query_role.as_str(), parent_options.query_role.as_str(),
std::option::Option::None, std::option::Option::None,
); );
let create_escrow = match crate::execute_devnet_metaplex_token_metadata( let create_escrow = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&create_request, &create_request,
metaplex_decoders.as_slice(), metaplex_decoders.as_slice(),
metaplex_materializers.as_slice(), metaplex_materializers.as_slice(),
@@ -195,11 +223,12 @@ where
ata_request.submit = true; ata_request.submit = true;
ata_request.operator_confirmed = true; ata_request.operator_confirmed = true;
ata_request.post_validation_max_retries = 20; ata_request.post_validation_max_retries = 20;
let escrow_attribute_ata_creation = match crate::execute_devnet_spl_associated_token_account( let escrow_attribute_ata_creation =
match crate::execute_devnet_spl_associated_token_account_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&ata_request, &ata_request,
ata_decoders.as_slice(), ata_decoders.as_slice(),
ata_materializers.as_slice(), ata_materializers.as_slice(),
@@ -271,11 +300,11 @@ where
deposit_request.submit = true; deposit_request.submit = true;
deposit_request.operator_confirmed = true; deposit_request.operator_confirmed = true;
deposit_request.post_validation_max_retries = 20; deposit_request.post_validation_max_retries = 20;
let attribute_deposit = match crate::execute_devnet_spl_token( let attribute_deposit = match crate::execute_devnet_spl_token_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&deposit_request, &deposit_request,
token_decoders.as_slice(), token_decoders.as_slice(),
token_materializers.as_slice(), token_materializers.as_slice(),
@@ -357,11 +386,11 @@ where
parent_options.query_role.as_str(), parent_options.query_role.as_str(),
std::option::Option::Some(deposit_slot), std::option::Option::Some(deposit_slot),
); );
let transfer_out = match crate::execute_devnet_metaplex_token_metadata( let transfer_out = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&transfer_request, &transfer_request,
metaplex_decoders.as_slice(), metaplex_decoders.as_slice(),
metaplex_materializers.as_slice(), metaplex_materializers.as_slice(),
@@ -443,11 +472,11 @@ where
parent_options.query_role.as_str(), parent_options.query_role.as_str(),
std::option::Option::Some(transfer_slot), std::option::Option::Some(transfer_slot),
); );
let close_escrow = match crate::execute_devnet_metaplex_token_metadata( let close_escrow = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&close_request, &close_request,
metaplex_decoders.as_slice(), metaplex_decoders.as_slice(),
metaplex_materializers.as_slice(), metaplex_materializers.as_slice(),
@@ -992,7 +1021,8 @@ mod tests {
if let std::result::Result::Err(error) = store.initialize_store_schema().await { if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}"); panic!("PostgreSQL schema initialization failed: {error}");
} }
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/fixture.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/fixture.rs
// version: 22 // version: 23
//! Native Metaplex Token Metadata fixture preparation for Devnet demos. //! Native Metaplex Token Metadata fixture preparation for Devnet demos.
@@ -114,6 +114,21 @@ pub async fn prepare_metaplex_create_fixture(
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
) -> ks_core::Result<crate::MetaplexCreateFixturePreparationSummary> {
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return prepare_metaplex_create_fixture_with_wallet(http_pool, profile, &wallet, options).await;
}
/// Creates one family-consistent classic SPL mint using an explicitly resolved operator wallet.
pub async fn prepare_metaplex_create_fixture_with_wallet(
http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions,
) -> ks_core::Result<crate::MetaplexCreateFixturePreparationSummary> { ) -> ks_core::Result<crate::MetaplexCreateFixturePreparationSummary> {
if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() { if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
@@ -168,10 +183,6 @@ pub async fn prepare_metaplex_create_fixture(
return std::result::Result::Err(error); return std::result::Result::Err(error);
} }
} }
let operator = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let fixture_dir = options.wallet_dir.join("metaplex_token_metadata_validation"); let fixture_dir = options.wallet_dir.join("metaplex_token_metadata_validation");
let fixture_store = match ks_wallet::TemporaryWalletStore::new(fixture_dir.clone()) { let fixture_store = match ks_wallet::TemporaryWalletStore::new(fixture_dir.clone()) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -196,7 +207,7 @@ pub async fn prepare_metaplex_create_fixture(
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let mint_keypair_path = fixture_store.wallet_path(&fixture_alias); let mint_keypair_path = fixture_store.wallet_path(&fixture_alias);
let authority = operator.public_key(); let authority = wallet.public_key();
let mint = mint_wallet.public_key(); let mint = mint_wallet.public_key();
let mint_decimals = fixture_mint_decimals(options.asset_family); let mint_decimals = fixture_mint_decimals(options.asset_family);
let existing = let existing =
@@ -216,7 +227,7 @@ pub async fn prepare_metaplex_create_fixture(
http_pool, http_pool,
profile, profile,
options, options,
&operator, wallet,
&mint_wallet, &mint_wallet,
mint_decimals, mint_decimals,
) )
@@ -404,7 +415,7 @@ async fn create_native_classic_mint(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
operator: &ks_wallet::TemporaryWallet, operator: &crate::DevnetExecutionWallet,
mint_wallet: &ks_wallet::TemporaryWallet, mint_wallet: &ks_wallet::TemporaryWallet,
mint_decimals: u8, mint_decimals: u8,
) -> ks_core::Result<NativeFixturePreparationEvidence> { ) -> ks_core::Result<NativeFixturePreparationEvidence> {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/maintenance_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/maintenance_campaign.rs
// version: 8 // version: 9
//! Final bounded Devnet qualification campaign for current Metaplex maintenance operations. //! Final bounded Devnet qualification campaign for current Metaplex maintenance operations.
@@ -42,6 +42,34 @@ pub async fn execute_devnet_metaplex_maintenance_campaign<S, O>(
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexMaintenanceCampaignSummary> ) -> ks_core::Result<crate::DevnetMetaplexMaintenanceCampaignSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_metaplex_maintenance_campaign_with_wallet(
http_pool, store, profile, &wallet, options, observer,
)
.await;
}
/// Executes the bounded Metaplex maintenance campaign with an explicit operator wallet.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_metaplex_maintenance_campaign_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexMaintenanceCampaignSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -54,13 +82,8 @@ where
"Metaplex maintenance campaign requires the classic NFT fixture family", "Metaplex maintenance campaign requires the classic NFT fixture family",
)); ));
} }
let fixture = match crate::execute_devnet_metaplex_create_mint_campaign( let fixture = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
http_pool, http_pool, store, profile, wallet, options, observer,
store,
profile,
workspace_root,
options,
observer,
) )
.await .await
{ {
@@ -91,7 +114,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
options, options,
observer, observer,
) )
@@ -130,7 +153,7 @@ where
201, 201,
http_pool, http_pool,
profile, profile,
workspace_root, wallet,
options, options,
observer, observer,
) )
@@ -145,7 +168,7 @@ where
75, 75,
http_pool, http_pool,
profile, profile,
workspace_root, wallet,
options, options,
observer, observer,
) )
@@ -163,7 +186,7 @@ where
7, 7,
http_pool, http_pool,
profile, profile,
workspace_root, wallet,
options, options,
observer, observer,
) )
@@ -184,7 +207,7 @@ where
188, 188,
http_pool, http_pool,
profile, profile,
workspace_root, wallet,
options, options,
observer, observer,
) )
@@ -272,7 +295,7 @@ async fn execute_confirmed_operation<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S, store: &S,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary> ) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary>
@@ -304,11 +327,11 @@ where
request.operator_confirmed = true; request.operator_confirmed = true;
request.materialize_after_confirmation = true; request.materialize_after_confirmation = true;
request.post_validation_max_retries = 20; request.post_validation_max_retries = 20;
return crate::execute_devnet_metaplex_token_metadata( return crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&request, &request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -324,7 +347,7 @@ async fn simulate_expected_unavailable<O>(
expected_custom_error: u64, expected_custom_error: u64,
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary> ) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary>
@@ -337,12 +360,8 @@ where
); );
request.query_role = options.query_role.clone(); request.query_role = options.query_role.clone();
request.transaction_role = options.transaction_role.clone(); request.transaction_role = options.transaction_role.clone();
let summary = match crate::simulate_devnet_metaplex_token_metadata( let summary = match crate::simulate_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool, profile, wallet, &request, observer,
profile,
workspace_root,
&request,
observer,
) )
.await .await
{ {
@@ -596,7 +615,8 @@ mod tests {
if let std::result::Result::Err(error) = store.initialize_store_schema().await { if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}"); panic!("PostgreSQL schema initialization failed: {error}");
} }
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/pnft_lifecycle_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/pnft_lifecycle_campaign.rs
// version: 8 // version: 10
//! Confirmed Devnet pNFT delegate, lock, unlock, revoke and transfer campaign. //! Confirmed Devnet pNFT delegate, lock, unlock, revoke and transfer campaign.
@@ -76,6 +76,34 @@ pub async fn execute_devnet_metaplex_pnft_lifecycle_campaign<S, O>(
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexPnftLifecycleCampaignSummary> ) -> ks_core::Result<crate::DevnetMetaplexPnftLifecycleCampaignSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_metaplex_pnft_lifecycle_campaign_with_wallet(
http_pool, store, profile, &wallet, options, observer,
)
.await;
}
/// Executes one fresh pNFT lifecycle campaign with an explicit operator wallet.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_metaplex_pnft_lifecycle_campaign_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexPnftLifecycleCampaignSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -88,13 +116,8 @@ where
"Metaplex pNFT lifecycle campaign requires asset_family=programmable_nft", "Metaplex pNFT lifecycle campaign requires asset_family=programmable_nft",
)); ));
} }
let pnft = match crate::execute_devnet_metaplex_create_mint_campaign( let pnft = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
http_pool, http_pool, store, profile, wallet, options, observer,
store,
profile,
workspace_root,
options,
observer,
) )
.await .await
{ {
@@ -205,7 +228,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
options, options,
"delegate-staking", "delegate-staking",
delegate_staking_operation, delegate_staking_operation,
@@ -261,7 +284,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
options, options,
"lock", "lock",
lock_operation, lock_operation,
@@ -322,7 +345,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
options, options,
"unlock", "unlock",
unlock_operation, unlock_operation,
@@ -378,7 +401,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
options, options,
"revoke-staking", "revoke-staking",
revoke_operation, revoke_operation,
@@ -436,7 +459,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
options, options,
"delegate-transfer", "delegate-transfer",
delegate_transfer_operation, delegate_transfer_operation,
@@ -495,7 +518,7 @@ where
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
options, options,
transfer_operation, transfer_operation,
destination.1.as_str(), destination.1.as_str(),
@@ -631,7 +654,7 @@ async fn execute_profile_step<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S, store: &S,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
label: &str, label: &str,
operation: ks_lib::ExMetaplexTokenMetadataOperation, operation: ks_lib::ExMetaplexTokenMetadataOperation,
@@ -654,11 +677,11 @@ where
request.operator_confirmed = true; request.operator_confirmed = true;
request.materialize_after_confirmation = true; request.materialize_after_confirmation = true;
request.post_validation_max_retries = 20; request.post_validation_max_retries = 20;
return crate::execute_devnet_metaplex_token_metadata( return crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&request, &request,
decoders, decoders,
materializers, materializers,
@@ -672,7 +695,7 @@ async fn execute_delegate_step<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S, store: &S,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
label: &str, label: &str,
operation: ks_lib::ExMetaplexTokenMetadataOperation, operation: ks_lib::ExMetaplexTokenMetadataOperation,
@@ -698,11 +721,11 @@ where
request.post_validation_max_retries = 20; request.post_validation_max_retries = 20;
request.additional_authorized_signers = request.additional_authorized_signers =
std::vec![ks_lib::MdPubkey(delegate_wallet.public_key())]; std::vec![ks_lib::MdPubkey(delegate_wallet.public_key())];
return crate::execute_devnet_metaplex_token_metadata_with_signers( return crate::execute_devnet_metaplex_token_metadata_with_wallet_and_signers(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&request, &request,
decoders, decoders,
materializers, materializers,
@@ -717,7 +740,7 @@ async fn execute_delegate_transfer_step<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S, store: &S,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
operation: ks_lib::ExMetaplexTokenMetadataOperation, operation: ks_lib::ExMetaplexTokenMetadataOperation,
destination_token_record: &str, destination_token_record: &str,
@@ -749,11 +772,11 @@ where
request.post_validation_max_retries = 20; request.post_validation_max_retries = 20;
request.additional_authorized_signers = request.additional_authorized_signers =
std::vec![ks_lib::MdPubkey(delegate_wallet.public_key())]; std::vec![ks_lib::MdPubkey(delegate_wallet.public_key())];
return crate::execute_devnet_metaplex_token_metadata_with_signers( return crate::execute_devnet_metaplex_token_metadata_with_wallet_and_signers(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&request, &request,
decoders, decoders,
materializers, materializers,
@@ -1090,7 +1113,8 @@ mod tests {
if let std::result::Result::Err(error) = store.initialize_store_schema().await { if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}"); panic!("PostgreSQL schema initialization failed: {error}");
} }
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/print_burn_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/print_burn_campaign.rs
// version: 9 // version: 10
//! Devnet printable master NFT `Print -> Burn` campaign. //! Devnet printable master NFT `Print -> Burn` campaign.
@@ -84,6 +84,34 @@ pub async fn execute_devnet_metaplex_print_burn_campaign<S, O>(
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexPrintBurnCampaignSummary> ) -> ks_core::Result<crate::DevnetMetaplexPrintBurnCampaignSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_metaplex_print_burn_campaign_with_wallet(
http_pool, store, profile, &wallet, options, observer,
)
.await;
}
/// Executes one printable master NFT `Print -> Burn` campaign with an explicit operator wallet.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_metaplex_print_burn_campaign_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexPrintBurnCampaignSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -100,11 +128,11 @@ where
master_options.asset_family = crate::MetaplexTokenMetadataAssetFamily::Nft; master_options.asset_family = crate::MetaplexTokenMetadataAssetFamily::Nft;
master_options.collection_mint = std::option::Option::None; master_options.collection_mint = std::option::Option::None;
master_options.print_supply_limit = std::option::Option::Some(1); master_options.print_supply_limit = std::option::Option::Some(1);
let master = match crate::execute_devnet_metaplex_create_mint_campaign( let master = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&master_options, &master_options,
observer, observer,
) )
@@ -329,11 +357,11 @@ where
options.query_role.as_str(), options.query_role.as_str(),
std::option::Option::Some(master_mint_slot), std::option::Option::Some(master_mint_slot),
); );
let print = match crate::execute_devnet_metaplex_token_metadata_with_signers( let print = match crate::execute_devnet_metaplex_token_metadata_with_wallet_and_signers(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&print_request, &print_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -498,11 +526,11 @@ where
options.query_role.as_str(), options.query_role.as_str(),
std::option::Option::Some(print_slot), std::option::Option::Some(print_slot),
); );
let burn = match crate::execute_devnet_metaplex_token_metadata( let burn = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&burn_request, &burn_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -1199,7 +1227,8 @@ mod tests {
if let std::result::Result::Err(error) = store.initialize_store_schema().await { if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}"); panic!("PostgreSQL schema initialization failed: {error}");
} }
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs
// version: 6 // version: 7
//! Bounded Devnet availability probe for the current Metaplex `Use` surface. //! Bounded Devnet availability probe for the current Metaplex `Use` surface.
@@ -46,6 +46,34 @@ pub async fn execute_devnet_metaplex_use_probe<S, O>(
options: &crate::MetaplexCreateFixturePreparationOptions, options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexUseProbeSummary> ) -> ks_core::Result<crate::DevnetMetaplexUseProbeSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_metaplex_use_probe_with_wallet(
http_pool, store, profile, &wallet, options, observer,
)
.await;
}
/// Executes one fresh classic NFT `Use` probe with an explicit operator wallet.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_metaplex_use_probe_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::MetaplexCreateFixturePreparationOptions,
observer: &O,
) -> ks_core::Result<crate::DevnetMetaplexUseProbeSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -59,11 +87,11 @@ where
)); ));
} }
let fixture_options = options.clone().with_multiple_uses(2); let fixture_options = options.clone().with_multiple_uses(2);
let fixture = match crate::execute_devnet_metaplex_create_mint_campaign( let fixture = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&fixture_options, &fixture_options,
observer, observer,
) )
@@ -93,10 +121,10 @@ where
); );
simulation_request.query_role = fixture_options.query_role.clone(); simulation_request.query_role = fixture_options.query_role.clone();
simulation_request.transaction_role = fixture_options.transaction_role.clone(); simulation_request.transaction_role = fixture_options.transaction_role.clone();
let simulation = match crate::simulate_devnet_metaplex_token_metadata( let simulation = match crate::simulate_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
profile, profile,
workspace_root, wallet,
&simulation_request, &simulation_request,
observer, observer,
) )
@@ -139,11 +167,11 @@ where
execution_request.post_validation_max_retries = 20; execution_request.post_validation_max_retries = 20;
execution_request.postcondition_reads = execution_request.postcondition_reads =
metadata_read(fixture.fixture.metadata.as_str(), fixture_options.query_role.as_str()); metadata_read(fixture.fixture.metadata.as_str(), fixture_options.query_role.as_str());
let execution = match crate::execute_devnet_metaplex_token_metadata( let execution = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&execution_request, &execution_request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -444,7 +472,8 @@ mod tests {
if let std::result::Result::Err(error) = store.initialize_store_schema().await { if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}"); panic!("PostgreSQL schema initialization failed: {error}");
} }
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/solana_program/campaign.rs // file: ks-pipeline-demo-scenarios/src/metadata/solana_program/campaign.rs
// version: 4 // version: 5
//! Ordered confirmed Devnet campaign for all Solana Program Metadata operations. //! Ordered confirmed Devnet campaign for all Solana Program Metadata operations.
@@ -36,6 +36,28 @@ pub async fn execute_devnet_solana_program_metadata_campaign<O>(
options: &crate::SolanaProgramMetadataFixturePreparationOptions, options: &crate::SolanaProgramMetadataFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetSolanaProgramMetadataCampaignSummary> ) -> ks_core::Result<crate::DevnetSolanaProgramMetadataCampaignSummary>
where
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_solana_program_metadata_campaign_with_wallet(
http_pool, profile, &wallet, options, observer,
)
.await;
}
/// Executes the complete Solana Program Metadata campaign with an explicitly resolved wallet.
pub async fn execute_devnet_solana_program_metadata_campaign_with_wallet<O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::SolanaProgramMetadataFixturePreparationOptions,
observer: &O,
) -> ks_core::Result<crate::DevnetSolanaProgramMetadataCampaignSummary>
where where
O: crate::SolanaExecutionObserver, O: crate::SolanaExecutionObserver,
{ {
@@ -44,11 +66,8 @@ where
"Solana Program Metadata Devnet campaign requires explicit operator confirmation", "Solana Program Metadata Devnet campaign requires explicit operator confirmation",
)); ));
} }
let fixture = match crate::prepare_solana_program_metadata_fixture( let fixture = match crate::prepare_solana_program_metadata_fixture_with_wallet(
http_pool, http_pool, profile, wallet, options,
profile,
workspace_root,
options,
) )
.await .await
{ {
@@ -63,12 +82,8 @@ where
request.transaction_role = options.transaction_role.clone(); request.transaction_role = options.transaction_role.clone();
request.submit = true; request.submit = true;
request.operator_confirmed = true; request.operator_confirmed = true;
let execution = match crate::execute_devnet_solana_program_metadata( let execution = match crate::execute_devnet_solana_program_metadata_with_wallet(
http_pool, http_pool, profile, wallet, &request, observer,
profile,
workspace_root,
&request,
observer,
) )
.await .await
{ {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/solana_program/devnet_execution.rs // file: ks-pipeline-demo-scenarios/src/metadata/solana_program/devnet_execution.rs
// version: 5 // version: 6
//! Real Devnet simulation and controlled submission for Solana Program Metadata. //! Real Devnet simulation and controlled submission for Solana Program Metadata.
@@ -155,7 +155,6 @@ pub struct DevnetSolanaProgramMetadataExecutionSummary {
} }
struct PreparedSolanaProgramMetadataExecution { struct PreparedSolanaProgramMetadataExecution {
wallet: ks_wallet::TemporaryWallet,
unsigned: ks_lib::ExSolanaUnsignedTransaction, unsigned: ks_lib::ExSolanaUnsignedTransaction,
evidence: ks_lib::ExSolanaSimulationEvidence, evidence: ks_lib::ExSolanaSimulationEvidence,
summary: crate::DevnetSolanaProgramMetadataExecutionSummary, summary: crate::DevnetSolanaProgramMetadataExecutionSummary,
@@ -169,6 +168,28 @@ pub async fn simulate_devnet_solana_program_metadata<O>(
request: &crate::DevnetSolanaProgramMetadataExecutionRequest, request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetSolanaProgramMetadataExecutionSummary> ) -> ks_core::Result<crate::DevnetSolanaProgramMetadataExecutionSummary>
where
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return simulate_devnet_solana_program_metadata_with_wallet(
http_pool, profile, &wallet, request, observer,
)
.await;
}
/// Simulates one stable Solana Program Metadata operation with an explicitly resolved wallet.
pub async fn simulate_devnet_solana_program_metadata_with_wallet<O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
observer: &O,
) -> ks_core::Result<crate::DevnetSolanaProgramMetadataExecutionSummary>
where where
O: crate::SolanaExecutionObserver, O: crate::SolanaExecutionObserver,
{ {
@@ -177,8 +198,7 @@ where
"simulate_devnet_solana_program_metadata requires submit=false", "simulate_devnet_solana_program_metadata requires submit=false",
)); ));
} }
let prepared = let prepared = match prepare_execution(http_pool, profile, wallet, request, observer).await {
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -196,8 +216,29 @@ pub async fn execute_devnet_solana_program_metadata<O>(
where where
O: crate::SolanaExecutionObserver, O: crate::SolanaExecutionObserver,
{ {
let prepared = let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
match prepare_execution(http_pool, profile, workspace_root, request, observer).await { std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_solana_program_metadata_with_wallet(
http_pool, profile, &wallet, request, observer,
)
.await;
}
/// Executes one Solana Program Metadata operation with an explicitly resolved wallet.
pub async fn execute_devnet_solana_program_metadata_with_wallet<O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
observer: &O,
) -> ks_core::Result<crate::DevnetSolanaProgramMetadataExecutionSummary>
where
O: crate::SolanaExecutionObserver,
{
let prepared = match prepare_execution(http_pool, profile, wallet, request, observer).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -230,7 +271,7 @@ where
} }
let signed = match prepared let signed = match prepared
.unsigned .unsigned
.sign_after_simulation(&prepared.evidence, &[prepared.wallet.as_signer()]) .sign_after_simulation(&prepared.evidence, &[wallet.as_signer()])
{ {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -322,7 +363,7 @@ where
async fn prepare_execution<O>( async fn prepare_execution<O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetSolanaProgramMetadataExecutionRequest, request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
observer: &O, observer: &O,
) -> ks_core::Result<PreparedSolanaProgramMetadataExecution> ) -> ks_core::Result<PreparedSolanaProgramMetadataExecution>
@@ -355,11 +396,7 @@ where
), ),
)); ));
} }
let wallet = match crate::load_profile_temporary_wallet(profile, workspace_root).await { let wallet_summary = wallet.identity();
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_summary = wallet.summary();
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone()); let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
let balance = match http_pool let balance = match http_pool
.get_balance_for_role( .get_balance_for_role(
@@ -518,7 +555,6 @@ where
}; };
let evidence = unsigned.bind_simulation(simulation.clone()); let evidence = unsigned.bind_simulation(simulation.clone());
return std::result::Result::Ok(PreparedSolanaProgramMetadataExecution { return std::result::Result::Ok(PreparedSolanaProgramMetadataExecution {
wallet,
unsigned, unsigned,
evidence, evidence,
summary: crate::DevnetSolanaProgramMetadataExecutionSummary { summary: crate::DevnetSolanaProgramMetadataExecutionSummary {
@@ -625,9 +661,11 @@ fn validate_profile(
"Solana Program Metadata orchestration requires a Devnet wallet profile", "Solana Program Metadata orchestration requires a Devnet wallet profile",
)); ));
} }
if !profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist { if profile.wallet.wallet_alias.is_none()
&& (!profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist)
{
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
"Solana Program Metadata orchestration requires an enabled persistent temporary wallet", "Solana Program Metadata orchestration requires an enabled persistent temporary wallet when no native wallet alias is selected",
)); ));
} }
if !profile.execution.require_simulation { if !profile.execution.require_simulation {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/metadata/solana_program/fixture.rs // file: ks-pipeline-demo-scenarios/src/metadata/solana_program/fixture.rs
// version: 9 // version: 10
//! Native Solana Program Metadata fixture preparation for Devnet journeys. //! Native Solana Program Metadata fixture preparation for Devnet journeys.
@@ -111,6 +111,24 @@ pub async fn prepare_solana_program_metadata_fixture(
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
options: &crate::SolanaProgramMetadataFixturePreparationOptions, options: &crate::SolanaProgramMetadataFixturePreparationOptions,
) -> ks_core::Result<crate::SolanaProgramMetadataFixturePreparationSummary> {
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return prepare_solana_program_metadata_fixture_with_wallet(
http_pool, profile, &wallet, options,
)
.await;
}
/// Creates two unique pre-funded PDAs with an explicitly resolved execution wallet.
pub async fn prepare_solana_program_metadata_fixture_with_wallet(
http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig,
operator: &crate::DevnetExecutionWallet,
options: &crate::SolanaProgramMetadataFixturePreparationOptions,
) -> ks_core::Result<crate::SolanaProgramMetadataFixturePreparationSummary> { ) -> ks_core::Result<crate::SolanaProgramMetadataFixturePreparationSummary> {
if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() { if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
@@ -119,8 +137,9 @@ pub async fn prepare_solana_program_metadata_fixture(
} }
if profile.wallet.cluster != "devnet" if profile.wallet.cluster != "devnet"
|| !profile.execution.devnet_send_enabled || !profile.execution.devnet_send_enabled
|| !profile.wallet.temporary_wallet_enabled || (profile.wallet.wallet_alias.is_none()
|| !profile.wallet.temporary_wallet_persist && (!profile.wallet.temporary_wallet_enabled
|| !profile.wallet.temporary_wallet_persist))
{ {
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
"Solana Program Metadata fixture requires one persistent send-enabled Devnet profile", "Solana Program Metadata fixture requires one persistent send-enabled Devnet profile",
@@ -143,10 +162,6 @@ pub async fn prepare_solana_program_metadata_fixture(
"Solana Program Metadata fixture preparation requires a Devnet endpoint", "Solana Program Metadata fixture preparation requires a Devnet endpoint",
)); ));
} }
let operator = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let authority = ks_lib::MdPubkey(operator.public_key()); let authority = ks_lib::MdPubkey(operator.public_key());
let described_program = ks_lib::MdPubkey(ks_program_ids::SYSTEM_PROGRAM_ID.to_string()); let described_program = ks_lib::MdPubkey(ks_program_ids::SYSTEM_PROGRAM_ID.to_string());
let unique = uuid::Uuid::new_v4().simple().to_string(); let unique = uuid::Uuid::new_v4().simple().to_string();
@@ -282,7 +297,7 @@ pub async fn prepare_solana_program_metadata_fixture(
http_pool, http_pool,
profile, profile,
options, options,
&operator, operator,
buffer.clone(), buffer.clone(),
buffer_prefund_lamports, buffer_prefund_lamports,
"buffer", "buffer",
@@ -296,7 +311,7 @@ pub async fn prepare_solana_program_metadata_fixture(
http_pool, http_pool,
profile, profile,
options, options,
&operator, operator,
metadata.clone(), metadata.clone(),
metadata_prefund_lamports, metadata_prefund_lamports,
"metadata", "metadata",
@@ -680,7 +695,7 @@ async fn submit_prefund_transfer(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
options: &crate::SolanaProgramMetadataFixturePreparationOptions, options: &crate::SolanaProgramMetadataFixturePreparationOptions,
operator: &ks_wallet::TemporaryWallet, operator: &crate::DevnetExecutionWallet,
recipient: ks_lib::MdPubkey, recipient: ks_lib::MdPubkey,
lamports: u64, lamports: u64,
label: &str, label: &str,

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/solana.rs // file: ks-pipeline-demo-scenarios/src/solana.rs
// version: 17 // version: 18
//! Devnet Solana execution orchestration with canonical post-validation. //! Devnet Solana execution orchestration with canonical post-validation.
@@ -260,6 +260,43 @@ pub async fn execute_devnet_system_transfer<S, O>(
materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>], materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>],
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetSystemTransferSummary> ) -> ks_core::Result<crate::DevnetSystemTransferSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_system_transfer_with_wallet(
http_pool,
store,
profile,
&wallet,
request,
decoders,
materializers,
observer,
)
.await;
}
/// Executes a bounded Devnet System transfer with an explicitly resolved wallet capability.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_system_transfer_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetSystemTransferRequest,
decoders: &[std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>],
materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>],
observer: &O,
) -> ks_core::Result<crate::DevnetSystemTransferSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -305,11 +342,7 @@ where
if let std::result::Result::Err(error) = cancellation_result { if let std::result::Result::Err(error) = cancellation_result {
return std::result::Result::Err(error); return std::result::Result::Err(error);
} }
let wallet = match load_profile_temporary_wallet(profile, workspace_root).await { let wallet_summary = wallet.identity();
std::result::Result::Ok(wallet) => wallet,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_summary = wallet.summary();
let source_pubkey = ks_lib::MdPubkey(wallet_summary.public_key.clone()); let source_pubkey = ks_lib::MdPubkey(wallet_summary.public_key.clone());
if source_pubkey == request.recipient { if source_pubkey == request.recipient {
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
@@ -945,9 +978,11 @@ fn validate_devnet_profile(
profile.name profile.name
))); )));
} }
if !profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist { if profile.wallet.wallet_alias.is_none()
&& (!profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist)
{
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
"Devnet execution requires an enabled persistent temporary wallet", "Devnet execution requires an enabled persistent temporary wallet when no native wallet alias is selected",
)); ));
} }
if !profile.execution.require_simulation { if !profile.execution.require_simulation {
@@ -983,7 +1018,7 @@ fn validate_devnet_profile(
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
/// Resolves the wallet directory selected by one profile relative to the workspace root. /// Resolves the persistent native wallet directory selected by one profile.
fn profile_wallet_directory( fn profile_wallet_directory(
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
@@ -995,6 +1030,18 @@ fn profile_wallet_directory(
return workspace_root.join(configured); return workspace_root.join(configured);
} }
/// Resolves the profile-specific temporary wallet and fixture directory.
fn profile_temporary_wallet_directory(
profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path,
) -> std::path::PathBuf {
let configured = std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
if configured.is_absolute() {
return configured;
}
return workspace_root.join(configured);
}
/// Inspects the persistent native wallet alias selected by one profile, when configured. /// Inspects the persistent native wallet alias selected by one profile, when configured.
pub async fn inspect_selected_profile_wallet( pub async fn inspect_selected_profile_wallet(
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
@@ -1065,7 +1112,7 @@ pub(crate) async fn load_profile_temporary_wallet(
"profile selects a persistent native wallet; temporary-wallet fallback is forbidden", "profile selects a persistent native wallet; temporary-wallet fallback is forbidden",
)); ));
} }
let store = match ks_wallet::TemporaryWalletStore::new(profile_wallet_directory( let store = match ks_wallet::TemporaryWalletStore::new(profile_temporary_wallet_directory(
profile, profile,
workspace_root, workspace_root,
)) { )) {
@@ -1500,7 +1547,7 @@ mod tests {
profile.database.backend = "postgres".to_string(); profile.database.backend = "postgres".to_string();
profile.database.postgres.url = database_url; profile.database.postgres.url = database_url;
if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") { if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") {
profile.wallet.wallet_dir = directory; profile.wallet.temporary_wallet_dir = directory;
} }
let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) { let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs // file: ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs
// version: 16 // version: 17
//! Devnet ATA execution with stateful and canonical post-validation. //! Devnet ATA execution with stateful and canonical post-validation.
@@ -108,7 +108,6 @@ pub struct DevnetSplAssociatedTokenAccountExecutionSummary {
} }
struct PreparedAtaExecution { struct PreparedAtaExecution {
wallet: ks_wallet::TemporaryWallet,
unsigned: ks_lib::ExSolanaUnsignedTransaction, unsigned: ks_lib::ExSolanaUnsignedTransaction,
evidence: ks_lib::ExSolanaSimulationEvidence, evidence: ks_lib::ExSolanaSimulationEvidence,
summary: DevnetSplAssociatedTokenAccountExecutionSummary, summary: DevnetSplAssociatedTokenAccountExecutionSummary,
@@ -130,8 +129,12 @@ where
"simulate_devnet_spl_associated_token_account requires submit=false", "simulate_devnet_spl_associated_token_account requires submit=false",
)); ));
} }
let prepared = let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
match prepare_execution(http_pool, profile, workspace_root, request, observer).await { std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
let prepared = match prepare_execution(http_pool, profile, &wallet, request, observer).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -157,8 +160,44 @@ where
+ Sync, + Sync,
O: crate::SolanaExecutionObserver, O: crate::SolanaExecutionObserver,
{ {
let prepared = let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
match prepare_execution(http_pool, profile, workspace_root, request, observer).await { std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_spl_associated_token_account_with_wallet(
http_pool,
store,
profile,
&wallet,
request,
decoders,
materializers,
observer,
)
.await;
}
/// Executes this Devnet operation with an explicitly resolved wallet capability.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_spl_associated_token_account_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &DevnetSplAssociatedTokenAccountExecutionRequest,
decoders: &[std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>],
materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>],
observer: &O,
) -> ks_core::Result<DevnetSplAssociatedTokenAccountExecutionSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let prepared = match prepare_execution(http_pool, profile, wallet, request, observer).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -191,7 +230,7 @@ where
} }
let signed = match prepared let signed = match prepared
.unsigned .unsigned
.sign_after_simulation(&prepared.evidence, &[prepared.wallet.as_signer()]) .sign_after_simulation(&prepared.evidence, &[wallet.as_signer()])
{ {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -460,7 +499,7 @@ where
async fn prepare_execution<O>( async fn prepare_execution<O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
request: &DevnetSplAssociatedTokenAccountExecutionRequest, request: &DevnetSplAssociatedTokenAccountExecutionRequest,
observer: &O, observer: &O,
) -> ks_core::Result<PreparedAtaExecution> ) -> ks_core::Result<PreparedAtaExecution>
@@ -491,11 +530,7 @@ where
), ),
)); ));
} }
let wallet = match crate::load_profile_temporary_wallet(profile, workspace_root).await { let wallet_summary = wallet.identity();
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_summary = wallet.summary();
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone()); let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
let balance = match http_pool let balance = match http_pool
.get_balance_for_role( .get_balance_for_role(
@@ -664,7 +699,7 @@ where
materializations: std::vec::Vec::new(), materializations: std::vec::Vec::new(),
post_execution: std::option::Option::None, post_execution: std::option::Option::None,
}; };
return std::result::Result::Ok(PreparedAtaExecution { wallet, unsigned, evidence, summary }); return std::result::Result::Ok(PreparedAtaExecution { unsigned, evidence, summary });
} }
fn validate_profile( fn validate_profile(
@@ -676,9 +711,11 @@ fn validate_profile(
"SPL Associated Token Account Devnet orchestration requires a Devnet wallet profile", "SPL Associated Token Account Devnet orchestration requires a Devnet wallet profile",
)); ));
} }
if !profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist { if profile.wallet.wallet_alias.is_none()
&& (!profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist)
{
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
"SPL Associated Token Account Devnet orchestration requires an enabled persistent temporary wallet", "SPL Associated Token Account Devnet orchestration requires an enabled persistent temporary wallet when no native wallet alias is selected",
)); ));
} }
if !profile.execution.require_simulation { if !profile.execution.require_simulation {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/memo.rs // file: ks-pipeline-demo-scenarios/src/spl/memo.rs
// version: 12 // version: 13
//! Devnet SPL Memo v4 execution with canonical post-validation. //! Devnet SPL Memo v4 execution with canonical post-validation.
@@ -124,6 +124,43 @@ pub async fn execute_devnet_memo<S, O>(
materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>], materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>],
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetMemoExecutionSummary> ) -> ks_core::Result<crate::DevnetMemoExecutionSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_memo_with_wallet(
http_pool,
store,
profile,
&wallet,
request,
decoders,
materializers,
observer,
)
.await;
}
/// Executes one Devnet Memo operation with an explicitly resolved wallet capability.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_memo_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetMemoExecutionRequest,
decoders: &[std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>],
materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>],
observer: &O,
) -> ks_core::Result<crate::DevnetMemoExecutionSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -155,11 +192,7 @@ where
), ),
)); ));
} }
let wallet = match crate::load_profile_temporary_wallet(profile, workspace_root).await { let wallet_summary = wallet.identity();
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_summary = wallet.summary();
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone()); let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
let balance = match http_pool let balance = match http_pool
.get_balance_for_role( .get_balance_for_role(
@@ -534,9 +567,11 @@ fn validate_profile(
"Memo Devnet orchestration requires a Devnet wallet profile", "Memo Devnet orchestration requires a Devnet wallet profile",
)); ));
} }
if !profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist { if profile.wallet.wallet_alias.is_none()
&& (!profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist)
{
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
"Memo Devnet orchestration requires an enabled persistent temporary wallet", "Memo Devnet orchestration requires an enabled persistent temporary wallet when no native wallet alias is selected",
)); ));
} }
if !profile.execution.require_simulation { if !profile.execution.require_simulation {
@@ -825,7 +860,7 @@ mod tests {
profile.database.backend = "postgres".to_string(); profile.database.backend = "postgres".to_string();
profile.database.postgres.url = database_url; profile.database.postgres.url = database_url;
if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") { if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") {
profile.wallet.wallet_dir = directory; profile.wallet.temporary_wallet_dir = directory;
} }
let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) { let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/token/execution.rs // file: ks-pipeline-demo-scenarios/src/spl/token/execution.rs
// version: 16 // version: 17
//! Devnet classic SPL Token execution with stateful and canonical post-validation. //! Devnet classic SPL Token execution with stateful and canonical post-validation.
@@ -105,7 +105,6 @@ pub struct DevnetSplTokenExecutionSummary {
} }
struct PreparedTokenExecution { struct PreparedTokenExecution {
wallet: ks_wallet::TemporaryWallet,
unsigned: ks_lib::ExSolanaUnsignedTransaction, unsigned: ks_lib::ExSolanaUnsignedTransaction,
evidence: ks_lib::ExSolanaSimulationEvidence, evidence: ks_lib::ExSolanaSimulationEvidence,
summary: crate::DevnetSplTokenExecutionSummary, summary: crate::DevnetSplTokenExecutionSummary,
@@ -127,8 +126,12 @@ where
"simulate_devnet_spl_token requires submit=false", "simulate_devnet_spl_token requires submit=false",
)); ));
} }
let prepared = let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
match prepare_execution(http_pool, profile, workspace_root, request, observer).await { std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
let prepared = match prepare_execution(http_pool, profile, &wallet, request, observer).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -154,8 +157,44 @@ where
+ Sync, + Sync,
O: crate::SolanaExecutionObserver, O: crate::SolanaExecutionObserver,
{ {
let prepared = let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
match prepare_execution(http_pool, profile, workspace_root, request, observer).await { std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_spl_token_with_wallet(
http_pool,
store,
profile,
&wallet,
request,
decoders,
materializers,
observer,
)
.await;
}
/// Executes this Devnet operation with an explicitly resolved wallet capability.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_spl_token_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetSplTokenExecutionRequest,
decoders: &[std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>],
materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>],
observer: &O,
) -> ks_core::Result<crate::DevnetSplTokenExecutionSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let prepared = match prepare_execution(http_pool, profile, wallet, request, observer).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -188,7 +227,7 @@ where
} }
let signed = match prepared let signed = match prepared
.unsigned .unsigned
.sign_after_simulation(&prepared.evidence, &[prepared.wallet.as_signer()]) .sign_after_simulation(&prepared.evidence, &[wallet.as_signer()])
{ {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -428,7 +467,7 @@ where
async fn prepare_execution<O>( async fn prepare_execution<O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetSplTokenExecutionRequest, request: &crate::DevnetSplTokenExecutionRequest,
observer: &O, observer: &O,
) -> ks_core::Result<PreparedTokenExecution> ) -> ks_core::Result<PreparedTokenExecution>
@@ -479,11 +518,7 @@ where
failed_readiness_message(&readiness), failed_readiness_message(&readiness),
)); ));
} }
let wallet = match crate::load_profile_temporary_wallet(profile, workspace_root).await { let wallet_summary = wallet.identity();
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_summary = wallet.summary();
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone()); let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
let balance = match http_pool let balance = match http_pool
.get_balance_for_role( .get_balance_for_role(
@@ -620,7 +655,7 @@ where
materializations: std::vec::Vec::new(), materializations: std::vec::Vec::new(),
post_execution: std::option::Option::None, post_execution: std::option::Option::None,
}; };
return std::result::Result::Ok(PreparedTokenExecution { wallet, unsigned, evidence, summary }); return std::result::Result::Ok(PreparedTokenExecution { unsigned, evidence, summary });
} }
fn validate_profile( fn validate_profile(
@@ -632,9 +667,11 @@ fn validate_profile(
"SPL Token Devnet orchestration requires a Devnet wallet profile", "SPL Token Devnet orchestration requires a Devnet wallet profile",
)); ));
} }
if !profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist { if profile.wallet.wallet_alias.is_none()
&& (!profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist)
{
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
"SPL Token Devnet orchestration requires an enabled persistent temporary wallet", "SPL Token Devnet orchestration requires an enabled persistent temporary wallet when no native wallet alias is selected",
)); ));
} }
if !profile.execution.require_simulation { if !profile.execution.require_simulation {
@@ -1093,7 +1130,7 @@ mod tests {
}; };
let mut profile = example_devnet_profile(); let mut profile = example_devnet_profile();
if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") { if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") {
profile.wallet.wallet_dir = directory; profile.wallet.temporary_wallet_dir = directory;
} }
let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) { let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -1263,7 +1300,7 @@ mod tests {
}; };
let mut profile = example_devnet_profile(); let mut profile = example_devnet_profile();
if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") { if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") {
profile.wallet.wallet_dir = directory; profile.wallet.temporary_wallet_dir = directory;
} }
let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) { let pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs // file: ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs
// version: 15 // version: 16
//! Controlled Devnet lifecycle for freshly prepared classic SPL Token accounts. //! Controlled Devnet lifecycle for freshly prepared classic SPL Token accounts.
@@ -1512,7 +1512,7 @@ mod tests {
} }
let mut profile = example_devnet_profile(); let mut profile = example_devnet_profile();
if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") { if let std::result::Result::Ok(directory) = std::env::var("KS_DEVNET_WALLET_DIR") {
profile.wallet.wallet_dir = directory; profile.wallet.temporary_wallet_dir = directory;
} }
let database_url = match std::env::var("KS_SECRET_POSTGRES_TEST_URL") { let database_url = match std::env::var("KS_SECRET_POSTGRES_TEST_URL") {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/token_2022/devnet_execution.rs // file: ks-pipeline-demo-scenarios/src/spl/token_2022/devnet_execution.rs
// version: 10 // version: 12
//! Devnet Token-2022 execution with stateful and canonical post-validation. //! Devnet Token-2022 execution with stateful and canonical post-validation.
@@ -105,7 +105,6 @@ pub struct DevnetSplToken2022ExecutionSummary {
} }
struct PreparedToken2022Execution { struct PreparedToken2022Execution {
wallet: ks_wallet::TemporaryWallet,
unsigned: ks_lib::ExSolanaUnsignedTransaction, unsigned: ks_lib::ExSolanaUnsignedTransaction,
evidence: ks_lib::ExSolanaSimulationEvidence, evidence: ks_lib::ExSolanaSimulationEvidence,
summary: crate::DevnetSplToken2022ExecutionSummary, summary: crate::DevnetSplToken2022ExecutionSummary,
@@ -127,8 +126,12 @@ where
"simulate_devnet_spl_token_2022 requires submit=false", "simulate_devnet_spl_token_2022 requires submit=false",
)); ));
} }
let prepared = let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
match prepare_execution(http_pool, profile, workspace_root, request, observer).await { std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
let prepared = match prepare_execution(http_pool, profile, &wallet, request, observer).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -154,8 +157,44 @@ where
+ Sync, + Sync,
O: crate::SolanaExecutionObserver, O: crate::SolanaExecutionObserver,
{ {
let prepared = let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
match prepare_execution(http_pool, profile, workspace_root, request, observer).await { std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_spl_token_2022_with_wallet(
http_pool,
store,
profile,
&wallet,
request,
decoders,
materializers,
observer,
)
.await;
}
/// Executes this Devnet operation with an explicitly resolved wallet capability.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_spl_token_2022_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetSplToken2022ExecutionRequest,
decoders: &[std::sync::Arc<dyn ks_lib::DcApiInstructionDecoder>],
materializers: &[std::sync::Arc<dyn ks_lib::MtApiEventMaterializer>],
observer: &O,
) -> ks_core::Result<crate::DevnetSplToken2022ExecutionSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let prepared = match prepare_execution(http_pool, profile, wallet, request, observer).await {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -188,7 +227,7 @@ where
} }
let signed = match prepared let signed = match prepared
.unsigned .unsigned
.sign_after_simulation(&prepared.evidence, &[prepared.wallet.as_signer()]) .sign_after_simulation(&prepared.evidence, &[wallet.as_signer()])
{ {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -428,7 +467,7 @@ where
async fn prepare_execution<O>( async fn prepare_execution<O>(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, wallet: &crate::DevnetExecutionWallet,
request: &crate::DevnetSplToken2022ExecutionRequest, request: &crate::DevnetSplToken2022ExecutionRequest,
observer: &O, observer: &O,
) -> ks_core::Result<PreparedToken2022Execution> ) -> ks_core::Result<PreparedToken2022Execution>
@@ -469,11 +508,7 @@ where
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let wallet = match crate::load_profile_temporary_wallet(profile, workspace_root).await { let wallet_summary = wallet.identity();
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_summary = wallet.summary();
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone()); let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
let balance = match http_pool let balance = match http_pool
.get_balance_for_role( .get_balance_for_role(
@@ -610,12 +645,7 @@ where
materializations: std::vec::Vec::new(), materializations: std::vec::Vec::new(),
post_execution: std::option::Option::None, post_execution: std::option::Option::None,
}; };
return std::result::Result::Ok(PreparedToken2022Execution { return std::result::Result::Ok(PreparedToken2022Execution { unsigned, evidence, summary });
wallet,
unsigned,
evidence,
summary,
});
} }
fn preflight_request( fn preflight_request(
@@ -872,9 +902,11 @@ fn validate_profile(
"Token-2022 Devnet orchestration requires a Devnet wallet profile", "Token-2022 Devnet orchestration requires a Devnet wallet profile",
)); ));
} }
if !profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist { if profile.wallet.wallet_alias.is_none()
&& (!profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist)
{
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
"Token-2022 Devnet orchestration requires an enabled persistent temporary wallet", "Token-2022 Devnet orchestration requires an enabled persistent temporary wallet when no native wallet alias is selected",
)); ));
} }
if !profile.execution.require_simulation { if !profile.execution.require_simulation {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/campaign.rs // file: ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/campaign.rs
// version: 9 // version: 10
//! Ordered confirmed Devnet campaign for the five Token-2022 Token Metadata instructions. //! Ordered confirmed Devnet campaign for the five Token-2022 Token Metadata instructions.
@@ -50,6 +50,34 @@ pub async fn execute_devnet_token_2022_metadata_campaign<S, O>(
options: &crate::Token2022MetadataFixturePreparationOptions, options: &crate::Token2022MetadataFixturePreparationOptions,
observer: &O, observer: &O,
) -> ks_core::Result<crate::DevnetToken2022MetadataCampaignSummary> ) -> ks_core::Result<crate::DevnetToken2022MetadataCampaignSummary>
where
S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore
+ ks_store::DecodePipelineStore
+ Sync,
O: crate::SolanaExecutionObserver,
{
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return execute_devnet_token_2022_metadata_campaign_with_wallet(
http_pool, store, profile, &wallet, options, observer,
)
.await;
}
/// Executes the Token-2022 metadata campaign with an explicitly resolved wallet.
#[allow(clippy::too_many_arguments)]
pub async fn execute_devnet_token_2022_metadata_campaign_with_wallet<S, O>(
http_pool: &ks_onchain_transport::HttpEndpointPool,
store: &S,
profile: &ks_config::ProfileConfig,
wallet: &crate::DevnetExecutionWallet,
options: &crate::Token2022MetadataFixturePreparationOptions,
observer: &O,
) -> ks_core::Result<crate::DevnetToken2022MetadataCampaignSummary>
where where
S: ks_store::RawTransactionStore S: ks_store::RawTransactionStore
+ ks_store::CoreExtractionStore + ks_store::CoreExtractionStore
@@ -62,11 +90,8 @@ where
"Token-2022 Token Metadata Devnet campaign requires explicit operator confirmation", "Token-2022 Token Metadata Devnet campaign requires explicit operator confirmation",
)); ));
} }
let fixture = match crate::prepare_token_2022_metadata_fixture( let fixture = match crate::prepare_token_2022_metadata_fixture_with_wallet(
http_pool, http_pool, profile, wallet, options,
profile,
workspace_root,
options,
) )
.await .await
{ {
@@ -90,11 +115,11 @@ where
request.submit = true; request.submit = true;
request.operator_confirmed = true; request.operator_confirmed = true;
request.post_validation_max_retries = 20; request.post_validation_max_retries = 20;
let execution = match crate::execute_devnet_spl_token_2022( let execution = match crate::execute_devnet_spl_token_2022_with_wallet(
http_pool, http_pool,
store, store,
profile, profile,
workspace_root, wallet,
&request, &request,
decoders.as_slice(), decoders.as_slice(),
materializers.as_slice(), materializers.as_slice(),
@@ -521,7 +546,8 @@ mod tests {
if let std::result::Result::Err(error) = store.initialize_store_schema().await { if let std::result::Result::Err(error) = store.initialize_store_schema().await {
panic!("PostgreSQL schema initialization failed: {error}"); panic!("PostgreSQL schema initialization failed: {error}");
} }
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured_wallet_dir =
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() { let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir configured_wallet_dir
} else { } else {

View File

@@ -1,5 +1,5 @@
// file: ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/fixture.rs // file: ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/fixture.rs
// version: 6 // version: 7
//! Native Token-2022 mint preparation for the embedded Token Metadata Devnet campaign. //! Native Token-2022 mint preparation for the embedded Token Metadata Devnet campaign.
@@ -77,6 +77,22 @@ pub async fn prepare_token_2022_metadata_fixture(
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path, workspace_root: &std::path::Path,
options: &crate::Token2022MetadataFixturePreparationOptions, options: &crate::Token2022MetadataFixturePreparationOptions,
) -> ks_core::Result<crate::Token2022MetadataFixturePreparationSummary> {
let temporary = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = crate::DevnetExecutionWallet::Temporary(temporary);
return prepare_token_2022_metadata_fixture_with_wallet(http_pool, profile, &wallet, options)
.await;
}
/// Creates a fresh Token-2022 metadata fixture with an explicitly resolved execution wallet.
pub async fn prepare_token_2022_metadata_fixture_with_wallet(
http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig,
operator: &crate::DevnetExecutionWallet,
options: &crate::Token2022MetadataFixturePreparationOptions,
) -> ks_core::Result<crate::Token2022MetadataFixturePreparationSummary> { ) -> ks_core::Result<crate::Token2022MetadataFixturePreparationSummary> {
if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() { if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
@@ -84,8 +100,9 @@ pub async fn prepare_token_2022_metadata_fixture(
)); ));
} }
if profile.wallet.cluster != "devnet" if profile.wallet.cluster != "devnet"
|| !profile.wallet.temporary_wallet_enabled || (profile.wallet.wallet_alias.is_none()
|| !profile.wallet.temporary_wallet_persist && (!profile.wallet.temporary_wallet_enabled
|| !profile.wallet.temporary_wallet_persist))
{ {
return std::result::Result::Err(ks_core::Error::config( return std::result::Result::Err(ks_core::Error::config(
"Token-2022 metadata fixture requires a persistent Devnet wallet profile", "Token-2022 metadata fixture requires a persistent Devnet wallet profile",
@@ -96,10 +113,6 @@ pub async fn prepare_token_2022_metadata_fixture(
"Token-2022 metadata fixture requires enabled Devnet submission and explicit operator confirmation", "Token-2022 metadata fixture requires enabled Devnet submission and explicit operator confirmation",
)); ));
} }
let operator = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let fixture_store = match ks_wallet::TemporaryWalletStore::new( let fixture_store = match ks_wallet::TemporaryWalletStore::new(
options.wallet_dir.join("token_2022_metadata_validation"), options.wallet_dir.join("token_2022_metadata_validation"),
) { ) {
@@ -147,7 +160,7 @@ pub async fn prepare_token_2022_metadata_fixture(
http_pool, http_pool,
profile, profile,
options, options,
&operator, operator,
&mint_wallet, &mint_wallet,
mint_space, mint_space,
rent_budget_space, rent_budget_space,
@@ -285,7 +298,7 @@ async fn create_native_token_2022_metadata_mint(
http_pool: &ks_onchain_transport::HttpEndpointPool, http_pool: &ks_onchain_transport::HttpEndpointPool,
profile: &ks_config::ProfileConfig, profile: &ks_config::ProfileConfig,
options: &crate::Token2022MetadataFixturePreparationOptions, options: &crate::Token2022MetadataFixturePreparationOptions,
operator: &ks_wallet::TemporaryWallet, operator: &crate::DevnetExecutionWallet,
mint_wallet: &ks_wallet::TemporaryWallet, mint_wallet: &ks_wallet::TemporaryWallet,
mint_space: usize, mint_space: usize,
rent_budget_space: usize, rent_budget_space: usize,

View File

@@ -0,0 +1,138 @@
// file: ks-pipeline-demo-scenarios/src/wallet.rs
// version: 1
//! Runtime wallet resolution shared by Devnet demonstration scenarios.
/// Resolved signing capability used by Devnet demonstration scenarios.
///
/// The enum owns either the historical managed temporary wallet or an
/// authenticated native `.kswallet`. It never exposes private key bytes.
pub enum DevnetExecutionWallet {
/// Managed temporary wallet used when no persistent alias is selected.
Temporary(ks_wallet::TemporaryWallet),
/// Authenticated native wallet selected explicitly by alias.
Persistent(ks_wallet::UnlockedWallet),
}
impl std::fmt::Debug for crate::DevnetExecutionWallet {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("DevnetExecutionWallet")
.field("identity", &self.identity())
.finish();
}
}
impl crate::DevnetExecutionWallet {
/// Returns the non-secret resolved wallet identity.
pub fn identity(&self) -> ks_wallet::WalletIdentity {
return match self {
Self::Temporary(wallet) => wallet.identity(),
Self::Persistent(wallet) => wallet.identity(),
};
}
/// Returns the resolved Solana public key.
pub fn public_key(&self) -> std::string::String {
return match self {
Self::Temporary(wallet) => wallet.public_key(),
Self::Persistent(wallet) => wallet.public_key(),
};
}
/// Returns whether this execution capability is temporary or native persistent.
pub fn persistence(&self) -> ks_wallet::WalletPersistence {
return match self {
Self::Temporary(wallet) => wallet.persistence(),
Self::Persistent(_) => ks_wallet::WalletPersistence::Persistent,
};
}
/// Returns the signer interface without exposing private key bytes.
pub fn as_signer(&self) -> &dyn solana_signer::Signer {
return match self {
Self::Temporary(wallet) => wallet.as_signer(),
Self::Persistent(wallet) => wallet.as_signer(),
};
}
/// Returns the signer interface while preserving `Sync` for async execution paths.
pub fn as_sync_signer(&self) -> &(dyn solana_signer::Signer + std::marker::Sync) {
return match self {
Self::Temporary(wallet) => wallet.as_sync_signer(),
Self::Persistent(wallet) => wallet.as_sync_signer(),
};
}
}
/// Resolves the execution wallet selected by one profile.
///
/// A profile with `wallet_alias` requires an explicit runtime password and
/// resolves only the native wallet store. A profile without `wallet_alias`
/// retains the managed temporary-wallet behavior.
pub async fn resolve_devnet_execution_wallet(
profile: &ks_config::ProfileConfig,
workspace_root: &std::path::Path,
password: std::option::Option<ks_wallet::WalletPassword>,
) -> ks_core::Result<crate::DevnetExecutionWallet> {
if profile.wallet.cluster != "devnet" {
return std::result::Result::Err(ks_core::Error::config(
"Devnet execution wallet resolution requires a Devnet profile",
));
}
if profile.wallet.wallet_alias.is_some() {
let password = match password {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"wallet_persistent_password_required",
"selected native wallet requires an explicit runtime password",
));
},
};
let wallet =
match crate::unlock_selected_profile_wallet(profile, workspace_root, password).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
tracing::info!(
target: "ks-pipeline-demo-scenarios.wallet",
action = "resolve_execution_wallet",
profile = profile.name.as_str(),
wallet_alias = wallet.alias().as_str(),
public_key = %wallet.public_key(),
persistence = "persistent",
"resolved authenticated native wallet for Devnet execution"
);
return std::result::Result::Ok(crate::DevnetExecutionWallet::Persistent(wallet));
}
let wallet = match crate::load_profile_temporary_wallet(profile, workspace_root).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
tracing::debug!(
target: "ks-pipeline-demo-scenarios.wallet",
action = "resolve_execution_wallet",
profile = profile.name.as_str(),
wallet_alias = wallet.identity().alias.as_str(),
public_key = %wallet.public_key(),
persistence = "temporary",
"resolved managed temporary wallet for Devnet execution"
);
return std::result::Result::Ok(crate::DevnetExecutionWallet::Temporary(wallet));
}
#[cfg(test)]
mod tests {
#[test]
fn debug_contains_only_non_secret_identity() {
let alias = ks_wallet::WalletAlias::parse("scenario-temp")
.unwrap_or_else(|error| panic!("alias must be valid: {error}"));
let wallet =
crate::DevnetExecutionWallet::Temporary(ks_wallet::TemporaryWallet::generate(alias));
let debug = format!("{wallet:?}");
assert!(debug.contains("scenario-temp"));
assert!(!debug.contains("keypair"));
assert!(!debug.contains("password"));
}
}

View File

@@ -0,0 +1,13 @@
<!-- file: ks-wallet-demo-scenarios/CHANGELOG.md -->
<!-- version: 1 -->
# CHANGELOG — ks-wallet-demo-scenarios
## `0.5.2-pre.006-delta-fix-010`
- crée la crate réutilisable `ks-wallet-demo-scenarios` sans dépendance vers Tauri ou le desktop ;
- ajoute `rotate_wallet_password()` pour valider quun changement de mot de passe conserve exactement lidentité publique ;
- ajoute `expect_wallet_password_rejected()` pour exiger le rejet de lancien mot de passe avec le code `wallet_native_authentication_failed` ;
- ajoute `unlock_and_sign_wallet()` pour vérifier une ouverture authentifiée et une vraie capacité de signature sans exposer le secret ;
- ajoute un test externe ordonné A → B → rejet de A → ouverture/signature avec B → restauration B → A → ouverture/signature finale avec A ;
- prépare la crate à recevoir ultérieurement les scénarios dimport/export, migration et formats tiers sans étendre `kb-app-demo-desktop`.

View File

@@ -0,0 +1,20 @@
# file: ks-wallet-demo-scenarios/Cargo.toml
# version: 1
[package]
name = "ks-wallet-demo-scenarios"
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
ks-core = { path = "../ks-core" }
ks-wallet = { path = "../ks-wallet" }
[dev-dependencies]
tempfile.workspace = true
tokio.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,43 @@
<!-- file: ks-wallet-demo-scenarios/README.md -->
<!-- version: 1 -->
# ks-wallet-demo-scenarios
`ks-wallet-demo-scenarios` regroupe les scénarios dintégration réutilisables qui valident `ks-wallet` comme le ferait un consommateur externe.
## Responsabilité
La crate nimplémente ni stockage de secret, ni chiffrement, ni codec `.kswallet`. Elle dépend uniquement de lAPI publique de `ks-wallet` et orchestre des séquences vérifiables autour des invariants wallet.
La première surface couvre le cycle de mot de passe natif :
1. changement du mot de passe A vers B avec conservation de lidentité publique ;
2. vérification explicite que A est rejeté ;
3. ouverture et signature avec B ;
4. restauration du mot de passe B vers A ;
5. contrôle final douverture et de signature avec A restauré.
Les étapes publiques restent séparées afin dêtre réutilisables dans dautres scénarios. Le test dintégration les exécute dans un seul test ordonné : il ne dépend jamais de lordre dexécution de plusieurs `#[test]`.
## Frontières de sécurité
- les fonctions reçoivent des `ks_wallet::WalletPassword` déjà construits ;
- aucun mot de passe nest sérialisé, journalisé ou inclus dans les summaries ;
- les summaries contiennent uniquement lidentité publique, un code de rejet, une signature publique et la longueur du challenge lorsque ces informations sont nécessaires ;
- la création du wallet de fixture appartient au test externe et utilise un répertoire temporaire privé ;
- les fonctions de scénario ne lisent jamais directement les bytes du `.kswallet`.
## Relations
- `ks-wallet` possède le format, le secret, lauthentification et la capacité `Signer` ;
- `ks-wallet-demo-scenarios` possède uniquement lorchestration de validation ;
- `kb-app-demo-desktop` nest pas requis pour valider le cycle de mot de passe ;
- les futurs scénarios dimport/export ou de migration pourront être ajoutés ici sans alourdir lAPI Tauri.
## Documentation
- [USAGE.md](USAGE.md)
- [TODO.md](TODO.md)
- [CHANGELOG.md](CHANGELOG.md)
- [`ks-wallet`](../ks-wallet/README.md)
- [ROADMAP général](../ROADMAP.md)

View File

@@ -0,0 +1,19 @@
<!-- file: ks-wallet-demo-scenarios/TODO.md -->
<!-- version: 1 -->
# TODO — ks-wallet-demo-scenarios
## `0.5.2`
- [x] créer une crate de scénarios indépendante de Tauri et consommatrice exclusive de lAPI publique `ks-wallet` ;
- [x] ajouter une étape réutilisable de rotation de mot de passe avec conservation stricte de lalias et de la pubkey ;
- [x] ajouter une étape qui exige le rejet de lancien mot de passe par le code dauthentification natif attendu ;
- [x] ajouter une étape douverture authentifiée et de signature dun challenge non vide ;
- [x] composer un test externe ordonné A → B → rejet de A → ouverture/signature avec B → B → A → ouverture/signature avec A restauré.
## Version ultérieure non déterminée
- [ ] ajouter des scénarios de round-trip pour chaque nouveau format dimport/export supporté par `ks-wallet` ;
- [ ] ajouter des scénarios de migration legacy vers `.kswallet` lorsque plusieurs formats sources doivent être comparés ;
- [ ] ajouter des scénarios douverture explicite hors store si une validation opérateur supplémentaire devient utile ;
- [ ] najouter une CLI que si une validation manuelle hors tests apporte une valeur distincte des consommateurs existants.

View File

@@ -0,0 +1,88 @@
<!-- file: ks-wallet-demo-scenarios/USAGE.md -->
<!-- version: 1 -->
# Utilisation de ks-wallet-demo-scenarios
## Exécuter la validation intégrée
La validation autonome de la crate utilise un wallet synthétique et naltère aucun wallet réel du workspace :
```bash
cargo test -p ks-wallet-demo-scenarios
```
Le test suit un ordre déterministe dans une seule fonction : A → B, rejet de A, ouverture/signature avec B, B → A, puis ouverture/signature finale avec A.
## Réutiliser une rotation
```rust
let summary = match ks_wallet_demo_scenarios::rotate_wallet_password(
&manager,
&alias,
current_password,
replacement_password,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
println!("public key={}", summary.identity.public_key);
```
La fonction inspecte lidentité avant rotation, appelle `WalletManager::change_password()` puis refuse le résultat si lalias ou la pubkey a changé.
## Vérifier quun ancien mot de passe est rejeté
```rust
let rejection = match ks_wallet_demo_scenarios::expect_wallet_password_rejected(
&manager,
&alias,
old_password,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
assert_eq!(
rejection.rejection_code,
"wallet_native_authentication_failed",
);
```
Un échec provenant dune autre cause nest pas converti en succès du scénario. Une ouverture réussie avec le mot de passe supposé rejeté fait échouer explicitement la validation.
## Vérifier ouverture et signature
```rust
let signing = match ks_wallet_demo_scenarios::unlock_and_sign_wallet(
&manager,
&alias,
active_password,
b"wallet scenario challenge",
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error);
},
};
assert!(!signing.signature.is_empty());
```
Le challenge doit être non vide. La fonction compare lidentité inspectée à lidentité authentifiée avant de signer, puis verrouille la capacité ouverte avant le retour.
## Limites actuelles
La crate ne fournit pas encore de CLI ni de gestion de secrets par variables denvironnement. Les consommateurs construisent les `WalletPassword` explicitement et les transmettent aux étapes de scénario.
Les scénarios import/export, migration legacy et formats tiers seront ajoutés lorsquils apporteront une validation supplémentaire au-delà des tests unitaires de `ks-wallet`.

View File

@@ -0,0 +1,23 @@
// file: ks-wallet-demo-scenarios/src/lib.rs
// version: 1
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
#![warn(missing_docs)]
//! Reusable integration scenarios for the public `ks-wallet` boundary.
mod password_lifecycle;
/// Safe summary returned after a password is rejected as expected.
pub use self::password_lifecycle::WalletPasswordRejectionSummary;
/// Safe summary returned after a password rotation preserving wallet identity.
pub use self::password_lifecycle::WalletPasswordRotationSummary;
/// Safe summary returned after authenticated message signing.
pub use self::password_lifecycle::WalletSigningScenarioSummary;
/// Requires one supplied password to fail native wallet authentication.
pub use self::password_lifecycle::expect_wallet_password_rejected;
/// Changes one native wallet password and verifies that its public identity is preserved.
pub use self::password_lifecycle::rotate_wallet_password;
/// Authenticates one native wallet and signs an explicit non-empty challenge.
pub use self::password_lifecycle::unlock_and_sign_wallet;

View File

@@ -0,0 +1,161 @@
// file: ks-wallet-demo-scenarios/src/password_lifecycle.rs
// version: 1
//! Password lifecycle scenarios driven only through the public wallet API.
/// Safe summary returned after a password rotation preserving wallet identity.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WalletPasswordRotationSummary {
/// Persistent wallet identity retained across the password rotation.
pub identity: ks_wallet::WalletIdentity,
}
/// Safe summary returned after a password is rejected as expected.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WalletPasswordRejectionSummary {
/// Persistent wallet identity whose old password was rejected.
pub identity: ks_wallet::WalletIdentity,
/// Stable `ks-wallet` error code that proved the authentication rejection.
pub rejection_code: std::string::String,
}
/// Safe summary returned after authenticated message signing.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WalletSigningScenarioSummary {
/// Persistent wallet identity used by the signing capability.
pub identity: ks_wallet::WalletIdentity,
/// Base58 signature produced for the supplied challenge.
pub signature: std::string::String,
/// Number of challenge bytes signed by the wallet.
pub message_length: usize,
}
/// Changes one native wallet password and verifies that its public identity is preserved.
pub async fn rotate_wallet_password(
manager: &ks_wallet::WalletManager,
alias: &ks_wallet::WalletAlias,
current_password: ks_wallet::WalletPassword,
replacement_password: ks_wallet::WalletPassword,
) -> ks_core::Result<crate::WalletPasswordRotationSummary> {
let before = match manager.lookup(alias).await {
std::result::Result::Ok(std::option::Option::Some(handle)) => handle,
std::result::Result::Ok(std::option::Option::None) => {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_native_not_found",
"password lifecycle scenario requires an existing native wallet",
));
},
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let changed = match manager.change_password(alias, current_password, replacement_password).await
{
std::result::Result::Ok(handle) => handle,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if before.alias() != changed.alias() || before.public_key() != changed.public_key() {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_password_rotation_identity_changed",
"password rotation changed the native wallet public identity",
));
}
return std::result::Result::Ok(crate::WalletPasswordRotationSummary {
identity: changed.identity(),
});
}
/// Requires one supplied password to fail native wallet authentication.
pub async fn expect_wallet_password_rejected(
manager: &ks_wallet::WalletManager,
alias: &ks_wallet::WalletAlias,
rejected_password: ks_wallet::WalletPassword,
) -> ks_core::Result<crate::WalletPasswordRejectionSummary> {
let handle = match manager.lookup(alias).await {
std::result::Result::Ok(std::option::Option::Some(handle)) => handle,
std::result::Result::Ok(std::option::Option::None) => {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_native_not_found",
"password rejection scenario requires an existing native wallet",
));
},
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let rejection = match manager.unlock(alias, rejected_password).await {
std::result::Result::Ok(wallet) => {
let accepted_identity = wallet.identity();
wallet.lock();
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_rejected_password_accepted",
format!(
"password expected to be rejected authenticated wallet alias {} with public key {}",
accepted_identity.alias.as_str(),
accepted_identity.public_key
),
));
},
std::result::Result::Err(error) => error,
};
if rejection.code() != "wallet_native_authentication_failed" {
return std::result::Result::Err(rejection);
}
return std::result::Result::Ok(crate::WalletPasswordRejectionSummary {
identity: handle.identity(),
rejection_code: rejection.code().to_owned(),
});
}
/// Authenticates one native wallet and signs an explicit non-empty challenge.
pub async fn unlock_and_sign_wallet(
manager: &ks_wallet::WalletManager,
alias: &ks_wallet::WalletAlias,
password: ks_wallet::WalletPassword,
challenge: &[u8],
) -> ks_core::Result<crate::WalletSigningScenarioSummary> {
if challenge.is_empty() {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_signing_challenge_empty",
"wallet signing scenario requires a non-empty challenge",
));
}
let expected = match manager.lookup(alias).await {
std::result::Result::Ok(std::option::Option::Some(handle)) => handle,
std::result::Result::Ok(std::option::Option::None) => {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_native_not_found",
"wallet signing scenario requires an existing native wallet",
));
},
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = match manager.unlock(alias, password).await {
std::result::Result::Ok(wallet) => wallet,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let identity = wallet.identity();
if &identity.alias != expected.alias() || identity.public_key.as_str() != expected.public_key()
{
wallet.lock();
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_unlocked_identity_mismatch",
"authenticated wallet identity differs from the inspected native wallet",
));
}
let signature = match wallet.sign_message(challenge) {
std::result::Result::Ok(signature) => signature,
std::result::Result::Err(error) => {
wallet.lock();
return std::result::Result::Err(error);
},
};
wallet.lock();
if signature.is_empty() {
return std::result::Result::Err(ks_core::Error::new(
"wallet_demo_signature_empty",
"authenticated wallet produced an empty signature",
));
}
return std::result::Result::Ok(crate::WalletSigningScenarioSummary {
identity,
signature,
message_length: challenge.len(),
});
}

View File

@@ -0,0 +1,90 @@
// file: ks-wallet-demo-scenarios/tests/password_lifecycle.rs
// version: 1
//! External ordered password lifecycle scenario.
const PASSWORD_A: &str = "ks-wallet-demo-password-A";
const PASSWORD_B: &str = "ks-wallet-demo-password-B";
const CHALLENGE_B: &[u8] = b"ks-wallet-demo-scenarios password-B signing proof";
const CHALLENGE_A_RESTORED: &[u8] = b"ks-wallet-demo-scenarios restored password-A signing proof";
fn make_directory_private(path: &std::path::Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700)).unwrap_or_else(
|error| panic!("wallet scenario directory permissions must be set: {error}"),
);
}
}
fn password(value: &str) -> ks_wallet::WalletPassword {
return ks_wallet::WalletPassword::new(value.to_owned())
.unwrap_or_else(|error| panic!("scenario password must be accepted: {error}"));
}
#[tokio::test]
async fn password_rotation_runs_a_to_b_rejects_a_then_restores_a() {
let directory = tempfile::tempdir()
.unwrap_or_else(|error| panic!("temporary scenario directory must exist: {error}"));
make_directory_private(directory.path());
let manager = ks_wallet::WalletManager::new(directory.path())
.unwrap_or_else(|error| panic!("wallet manager must be created: {error}"));
let alias = ks_wallet::WalletAlias::parse("password-lifecycle")
.unwrap_or_else(|error| panic!("scenario alias must be valid: {error}"));
let created = manager
.create(alias.clone(), password(PASSWORD_A))
.await
.unwrap_or_else(|error| panic!("scenario wallet creation must succeed: {error}"));
let initial_public_key = created.public_key();
created.lock();
let rotated_to_b = ks_wallet_demo_scenarios::rotate_wallet_password(
&manager,
&alias,
password(PASSWORD_A),
password(PASSWORD_B),
)
.await
.unwrap_or_else(|error| panic!("password A to B rotation must succeed: {error}"));
assert_eq!(rotated_to_b.identity.public_key, initial_public_key);
let rejected_a = ks_wallet_demo_scenarios::expect_wallet_password_rejected(
&manager,
&alias,
password(PASSWORD_A),
)
.await
.unwrap_or_else(|error| panic!("old password A must be rejected: {error}"));
assert_eq!(rejected_a.identity.public_key, initial_public_key);
assert_eq!(rejected_a.rejection_code, "wallet_native_authentication_failed");
let signed_with_b = ks_wallet_demo_scenarios::unlock_and_sign_wallet(
&manager,
&alias,
password(PASSWORD_B),
CHALLENGE_B,
)
.await
.unwrap_or_else(|error| panic!("password B must unlock and sign: {error}"));
assert_eq!(signed_with_b.identity.public_key, initial_public_key);
assert_eq!(signed_with_b.message_length, CHALLENGE_B.len());
assert!(!signed_with_b.signature.is_empty());
let restored_to_a = ks_wallet_demo_scenarios::rotate_wallet_password(
&manager,
&alias,
password(PASSWORD_B),
password(PASSWORD_A),
)
.await
.unwrap_or_else(|error| panic!("password B to A restoration must succeed: {error}"));
assert_eq!(restored_to_a.identity.public_key, initial_public_key);
let signed_with_restored_a = ks_wallet_demo_scenarios::unlock_and_sign_wallet(
&manager,
&alias,
password(PASSWORD_A),
CHALLENGE_A_RESTORED,
)
.await
.unwrap_or_else(|error| panic!("restored password A must unlock and sign: {error}"));
assert_eq!(signed_with_restored_a.identity.public_key, initial_public_key);
assert_eq!(signed_with_restored_a.message_length, CHALLENGE_A_RESTORED.len());
assert!(!signed_with_restored_a.signature.is_empty());
}

View File

@@ -1,5 +1,5 @@
<!-- file: ks-wallet/README.md --> <!-- file: ks-wallet/README.md -->
<!-- version: 12 --> <!-- version: 13 -->
# ks-wallet # ks-wallet
@@ -66,6 +66,8 @@ Le scan automatique reste strictement borné au répertoire fourni à `WalletMan
`ks-config` peut sélectionner un alias ou une identité non sensible, mais ne stocke aucun mot de passe ni matériau secret. `ks-config` peut sélectionner un alias ou une identité non sensible, mais ne stocke aucun mot de passe ni matériau secret.
`ks-wallet-demo-scenarios` consomme uniquement lAPI publique de `ks-wallet` pour orchestrer les validations intégrées du cycle de vie et, ultérieurement, des formats de transfert. Cette crate de scénarios ne fait pas partie de la frontière de stockage ou de chiffrement.
Une application desktop doit projeter les informations autorisées dans ses propres DTO Tauri ; `ks-wallet` ne réintroduit pas TS-RS. Une application desktop doit projeter les informations autorisées dans ses propres DTO Tauri ; `ks-wallet` ne réintroduit pas TS-RS.
## Documentation ## Documentation

View File

@@ -1,5 +1,5 @@
<!-- file: ks-wallet/USAGE.md --> <!-- file: ks-wallet/USAGE.md -->
<!-- version: 12 --> <!-- version: 13 -->
# Utilisation de ks-wallet # Utilisation de ks-wallet
@@ -458,7 +458,7 @@ lookup by alias
sign without exposing private bytes sign without exposing private bytes
``` ```
Le store découvrira les wallets persistants en scannant son répertoire résolu (racine `KS_WALLETS_DIRECTORY`, `wallets` par défaut, puis éventuel sous-répertoire configuré) pour les `<alias>.kswallet` valides. Le scan ne doit pas faire confiance au seul nom du fichier. Le store découvre les wallets persistants en scannant directement sa racine résolue (`KS_WALLETS_DIRECTORY`, `wallets` par défaut) pour les `<alias>.kswallet` valides. Les sous-répertoires `temporary/...` appartiennent aux fixtures et ne font pas partie du scan natif persistant. Le scan ne doit pas faire confiance au seul nom du fichier.
Le changement de mot de passe rechiffre la même keypair. Il n'existe pas de rotation normale du secret Ed25519 conservant la même pubkey : une nouvelle clé secrète signifie une nouvelle identité Solana. Le changement de mot de passe rechiffre la même keypair. Il n'existe pas de rotation normale du secret Ed25519 conservant la même pubkey : une nouvelle clé secrète signifie une nouvelle identité Solana.

View File

@@ -196,11 +196,12 @@
} }
}, },
"wallet": { "wallet": {
"wallet_dir": "wallets/temporary/local_devnet", "wallet_dir": "wallets",
"cluster": "devnet", "cluster": "devnet",
"temporary_wallet_enabled": true, "temporary_wallet_enabled": true,
"temporary_wallet_alias": "local-devnet-operator", "temporary_wallet_alias": "local-devnet-operator",
"temporary_wallet_persist": true "temporary_wallet_persist": true,
"temporary_wallet_dir": "wallets/temporary/local_devnet"
}, },
"execution": { "execution": {
"dry_run_default": true, "dry_run_default": true,

View File

@@ -196,11 +196,12 @@
} }
}, },
"wallet": { "wallet": {
"wallet_dir": "wallets/temporary/local_devnet", "wallet_dir": "wallets",
"cluster": "devnet", "cluster": "devnet",
"temporary_wallet_enabled": true, "temporary_wallet_enabled": true,
"temporary_wallet_alias": "local-devnet-operator", "temporary_wallet_alias": "local-devnet-operator",
"temporary_wallet_persist": true "temporary_wallet_persist": true,
"temporary_wallet_dir": "wallets/temporary/local_devnet"
}, },
"execution": { "execution": {
"dry_run_default": true, "dry_run_default": true,
@@ -618,11 +619,12 @@
} }
}, },
"wallet": { "wallet": {
"wallet_dir": "wallets/temporary/mainnet_research", "wallet_dir": "wallets",
"cluster": "mainnet-beta", "cluster": "mainnet-beta",
"temporary_wallet_enabled": false, "temporary_wallet_enabled": false,
"temporary_wallet_alias": "mainnet_research-disabled", "temporary_wallet_alias": "mainnet_research-disabled",
"temporary_wallet_persist": false "temporary_wallet_persist": false,
"temporary_wallet_dir": "wallets/temporary/mainnet_research"
}, },
"execution": { "execution": {
"dry_run_default": true, "dry_run_default": true,
@@ -1040,11 +1042,12 @@
} }
}, },
"wallet": { "wallet": {
"wallet_dir": "wallets/temporary/mainnet", "wallet_dir": "wallets",
"cluster": "mainnet-beta", "cluster": "mainnet-beta",
"temporary_wallet_enabled": false, "temporary_wallet_enabled": false,
"temporary_wallet_alias": "mainnet-disabled", "temporary_wallet_alias": "mainnet-disabled",
"temporary_wallet_persist": false "temporary_wallet_persist": false,
"temporary_wallet_dir": "wallets/temporary/mainnet"
}, },
"execution": { "execution": {
"dry_run_default": true, "dry_run_default": true,