// file: ks-pipeline-demo-scenarios/src/spl/token_2022/devnet_execution.rs // version: 9 //! Devnet Token-2022 execution with stateful and canonical post-validation. /// Complete request for one Devnet Token-2022 execution. #[derive(Clone, Debug, Eq, PartialEq)] pub struct DevnetSplToken2022ExecutionRequest { /// Stable caller-provided execution identifier. pub intent_id: std::string::String, /// Endpoint role used for state, balance, blockhash, fee and hydration calls. pub query_role: std::string::String, /// Endpoint role used for simulation, submission and confirmation polling. pub transaction_role: std::string::String, /// Exact typed Token-2022 operation. pub operation: ks_lib::ExSplToken2022Operation, /// Explicitly authorizes signing and submission after successful simulation. pub submit: bool, /// Explicit operator confirmation required by the active profile. pub operator_confirmed: bool, /// Number of `getTransaction` retries after the first hydration attempt. pub post_validation_max_retries: u32, /// Replaces existing core and decode outputs for the submitted signature. pub force_post_validation_replay: bool, } impl crate::DevnetSplToken2022ExecutionRequest { /// Creates a conservative simulation-only request. pub fn new( intent_id: impl std::convert::Into, operation: ks_lib::ExSplToken2022Operation, ) -> Self { return Self { intent_id: intent_id.into(), query_role: "http_queries".to_string(), transaction_role: "http_transactions".to_string(), operation, submit: false, operator_confirmed: false, post_validation_max_retries: 10, force_post_validation_replay: false, }; } /// Validates request-local bounds independently from one profile. pub fn validate(&self) -> ks_core::Result<()> { if self.intent_id.trim().is_empty() { return std::result::Result::Err(ks_core::Error::config( "Devnet Token-2022 execution intent id must not be empty", )); } if self.query_role.trim().is_empty() || self.transaction_role.trim().is_empty() { return std::result::Result::Err(ks_core::Error::config( "Devnet Token-2022 execution endpoint roles must not be empty", )); } if self.post_validation_max_retries > 20 { return std::result::Result::Err(ks_core::Error::config( "post-execution getTransaction retries must not exceed 20", )); } return std::result::Result::Ok(()); } } /// Complete result of one Devnet Token-2022 execution. #[derive(Clone, Debug, PartialEq)] pub struct DevnetSplToken2022ExecutionSummary { /// Profile used by the orchestration. pub profile_name: std::string::String, /// Exact classified cluster. pub cluster: ks_lib::ExApiExecutionCluster, /// Genesis hash returned by the selected endpoint. pub genesis_hash: std::string::String, /// Non-secret persistent wallet description. pub wallet: ks_wallet::WalletSummary, /// Wallet balance observed before planning. pub balance_lamports: u64, /// Stateful readiness report produced before plan simulation. pub stateful_preflight: ks_pipeline::Token2022PreflightReport, /// Exact prepared Token plan. pub plan: ks_lib::ExApiPreparedExecutionPlan, /// Recent blockhash used by the exact transaction. pub latest_blockhash: ks_onchain_transport::LatestBlockhashResult, /// Fee estimate for the exact compiled message. pub fee: ks_onchain_transport::FeeForMessageResult, /// Exact simulation result bound to the compiled message. pub simulation: ks_lib::ExApiExecutionSimulationResult, /// Submission result when explicitly authorized. pub send_result: std::option::Option, /// Confirmation result when submitted. pub confirmation: std::option::Option, /// Canonical hydration result for the exact signature. pub backfill: std::option::Option, /// Core extraction result for the exact signature. pub core_extraction: std::option::Option, /// First Token decode and materialization replay. pub decode_replay: std::option::Option, /// Second replay proving idempotence for the same decoder version and input. pub idempotence_replay: std::option::Option, /// Exact materialized rows produced for the submitted Token transaction. pub materializations: std::vec::Vec, /// Aggregated post-execution validation diagnostic. pub post_execution: std::option::Option, } struct PreparedToken2022Execution { wallet: ks_wallet::TemporaryWallet, unsigned: ks_lib::ExSolanaUnsignedTransaction, evidence: ks_lib::ExSolanaSimulationEvidence, summary: crate::DevnetSplToken2022ExecutionSummary, } /// Simulates one Devnet Token-2022 operation after stateful preflight. pub async fn simulate_devnet_spl_token_2022( http_pool: &ks_onchain_transport::HttpEndpointPool, profile: &ks_config::ProfileConfig, workspace_root: &std::path::Path, request: &crate::DevnetSplToken2022ExecutionRequest, observer: &O, ) -> ks_core::Result where O: crate::SolanaExecutionObserver, { if request.submit { return std::result::Result::Err(ks_core::Error::config( "simulate_devnet_spl_token_2022 requires submit=false", )); } let prepared = match prepare_execution(http_pool, profile, workspace_root, request, observer).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(prepared.summary); } /// Executes one Devnet Token-2022 simulation or authorized submission. #[allow(clippy::too_many_arguments)] pub async fn execute_devnet_spl_token_2022( http_pool: &ks_onchain_transport::HttpEndpointPool, store: &S, profile: &ks_config::ProfileConfig, workspace_root: &std::path::Path, request: &crate::DevnetSplToken2022ExecutionRequest, decoders: &[std::sync::Arc], materializers: &[std::sync::Arc], observer: &O, ) -> ks_core::Result where S: ks_store::RawTransactionStore + ks_store::CoreExtractionStore + ks_store::DecodePipelineStore + Sync, O: crate::SolanaExecutionObserver, { let prepared = match prepare_execution(http_pool, profile, workspace_root, request, observer).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if !request.submit { return std::result::Result::Ok(prepared.summary); } if !prepared.summary.simulation.success { return std::result::Result::Err(ks_core::Error::new( "execution_simulation_failed", crate::simulation_failure_message(&prepared.summary.simulation), )); } if let std::result::Result::Err(error) = validate_profile_wallet_signers( prepared.unsigned.required_signer_pubkeys(), prepared.summary.wallet.public_key.as_str(), ) { return std::result::Result::Err(error); } let send_evaluation = match ks_lib::ExSafetyChecker .evaluate_send(&prepared.summary.plan, &prepared.summary.simulation) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if send_evaluation.decision == ks_lib::ExSafetyDecision::Deny { return std::result::Result::Err(ks_core::Error::new( "execution_send_denied", crate::violation_message(send_evaluation.violations.as_slice()), )); } let signed = match prepared .unsigned .sign_after_simulation(&prepared.evidence, &[prepared.wallet.as_signer()]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if let std::result::Result::Err(error) = signed.verify_signatures() { return std::result::Result::Err(error); } let signature = signed.primary_signature().clone(); let mut summary = prepared.summary; let mut diagnostic = ks_lib::ExApiPostExecutionDiagnostic { signature: signature.clone(), canonical_inserted: false, core_extracted: false, decode_replayed: false, materialized: false, diagnostics: std::vec::Vec::new(), }; let send_config = match ks_onchain_transport::SendTransactionConfig::from_execution_config( &profile.execution, std::option::Option::Some(summary.latest_blockhash.context.slot), ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let sent = match http_pool .send_transaction_for_role( request.transaction_role.as_str(), signed.transaction_base64().as_str(), &signature, &send_config, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic.diagnostics.push(format!("Token-2022 submission failed: {error}")); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); }, }; summary.send_result = std::option::Option::Some(sent.to_execution_result(ks_lib::ExApiExecutionCluster::Devnet)); let confirmation_config = match ks_onchain_transport::ConfirmTransactionConfig::from_execution_config( &profile.execution, std::option::Option::Some(summary.latest_blockhash.last_valid_block_height), std::option::Option::Some(summary.latest_blockhash.context.slot), ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let confirmation = match http_pool .confirm_transaction_for_roles( request.transaction_role.as_str(), request.query_role.as_str(), ks_lib::ExApiExecutionCluster::Devnet, &signature, &confirmation_config, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic.diagnostics.push(format!("Token-2022 confirmation failed: {error}")); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); }, }; let confirmation_status = confirmation.status; summary.confirmation = std::option::Option::Some(confirmation); if !matches!( confirmation_status, ks_lib::ExApiExecutionConfirmationStatus::Confirmed | ks_lib::ExApiExecutionConfirmationStatus::Finalized ) { diagnostic.diagnostics.push(format!( "Token-2022 post-validation stopped at confirmation status {confirmation_status:?}" )); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); } let backfill = match crate::hydrate_signature( http_pool, store, profile, request.query_role.as_str(), request.post_validation_max_retries, observer, &signature, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic.diagnostics.push(format!("Token-2022 hydration failed: {error}")); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); }, }; diagnostic.canonical_inserted = crate::canonical_available(&backfill); summary.backfill = std::option::Option::Some(backfill); if !diagnostic.canonical_inserted { diagnostic.diagnostics.push( "confirmed Token-2022 transaction was unavailable for canonical hydration".to_string(), ); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); } let extraction = match ks_pipeline::execute_core_extraction( store, &ks_pipeline::CoreExtractionRequest { source: ks_pipeline::CoreExtractionSource::Signatures(std::vec![signature.0.clone()]), limit: 1, max_concurrent_extractions: 1, force_replay: request.force_post_validation_replay, }, observer, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic .diagnostics .push(format!("Token-2022 core extraction failed: {error}")); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); }, }; diagnostic.core_extracted = extraction.failed == 0 && !extraction.cancelled && extraction.selected == 1 && extraction.extracted.saturating_add(extraction.skipped) >= 1; summary.core_extraction = std::option::Option::Some(extraction); if !diagnostic.core_extracted { diagnostic .diagnostics .push("Token-2022 core extraction did not complete".to_string()); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); } let first_replay = match crate::replay_program( store, &signature, false, request.force_post_validation_replay, &[ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID], decoders, materializers, observer, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic.diagnostics.push(format!("Token-2022 decode replay failed: {error}")); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); }, }; diagnostic.decode_replayed = crate::decode_completed(&first_replay); summary.decode_replay = std::option::Option::Some(first_replay); let filter = match ks_store::MaterializedEventFilter::new( std::option::Option::None, std::option::Option::None, std::option::Option::Some(signature.0.clone()), 64, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let rows = match ks_store::DecodePipelineStore::list_materialized_events(store, &filter).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic .diagnostics .push(format!("Token-2022 materialization query failed: {error}")); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); }, }; summary.materializations = rows .into_iter() .filter(|row| return row.source_decoder_name == "spl.token_2022") .collect(); diagnostic.materialized = !summary.plan.policy.post_execution_validation.materialization_required || !summary.materializations.is_empty(); let second_replay = match crate::replay_program( store, &signature, true, request.force_post_validation_replay, &[ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID], decoders, materializers, observer, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { diagnostic .diagnostics .push(format!("Token-2022 idempotence replay failed: {error}")); summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); }, }; let idempotent = second_replay.failed_inputs == 0 && second_replay.processing_error_inputs == 0 && second_replay.processors.iter().all(|processor| { return processor.failed == 0 && processor.processing_errors == 0 && processor.materialized_outputs == 0 && processor.materialization_refused == 0; }); summary.idempotence_replay = std::option::Option::Some(second_replay); if !idempotent { diagnostic .diagnostics .push("second Token-2022 replay did not prove a clean idempotent skip".to_string()); } else if diagnostic.canonical_inserted && diagnostic.core_extracted && diagnostic.decode_replayed && diagnostic.materialized { diagnostic.diagnostics.push( "Token-2022 completed canonical hydration, core extraction, decode, materialization and idempotence validation" .to_string(), ); } summary.post_execution = std::option::Option::Some(diagnostic); return std::result::Result::Ok(summary); } async fn prepare_execution( http_pool: &ks_onchain_transport::HttpEndpointPool, profile: &ks_config::ProfileConfig, workspace_root: &std::path::Path, request: &crate::DevnetSplToken2022ExecutionRequest, observer: &O, ) -> ks_core::Result where O: crate::SolanaExecutionObserver, { if let std::result::Result::Err(error) = request.validate() { return std::result::Result::Err(error); } if let std::result::Result::Err(error) = validate_profile(profile, request) { return std::result::Result::Err(error); } if let std::result::Result::Err(error) = crate::ensure_not_cancelled(observer, "token_validate") { return std::result::Result::Err(error); } let genesis = match http_pool.get_genesis_hash_for_role(request.query_role.as_str()).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if genesis.classified_cluster != std::option::Option::Some(ks_lib::ExApiExecutionCluster::Devnet) { return std::result::Result::Err(ks_core::Error::new( "execution_cluster_mismatch", format!( "expected Devnet genesis hash but endpoint returned {} classified as {:?}", genesis.genesis_hash, genesis.classified_cluster ), )); } let preflight_request = match preflight_request(request) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let readiness = match ks_pipeline::inspect_token_2022_preflight(http_pool, &preflight_request).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let wallet = match crate::load_profile_wallet(profile, workspace_root).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let wallet_summary = wallet.summary(); let fee_payer = ks_lib::MdPubkey(wallet_summary.public_key.clone()); let balance = match http_pool .get_balance_for_role( request.query_role.as_str(), &fee_payer, &ks_onchain_transport::GetBalanceConfig::confirmed(), ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if balance.lamports < profile.execution.max_fee_lamports { return std::result::Result::Err(ks_core::Error::new( "execution_balance_insufficient", format!( "Devnet wallet balance {} is below the configured fee ceiling {}", balance.lamports, profile.execution.max_fee_lamports ), )); } let plan = match build_plan(profile, request, fee_payer.clone()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let plan_evaluation = match ks_lib::ExSafetyChecker.evaluate_prepared_plan(&plan) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if plan_evaluation.decision == ks_lib::ExSafetyDecision::Deny { return std::result::Result::Err(ks_core::Error::new( "execution_plan_denied", crate::violation_message(plan_evaluation.violations.as_slice()), )); } let latest_blockhash = match http_pool .get_latest_blockhash_for_role( request.query_role.as_str(), &ks_onchain_transport::GetLatestBlockhashConfig::confirmed(), ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let unsigned = match ks_lib::executor_solana_build_legacy_transaction( &plan, latest_blockhash.blockhash.as_str(), ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let fee = match http_pool .get_fee_for_message_for_role( request.query_role.as_str(), unsigned.message_base64().as_str(), &ks_onchain_transport::GetFeeForMessageConfig::new( ks_onchain_transport::RpcCommitmentLevel::Confirmed, std::option::Option::Some(latest_blockhash.context.slot), ), ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if fee.fee_lamports.is_none() { return std::result::Result::Err(ks_core::Error::new( "execution_fee_unavailable", "getFeeForMessage returned null for the selected recent blockhash", )); } let unsigned_base64 = match unsigned.transaction_base64() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let simulation_config = match ks_onchain_transport::SimulateTransactionConfig::new( ks_onchain_transport::RpcCommitmentLevel::Confirmed, false, false, std::option::Option::Some(latest_blockhash.context.slot), true, std::option::Option::None, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; crate::emit( observer, crate::SolanaExecutionProgressLevel::Info, "spl_token_2022_simulation", format!("simulating exact Token-2022 message {}", unsigned.message_hash()), std::option::Option::None, ); let simulation_rpc = match http_pool .simulate_transaction_for_role( request.transaction_role.as_str(), unsigned_base64.as_str(), &simulation_config, ) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let simulation = simulation_rpc.to_execution_result( ks_lib::ExApiExecutionCluster::Devnet, ks_lib::ExApiExecutionBlockhashKind::Latest, std::option::Option::Some( simulation_rpc.context.slot.saturating_sub(latest_blockhash.context.slot), ), std::option::Option::None, std::option::Option::None, std::option::Option::Some(&fee), ); let evidence = unsigned.bind_simulation(simulation.clone()); let summary = crate::DevnetSplToken2022ExecutionSummary { profile_name: profile.name.clone(), cluster: ks_lib::ExApiExecutionCluster::Devnet, genesis_hash: genesis.genesis_hash, wallet: wallet_summary, balance_lamports: balance.lamports, stateful_preflight: readiness, plan, latest_blockhash, fee, simulation, send_result: std::option::Option::None, confirmation: std::option::Option::None, backfill: std::option::Option::None, core_extraction: std::option::Option::None, decode_replay: std::option::Option::None, idempotence_replay: std::option::Option::None, materializations: std::vec::Vec::new(), post_execution: std::option::Option::None, }; return std::result::Result::Ok(PreparedToken2022Execution { wallet, unsigned, evidence, summary, }); } fn preflight_request( request: &crate::DevnetSplToken2022ExecutionRequest, ) -> ks_core::Result { let operation = match &request.operation { ks_lib::ExSplToken2022Operation::Instruction { value } => value.as_ref(), ks_lib::ExSplToken2022Operation::Batch { instructions: _ } => { return std::result::Result::Err(ks_core::Error::new( "execution_spl_token_2022_batch_not_supported", "Devnet Token-2022 validation scenarios require one non-batch operation", )); }, }; let mut requirements = std::vec::Vec::new(); match operation { ks_lib::ExSplTokenSingleOperation::MintToChecked { mint, destination, authority, amount: _, decimals, } => { requirements.push(mint_requirement("mint", mint, std::option::Option::Some(*decimals))); requirements.push(account_requirement( "destination", destination, mint, std::option::Option::None, )); if let std::result::Result::Err(error) = validate_single_authority(authority) { return std::result::Result::Err(error); } }, ks_lib::ExSplTokenSingleOperation::TransferChecked { source, mint, destination, authority, amount: _, decimals, } => { requirements.push(account_requirement( "source", source, mint, std::option::Option::Some(authority.authority.clone()), )); requirements.push(mint_requirement("mint", mint, std::option::Option::Some(*decimals))); requirements.push(account_requirement( "destination", destination, mint, std::option::Option::None, )); if let std::result::Result::Err(error) = validate_single_authority(authority) { return std::result::Result::Err(error); } }, ks_lib::ExSplTokenSingleOperation::ApproveChecked { source, mint, delegate: _, authority, amount: _, decimals, } => { requirements.push(account_requirement( "source", source, mint, std::option::Option::Some(authority.authority.clone()), )); requirements.push(mint_requirement("mint", mint, std::option::Option::Some(*decimals))); if let std::result::Result::Err(error) = validate_single_authority(authority) { return std::result::Result::Err(error); } }, ks_lib::ExSplTokenSingleOperation::Revoke { source, authority } => { requirements.push(account_requirement( "source", source, &ks_lib::MdPubkey(std::string::String::new()), std::option::Option::Some(authority.authority.clone()), )); requirements[0].expected_mint = std::option::Option::None; if let std::result::Result::Err(error) = validate_single_authority(authority) { return std::result::Result::Err(error); } }, ks_lib::ExSplTokenSingleOperation::BurnChecked { source, mint, authority, amount: _, decimals, } => { requirements.push(account_requirement( "source", source, mint, std::option::Option::Some(authority.authority.clone()), )); requirements.push(mint_requirement("mint", mint, std::option::Option::Some(*decimals))); if let std::result::Result::Err(error) = validate_single_authority(authority) { return std::result::Result::Err(error); } }, ks_lib::ExSplTokenSingleOperation::FreezeAccount { account, mint, authority } | ks_lib::ExSplTokenSingleOperation::ThawAccount { account, mint, authority } => { requirements.push(account_requirement( "account", account, mint, std::option::Option::None, )); requirements.push(mint_requirement("mint", mint, std::option::Option::None)); if let std::result::Result::Err(error) = validate_single_authority(authority) { return std::result::Result::Err(error); } }, ks_lib::ExSplTokenSingleOperation::CloseAccount { account, destination: _, authority } => { requirements.push(account_requirement( "account", account, &ks_lib::MdPubkey(std::string::String::new()), std::option::Option::Some(authority.authority.clone()), )); requirements[0].expected_mint = std::option::Option::None; if let std::result::Result::Err(error) = validate_single_authority(authority) { return std::result::Result::Err(error); } }, ks_lib::ExSplTokenSingleOperation::InitializeTokenMetadata { metadata, update_authority: _, mint, mint_authority: _, name: _, symbol: _, uri: _, } => { if metadata != mint { return std::result::Result::Err(ks_core::Error::new( "execution_spl_token_2022_metadata_mint_mismatch", "Token Metadata Initialize on Token-2022 requires metadata and mint to be the same account", )); } requirements.push(metadata_mint_requirement("metadata", metadata, "metadata_pointer")); }, ks_lib::ExSplTokenSingleOperation::UpdateTokenMetadataField { metadata, update_authority: _, field: _, value: _, } | ks_lib::ExSplTokenSingleOperation::RemoveTokenMetadataKey { metadata, update_authority: _, key: _, idempotent: _, } | ks_lib::ExSplTokenSingleOperation::UpdateTokenMetadataAuthority { metadata, current_authority: _, new_authority: _, } | ks_lib::ExSplTokenSingleOperation::EmitTokenMetadata { metadata, start: _, end: _ } => { requirements.push(metadata_mint_requirement("metadata", metadata, "token_metadata")); }, _ => { return std::result::Result::Err(ks_core::Error::new( "execution_spl_token_2022_devnet_operation_unsupported", format!( "Token-2022 Devnet validation does not expose {}", operation.operation_code() ), )); }, } return std::result::Result::Ok(ks_pipeline::Token2022PreflightRequest { query_role: request.query_role.clone(), min_context_slot: std::option::Option::None, max_accounts: ks_pipeline::MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS, max_total_data_bytes: ks_pipeline::MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES, requirements, elgamal_registry: std::option::Option::None, }); } fn validate_single_authority(authority: &ks_lib::ExSplTokenAuthority) -> ks_core::Result<()> { if !authority.multisig_signers.is_empty() { return std::result::Result::Err(ks_core::Error::new( "execution_spl_token_2022_devnet_multisig_not_supported", "the Devnet validation UI currently supports one profile-wallet authority", )); } return std::result::Result::Ok(()); } fn mint_requirement( role: &str, mint: &ks_lib::MdPubkey, decimals: std::option::Option, ) -> ks_pipeline::Token2022PreflightRequirement { return ks_pipeline::Token2022PreflightRequirement { role: role.to_string(), account: mint.clone(), kind: ks_lib::DcToken2022StateKind::Mint, max_data_bytes: 65_536, expected_mint: std::option::Option::None, expected_owner: std::option::Option::None, expected_decimals: decimals, required_extensions: std::vec::Vec::new(), context: ks_pipeline::Token2022StatefulContext::default(), }; } fn metadata_mint_requirement( role: &str, mint: &ks_lib::MdPubkey, required_extension: &str, ) -> ks_pipeline::Token2022PreflightRequirement { let mut requirement = mint_requirement(role, mint, std::option::Option::None); requirement.required_extensions = std::vec![required_extension.to_string()]; return requirement; } fn account_requirement( role: &str, account: &ks_lib::MdPubkey, mint: &ks_lib::MdPubkey, owner: std::option::Option, ) -> ks_pipeline::Token2022PreflightRequirement { return ks_pipeline::Token2022PreflightRequirement { role: role.to_string(), account: account.clone(), kind: ks_lib::DcToken2022StateKind::Account, max_data_bytes: 65_536, expected_mint: std::option::Option::Some(mint.clone()), expected_owner: owner, expected_decimals: std::option::Option::None, required_extensions: std::vec::Vec::new(), context: ks_pipeline::Token2022StatefulContext::default(), }; } fn validate_profile( profile: &ks_config::ProfileConfig, request: &crate::DevnetSplToken2022ExecutionRequest, ) -> ks_core::Result<()> { if profile.wallet.cluster != "devnet" { return std::result::Result::Err(ks_core::Error::config( "Token-2022 Devnet orchestration requires a Devnet wallet profile", )); } if !profile.wallet.temporary_wallet_enabled || !profile.wallet.temporary_wallet_persist { return std::result::Result::Err(ks_core::Error::config( "Token-2022 Devnet orchestration requires an enabled persistent temporary wallet", )); } if !profile.execution.require_simulation { return std::result::Result::Err(ks_core::Error::config( "Token-2022 Devnet orchestration requires simulation", )); } if request.submit && !profile.execution.devnet_send_enabled { return std::result::Result::Err(ks_core::Error::config( "Devnet transaction submission is disabled by the wallet profile", )); } if request.submit && profile.execution.require_operator_confirmation && !request.operator_confirmed { return std::result::Result::Err(ks_core::Error::config( "Token-2022 Devnet submission requires explicit operator confirmation", )); } return std::result::Result::Ok(()); } fn build_plan( profile: &ks_config::ProfileConfig, request: &crate::DevnetSplToken2022ExecutionRequest, fee_payer: ks_lib::MdPubkey, ) -> ks_core::Result { let materialization_required = operation_requires_materialization(&request.operation); let authorized_signers = std::vec![fee_payer.clone()]; let intent = ks_lib::ExSplToken2022ExecutionIntent { intent_id: request.intent_id.clone(), fee_payer, policy: ks_lib::ExApiExecutionPolicy { cluster: ks_lib::ExApiExecutionClusterPolicy { expected_cluster: ks_lib::ExApiExecutionCluster::Devnet, allow_mainnet: false, mainnet_confirmation: false, }, simulation: ks_lib::ExApiExecutionSimulationPolicy::Required, blockhash: ks_lib::ExApiExecutionBlockhashPolicy { kind: ks_lib::ExApiExecutionBlockhashKind::Latest, max_age_slots: std::option::Option::Some( profile.execution.recent_blockhash_max_age_slots, ), nonce_account: std::option::Option::None, nonce_authority: std::option::Option::None, }, cost_limit: ks_lib::ExApiExecutionCostLimit { max_spend_lamports: std::option::Option::Some( profile.execution.devnet_max_spend_lamports, ), max_fee_lamports: std::option::Option::Some(profile.execution.max_fee_lamports), max_compute_unit_price_micro_lamports: std::option::Option::Some( profile.execution.max_compute_unit_price_micro_lamports, ), }, authorized_signers, dry_run: !request.submit, post_execution_validation: ks_lib::ExApiPostExecutionValidationPolicy { canonical_insert_required: true, core_extraction_required: true, decode_replay_required: true, materialization_required, }, }, operation: request.operation.clone(), }; return ks_lib::ExApiTypedInstructionExecutor::build_prepared_plan( &ks_lib::ExSplToken2022Executor, &intent, ); } fn operation_requires_materialization(operation: &ks_lib::ExSplToken2022Operation) -> bool { return !matches!( operation, ks_lib::ExSplToken2022Operation::Instruction { value } if matches!(value.as_ref(), ks_lib::ExSplTokenSingleOperation::EmitTokenMetadata { .. }) ); } fn validate_profile_wallet_signers( required_signers: &[std::string::String], wallet_pubkey: &str, ) -> ks_core::Result<()> { if required_signers.iter().any(|value| return value.as_str() != wallet_pubkey) { return std::result::Result::Err(ks_core::Error::new( "execution_spl_token_2022_external_signer_unavailable", format!( "the Devnet Token orchestrator can sign only with profile wallet {}; required signers are {}", wallet_pubkey, required_signers.join(",") ), )); } return std::result::Result::Ok(()); } #[cfg(test)] mod tests { fn pubkey(byte: u8) -> ks_lib::MdPubkey { return ks_lib::MdPubkey(bs58::encode([byte; 32]).into_string()); } fn request( operation: ks_lib::ExSplTokenSingleOperation, ) -> crate::DevnetSplToken2022ExecutionRequest { return crate::DevnetSplToken2022ExecutionRequest::new( "metadata-preflight-test", ks_lib::ExSplToken2022Operation::Instruction { value: std::boxed::Box::new(operation) }, ); } #[test] fn metadata_preflight_requires_pointer_before_initialize_and_metadata_afterward() { let mint = pubkey(1); let initialize = request(ks_lib::ExSplTokenSingleOperation::InitializeTokenMetadata { metadata: mint.clone(), update_authority: pubkey(2), mint: mint.clone(), mint_authority: pubkey(2), name: "Token".to_string(), symbol: "TOK".to_string(), uri: "https://example.invalid/token.json".to_string(), }); let initialize_preflight = super::preflight_request(&initialize).unwrap_or_else(|error| panic!("{error}")); assert_eq!( initialize_preflight.requirements[0].required_extensions, vec!["metadata_pointer".to_string()] ); let update = request(ks_lib::ExSplTokenSingleOperation::UpdateTokenMetadataField { metadata: mint, update_authority: pubkey(2), field: ks_lib::ExSplTokenMetadataField::Key("campaign".to_string()), value: "0.4.8-pre.012".to_string(), }); let update_preflight = super::preflight_request(&update).unwrap_or_else(|error| panic!("{error}")); assert_eq!( update_preflight.requirements[0].required_extensions, vec!["token_metadata".to_string()] ); } #[test] fn metadata_initialize_rejects_external_metadata_account_and_emit_skips_materialization() { let invalid = request(ks_lib::ExSplTokenSingleOperation::InitializeTokenMetadata { metadata: pubkey(1), update_authority: pubkey(2), mint: pubkey(3), mint_authority: pubkey(2), name: "Token".to_string(), symbol: "TOK".to_string(), uri: "https://example.invalid/token.json".to_string(), }); assert!(super::preflight_request(&invalid).is_err()); let emit = ks_lib::ExSplToken2022Operation::Instruction { value: std::boxed::Box::new(ks_lib::ExSplTokenSingleOperation::EmitTokenMetadata { metadata: pubkey(1), start: std::option::Option::None, end: std::option::Option::None, }), }; assert!(!super::operation_requires_materialization(&emit)); } }