// file: kb-app-demo-desktop/src/demo_spl_ata.rs // version: 13 //! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads. use ts_rs::TS; // rust-rules: derive-import /// Request for one representative ATA creation on Devnet. #[derive(Clone, Debug, serde::Deserialize, TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaRequest.ts" )] pub(crate) struct DemoExecutionSplAtaRequest { /// Selected Devnet profile. pub(crate) profile_name: std::string::String, /// Wallet owner used by canonical derivation. pub(crate) wallet_owner: std::string::String, /// Mint account. pub(crate) mint: std::string::String, /// `classic` or `token_2022`. pub(crate) token_program: std::string::String, /// `create` or `create_idempotent`. pub(crate) mode: std::string::String, /// Whether to sign and submit after simulation. pub(crate) submit: bool, /// Explicit operator confirmation. pub(crate) operator_confirmed: bool, /// Whether post-execution outputs should be force-replayed. pub(crate) force_post_validation_replay: bool, } /// Request used to derive the representative wallet ATA before execution. #[derive(Clone, Debug, serde::Deserialize, TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationRequest.ts" )] pub(crate) struct DemoSplAtaDerivationRequest { /// Selected Devnet profile. pub(crate) profile_name: std::string::String, /// Mint account. pub(crate) mint: std::string::String, /// `classic` or `token_2022`. pub(crate) token_program: std::string::String, } /// Readonly payer, wallet, Token Program and derived ATA values. #[derive(Clone, Debug, serde::Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationPayload.ts" )] pub(crate) struct DemoSplAtaDerivationPayload { /// Profile wallet used as payer. pub(crate) payer: std::string::String, /// Wallet owner selected by the representative panel. pub(crate) wallet_owner: std::string::String, /// Exact Token Program ID. pub(crate) token_program_id: std::string::String, /// Canonically derived ATA. pub(crate) associated_token_account: std::string::String, } /// UI-safe ATA execution result. #[derive(Clone, Debug, serde::Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaSummaryPayload.ts" )] pub(crate) struct DemoExecutionSplAtaSummaryPayload { /// Stable operation code. pub(crate) operation: std::string::String, /// Profile name. pub(crate) profile_name: std::string::String, /// Profile wallet public key. pub(crate) wallet_public_key: std::string::String, /// Target Token Program ID. pub(crate) token_program_id: std::string::String, /// Derived ATA from the exact plan. pub(crate) associated_token_account: std::string::String, /// Wallet balance as lossless JSON text. pub(crate) balance_lamports: std::string::String, /// Fee estimate as lossless JSON text. pub(crate) fee_lamports: std::option::Option, /// Aggregate stateful readiness. pub(crate) readiness_status: std::string::String, /// Exact simulation outcome. pub(crate) simulation_success: bool, /// Runtime simulation error. pub(crate) simulation_error: std::option::Option, /// Submitted signature. pub(crate) transaction_signature: std::option::Option, /// Terminal confirmation status. pub(crate) confirmation_status: std::option::Option, /// Number of ATA-owned materialized facts. pub(crate) materialization_count: u32, /// Whether the second replay created no output. pub(crate) idempotence_validated: bool, /// Exact plan JSON. pub(crate) plan_json: std::string::String, /// Stateful readiness JSON. pub(crate) readiness_json: std::string::String, /// Simulation JSON. pub(crate) simulation_json: std::string::String, /// Confirmation and replay diagnostics JSON. pub(crate) diagnostics_json: std::string::String, } /// Bounded ATA lifecycle journal request. #[derive(Clone, Debug, serde::Deserialize, TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRequest.ts" )] pub(crate) struct DemoSplAtaJournalRequest { /// Selected Devnet profile. pub(crate) profile_name: std::string::String, /// Optional partial signature. pub(crate) signature_contains: std::option::Option, /// Optional exact mint. pub(crate) mint: std::option::Option, /// Optional exact ATA. pub(crate) associated_token_account: std::option::Option, /// Optional exact materialized operation. pub(crate) operation: std::option::Option, /// Maximum returned rows. pub(crate) limit: u32, } /// One UI-safe ATA lifecycle journal row. #[derive(Clone, Debug, serde::Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts( export, export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRow.ts" )] pub(crate) struct DemoSplAtaJournalRow { /// Transaction signature. pub(crate) signature: std::string::String, /// Slot as lossless JSON text. pub(crate) slot: std::string::String, /// Materialized family. pub(crate) family: std::string::String, /// Lifecycle operation. pub(crate) operation: std::option::Option, /// Exact mint when present. pub(crate) mint: std::option::Option, /// Exact ATA when present. pub(crate) associated_token_account: std::option::Option, /// Full bounded payload JSON. pub(crate) payload_json: std::string::String, } /// Derives the representative profile wallet ATA without exposing private key material. pub(crate) async fn demo_spl_ata_derive( state: tauri::State<'_, crate::AppState>, request: crate::DemoSplAtaDerivationRequest, ) -> std::result::Result { let profile = match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let wallet = match load_profile_wallet(&profile).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let wallet_owner = wallet.public_key(); let token_program = match parse_token_program(&request.token_program) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let associated_token_account = match derive_ata(wallet_owner.as_str(), request.mint.trim(), token_program.program_id()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(crate::DemoSplAtaDerivationPayload { payer: wallet_owner.clone(), wallet_owner, token_program_id: token_program.program_id().to_string(), associated_token_account, }); } /// Executes one representative Create or CreateIdempotent ATA flow. pub(crate) async fn demo_execution_spl_ata_execute( app_handle: tauri::AppHandle, state: tauri::State<'_, crate::AppState>, request: crate::DemoExecutionSplAtaRequest, ) -> std::result::Result { let acquired = state.demo_execution_solana_core_running().compare_exchange( false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire, ); if acquired.is_err() { return std::result::Result::Err("a Devnet execution is already running".to_string()); } let _run_guard = crate::DemoExecutionRunGuard { running: state.demo_execution_solana_core_running(), }; state .demo_execution_solana_core_cancel_requested() .store(false, std::sync::atomic::Ordering::Release); let profile = match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let profile_wallet = match load_profile_wallet(&profile).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if request.wallet_owner.trim() != profile_wallet.public_key() { return std::result::Result::Err( "the representative ATA panel requires the profile wallet as wallet owner".to_string(), ); } let token_program = match parse_token_program(&request.token_program) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let wallet_owner = ks_lib::MdPubkey(request.wallet_owner.trim().to_string()); let mint = ks_lib::MdPubkey(request.mint.trim().to_string()); let operation = match request.mode.as_str() { "create" => ks_lib::ExSplAssociatedTokenAccountOperation::Create { wallet_owner, mint, token_program, }, "create_idempotent" => ks_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent { wallet_owner, mint, token_program, }, _ => { return std::result::Result::Err( "ATA mode must be create or create_idempotent".to_string(), ); }, }; let mut pipeline_request = ks_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionRequest::new( format!("demo-spl-ata-execution-{}", chrono::Utc::now().timestamp_micros()), operation, ); pipeline_request.submit = request.submit; pipeline_request.operator_confirmed = request.operator_confirmed; pipeline_request.post_validation_max_retries = 20; pipeline_request.force_post_validation_replay = request.force_post_validation_replay; let http_pool = match ks_onchain_transport::HttpEndpointPool::from_profile(&profile) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let store = match crate::connect_postgres_store(&profile).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; if let std::result::Result::Err(error) = store.initialize_store_schema().await { return std::result::Result::Err(error.to_string()); } let decoders: std::vec::Vec> = std::vec![ std::sync::Arc::new(ks_lib::DcSplAssociatedTokenAccountDecoder), std::sync::Arc::new(ks_lib::DcSplTokenDecoder), ]; let materializers: std::vec::Vec> = std::vec![ std::sync::Arc::new(ks_lib::MtTokenAccountsMaterializer), std::sync::Arc::new(ks_lib::MtRiskMaterializer), ]; let observer = crate::DemoExecutionSolanaCoreObserver { app_handle, cancel_requested: state.demo_execution_solana_core_cancel_requested(), }; let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_associated_token_account( &http_pool, &store, &profile, crate::workspace_root_dir().as_path(), &pipeline_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.to_string()), }; return std::result::Result::Ok(summary_payload(summary)); } /// Loads a bounded journal of ATA-owned lifecycle facts. pub(crate) async fn demo_spl_ata_journal( state: tauri::State<'_, crate::AppState>, request: crate::DemoSplAtaJournalRequest, ) -> std::result::Result, std::string::String> { if request.limit == 0 || request.limit > 500 { return std::result::Result::Err("ATA journal limit must be between 1 and 500".to_string()); } let profile = match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let store = match crate::connect_postgres_store(&profile).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let filter = match ks_store::MaterializedEventFilter::new( std::option::Option::Some("materializer.token.accounts".to_string()), std::option::Option::None, request.signature_contains.clone(), 500, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let rows = match ks_store::DecodePipelineStore::list_materialized_events(&store, &filter).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let mut output = std::vec::Vec::new(); for row in rows { if row.source_decoder_name != "spl.associated_token_account" || !journal_matches(&row.payload_json, &request) { continue; } output.push(journal_row(row)); if output.len() >= request.limit as usize { break; } } return std::result::Result::Ok(output); } async fn load_profile_wallet( profile: &ks_config::ProfileConfig, ) -> std::result::Result { let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let directory = if configured.is_absolute() { configured } else { crate::workspace_root_dir().join(configured) }; let store = match ks_wallet::TemporaryWalletStore::new(directory) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let alias = match ks_wallet::WalletAlias::parse(profile.wallet.temporary_wallet_alias.clone()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; return match store.load_or_create(alias).await { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(error) => std::result::Result::Err(error.to_string()), }; } fn parse_token_program( value: &str, ) -> std::result::Result { return match value.trim() { "classic" => std::result::Result::Ok(ks_lib::ExSplAssociatedTokenProgram::Classic), "token_2022" => std::result::Result::Ok(ks_lib::ExSplAssociatedTokenProgram::Token2022), _ => std::result::Result::Err("Token Program must be classic or token_2022".to_string()), }; } fn derive_ata( wallet: &str, mint: &str, token_program: &str, ) -> std::result::Result { let wallet: solana_pubkey::Pubkey = match wallet.parse() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(format!("invalid wallet: {error}")); }, }; let mint: solana_pubkey::Pubkey = match mint.parse() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(format!("invalid mint: {error}")); }, }; let token_program: solana_pubkey::Pubkey = match token_program.parse() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(format!("invalid Token Program: {error}")); }, }; return std::result::Result::Ok( spl_associated_token_account_interface::address::get_associated_token_address_with_program_id( &wallet, &mint, &token_program, ) .to_string(), ); } fn summary_payload( summary: ks_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionSummary, ) -> crate::DemoExecutionSplAtaSummaryPayload { let (associated_token_account, token_program_id) = match summary.plan.instructions.first() { std::option::Option::Some(instruction) => { let ata = match instruction.accounts.get(1) { std::option::Option::Some(account) => account.pubkey.0.clone(), std::option::Option::None => std::string::String::new(), }; let token_program = match instruction.accounts.last() { std::option::Option::Some(account) => account.pubkey.0.clone(), std::option::Option::None => std::string::String::new(), }; (ata, token_program) }, std::option::Option::None => (std::string::String::new(), std::string::String::new()), }; let transaction_signature = summary.send_result.as_ref().map(|value| return value.signature.0.clone()); let confirmation_status = summary.confirmation.as_ref().map(|value| { return crate::confirmation_status_code(value.status); }); let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| { return replay.failed_inputs == 0 && replay.processing_error_inputs == 0 && replay.processors.iter().all(|processor| { return processor.failed == 0 && processor.processing_errors == 0 && processor.materialized_outputs == 0 && processor.materialization_refused == 0; }); }); let materialization_count = match u32::try_from(summary.materializations.len()) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => u32::MAX, }; let diagnostics = serde_json::json!({ "confirmation": summary.confirmation.as_ref(), "postStateValidation": summary.post_state_validation.as_ref(), "postExecution": summary.post_execution.as_ref(), "backfill": summary.backfill.as_ref().map(|value| { return serde_json::json!({ "candidatesCompleted": value.candidates_completed, "candidatesCancelled": value.candidates_cancelled, "candidatesNotStarted": value.candidates_not_started, "canonicalInserted": value.canonical_inserted, "canonicalSkipped": value.canonical_skipped, "existingSkipped": value.existing_skipped, "missing": value.missing, "failed": value.failed }); }), "coreExtraction": summary.core_extraction.as_ref().map(|value| { return serde_json::json!({ "selected": value.selected, "extracted": value.extracted, "skipped": value.skipped, "failed": value.failed, "cancelled": value.cancelled }); }), "decodeReplay": summary.decode_replay.as_ref().map(|value| { return serde_json::json!({ "campaignId": value.campaign_id, "selected": value.selected, "completed": value.completed, "unmatched": value.unmatched, "failedInputs": value.failed_inputs, "processingErrorInputs": value.processing_error_inputs, "cancelled": value.cancelled }); }), "idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| { return serde_json::json!({ "campaignId": value.campaign_id, "selected": value.selected, "completed": value.completed, "unmatched": value.unmatched, "failedInputs": value.failed_inputs, "processingErrorInputs": value.processing_error_inputs, "cancelled": value.cancelled, "materializedOutputs": value.processors.iter().map(|processor| { return processor.materialized_outputs; }).sum::(), "materializationRefused": value.processors.iter().map(|processor| { return processor.materialization_refused; }).sum::() }); }), "materializations": summary.materializations.iter().map(|value| { return serde_json::json!({ "processorName": value.processor_name, "outputKey": value.output_key, "family": value.materialized_family, "signature": value.signature, "slot": value.slot.to_string(), "payload": value.payload_json }); }).collect::>() }); let plan_json = crate::pretty_json(&summary.plan); let readiness_json = crate::pretty_json(&summary.stateful_readiness); let simulation_json = crate::pretty_json(&summary.simulation); let diagnostics_json = crate::pretty_json(&diagnostics); return crate::DemoExecutionSplAtaSummaryPayload { operation: summary.stateful_readiness.operation_code, profile_name: summary.profile_name, wallet_public_key: summary.wallet.public_key, token_program_id, associated_token_account, balance_lamports: summary.balance_lamports.to_string(), fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()), readiness_status: match summary.stateful_readiness.status { ks_pipeline::SplAssociatedTokenAccountStatefulReadinessStatus::Ready => { "ready".to_string() }, ks_pipeline::SplAssociatedTokenAccountStatefulReadinessStatus::Blocked => { "blocked".to_string() }, }, simulation_success: summary.simulation.success, simulation_error: summary.simulation.error, transaction_signature, confirmation_status, materialization_count, idempotence_validated, plan_json, readiness_json, simulation_json, diagnostics_json, }; } fn journal_matches(payload: &serde_json::Value, request: &crate::DemoSplAtaJournalRequest) -> bool { for (expected, key) in [ (&request.operation, "operation"), (&request.mint, "mint"), (&request.associated_token_account, "associatedTokenAccount"), ] { if expected.as_ref().is_some_and(|value| { return payload.get(key).and_then(serde_json::Value::as_str) != std::option::Option::Some(value.as_str()); }) { return false; } } return true; } fn journal_row(row: ks_store::MaterializedEventQueryRow) -> crate::DemoSplAtaJournalRow { let text = |key: &str| { return row .payload_json .get(key) .and_then(serde_json::Value::as_str) .map(std::string::ToString::to_string); }; let operation = text("operation"); let mint = text("mint"); let associated_token_account = text("associatedTokenAccount"); let payload_json = crate::pretty_json(&row.payload_json); return crate::DemoSplAtaJournalRow { signature: row.signature, slot: row.slot.to_string(), family: row.materialized_family, operation, mint, associated_token_account, payload_json, }; } #[cfg(test)] mod tests { #[test] fn classic_and_token_2022_derivations_are_distinct_and_canonical() { let wallet = ks_program_ids::SYSTEM_PROGRAM_ID; let mint = "So11111111111111111111111111111111111111112"; let classic = super::derive_ata(wallet, mint, ks_program_ids::SPL_TOKEN_PROGRAM_ID) .unwrap_or_else(|error| panic!("classic derivation failed: {error}")); let token_2022 = super::derive_ata(wallet, mint, ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID) .unwrap_or_else(|error| panic!("Token-2022 derivation failed: {error}")); assert_eq!(classic, "aqxoAhCwpy3oB1BpNw9hL1HdLYLgPpbPjzxDrrQj3Fs"); assert_eq!(token_2022, "2sZUUBGq1i6aE47ZoxCaCW89jmYm2EXLPPmNMgMDXHMS"); } #[test] fn lifecycle_journal_filters_are_exact() { let request = crate::DemoSplAtaJournalRequest { profile_name: "local_devnet".to_string(), signature_contains: std::option::Option::None, mint: std::option::Option::Some("mint111".to_string()), associated_token_account: std::option::Option::Some("ata111".to_string()), operation: std::option::Option::Some("create_idempotent".to_string()), limit: 100, }; assert!(super::journal_matches( &serde_json::json!({ "operation": "create_idempotent", "mint": "mint111", "associatedTokenAccount": "ata111" }), &request, )); assert!(!super::journal_matches( &serde_json::json!({ "operation": "create", "mint": "mint111", "associatedTokenAccount": "ata111" }), &request, )); } }