v0.5.2-pre.006-fix-010
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/app_state.rs
|
||||
// version: 16
|
||||
// version: 17
|
||||
|
||||
//! Shared Tauri application state and startup initialization.
|
||||
|
||||
@@ -30,6 +30,8 @@ pub(crate) struct AppState {
|
||||
demo_decode_replay_campaign_id: std::sync::Mutex<std::option::Option<std::string::String>>,
|
||||
demo_execution_solana_core_running: std::sync::atomic::AtomicBool,
|
||||
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool,
|
||||
demo_execution_wallet_alias_overrides:
|
||||
std::sync::Mutex<std::collections::BTreeMap<std::string::String, std::string::String>>,
|
||||
startup_sequence_started: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
@@ -115,6 +117,9 @@ impl crate::AppState {
|
||||
demo_decode_replay_campaign_id: std::sync::Mutex::new(std::option::Option::None),
|
||||
demo_execution_solana_core_running: std::sync::atomic::AtomicBool::new(false),
|
||||
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool::new(false),
|
||||
demo_execution_wallet_alias_overrides: std::sync::Mutex::new(
|
||||
std::collections::BTreeMap::new(),
|
||||
),
|
||||
startup_sequence_started: std::sync::atomic::AtomicBool::new(false),
|
||||
});
|
||||
}
|
||||
@@ -169,6 +174,46 @@ impl crate::AppState {
|
||||
return &self.active_profile;
|
||||
}
|
||||
|
||||
/// Returns the session-only execution-wallet alias override for one profile.
|
||||
pub(crate) fn demo_execution_wallet_alias_override(
|
||||
&self,
|
||||
profile_name: &str,
|
||||
) -> std::result::Result<std::option::Option<std::string::String>, std::string::String> {
|
||||
let lock_result = self.demo_execution_wallet_alias_overrides.lock();
|
||||
let guard = match lock_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(
|
||||
"demo execution wallet selection lock is poisoned".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(guard.get(profile_name).cloned());
|
||||
}
|
||||
|
||||
/// Sets or clears the session-only execution-wallet alias override for one profile.
|
||||
pub(crate) fn set_demo_execution_wallet_alias_override(
|
||||
&self,
|
||||
profile_name: std::string::String,
|
||||
alias: std::option::Option<std::string::String>,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
let lock_result = self.demo_execution_wallet_alias_overrides.lock();
|
||||
let mut guard = match lock_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(
|
||||
"demo execution wallet selection lock is poisoned".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
if let std::option::Option::Some(alias) = alias {
|
||||
guard.insert(profile_name, alias);
|
||||
} else {
|
||||
guard.remove(profile_name.as_str());
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
/// Returns the configured HTTP endpoint pool.
|
||||
pub(crate) fn http_pool(&self) -> &ks_onchain_transport::HttpEndpointPool {
|
||||
return &self.http_pool;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// file: kb-app-demo-desktop/src/demo_devnet_common.rs
|
||||
// version: 4
|
||||
// version: 6
|
||||
|
||||
//! Shared Devnet demo UI contracts.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
const DEMO_WALLET_PASSWORD_ENV: &str = "KB_SECRET_DEMO_WALLET_PASSWORD";
|
||||
|
||||
/// Readiness report for one Devnet profile PostgreSQL store.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -73,3 +75,58 @@ fn bounded_u32(value: usize) -> u32 {
|
||||
std::result::Result::Err(_) => u32::MAX,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns whether the backend-only demo wallet password is configured.
|
||||
pub(crate) fn demo_wallet_password_configured() -> bool {
|
||||
return std::env::var_os(DEMO_WALLET_PASSWORD_ENV).is_some();
|
||||
}
|
||||
|
||||
/// Loads the backend-only demo wallet password without exposing it to Tauri DTOs.
|
||||
pub(crate) fn demo_wallet_password()
|
||||
-> std::result::Result<ks_wallet::WalletPassword, std::string::String> {
|
||||
let password_text = match std::env::var(DEMO_WALLET_PASSWORD_ENV) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(format!(
|
||||
"backend secret {DEMO_WALLET_PASSWORD_ENV} is not configured as UTF-8"
|
||||
));
|
||||
},
|
||||
};
|
||||
return match ks_wallet::WalletPassword::new(password_text) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Resolves the temporary or persistent signer selected by one Devnet profile.
|
||||
pub(crate) async fn resolve_devnet_execution_wallet(
|
||||
state: &crate::AppState,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
) -> std::result::Result<ks_pipeline_demo_scenarios::DevnetExecutionWallet, std::string::String> {
|
||||
let alias_override = match state.demo_execution_wallet_alias_override(profile.name.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut effective_profile = profile.clone();
|
||||
if let std::option::Option::Some(alias) = alias_override {
|
||||
effective_profile.wallet.wallet_alias = std::option::Option::Some(alias);
|
||||
}
|
||||
let password = if effective_profile.wallet.wallet_alias.is_some() {
|
||||
match demo_wallet_password() {
|
||||
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
} else {
|
||||
std::option::Option::None
|
||||
};
|
||||
return match ks_pipeline_demo_scenarios::resolve_devnet_execution_wallet(
|
||||
&effective_profile,
|
||||
crate::workspace_root_dir().as_path(),
|
||||
password,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_metadata_metaplex_token_metadata.rs
|
||||
// version: 8
|
||||
// version: 11
|
||||
|
||||
//! Desktop adapters for generic and qualified Metaplex Token Metadata execution workflows.
|
||||
|
||||
@@ -235,22 +235,26 @@ pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_token_metadata(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary =
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_token_metadata_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
&execution_wallet,
|
||||
&execution_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()),
|
||||
};
|
||||
let transaction_signature =
|
||||
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
|
||||
let confirmation_status =
|
||||
@@ -417,7 +421,7 @@ async fn prepare_create_fixture_for_family(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let configured = std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured.is_absolute() {
|
||||
configured
|
||||
} else {
|
||||
@@ -1048,7 +1052,8 @@ pub(crate) async fn demo_execution_metadata_metaplex_execute_campaign(
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
@@ -1070,8 +1075,8 @@ pub(crate) async fn demo_execution_metadata_metaplex_execute_campaign(
|
||||
wallet_dir,
|
||||
&http_pool,
|
||||
&store,
|
||||
&state,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
@@ -1133,8 +1138,8 @@ fn execute_campaign_projection<'a, S, O>(
|
||||
wallet_dir: std::path::PathBuf,
|
||||
http_pool: &'a ks_onchain_transport::HttpEndpointPool,
|
||||
store: &'a S,
|
||||
state: &'a crate::AppState,
|
||||
profile: &'a ks_config::ProfileConfig,
|
||||
workspace_root: &'a std::path::Path,
|
||||
observer: &'a O,
|
||||
) -> CampaignProjectionFuture<'a>
|
||||
where
|
||||
@@ -1150,8 +1155,8 @@ where
|
||||
wallet_dir,
|
||||
http_pool,
|
||||
store,
|
||||
state,
|
||||
profile,
|
||||
workspace_root,
|
||||
observer,
|
||||
));
|
||||
}
|
||||
@@ -1161,8 +1166,8 @@ async fn execute_campaign_projection_unboxed<S, O>(
|
||||
wallet_dir: std::path::PathBuf,
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
store: &S,
|
||||
state: &crate::AppState,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<CampaignProjection>
|
||||
where
|
||||
@@ -1174,6 +1179,15 @@ where
|
||||
{
|
||||
let base_options =
|
||||
ks_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir);
|
||||
let execution_wallet = match crate::resolve_devnet_execution_wallet(state, profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"demo_wallet_resolution_failed",
|
||||
error,
|
||||
));
|
||||
},
|
||||
};
|
||||
return match campaign_id {
|
||||
"create_mint_nft" => {
|
||||
execute_create_mint_projection(
|
||||
@@ -1184,7 +1198,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
@@ -1198,7 +1212,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
@@ -1212,7 +1226,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
@@ -1226,7 +1240,7 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
@@ -1240,17 +1254,17 @@ where
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
},
|
||||
"collection_verify" => {
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_collection_verify_campaign(
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_collection_verify_campaign_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&base_options,
|
||||
observer,
|
||||
).await {
|
||||
@@ -1285,11 +1299,11 @@ where
|
||||
},
|
||||
"print_burn" => {
|
||||
let summary =
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_print_burn_campaign(
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_print_burn_campaign_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&base_options,
|
||||
observer,
|
||||
)
|
||||
@@ -1327,11 +1341,11 @@ where
|
||||
ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
|
||||
);
|
||||
let summary =
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_pnft_lifecycle_campaign(
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_pnft_lifecycle_campaign_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&options,
|
||||
observer,
|
||||
)
|
||||
@@ -1361,11 +1375,11 @@ where
|
||||
})
|
||||
},
|
||||
"escrow" => {
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_escrow_campaign(
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_escrow_campaign_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&base_options,
|
||||
observer,
|
||||
)
|
||||
@@ -1410,11 +1424,11 @@ where
|
||||
ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
|
||||
);
|
||||
let summary =
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_maintenance_campaign(
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_maintenance_campaign_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&execution_wallet,
|
||||
&options,
|
||||
observer,
|
||||
)
|
||||
@@ -1465,19 +1479,20 @@ where
|
||||
let options = base_options.clone().with_asset_family(
|
||||
ks_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
|
||||
);
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_use_probe(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&options,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary =
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_use_probe_with_wallet(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
&execution_wallet,
|
||||
&options,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut executions = vec![
|
||||
metaplex_execution_json("fixture.create", &summary.fixture.create),
|
||||
metaplex_execution_json("fixture.mint", &summary.fixture.mint),
|
||||
@@ -1513,7 +1528,7 @@ async fn execute_create_mint_projection<S, O>(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
store: &S,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
wallet: &ks_pipeline_demo_scenarios::DevnetExecutionWallet,
|
||||
observer: &O,
|
||||
) -> ks_core::Result<CampaignProjection>
|
||||
where
|
||||
@@ -1523,19 +1538,15 @@ where
|
||||
+ Sync,
|
||||
O: ks_pipeline_demo_scenarios::SolanaExecutionObserver,
|
||||
{
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_metaplex_create_mint_campaign(
|
||||
http_pool,
|
||||
store,
|
||||
profile,
|
||||
workspace_root,
|
||||
&options,
|
||||
observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary =
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_metaplex_create_mint_campaign_with_wallet(
|
||||
http_pool, store, profile, wallet, &options, observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let executions = vec![
|
||||
metaplex_execution_json("create", &summary.create),
|
||||
metaplex_execution_json("mint", &summary.mint),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_metadata_solana_program.rs
|
||||
// version: 3
|
||||
// version: 6
|
||||
|
||||
//! Thin desktop adapter for complete Solana Program Metadata Devnet campaigns.
|
||||
|
||||
@@ -170,11 +170,14 @@ pub(crate) async fn demo_execution_metadata_solana_program_execute_campaign(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_solana_program_metadata_campaign(
|
||||
let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_solana_program_metadata_campaign_with_wallet(
|
||||
&http_pool,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&options,
|
||||
&observer,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_metadata_token_2022.rs
|
||||
// version: 2
|
||||
// version: 5
|
||||
|
||||
//! Thin desktop adapter for the complete Token-2022 Token Metadata Devnet campaign.
|
||||
|
||||
@@ -96,7 +96,8 @@ pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let configured_wallet_dir =
|
||||
std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
||||
configured_wallet_dir
|
||||
} else {
|
||||
@@ -109,19 +110,24 @@ pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_token_2022_metadata_campaign(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&options,
|
||||
&observer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary =
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_token_2022_metadata_campaign_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
&execution_wallet,
|
||||
&options,
|
||||
&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(campaign_summary_payload(request.profile_name, summary));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
|
||||
// version: 17
|
||||
// version: 20
|
||||
|
||||
//! Tauri adapter for bounded Solana Core execution on Devnet.
|
||||
|
||||
@@ -509,12 +509,15 @@ pub(crate) async fn demo_execution_solana_core_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_system_transfer(
|
||||
let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_system_transfer_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -582,12 +585,15 @@ pub(crate) async fn demo_execution_spl_memo_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_memo(
|
||||
let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_memo_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_ata.rs
|
||||
// version: 13
|
||||
// version: 16
|
||||
|
||||
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
|
||||
|
||||
@@ -279,21 +279,26 @@ pub(crate) async fn demo_execution_spl_ata_execute(
|
||||
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
|
||||
{
|
||||
let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary =
|
||||
match ks_pipeline_demo_scenarios::execute_devnet_spl_associated_token_account_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
&execution_wallet,
|
||||
&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));
|
||||
}
|
||||
|
||||
@@ -346,7 +351,7 @@ pub(crate) async fn demo_spl_ata_journal(
|
||||
async fn load_profile_wallet(
|
||||
profile: &ks_config::ProfileConfig,
|
||||
) -> std::result::Result<ks_wallet::TemporaryWallet, std::string::String> {
|
||||
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let configured = std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let directory = if configured.is_absolute() {
|
||||
configured
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_token.rs
|
||||
// version: 10
|
||||
// version: 13
|
||||
|
||||
//! Thin Tauri adapter for classic SPL Token execution and materialized journals.
|
||||
|
||||
@@ -223,12 +223,15 @@ pub(crate) async fn demo_execution_spl_token_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token(
|
||||
let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_token_2022.rs
|
||||
// version: 15
|
||||
// version: 18
|
||||
|
||||
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
|
||||
|
||||
@@ -192,12 +192,15 @@ pub(crate) async fn demo_execution_spl_token_2022_execute(
|
||||
app_handle,
|
||||
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
||||
};
|
||||
let workspace_root = crate::workspace_root_dir();
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token_2022(
|
||||
let execution_wallet = match crate::resolve_devnet_execution_wallet(&state, &profile).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let summary = match ks_pipeline_demo_scenarios::execute_devnet_spl_token_2022_with_wallet(
|
||||
&http_pool,
|
||||
&store,
|
||||
&profile,
|
||||
workspace_root.as_path(),
|
||||
&execution_wallet,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
@@ -220,7 +223,7 @@ pub(crate) fn demo_spl_token_2022_fixture(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let configured = std::path::PathBuf::from(profile.wallet.temporary_wallet_dir.as_str());
|
||||
let wallet_dir = if configured.is_absolute() {
|
||||
configured
|
||||
} else {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// file: kb-app-demo-desktop/src/demo_wallet.rs
|
||||
// version: 5
|
||||
// version: 7
|
||||
|
||||
//! UI-safe wallet management and public on-chain exploration.
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
const DEMO_WALLET_PASSWORD_ENV: &str = "KB_SECRET_DEMO_WALLET_PASSWORD";
|
||||
const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
|
||||
|
||||
/// UI-safe inventory of native wallets visible to the active profile.
|
||||
@@ -21,6 +22,10 @@ pub(crate) struct DemoWalletInventoryPayload {
|
||||
pub(crate) cluster: std::string::String,
|
||||
/// Profiles available for explicit read-only on-chain exploration.
|
||||
pub(crate) onchain_profiles: std::vec::Vec<DemoWalletOnchainProfilePayload>,
|
||||
/// Devnet profiles and their session/config execution-wallet selections.
|
||||
pub(crate) execution_profiles: std::vec::Vec<DemoWalletExecutionProfilePayload>,
|
||||
/// Workspace-relative directory used for secret exports from this demo.
|
||||
pub(crate) export_directory: std::string::String,
|
||||
/// Secret transfer formats supported by the reusable wallet boundary.
|
||||
pub(crate) transfer_formats: std::vec::Vec<DemoWalletTransferFormatPayload>,
|
||||
/// Canonical classic SPL Token program identifier used by the explorer.
|
||||
@@ -119,7 +124,7 @@ pub(crate) struct DemoWalletImportRequest {
|
||||
pub(crate) format: std::string::String,
|
||||
}
|
||||
|
||||
/// Request to export one native wallet to an explicit external file.
|
||||
/// Request to export one native wallet to the application-owned export directory.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
@@ -128,12 +133,42 @@ pub(crate) struct DemoWalletImportRequest {
|
||||
pub(crate) struct DemoWalletExportRequest {
|
||||
/// Alias of the native wallet to export.
|
||||
pub(crate) alias: std::string::String,
|
||||
/// Explicit destination path created without overwrite.
|
||||
pub(crate) destination_path: std::string::String,
|
||||
/// File name created inside the application-owned wallet export directory.
|
||||
pub(crate) file_name: std::string::String,
|
||||
/// Stable transfer format code selected by the operator.
|
||||
pub(crate) format: std::string::String,
|
||||
}
|
||||
|
||||
/// One Devnet profile and its effective desktop-session execution-wallet selection.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionProfilePayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoWalletExecutionProfilePayload {
|
||||
/// Resolved Devnet profile name.
|
||||
pub(crate) name: std::string::String,
|
||||
/// Alias persisted in wallet configuration, when present.
|
||||
pub(crate) configured_alias: std::option::Option<std::string::String>,
|
||||
/// Session-only desktop override, when present.
|
||||
pub(crate) runtime_alias: std::option::Option<std::string::String>,
|
||||
/// Effective alias used by execution demos, or `None` for the temporary fallback.
|
||||
pub(crate) effective_alias: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Request to set or clear one session-only execution-wallet selection.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionSelectionRequest.ts"
|
||||
)]
|
||||
pub(crate) struct DemoWalletExecutionSelectionRequest {
|
||||
/// Devnet profile whose execution wallet is selected.
|
||||
pub(crate) profile: std::string::String,
|
||||
/// Native wallet alias, or `None` to return to profile configuration.
|
||||
pub(crate) alias: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// Exact SOL balance returned for one public wallet address.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
@@ -226,19 +261,87 @@ pub(crate) async fn demo_wallet_inventory(
|
||||
for handle in handles {
|
||||
wallets.push(wallet_identity_payload(&handle, selected_alias.as_deref()));
|
||||
}
|
||||
let execution_profiles = match execution_profile_payloads(state) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(DemoWalletInventoryPayload {
|
||||
profile: profile.name.clone(),
|
||||
cluster: profile.wallet.cluster.clone(),
|
||||
onchain_profiles: onchain_profile_payloads(state),
|
||||
execution_profiles,
|
||||
export_directory: "data/wallets".to_string(),
|
||||
transfer_formats: wallet_transfer_format_payloads(),
|
||||
token_program_id: ks_program_ids::SPL_TOKEN_PROGRAM_ID.to_string(),
|
||||
token_2022_program_id: ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string(),
|
||||
create_password_configured: std::env::var_os(DEMO_WALLET_PASSWORD_ENV).is_some(),
|
||||
create_password_configured: crate::demo_wallet_password_configured(),
|
||||
selected_alias,
|
||||
wallets,
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets or clears the session-only native wallet used by Devnet execution demos.
|
||||
pub(crate) async fn demo_wallet_select_execution_wallet(
|
||||
state: &crate::AppState,
|
||||
request: crate::DemoWalletExecutionSelectionRequest,
|
||||
) -> std::result::Result<DemoWalletInventoryPayload, std::string::String> {
|
||||
let profile = match devnet_execution_profile(state, request.profile.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let profile_name = profile.name.clone();
|
||||
if let std::option::Option::Some(alias_text) = request.alias {
|
||||
let alias = match ks_wallet::WalletAlias::parse(alias_text.trim().to_string()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let password = match crate::demo_wallet_password() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let manager = match wallet_manager_for_profile(profile) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let wallet = match manager.unlock(&alias, password).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let public_key = wallet.public_key();
|
||||
wallet.lock();
|
||||
if let std::result::Result::Err(error) = state.set_demo_execution_wallet_alias_override(
|
||||
profile_name.clone(),
|
||||
std::option::Option::Some(alias.as_str().to_string()),
|
||||
) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
tracing::info!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "select_demo_execution_wallet",
|
||||
profile = profile_name.as_str(),
|
||||
wallet_alias = alias.as_str(),
|
||||
public_key = %public_key,
|
||||
selection_source = "runtime",
|
||||
"selected authenticated native wallet for desktop execution demos"
|
||||
);
|
||||
} else {
|
||||
if let std::result::Result::Err(error) = state.set_demo_execution_wallet_alias_override(
|
||||
profile_name.clone(),
|
||||
std::option::Option::None,
|
||||
) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
tracing::info!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "select_demo_execution_wallet",
|
||||
profile = profile_name.as_str(),
|
||||
selection_source = "configuration",
|
||||
"cleared desktop execution-wallet override"
|
||||
);
|
||||
}
|
||||
return demo_wallet_inventory(state).await;
|
||||
}
|
||||
|
||||
/// Creates one password-protected native wallet without sending the password through Tauri IPC.
|
||||
pub(crate) async fn demo_wallet_create_native(
|
||||
state: &crate::AppState,
|
||||
@@ -248,7 +351,7 @@ pub(crate) async fn demo_wallet_create_native(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let password = match demo_wallet_password() {
|
||||
let password = match crate::demo_wallet_password() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -309,7 +412,7 @@ pub(crate) async fn demo_wallet_import_file(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let password = match demo_wallet_password() {
|
||||
let password = match crate::demo_wallet_password() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -341,7 +444,7 @@ pub(crate) async fn demo_wallet_import_file(
|
||||
pub(crate) async fn demo_wallet_export_file(
|
||||
state: &crate::AppState,
|
||||
request: crate::DemoWalletExportRequest,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
) -> std::result::Result<std::string::String, std::string::String> {
|
||||
let alias = match ks_wallet::WalletAlias::parse(request.alias.trim().to_string()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
@@ -350,7 +453,15 @@ pub(crate) async fn demo_wallet_export_file(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let password = match demo_wallet_password() {
|
||||
let file_name = match validated_wallet_export_file_name(request.file_name.as_str(), format) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let export_directory = match prepare_wallet_export_directory().await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let password = match crate::demo_wallet_password() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -358,13 +469,21 @@ pub(crate) async fn demo_wallet_export_file(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return match manager
|
||||
.export_file(&alias, password, std::path::PathBuf::from(request.destination_path), format)
|
||||
.await
|
||||
let destination = export_directory.join(file_name.as_str());
|
||||
if let std::result::Result::Err(error) =
|
||||
manager.export_file(&alias, password, destination, format).await
|
||||
{
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
tracing::info!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "export_wallet_secret",
|
||||
wallet_alias = alias.as_str(),
|
||||
transfer_format = format.code(),
|
||||
export_file_name = file_name.as_str(),
|
||||
"exported native wallet secret to application-owned data directory"
|
||||
);
|
||||
return std::result::Result::Ok(format!("data/wallets/{file_name}"));
|
||||
}
|
||||
|
||||
/// Inspects one explicitly selected native wallet file and returns only its safe identity.
|
||||
@@ -489,21 +608,6 @@ pub(crate) async fn demo_wallet_rpc_execute(
|
||||
});
|
||||
}
|
||||
|
||||
fn demo_wallet_password() -> std::result::Result<ks_wallet::WalletPassword, std::string::String> {
|
||||
let password_text = match std::env::var(DEMO_WALLET_PASSWORD_ENV) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(format!(
|
||||
"backend secret {DEMO_WALLET_PASSWORD_ENV} is not configured as UTF-8"
|
||||
));
|
||||
},
|
||||
};
|
||||
return match ks_wallet::WalletPassword::new(password_text) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
fn wallet_transfer_format_payloads() -> std::vec::Vec<crate::DemoWalletTransferFormatPayload> {
|
||||
let mut formats = std::vec::Vec::new();
|
||||
for format in ks_wallet::WalletTransferFormat::supported() {
|
||||
@@ -528,6 +632,127 @@ fn wallet_transfer_format_from_code(
|
||||
return std::result::Result::Err(format!("wallet transfer format '{code}' is not supported"));
|
||||
}
|
||||
|
||||
fn execution_profile_payloads(
|
||||
state: &crate::AppState,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoWalletExecutionProfilePayload>, std::string::String>
|
||||
{
|
||||
let mut profiles = std::vec::Vec::new();
|
||||
for profile in &state.app_config().profiles {
|
||||
if profile.wallet.cluster != "devnet" {
|
||||
continue;
|
||||
}
|
||||
let runtime_alias = match state.demo_execution_wallet_alias_override(profile.name.as_str())
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let effective_alias = match runtime_alias.as_ref() {
|
||||
std::option::Option::Some(value) => std::option::Option::Some(value.clone()),
|
||||
std::option::Option::None => profile.wallet.wallet_alias.clone(),
|
||||
};
|
||||
profiles.push(crate::DemoWalletExecutionProfilePayload {
|
||||
name: profile.name.clone(),
|
||||
configured_alias: profile.wallet.wallet_alias.clone(),
|
||||
runtime_alias,
|
||||
effective_alias,
|
||||
});
|
||||
}
|
||||
return std::result::Result::Ok(profiles);
|
||||
}
|
||||
|
||||
fn devnet_execution_profile<'a>(
|
||||
state: &'a crate::AppState,
|
||||
profile_name: &str,
|
||||
) -> std::result::Result<&'a ks_config::ProfileConfig, std::string::String> {
|
||||
let profile = match onchain_profile(state, profile_name) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if profile.wallet.cluster != "devnet" {
|
||||
return std::result::Result::Err(
|
||||
"wallet execution selection requires a Devnet profile".to_string(),
|
||||
);
|
||||
}
|
||||
return std::result::Result::Ok(profile);
|
||||
}
|
||||
|
||||
fn validated_wallet_export_file_name(
|
||||
file_name: &str,
|
||||
format: ks_wallet::WalletTransferFormat,
|
||||
) -> std::result::Result<std::string::String, std::string::String> {
|
||||
let file_name = file_name.trim();
|
||||
if file_name.is_empty() || file_name.len() > 128 {
|
||||
return std::result::Result::Err(
|
||||
"wallet export file name must contain between 1 and 128 bytes".to_string(),
|
||||
);
|
||||
}
|
||||
if file_name == "." || file_name == ".." || file_name.starts_with('.') {
|
||||
return std::result::Result::Err("wallet export file name is invalid".to_string());
|
||||
}
|
||||
if !file_name.chars().all(|value| {
|
||||
return value.is_ascii_alphanumeric() || value == '-' || value == '_' || value == '.';
|
||||
}) {
|
||||
return std::result::Result::Err(
|
||||
"wallet export file name contains unsupported characters".to_string(),
|
||||
);
|
||||
}
|
||||
let expected_extension = format.default_extension();
|
||||
let path = std::path::Path::new(file_name);
|
||||
if path.extension().is_none() {
|
||||
return std::result::Result::Ok(format!("{file_name}.{expected_extension}"));
|
||||
}
|
||||
if path.extension().and_then(std::ffi::OsStr::to_str)
|
||||
!= std::option::Option::Some(expected_extension)
|
||||
{
|
||||
return std::result::Result::Err(format!(
|
||||
"wallet export file extension must be '.{expected_extension}' for format '{}'",
|
||||
format.code()
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(file_name.to_string());
|
||||
}
|
||||
|
||||
async fn prepare_wallet_export_directory()
|
||||
-> std::result::Result<std::path::PathBuf, std::string::String> {
|
||||
let data_directory = crate::workspace_root_dir().join("data");
|
||||
if let std::result::Result::Ok(metadata) = tokio::fs::symlink_metadata(&data_directory).await {
|
||||
if metadata.file_type().is_symlink() || !metadata.is_dir() {
|
||||
return std::result::Result::Err(
|
||||
"wallet export data directory must be a regular directory".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if let std::result::Result::Err(error) = tokio::fs::create_dir_all(&data_directory).await {
|
||||
return std::result::Result::Err(format!(
|
||||
"cannot create wallet export data directory: {error}"
|
||||
));
|
||||
}
|
||||
let export_directory = data_directory.join("wallets");
|
||||
if let std::result::Result::Ok(metadata) = tokio::fs::symlink_metadata(&export_directory).await
|
||||
{
|
||||
if metadata.file_type().is_symlink() || !metadata.is_dir() {
|
||||
return std::result::Result::Err(
|
||||
"wallet export directory must be a regular directory".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if let std::result::Result::Err(error) = tokio::fs::create_dir_all(&export_directory).await {
|
||||
return std::result::Result::Err(format!("cannot create wallet export directory: {error}"));
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let permissions = std::fs::Permissions::from_mode(0o700);
|
||||
if let std::result::Result::Err(error) =
|
||||
tokio::fs::set_permissions(&export_directory, permissions).await
|
||||
{
|
||||
return std::result::Result::Err(format!(
|
||||
"cannot secure wallet export directory permissions: {error}"
|
||||
));
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(export_directory);
|
||||
}
|
||||
|
||||
fn onchain_profile<'a>(
|
||||
state: &'a crate::AppState,
|
||||
profile_name: &str,
|
||||
@@ -676,14 +901,19 @@ mod tests {
|
||||
"importWalletTransferButton",
|
||||
"walletExportAliasSelect",
|
||||
"walletExportFormatSelect",
|
||||
"walletExportDestinationPathInput",
|
||||
"walletExportFileNameInput",
|
||||
"walletExportDirectory",
|
||||
"exportWalletTransferButton",
|
||||
"walletExecutionProfileSelect",
|
||||
"walletExecutionAliasSelect",
|
||||
"applyWalletExecutionSelectionButton",
|
||||
"walletExternalPathInput",
|
||||
"inspectWalletFileButton",
|
||||
"walletInspectionJson",
|
||||
] {
|
||||
assert!(html.contains(required));
|
||||
}
|
||||
assert!(script.contains("demo_wallet_select_execution_wallet"));
|
||||
assert!(script.contains("demo_wallet_create_native"));
|
||||
assert!(script.contains("demo_wallet_inspect_transfer_file"));
|
||||
assert!(script.contains("demo_wallet_import_file"));
|
||||
@@ -700,6 +930,7 @@ mod tests {
|
||||
assert!(main_html.contains("openDemoWalletLink"));
|
||||
assert!(main_script.contains("open_demo_wallet_window"));
|
||||
assert!(tauri_runtime.contains("open_demo_wallet_window"));
|
||||
assert!(tauri_runtime.contains("demo_wallet_select_execution_wallet"));
|
||||
assert!(tauri_runtime.contains("demo_wallet_create_native"));
|
||||
assert!(tauri_runtime.contains("demo_wallet_inspect_transfer_file"));
|
||||
assert!(tauri_runtime.contains("demo_wallet_import_file"));
|
||||
@@ -721,6 +952,13 @@ mod tests {
|
||||
cluster: "devnet".to_string(),
|
||||
active: false,
|
||||
}],
|
||||
execution_profiles: std::vec![super::DemoWalletExecutionProfilePayload {
|
||||
name: "local_devnet".to_string(),
|
||||
configured_alias: std::option::Option::None,
|
||||
runtime_alias: std::option::Option::Some("selected-wallet".to_string()),
|
||||
effective_alias: std::option::Option::Some("selected-wallet".to_string()),
|
||||
}],
|
||||
export_directory: "data/wallets".to_string(),
|
||||
transfer_formats: super::wallet_transfer_format_payloads(),
|
||||
token_program_id: ks_program_ids::SPL_TOKEN_PROGRAM_ID.to_string(),
|
||||
token_2022_program_id: ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string(),
|
||||
@@ -752,6 +990,34 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_export_file_name_is_bounded_and_format_specific() {
|
||||
let json = super::validated_wallet_export_file_name(
|
||||
"operator",
|
||||
ks_wallet::WalletTransferFormat::SolanaCliJson,
|
||||
);
|
||||
assert_eq!(json, std::result::Result::Ok("operator.json".to_string()));
|
||||
let base58 = super::validated_wallet_export_file_name(
|
||||
"operator.txt",
|
||||
ks_wallet::WalletTransferFormat::SolanaPrivateKeyBase58,
|
||||
);
|
||||
assert_eq!(base58, std::result::Result::Ok("operator.txt".to_string()));
|
||||
assert!(
|
||||
super::validated_wallet_export_file_name(
|
||||
"../operator.json",
|
||||
ks_wallet::WalletTransferFormat::SolanaCliJson,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
super::validated_wallet_export_file_name(
|
||||
"operator.txt",
|
||||
ks_wallet::WalletTransferFormat::SolanaCliJson,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_rpc_surface_is_read_only_and_bounded() {
|
||||
for allowed in ["getTokenAccountsByOwner", "getSignaturesForAddress", "getTransaction"] {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/lib.rs
|
||||
// version: 41
|
||||
// version: 43
|
||||
|
||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||
|
||||
@@ -151,10 +151,16 @@ pub(crate) use self::demo_decode_replay::demo_decode_replay_options;
|
||||
pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayload;
|
||||
/// Restores the shared single-execution state flag when a Devnet run finishes.
|
||||
pub(crate) use self::demo_devnet_common::DemoExecutionRunGuard;
|
||||
/// Loads the backend-only demo wallet password.
|
||||
pub(crate) use self::demo_devnet_common::demo_wallet_password;
|
||||
/// Returns whether the backend-only demo wallet password is configured.
|
||||
pub(crate) use self::demo_devnet_common::demo_wallet_password_configured;
|
||||
/// Converts the reusable scenario readiness report to the desktop TS-RS payload.
|
||||
pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload;
|
||||
/// Serializes a diagnostic value to indented JSON without panicking.
|
||||
pub(crate) use self::demo_devnet_common::pretty_json;
|
||||
/// Resolves the temporary or persistent signer selected by one Devnet profile.
|
||||
pub(crate) use self::demo_devnet_common::resolve_devnet_execution_wallet;
|
||||
/// Bridges Metadata execution progress to the Metadata desktop window.
|
||||
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataObserver;
|
||||
/// Progress event emitted by Metadata execution workflows.
|
||||
@@ -367,6 +373,10 @@ pub(crate) use self::demo_transport::DemoEndpointRolePayload;
|
||||
pub(crate) use self::demo_wallet::DemoWalletBalancePayload;
|
||||
/// Request to create one native wallet from the backend-only demo secret.
|
||||
pub(crate) use self::demo_wallet::DemoWalletCreateRequest;
|
||||
/// One Devnet profile and its effective execution-wallet selection.
|
||||
pub(crate) use self::demo_wallet::DemoWalletExecutionProfilePayload;
|
||||
/// Request to set or clear one session-only execution-wallet selection.
|
||||
pub(crate) use self::demo_wallet::DemoWalletExecutionSelectionRequest;
|
||||
/// Request to export one native wallet secret through the backend-only demo secret.
|
||||
pub(crate) use self::demo_wallet::DemoWalletExportRequest;
|
||||
/// UI-safe native wallet identity exposed by the desktop adapter.
|
||||
@@ -403,6 +413,8 @@ pub(crate) use self::demo_wallet::demo_wallet_inspect_transfer_file;
|
||||
pub(crate) use self::demo_wallet::demo_wallet_inventory;
|
||||
/// Executes one bounded read-only wallet explorer RPC through an explicitly selected profile.
|
||||
pub(crate) use self::demo_wallet::demo_wallet_rpc_execute;
|
||||
/// Sets or clears the session-only native wallet used by Devnet execution demos.
|
||||
pub(crate) use self::demo_wallet::demo_wallet_select_execution_wallet;
|
||||
/// UI-safe WebSocket endpoint snapshot.
|
||||
pub(crate) use self::demo_ws::DemoWsEndpointPayload;
|
||||
/// WebSocket execution response payload.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/tauri.rs
|
||||
// version: 43
|
||||
// version: 44
|
||||
|
||||
//! Tauri runtime assembly and private command wrappers.
|
||||
|
||||
@@ -38,6 +38,7 @@ pub fn run() -> ks_core::Result<()> {
|
||||
load_demo_config,
|
||||
open_demo_wallet_window,
|
||||
demo_wallet_inventory,
|
||||
demo_wallet_select_execution_wallet,
|
||||
demo_wallet_create_native,
|
||||
demo_wallet_inspect_transfer_file,
|
||||
demo_wallet_import_file,
|
||||
@@ -258,6 +259,14 @@ async fn demo_wallet_inventory(
|
||||
return crate::demo_wallet_inventory(state.inner()).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn demo_wallet_select_execution_wallet(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoWalletExecutionSelectionRequest,
|
||||
) -> std::result::Result<crate::DemoWalletInventoryPayload, std::string::String> {
|
||||
return crate::demo_wallet_select_execution_wallet(state.inner(), request).await;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn demo_wallet_create_native(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
@@ -285,7 +294,7 @@ async fn demo_wallet_import_file(
|
||||
async fn demo_wallet_export_file(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoWalletExportRequest,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
) -> std::result::Result<std::string::String, std::string::String> {
|
||||
return crate::demo_wallet_export_file(state.inner(), request).await;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user