v0.5.2-pre.006-fix-010
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# file: Cargo.toml
|
||||
# version: 63
|
||||
# version: 64
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
@@ -14,6 +14,7 @@ members = [
|
||||
"ks-onchain-transport",
|
||||
"ks-store",
|
||||
"ks-wallet",
|
||||
"ks-wallet-demo-scenarios",
|
||||
"kb-app-demo-desktop",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: README.md -->
|
||||
<!-- version: 24 -->
|
||||
<!-- version: 25 -->
|
||||
|
||||
# Khadhroony Bot3
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
## Architecture
|
||||
|
||||
Le workspace contient onze crates :
|
||||
Le workspace contient douze crates :
|
||||
|
||||
- `ks-core` : erreurs et primitives transversales ;
|
||||
- `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-store` : contrats de stockage et adaptateur PostgreSQL ;
|
||||
- `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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ROADMAP.md -->
|
||||
<!-- version: 38 -->
|
||||
<!-- version: 39 -->
|
||||
|
||||
# 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é ;
|
||||
- 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.
|
||||
- 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 d’import/export et migration.
|
||||
|
||||
### 0.5.3 — audit et normalisation de `ks-store`
|
||||
|
||||
|
||||
@@ -465,6 +465,7 @@
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"wallet_dir",
|
||||
"temporary_wallet_dir",
|
||||
"cluster",
|
||||
"temporary_wallet_enabled",
|
||||
"temporary_wallet_alias",
|
||||
@@ -474,6 +475,9 @@
|
||||
"wallet_dir": {
|
||||
"$ref": "#/$defs/non_empty_string"
|
||||
},
|
||||
"temporary_wallet_dir": {
|
||||
"$ref": "#/$defs/non_empty_string"
|
||||
},
|
||||
"cluster": {
|
||||
"$ref": "#/$defs/non_empty_string"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
],
|
||||
"properties": {
|
||||
"wallets_directory": {
|
||||
"$ref": "#/$defs/non_empty_string"
|
||||
"$ref": "#/$defs/non_empty_string",
|
||||
"description": "Root directory dedicated to persistent native .kswallet containers."
|
||||
},
|
||||
"default_profile": {
|
||||
"$ref": "#/$defs/non_empty_string"
|
||||
@@ -45,7 +46,8 @@
|
||||
"$ref": "#/$defs/non_empty_string"
|
||||
},
|
||||
"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": {
|
||||
"$ref": "#/$defs/non_empty_string"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/README.md -->
|
||||
<!-- version: 35 -->
|
||||
<!-- version: 36 -->
|
||||
|
||||
# Documentation active de Khadhroony Bot3
|
||||
|
||||
@@ -70,7 +70,7 @@ Elles peuvent être chargées directement par les tests unitaires ou d’intégr
|
||||
|
||||
## 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-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-store`](../ks-store/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).
|
||||
|
||||
## 8. Guides
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/architecture/CRATE_MAP.md -->
|
||||
<!-- version: 7 -->
|
||||
<!-- version: 8 -->
|
||||
|
||||
# Carte des crates
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
| `ks-onchain-transport` | bibliothèque | transports RPC HTTP/WebSocket et pools d’endpoints | README/TODO/USAGE/CHANGELOG présents |
|
||||
| `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-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` |
|
||||
|
||||
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 qu’il 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 qu’il appartient au domaine applicatif Bot. Voir [`../decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md`](../decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md).
|
||||
|
||||
## 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 transports Solana sont réunis dans `ks-onchain-transport` ;
|
||||
- l’application 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 n’est 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 l’audit d’alignement.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/V0_5_2_KS_WALLET_RESTRUCTURING_PLAN.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 11 -->
|
||||
|
||||
# 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.
|
||||
|
||||
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 :
|
||||
|
||||
@@ -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] 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] 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
|
||||
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
<!-- file: kb-app-demo-desktop/CHANGELOG.md -->
|
||||
<!-- version: 57 -->
|
||||
<!-- version: 60 -->
|
||||
|
||||
# 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 d’export `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`
|
||||
|
||||
- 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 ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/README.md -->
|
||||
<!-- version: 20 -->
|
||||
<!-- version: 21 -->
|
||||
|
||||
# kb-app-demo-desktop
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
## Fonctionnalités
|
||||
|
||||
- configuration et diagnostics de logging ;
|
||||
- fenêtre Wallets dédiée : inventaire sûr des identités `.kswallet`, création d’un 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 d’un `.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 d’un 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 d’un `.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 ;
|
||||
- diagnostics PostgreSQL ;
|
||||
- backfill, extraction Core et replay, avec enregistrement générique de Solana Program Metadata ;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- file: kb-app-demo-desktop/TODO.md -->
|
||||
<!-- version: 17 -->
|
||||
<!-- version: 19 -->
|
||||
|
||||
# TODO — kb-app-demo-desktop
|
||||
|
||||
## É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 d’un `.kswallet` dans `demo_wallet`, confirmer `persistence="persistent"` dans les logs puis retrouver les signatures produites depuis l’explorateur.
|
||||
- [ ] `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.
|
||||
- [ ] Observabilité — afficher explicitement les comptes relus et les projections matérialisées.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/USAGE.md -->
|
||||
<!-- version: 20 -->
|
||||
<!-- version: 21 -->
|
||||
|
||||
# Utilisation de kb-app-demo-desktop
|
||||
|
||||
@@ -11,9 +11,10 @@ L’application 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 d’une adresse publique. Les DTO frontend ne contiennent jamais de matériau secret. Aucun password, chemin interne du store, sel, nonce, ciphertext ou byte privé n’est 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 ;
|
||||
- sélection session-only du `.kswallet` utilisé par les démos Devnet, sans réécrire la configuration ;
|
||||
- création d’un nouveau `.kswallet` dans le store du profil actif ;
|
||||
- inspection et import d'un keypair externe dans 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 **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.
|
||||
|
||||
### 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é
|
||||
|
||||
L’explorateur on-chain ne déverrouille aucun wallet : seule la pubkey publique est nécessaire. Le bouton `Explorer` d’un wallet inventorié remplit l’adresse automatiquement ; toute autre pubkey Solana peut également être saisie.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_wallet.html -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -49,6 +49,33 @@
|
||||
<dt class="col-sm-3">Wallets trouvés</dt>
|
||||
<dd class="col-sm-9"><code id="walletInventoryCount">0</code></dd>
|
||||
</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">
|
||||
<table class="table table-sm align-middle mb-0" id="walletInventoryTable">
|
||||
<thead>
|
||||
@@ -56,7 +83,7 @@
|
||||
<th>Alias</th>
|
||||
<th>Pubkey déclarée</th>
|
||||
<th>Format</th>
|
||||
<th>Sélectionné</th>
|
||||
<th>Exécution</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -128,13 +155,16 @@
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body">
|
||||
<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>
|
||||
<select id="walletExportAliasSelect" class="form-select"></select>
|
||||
<label for="walletExportFormatSelect" class="form-label mt-3">Format d'export</label>
|
||||
<select id="walletExportFormatSelect" class="form-select"></select>
|
||||
<label for="walletExportDestinationPathInput" class="form-label mt-3">Chemin destination</label>
|
||||
<input id="walletExportDestinationPathInput" class="form-control font-monospace" type="text" autocomplete="off" placeholder="/chemin/vers/export.json">
|
||||
<label for="walletExportFileNameInput" class="form-label mt-3">Nom du fichier</label>
|
||||
<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">
|
||||
<button id="exportWalletTransferButton" class="btn btn-primary" type="button">Exporter</button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_wallet.ts
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
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 { DemoWalletCreateRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletCreateRequest.ts";
|
||||
import type { DemoWalletExportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExportRequest.ts";
|
||||
import type { DemoWalletExecutionProfilePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionProfilePayload.ts";
|
||||
import type { DemoWalletExecutionSelectionRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionSelectionRequest.ts";
|
||||
import type { DemoWalletIdentityPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletIdentityPayload.ts";
|
||||
import type { DemoWalletImportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletImportRequest.ts";
|
||||
import type { DemoWalletInventoryPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletInventoryPayload.ts";
|
||||
@@ -96,6 +98,102 @@ function renderOnchainProfiles(profiles: ReadonlyArray<DemoWalletOnchainProfileP
|
||||
updateOnchainClusterLabel();
|
||||
}
|
||||
|
||||
function selectedExecutionProfilePayload(): DemoWalletExecutionProfilePayload | null {
|
||||
const profile = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.value.trim() ?? "";
|
||||
if (!walletInventory || profile.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return walletInventory.execution_profiles.find(candidate => candidate.name === profile) ?? null;
|
||||
}
|
||||
|
||||
function renderExecutionProfiles(profiles: ReadonlyArray<DemoWalletExecutionProfilePayload>): void {
|
||||
const select = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect");
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
const previous = select.value;
|
||||
select.replaceChildren();
|
||||
for (const profile of profiles) {
|
||||
const option = document.createElement("option");
|
||||
option.value = profile.name;
|
||||
option.textContent = profile.name;
|
||||
select.appendChild(option);
|
||||
}
|
||||
if (profiles.some(profile => profile.name === previous)) {
|
||||
select.value = previous;
|
||||
} else if (profiles.length > 0) {
|
||||
select.value = profiles[0].name;
|
||||
}
|
||||
}
|
||||
|
||||
function renderExecutionWalletAliases(wallets: ReadonlyArray<DemoWalletIdentityPayload>): void {
|
||||
const select = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect");
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
select.replaceChildren();
|
||||
const configuredOption = document.createElement("option");
|
||||
configuredOption.value = "";
|
||||
configuredOption.textContent = "Utiliser la configuration du profil";
|
||||
select.appendChild(configuredOption);
|
||||
for (const wallet of wallets) {
|
||||
const option = document.createElement("option");
|
||||
option.value = wallet.alias;
|
||||
option.textContent = `${wallet.alias} — ${wallet.public_key}`;
|
||||
select.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
function updateExecutionSelectionDisplay(): void {
|
||||
const profile = selectedExecutionProfilePayload();
|
||||
const aliasSelect = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect");
|
||||
if (!profile) {
|
||||
setText("#walletExecutionConfiguredAlias", "—");
|
||||
setText("#walletExecutionRuntimeAlias", "—");
|
||||
setText("#walletExecutionEffectiveAlias", "temporaire");
|
||||
if (aliasSelect) {
|
||||
aliasSelect.value = "";
|
||||
}
|
||||
return;
|
||||
}
|
||||
setText("#walletExecutionConfiguredAlias", profile.configured_alias ?? "—");
|
||||
setText("#walletExecutionRuntimeAlias", profile.runtime_alias ?? "—");
|
||||
setText("#walletExecutionEffectiveAlias", profile.effective_alias ?? "temporaire");
|
||||
if (aliasSelect) {
|
||||
const requested = profile.runtime_alias ?? "";
|
||||
if (Array.from(aliasSelect.options).some(option => option.value === requested)) {
|
||||
aliasSelect.value = requested;
|
||||
} else {
|
||||
aliasSelect.value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applyExecutionWalletSelection(): Promise<void> {
|
||||
const profile = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.value.trim() ?? "";
|
||||
const aliasText = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect")?.value.trim() ?? "";
|
||||
if (profile.length === 0) {
|
||||
setText("#walletExecutionSelectionStatus", "Profil Devnet requis");
|
||||
return;
|
||||
}
|
||||
const request: DemoWalletExecutionSelectionRequest = {
|
||||
profile,
|
||||
alias: aliasText.length === 0 ? null : aliasText,
|
||||
};
|
||||
setText("#walletExecutionSelectionStatus", "Validation...");
|
||||
try {
|
||||
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_select_execution_wallet", { request });
|
||||
applyWalletInventoryPayload(payload);
|
||||
const effective = payload.execution_profiles.find(candidate => candidate.name === profile)?.effective_alias ?? null;
|
||||
setText("#walletExecutionSelectionStatus", effective ? `Persistant : ${effective}` : "Configuration du profil");
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `execution wallet selection updated for ${profile}`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
setText("#walletExecutionSelectionStatus", `Erreur : ${message}`);
|
||||
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Execution wallet selection failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function executeWalletRpc(profile: string, method: string, params: unknown[]): Promise<DemoWalletRpcExecutionPayload> {
|
||||
const request: DemoWalletRpcRequest = {
|
||||
profile,
|
||||
@@ -159,6 +257,28 @@ function selectedTransferFormat(selector: string): string {
|
||||
return document.querySelector<HTMLSelectElement>(selector)?.value.trim() ?? "";
|
||||
}
|
||||
|
||||
function selectedExportFormatPayload(): DemoWalletTransferFormatPayload | null {
|
||||
const code = selectedTransferFormat("#walletExportFormatSelect");
|
||||
if (!walletInventory || code.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return walletInventory.transfer_formats.find(format => format.code === code) ?? null;
|
||||
}
|
||||
|
||||
function updateExportFileName(force: boolean): void {
|
||||
const input = document.querySelector<HTMLInputElement>("#walletExportFileNameInput");
|
||||
const alias = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.value.trim() ?? "";
|
||||
const format = selectedExportFormatPayload();
|
||||
if (!input || alias.length === 0 || !format) {
|
||||
return;
|
||||
}
|
||||
const generated = `${alias}.${format.default_extension}`;
|
||||
if (force || input.value.trim().length === 0 || input.dataset.autoGenerated === "true") {
|
||||
input.value = generated;
|
||||
input.dataset.autoGenerated = "true";
|
||||
}
|
||||
}
|
||||
|
||||
function setTransferPublicKey(publicKey: string): void {
|
||||
inspectedTransferPublicKey = publicKey;
|
||||
setText("#walletTransferPublicKey", publicKey.length > 0 ? publicKey : "—");
|
||||
@@ -196,7 +316,8 @@ function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): vo
|
||||
publicKeyCell.textContent = wallet.public_key;
|
||||
publicKeyCell.className = "font-monospace text-break";
|
||||
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.className = "btn btn-sm btn-outline-primary";
|
||||
exploreButton.textContent = "Explorer";
|
||||
@@ -208,7 +329,7 @@ function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): vo
|
||||
document.querySelector<HTMLElement>("#walletAddressInput")?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
});
|
||||
actionCell.appendChild(exploreButton);
|
||||
if (wallet.selected) {
|
||||
if (executionSelected) {
|
||||
row.classList.add("table-primary");
|
||||
}
|
||||
row.append(aliasCell, publicKeyCell, formatCell, selectedCell, actionCell);
|
||||
@@ -216,21 +337,30 @@ function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): vo
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshWalletInventory(): Promise<void> {
|
||||
setText("#walletInventoryStatus", "Chargement...");
|
||||
try {
|
||||
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_inventory");
|
||||
function applyWalletInventoryPayload(payload: DemoWalletInventoryPayload): void {
|
||||
walletInventory = payload;
|
||||
setText("#walletProfile", payload.profile);
|
||||
setText("#walletCluster", payload.cluster);
|
||||
renderOnchainProfiles(payload.onchain_profiles, payload.profile);
|
||||
renderExecutionProfiles(payload.execution_profiles);
|
||||
renderTransferFormats(payload.transfer_formats);
|
||||
renderExportWalletAliases(payload.wallets);
|
||||
renderExecutionWalletAliases(payload.wallets);
|
||||
setText("#walletCreateSecretStatus", payload.create_password_configured ? "configuré" : "absent");
|
||||
setText("#walletSelectedAlias", payload.selected_alias ?? "—");
|
||||
setText("#walletInventoryCount", String(payload.wallets.length));
|
||||
setText("#walletExportDirectory", `${payload.export_directory}/`);
|
||||
setText("#walletInventoryStatus", "OK");
|
||||
renderWalletRows(payload.wallets);
|
||||
updateExecutionSelectionDisplay();
|
||||
updateExportFileName(false);
|
||||
}
|
||||
|
||||
async function refreshWalletInventory(): Promise<void> {
|
||||
setText("#walletInventoryStatus", "Chargement...");
|
||||
try {
|
||||
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_inventory");
|
||||
applyWalletInventoryPayload(payload);
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", "wallet inventory refreshed");
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
@@ -238,6 +368,7 @@ async function refreshWalletInventory(): Promise<void> {
|
||||
setText("#walletInventoryCount", "0");
|
||||
renderWalletRows([]);
|
||||
renderExportWalletAliases([]);
|
||||
renderExecutionWalletAliases([]);
|
||||
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> {
|
||||
const alias = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.value.trim() ?? "";
|
||||
const format = selectedTransferFormat("#walletExportFormatSelect");
|
||||
const destinationPath = inputValue("#walletExportDestinationPathInput");
|
||||
if (alias.length === 0 || format.length === 0 || destinationPath.length === 0) {
|
||||
setText("#walletExportStatus", "Wallet, format et destination requis");
|
||||
const fileName = inputValue("#walletExportFileNameInput");
|
||||
if (alias.length === 0 || format.length === 0 || fileName.length === 0) {
|
||||
setText("#walletExportStatus", "Wallet, format et nom de fichier requis");
|
||||
return;
|
||||
}
|
||||
const request: DemoWalletExportRequest = { alias, destination_path: destinationPath, format };
|
||||
const request: DemoWalletExportRequest = { alias, file_name: fileName, format };
|
||||
setText("#walletExportStatus", "Export...");
|
||||
try {
|
||||
await invoke<void>("demo_wallet_export_file", { request });
|
||||
setText("#walletExportStatus", `Exporté : ${alias}`);
|
||||
const exportedPath = await invoke<string>("demo_wallet_export_file", { request });
|
||||
setText("#walletExportStatus", `Exporté : ${exportedPath}`);
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `native wallet exported: ${alias}`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
@@ -647,6 +778,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelector<HTMLButtonElement>("#refreshWalletInventoryButton")?.addEventListener("click", () => {
|
||||
void refreshWalletInventory();
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#applyWalletExecutionSelectionButton")?.addEventListener("click", () => {
|
||||
void applyExecutionWalletSelection();
|
||||
});
|
||||
document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.addEventListener("change", () => {
|
||||
updateExecutionSelectionDisplay();
|
||||
if (walletInventory) {
|
||||
renderWalletRows(walletInventory.wallets);
|
||||
}
|
||||
setText("#walletExecutionSelectionStatus", "En attente");
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#createWalletButton")?.addEventListener("click", () => {
|
||||
void createWallet();
|
||||
});
|
||||
@@ -668,6 +809,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelector<HTMLButtonElement>("#exportWalletTransferButton")?.addEventListener("click", () => {
|
||||
void exportWallet();
|
||||
});
|
||||
document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.addEventListener("change", () => {
|
||||
updateExportFileName(false);
|
||||
});
|
||||
document.querySelector<HTMLSelectElement>("#walletExportFormatSelect")?.addEventListener("change", () => {
|
||||
updateExportFileName(false);
|
||||
});
|
||||
document.querySelector<HTMLInputElement>("#walletExportFileNameInput")?.addEventListener("input", event => {
|
||||
const input = event.currentTarget as HTMLInputElement;
|
||||
input.dataset.autoGenerated = "false";
|
||||
});
|
||||
document.querySelector<HTMLInputElement>("#walletImportSourcePathInput")?.addEventListener("input", () => {
|
||||
setTransferPublicKey("");
|
||||
setText("#walletTransferStatus", "En attente");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/app_state.rs
|
||||
// version: 16
|
||||
// version: 17
|
||||
|
||||
//! 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_execution_solana_core_running: 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,
|
||||
}
|
||||
|
||||
@@ -115,6 +117,9 @@ impl crate::AppState {
|
||||
demo_decode_replay_campaign_id: std::sync::Mutex::new(std::option::Option::None),
|
||||
demo_execution_solana_core_running: std::sync::atomic::AtomicBool::new(false),
|
||||
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool::new(false),
|
||||
demo_execution_wallet_alias_overrides: std::sync::Mutex::new(
|
||||
std::collections::BTreeMap::new(),
|
||||
),
|
||||
startup_sequence_started: std::sync::atomic::AtomicBool::new(false),
|
||||
});
|
||||
}
|
||||
@@ -169,6 +174,46 @@ impl crate::AppState {
|
||||
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.
|
||||
pub(crate) fn http_pool(&self) -> &ks_onchain_transport::HttpEndpointPool {
|
||||
return &self.http_pool;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// file: kb-app-demo-desktop/src/demo_devnet_common.rs
|
||||
// version: 4
|
||||
// version: 6
|
||||
|
||||
//! Shared Devnet demo UI contracts.
|
||||
|
||||
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.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -73,3 +75,58 @@ fn bounded_u32(value: usize) -> u32 {
|
||||
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()),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -235,12 +235,16 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_token_metadata(
|
||||
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_metaplex_token_metadata_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&execution_request,
|
||||
decoders.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::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() {
|
||||
configured
|
||||
} else {
|
||||
@@ -1048,7 +1052,8 @@ pub(crate) async fn demo_execution_metadata_metaplex_execute_campaign(
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
@@ -1070,8 +1075,8 @@ pub(crate) async fn demo_execution_metadata_metaplex_execute_campaign(
|
||||
wallet_dir,
|
||||
&http_pool,
|
||||
&store,
|
||||
&state,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
@@ -1133,8 +1138,8 @@ fn execute_campaign_projection<'a, S, O>(
|
||||
wallet_dir: std::path::PathBuf,
|
||||
http_pool: &'a ks_onchain_transport::HttpEndpointPool,
|
||||
store: &'a S,
|
||||
state: &'a crate::AppState,
|
||||
profile: &'a ks_config::ProfileConfig,
|
||||
workspace_root: &'a std::path::Path,
|
||||
observer: &'a O,
|
||||
) -> CampaignProjectionFuture<'a>
|
||||
where
|
||||
@@ -1150,8 +1155,8 @@ where
|
||||
wallet_dir,
|
||||
http_pool,
|
||||
store,
|
||||
state,
|
||||
profile,
|
||||
workspace_root,
|
||||
observer,
|
||||
));
|
||||
}
|
||||
@@ -1161,8 +1166,8 @@ async fn execute_campaign_projection_unboxed<S, O>(
|
||||
wallet_dir: std::path::PathBuf,
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
store: &S,
|
||||
state: &crate::AppState,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<CampaignProjection>
|
||||
where
|
||||
@@ -1174,6 +1179,15 @@ where
|
||||
{
|
||||
let base_options =
|
||||
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 {
|
||||
"create_mint_nft" => {
|
||||
execute_create_mint_projection(
|
||||
@@ -1184,7 +1198,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
@@ -1198,7 +1212,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
@@ -1212,7 +1226,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
@@ -1226,7 +1240,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
@@ -1240,17 +1254,17 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
},
|
||||
"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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&base_options,
|
||||
observer,
|
||||
).await {
|
||||
@@ -1285,11 +1299,11 @@ where
|
||||
},
|
||||
"print_burn" => {
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&base_options,
|
||||
observer,
|
||||
)
|
||||
@@ -1327,11 +1341,11 @@ where
|
||||
ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
|
||||
);
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&options,
|
||||
observer,
|
||||
)
|
||||
@@ -1361,11 +1375,11 @@ where
|
||||
})
|
||||
},
|
||||
"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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&base_options,
|
||||
observer,
|
||||
)
|
||||
@@ -1410,11 +1424,11 @@ where
|
||||
ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
|
||||
);
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&options,
|
||||
observer,
|
||||
)
|
||||
@@ -1465,11 +1479,12 @@ where
|
||||
let options = base_options.clone().with_asset_family(
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&options,
|
||||
observer,
|
||||
)
|
||||
@@ -1513,7 +1528,7 @@ async fn execute_create_mint_projection<S, O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
store: &S,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &ks_pipeline_demo_scenarios::DevnetExecutionWallet,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<CampaignProjection>
|
||||
where
|
||||
@@ -1523,13 +1538,9 @@ where
|
||||
+ Sync,
|
||||
O: ks_pipeline_demo_scenarios::SolanaExecutionObserver,
|
||||
{
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_create_mint_campaign(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&options,
|
||||
observer,
|
||||
let summary =
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_create_mint_campaign_with_wallet(
|
||||
http_pool, store, profile, wallet, &options, observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -170,11 +170,14 @@ pub(crate) async fn demo_execution_metadata_solana_program_execute_campaign(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_solana_program_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_solana_program_metadata_campaign_with_wallet(
|
||||
&http_pool,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&options,
|
||||
&observer,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -96,7 +96,8 @@ pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
@@ -109,11 +110,16 @@ pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
|
||||
app_handle,
|
||||
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,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&options,
|
||||
&observer,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
|
||||
// version: 17
|
||||
// version: 20
|
||||
|
||||
//! Tauri adapter for bounded Solana Core execution on Devnet.
|
||||
|
||||
@@ -509,12 +509,15 @@ pub(crate) async fn demo_execution_solana_core_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_system_transfer(
|
||||
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_system_transfer_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -582,12 +585,15 @@ pub(crate) async fn demo_execution_spl_memo_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_memo(
|
||||
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_memo_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -279,11 +279,16 @@ pub(crate) async fn demo_execution_spl_ata_execute(
|
||||
app_handle,
|
||||
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,
|
||||
&store,
|
||||
&profile,
|
||||
crate::workspace_root_dir().as_path(),
|
||||
&execution_wallet,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -346,7 +351,7 @@ pub(crate) async fn demo_spl_ata_journal(
|
||||
async fn load_profile_wallet(
|
||||
profile: &ks_config::ProfileConfig,
|
||||
) -> 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() {
|
||||
configured
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -223,12 +223,15 @@ pub(crate) async fn demo_execution_spl_token_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token(
|
||||
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_token_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -192,12 +192,15 @@ pub(crate) async fn demo_execution_spl_token_2022_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token_2022(
|
||||
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_token_2022_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&pipeline_request,
|
||||
decoders.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::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() {
|
||||
configured
|
||||
} else {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// file: kb-app-demo-desktop/src/demo_wallet.rs
|
||||
// version: 5
|
||||
// version: 7
|
||||
|
||||
//! 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
|
||||
|
||||
const DEMO_WALLET_PASSWORD_ENV: &str = "KB_SECRET_DEMO_WALLET_PASSWORD";
|
||||
const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
|
||||
|
||||
/// 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,
|
||||
/// Profiles available for explicit read-only on-chain exploration.
|
||||
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.
|
||||
pub(crate) transfer_formats: std::vec::Vec<DemoWalletTransferFormatPayload>,
|
||||
/// Canonical classic SPL Token program identifier used by the explorer.
|
||||
@@ -119,7 +124,7 @@ pub(crate) struct DemoWalletImportRequest {
|
||||
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)]
|
||||
#[ts(
|
||||
export,
|
||||
@@ -128,12 +133,42 @@ pub(crate) struct DemoWalletImportRequest {
|
||||
pub(crate) struct DemoWalletExportRequest {
|
||||
/// Alias of the native wallet to export.
|
||||
pub(crate) alias: std::string::String,
|
||||
/// Explicit destination path created without overwrite.
|
||||
pub(crate) destination_path: std::string::String,
|
||||
/// File name created inside the application-owned wallet export directory.
|
||||
pub(crate) file_name: std::string::String,
|
||||
/// Stable transfer format code selected by the operator.
|
||||
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.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
@@ -226,19 +261,87 @@ pub(crate) async fn demo_wallet_inventory(
|
||||
for handle in handles {
|
||||
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 {
|
||||
profile: profile.name.clone(),
|
||||
cluster: profile.wallet.cluster.clone(),
|
||||
onchain_profiles: onchain_profile_payloads(state),
|
||||
execution_profiles,
|
||||
export_directory: "data/wallets".to_string(),
|
||||
transfer_formats: wallet_transfer_format_payloads(),
|
||||
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(),
|
||||
create_password_configured: std::env::var_os(DEMO_WALLET_PASSWORD_ENV).is_some(),
|
||||
create_password_configured: crate::demo_wallet_password_configured(),
|
||||
selected_alias,
|
||||
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.
|
||||
pub(crate) async fn demo_wallet_create_native(
|
||||
state: &crate::AppState,
|
||||
@@ -248,7 +351,7 @@ pub(crate) async fn demo_wallet_create_native(
|
||||
std::result::Result::Ok(value) => value,
|
||||
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::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::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::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(
|
||||
state: &crate::AppState,
|
||||
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()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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::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::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::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return match manager
|
||||
.export_file(&alias, password, std::path::PathBuf::from(request.destination_path), format)
|
||||
.await
|
||||
let destination = export_directory.join(file_name.as_str());
|
||||
if let std::result::Result::Err(error) =
|
||||
manager.export_file(&alias, password, destination, format).await
|
||||
{
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return 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.
|
||||
@@ -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> {
|
||||
let mut formats = std::vec::Vec::new();
|
||||
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"));
|
||||
}
|
||||
|
||||
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>(
|
||||
state: &'a crate::AppState,
|
||||
profile_name: &str,
|
||||
@@ -676,14 +901,19 @@ mod tests {
|
||||
"importWalletTransferButton",
|
||||
"walletExportAliasSelect",
|
||||
"walletExportFormatSelect",
|
||||
"walletExportDestinationPathInput",
|
||||
"walletExportFileNameInput",
|
||||
"walletExportDirectory",
|
||||
"exportWalletTransferButton",
|
||||
"walletExecutionProfileSelect",
|
||||
"walletExecutionAliasSelect",
|
||||
"applyWalletExecutionSelectionButton",
|
||||
"walletExternalPathInput",
|
||||
"inspectWalletFileButton",
|
||||
"walletInspectionJson",
|
||||
] {
|
||||
assert!(html.contains(required));
|
||||
}
|
||||
assert!(script.contains("demo_wallet_select_execution_wallet"));
|
||||
assert!(script.contains("demo_wallet_create_native"));
|
||||
assert!(script.contains("demo_wallet_inspect_transfer_file"));
|
||||
assert!(script.contains("demo_wallet_import_file"));
|
||||
@@ -700,6 +930,7 @@ mod tests {
|
||||
assert!(main_html.contains("openDemoWalletLink"));
|
||||
assert!(main_script.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_inspect_transfer_file"));
|
||||
assert!(tauri_runtime.contains("demo_wallet_import_file"));
|
||||
@@ -721,6 +952,13 @@ mod tests {
|
||||
cluster: "devnet".to_string(),
|
||||
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(),
|
||||
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(),
|
||||
@@ -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]
|
||||
fn wallet_rpc_surface_is_read_only_and_bounded() {
|
||||
for allowed in ["getTokenAccountsByOwner", "getSignaturesForAddress", "getTransaction"] {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/lib.rs
|
||||
// version: 41
|
||||
// version: 43
|
||||
|
||||
//! 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;
|
||||
/// Restores the shared single-execution state flag when a Devnet run finishes.
|
||||
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.
|
||||
pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload;
|
||||
/// Serializes a diagnostic value to indented JSON without panicking.
|
||||
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.
|
||||
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataObserver;
|
||||
/// 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;
|
||||
/// Request to create one native wallet from the backend-only demo secret.
|
||||
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.
|
||||
pub(crate) use self::demo_wallet::DemoWalletExportRequest;
|
||||
/// 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;
|
||||
/// Executes one bounded read-only wallet explorer RPC through an explicitly selected profile.
|
||||
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.
|
||||
pub(crate) use self::demo_ws::DemoWsEndpointPayload;
|
||||
/// WebSocket execution response payload.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/tauri.rs
|
||||
// version: 43
|
||||
// version: 44
|
||||
|
||||
//! Tauri runtime assembly and private command wrappers.
|
||||
|
||||
@@ -38,6 +38,7 @@ pub fn run() -> ks_core::Result<()> {
|
||||
load_demo_config,
|
||||
open_demo_wallet_window,
|
||||
demo_wallet_inventory,
|
||||
demo_wallet_select_execution_wallet,
|
||||
demo_wallet_create_native,
|
||||
demo_wallet_inspect_transfer_file,
|
||||
demo_wallet_import_file,
|
||||
@@ -258,6 +259,14 @@ async fn demo_wallet_inventory(
|
||||
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]
|
||||
async fn demo_wallet_create_native(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
@@ -285,7 +294,7 @@ async fn demo_wallet_import_file(
|
||||
async fn demo_wallet_export_file(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
<!-- file: ks-config/CHANGELOG.md -->
|
||||
<!-- version: 21 -->
|
||||
<!-- version: 24 -->
|
||||
|
||||
# CHANGELOG — ks-config
|
||||
|
||||
## `0.5.2-pre.006-delta-fix-007`
|
||||
|
||||
- corrige le test de séparation des répertoires wallet afin qu’il résolve d’abord `${KS_WALLETS_DIRECTORY:-wallets}` comme le chemin de chargement runtime ;
|
||||
- rend le test indépendant d’un éventuel override `KS_WALLETS_DIRECTORY` en vérifiant la relation `temporary_wallet_dir = wallet_dir/temporary/local_devnet` plutôt qu’une 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`
|
||||
|
||||
- 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` ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ks-config/USAGE.md -->
|
||||
<!-- version: 11 -->
|
||||
<!-- version: 12 -->
|
||||
|
||||
# Utilisation de ks-config
|
||||
|
||||
@@ -92,9 +92,9 @@ Les endpoints WebSocket source sélectionnent une classe `ws_endpoint_defaults`;
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/settings.rs
|
||||
// version: 24
|
||||
// version: 25
|
||||
|
||||
//! Resolved runtime configuration models retained during the `0.5.1` source-document split.
|
||||
|
||||
@@ -222,8 +222,10 @@ pub struct AccountListenerConfig {
|
||||
/// Wallet configuration.
|
||||
#[derive(Clone, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct WalletConfig {
|
||||
/// Wallet directory path.
|
||||
/// Persistent native wallet directory path.
|
||||
pub wallet_dir: std::string::String,
|
||||
/// Profile-specific temporary wallet and fixture directory path.
|
||||
pub temporary_wallet_dir: std::string::String,
|
||||
/// Selected cluster name.
|
||||
pub cluster: std::string::String,
|
||||
/// 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::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") {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-config/src/wallet.rs
|
||||
// version: 4
|
||||
// version: 6
|
||||
|
||||
//! Independent wallet configuration document loading and validation.
|
||||
|
||||
@@ -21,7 +21,7 @@ pub struct WalletConfigDocument {
|
||||
pub struct WalletProfileConfig {
|
||||
/// Profile code.
|
||||
pub name: std::string::String,
|
||||
/// Wallet subdirectory or absolute directory.
|
||||
/// Temporary wallet/fixture subdirectory or absolute directory.
|
||||
pub directory: std::string::String,
|
||||
/// Selected cluster name.
|
||||
pub cluster: std::string::String,
|
||||
@@ -143,14 +143,16 @@ pub fn resolved_wallet_profile(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let configured = std::path::PathBuf::from(profile.directory.as_str());
|
||||
let wallet_dir = if configured.is_absolute() {
|
||||
configured
|
||||
let persistent_wallet_dir = std::path::PathBuf::from(document.wallets_directory.as_str());
|
||||
let configured_temporary = std::path::PathBuf::from(profile.directory.as_str());
|
||||
let temporary_wallet_dir = if configured_temporary.is_absolute() {
|
||||
configured_temporary
|
||||
} else {
|
||||
std::path::PathBuf::from(document.wallets_directory.as_str()).join(configured)
|
||||
persistent_wallet_dir.join(configured_temporary)
|
||||
};
|
||||
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(),
|
||||
wallet_alias: profile.wallet_alias.clone(),
|
||||
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]
|
||||
fn wallet_profile_without_persistent_alias_remains_backward_compatible() {
|
||||
let mut value = match serde_json::from_str::<serde_json::Value>(DEFAULT_WALLET) {
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
<!-- file: ks-pipeline-demo-scenarios/CHANGELOG.md -->
|
||||
<!-- version: 79 -->
|
||||
<!-- version: 81 -->
|
||||
|
||||
# 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`
|
||||
|
||||
- ajoute l'inspection et l'unlock explicites du wallet natif sélectionné par `profile.wallet.wallet_alias`, avec `WalletPassword` fourni uniquement au runtime ;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- file: ks-pipeline-demo-scenarios/TODO.md -->
|
||||
<!-- version: 44 -->
|
||||
<!-- version: 45 -->
|
||||
|
||||
# TODO — ks-pipeline-demo-scenarios
|
||||
|
||||
## É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` — 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ks-pipeline-demo-scenarios/USAGE.md -->
|
||||
<!-- version: 32 -->
|
||||
<!-- version: 33 -->
|
||||
|
||||
# Utilisation de ks-pipeline-demo-scenarios
|
||||
|
||||
@@ -312,7 +312,7 @@ cargo test -p ks-pipeline-demo-scenarios optional_devnet_current_operation_from_
|
||||
|
||||
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 l’hydratation canonique, l’extraction Core, le replay et la vérification d’idempotence après une soumission ;
|
||||
- `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 ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/lib.rs
|
||||
// version: 30
|
||||
// version: 31
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -12,6 +12,7 @@ mod environment;
|
||||
mod metadata;
|
||||
mod solana;
|
||||
mod spl;
|
||||
mod wallet;
|
||||
|
||||
/// Readiness report for one Devnet profile PostgreSQL store.
|
||||
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;
|
||||
/// 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;
|
||||
/// 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.
|
||||
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.
|
||||
@@ -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;
|
||||
/// 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;
|
||||
/// 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.
|
||||
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.
|
||||
@@ -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;
|
||||
/// 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;
|
||||
/// 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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
pub use self::metadata::metaplex_token_metadata::escrow_campaign::DevnetMetaplexEscrowCampaignState;
|
||||
/// Complete evidence retained by one Token Owned Escrow Devnet campaign.
|
||||
pub use self::metadata::metaplex_token_metadata::escrow_campaign::DevnetMetaplexEscrowCampaignSummary;
|
||||
/// Executes a fresh Token Owned Escrow lifecycle on Devnet.
|
||||
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.
|
||||
pub use self::metadata::metaplex_token_metadata::escrow_campaign::metaplex_escrow_campaign_operation_names;
|
||||
/// 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;
|
||||
/// Creates or reuses a classic SPL mint and derives its Metaplex PDAs.
|
||||
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.
|
||||
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.
|
||||
@@ -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;
|
||||
/// Executes the final bounded current maintenance campaign on Devnet.
|
||||
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.
|
||||
pub use self::metadata::metaplex_token_metadata::maintenance_campaign::metaplex_maintenance_campaign_operation_names;
|
||||
/// 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;
|
||||
/// 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;
|
||||
/// 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.
|
||||
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.
|
||||
@@ -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;
|
||||
/// 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;
|
||||
/// 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.
|
||||
pub use self::metadata::metaplex_token_metadata::print_burn_campaign::metaplex_print_burn_campaign_operation_names;
|
||||
/// 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;
|
||||
/// Executes one bounded current `Use` availability probe on Devnet.
|
||||
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.
|
||||
pub use self::metadata::metaplex_token_metadata::use_campaign::metaplex_use_probe_operation_names;
|
||||
/// 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;
|
||||
/// Prepares fresh accounts and executes the nine stable operations on Devnet.
|
||||
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.
|
||||
pub use self::metadata::solana_program::devnet_execution::DevnetSolanaProgramMetadataExecutionRequest;
|
||||
/// Complete evidence produced by one Solana Program Metadata Devnet execution.
|
||||
pub use self::metadata::solana_program::devnet_execution::DevnetSolanaProgramMetadataExecutionSummary;
|
||||
/// Executes one Solana Program Metadata simulation or authorized submission.
|
||||
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.
|
||||
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`.
|
||||
pub use self::metadata::solana_program::fixture::SOLANA_PROGRAM_METADATA_FIXTURE_BUFFER_DATA_BYTES;
|
||||
/// 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;
|
||||
/// Creates two unique pre-funded PDAs and the nine typed journey steps.
|
||||
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.
|
||||
pub use self::metadata::solana_program::scenarios::SolanaProgramMetadataFixtureState;
|
||||
/// 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;
|
||||
/// Executes one bounded System Program transfer on Devnet.
|
||||
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.
|
||||
pub use self::solana::inspect_selected_profile_wallet;
|
||||
/// 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;
|
||||
/// Executes one Devnet Associated Token Account simulation or authorized submission.
|
||||
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.
|
||||
pub use self::spl::associated_token_account::simulate_devnet_spl_associated_token_account;
|
||||
/// 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;
|
||||
/// Executes one SPL Memo v4 Devnet simulation or explicitly authorized submission.
|
||||
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.
|
||||
pub use self::spl::token::execution::DevnetSplTokenExecutionRequest;
|
||||
/// 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;
|
||||
/// Executes one Devnet classic SPL Token simulation or authorized submission.
|
||||
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.
|
||||
pub(crate) use self::spl::token::execution::hydrate_signature;
|
||||
/// 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;
|
||||
/// Executes one Devnet Token-2022 simulation or authorized submission.
|
||||
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.
|
||||
pub use self::spl::token_2022::devnet_execution::simulate_devnet_spl_token_2022;
|
||||
/// 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;
|
||||
/// 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;
|
||||
/// 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.
|
||||
pub use self::spl::token_2022::metadata::campaign::token_2022_metadata_campaign_operation_names;
|
||||
/// 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;
|
||||
/// 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;
|
||||
/// 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.
|
||||
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.
|
||||
@@ -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;
|
||||
/// Validates one bounded Token-2022 milestone 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.
|
||||
pub(crate) use self::constants::SOLANA_PROGRAM_METADATA_CAMPAIGN_TRANSACTION_COUNT;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -50,6 +50,34 @@ pub async fn execute_devnet_metaplex_collection_verify_campaign<S, O>(
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -65,11 +93,11 @@ where
|
||||
let mut parent_options = options.clone();
|
||||
parent_options.asset_family = crate::MetaplexTokenMetadataAssetFamily::Collection;
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&parent_options,
|
||||
observer,
|
||||
)
|
||||
@@ -81,11 +109,11 @@ where
|
||||
let mut member_options = options.clone();
|
||||
member_options.asset_family = crate::MetaplexTokenMetadataAssetFamily::Nft;
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&member_options,
|
||||
observer,
|
||||
)
|
||||
@@ -168,11 +196,11 @@ where
|
||||
options.query_role.as_str(),
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&verify_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -240,11 +268,11 @@ where
|
||||
options.query_role.as_str(),
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&unverify_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -667,7 +695,8 @@ mod tests {
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
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() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -52,6 +52,34 @@ pub async fn execute_devnet_metaplex_create_mint_campaign<S, O>(
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -64,8 +92,9 @@ where
|
||||
"Metaplex Create -> Mint Devnet campaign requires devnet_send_enabled=true",
|
||||
));
|
||||
}
|
||||
let fixture =
|
||||
match crate::prepare_metaplex_create_fixture(http_pool, profile, workspace_root, options)
|
||||
let fixture = match crate::prepare_metaplex_create_fixture_with_wallet(
|
||||
http_pool, profile, wallet, options,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -100,11 +129,11 @@ where
|
||||
std::option::Option::None,
|
||||
false,
|
||||
);
|
||||
let create = match crate::execute_devnet_metaplex_token_metadata(
|
||||
let create = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&create_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -239,11 +268,11 @@ where
|
||||
std::option::Option::Some(create_slot),
|
||||
true,
|
||||
);
|
||||
let mint = match crate::execute_devnet_metaplex_token_metadata(
|
||||
let mint = match crate::execute_devnet_metaplex_token_metadata_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&mint_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -832,7 +861,8 @@ mod tests {
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
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() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -363,7 +363,6 @@ pub struct DevnetMetaplexTokenMetadataExecutionSummary {
|
||||
}
|
||||
|
||||
struct PreparedMetaplexExecution {
|
||||
wallet: ks_wallet::TemporaryWallet,
|
||||
unsigned: ks_lib::ExSolanaUnsignedTransaction,
|
||||
evidence: ks_lib::ExSolanaSimulationEvidence,
|
||||
summary: crate::DevnetMetaplexTokenMetadataExecutionSummary,
|
||||
@@ -377,6 +376,28 @@ pub async fn simulate_devnet_metaplex_token_metadata<O>(
|
||||
request: &crate::DevnetMetaplexTokenMetadataExecutionRequest,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
O: crate::SolanaExecutionObserver,
|
||||
{
|
||||
@@ -385,8 +406,8 @@ where
|
||||
"simulate_devnet_metaplex_token_metadata requires submit=false",
|
||||
));
|
||||
}
|
||||
let prepared =
|
||||
match prepare_execution(http_pool, profile, workspace_root, request, &[], observer).await {
|
||||
let prepared = match prepare_execution(http_pool, profile, wallet, request, &[], observer).await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -412,11 +433,48 @@ where
|
||||
+ Sync,
|
||||
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,
|
||||
store,
|
||||
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,
|
||||
decoders,
|
||||
materializers,
|
||||
@@ -446,14 +504,47 @@ where
|
||||
+ Sync,
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&wallet,
|
||||
request,
|
||||
decoders,
|
||||
materializers,
|
||||
additional_signers,
|
||||
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
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -483,7 +574,7 @@ where
|
||||
let signed = match sign_prepared_transaction(
|
||||
prepared.unsigned,
|
||||
&prepared.evidence,
|
||||
prepared.wallet.as_sync_signer(),
|
||||
wallet.as_sync_signer(),
|
||||
additional_signers,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -805,7 +896,7 @@ fn replay_summary_diagnostic(
|
||||
async fn prepare_execution<O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
request: &crate::DevnetMetaplexTokenMetadataExecutionRequest,
|
||||
additional_signers: &[&(dyn solana_signer::Signer + std::marker::Sync)],
|
||||
observer: &O,
|
||||
@@ -839,11 +930,7 @@ where
|
||||
),
|
||||
));
|
||||
}
|
||||
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),
|
||||
};
|
||||
let wallet_summary = wallet.summary();
|
||||
let wallet_summary = wallet.identity();
|
||||
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
|
||||
let balance = match http_pool
|
||||
.get_balance_for_role(
|
||||
@@ -1000,7 +1087,6 @@ where
|
||||
};
|
||||
let evidence = unsigned.bind_simulation(simulation.clone());
|
||||
return std::result::Result::Ok(PreparedMetaplexExecution {
|
||||
wallet,
|
||||
unsigned,
|
||||
evidence,
|
||||
summary: crate::DevnetMetaplexTokenMetadataExecutionSummary {
|
||||
@@ -1175,9 +1261,11 @@ fn validate_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(
|
||||
"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 {
|
||||
@@ -1416,7 +1504,7 @@ mod tests {
|
||||
};
|
||||
let mut profile = example_devnet_profile();
|
||||
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) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -64,6 +64,34 @@ pub async fn execute_devnet_metaplex_escrow_campaign<S, O>(
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -80,11 +108,11 @@ where
|
||||
clean_fixture_options(options, crate::MetaplexTokenMetadataAssetFamily::Nft);
|
||||
let attribute_options =
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&parent_options,
|
||||
observer,
|
||||
)
|
||||
@@ -93,11 +121,11 @@ where
|
||||
std::result::Result::Ok(value) => value,
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&attribute_options,
|
||||
observer,
|
||||
)
|
||||
@@ -148,11 +176,11 @@ where
|
||||
parent_options.query_role.as_str(),
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&create_request,
|
||||
metaplex_decoders.as_slice(),
|
||||
metaplex_materializers.as_slice(),
|
||||
@@ -195,11 +223,12 @@ where
|
||||
ata_request.submit = true;
|
||||
ata_request.operator_confirmed = true;
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&ata_request,
|
||||
ata_decoders.as_slice(),
|
||||
ata_materializers.as_slice(),
|
||||
@@ -271,11 +300,11 @@ where
|
||||
deposit_request.submit = true;
|
||||
deposit_request.operator_confirmed = true;
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&deposit_request,
|
||||
token_decoders.as_slice(),
|
||||
token_materializers.as_slice(),
|
||||
@@ -357,11 +386,11 @@ where
|
||||
parent_options.query_role.as_str(),
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&transfer_request,
|
||||
metaplex_decoders.as_slice(),
|
||||
metaplex_materializers.as_slice(),
|
||||
@@ -443,11 +472,11 @@ where
|
||||
parent_options.query_role.as_str(),
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&close_request,
|
||||
metaplex_decoders.as_slice(),
|
||||
metaplex_materializers.as_slice(),
|
||||
@@ -992,7 +1021,8 @@ mod tests {
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
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() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -114,6 +114,21 @@ pub async fn prepare_metaplex_create_fixture(
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
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> {
|
||||
if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
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_store = match ks_wallet::TemporaryWalletStore::new(fixture_dir.clone()) {
|
||||
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),
|
||||
};
|
||||
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_decimals = fixture_mint_decimals(options.asset_family);
|
||||
let existing =
|
||||
@@ -216,7 +227,7 @@ pub async fn prepare_metaplex_create_fixture(
|
||||
http_pool,
|
||||
profile,
|
||||
options,
|
||||
&operator,
|
||||
wallet,
|
||||
&mint_wallet,
|
||||
mint_decimals,
|
||||
)
|
||||
@@ -404,7 +415,7 @@ async fn create_native_classic_mint(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
operator: &ks_wallet::TemporaryWallet,
|
||||
operator: &crate::DevnetExecutionWallet,
|
||||
mint_wallet: &ks_wallet::TemporaryWallet,
|
||||
mint_decimals: u8,
|
||||
) -> ks_core::Result<NativeFixturePreparationEvidence> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -42,6 +42,34 @@ pub async fn execute_devnet_metaplex_maintenance_campaign<S, O>(
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -54,13 +82,8 @@ where
|
||||
"Metaplex maintenance campaign requires the classic NFT fixture family",
|
||||
));
|
||||
}
|
||||
let fixture = match crate::execute_devnet_metaplex_create_mint_campaign(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
options,
|
||||
observer,
|
||||
let fixture = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
|
||||
http_pool, store, profile, wallet, options, observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -91,7 +114,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
observer,
|
||||
)
|
||||
@@ -130,7 +153,7 @@ where
|
||||
201,
|
||||
http_pool,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
observer,
|
||||
)
|
||||
@@ -145,7 +168,7 @@ where
|
||||
75,
|
||||
http_pool,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
observer,
|
||||
)
|
||||
@@ -163,7 +186,7 @@ where
|
||||
7,
|
||||
http_pool,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
observer,
|
||||
)
|
||||
@@ -184,7 +207,7 @@ where
|
||||
188,
|
||||
http_pool,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
observer,
|
||||
)
|
||||
@@ -272,7 +295,7 @@ async fn execute_confirmed_operation<S, O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
store: &S,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary>
|
||||
@@ -304,11 +327,11 @@ where
|
||||
request.operator_confirmed = true;
|
||||
request.materialize_after_confirmation = true;
|
||||
request.post_validation_max_retries = 20;
|
||||
return crate::execute_devnet_metaplex_token_metadata(
|
||||
return crate::execute_devnet_metaplex_token_metadata_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -324,7 +347,7 @@ async fn simulate_expected_unavailable<O>(
|
||||
expected_custom_error: u64,
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<crate::DevnetMetaplexTokenMetadataExecutionSummary>
|
||||
@@ -337,12 +360,8 @@ where
|
||||
);
|
||||
request.query_role = options.query_role.clone();
|
||||
request.transaction_role = options.transaction_role.clone();
|
||||
let summary = match crate::simulate_devnet_metaplex_token_metadata(
|
||||
http_pool,
|
||||
profile,
|
||||
workspace_root,
|
||||
&request,
|
||||
observer,
|
||||
let summary = match crate::simulate_devnet_metaplex_token_metadata_with_wallet(
|
||||
http_pool, profile, wallet, &request, observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -596,7 +615,8 @@ mod tests {
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
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() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -76,6 +76,34 @@ pub async fn execute_devnet_metaplex_pnft_lifecycle_campaign<S, O>(
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -88,13 +116,8 @@ where
|
||||
"Metaplex pNFT lifecycle campaign requires asset_family=programmable_nft",
|
||||
));
|
||||
}
|
||||
let pnft = match crate::execute_devnet_metaplex_create_mint_campaign(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
options,
|
||||
observer,
|
||||
let pnft = match crate::execute_devnet_metaplex_create_mint_campaign_with_wallet(
|
||||
http_pool, store, profile, wallet, options, observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -205,7 +228,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
"delegate-staking",
|
||||
delegate_staking_operation,
|
||||
@@ -261,7 +284,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
"lock",
|
||||
lock_operation,
|
||||
@@ -322,7 +345,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
"unlock",
|
||||
unlock_operation,
|
||||
@@ -378,7 +401,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
"revoke-staking",
|
||||
revoke_operation,
|
||||
@@ -436,7 +459,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
"delegate-transfer",
|
||||
delegate_transfer_operation,
|
||||
@@ -495,7 +518,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
options,
|
||||
transfer_operation,
|
||||
destination.1.as_str(),
|
||||
@@ -631,7 +654,7 @@ async fn execute_profile_step<S, O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
store: &S,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
label: &str,
|
||||
operation: ks_lib::ExMetaplexTokenMetadataOperation,
|
||||
@@ -654,11 +677,11 @@ where
|
||||
request.operator_confirmed = true;
|
||||
request.materialize_after_confirmation = true;
|
||||
request.post_validation_max_retries = 20;
|
||||
return crate::execute_devnet_metaplex_token_metadata(
|
||||
return crate::execute_devnet_metaplex_token_metadata_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&request,
|
||||
decoders,
|
||||
materializers,
|
||||
@@ -672,7 +695,7 @@ async fn execute_delegate_step<S, O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
store: &S,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
label: &str,
|
||||
operation: ks_lib::ExMetaplexTokenMetadataOperation,
|
||||
@@ -698,11 +721,11 @@ where
|
||||
request.post_validation_max_retries = 20;
|
||||
request.additional_authorized_signers =
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&request,
|
||||
decoders,
|
||||
materializers,
|
||||
@@ -717,7 +740,7 @@ async fn execute_delegate_transfer_step<S, O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
store: &S,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
operation: ks_lib::ExMetaplexTokenMetadataOperation,
|
||||
destination_token_record: &str,
|
||||
@@ -749,11 +772,11 @@ where
|
||||
request.post_validation_max_retries = 20;
|
||||
request.additional_authorized_signers =
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&request,
|
||||
decoders,
|
||||
materializers,
|
||||
@@ -1090,7 +1113,8 @@ mod tests {
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
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() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -84,6 +84,34 @@ pub async fn execute_devnet_metaplex_print_burn_campaign<S, O>(
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -100,11 +128,11 @@ where
|
||||
master_options.asset_family = crate::MetaplexTokenMetadataAssetFamily::Nft;
|
||||
master_options.collection_mint = std::option::Option::None;
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&master_options,
|
||||
observer,
|
||||
)
|
||||
@@ -329,11 +357,11 @@ where
|
||||
options.query_role.as_str(),
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&print_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -498,11 +526,11 @@ where
|
||||
options.query_role.as_str(),
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&burn_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -1199,7 +1227,8 @@ mod tests {
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
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() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -46,6 +46,34 @@ pub async fn execute_devnet_metaplex_use_probe<S, O>(
|
||||
options: &crate::MetaplexCreateFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -59,11 +87,11 @@ where
|
||||
));
|
||||
}
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&fixture_options,
|
||||
observer,
|
||||
)
|
||||
@@ -93,10 +121,10 @@ where
|
||||
);
|
||||
simulation_request.query_role = fixture_options.query_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,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&simulation_request,
|
||||
observer,
|
||||
)
|
||||
@@ -139,11 +167,11 @@ where
|
||||
execution_request.post_validation_max_retries = 20;
|
||||
execution_request.postcondition_reads =
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&execution_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -444,7 +472,8 @@ mod tests {
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
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() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -36,6 +36,28 @@ pub async fn execute_devnet_solana_program_metadata_campaign<O>(
|
||||
options: &crate::SolanaProgramMetadataFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
O: crate::SolanaExecutionObserver,
|
||||
{
|
||||
@@ -44,11 +66,8 @@ where
|
||||
"Solana Program Metadata Devnet campaign requires explicit operator confirmation",
|
||||
));
|
||||
}
|
||||
let fixture = match crate::prepare_solana_program_metadata_fixture(
|
||||
http_pool,
|
||||
profile,
|
||||
workspace_root,
|
||||
options,
|
||||
let fixture = match crate::prepare_solana_program_metadata_fixture_with_wallet(
|
||||
http_pool, profile, wallet, options,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -63,12 +82,8 @@ where
|
||||
request.transaction_role = options.transaction_role.clone();
|
||||
request.submit = true;
|
||||
request.operator_confirmed = true;
|
||||
let execution = match crate::execute_devnet_solana_program_metadata(
|
||||
http_pool,
|
||||
profile,
|
||||
workspace_root,
|
||||
&request,
|
||||
observer,
|
||||
let execution = match crate::execute_devnet_solana_program_metadata_with_wallet(
|
||||
http_pool, profile, wallet, &request, observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -155,7 +155,6 @@ pub struct DevnetSolanaProgramMetadataExecutionSummary {
|
||||
}
|
||||
|
||||
struct PreparedSolanaProgramMetadataExecution {
|
||||
wallet: ks_wallet::TemporaryWallet,
|
||||
unsigned: ks_lib::ExSolanaUnsignedTransaction,
|
||||
evidence: ks_lib::ExSolanaSimulationEvidence,
|
||||
summary: crate::DevnetSolanaProgramMetadataExecutionSummary,
|
||||
@@ -169,6 +168,28 @@ pub async fn simulate_devnet_solana_program_metadata<O>(
|
||||
request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
O: crate::SolanaExecutionObserver,
|
||||
{
|
||||
@@ -177,8 +198,7 @@ where
|
||||
"simulate_devnet_solana_program_metadata requires submit=false",
|
||||
));
|
||||
}
|
||||
let prepared =
|
||||
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
||||
let prepared = match prepare_execution(http_pool, profile, wallet, request, observer).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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
|
||||
O: crate::SolanaExecutionObserver,
|
||||
{
|
||||
let prepared =
|
||||
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
||||
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_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::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -230,7 +271,7 @@ where
|
||||
}
|
||||
let signed = match prepared
|
||||
.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::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -322,7 +363,7 @@ where
|
||||
async fn prepare_execution<O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
request: &crate::DevnetSolanaProgramMetadataExecutionRequest,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<PreparedSolanaProgramMetadataExecution>
|
||||
@@ -355,11 +396,7 @@ where
|
||||
),
|
||||
));
|
||||
}
|
||||
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),
|
||||
};
|
||||
let wallet_summary = wallet.summary();
|
||||
let wallet_summary = wallet.identity();
|
||||
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
|
||||
let balance = match http_pool
|
||||
.get_balance_for_role(
|
||||
@@ -518,7 +555,6 @@ where
|
||||
};
|
||||
let evidence = unsigned.bind_simulation(simulation.clone());
|
||||
return std::result::Result::Ok(PreparedSolanaProgramMetadataExecution {
|
||||
wallet,
|
||||
unsigned,
|
||||
evidence,
|
||||
summary: crate::DevnetSolanaProgramMetadataExecutionSummary {
|
||||
@@ -625,9 +661,11 @@ fn validate_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(
|
||||
"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 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/metadata/solana_program/fixture.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
//! 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,
|
||||
workspace_root: &std::path::Path,
|
||||
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> {
|
||||
if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() {
|
||||
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"
|
||||
|| !profile.execution.devnet_send_enabled
|
||||
|| !profile.wallet.temporary_wallet_enabled
|
||||
|| !profile.wallet.temporary_wallet_persist
|
||||
|| (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(
|
||||
"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",
|
||||
));
|
||||
}
|
||||
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 described_program = ks_lib::MdPubkey(ks_program_ids::SYSTEM_PROGRAM_ID.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,
|
||||
profile,
|
||||
options,
|
||||
&operator,
|
||||
operator,
|
||||
buffer.clone(),
|
||||
buffer_prefund_lamports,
|
||||
"buffer",
|
||||
@@ -296,7 +311,7 @@ pub async fn prepare_solana_program_metadata_fixture(
|
||||
http_pool,
|
||||
profile,
|
||||
options,
|
||||
&operator,
|
||||
operator,
|
||||
metadata.clone(),
|
||||
metadata_prefund_lamports,
|
||||
"metadata",
|
||||
@@ -680,7 +695,7 @@ async fn submit_prefund_transfer(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
options: &crate::SolanaProgramMetadataFixturePreparationOptions,
|
||||
operator: &ks_wallet::TemporaryWallet,
|
||||
operator: &crate::DevnetExecutionWallet,
|
||||
recipient: ks_lib::MdPubkey,
|
||||
lamports: u64,
|
||||
label: &str,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/solana.rs
|
||||
// version: 17
|
||||
// version: 18
|
||||
|
||||
//! 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>],
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -305,11 +342,7 @@ where
|
||||
if let std::result::Result::Err(error) = cancellation_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let wallet = match load_profile_temporary_wallet(profile, workspace_root).await {
|
||||
std::result::Result::Ok(wallet) => wallet,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let wallet_summary = wallet.summary();
|
||||
let wallet_summary = wallet.identity();
|
||||
let source_pubkey = ks_lib::MdPubkey(wallet_summary.public_key.clone());
|
||||
if source_pubkey == request.recipient {
|
||||
return std::result::Result::Err(ks_core::Error::config(
|
||||
@@ -945,9 +978,11 @@ fn validate_devnet_profile(
|
||||
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(
|
||||
"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 {
|
||||
@@ -983,7 +1018,7 @@ fn validate_devnet_profile(
|
||||
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(
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
@@ -995,6 +1030,18 @@ fn profile_wallet_directory(
|
||||
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.
|
||||
pub async fn inspect_selected_profile_wallet(
|
||||
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",
|
||||
));
|
||||
}
|
||||
let store = match ks_wallet::TemporaryWalletStore::new(profile_wallet_directory(
|
||||
let store = match ks_wallet::TemporaryWalletStore::new(profile_temporary_wallet_directory(
|
||||
profile,
|
||||
workspace_root,
|
||||
)) {
|
||||
@@ -1500,7 +1547,7 @@ mod tests {
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
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) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs
|
||||
// version: 16
|
||||
// version: 17
|
||||
|
||||
//! Devnet ATA execution with stateful and canonical post-validation.
|
||||
|
||||
@@ -108,7 +108,6 @@ pub struct DevnetSplAssociatedTokenAccountExecutionSummary {
|
||||
}
|
||||
|
||||
struct PreparedAtaExecution {
|
||||
wallet: ks_wallet::TemporaryWallet,
|
||||
unsigned: ks_lib::ExSolanaUnsignedTransaction,
|
||||
evidence: ks_lib::ExSolanaSimulationEvidence,
|
||||
summary: DevnetSplAssociatedTokenAccountExecutionSummary,
|
||||
@@ -130,8 +129,12 @@ where
|
||||
"simulate_devnet_spl_associated_token_account requires submit=false",
|
||||
));
|
||||
}
|
||||
let prepared =
|
||||
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
||||
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);
|
||||
let prepared = match prepare_execution(http_pool, profile, &wallet, request, observer).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -157,8 +160,44 @@ where
|
||||
+ Sync,
|
||||
O: crate::SolanaExecutionObserver,
|
||||
{
|
||||
let prepared =
|
||||
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
||||
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_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::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -191,7 +230,7 @@ where
|
||||
}
|
||||
let signed = match prepared
|
||||
.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::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -460,7 +499,7 @@ where
|
||||
async fn prepare_execution<O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
request: &DevnetSplAssociatedTokenAccountExecutionRequest,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<PreparedAtaExecution>
|
||||
@@ -491,11 +530,7 @@ where
|
||||
),
|
||||
));
|
||||
}
|
||||
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),
|
||||
};
|
||||
let wallet_summary = wallet.summary();
|
||||
let wallet_summary = wallet.identity();
|
||||
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
|
||||
let balance = match http_pool
|
||||
.get_balance_for_role(
|
||||
@@ -664,7 +699,7 @@ where
|
||||
materializations: std::vec::Vec::new(),
|
||||
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(
|
||||
@@ -676,9 +711,11 @@ fn validate_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(
|
||||
"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 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/spl/memo.rs
|
||||
// version: 12
|
||||
// version: 13
|
||||
|
||||
//! 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>],
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -155,11 +192,7 @@ where
|
||||
),
|
||||
));
|
||||
}
|
||||
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),
|
||||
};
|
||||
let wallet_summary = wallet.summary();
|
||||
let wallet_summary = wallet.identity();
|
||||
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
|
||||
let balance = match http_pool
|
||||
.get_balance_for_role(
|
||||
@@ -534,9 +567,11 @@ fn validate_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(
|
||||
"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 {
|
||||
@@ -825,7 +860,7 @@ mod tests {
|
||||
profile.database.backend = "postgres".to_string();
|
||||
profile.database.postgres.url = database_url;
|
||||
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) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -105,7 +105,6 @@ pub struct DevnetSplTokenExecutionSummary {
|
||||
}
|
||||
|
||||
struct PreparedTokenExecution {
|
||||
wallet: ks_wallet::TemporaryWallet,
|
||||
unsigned: ks_lib::ExSolanaUnsignedTransaction,
|
||||
evidence: ks_lib::ExSolanaSimulationEvidence,
|
||||
summary: crate::DevnetSplTokenExecutionSummary,
|
||||
@@ -127,8 +126,12 @@ where
|
||||
"simulate_devnet_spl_token requires submit=false",
|
||||
));
|
||||
}
|
||||
let prepared =
|
||||
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
||||
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);
|
||||
let prepared = match prepare_execution(http_pool, profile, &wallet, request, observer).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -154,8 +157,44 @@ where
|
||||
+ Sync,
|
||||
O: crate::SolanaExecutionObserver,
|
||||
{
|
||||
let prepared =
|
||||
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
||||
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_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::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -188,7 +227,7 @@ where
|
||||
}
|
||||
let signed = match prepared
|
||||
.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::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -428,7 +467,7 @@ where
|
||||
async fn prepare_execution<O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
request: &crate::DevnetSplTokenExecutionRequest,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<PreparedTokenExecution>
|
||||
@@ -479,11 +518,7 @@ where
|
||||
failed_readiness_message(&readiness),
|
||||
));
|
||||
}
|
||||
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),
|
||||
};
|
||||
let wallet_summary = wallet.summary();
|
||||
let wallet_summary = wallet.identity();
|
||||
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
|
||||
let balance = match http_pool
|
||||
.get_balance_for_role(
|
||||
@@ -620,7 +655,7 @@ where
|
||||
materializations: std::vec::Vec::new(),
|
||||
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(
|
||||
@@ -632,9 +667,11 @@ fn validate_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(
|
||||
"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 {
|
||||
@@ -1093,7 +1130,7 @@ mod tests {
|
||||
};
|
||||
let mut profile = example_devnet_profile();
|
||||
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) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -1263,7 +1300,7 @@ mod tests {
|
||||
};
|
||||
let mut profile = example_devnet_profile();
|
||||
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) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs
|
||||
// version: 15
|
||||
// version: 16
|
||||
|
||||
//! Controlled Devnet lifecycle for freshly prepared classic SPL Token accounts.
|
||||
|
||||
@@ -1512,7 +1512,7 @@ mod tests {
|
||||
}
|
||||
let mut profile = example_devnet_profile();
|
||||
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") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -105,7 +105,6 @@ pub struct DevnetSplToken2022ExecutionSummary {
|
||||
}
|
||||
|
||||
struct PreparedToken2022Execution {
|
||||
wallet: ks_wallet::TemporaryWallet,
|
||||
unsigned: ks_lib::ExSolanaUnsignedTransaction,
|
||||
evidence: ks_lib::ExSolanaSimulationEvidence,
|
||||
summary: crate::DevnetSplToken2022ExecutionSummary,
|
||||
@@ -127,8 +126,12 @@ where
|
||||
"simulate_devnet_spl_token_2022 requires submit=false",
|
||||
));
|
||||
}
|
||||
let prepared =
|
||||
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
||||
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);
|
||||
let prepared = match prepare_execution(http_pool, profile, &wallet, request, observer).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -154,8 +157,44 @@ where
|
||||
+ Sync,
|
||||
O: crate::SolanaExecutionObserver,
|
||||
{
|
||||
let prepared =
|
||||
match prepare_execution(http_pool, profile, workspace_root, request, observer).await {
|
||||
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_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::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -188,7 +227,7 @@ where
|
||||
}
|
||||
let signed = match prepared
|
||||
.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::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -428,7 +467,7 @@ where
|
||||
async fn prepare_execution<O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &crate::DevnetExecutionWallet,
|
||||
request: &crate::DevnetSplToken2022ExecutionRequest,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<PreparedToken2022Execution>
|
||||
@@ -469,11 +508,7 @@ where
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
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),
|
||||
};
|
||||
let wallet_summary = wallet.summary();
|
||||
let wallet_summary = wallet.identity();
|
||||
let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone());
|
||||
let balance = match http_pool
|
||||
.get_balance_for_role(
|
||||
@@ -610,12 +645,7 @@ where
|
||||
materializations: std::vec::Vec::new(),
|
||||
post_execution: std::option::Option::None,
|
||||
};
|
||||
return std::result::Result::Ok(PreparedToken2022Execution {
|
||||
wallet,
|
||||
unsigned,
|
||||
evidence,
|
||||
summary,
|
||||
});
|
||||
return std::result::Result::Ok(PreparedToken2022Execution { unsigned, evidence, summary });
|
||||
}
|
||||
|
||||
fn preflight_request(
|
||||
@@ -872,9 +902,11 @@ fn validate_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(
|
||||
"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 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -50,6 +50,34 @@ pub async fn execute_devnet_token_2022_metadata_campaign<S, O>(
|
||||
options: &crate::Token2022MetadataFixturePreparationOptions,
|
||||
observer: &O,
|
||||
) -> 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
|
||||
S: ks_store::RawTransactionStore
|
||||
+ ks_store::CoreExtractionStore
|
||||
@@ -62,11 +90,8 @@ where
|
||||
"Token-2022 Token Metadata Devnet campaign requires explicit operator confirmation",
|
||||
));
|
||||
}
|
||||
let fixture = match crate::prepare_token_2022_metadata_fixture(
|
||||
http_pool,
|
||||
profile,
|
||||
workspace_root,
|
||||
options,
|
||||
let fixture = match crate::prepare_token_2022_metadata_fixture_with_wallet(
|
||||
http_pool, profile, wallet, options,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -90,11 +115,11 @@ where
|
||||
request.submit = true;
|
||||
request.operator_confirmed = true;
|
||||
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,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
wallet,
|
||||
&request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -521,7 +546,8 @@ mod tests {
|
||||
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
||||
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() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
|
||||
@@ -77,6 +77,22 @@ pub async fn prepare_token_2022_metadata_fixture(
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
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> {
|
||||
if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() {
|
||||
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"
|
||||
|| !profile.wallet.temporary_wallet_enabled
|
||||
|| !profile.wallet.temporary_wallet_persist
|
||||
|| (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(
|
||||
"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",
|
||||
));
|
||||
}
|
||||
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(
|
||||
options.wallet_dir.join("token_2022_metadata_validation"),
|
||||
) {
|
||||
@@ -147,7 +160,7 @@ pub async fn prepare_token_2022_metadata_fixture(
|
||||
http_pool,
|
||||
profile,
|
||||
options,
|
||||
&operator,
|
||||
operator,
|
||||
&mint_wallet,
|
||||
mint_space,
|
||||
rent_budget_space,
|
||||
@@ -285,7 +298,7 @@ async fn create_native_token_2022_metadata_mint(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
options: &crate::Token2022MetadataFixturePreparationOptions,
|
||||
operator: &ks_wallet::TemporaryWallet,
|
||||
operator: &crate::DevnetExecutionWallet,
|
||||
mint_wallet: &ks_wallet::TemporaryWallet,
|
||||
mint_space: usize,
|
||||
rent_budget_space: usize,
|
||||
|
||||
138
ks-pipeline-demo-scenarios/src/wallet.rs
Normal file
138
ks-pipeline-demo-scenarios/src/wallet.rs
Normal 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"));
|
||||
}
|
||||
}
|
||||
13
ks-wallet-demo-scenarios/CHANGELOG.md
Normal file
13
ks-wallet-demo-scenarios/CHANGELOG.md
Normal 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 qu’un changement de mot de passe conserve exactement l’identité publique ;
|
||||
- ajoute `expect_wallet_password_rejected()` pour exiger le rejet de l’ancien 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 d’import/export, migration et formats tiers sans étendre `kb-app-demo-desktop`.
|
||||
20
ks-wallet-demo-scenarios/Cargo.toml
Normal file
20
ks-wallet-demo-scenarios/Cargo.toml
Normal 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
|
||||
43
ks-wallet-demo-scenarios/README.md
Normal file
43
ks-wallet-demo-scenarios/README.md
Normal 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 d’intégration réutilisables qui valident `ks-wallet` comme le ferait un consommateur externe.
|
||||
|
||||
## Responsabilité
|
||||
|
||||
La crate n’implémente ni stockage de secret, ni chiffrement, ni codec `.kswallet`. Elle dépend uniquement de l’API 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 l’identité 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 d’ouverture et de signature avec A restauré.
|
||||
|
||||
Les étapes publiques restent séparées afin d’être réutilisables dans d’autres scénarios. Le test d’intégration les exécute dans un seul test ordonné : il ne dépend jamais de l’ordre d’exécution de plusieurs `#[test]`.
|
||||
|
||||
## Frontières de sécurité
|
||||
|
||||
- les fonctions reçoivent des `ks_wallet::WalletPassword` déjà construits ;
|
||||
- aucun mot de passe n’est sérialisé, journalisé ou inclus dans les summaries ;
|
||||
- les summaries contiennent uniquement l’identité 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, l’authentification et la capacité `Signer` ;
|
||||
- `ks-wallet-demo-scenarios` possède uniquement l’orchestration de validation ;
|
||||
- `kb-app-demo-desktop` n’est pas requis pour valider le cycle de mot de passe ;
|
||||
- les futurs scénarios d’import/export ou de migration pourront être ajoutés ici sans alourdir l’API 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)
|
||||
19
ks-wallet-demo-scenarios/TODO.md
Normal file
19
ks-wallet-demo-scenarios/TODO.md
Normal 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 l’API publique `ks-wallet` ;
|
||||
- [x] ajouter une étape réutilisable de rotation de mot de passe avec conservation stricte de l’alias et de la pubkey ;
|
||||
- [x] ajouter une étape qui exige le rejet de l’ancien mot de passe par le code d’authentification natif attendu ;
|
||||
- [x] ajouter une étape d’ouverture authentifiée et de signature d’un 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 d’import/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 d’ouverture explicite hors store si une validation opérateur supplémentaire devient utile ;
|
||||
- [ ] n’ajouter une CLI que si une validation manuelle hors tests apporte une valeur distincte des consommateurs existants.
|
||||
88
ks-wallet-demo-scenarios/USAGE.md
Normal file
88
ks-wallet-demo-scenarios/USAGE.md
Normal 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 n’altè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 l’identité avant rotation, appelle `WalletManager::change_password()` puis refuse le résultat si l’alias ou la pubkey a changé.
|
||||
|
||||
## Vérifier qu’un 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 d’une autre cause n’est 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 l’identité inspectée à l’identité 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 d’environnement. 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 lorsqu’ils apporteront une validation supplémentaire au-delà des tests unitaires de `ks-wallet`.
|
||||
23
ks-wallet-demo-scenarios/src/lib.rs
Normal file
23
ks-wallet-demo-scenarios/src/lib.rs
Normal 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;
|
||||
161
ks-wallet-demo-scenarios/src/password_lifecycle.rs
Normal file
161
ks-wallet-demo-scenarios/src/password_lifecycle.rs
Normal 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(),
|
||||
});
|
||||
}
|
||||
90
ks-wallet-demo-scenarios/tests/password_lifecycle.rs
Normal file
90
ks-wallet-demo-scenarios/tests/password_lifecycle.rs
Normal 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());
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ks-wallet/README.md -->
|
||||
<!-- version: 12 -->
|
||||
<!-- version: 13 -->
|
||||
|
||||
# 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-wallet-demo-scenarios` consomme uniquement l’API 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.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ks-wallet/USAGE.md -->
|
||||
<!-- version: 12 -->
|
||||
<!-- version: 13 -->
|
||||
|
||||
# Utilisation de ks-wallet
|
||||
|
||||
@@ -458,7 +458,7 @@ lookup by alias
|
||||
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.
|
||||
|
||||
|
||||
@@ -196,11 +196,12 @@
|
||||
}
|
||||
},
|
||||
"wallet": {
|
||||
"wallet_dir": "wallets/temporary/local_devnet",
|
||||
"wallet_dir": "wallets",
|
||||
"cluster": "devnet",
|
||||
"temporary_wallet_enabled": true,
|
||||
"temporary_wallet_alias": "local-devnet-operator",
|
||||
"temporary_wallet_persist": true
|
||||
"temporary_wallet_persist": true,
|
||||
"temporary_wallet_dir": "wallets/temporary/local_devnet"
|
||||
},
|
||||
"execution": {
|
||||
"dry_run_default": true,
|
||||
|
||||
@@ -196,11 +196,12 @@
|
||||
}
|
||||
},
|
||||
"wallet": {
|
||||
"wallet_dir": "wallets/temporary/local_devnet",
|
||||
"wallet_dir": "wallets",
|
||||
"cluster": "devnet",
|
||||
"temporary_wallet_enabled": true,
|
||||
"temporary_wallet_alias": "local-devnet-operator",
|
||||
"temporary_wallet_persist": true
|
||||
"temporary_wallet_persist": true,
|
||||
"temporary_wallet_dir": "wallets/temporary/local_devnet"
|
||||
},
|
||||
"execution": {
|
||||
"dry_run_default": true,
|
||||
@@ -618,11 +619,12 @@
|
||||
}
|
||||
},
|
||||
"wallet": {
|
||||
"wallet_dir": "wallets/temporary/mainnet_research",
|
||||
"wallet_dir": "wallets",
|
||||
"cluster": "mainnet-beta",
|
||||
"temporary_wallet_enabled": false,
|
||||
"temporary_wallet_alias": "mainnet_research-disabled",
|
||||
"temporary_wallet_persist": false
|
||||
"temporary_wallet_persist": false,
|
||||
"temporary_wallet_dir": "wallets/temporary/mainnet_research"
|
||||
},
|
||||
"execution": {
|
||||
"dry_run_default": true,
|
||||
@@ -1040,11 +1042,12 @@
|
||||
}
|
||||
},
|
||||
"wallet": {
|
||||
"wallet_dir": "wallets/temporary/mainnet",
|
||||
"wallet_dir": "wallets",
|
||||
"cluster": "mainnet-beta",
|
||||
"temporary_wallet_enabled": false,
|
||||
"temporary_wallet_alias": "mainnet-disabled",
|
||||
"temporary_wallet_persist": false
|
||||
"temporary_wallet_persist": false,
|
||||
"temporary_wallet_dir": "wallets/temporary/mainnet"
|
||||
},
|
||||
"execution": {
|
||||
"dry_run_default": true,
|
||||
|
||||
Reference in New Issue
Block a user