v0.2.6-pre.008

This commit is contained in:
2026-08-21 11:36:28 +02:00
parent 787df2861f
commit 1a9edd14e1
19 changed files with 902 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/src/app_state.rs
// version: 8
// version: 9
//! Shared backend state owned by the Wallet Desk Tauri application.
@@ -11,6 +11,7 @@ pub(crate) struct AppState {
splash_sequence_started: std::sync::atomic::AtomicBool,
transport_runtime: crate::TransportRuntime,
wallet_config_startup: crate::WalletConfigStartup,
wallet_import_source: std::sync::Mutex<std::option::Option<crate::PendingWalletImport>>,
wallet_session: std::sync::Mutex<crate::WalletSession>,
}
@@ -69,6 +70,7 @@ impl AppState {
splash_sequence_started: std::sync::atomic::AtomicBool::new(false),
transport_runtime,
wallet_config_startup,
wallet_import_source: std::sync::Mutex::new(std::option::Option::None),
wallet_session: std::sync::Mutex::new(crate::WalletSession::no_selection()),
});
}
@@ -124,7 +126,7 @@ impl AppState {
effective_wallets_directory_created_on_startup: self.wallet_config_startup.effective_directory_created_on_startup(),
fallback_logging_active: runtime.fallback_active,
root_wallets_directory_created_on_startup: self.wallet_config_startup.root_directory_created_on_startup(),
shell_phase: "pre.007-wallet-balance".to_owned(),
shell_phase: "pre.008-wallet-import".to_owned(),
startup_diagnostic: runtime.startup_diagnostic.clone(),
transport_available_endpoint_count,
transport_clusters: self.transport_runtime.clusters(),
@@ -220,6 +222,102 @@ impl AppState {
return std::result::Result::Ok(dto);
}
/// Replaces any previous native-picker transfer source with one newly validated bounded Rust-only source.
pub(crate) async fn stage_import_source(
&self,
source: std::path::PathBuf,
format: crate::WalletTransferFormatDto,
) -> ksp_core_lib::Result<crate::WalletTransferInspectionDto> {
let staged = crate::stage_wallet_import_source(source, format).await;
let staged = match staged {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let dto = staged.safe_projection();
let slot = self.wallet_import_source.lock();
let mut slot = match slot {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(import_source_lock_error()),
};
let previous = std::mem::replace(&mut *slot, std::option::Option::Some(staged));
drop(previous);
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_IMPORT, transfer_format = dto.format.code(), "Wallet transfer source inspected and staged in Rust-only memory");
return std::result::Result::Ok(dto);
}
/// Clears any staged external transfer source and zeroizes its bounded secret bytes.
pub(crate) fn clear_import_source(&self) -> ksp_core_lib::Result<()> {
let slot = self.wallet_import_source.lock();
let mut slot = match slot {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(import_source_lock_error()),
};
let previous = slot.take();
drop(previous);
return std::result::Result::Ok(());
}
/// Imports the staged Solana transfer bytes into a new root-scoped native Wallet and retains the returned OWNER handle only in Rust.
pub(crate) async fn import_wallet(&self, request: crate::WalletImportRequestDto) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
let pending = {
let slot = self.wallet_import_source.lock();
let mut slot = match slot {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(import_source_lock_error()),
};
slot.take()
};
let pending = match pending {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_WALLET_IMPORT_SOURCE_MISSING,
"Wallet import requires one inspected native-picker transfer source",
));
},
};
let deselected = self.deselect_wallet();
if let std::result::Result::Err(error) = deselected {
return std::result::Result::Err(error);
}
let crate::WalletImportRequestDto { alias, filename, initial_note, owner_password, view_password } = request;
let root = self.wallet_inventory_root().to_path_buf();
let destination = crate::new_wallet_destination(root.as_path(), filename.as_str());
let destination = match destination {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let view_enabled = view_password.is_some();
let owner_password = ksp_wallet_lib::OwnerPassword::new(owner_password);
let view_password = view_password.map(ksp_wallet_lib::ViewPassword::new);
let alias = normalize_optional_text(alias);
let note = normalize_optional_text(initial_note);
let note_texts = note.into_iter().collect::<std::vec::Vec<_>>();
let metadata = ksp_wallet_lib::WalletCreateMetadataV1::new(alias, note_texts);
let transfer_format = pending.wallet_format();
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_IMPORT, wallet_id = filename.as_str(), transfer_format = transfer_format.code(), view_enabled, "Wallet transfer import requested under effective Config root");
let imported =
ksp_wallet_lib::import_wallet_transfer_v1(destination.as_path(), pending.source_bytes(), transfer_format, owner_password, view_password, metadata)
.await;
let owner = match imported {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_IMPORT, wallet_id = filename.as_str(), transfer_format = transfer_format.code(), error_domain = error.code().domain(), error_code = error.code().code(), "Wallet transfer import failed");
return std::result::Result::Err(error);
},
};
let configured_secret_candidate_count = self.secret_candidate_count_or_zero(filename.as_str());
let dto = crate::owner_projection(filename.as_str(), view_enabled, configured_secret_candidate_count, &owner);
let session = self.wallet_session.lock();
let mut session = match session {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(session_lock_error()),
};
*session = crate::WalletSession::Owner { wallet_id: filename.clone(), path: destination, wallet: std::boxed::Box::new(owner) };
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_IMPORT, wallet_id = filename.as_str(), transfer_format = transfer_format.code(), view_enabled, "Wallet transfer imported and OWNER session opened");
return std::result::Result::Ok(dto);
}
/// Opens the selected locked Wallet with one manual VIEW password supplied frontend -> Rust.
pub(crate) async fn unlock_wallet_view_manual(&self, request: crate::WalletUnlockRequestDto) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
let context = self.begin_unlock_operation(crate::WalletUnlockCapability::View);
@@ -656,6 +754,10 @@ fn normalize_optional_text(value: std::option::Option<String>) -> std::option::O
});
}
fn import_source_lock_error() -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_LOCK_FAILED, "Wallet Desk staged import source lock is poisoned");
}
fn session_lock_error() -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_LOCK_FAILED, "Wallet Desk Wallet session state lock is poisoned");
}