// file: kb-pipeline-demo-scenarios/src/solana_token_2022_metadata_campaign.rs // version: 3 //! Ordered confirmed Devnet campaign for the five Token-2022 Token Metadata instructions. /// One confirmed Token Metadata campaign step and its authoritative postcondition. #[derive(Clone, Debug, PartialEq)] pub struct DevnetToken2022MetadataCampaignStepSummary { /// Stable step identifier. pub step_id: std::string::String, /// Canonical operation code built by `kb-lib`. pub operation_code: std::string::String, /// Complete simulation, submission, replay and instruction-materialization evidence. pub execution: crate::DevnetSplToken2022ExecutionSummary, /// Authoritative mint snapshot read at or after the confirmed transaction slot. pub stateful_snapshot: kb_pipeline::Token2022StatefulReadResult, /// Validated return-data evidence for the `Emit` step only. pub emit_evidence: std::option::Option, /// Explicit stateful or return-data postcondition. pub postcondition: kb_pipeline::Token2022ExecutionPostcondition, } /// Complete evidence retained by the five-step Token Metadata campaign. #[derive(Clone, Debug, PartialEq)] pub struct DevnetToken2022MetadataCampaignSummary { /// Fresh Token-2022 mint prepared for this campaign. pub fixture: crate::Token2022MetadataFixturePreparationSummary, /// Five confirmed steps in interface order. pub steps: std::vec::Vec, } /// Returns the exact ordered operation names exercised by the Devnet campaign. pub fn token_2022_metadata_campaign_operation_names() -> &'static [&'static str; 5] { return &[ "initialize_token_metadata", "update_token_metadata_field", "emit_token_metadata", "remove_token_metadata_key", "update_token_metadata_authority", ]; } /// Prepares a fresh mint and executes all five Token Metadata interface instructions on Devnet. #[allow(clippy::too_many_arguments)] pub async fn execute_devnet_token_2022_metadata_campaign( http_pool: &kb_onchain_transport::HttpEndpointPool, store: &S, profile: &kb_config::ProfileConfig, workspace_root: &std::path::Path, options: &crate::Token2022MetadataFixturePreparationOptions, observer: &O, ) -> kb_core::Result where S: kb_store::RawTransactionStore + kb_store::CoreExtractionStore + kb_store::DecodePipelineStore + Sync, O: crate::SolanaExecutionObserver, { if !options.operator_confirmed { return std::result::Result::Err(kb_core::Error::config( "Token-2022 Token Metadata Devnet campaign requires explicit operator confirmation", )); } let fixture = match crate::prepare_token_2022_metadata_fixture( http_pool, profile, workspace_root, options, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let operations = campaign_operations(&fixture); let decoders: std::vec::Vec> = std::vec![std::sync::Arc::new(kb_lib::DcSplToken2022Decoder)]; let materializers: std::vec::Vec> = std::vec![std::sync::Arc::new(kb_lib::MtMetadataToken2022Materializer)]; let mut steps = std::vec::Vec::with_capacity(operations.len()); for (index, (step_id, operation)) in operations.into_iter().enumerate() { let operation_code = operation.operation_code().to_string(); let mut request = crate::DevnetSplToken2022ExecutionRequest::new( format!("token-2022-metadata-devnet-{step_id}-{}", uuid::Uuid::new_v4()), operation.clone(), ); request.query_role = options.query_role.clone(); request.transaction_role = options.transaction_role.clone(); request.submit = true; request.operator_confirmed = true; request.post_validation_max_retries = 20; let execution = match crate::execute_devnet_spl_token_2022( http_pool, store, profile, workspace_root, &request, decoders.as_slice(), materializers.as_slice(), observer, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let confirmation = match execution.confirmation.as_ref() { std::option::Option::Some(value) if matches!( value.status, kb_lib::ExApiExecutionConfirmationStatus::Confirmed | kb_lib::ExApiExecutionConfirmationStatus::Finalized ) => { value }, std::option::Option::Some(value) => { return std::result::Result::Err(kb_core::Error::new( "token_2022_metadata_campaign_step_not_confirmed", format!("campaign step {step_id} stopped at {:?}", value.status), )); }, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "token_2022_metadata_campaign_confirmation_missing", format!("campaign step {step_id} has no confirmation evidence"), )); }, }; let diagnostic = match execution.post_execution.as_ref() { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "token_2022_metadata_campaign_post_execution_missing", format!("campaign step {step_id} has no post-execution diagnostic"), )); }, }; if !execution.simulation.success || !diagnostic.canonical_inserted || !diagnostic.core_extracted || !diagnostic.decode_replayed { return std::result::Result::Err(kb_core::Error::new( "token_2022_metadata_campaign_replay_incomplete", format!( "campaign step {step_id} did not complete simulation and canonical decode replay" ), )); } let idempotence_replay = match execution.idempotence_replay.as_ref() { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "token_2022_metadata_campaign_idempotence_missing", format!("campaign step {step_id} has no idempotence replay evidence"), )); }, }; if idempotence_replay.failed_inputs != 0 || idempotence_replay.processing_error_inputs != 0 || idempotence_replay.processors.iter().any(|processor| { return processor.failed != 0 || processor.processing_errors != 0 || processor.materialized_outputs != 0 || processor.materialization_refused != 0; }) { return std::result::Result::Err(kb_core::Error::new( "token_2022_metadata_campaign_idempotence_failed", format!("campaign step {step_id} did not produce a clean second replay"), )); } if !execution.plan.policy.post_execution_validation.materialization_required && operation_code != kb_lib::EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION { return std::result::Result::Err(kb_core::Error::new( "token_2022_metadata_campaign_materialization_policy_invalid", format!("campaign step {step_id} unexpectedly disabled materialization"), )); } if execution.plan.policy.post_execution_validation.materialization_required && execution.materializations.is_empty() { return std::result::Result::Err(kb_core::Error::new( "token_2022_metadata_campaign_materialization_missing", format!("campaign step {step_id} produced no metadata materialization"), )); } let stateful_snapshot = match kb_pipeline::read_token_2022_stateful_snapshot( http_pool, &kb_pipeline::Token2022StatefulReadRequest { query_role: options.query_role.clone(), account: fixture.mint.clone(), kind: kb_lib::DcToken2022StateKind::Mint, min_context_slot: confirmation.slot, max_data_bytes: kb_pipeline::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES, context: kb_pipeline::Token2022StatefulContext::default(), }, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let emit_evidence = match &operation { kb_lib::ExSplToken2022Operation::Instruction { value } => match value.as_ref() { kb_lib::ExSplTokenSingleOperation::EmitTokenMetadata { start, end, .. } => { match kb_pipeline::inspect_token_2022_metadata_emit_simulation( &execution.simulation, *start, *end, ) { std::result::Result::Ok(value) => std::option::Option::Some(value), std::result::Result::Err(error) => return std::result::Result::Err(error), } }, _ => std::option::Option::None, }, kb_lib::ExSplToken2022Operation::Batch { instructions: _ } => std::option::Option::None, }; let postcondition = campaign_postcondition( index, &fixture, &stateful_snapshot.snapshot, emit_evidence.as_ref(), ); if postcondition.status != kb_pipeline::Token2022ExecutionPostconditionStatus::Confirmed { return std::result::Result::Err(kb_core::Error::new( "token_2022_metadata_campaign_postcondition_failed", format!("campaign step {step_id}: {}", postcondition.diagnostic), )); } steps.push(crate::DevnetToken2022MetadataCampaignStepSummary { step_id: step_id.to_string(), operation_code, execution, stateful_snapshot, emit_evidence, postcondition, }); } return std::result::Result::Ok(crate::DevnetToken2022MetadataCampaignSummary { fixture, steps, }); } fn campaign_operations( fixture: &crate::Token2022MetadataFixturePreparationSummary, ) -> std::vec::Vec<(&'static str, kb_lib::ExSplToken2022Operation)> { let metadata = fixture.mint.clone(); let authority = fixture.initial_authority.clone(); return std::vec![ ( "token_2022_metadata_initialize", instruction(kb_lib::ExSplTokenSingleOperation::InitializeTokenMetadata { metadata: metadata.clone(), update_authority: authority.clone(), mint: metadata.clone(), mint_authority: authority.clone(), name: crate::TOKEN_2022_METADATA_CAMPAIGN_NAME.to_string(), symbol: crate::TOKEN_2022_METADATA_CAMPAIGN_SYMBOL.to_string(), uri: crate::TOKEN_2022_METADATA_CAMPAIGN_URI.to_string(), }), ), ( "token_2022_metadata_update_field", instruction(kb_lib::ExSplTokenSingleOperation::UpdateTokenMetadataField { metadata: metadata.clone(), update_authority: authority.clone(), field: kb_lib::ExSplTokenMetadataField::Key( crate::TOKEN_2022_METADATA_CAMPAIGN_KEY.to_string(), ), value: crate::TOKEN_2022_METADATA_CAMPAIGN_VALUE.to_string(), }), ), ( "token_2022_metadata_emit", instruction(kb_lib::ExSplTokenSingleOperation::EmitTokenMetadata { metadata: metadata.clone(), start: std::option::Option::None, end: std::option::Option::None, }), ), ( "token_2022_metadata_remove_key", instruction(kb_lib::ExSplTokenSingleOperation::RemoveTokenMetadataKey { metadata: metadata.clone(), update_authority: authority.clone(), key: crate::TOKEN_2022_METADATA_CAMPAIGN_KEY.to_string(), idempotent: false, }), ), ( "token_2022_metadata_update_authority", instruction(kb_lib::ExSplTokenSingleOperation::UpdateTokenMetadataAuthority { metadata, current_authority: authority, new_authority: std::option::Option::Some(fixture.final_authority.clone()), }), ), ]; } fn instruction(value: kb_lib::ExSplTokenSingleOperation) -> kb_lib::ExSplToken2022Operation { return kb_lib::ExSplToken2022Operation::Instruction { value: std::boxed::Box::new(value) }; } fn campaign_postcondition( step_index: usize, fixture: &crate::Token2022MetadataFixturePreparationSummary, snapshot: &kb_pipeline::Token2022StatefulSnapshotBundle, emit_evidence: std::option::Option<&kb_pipeline::Token2022MetadataEmitEvidence>, ) -> kb_pipeline::Token2022ExecutionPostcondition { if step_index == 4 { return kb_pipeline::inspect_token_2022_metadata_authority_postcondition( &fixture.mint, std::option::Option::Some(&fixture.final_authority), snapshot, ); } let value_fields = metadata_value_fields(snapshot); let confirmed = match step_index { 0 => value_fields.is_some_and(|value| { return required_fields_match(value, fixture.initial_authority.0.as_str()) && !contains_campaign_pair(value); }), 1 => value_fields.is_some_and(|value| { return required_fields_match(value, fixture.initial_authority.0.as_str()) && contains_campaign_pair(value); }), 2 => { let snapshot_matches = value_fields.is_some_and(|value| { return required_fields_match(value, fixture.initial_authority.0.as_str()) && contains_campaign_pair(value); }); let emit_matches = emit_evidence .and_then(|evidence| return evidence.decoded_metadata.as_ref()) .is_some_and(|value| { return required_fields_match(value, fixture.initial_authority.0.as_str()) && contains_campaign_pair(value); }); snapshot_matches && emit_matches }, 3 => value_fields.is_some_and(|value| { return required_fields_match(value, fixture.initial_authority.0.as_str()) && !contains_campaign_pair(value); }), _ => false, }; return kb_pipeline::Token2022ExecutionPostcondition { role: "metadata".to_string(), account: fixture.mint.clone(), status: if confirmed { kb_pipeline::Token2022ExecutionPostconditionStatus::Confirmed } else { kb_pipeline::Token2022ExecutionPostconditionStatus::Contradicted }, diagnostic: if confirmed { format!("Token-2022 metadata campaign postcondition {step_index} is confirmed") } else { format!("Token-2022 metadata campaign postcondition {step_index} is contradicted") }, }; } fn metadata_value_fields( snapshot: &kb_pipeline::Token2022StatefulSnapshotBundle, ) -> std::option::Option<&serde_json::Value> { return snapshot.outputs.iter().find_map(|output| { if output.family != kb_lib::MdMaterializedEventFamily::Metadata || output.payload_json.get("projectionKind").and_then(serde_json::Value::as_str) != std::option::Option::Some("token_metadata") { return std::option::Option::None; } return output.payload_json.get("valueFields"); }); } fn required_fields_match(value: &serde_json::Value, authority: &str) -> bool { return value.get("updateAuthority").and_then(serde_json::Value::as_str) == std::option::Option::Some(authority) && value.get("name").and_then(serde_json::Value::as_str) == std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_NAME) && value.get("symbol").and_then(serde_json::Value::as_str) == std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_SYMBOL) && value.get("uri").and_then(serde_json::Value::as_str) == std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_URI); } fn contains_campaign_pair(value: &serde_json::Value) -> bool { return value .get("additionalMetadata") .and_then(serde_json::Value::as_array) .is_some_and(|pairs| { return pairs.iter().any(|pair| { return pair.get("key").and_then(serde_json::Value::as_str) == std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_KEY) && pair.get("value").and_then(serde_json::Value::as_str) == std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_VALUE); }); }); } #[cfg(test)] mod tests { fn pubkey(byte: u8) -> kb_lib::MdPubkey { return kb_lib::MdPubkey(bs58::encode([byte; 32]).into_string()); } fn fixture() -> crate::Token2022MetadataFixturePreparationSummary { return crate::Token2022MetadataFixturePreparationSummary { mint_keypair_path: std::path::PathBuf::from("mint.json"), final_authority_keypair_path: std::path::PathBuf::from("authority.json"), mint: pubkey(1), initial_authority: pubkey(2), final_authority: pubkey(3), mint_space: 234, rent_budget_space: 512, rent_reserve_lamports: 1, preparation_signature: "signature".to_string(), initial_snapshot: kb_pipeline::Token2022StatefulReadResult { commitment: "confirmed".to_string(), context_slot: 1, snapshot: kb_pipeline::Token2022StatefulSnapshotBundle { account_key: pubkey(1).0, slot: 1, state_kind: "mint".to_string(), extension_names: vec!["metadata_pointer".to_string()], outputs: std::vec::Vec::new(), }, }, }; } #[test] fn campaign_contract_covers_five_interface_operations_once_in_order() { let operations = super::campaign_operations(&fixture()); assert_eq!( operations .iter() .map(|(_, operation)| return operation.operation_code()) .collect::>(), vec![ kb_lib::EX_SPL_TOKEN_2022_INITIALIZE_TOKEN_METADATA_OPERATION, kb_lib::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION, kb_lib::EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION, kb_lib::EX_SPL_TOKEN_2022_REMOVE_TOKEN_METADATA_KEY_OPERATION, kb_lib::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION, ] ); assert_eq!( crate::token_2022_metadata_campaign_operation_names(), &[ "initialize_token_metadata", "update_token_metadata_field", "emit_token_metadata", "remove_token_metadata_key", "update_token_metadata_authority", ] ); } #[test] fn campaign_pair_detection_is_exact_and_removal_is_observable() { let with_pair = serde_json::json!({ "additionalMetadata": [{ "key": crate::TOKEN_2022_METADATA_CAMPAIGN_KEY, "value": crate::TOKEN_2022_METADATA_CAMPAIGN_VALUE }] }); let without_pair = serde_json::json!({"additionalMetadata": []}); assert!(super::contains_campaign_pair(&with_pair)); assert!(!super::contains_campaign_pair(&without_pair)); } #[tokio::test] async fn optional_devnet_token_2022_metadata_campaign_from_env() { if std::env::var("KB_DEVNET_TOKEN_2022_METADATA_CAMPAIGN_TEST").ok().as_deref() != std::option::Option::Some("1") { return; } if std::env::var("KB_DEVNET_TOKEN_2022_METADATA_OPERATOR_CONFIRMED") .ok() .as_deref() != std::option::Option::Some("1") { panic!( "KB_DEVNET_TOKEN_2022_METADATA_OPERATOR_CONFIRMED=1 is required for the spending campaign" ); } let workspace_root = match std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent() { std::option::Option::Some(value) => value, std::option::Option::None => panic!("workspace root cannot be resolved"), }; let config_path = match std::env::var("KB_DEVNET_CONFIG_PATH") { std::result::Result::Ok(value) => { let path = std::path::PathBuf::from(value); if path.is_absolute() { path } else { workspace_root.join(path) } }, std::result::Result::Err(_) => workspace_root.join("config/example.config.json"), }; let config = kb_config::read_config_json_file_with_environment( config_path.as_path(), workspace_root, ) .unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}")); let profile_name = std::env::var("KB_DEVNET_PROFILE").ok(); let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref()) .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); let database_url = std::env::var("KB_POSTGRES_TEST_URL") .unwrap_or_else(|error| panic!("KB_POSTGRES_TEST_URL is required: {error}")); profile.database.backend = "postgres".to_string(); profile.database.postgres.url = database_url; let pool = kb_onchain_transport::HttpEndpointPool::from_profile(&profile) .unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}")); let store_options = kb_store::PostgresStoreOptions::new( profile.database.postgres.url.clone(), profile.database.postgres.max_connections, profile.database.postgres.connect_timeout_ms, false, ) .unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}")); let store = kb_store::PostgresStore::connect(store_options) .await .unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}")); if let std::result::Result::Err(error) = store.initialize_store_schema().await { panic!("PostgreSQL schema initialization failed: {error}"); } let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let wallet_dir = if configured_wallet_dir.is_absolute() { configured_wallet_dir } else { workspace_root.join(configured_wallet_dir) }; let mut options = crate::Token2022MetadataFixturePreparationOptions::new(wallet_dir); options.operator_confirmed = true; let summary = crate::execute_devnet_token_2022_metadata_campaign( &pool, &store, &profile, workspace_root, &options, &crate::NoopSolanaExecutionObserver, ) .await .unwrap_or_else(|error| { panic!("Token-2022 Token Metadata Devnet campaign failed: {error}") }); assert_eq!(summary.steps.len(), 5); assert!(summary.steps.iter().all(|step| { return step.postcondition.status == kb_pipeline::Token2022ExecutionPostconditionStatus::Confirmed; })); println!( "TOKEN_2022_METADATA_FIXTURE mint={} preparation_signature={} initial_authority={} final_authority={}", summary.fixture.mint.0, summary.fixture.preparation_signature, summary.fixture.initial_authority.0, summary.fixture.final_authority.0, ); for step in &summary.steps { let signature = step .execution .confirmation .as_ref() .map(|value| return value.signature.0.as_str()) .unwrap_or("missing"); let slot = step.execution.confirmation.as_ref().and_then(|value| return value.slot); println!( "TOKEN_2022_METADATA_STEP id={} operation={} signature={} slot={:?} postcondition={:?} materializations={} emit_bytes={}", step.step_id, step.operation_code, signature, slot, step.postcondition.status, step.execution.materializations.len(), step.emit_evidence.as_ref().map(|value| return value.data.len()).unwrap_or(0), ); } } }