// file: kb-pipeline/src/solana_metaplex_token_metadata_preflight.rs // version: 2 //! Stateful Metaplex Token Metadata preflight contracts. /// Maximum number of correlated Metaplex snapshots accepted by one preflight. pub const MAX_METAPLEX_TOKEN_METADATA_PREFLIGHT_ACCOUNTS: usize = 32; /// Complete preflight request for one prepared Metaplex execution plan. #[derive(Clone, Debug, PartialEq)] pub struct MetaplexTokenMetadataPreflightRequest { /// Exact prepared plan produced by `kb-lib`. pub plan: kb_lib::ExApiPreparedExecutionPlan, /// Confirmed bounded state snapshots correlated with the plan. pub snapshots: std::vec::Vec, /// Whether deprecated execution was explicitly approved by the operator. pub allow_deprecated_operation: bool, } /// Deterministic Metaplex preflight report. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct MetaplexTokenMetadataPreflightReport { /// Stable operation code. pub operation_code: std::string::String, /// Highest confirmed context slot across supplied snapshots. pub context_slot: u64, /// Canonical accounts inspected by the preflight. pub inspected_accounts: std::vec::Vec, /// Ordered successful checks. pub checks: std::vec::Vec, } /// Validates program ownership, snapshot bounds, account correlation and deprecation policy. pub fn inspect_metaplex_token_metadata_preflight( request: &crate::MetaplexTokenMetadataPreflightRequest, ) -> kb_core::Result { if request.snapshots.len() > crate::MAX_METAPLEX_TOKEN_METADATA_PREFLIGHT_ACCOUNTS { return std::result::Result::Err(kb_core::Error::new( "metaplex_preflight_account_limit_exceeded", "Metaplex preflight account limit exceeded", )); } if request.plan.instructions.is_empty() { return std::result::Result::Err(kb_core::Error::new( "metaplex_preflight_empty_plan", "Metaplex execution plan must contain at least one instruction", )); } for instruction in &request.plan.instructions { if instruction.program_id.0 != kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID { return std::result::Result::Err(kb_core::Error::new( "metaplex_preflight_program_mismatch", "Every Metaplex plan instruction must target Token Metadata", )); } if instruction.operation_code != request.plan.operation_code { return std::result::Result::Err(kb_core::Error::new( "metaplex_preflight_operation_mismatch", "Metaplex plan and instruction operation codes must match", )); } } let deprecated = kb_lib::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES .contains(&request.plan.operation_code.as_str()); if deprecated && !request.allow_deprecated_operation { return std::result::Result::Err(kb_core::Error::new( "metaplex_preflight_deprecated_operation_not_approved", "Deprecated Metaplex execution requires explicit operator approval", )); } let mut inspected = std::collections::BTreeMap::::new(); let mut context_slot = 0_u64; for snapshot in &request.snapshots { if snapshot.commitment != "confirmed" { return std::result::Result::Err(kb_core::Error::new( "metaplex_preflight_commitment_mismatch", "Metaplex stateful snapshots must use confirmed commitment", )); } context_slot = context_slot.max(snapshot.context_slot); inspected .entry(snapshot.snapshot.account.0.clone()) .or_insert_with(|| return snapshot.snapshot.account.clone()); } return std::result::Result::Ok(crate::MetaplexTokenMetadataPreflightReport { operation_code: request.plan.operation_code.clone(), context_slot, inspected_accounts: inspected.into_values().collect(), checks: vec![ "program_id_exact".to_string(), "operation_code_exact".to_string(), "confirmed_state_snapshots".to_string(), "deprecated_policy_explicit".to_string(), "account_correlation_bounded".to_string(), ], }); } #[cfg(test)] mod tests { #[test] fn rejects_empty_plans_and_unapproved_deprecated_operations() { let mut plan = kb_lib::ExApiPreparedExecutionPlan { executor_name: "kb-lib".to_string(), executor_version: "0".to_string(), intent_id: "i".to_string(), operation_code: kb_lib::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION.to_string(), fee_payer: kb_lib::MdPubkey("11111111111111111111111111111111".to_string()), instructions: vec![], required_signers: vec![], policy: std::default::Default::default(), requested_spend_lamports: 0, requested_compute_unit_price_micro_lamports: std::option::Option::None, }; let empty = crate::MetaplexTokenMetadataPreflightRequest { plan: plan.clone(), snapshots: vec![], allow_deprecated_operation: false, }; assert!(crate::inspect_metaplex_token_metadata_preflight(&empty).is_err()); plan.instructions.push(kb_lib::ExApiPlannedInstruction { program_id: kb_lib::MdProgramId( kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(), ), operation_code: plan.operation_code.clone(), accounts: vec![], data: vec![14], }); let deprecated = crate::MetaplexTokenMetadataPreflightRequest { plan, snapshots: vec![], allow_deprecated_operation: false, }; assert!(crate::inspect_metaplex_token_metadata_preflight(&deprecated).is_err()); } }