// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs // version: 4 //! Coherent Metaplex Token Metadata scenario journeys for tests and Devnet campaigns. /// Stable asset family covered by one Metaplex scenario. #[derive( Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize, )] #[serde(rename_all = "snake_case")] pub enum MetaplexTokenMetadataAssetFamily { /// Non-fungible token. Nft, /// Semi-fungible token. Sft, /// Fungible token with Metaplex metadata. Fungible, /// Collection parent and member relationship. Collection, /// Programmable non-fungible token. ProgrammableNft, } /// Stable fixture graph required by one scenario journey. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] pub enum MetaplexTokenMetadataFixtureKind { /// Fresh zero-decimal classic SPL mint with profile-owned authorities. ClassicNftMint, /// Fresh SFT mint and token account. SemiFungibleMint, /// Fresh fungible mint and token account. FungibleMint, /// Collection parent plus one unverified member. CollectionParentAndMember, /// Programmable NFT with token record and optional rule set. ProgrammableNftTokenRecord, } /// Stable lifecycle state consumed or produced by one scenario journey. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] pub enum MetaplexTokenMetadataFixtureState { /// Mint exists but metadata does not. MintPrepared, /// Metadata exists and remains mutable. MetadataCreated, /// Master edition exists. MasterEditionCreated, /// Collection member exists but is not verified. CollectionMemberUnverified, /// Collection member is verified. CollectionMemberVerified, /// Programmable token record exists. ProgrammableTokenRecordCreated, /// Programmable asset is delegated. Delegated, /// Programmable asset is locked. Locked, /// Scenario has reached its terminal validated state. Validated, } /// Execution mode accepted by one scenario. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] pub enum MetaplexTokenMetadataScenarioMode { /// Purely deterministic scenario using synthetic accounts and plans. Synthetic, /// Devnet or Testnet simulation without submission. NetworkSimulation, /// Explicitly authorized Devnet or Testnet submission. NetworkSubmission, } /// One stable Metaplex journey reusable by automated tests and demo adapters. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct MetaplexTokenMetadataScenario { /// Stable scenario identifier. pub id: std::string::String, /// Operator-visible scenario label. pub label: std::string::String, /// Asset family exercised by the scenario. pub asset_family: crate::MetaplexTokenMetadataAssetFamily, /// Fixture graph required by the scenario. pub fixture_kind: crate::MetaplexTokenMetadataFixtureKind, /// Required initial lifecycle state. pub initial_state: crate::MetaplexTokenMetadataFixtureState, /// Expected terminal lifecycle state. pub resulting_state: crate::MetaplexTokenMetadataFixtureState, /// Execution mode. pub mode: crate::MetaplexTokenMetadataScenarioMode, /// Stable operation codes executed in order. pub operation_codes: std::vec::Vec, /// Whether the scenario requires a collection account. pub requires_collection: bool, /// Whether the scenario requires programmable authorization rules. pub requires_programmable_rules: bool, /// Whether a confirmed postcondition is mandatory. pub requires_postcondition: bool, /// Canonical account projections expected when materialization is enabled. pub materialization_targets: std::vec::Vec, } fn scenario( id: &str, label: &str, asset_family: crate::MetaplexTokenMetadataAssetFamily, fixture_kind: crate::MetaplexTokenMetadataFixtureKind, initial_state: crate::MetaplexTokenMetadataFixtureState, resulting_state: crate::MetaplexTokenMetadataFixtureState, operation_codes: std::vec::Vec, requires_collection: bool, requires_programmable_rules: bool, materialization_targets: &[&str], ) -> crate::MetaplexTokenMetadataScenario { return crate::MetaplexTokenMetadataScenario { id: id.to_string(), label: label.to_string(), asset_family, fixture_kind, initial_state, resulting_state, mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic, operation_codes, requires_collection, requires_programmable_rules, requires_postcondition: true, materialization_targets: materialization_targets .iter() .map(|value| return (*value).to_string()) .collect(), }; } /// Returns the complete ordered synthetic Metaplex journey inventory. pub fn metaplex_token_metadata_synthetic_scenarios() -> std::vec::Vec { return vec![ scenario( "metaplex_nft_create_update", "NFT classique : création, mise à jour et édition maître", crate::MetaplexTokenMetadataAssetFamily::Nft, crate::MetaplexTokenMetadataFixtureKind::ClassicNftMint, crate::MetaplexTokenMetadataFixtureState::MintPrepared, crate::MetaplexTokenMetadataFixtureState::MasterEditionCreated, 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_SET_TOKEN_STANDARD_OPERATION.to_string(), ], false, false, &["metadata_state", "edition_state"], ), scenario( "metaplex_sft_create_mint", "SFT : création des metadata puis émission de supply", crate::MetaplexTokenMetadataAssetFamily::Sft, crate::MetaplexTokenMetadataFixtureKind::SemiFungibleMint, crate::MetaplexTokenMetadataFixtureState::MintPrepared, crate::MetaplexTokenMetadataFixtureState::Validated, vec![ kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(), kb_lib::EX_METAPLEX_TOKEN_METADATA_MINT_OPERATION.to_string(), kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(), ], false, false, &["metadata_state"], ), scenario( "metaplex_fungible_create_update", "Fungible : création et transitions d’autorité", crate::MetaplexTokenMetadataAssetFamily::Fungible, crate::MetaplexTokenMetadataFixtureKind::FungibleMint, crate::MetaplexTokenMetadataFixtureState::MintPrepared, crate::MetaplexTokenMetadataFixtureState::Validated, 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"], ), scenario( "metaplex_collection_verify_unverify", "Collection : membre non vérifié, vérification puis révocation", crate::MetaplexTokenMetadataAssetFamily::Collection, crate::MetaplexTokenMetadataFixtureKind::CollectionParentAndMember, crate::MetaplexTokenMetadataFixtureState::CollectionMemberUnverified, crate::MetaplexTokenMetadataFixtureState::CollectionMemberVerified, vec![ kb_lib::EX_METAPLEX_TOKEN_METADATA_VERIFY_OPERATION.to_string(), kb_lib::EX_METAPLEX_TOKEN_METADATA_UNVERIFY_OPERATION.to_string(), kb_lib::EX_METAPLEX_TOKEN_METADATA_SET_COLLECTION_SIZE_OPERATION.to_string(), ], true, false, &["metadata_state", "edition_state"], ), scenario( "metaplex_pnft_delegate_transfer_revoke", "pNFT : token record, délégation, verrouillage, transfert et révocation", crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft, crate::MetaplexTokenMetadataFixtureKind::ProgrammableNftTokenRecord, crate::MetaplexTokenMetadataFixtureState::ProgrammableTokenRecordCreated, crate::MetaplexTokenMetadataFixtureState::Validated, vec![ kb_lib::EX_METAPLEX_TOKEN_METADATA_DELEGATE_OPERATION.to_string(), kb_lib::EX_METAPLEX_TOKEN_METADATA_LOCK_OPERATION.to_string(), kb_lib::EX_METAPLEX_TOKEN_METADATA_UNLOCK_OPERATION.to_string(), kb_lib::EX_METAPLEX_TOKEN_METADATA_TRANSFER_OPERATION.to_string(), kb_lib::EX_METAPLEX_TOKEN_METADATA_REVOKE_OPERATION.to_string(), ], false, true, &["metadata_state", "programmable_token_state", "delegate_admin_state"], ), ]; } /// 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]; } #[cfg(test)] mod tests { #[test] fn inventory_is_exact_unique_and_uses_coherent_fixture_graphs() { let scenarios = crate::metaplex_token_metadata_synthetic_scenarios(); assert_eq!(scenarios.len(), 5); let ids = scenarios .iter() .map(|scenario| return scenario.id.as_str()) .collect::>(); assert_eq!(ids.len(), scenarios.len()); for scenario in scenarios { assert!(!scenario.label.trim().is_empty()); assert!(!scenario.operation_codes.is_empty()); assert!(!scenario.materialization_targets.is_empty()); assert!(scenario.requires_postcondition); } } #[test] fn collection_and_programmable_journeys_declare_special_requirements() { let scenarios = crate::metaplex_token_metadata_synthetic_scenarios(); let collection = scenarios.iter().find(|scenario| { return scenario.asset_family == crate::MetaplexTokenMetadataAssetFamily::Collection; }); assert!(collection.is_some_and(|scenario| return scenario.requires_collection)); let programmable = scenarios.iter().find(|scenario| { return scenario.asset_family == crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft; }); assert!(programmable.is_some_and(|scenario| return scenario.requires_programmable_rules)); } #[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); } }