From 40d831f84d9ff52c78c9e8baf8dfab57e4e1ff96 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Tue, 4 Aug 2026 21:32:09 +0200 Subject: [PATCH] v0.4.7-pre.015 --- Cargo.toml | 4 +- .../V0_4_7_PRE_015_METAPLEX_NFT_LIFECYCLE.md | 34 ++++++++++++++ kb-app-demo-desktop/CHANGELOG.md | 7 ++- .../src/demo_execution_metadata.rs | 46 ++++++++++++++----- kb-pipeline-demo-scenarios/CHANGELOG.md | 7 ++- kb-pipeline-demo-scenarios/TODO.md | 2 + ...olana_metaplex_token_metadata_scenarios.rs | 44 +++++++++++++----- 7 files changed, 117 insertions(+), 27 deletions(-) create mode 100644 docs/validation/V0_4_7_PRE_015_METAPLEX_NFT_LIFECYCLE.md diff --git a/Cargo.toml b/Cargo.toml index 08c298d..1d66613 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 21 +# version: 22 [workspace] resolver = "3" @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.4.7-pre.14" +version = "0.4.7-pre.15" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" diff --git a/docs/validation/V0_4_7_PRE_015_METAPLEX_NFT_LIFECYCLE.md b/docs/validation/V0_4_7_PRE_015_METAPLEX_NFT_LIFECYCLE.md new file mode 100644 index 0000000..11b4eda --- /dev/null +++ b/docs/validation/V0_4_7_PRE_015_METAPLEX_NFT_LIFECYCLE.md @@ -0,0 +1,34 @@ + + + +# Validation `0.4.7-pre.015` — cycle NFT classique + +## Portée + +Cette prérelease prolonge le parcours Devnet NFT classique validé en `pre.014`. + +Le parcours exécutable devient : + +1. création native du mint SPL classique ; +2. `Create` Metaplex avec metadata et master edition ; +3. `UpdateAsUpdateAuthorityV2` pour enregistrer la vente primaire ; +4. `UpdateAsUpdateAuthorityV2` avec `is_mutable = false`. + +La master edition n’est pas une étape séparée : elle est créée par le wrapper `Create` lorsque `master_edition` et `print_supply = Zero` sont fournis. + +## Validation attendue + +Pour chaque étape : + +- préparation contextuelle ; +- simulation exacte réussie ; +- soumission explicitement confirmée ; +- confirmation Devnet ; +- relecture du PDA metadata ; +- matérialisation optionnelle du snapshot confirmé. + +Après la dernière étape, une nouvelle mise à jour doit être refusée par Metaplex puisque les metadata sont immutables. + +## Hors portée de cette prérelease + +`Print` et `Burn` exigent encore des fixtures supplémentaires : token account du master, mint et token account de l’édition, edition marker et comptes de burn. Ils restent à implémenter dans la prérelease suivante et ne sont pas déclarés validés ici. diff --git a/kb-app-demo-desktop/CHANGELOG.md b/kb-app-demo-desktop/CHANGELOG.md index be7f1fb..20ea764 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.15 +- expose l’étape NFT « rendre les metadata immutables » dans le runner cohérent ; +- prépare cette étape depuis le contexte confirmé de l’étape précédente ; +- maintient les lectures de postcondition et la matérialisation sur le PDA metadata. + ## 0.4.7-pre.014 - Afficher les cinq parcours Metaplex Devnet dans le runner Metadata. diff --git a/kb-app-demo-desktop/src/demo_execution_metadata.rs b/kb-app-demo-desktop/src/demo_execution_metadata.rs index 8587e9d..610d1b2 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: 13 +// version: 14 //! Thin desktop adapters for metadata execution demo scenarios. @@ -475,7 +475,8 @@ pub(crate) async fn demo_execution_metadata_prepare_step( std::option::Option::Some(value) if !value.trim().is_empty() => value, _ => { return std::result::Result::Err( - "prepare and submit the Create step before preparing Update".to_string(), + "prepare and submit the previous NFT lifecycle step before continuing" + .to_string(), ); }, }; @@ -494,7 +495,7 @@ pub(crate) async fn demo_execution_metadata_prepare_step( return match previous_value.get(name).and_then(serde_json::Value::as_str) { std::option::Option::Some(value) => std::result::Result::Ok(value.to_string()), std::option::Option::None => std::result::Result::Err(format!( - "previous Create step does not contain `{name}`" + "previous scenario step does not contain `{name}`" )), }; }; @@ -506,14 +507,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 master_edition = match string_field("master_edition") { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => return std::result::Result::Err(error), - }; let authority = match string_field("authority") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; + let master_edition = + match previous_value.get("master_edition").and_then(serde_json::Value::as_str) { + std::option::Option::Some(value) => value.to_string(), + std::option::Option::None => std::string::String::new(), + }; + let immutable_transition = scenario.asset_family + == kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft + && step_index == 2; let value = serde_json::json!({ "operation": "update_as_update_authority_v2", "authority": authority.clone(), @@ -527,8 +532,16 @@ pub(crate) async fn demo_execution_metadata_prepare_step( "AsUpdateAuthorityV2": { "new_update_authority": null, "data": null, - "primary_sale_happened": true, - "is_mutable": null, + "primary_sale_happened": if immutable_transition { + serde_json::Value::Null + } else { + serde_json::json!(true) + }, + "is_mutable": if immutable_transition { + serde_json::json!(false) + } else { + serde_json::Value::Null + }, "collection": "None", "collection_details": "None", "uses": "None", @@ -543,7 +556,11 @@ pub(crate) async fn demo_execution_metadata_prepare_step( metadata, master_edition, crate::pretty_json(&value), - "metadata_created".to_string(), + if immutable_transition { + "metadata_immutable".to_string() + } else { + "metadata_created".to_string() + }, ) }; let metadata_read = serde_json::json!({ @@ -580,7 +597,14 @@ pub(crate) async fn demo_execution_metadata_prepare_step( scenario_id: scenario.id, step_index, operation: operation.clone(), - label: format!("Étape {} — {}", step_index + 1, operation), + label: if scenario.asset_family + == kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft + && step_index == 2 + { + "Étape 3 — rendre les metadata immutables".to_string() + } else { + format!("Étape {} — {}", step_index + 1, operation) + }, operation_json, preflight_reads_json: crate::pretty_json(&preflight_reads), postcondition_reads_json: crate::pretty_json(&postcondition_reads), diff --git a/kb-pipeline-demo-scenarios/CHANGELOG.md b/kb-pipeline-demo-scenarios/CHANGELOG.md index 839f6c2..bbaac2c 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.15 +- étend le parcours NFT Devnet à une troisième étape `UpdateAsUpdateAuthorityV2` qui rend les metadata immutables ; +- distingue l’édition maître créée par `Create` des futures fixtures d’édition imprimée ; +- conserve `Print` et `Burn` comme étapes ultérieures tant que leurs comptes SPL et édition ne sont pas préparés de bout en bout. + ## 0.4.7-pre.014 - Expose cinq parcours Metaplex Devnet préparables : NFT, SFT, fungible, collection parent et pNFT sans rule set. diff --git a/kb-pipeline-demo-scenarios/TODO.md b/kb-pipeline-demo-scenarios/TODO.md index 8621528..b824750 100644 --- a/kb-pipeline-demo-scenarios/TODO.md +++ b/kb-pipeline-demo-scenarios/TODO.md @@ -27,3 +27,5 @@ ## Report conditionnel — registre ElGamal - [ ] Registre ElGamal - créer un scénario exécutable uniquement après confirmation du déploiement et disponibilité des preuves requises. + +- Metaplex NFT Devnet — préparer les comptes SPL et PDA nécessaires aux étapes `Print` puis `Burn` après le parcours immuable de `pre.015`. 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 52e451e..3b90f5c 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: 5 +// version: 6 //! Coherent Metaplex Token Metadata scenario journeys for tests and Devnet campaigns. @@ -47,6 +47,8 @@ pub enum MetaplexTokenMetadataFixtureState { MetadataCreated, /// Master edition exists. MasterEditionCreated, + /// Metadata is immutable after a confirmed update-authority transition. + MetadataImmutable, /// Collection member exists but is not verified. CollectionMemberUnverified, /// Collection member is verified. @@ -226,8 +228,8 @@ pub fn metaplex_token_metadata_devnet_scenarios() -> std::vec::Vec { let definitions = [ ( - "metaplex_nft_create_update_devnet", - "NFT classique : création confirmée puis mise à jour", + "metaplex_nft_lifecycle_devnet", + "NFT classique : création, mise à jour puis immutabilité", crate::MetaplexTokenMetadataAssetFamily::Nft, crate::MetaplexTokenMetadataFixtureKind::ClassicNftMint, false, @@ -289,11 +291,23 @@ pub fn metaplex_token_metadata_devnet_scenarios() 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(), - ], + if asset_family == crate::MetaplexTokenMetadataAssetFamily::Nft { + crate::MetaplexTokenMetadataFixtureState::MetadataImmutable + } else { + crate::MetaplexTokenMetadataFixtureState::MetadataCreated + }, + if asset_family == crate::MetaplexTokenMetadataAssetFamily::Nft { + vec![ + kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(), + kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(), + kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(), + ] + } else { + 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, @@ -348,14 +362,20 @@ mod tests { .collect::>(); assert_eq!(families.len(), 5); for scenario in devnet { - assert_eq!(scenario.operation_codes.len(), 2); + let expected_len = + if scenario.asset_family == crate::MetaplexTokenMetadataAssetFamily::Nft { + 3 + } else { + 2 + }; + assert_eq!(scenario.operation_codes.len(), expected_len); 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!( + scenario.operation_codes[1..].iter().all(|operation| return operation + == kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION) ); assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation); }