Files
khadhroony-bot3/kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs
2026-08-04 17:14:01 +02:00

297 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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<std::string::String>,
/// 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<std::string::String>,
}
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<std::string::String>,
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<crate::MetaplexTokenMetadataScenario> {
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 dautorité",
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<crate::MetaplexTokenMetadataScenario> {
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::<std::collections::BTreeSet<&str>>();
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);
}
}