From b358b12f3b1c137a39bc081fa0c93ed30640101b Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Tue, 4 Aug 2026 19:43:03 +0200 Subject: [PATCH] v0.4.7-pre.014 --- Cargo.toml | 4 +- ...E_014_METAPLEX_COHERENT_DEVNET_JOURNEYS.md | 42 ++++++ kb-app-demo-desktop/CHANGELOG.md | 7 +- .../src/demo_execution_metadata.rs | 99 +++++++++++++- kb-pipeline-demo-scenarios/CHANGELOG.md | 7 +- ...olana_metaplex_token_metadata_scenarios.rs | 129 +++++++++++++----- 6 files changed, 249 insertions(+), 39 deletions(-) create mode 100644 docs/validation/V0_4_7_PRE_014_METAPLEX_COHERENT_DEVNET_JOURNEYS.md diff --git a/Cargo.toml b/Cargo.toml index 1e5d4a0..08c298d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 20 +# version: 21 [workspace] resolver = "3" @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.4.7-pre.13" +version = "0.4.7-pre.14" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" diff --git a/docs/validation/V0_4_7_PRE_014_METAPLEX_COHERENT_DEVNET_JOURNEYS.md b/docs/validation/V0_4_7_PRE_014_METAPLEX_COHERENT_DEVNET_JOURNEYS.md new file mode 100644 index 0000000..328d336 --- /dev/null +++ b/docs/validation/V0_4_7_PRE_014_METAPLEX_COHERENT_DEVNET_JOURNEYS.md @@ -0,0 +1,42 @@ + + + +# Validation `0.4.7-pre.014` — parcours Metaplex Devnet cohérents + +## Objet + +Cette prérelease étend le runner Metadata depuis le seul parcours NFT vers cinq parcours Devnet préparables : NFT, SFT, fungible, collection parent et pNFT sans rule set. + +Chaque parcours expose d'abord un cycle borné et comparable : + +1. création native d'un mint SPL classique par le workspace ; +2. préparation contextuelle de `Create` ; +3. simulation exacte ; +4. soumission et confirmation explicites ; +5. relecture et matérialisation optionnelle des comptes applicables ; +6. préparation de `UpdateAsUpdateAuthorityV2` depuis l'état créé ; +7. simulation, soumission, confirmation et postconditions. + +## Familles et contrats Create + +| Parcours | `token_standard` | Edition relue | Particularité | +|------------|---------------------------|--------------:|-----------------------------------------| +| NFT | `NonFungible` | oui | `print_supply = Zero` | +| SFT | `FungibleAsset` | non | `decimals = 0`, pas de master edition | +| Fungible | `Fungible` | non | `decimals = 0`, pas de master edition | +| Collection | `NonFungible` | oui | `collection_details = V1(size=0)` | +| pNFT | `ProgrammableNonFungible` | oui | aucun rule set pour ce premier parcours | + +## Limite volontaire + +Cette prérelease ne déclare pas encore validées les opérations spécialisées `mint`, `verify`, `unverify`, `delegate`, `lock`, `unlock`, `transfer`, `revoke`, `burn`, `resize` et apparentées. Elles doivent être ajoutées comme étapes dépendantes d'un contexte confirmé, pas comme modèles JSON isolés. + +## Validation attendue + +- `cargo fmt --all` ; +- `cargo check --workspace` ; +- `cargo clippy --all-targets` ; +- `python3 scripts/audit_rust_workspace_rules.py` ; +- `cargo test -p kb-pipeline-demo-scenarios` ; +- `cargo test -p kb-app-demo-desktop` ; +- pour chaque parcours : préparer `Create`, simuler, soumettre, confirmer, puis préparer et valider `Update`. diff --git a/kb-app-demo-desktop/CHANGELOG.md b/kb-app-demo-desktop/CHANGELOG.md index bf22333..be7f1fb 100644 --- a/kb-app-demo-desktop/CHANGELOG.md +++ b/kb-app-demo-desktop/CHANGELOG.md @@ -1,8 +1,13 @@ - + # CHANGELOG — kb-app-demo-desktop +## 0.4.7-pre.014 + +- Afficher les cinq parcours Metaplex Devnet dans le runner Metadata. +- Adapter automatiquement le JSON `Create`, les lectures de postcondition et les cibles de matérialisation à la famille sélectionnée. + ## 0.4.7-pre.013-fix-015 - aligne les tests desktop sur le nombre réel de scénarios synthétiques et Devnet exposés par les registres réutilisables ; diff --git a/kb-app-demo-desktop/src/demo_execution_metadata.rs b/kb-app-demo-desktop/src/demo_execution_metadata.rs index 93b11a3..8587e9d 100644 --- a/kb-app-demo-desktop/src/demo_execution_metadata.rs +++ b/kb-app-demo-desktop/src/demo_execution_metadata.rs @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/src/demo_execution_metadata.rs -// version: 12 +// version: 13 //! Thin desktop adapters for metadata execution demo scenarios. @@ -323,6 +323,84 @@ pub(crate) async fn demo_execution_metadata_prepare_create_fixture( }); } +fn adapt_create_operation_json( + operation_json: &str, + asset_family: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily, +) -> std::result::Result { + let mut value: serde_json::Value = match serde_json::from_str(operation_json) { + std::result::Result::Ok(parsed) => parsed, + std::result::Result::Err(error) => { + return std::result::Result::Err(format!( + "invalid generated Metaplex Create JSON: {error}" + )); + }, + }; + if matches!( + asset_family, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft + | kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible + ) { + value["master_edition"] = serde_json::Value::Null; + } + let create = match value + .get_mut("create_args") + .and_then(|args| return args.get_mut("V1")) + .and_then(serde_json::Value::as_object_mut) + { + std::option::Option::Some(found) => found, + std::option::Option::None => { + return std::result::Result::Err( + "generated Metaplex Create JSON has no create_args.V1 object".to_string(), + ); + }, + }; + match asset_family { + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => { + create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet NFT")); + create.insert("symbol".to_string(), serde_json::json!("KHNFT")); + create.insert("token_standard".to_string(), serde_json::json!("NonFungible")); + create.insert("collection_details".to_string(), serde_json::Value::Null); + create.insert("decimals".to_string(), serde_json::Value::Null); + create.insert("print_supply".to_string(), serde_json::json!("Zero")); + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => { + create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet SFT")); + create.insert("symbol".to_string(), serde_json::json!("KHSFT")); + create.insert("token_standard".to_string(), serde_json::json!("FungibleAsset")); + create.insert("collection_details".to_string(), serde_json::Value::Null); + create.insert("decimals".to_string(), serde_json::json!(0)); + create.insert("print_supply".to_string(), serde_json::Value::Null); + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => { + create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Fungible")); + create.insert("symbol".to_string(), serde_json::json!("KHFUN")); + create.insert("token_standard".to_string(), serde_json::json!("Fungible")); + create.insert("collection_details".to_string(), serde_json::Value::Null); + create.insert("decimals".to_string(), serde_json::json!(0)); + create.insert("print_supply".to_string(), serde_json::Value::Null); + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => { + create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Collection")); + create.insert("symbol".to_string(), serde_json::json!("KHCOL")); + create.insert("token_standard".to_string(), serde_json::json!("NonFungible")); + create.insert("collection_details".to_string(), serde_json::json!({"V1": {"size": 0}})); + create.insert("decimals".to_string(), serde_json::Value::Null); + create.insert("print_supply".to_string(), serde_json::json!("Zero")); + }, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => { + create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet pNFT")); + create.insert("symbol".to_string(), serde_json::json!("KHPNFT")); + create + .insert("token_standard".to_string(), serde_json::json!("ProgrammableNonFungible")); + create.insert("collection_details".to_string(), serde_json::Value::Null); + create.insert("rule_set".to_string(), serde_json::Value::Null); + create.insert("decimals".to_string(), serde_json::Value::Null); + create.insert("print_supply".to_string(), serde_json::json!("Zero")); + }, + } + return std::result::Result::Ok(crate::pretty_json(&value)); +} + /// Prepares one coherent scenario step and all of its stateful reads. pub(crate) async fn demo_execution_metadata_prepare_step( state: tauri::State<'_, crate::AppState>, @@ -374,11 +452,18 @@ pub(crate) async fn demo_execution_metadata_prepare_step( std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; + let operation_json = match adapt_create_operation_json( + fixture.operation_json.as_str(), + scenario.asset_family, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; ( fixture.mint, fixture.metadata, fixture.master_edition, - fixture.operation_json, + operation_json, if fixture.mint_created { "mint_prepared".to_string() } else { @@ -480,7 +565,13 @@ pub(crate) async fn demo_execution_metadata_prepare_step( } else { serde_json::json!([metadata_read.clone()]) }; - let postcondition_reads = if operation == "create" { + let create_reads_edition = matches!( + scenario.asset_family, + kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft + | kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection + | kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft + ); + let postcondition_reads = if operation == "create" && create_reads_edition { serde_json::json!([metadata_read, edition_read]) } else { serde_json::json!([metadata_read]) @@ -675,7 +766,7 @@ mod tests { .iter() .filter(|payload| return payload.mode == "network_simulation") .count(), - 1 + 5 ); } diff --git a/kb-pipeline-demo-scenarios/CHANGELOG.md b/kb-pipeline-demo-scenarios/CHANGELOG.md index 32b2e21..839f6c2 100644 --- a/kb-pipeline-demo-scenarios/CHANGELOG.md +++ b/kb-pipeline-demo-scenarios/CHANGELOG.md @@ -1,8 +1,13 @@ - + # CHANGELOG — kb-pipeline-demo-scenarios +## 0.4.7-pre.014 + +- Expose cinq parcours Metaplex Devnet préparables : NFT, SFT, fungible, collection parent et pNFT sans rule set. +- Conserver un cycle borné `Create -> UpdateAsUpdateAuthorityV2` pour chaque famille avant d'étendre les opérations spécialisées. + ## 0.4.7-pre.013-fix-014 - crée un nouveau mint natif pour chaque nouvelle préparation `Create`, afin de ne jamais réutiliser un PDA metadata déjà créé ; diff --git a/kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs b/kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs index 5214932..52e451e 100644 --- a/kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs +++ b/kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs @@ -1,5 +1,5 @@ // file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs -// version: 4 +// version: 5 //! Coherent Metaplex Token Metadata scenario journeys for tests and Devnet campaigns. @@ -224,23 +224,85 @@ pub fn metaplex_token_metadata_synthetic_scenarios() /// Returns the ordered Metaplex journeys intended for opt-in Devnet simulation. pub fn metaplex_token_metadata_devnet_scenarios() -> std::vec::Vec { - let mut scenario = scenario( - "metaplex_nft_create_update_devnet", - "NFT classique : création confirmée puis mise à jour", - crate::MetaplexTokenMetadataAssetFamily::Nft, - crate::MetaplexTokenMetadataFixtureKind::ClassicNftMint, - crate::MetaplexTokenMetadataFixtureState::MintPrepared, - crate::MetaplexTokenMetadataFixtureState::MetadataCreated, - vec![ - kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(), - kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(), - ], - false, - false, - &["metadata_state", "edition_state"], - ); - scenario.mode = crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation; - return vec![scenario]; + let definitions = [ + ( + "metaplex_nft_create_update_devnet", + "NFT classique : création confirmée puis mise à jour", + crate::MetaplexTokenMetadataAssetFamily::Nft, + crate::MetaplexTokenMetadataFixtureKind::ClassicNftMint, + false, + false, + &["metadata_state", "edition_state"][..], + ), + ( + "metaplex_sft_create_update_devnet", + "SFT : création confirmée puis mise à jour", + crate::MetaplexTokenMetadataAssetFamily::Sft, + crate::MetaplexTokenMetadataFixtureKind::SemiFungibleMint, + false, + false, + &["metadata_state"][..], + ), + ( + "metaplex_fungible_create_update_devnet", + "Fungible : création confirmée puis mise à jour", + crate::MetaplexTokenMetadataAssetFamily::Fungible, + crate::MetaplexTokenMetadataFixtureKind::FungibleMint, + false, + false, + &["metadata_state"][..], + ), + ( + "metaplex_collection_create_update_devnet", + "Collection parent : création confirmée puis mise à jour", + crate::MetaplexTokenMetadataAssetFamily::Collection, + crate::MetaplexTokenMetadataFixtureKind::CollectionParentAndMember, + true, + false, + &["metadata_state", "edition_state"][..], + ), + ( + "metaplex_pnft_create_update_devnet", + "pNFT sans rule set : création confirmée puis mise à jour", + crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft, + crate::MetaplexTokenMetadataFixtureKind::ProgrammableNftTokenRecord, + false, + false, + &["metadata_state", "edition_state"][..], + ), + ]; + return definitions + .into_iter() + .map( + |( + id, + label, + asset_family, + fixture_kind, + requires_collection, + requires_programmable_rules, + materialization_targets, + )| { + let mut value = scenario( + id, + label, + asset_family, + fixture_kind, + crate::MetaplexTokenMetadataFixtureState::MintPrepared, + crate::MetaplexTokenMetadataFixtureState::MetadataCreated, + vec![ + kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(), + kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(), + ], + requires_collection, + requires_programmable_rules, + materialization_targets, + ); + value.mode = crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation; + return value; + }, + ) + .collect(); } #[cfg(test)] @@ -279,18 +341,23 @@ mod tests { #[test] fn devnet_inventory_exposes_only_fully_preparable_journeys() { let devnet = crate::metaplex_token_metadata_devnet_scenarios(); - assert_eq!(devnet.len(), 1); - let scenario = &devnet[0]; - assert_eq!(scenario.asset_family, crate::MetaplexTokenMetadataAssetFamily::Nft); - assert_eq!(scenario.operation_codes.len(), 2); - assert_eq!( - scenario.operation_codes[0], - kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION - ); - assert_eq!( - scenario.operation_codes[1], - kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION - ); - assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation); + assert_eq!(devnet.len(), 5); + let families = devnet + .iter() + .map(|scenario| return scenario.asset_family) + .collect::>(); + assert_eq!(families.len(), 5); + for scenario in devnet { + assert_eq!(scenario.operation_codes.len(), 2); + assert_eq!( + scenario.operation_codes[0], + kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION + ); + assert_eq!( + scenario.operation_codes[1], + kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION + ); + assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation); + } } }