2189 lines
92 KiB
Rust
2189 lines
92 KiB
Rust
// file: kb-app-demo-desktop/src/tauri.rs
|
|
// version: 9
|
|
|
|
//! Tauri runtime assembly and private command wrappers.
|
|
|
|
use tauri::Manager; // rust-rules: trait-import
|
|
|
|
/// Runs the desktop demo application.
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() -> kb_core::Result<()> {
|
|
let rustls_result = install_default_rustls_provider();
|
|
if let std::result::Result::Err(error) = rustls_result {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let app_state_result = crate::AppState::initialize();
|
|
let app_state;
|
|
if let std::result::Result::Ok(state) = app_state_result {
|
|
app_state = state;
|
|
} else if let std::result::Result::Err(error) = app_state_result {
|
|
return std::result::Result::Err(error);
|
|
} else {
|
|
return std::result::Result::Err(kb_core::Error::invalid_state(
|
|
"application state initialization produced no result",
|
|
));
|
|
}
|
|
tracing::info!(
|
|
target: crate::TRACING_TARGET,
|
|
config_path = app_state.config_path(),
|
|
active_profile = app_state.active_profile().name.as_str(),
|
|
logging_routes = app_state.logging_route_count(),
|
|
configured_profiles = app_state.app_config().profiles.len(),
|
|
"starting desktop demo application"
|
|
);
|
|
let tracing_builder = tauri_plugin_tracing::Builder::new();
|
|
let mut builder = tauri::Builder::default();
|
|
builder = builder.manage(app_state);
|
|
builder = builder.invoke_handler(tauri::generate_handler![
|
|
emit_frontend_log,
|
|
load_project_readme,
|
|
open_demo_backfill_window,
|
|
demo_backfill_options,
|
|
demo_backfill_execute,
|
|
demo_backfill_cancel,
|
|
open_demo_http_window,
|
|
demo_http_list_pool_clients,
|
|
demo_http_options,
|
|
demo_http_execute_request,
|
|
open_demo_ws_window,
|
|
demo_ws_list_pool_clients,
|
|
demo_ws_options,
|
|
demo_ws_status,
|
|
demo_ws_connect,
|
|
demo_ws_unsubscribe,
|
|
demo_ws_disconnect,
|
|
open_demo_sql_diag_window,
|
|
load_demo_sql_diag,
|
|
open_demo_sql_pg_raw_window,
|
|
load_demo_sql_pg_raw,
|
|
open_demo_sql_pg_core_window,
|
|
load_demo_sql_pg_core,
|
|
open_demo_sql_replay_candidates_window,
|
|
demo_sql_replay_options,
|
|
load_demo_sql_replay_transactions,
|
|
load_demo_sql_replay_programs,
|
|
load_demo_sql_replay_entities,
|
|
export_demo_sql_replay_csv,
|
|
demo_execution_solana_core_options,
|
|
demo_execution_solana_core_generate_recipient,
|
|
demo_execution_solana_core_execute,
|
|
open_demo_execution_solana_core_window,
|
|
demo_execution_spl_memo_execute,
|
|
demo_execution_solana_core_cancel,
|
|
open_demo_execution_spl_window,
|
|
demo_execution_spl_validation_scenarios,
|
|
demo_execution_spl_token_execute,
|
|
demo_spl_token_journal,
|
|
demo_execution_spl_token2022_execute,
|
|
demo_spl_token2022_fixture,
|
|
demo_spl_ata_derive,
|
|
demo_execution_spl_ata_execute,
|
|
demo_spl_ata_journal,
|
|
open_demo_core_extraction_window,
|
|
demo_core_extraction_options,
|
|
demo_core_extraction_execute,
|
|
demo_core_extraction_cancel,
|
|
open_demo_decode_replay_window,
|
|
demo_decode_replay_options,
|
|
demo_decode_replay_execute,
|
|
demo_decode_replay_cancel,
|
|
demo_decode_replay_diagnostics,
|
|
demo_decode_replay_annotations,
|
|
open_demo_config_window,
|
|
load_demo_config,
|
|
]);
|
|
builder = builder.plugin(tracing_builder.build::<tauri::Wry>());
|
|
builder = builder.setup(|app| {
|
|
let splash_window = match app.get_webview_window("splash") {
|
|
std::option::Option::Some(window) => window,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
|
|
std::io::ErrorKind::NotFound,
|
|
"splash window is missing",
|
|
)));
|
|
},
|
|
};
|
|
let main_window = match app.get_webview_window("main") {
|
|
std::option::Option::Some(window) => window,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(
|
|
std::io::ErrorKind::NotFound,
|
|
"main window is missing",
|
|
)));
|
|
},
|
|
};
|
|
tauri::async_runtime::spawn(async move {
|
|
let started_at = tokio::time::Instant::now();
|
|
crate::emit_splash_order(
|
|
&splash_window,
|
|
"add_log",
|
|
std::option::Option::Some("Configuration chargée"),
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
);
|
|
crate::emit_splash_order(
|
|
&splash_window,
|
|
"add_log",
|
|
std::option::Option::Some("Sous-système de logs initialisé"),
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
);
|
|
crate::emit_splash_order(
|
|
&splash_window,
|
|
"add_log",
|
|
std::option::Option::Some("Pool HTTP initialisé"),
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
);
|
|
crate::emit_splash_order(
|
|
&splash_window,
|
|
"fadein",
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
std::option::Option::Some(crate::SPLASH_FADE_MS),
|
|
);
|
|
crate::emit_splash_order(
|
|
&splash_window,
|
|
"add_msg",
|
|
std::option::Option::Some("Initialisation..."),
|
|
std::option::Option::Some("info"),
|
|
std::option::Option::None,
|
|
);
|
|
crate::emit_splash_order(
|
|
&splash_window,
|
|
"add_msg",
|
|
std::option::Option::Some("Loading complete..."),
|
|
std::option::Option::Some("success"),
|
|
std::option::Option::None,
|
|
);
|
|
crate::wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await;
|
|
if cfg!(debug_assertions) {
|
|
crate::emit_splash_order(
|
|
&splash_window,
|
|
"add_log",
|
|
std::option::Option::Some("Start Fade-out"),
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
);
|
|
}
|
|
crate::emit_splash_order(
|
|
&splash_window,
|
|
"fadeout",
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
std::option::Option::Some(crate::SPLASH_FADE_MS),
|
|
);
|
|
tokio::time::sleep(std::time::Duration::from_millis(crate::SPLASH_CLOSE_WAIT_MS)).await;
|
|
if let std::result::Result::Err(error) = splash_window.destroy() {
|
|
tracing::error!(target: crate::TRACING_TARGET, "cannot destroy splash window: {error:?}");
|
|
}
|
|
if let std::result::Result::Err(error) = main_window.show() {
|
|
tracing::error!(target: crate::TRACING_TARGET, "cannot show main window: {error:?}");
|
|
}
|
|
if let std::result::Result::Err(error) = main_window.set_focus() {
|
|
tracing::error!(target: crate::TRACING_TARGET, "cannot focus main window: {error:?}");
|
|
}
|
|
});
|
|
return std::result::Result::Ok(());
|
|
});
|
|
let run_result = builder.run(tauri::generate_context!());
|
|
return match run_result {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::tauri(
|
|
format!("cannot run desktop demo application: {error:?}"),
|
|
)),
|
|
};
|
|
}
|
|
|
|
fn install_default_rustls_provider() -> kb_core::Result<()> {
|
|
if rustls::crypto::CryptoProvider::get_default().is_some() {
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let provider_result = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
|
return match provider_result {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::invalid_state(
|
|
format!("cannot install default rustls crypto provider: {error:?}"),
|
|
)),
|
|
};
|
|
}
|
|
/// Opens or focuses the canonical to core extraction demo window.
|
|
#[tauri::command]
|
|
fn open_demo_core_extraction_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
tracing::info!(target: crate::TRACING_TARGET, "open core extraction demo window");
|
|
let existing_window = app_handle.get_webview_window("demo_core_extraction");
|
|
if let std::option::Option::Some(window) = existing_window {
|
|
let show_result = window.show();
|
|
if let std::result::Result::Err(error) = show_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
let focus_result = window.set_focus();
|
|
if let std::result::Result::Err(error) = focus_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let build_result = tauri::WebviewWindowBuilder::new(
|
|
&app_handle,
|
|
"demo_core_extraction",
|
|
tauri::WebviewUrl::App("demo_core_extraction.html".into()),
|
|
)
|
|
.title("Khadhroony Bot2 - Extraction canonical vers core")
|
|
.inner_size(1280.0, 860.0)
|
|
.min_inner_size(960.0, 620.0)
|
|
.resizable(true)
|
|
.visible(true)
|
|
.build();
|
|
return match build_result {
|
|
std::result::Result::Ok(window) => {
|
|
let focus_result = window.set_focus();
|
|
if let std::result::Result::Err(error) = focus_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
std::result::Result::Ok(())
|
|
},
|
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
|
};
|
|
}
|
|
|
|
/// Returns the initial core extraction demo options.
|
|
#[tauri::command]
|
|
fn demo_core_extraction_options(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> crate::DemoCoreExtractionOptionsPayload {
|
|
return crate::DemoCoreExtractionOptionsPayload {
|
|
processor_version: kb_pipeline::CORE_EXTRACTION_PROCESSOR_VERSION.to_string(),
|
|
default_limit: 100,
|
|
default_max_concurrent_extractions: 4,
|
|
running: state.demo_core_extraction_running().load(std::sync::atomic::Ordering::Acquire),
|
|
};
|
|
}
|
|
|
|
/// Executes one bounded canonical transaction to core extraction campaign.
|
|
#[tauri::command]
|
|
async fn demo_core_extraction_execute(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoCoreExtractionRequest,
|
|
) -> std::result::Result<crate::DemoCoreExtractionSummaryPayload, std::string::String> {
|
|
let acquire_result = state.demo_core_extraction_running().compare_exchange(
|
|
false,
|
|
true,
|
|
std::sync::atomic::Ordering::AcqRel,
|
|
std::sync::atomic::Ordering::Acquire,
|
|
);
|
|
if acquire_result.is_err() {
|
|
return std::result::Result::Err(
|
|
"a core extraction campaign is already running".to_string(),
|
|
);
|
|
}
|
|
let _run_guard = crate::DemoCoreExtractionRunGuard {
|
|
running: state.demo_core_extraction_running(),
|
|
};
|
|
state
|
|
.demo_core_extraction_cancel_requested()
|
|
.store(false, std::sync::atomic::Ordering::Release);
|
|
let pipeline_request_result = crate::build_demo_core_extraction_pipeline_request(request);
|
|
let pipeline_request = match pipeline_request_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let observer = crate::DemoCoreExtractionObserver {
|
|
app_handle,
|
|
cancel_requested: state.demo_core_extraction_cancel_requested(),
|
|
};
|
|
let summary_result =
|
|
kb_pipeline::execute_core_extraction(&store, &pipeline_request, &observer).await;
|
|
let summary = match summary_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
return std::result::Result::Ok(crate::demo_core_extraction_summary_payload(summary));
|
|
}
|
|
|
|
/// Requests cooperative cancellation of the current core extraction campaign.
|
|
#[tauri::command]
|
|
fn demo_core_extraction_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
|
|
let running = state.demo_core_extraction_running().load(std::sync::atomic::Ordering::Acquire);
|
|
state
|
|
.demo_core_extraction_cancel_requested()
|
|
.store(true, std::sync::atomic::Ordering::Release);
|
|
return running;
|
|
}
|
|
|
|
/// Opens or focuses the contextual decode replay window.
|
|
#[tauri::command]
|
|
fn open_demo_decode_replay_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
tracing::info!(target: crate::TRACING_TARGET, action = "open_window", window = "demo_decode_replay", "open contextual decode replay window");
|
|
let existing_window = app_handle.get_webview_window("demo_decode_replay");
|
|
if let std::option::Option::Some(window) = existing_window {
|
|
let show_result = window.show();
|
|
if let std::result::Result::Err(error) = show_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
let focus_result = window.set_focus();
|
|
if let std::result::Result::Err(error) = focus_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let build_result = tauri::WebviewWindowBuilder::new(
|
|
&app_handle,
|
|
"demo_decode_replay",
|
|
tauri::WebviewUrl::App("demo_decode_replay.html".into()),
|
|
)
|
|
.title("Khadhroony Bot2 - Décodage et matérialisation")
|
|
.inner_size(1320.0, 900.0)
|
|
.min_inner_size(980.0, 660.0)
|
|
.resizable(true)
|
|
.visible(true)
|
|
.build();
|
|
return match build_result {
|
|
std::result::Result::Ok(window) => {
|
|
let focus_result = window.set_focus();
|
|
if let std::result::Result::Err(error) = focus_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
std::result::Result::Ok(())
|
|
},
|
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
|
};
|
|
}
|
|
|
|
/// Returns available decoders and default replay bounds.
|
|
#[tauri::command]
|
|
fn demo_decode_replay_options(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> crate::DemoDecodeReplayOptionsPayload {
|
|
let decoders = crate::available_decoders();
|
|
let decoder_options = decoders
|
|
.iter()
|
|
.map(|decoder| {
|
|
let identity = decoder.identity();
|
|
return crate::DemoDecodeReplayDecoderOption {
|
|
name: identity.name,
|
|
version: identity.version,
|
|
program_ids: decoder
|
|
.surfaces()
|
|
.iter()
|
|
.map(|surface| return surface.program_id.to_string())
|
|
.collect(),
|
|
};
|
|
})
|
|
.collect::<std::vec::Vec<_>>();
|
|
let materializers = crate::available_materializers();
|
|
let materializer_names = materializers
|
|
.iter()
|
|
.map(|materializer| {
|
|
let identity = materializer.identity();
|
|
return format!("{}@{}", identity.name, identity.version);
|
|
})
|
|
.collect::<std::vec::Vec<_>>();
|
|
let running = state.demo_decode_replay_running().load(std::sync::atomic::Ordering::Acquire);
|
|
tracing::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
action = "load_options",
|
|
pipeline_version = kb_pipeline::DECODE_PIPELINE_VERSION,
|
|
decoder_count = decoder_options.len(),
|
|
materializer_names = ?materializer_names,
|
|
default_limit = 100_u32,
|
|
default_max_concurrent_inputs = 4_u32,
|
|
running,
|
|
"return contextual decode replay options"
|
|
);
|
|
return crate::DemoDecodeReplayOptionsPayload {
|
|
pipeline_version: kb_pipeline::DECODE_PIPELINE_VERSION.to_string(),
|
|
decoders: decoder_options,
|
|
materializer_names,
|
|
default_limit: 100,
|
|
default_max_concurrent_inputs: 4,
|
|
running,
|
|
};
|
|
}
|
|
|
|
/// Executes one bounded contextual decode and optional materialization campaign.
|
|
#[tauri::command]
|
|
async fn demo_decode_replay_execute(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoDecodeReplayRequest,
|
|
) -> std::result::Result<crate::DemoDecodeReplaySummaryPayload, std::string::String> {
|
|
let campaign_id = kb_pipeline::new_decode_campaign_id();
|
|
tracing::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
action = "execute",
|
|
campaign_id = %campaign_id,
|
|
instruction_state = %request.instruction_state,
|
|
signature_line_count = crate::optional_line_count(&request.signatures_text),
|
|
program_id = ?request.program_id,
|
|
instruction_paths_text = ?request.instruction_paths_text,
|
|
decoder_names = ?request.decoder_names,
|
|
limit = request.limit,
|
|
max_concurrent_inputs = request.max_concurrent_inputs,
|
|
all_compatible = request.all_compatible,
|
|
force_replay = request.force_replay,
|
|
force_replay_all_matching = request.force_replay_all_matching,
|
|
materialize_after_decode = request.materialize_after_decode,
|
|
"received contextual decode replay command"
|
|
);
|
|
let acquire_result = state.demo_decode_replay_running().compare_exchange(
|
|
false,
|
|
true,
|
|
std::sync::atomic::Ordering::AcqRel,
|
|
std::sync::atomic::Ordering::Acquire,
|
|
);
|
|
if acquire_result.is_err() {
|
|
tracing::warn!(target: crate::TRACING_TARGET, action = "execute", campaign_id = %campaign_id, accepted = false, reason = "already_running", "reject contextual decode replay command");
|
|
return std::result::Result::Err(
|
|
"a contextual decode replay campaign is already running".to_string(),
|
|
);
|
|
}
|
|
let _run_guard = crate::DemoDecodeReplayRunGuard {
|
|
running: state.demo_decode_replay_running(),
|
|
campaign_id: state.demo_decode_replay_campaign_id(),
|
|
};
|
|
let register_result = crate::register_active_campaign(
|
|
state.demo_decode_replay_campaign_id(),
|
|
campaign_id.as_str(),
|
|
);
|
|
if let std::result::Result::Err(error) = register_result {
|
|
tracing::error!(target: crate::TRACING_TARGET, action = "register_campaign", campaign_id = %campaign_id, error = %error, "cannot lock active contextual decode campaign slot");
|
|
return std::result::Result::Err(error);
|
|
}
|
|
state
|
|
.demo_decode_replay_cancel_requested()
|
|
.store(false, std::sync::atomic::Ordering::Release);
|
|
let pipeline_request_result =
|
|
crate::build_demo_decode_replay_pipeline_request(request, campaign_id.clone());
|
|
let pipeline_request = match pipeline_request_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
tracing::error!(target: crate::TRACING_TARGET, action = "build_request", campaign_id = %campaign_id, error = %error, "cannot build contextual decode replay request");
|
|
return std::result::Result::Err(error);
|
|
},
|
|
};
|
|
tracing::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
action = "execute_pipeline",
|
|
campaign_id = %pipeline_request.campaign_id,
|
|
signature_count = pipeline_request.selection.signatures.len(),
|
|
signature_sample = ?crate::text_sample(pipeline_request.selection.signatures.as_slice(), 5),
|
|
processing_states = ?pipeline_request.selection.processing_states,
|
|
min_slot = ?pipeline_request.selection.min_slot,
|
|
max_slot = ?pipeline_request.selection.max_slot,
|
|
program_ids = ?pipeline_request.selection.program_ids,
|
|
instruction_paths = ?pipeline_request.selection.instruction_paths,
|
|
incomplete_signatures = pipeline_request.selection.incomplete_signatures,
|
|
limit = pipeline_request.selection.limit,
|
|
decoder_names = ?pipeline_request.decoder_names,
|
|
dispatch_policy = ?pipeline_request.dispatch_policy,
|
|
max_concurrent_inputs = pipeline_request.max_concurrent_inputs,
|
|
force_replay = pipeline_request.force_replay,
|
|
force_replay_all_matching = pipeline_request.force_replay_all_matching,
|
|
materialize_after_decode = pipeline_request.materialize_after_decode,
|
|
"start contextual decode replay pipeline"
|
|
);
|
|
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
tracing::error!(target: crate::TRACING_TARGET, action = "connect_store", campaign_id = %pipeline_request.campaign_id, error = %error, "cannot connect contextual decode replay store");
|
|
return std::result::Result::Err(error);
|
|
},
|
|
};
|
|
let decoders = crate::available_decoders();
|
|
let materializers = crate::available_materializers();
|
|
let observer = crate::DemoDecodeReplayObserver {
|
|
app_handle,
|
|
campaign_id: pipeline_request.campaign_id.clone(),
|
|
cancel_requested: state.demo_decode_replay_cancel_requested(),
|
|
};
|
|
let summary_result = kb_pipeline::execute_decode_replay(
|
|
&store,
|
|
&pipeline_request,
|
|
decoders.as_slice(),
|
|
materializers.as_slice(),
|
|
&observer,
|
|
)
|
|
.await;
|
|
let summary = match summary_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
tracing::error!(target: crate::TRACING_TARGET, action = "execute_pipeline", campaign_id = %pipeline_request.campaign_id, error = %error, "contextual decode replay pipeline failed");
|
|
return std::result::Result::Err(error.to_string());
|
|
},
|
|
};
|
|
tracing::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
action = "execute_pipeline",
|
|
campaign_id = %summary.campaign_id,
|
|
selected = summary.selected,
|
|
started = summary.started,
|
|
completed = summary.completed,
|
|
unmatched = summary.unmatched,
|
|
not_started = summary.not_started,
|
|
failed_inputs = summary.failed_inputs,
|
|
cancelled = summary.cancelled,
|
|
processors = ?summary.processors,
|
|
"contextual decode replay command completed"
|
|
);
|
|
return std::result::Result::Ok(crate::demo_decode_replay_summary_payload(summary));
|
|
}
|
|
|
|
/// Requests cooperative cancellation of the current decode replay campaign.
|
|
#[tauri::command]
|
|
fn demo_decode_replay_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
|
|
let running = state.demo_decode_replay_running().load(std::sync::atomic::Ordering::Acquire);
|
|
state
|
|
.demo_decode_replay_cancel_requested()
|
|
.store(true, std::sync::atomic::Ordering::Release);
|
|
let campaign_lock_result = state.demo_decode_replay_campaign_id().lock();
|
|
let campaign_id = match campaign_lock_result {
|
|
std::result::Result::Ok(active_campaign_id) => active_campaign_id.clone(),
|
|
std::result::Result::Err(_) => std::option::Option::None,
|
|
};
|
|
tracing::debug!(target: crate::TRACING_TARGET, action = "cancel", campaign_id = ?campaign_id, running, cancellation_requested = true, "contextual decode replay cancellation command handled");
|
|
return running;
|
|
}
|
|
|
|
/// Loads read-only decode, materialization, ledger and coverage diagnostics.
|
|
#[tauri::command]
|
|
async fn demo_decode_replay_diagnostics(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::DemoDecodeDiagnosticsPayload, std::string::String> {
|
|
tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", coverage_limit = 500_u32, "load contextual decode replay diagnostics");
|
|
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let table_result = store.known_table_diagnostics().await;
|
|
let all_tables = match table_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
let selected_tables: std::vec::Vec<crate::DemoSqlTableSnapshot> = all_tables
|
|
.iter()
|
|
.filter(|table| {
|
|
return table.table_name.starts_with("kb_sol_decode_")
|
|
|| table.table_name.starts_with("kb_sol_mat_")
|
|
|| table.table_name == "kb_sol_ops_processing_ledger";
|
|
})
|
|
.map(crate::table_snapshot_from_pg)
|
|
.collect();
|
|
let coverage_result = kb_store::DecodePipelineStore::list_decode_coverage_summary(
|
|
&store,
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
500,
|
|
)
|
|
.await;
|
|
let coverage = match coverage_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", table_count = selected_tables.len(), coverage_count = coverage.len(), "contextual decode replay diagnostics loaded");
|
|
return std::result::Result::Ok(crate::DemoDecodeDiagnosticsPayload {
|
|
tables: selected_tables,
|
|
coverage: coverage.into_iter().map(crate::coverage_payload).collect(),
|
|
});
|
|
}
|
|
|
|
/// Loads a bounded journal of committed SPL Memo transaction annotations.
|
|
#[tauri::command]
|
|
async fn demo_decode_replay_annotations(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoTransactionAnnotationRequest,
|
|
) -> std::result::Result<std::vec::Vec<crate::DemoTransactionAnnotationRow>, std::string::String> {
|
|
let filter_result = kb_store::MaterializedEventFilter::new(
|
|
std::option::Option::Some("transaction_annotations".to_string()),
|
|
std::option::Option::Some("transaction_annotation".to_string()),
|
|
request.signature_contains,
|
|
request.limit,
|
|
);
|
|
let filter = match filter_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
tracing::debug!(target: crate::TRACING_TARGET, action = "load_transaction_annotations", signature_contains = ?filter.signature_contains, limit = filter.limit, "load bounded committed transaction annotation journal");
|
|
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let rows_result =
|
|
kb_store::DecodePipelineStore::list_materialized_events(&store, &filter).await;
|
|
let rows = match rows_result {
|
|
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::with_capacity(rows.len());
|
|
for row in rows {
|
|
let mapped = crate::annotation_payload(row);
|
|
match mapped {
|
|
std::result::Result::Ok(value) => output.push(value),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
}
|
|
tracing::debug!(target: crate::TRACING_TARGET, action = "load_transaction_annotations", row_count = output.len(), "bounded committed transaction annotation journal loaded");
|
|
return std::result::Result::Ok(output);
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn open_demo_config_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
if let std::option::Option::Some(window) = app_handle.get_webview_window("demo_config") {
|
|
if let std::result::Result::Err(error) = window.show() {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
if let std::result::Result::Err(error) = window.set_focus() {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let builder = tauri::WebviewWindowBuilder::new(
|
|
&app_handle,
|
|
"demo_config",
|
|
tauri::WebviewUrl::App("demo_config.html".into()),
|
|
)
|
|
.title("Khadhroony Bot3 - Configuration")
|
|
.inner_size(1280.0, 820.0)
|
|
.min_inner_size(960.0, 640.0);
|
|
let window = match builder.build() {
|
|
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) = window.set_focus() {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn load_demo_config(state: tauri::State<'_, crate::AppState>) -> crate::DemoConfigPayload {
|
|
return crate::demo_config_payload(state.inner());
|
|
}
|
|
|
|
fn into_ipc_result<T>(result: kb_core::Result<T>) -> std::result::Result<T, std::string::String> {
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn emit_frontend_log(payload: crate::FrontendLogPayload) {
|
|
crate::emit_frontend_log(payload);
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn load_project_readme() -> std::result::Result<std::string::String, std::string::String> {
|
|
return into_ipc_result(crate::load_project_readme());
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn demo_backfill_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
|
|
let running = state.demo_backfill_running().load(std::sync::atomic::Ordering::Acquire);
|
|
state
|
|
.demo_backfill_cancel_requested()
|
|
.store(true, std::sync::atomic::Ordering::Release);
|
|
return running;
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn open_demo_backfill_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
let existing_window = app_handle.get_webview_window("demo_backfill");
|
|
if let std::option::Option::Some(window) = existing_window {
|
|
if let std::result::Result::Err(error) = window.show() {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
if let std::result::Result::Err(error) = window.set_focus() {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let build_result = tauri::WebviewWindowBuilder::new(
|
|
&app_handle,
|
|
"demo_backfill",
|
|
tauri::WebviewUrl::App("demo_backfill.html".into()),
|
|
)
|
|
.title("Khadhroony Bot3 - Backfill HTTP")
|
|
.inner_size(1280.0, 860.0)
|
|
.min_inner_size(960.0, 620.0)
|
|
.resizable(true)
|
|
.visible(true)
|
|
.build();
|
|
return match build_result {
|
|
std::result::Result::Ok(window) => match window.set_focus() {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
|
},
|
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn demo_backfill_options(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> crate::DemoBackfillOptionsPayload {
|
|
let roles = crate::build_role_options(state.http_pool().snapshot());
|
|
let default_role = if roles.iter().any(|item| return item.role == "history_backfill") {
|
|
std::option::Option::Some("history_backfill".to_string())
|
|
} else {
|
|
roles.first().map(|item| return item.role.clone())
|
|
};
|
|
return crate::DemoBackfillOptionsPayload {
|
|
roles,
|
|
default_role,
|
|
default_commitment: "confirmed".to_string(),
|
|
default_page_size: 100,
|
|
default_max_pages: 20,
|
|
default_max_concurrent_requests: 4,
|
|
default_max_retries: 2,
|
|
running: state.demo_backfill_running().load(std::sync::atomic::Ordering::Acquire),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_backfill_execute(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoBackfillRequest,
|
|
) -> std::result::Result<crate::DemoBackfillSummaryPayload, std::string::String> {
|
|
let acquire_result = state.demo_backfill_running().compare_exchange(
|
|
false,
|
|
true,
|
|
std::sync::atomic::Ordering::AcqRel,
|
|
std::sync::atomic::Ordering::Acquire,
|
|
);
|
|
if acquire_result.is_err() {
|
|
return std::result::Result::Err("a backfill campaign is already running".to_string());
|
|
}
|
|
let _run_guard = crate::DemoBackfillRunGuard { running: state.demo_backfill_running() };
|
|
state
|
|
.demo_backfill_cancel_requested()
|
|
.store(false, std::sync::atomic::Ordering::Release);
|
|
let pipeline_request = match crate::build_demo_backfill_pipeline_request(request) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let profile = state.active_profile();
|
|
let store_options = match kb_store::PostgresStoreOptions::new(
|
|
profile.database.postgres.url.clone(),
|
|
profile.database.postgres.max_connections,
|
|
profile.database.postgres.connect_timeout_ms,
|
|
false,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
let store = match kb_store::PostgresStore::connect(store_options).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 observer = crate::DemoBackfillObserver {
|
|
app_handle,
|
|
cancel_requested: state.demo_backfill_cancel_requested(),
|
|
};
|
|
let summary = match kb_pipeline::execute_http_backfill(
|
|
state.http_pool(),
|
|
&store,
|
|
&pipeline_request,
|
|
&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(crate::demo_backfill_summary_payload(summary));
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn open_demo_http_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
let existing_window = app_handle.get_webview_window("demo_http");
|
|
if let std::option::Option::Some(window) = existing_window {
|
|
if let std::result::Result::Err(error) = window.show() {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
if let std::result::Result::Err(error) = window.set_focus() {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let build_result = tauri::WebviewWindowBuilder::new(
|
|
&app_handle,
|
|
"demo_http",
|
|
tauri::WebviewUrl::App("demo_http.html".into()),
|
|
)
|
|
.title("Khadhroony Bot3 - HTTP JSON-RPC")
|
|
.inner_size(1280.0, 860.0)
|
|
.min_inner_size(960.0, 620.0)
|
|
.resizable(true)
|
|
.visible(true)
|
|
.build();
|
|
return match build_result {
|
|
std::result::Result::Ok(window) => match window.set_focus() {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
|
},
|
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn demo_http_list_pool_clients(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot> {
|
|
return state.http_pool().snapshot();
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn demo_http_options(state: tauri::State<'_, crate::AppState>) -> crate::DemoHttpOptionsPayload {
|
|
return crate::DemoHttpOptionsPayload {
|
|
roles: crate::build_http_role_options(state.http_pool().snapshot()),
|
|
methods: crate::build_http_method_options(),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_http_execute_request(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoHttpRequest,
|
|
) -> std::result::Result<crate::DemoHttpExecutionPayload, std::string::String> {
|
|
return crate::demo_http_execute_request_inner(state, request).await;
|
|
}
|
|
|
|
/// Opens or focuses the WebSocket demo window.
|
|
#[tauri::command]
|
|
fn open_demo_ws_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
tracing::info!(target: crate::TRACING_TARGET, "open_demo_ws_window");
|
|
if let std::option::Option::Some(window) = app_handle.get_webview_window("demo_ws") {
|
|
if let std::result::Result::Err(error) = window.show() {
|
|
return std::result::Result::Err(
|
|
kb_core::Error::tauri(format!("cannot show demo_ws window: {error}")).to_string(),
|
|
);
|
|
}
|
|
if let std::result::Result::Err(error) = window.set_focus() {
|
|
return std::result::Result::Err(
|
|
kb_core::Error::tauri(format!("cannot focus demo_ws window: {error}")).to_string(),
|
|
);
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let build_result = tauri::WebviewWindowBuilder::new(
|
|
&app_handle,
|
|
"demo_ws",
|
|
tauri::WebviewUrl::App("demo_ws.html".into()),
|
|
)
|
|
.title("Khadhroony Bot3 - WebSocket standard")
|
|
.inner_size(1200.0, 760.0)
|
|
.min_inner_size(920.0, 560.0)
|
|
.resizable(true)
|
|
.visible(true)
|
|
.build();
|
|
return match build_result {
|
|
std::result::Result::Ok(window) => match window.set_focus() {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(error) => std::result::Result::Err(
|
|
kb_core::Error::tauri(format!("cannot focus created demo_ws window: {error}"))
|
|
.to_string(),
|
|
),
|
|
},
|
|
std::result::Result::Err(error) => std::result::Result::Err(
|
|
kb_core::Error::tauri(format!("cannot create demo_ws window: {error}")).to_string(),
|
|
),
|
|
};
|
|
}
|
|
|
|
/// Lists WebSocket endpoints available through the configured pool.
|
|
#[tauri::command]
|
|
fn demo_ws_list_pool_clients(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<
|
|
std::vec::Vec<kb_onchain_transport::WsPoolClientSnapshot>,
|
|
std::string::String,
|
|
> {
|
|
let pool = match state.demo_ws_pool() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
return std::result::Result::Ok(pool.snapshot());
|
|
}
|
|
|
|
/// Lists selectable WebSocket roles and methods for the demo UI.
|
|
#[tauri::command]
|
|
fn demo_ws_options(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::DemoWsOptionsPayload, std::string::String> {
|
|
return std::result::Result::Ok(crate::DemoWsOptionsPayload {
|
|
roles: crate::build_ws_role_options(match state.demo_ws_pool() {
|
|
std::result::Result::Ok(pool) => pool.snapshot(),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
}),
|
|
methods: crate::build_ws_method_options(),
|
|
});
|
|
}
|
|
|
|
/// Returns the current WebSocket demo session status.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_ws_status(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
|
|
return crate::demo_ws_status_inner(state.inner()).await;
|
|
}
|
|
|
|
/// Connects if needed, then subscribes through `kb_onchain_transport::WsSession`.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_ws_connect(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoWsRequest,
|
|
) -> std::result::Result<crate::DemoWsExecutionPayload, std::string::String> {
|
|
return crate::demo_ws_connect_inner(app_handle, state.inner(), request).await;
|
|
}
|
|
|
|
/// Unsubscribes one subscription while keeping the WebSocket connection open.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_ws_unsubscribe(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoWsUnsubscribeRequest,
|
|
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
|
|
return crate::demo_ws_unsubscribe_inner(app_handle, state.inner(), request.subscription_id)
|
|
.await;
|
|
}
|
|
|
|
/// Disconnects the current persistent WebSocket demo session.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_ws_disconnect(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
|
|
return crate::demo_ws_disconnect_inner(state.inner()).await;
|
|
}
|
|
|
|
/// Opens or focuses the SQL diagnostic demo window.
|
|
#[tauri::command]
|
|
fn open_demo_sql_diag_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
return crate::open_sql_demo_window(
|
|
app_handle,
|
|
"demo_sql_diag",
|
|
"demo_sql_diag.html",
|
|
"Khadhroony Bot3 - SQL diagnostics",
|
|
);
|
|
}
|
|
|
|
/// Loads read-only SQL diagnostics from the active profile.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn load_demo_sql_diag(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::DemoSqlDiagPayload, std::string::String> {
|
|
let profile = state.active_profile().clone();
|
|
let store_result = crate::connect_postgres_store(&profile).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let diagnostics_result = store.backend_diagnostics().await;
|
|
let diagnostics = match diagnostics_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
let tables_result = store.known_table_diagnostics().await;
|
|
let tables = match tables_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
return std::result::Result::Ok(crate::DemoSqlDiagPayload {
|
|
config_path: state.config_path().to_string(),
|
|
active_profile_name: profile.name.clone(),
|
|
backend: profile.database.backend.clone(),
|
|
masked_dsn: store.options().masked_dsn(),
|
|
current_schema: diagnostics.descriptor.current_schema,
|
|
health_status: crate::debug_status(diagnostics.health.status),
|
|
health_message: diagnostics.health.message,
|
|
migration_status: crate::debug_status(diagnostics.migrations.status),
|
|
migration_message: diagnostics.migrations.message,
|
|
server_version: diagnostics.server_version,
|
|
tables: crate::table_snapshots_from_pg(tables.as_slice()),
|
|
});
|
|
}
|
|
|
|
/// Opens or focuses the PostgreSQL canonical acquisition demo window.
|
|
#[tauri::command]
|
|
fn open_demo_sql_pg_raw_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
return crate::open_sql_demo_window(
|
|
app_handle,
|
|
"demo_sql_pg_raw",
|
|
"demo_sql_pg_raw.html",
|
|
"Khadhroony Bot3 - PostgreSQL canonical acquisition",
|
|
);
|
|
}
|
|
|
|
/// Loads read-only raw table diagnostics from PostgreSQL.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn load_demo_sql_pg_raw(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::DemoSqlPgRawPayload, std::string::String> {
|
|
let profile = state.active_profile().clone();
|
|
let store_result = crate::connect_postgres_store(&profile).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let tables_result = store.raw_table_diagnostics().await;
|
|
let tables = match tables_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
return std::result::Result::Ok(crate::DemoSqlPgRawPayload {
|
|
active_profile_name: profile.name.clone(),
|
|
masked_dsn: store.options().masked_dsn(),
|
|
tables: crate::table_snapshots_from_pg(tables.as_slice()),
|
|
});
|
|
}
|
|
|
|
/// Opens or focuses the PostgreSQL core store demo window.
|
|
#[tauri::command]
|
|
fn open_demo_sql_pg_core_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
return crate::open_sql_demo_window(
|
|
app_handle,
|
|
"demo_sql_pg_core",
|
|
"demo_sql_pg_core.html",
|
|
"Khadhroony Bot3 - PostgreSQL core store",
|
|
);
|
|
}
|
|
|
|
/// Loads read-only core table diagnostics from PostgreSQL.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn load_demo_sql_pg_core(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::DemoSqlPgCorePayload, std::string::String> {
|
|
let profile = state.active_profile().clone();
|
|
let store_result = crate::connect_postgres_store(&profile).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let tables_result = store.core_table_diagnostics().await;
|
|
let tables = match tables_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
return std::result::Result::Ok(crate::DemoSqlPgCorePayload {
|
|
active_profile_name: profile.name.clone(),
|
|
masked_dsn: store.options().masked_dsn(),
|
|
tables: crate::table_snapshots_from_pg(tables.as_slice()),
|
|
});
|
|
}
|
|
|
|
/// Opens or focuses the SQL replay candidate browser.
|
|
#[tauri::command]
|
|
fn open_demo_sql_replay_candidates_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
return crate::open_sql_demo_window(
|
|
app_handle,
|
|
"demo_sql_replay_candidates",
|
|
"demo_sql_replay_candidates.html",
|
|
"Khadhroony Bot3 - SQL replay candidates",
|
|
);
|
|
}
|
|
|
|
/// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn export_demo_sql_replay_csv(
|
|
file_name: std::string::String,
|
|
content: std::string::String,
|
|
) -> std::result::Result<std::string::String, std::string::String> {
|
|
if content.is_empty() {
|
|
return std::result::Result::Err("CSV export content must not be empty".to_owned());
|
|
}
|
|
if content.len() > 16 * 1024 * 1024 {
|
|
return std::result::Result::Err("CSV export content exceeds 16 MiB".to_owned());
|
|
}
|
|
let normalized_name_result = crate::validated_csv_file_name(&file_name);
|
|
let normalized_name = match normalized_name_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let current_dir_result = std::env::current_dir();
|
|
let current_dir = match current_dir_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(format!("cannot resolve current directory: {error}"));
|
|
},
|
|
};
|
|
let export_dir = crate::csv_export_directory_from_current_dir(¤t_dir);
|
|
let create_result = tokio::fs::create_dir_all(&export_dir).await;
|
|
if let std::result::Result::Err(error) = create_result {
|
|
return std::result::Result::Err(format!("cannot create CSV export directory: {error}"));
|
|
}
|
|
let export_path_result = crate::available_csv_export_path(&export_dir, normalized_name).await;
|
|
let export_path = match export_path_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let mut bytes = std::vec::Vec::with_capacity(content.len() + 3);
|
|
bytes.extend_from_slice(&[0xEF, 0xBB, 0xBF]);
|
|
bytes.extend_from_slice(content.as_bytes());
|
|
let write_result = tokio::fs::write(&export_path, bytes).await;
|
|
if let std::result::Result::Err(error) = write_result {
|
|
return std::result::Result::Err(format!("cannot write CSV export: {error}"));
|
|
}
|
|
let display_path = export_path.to_string_lossy().into_owned();
|
|
tracing::info!(target: crate::TRACING_TARGET, path = %display_path, "CSV replay candidate export written");
|
|
return std::result::Result::Ok(display_path);
|
|
}
|
|
|
|
/// Loads static browser options from the active PostgreSQL profile.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_sql_replay_options(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::DemoSqlReplayOptionsPayload, std::string::String> {
|
|
let profile = state.active_profile().clone();
|
|
let options_result = crate::postgres_store_options_from_profile(&profile, false);
|
|
let options = match options_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
let mut known_programs =
|
|
std::vec::Vec::with_capacity(kb_program_ids::registered_program_ids().len());
|
|
for entry in kb_program_ids::registered_program_ids() {
|
|
known_programs.push(crate::DemoSqlReplayKnownProgramOption {
|
|
code: entry.code().to_owned(),
|
|
program_id: entry.program_id().to_owned(),
|
|
});
|
|
}
|
|
return std::result::Result::Ok(crate::DemoSqlReplayOptionsPayload {
|
|
active_profile_name: profile.name,
|
|
masked_dsn: options.masked_dsn(),
|
|
maximum_limit: kb_store::MAX_REPLAY_CANDIDATE_ROWS,
|
|
known_programs,
|
|
});
|
|
}
|
|
|
|
/// Loads bounded transaction replay candidates from PostgreSQL.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn load_demo_sql_replay_transactions(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoSqlReplayTransactionRequest,
|
|
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayTransactionRow>, std::string::String> {
|
|
let program_scope_result = crate::program_scope_from_code(request.program_scope.as_str());
|
|
let program_scope = match program_scope_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let entity_kind_result = crate::optional_entity_kind_from_code(request.entity_kind.as_deref());
|
|
let entity_kind = match entity_kind_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let filter_result = kb_store::PostgresReplayTransactionFilter::new(
|
|
request.signature_contains,
|
|
request.min_slot,
|
|
request.max_slot,
|
|
request.raw_processing_state,
|
|
request.ledger_status,
|
|
request.program_id,
|
|
program_scope,
|
|
entity_kind,
|
|
request.entity_value,
|
|
request.limit,
|
|
request.newest_first,
|
|
);
|
|
let filter = match filter_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let rows_result = store.replay_transaction_candidates(&filter).await;
|
|
let rows = match rows_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay transaction candidates");
|
|
let mut output = std::vec::Vec::with_capacity(rows.len());
|
|
for row in rows {
|
|
output.push(crate::transaction_row_from_pg(row));
|
|
}
|
|
return std::result::Result::Ok(output);
|
|
}
|
|
|
|
/// Loads bounded program summaries from PostgreSQL.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn load_demo_sql_replay_programs(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoSqlReplayProgramRequest,
|
|
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayProgramRow>, std::string::String> {
|
|
let filter_result =
|
|
kb_store::PostgresReplayProgramFilter::new(request.program_id_contains, request.limit);
|
|
let filter = match filter_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let rows_result = store.replay_program_summaries(&filter).await;
|
|
let rows = match rows_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay program summaries");
|
|
let mut output = std::vec::Vec::with_capacity(rows.len());
|
|
for row in rows {
|
|
output.push(crate::program_row_from_pg(row));
|
|
}
|
|
return std::result::Result::Ok(output);
|
|
}
|
|
|
|
/// Loads bounded mint, owner or account-key summaries from PostgreSQL core tables.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn load_demo_sql_replay_entities(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoSqlReplayEntityRequest,
|
|
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayEntityRow>, std::string::String> {
|
|
let entity_kind_result = crate::entity_kind_from_code(request.entity_kind.as_str());
|
|
let entity_kind = match entity_kind_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let filter_result = kb_store::PostgresReplayEntityFilter::new(
|
|
entity_kind,
|
|
request.entity_value_contains,
|
|
request.limit,
|
|
);
|
|
let filter = match filter_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
let store_result = crate::connect_postgres_store(state.active_profile()).await;
|
|
let store = match store_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let rows_result = store.replay_entity_summaries(&filter).await;
|
|
let rows = match rows_result {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay entity summaries");
|
|
let mut output = std::vec::Vec::with_capacity(rows.len());
|
|
for row in rows {
|
|
output.push(crate::entity_row_from_pg(row));
|
|
}
|
|
return std::result::Result::Ok(output);
|
|
}
|
|
|
|
/// Opens or focuses the Solana Core execution demo window.
|
|
#[tauri::command]
|
|
fn open_demo_execution_solana_core_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
let existing_window = app_handle.get_webview_window("demo_execution_solana_core");
|
|
if let std::option::Option::Some(window) = existing_window {
|
|
let show_result = window.show();
|
|
if let std::result::Result::Err(error) = show_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
let focus_result = window.set_focus();
|
|
if let std::result::Result::Err(error) = focus_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let build_result = tauri::WebviewWindowBuilder::new(
|
|
&app_handle,
|
|
"demo_execution_solana_core",
|
|
tauri::WebviewUrl::App("demo_execution_solana_core.html".into()),
|
|
)
|
|
.title("Khadhroony Bot3 - Exécution Solana Devnet")
|
|
.inner_size(1360.0, 920.0)
|
|
.min_inner_size(1040.0, 700.0)
|
|
.resizable(true)
|
|
.visible(true)
|
|
.build();
|
|
return match build_result {
|
|
std::result::Result::Ok(window) => {
|
|
let focus_result = window.set_focus();
|
|
if let std::result::Result::Err(error) = focus_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
std::result::Result::Ok(())
|
|
},
|
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
|
};
|
|
}
|
|
|
|
/// Returns compatible Devnet profiles and conservative defaults.
|
|
#[tauri::command]
|
|
fn demo_execution_solana_core_options(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> crate::DemoExecutionSolanaCoreOptionsPayload {
|
|
let profiles = crate::devnet_profile_options(state.app_config());
|
|
let default_profile_name = profiles.first().map(|profile| return profile.name.clone());
|
|
return crate::DemoExecutionSolanaCoreOptionsPayload {
|
|
profiles,
|
|
default_profile_name,
|
|
default_transfer_lamports: 1_000_000,
|
|
running: state
|
|
.demo_execution_solana_core_running()
|
|
.load(std::sync::atomic::Ordering::Acquire),
|
|
};
|
|
}
|
|
|
|
/// Generates a disposable recipient public key without exposing its private key.
|
|
#[tauri::command]
|
|
fn demo_execution_solana_core_generate_recipient()
|
|
-> std::result::Result<crate::DemoExecutionSolanaCoreGeneratedRecipientPayload, std::string::String>
|
|
{
|
|
let alias = match kb_wallet::WalletAlias::parse("demo-devnet-recipient") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
let wallet = kb_wallet::TemporaryWallet::generate(alias);
|
|
return std::result::Result::Ok(crate::DemoExecutionSolanaCoreGeneratedRecipientPayload {
|
|
public_key: wallet.public_key(),
|
|
});
|
|
}
|
|
|
|
/// Executes one bounded System transfer simulation or Devnet submission.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_execution_solana_core_execute(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoExecutionSolanaCoreRequest,
|
|
) -> std::result::Result<crate::DemoExecutionSolanaCoreSummaryPayload, std::string::String> {
|
|
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
|
|
false,
|
|
true,
|
|
std::sync::atomic::Ordering::AcqRel,
|
|
std::sync::atomic::Ordering::Acquire,
|
|
);
|
|
if acquire_result.is_err() {
|
|
return std::result::Result::Err("a Solana Core execution is already running".to_string());
|
|
}
|
|
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
|
|
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 http_pool = match kb_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()),
|
|
};
|
|
let initialize_result = store.initialize_store_schema().await;
|
|
if let std::result::Result::Err(error) = initialize_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSystemTransferRequest::new(
|
|
format!("demo-execution-{}", chrono::Utc::now().timestamp_micros()),
|
|
kb_lib::MdPubkey(request.recipient.trim().to_string()),
|
|
request.lamports,
|
|
);
|
|
pipeline_request.airdrop_lamports = request.airdrop_lamports;
|
|
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;
|
|
pipeline_request.materialize_after_decode = request.materialize_after_decode;
|
|
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
|
|
std::vec![std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder)];
|
|
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> =
|
|
if request.materialize_after_decode {
|
|
std::vec![
|
|
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
|
|
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer,),
|
|
std::sync::Arc::new(kb_lib::MtLifecycleMaterializer),
|
|
std::sync::Arc::new(kb_lib::MtStakingMaterializer),
|
|
]
|
|
} else {
|
|
std::vec::Vec::new()
|
|
};
|
|
let observer = crate::DemoExecutionSolanaCoreObserver {
|
|
app_handle,
|
|
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
|
};
|
|
let workspace_root = crate::workspace_root_dir();
|
|
let summary = match kb_pipeline_demo_scenarios::execute_devnet_system_transfer(
|
|
&http_pool,
|
|
&store,
|
|
&profile,
|
|
workspace_root.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(crate::demo_execution_solana_core_summary_payload(summary));
|
|
}
|
|
|
|
/// Executes one Memo v4 simulation or explicitly authorized Devnet submission.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_execution_spl_memo_execute(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoExecutionMemoRequest,
|
|
) -> std::result::Result<crate::DemoExecutionMemoSummaryPayload, std::string::String> {
|
|
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
|
|
false,
|
|
true,
|
|
std::sync::atomic::Ordering::AcqRel,
|
|
std::sync::atomic::Ordering::Acquire,
|
|
);
|
|
if acquire_result.is_err() {
|
|
return std::result::Result::Err("a Devnet execution is already running".to_string());
|
|
}
|
|
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
|
|
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 http_pool = match kb_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 mut pipeline_request = kb_pipeline_demo_scenarios::DevnetMemoExecutionRequest::new(
|
|
format!("demo-memo-execution-{}", chrono::Utc::now().timestamp_micros()),
|
|
request.message,
|
|
);
|
|
pipeline_request.include_wallet_as_memo_signer = request.include_wallet_as_memo_signer;
|
|
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 decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
|
|
std::vec![std::sync::Arc::new(kb_lib::DcSplMemoDecoder)];
|
|
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> =
|
|
std::vec![std::sync::Arc::new(kb_lib::MtTransactionAnnotationMaterializer,)];
|
|
let observer = crate::DemoExecutionSolanaCoreObserver {
|
|
app_handle,
|
|
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
|
};
|
|
let workspace_root = crate::workspace_root_dir();
|
|
let summary = match kb_pipeline_demo_scenarios::execute_devnet_memo(
|
|
&http_pool,
|
|
&store,
|
|
&profile,
|
|
workspace_root.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(crate::demo_execution_solana_core_memo_summary_payload(
|
|
summary,
|
|
));
|
|
}
|
|
|
|
/// Requests cooperative cancellation before the next execution stage.
|
|
#[tauri::command]
|
|
fn demo_execution_solana_core_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
|
|
let running = state
|
|
.demo_execution_solana_core_running()
|
|
.load(std::sync::atomic::Ordering::Acquire);
|
|
if !running {
|
|
return false;
|
|
}
|
|
state
|
|
.demo_execution_solana_core_cancel_requested()
|
|
.store(true, std::sync::atomic::Ordering::Release);
|
|
return true;
|
|
}
|
|
|
|
/// Returns the complete ordered Devnet SPL validation inventory for milestone 0.4.6.
|
|
#[tauri::command]
|
|
fn demo_execution_spl_validation_scenarios()
|
|
-> std::vec::Vec<crate::DevnetSplValidationScenarioPayload> {
|
|
return kb_pipeline_demo_scenarios::devnet_spl_validation_scenarios()
|
|
.into_iter()
|
|
.map(|scenario| {
|
|
return crate::DevnetSplValidationScenarioPayload {
|
|
id: scenario.id,
|
|
label: scenario.label,
|
|
family: match scenario.family {
|
|
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Public => {
|
|
"token2022_public".to_string()
|
|
},
|
|
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::ElGamalRegistry => {
|
|
"elgamal_registry".to_string()
|
|
},
|
|
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Confidential => {
|
|
"token2022_confidential".to_string()
|
|
},
|
|
},
|
|
operation_code: scenario.operation_code,
|
|
implementation_status: match scenario.implementation_status {
|
|
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::Executable => {
|
|
"executable".to_string()
|
|
},
|
|
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::BackendReady => {
|
|
"backend_ready".to_string()
|
|
},
|
|
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::ProofFixtureRequired => {
|
|
"proof_fixture_required".to_string()
|
|
},
|
|
},
|
|
proof_required: scenario.proof_required,
|
|
required_fixture_variables: scenario.required_fixture_variables,
|
|
};
|
|
})
|
|
.collect();
|
|
}
|
|
|
|
/// Opens or focuses the dedicated SPL execution window.
|
|
#[tauri::command]
|
|
fn open_demo_execution_spl_window(
|
|
app_handle: tauri::AppHandle,
|
|
) -> std::result::Result<(), std::string::String> {
|
|
tracing::info!(target: crate::TRACING_TARGET, "open SPL Devnet execution window");
|
|
let existing_window = app_handle.get_webview_window("demo_execution_spl");
|
|
if let std::option::Option::Some(window) = existing_window {
|
|
let show_result = window.show();
|
|
if let std::result::Result::Err(error) = show_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
let focus_result = window.set_focus();
|
|
if let std::result::Result::Err(error) = focus_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let build_result = tauri::WebviewWindowBuilder::new(
|
|
&app_handle,
|
|
"demo_execution_spl",
|
|
tauri::WebviewUrl::App("demo_execution_spl.html".into()),
|
|
)
|
|
.title("Khadhroony Bot3 - Exécution SPL Devnet")
|
|
.inner_size(1500.0, 960.0)
|
|
.min_inner_size(1120.0, 720.0)
|
|
.resizable(true)
|
|
.visible(true)
|
|
.build();
|
|
return match build_result {
|
|
std::result::Result::Ok(window) => {
|
|
let focus_result = window.set_focus();
|
|
if let std::result::Result::Err(error) = focus_result {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
std::result::Result::Ok(())
|
|
},
|
|
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
|
};
|
|
}
|
|
|
|
/// Executes one checked SPL Token simulation or explicitly authorized Devnet submission.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_execution_spl_token_execute(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoExecutionSplTokenRequest,
|
|
) -> std::result::Result<crate::DemoExecutionSplTokenSummaryPayload, std::string::String> {
|
|
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
|
|
false,
|
|
true,
|
|
std::sync::atomic::Ordering::AcqRel,
|
|
std::sync::atomic::Ordering::Acquire,
|
|
);
|
|
if acquire_result.is_err() {
|
|
return std::result::Result::Err("a Devnet execution is already running".to_string());
|
|
}
|
|
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
|
|
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 http_pool = match kb_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 amount_raw = request.amount_raw.trim().to_string();
|
|
let operation = kb_lib::ExSplClassicTokenOperation::Instruction {
|
|
value: kb_lib::ExSplClassicTokenSingleOperation::TransferChecked {
|
|
source: kb_lib::MdPubkey(request.source.trim().to_string()),
|
|
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
|
|
destination: kb_lib::MdPubkey(request.destination.trim().to_string()),
|
|
authority: kb_lib::ExSplClassicTokenAuthority {
|
|
authority: kb_lib::MdPubkey(request.authority.trim().to_string()),
|
|
multisig_signers: std::vec::Vec::new(),
|
|
},
|
|
amount: kb_lib::ExSplClassicTokenAmount(amount_raw.clone()),
|
|
decimals: request.decimals,
|
|
},
|
|
};
|
|
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSplTokenExecutionRequest::new(
|
|
format!("demo-spl-token-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 decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
|
|
std::vec![std::sync::Arc::new(kb_lib::DcSplTokenDecoder)];
|
|
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
|
|
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
|
|
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
|
|
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
|
|
];
|
|
let observer = crate::DemoExecutionSolanaCoreObserver {
|
|
app_handle,
|
|
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
|
};
|
|
let workspace_root = crate::workspace_root_dir();
|
|
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token(
|
|
&http_pool,
|
|
&store,
|
|
&profile,
|
|
workspace_root.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(crate::demo_spl_token_summary_payload(
|
|
summary,
|
|
amount_raw,
|
|
request.decimals,
|
|
));
|
|
}
|
|
|
|
/// Loads a bounded, typed journal of committed classic SPL Token projections.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_spl_token_journal(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoSplTokenJournalRequest,
|
|
) -> std::result::Result<std::vec::Vec<crate::DemoSplTokenJournalRow>, std::string::String> {
|
|
let validated = match crate::validate_journal_request(request) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let profile =
|
|
match crate::select_devnet_profile(state.app_config(), validated.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()),
|
|
};
|
|
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
|
return std::result::Result::Err(error.to_string());
|
|
}
|
|
let needs_payload_filter =
|
|
validated.mint.is_some() || validated.account.is_some() || validated.operation.is_some();
|
|
let query_limit = if needs_payload_filter {
|
|
kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS
|
|
} else {
|
|
validated.limit
|
|
};
|
|
let filter = match kb_store::MaterializedEventFilter::new(
|
|
std::option::Option::None,
|
|
validated.family.clone(),
|
|
validated.signature_contains.clone(),
|
|
query_limit,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
|
};
|
|
let rows = match kb_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_token"
|
|
|| !crate::payload_matches(&row.payload_json, &validated)
|
|
{
|
|
continue;
|
|
}
|
|
output.push(crate::demo_spl_token_journal_row(row));
|
|
if output.len() >= validated.limit as usize {
|
|
break;
|
|
}
|
|
}
|
|
return std::result::Result::Ok(output);
|
|
}
|
|
|
|
/// Derives the representative profile wallet ATA without exposing private key material.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_spl_ata_derive(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoSplAtaDerivationRequest,
|
|
) -> std::result::Result<crate::DemoSplAtaDerivationPayload, std::string::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 wallet = match crate::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 crate::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 crate::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.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_execution_spl_ata_execute(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoExecutionSplAtaRequest,
|
|
) -> std::result::Result<crate::DemoExecutionSplAtaSummaryPayload, std::string::String> {
|
|
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::DemoExecutionSolanaCoreRunGuard {
|
|
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 crate::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 crate::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 = kb_lib::MdPubkey(request.wallet_owner.trim().to_string());
|
|
let mint = kb_lib::MdPubkey(request.mint.trim().to_string());
|
|
let operation = match request.mode.as_str() {
|
|
"create" => kb_lib::ExSplAssociatedTokenAccountOperation::Create {
|
|
wallet_owner,
|
|
mint,
|
|
token_program,
|
|
},
|
|
"create_idempotent" => kb_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 =
|
|
kb_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 kb_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::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> = std::vec![
|
|
std::sync::Arc::new(kb_lib::DcSplAssociatedTokenAccountDecoder),
|
|
std::sync::Arc::new(kb_lib::DcSplTokenDecoder),
|
|
];
|
|
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
|
|
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
|
|
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
|
|
];
|
|
let observer = crate::DemoExecutionSolanaCoreObserver {
|
|
app_handle,
|
|
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
|
};
|
|
let summary = match kb_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(crate::demo_spl_ata_summary_payload(summary));
|
|
}
|
|
|
|
/// Loads a bounded journal of ATA-owned lifecycle facts.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_spl_ata_journal(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoSplAtaJournalRequest,
|
|
) -> std::result::Result<std::vec::Vec<crate::DemoSplAtaJournalRow>, 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 kb_store::MaterializedEventFilter::new(
|
|
std::option::Option::Some("spl_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 kb_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"
|
|
|| !crate::journal_matches(&row.payload_json, &request)
|
|
{
|
|
continue;
|
|
}
|
|
output.push(crate::journal_row(row));
|
|
if output.len() >= request.limit as usize {
|
|
break;
|
|
}
|
|
}
|
|
return std::result::Result::Ok(output);
|
|
}
|
|
|
|
/// Executes one public Token-2022 simulation or explicitly authorized Devnet submission.
|
|
#[tauri::command]
|
|
#[allow(clippy::question_mark_used)]
|
|
async fn demo_execution_spl_token2022_execute(
|
|
app_handle: tauri::AppHandle,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
request: crate::DemoExecutionSplToken2022Request,
|
|
) -> std::result::Result<crate::DemoExecutionSplToken2022SummaryPayload, std::string::String> {
|
|
let acquire_result = state.demo_execution_solana_core_running().compare_exchange(
|
|
false,
|
|
true,
|
|
std::sync::atomic::Ordering::AcqRel,
|
|
std::sync::atomic::Ordering::Acquire,
|
|
);
|
|
if acquire_result.is_err() {
|
|
return std::result::Result::Err("a Devnet execution is already running".to_string());
|
|
}
|
|
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
|
|
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 http_pool = match kb_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 operation = match crate::operation_from_request(&request) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let operation_code = operation.operation_code().to_string();
|
|
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionRequest::new(
|
|
format!("demo-spl-token-2022-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 decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
|
|
std::vec![std::sync::Arc::new(kb_lib::DcSplToken2022Decoder)];
|
|
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
|
|
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
|
|
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
|
|
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
|
|
std::sync::Arc::new(kb_lib::MtFeesMaterializer),
|
|
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer),
|
|
];
|
|
let observer = crate::DemoExecutionSolanaCoreObserver {
|
|
app_handle,
|
|
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
|
|
};
|
|
let workspace_root = crate::workspace_root_dir();
|
|
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token2022(
|
|
&http_pool,
|
|
&store,
|
|
&profile,
|
|
workspace_root.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(crate::demo_spl_token2022_summary_payload(
|
|
request.scenario_id,
|
|
operation_code,
|
|
summary,
|
|
));
|
|
}
|
|
|
|
/// Loads public Token-2022 fixture values without reading private key bytes.
|
|
#[tauri::command]
|
|
fn demo_spl_token2022_fixture(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
profile_name: std::string::String,
|
|
) -> std::result::Result<crate::DemoSplToken2022FixturePayload, std::string::String> {
|
|
let profile = match crate::select_devnet_profile(state.app_config(), profile_name.as_str()) {
|
|
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 wallet_dir = if configured.is_absolute() {
|
|
configured
|
|
} else {
|
|
crate::workspace_root_dir().join(configured)
|
|
};
|
|
let fixture_path = wallet_dir.join("spl_token2022_validation").join("fixture.env");
|
|
let contents = match std::fs::read_to_string(&fixture_path) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(format!(
|
|
"unable to read Token-2022 fixture {}: {error}",
|
|
fixture_path.display()
|
|
));
|
|
},
|
|
};
|
|
let values = crate::parse_fixture(contents.as_str());
|
|
let required = |name: &str| -> std::result::Result<std::string::String, std::string::String> {
|
|
return match values.get(name) {
|
|
std::option::Option::Some(value) if !value.trim().is_empty() => {
|
|
std::result::Result::Ok(value.clone())
|
|
},
|
|
_ => std::result::Result::Err(format!("Token-2022 fixture is missing {name}")),
|
|
};
|
|
};
|
|
let program_id = match required("TOKEN_2022_PROGRAM") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let mint = match required("TOKEN_2022_MINT") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let source = match required("TOKEN_2022_SOURCE") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let destination = match required("TOKEN_2022_DESTINATION") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let close_account = match required("TOKEN_2022_CLOSE_ACCOUNT") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let delegate = match required("TOKEN_2022_DELEGATE") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let authority = match required("TOKEN_2022_AUTHORITY") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let freeze_authority = match required("TOKEN_2022_FREEZE_AUTHORITY") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let decimals_text = match required("TOKEN_2022_DECIMALS") {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let decimals = match decimals_text.parse::<u8>() {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(format!("invalid TOKEN_2022_DECIMALS value: {error}"));
|
|
},
|
|
};
|
|
return std::result::Result::Ok(crate::DemoSplToken2022FixturePayload {
|
|
fixture_path: fixture_path.display().to_string(),
|
|
program_id,
|
|
mint,
|
|
source,
|
|
destination,
|
|
close_account,
|
|
delegate,
|
|
authority,
|
|
freeze_authority,
|
|
decimals,
|
|
default_amount_raw: values
|
|
.get("TOKEN_2022_TRANSFER_AMOUNT_RAW")
|
|
.cloned()
|
|
.unwrap_or_else(|| return "1".to_string()),
|
|
});
|
|
}
|