v0.1.0-pre.053
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/app_state.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Shared Tauri application state and startup initialization.
|
||||
|
||||
@@ -21,6 +21,8 @@ pub(crate) struct AppState {
|
||||
demo_decode_replay_running: std::sync::atomic::AtomicBool,
|
||||
demo_decode_replay_cancel_requested: std::sync::atomic::AtomicBool,
|
||||
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,
|
||||
}
|
||||
|
||||
impl crate::AppState {
|
||||
@@ -60,6 +62,8 @@ impl crate::AppState {
|
||||
demo_decode_replay_running: std::sync::atomic::AtomicBool::new(false),
|
||||
demo_decode_replay_cancel_requested: std::sync::atomic::AtomicBool::new(false),
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,6 +156,18 @@ impl crate::AppState {
|
||||
return &self.demo_decode_replay_campaign_id;
|
||||
}
|
||||
|
||||
/// Returns the shared execution running flag used by Solana and SPL windows.
|
||||
pub(crate) fn demo_execution_solana_core_running(&self) -> &std::sync::atomic::AtomicBool {
|
||||
return &self.demo_execution_solana_core_running;
|
||||
}
|
||||
|
||||
/// Returns the shared execution cancellation flag used by Solana and SPL windows.
|
||||
pub(crate) fn demo_execution_solana_core_cancel_requested(
|
||||
&self,
|
||||
) -> &std::sync::atomic::AtomicBool {
|
||||
return &self.demo_execution_solana_core_cancel_requested;
|
||||
}
|
||||
|
||||
/// Returns the number of logging routes held by the logging guard.
|
||||
pub(crate) fn logging_route_count(&self) -> usize {
|
||||
let lock_result = self.logging_guard.lock();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_decode_replay.rs
|
||||
// version: 24
|
||||
// version: 25
|
||||
|
||||
//! Tauri commands and UI payloads for contextual instruction decode replay.
|
||||
|
||||
@@ -370,6 +370,7 @@ pub(crate) fn available_decoders()
|
||||
-> std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> {
|
||||
return std::vec![
|
||||
std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder),
|
||||
std::sync::Arc::new(kb_lib::DcMetadataMetaplexTokenMetadataDecoder),
|
||||
std::sync::Arc::new(kb_lib::DcSplAssociatedTokenAccountDecoder,),
|
||||
std::sync::Arc::new(kb_lib::DcSplElgamalRegistryDecoder),
|
||||
std::sync::Arc::new(kb_lib::DcSplMemoDecoder),
|
||||
@@ -716,9 +717,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_memo_token_2022_elgamal_classic_token_and_ata_decoders_are_registered() {
|
||||
fn native_memo_token2022_elgamal_classic_token_and_ata_decoders_are_registered() {
|
||||
let decoders = crate::available_decoders();
|
||||
assert_eq!(decoders.len(), 6);
|
||||
assert_eq!(decoders.len(), 7);
|
||||
let names = decoders
|
||||
.iter()
|
||||
.map(|decoder| return decoder.identity().name)
|
||||
@@ -726,12 +727,13 @@ mod tests {
|
||||
assert_eq!(
|
||||
names,
|
||||
std::vec![
|
||||
"kb_decoder_metadata_metaplex_token_metadata".to_string(),
|
||||
"solana_native_classifier".to_string(),
|
||||
"spl_associated_token_account".to_string(),
|
||||
"spl_elgamal_registry".to_string(),
|
||||
"spl_memo".to_string(),
|
||||
"spl_token".to_string(),
|
||||
"spl_token_2022".to_string(),
|
||||
"spl_token2022".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
669
kb-app-demo-desktop/src/demo_execution_solana_core.rs
Normal file
669
kb-app-demo-desktop/src/demo_execution_solana_core.rs
Normal file
@@ -0,0 +1,669 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
|
||||
// version: 5
|
||||
|
||||
//! Tauri adapter for bounded Solana Core execution on Devnet.
|
||||
|
||||
use tauri::Emitter; // rust-rules: trait-import
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// One Devnet execution profile exposed to the demo window.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProfileOption.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSolanaCoreProfileOption {
|
||||
/// Stable profile name.
|
||||
pub(crate) name: std::string::String,
|
||||
/// Non-secret temporary wallet alias.
|
||||
pub(crate) wallet_alias: std::string::String,
|
||||
/// Maximum Devnet spend allowed by the profile.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) max_spend_lamports: u64,
|
||||
/// Maximum faucet request allowed by the profile.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) max_airdrop_lamports: u64,
|
||||
/// Whether signed Devnet submission is enabled.
|
||||
pub(crate) send_enabled: bool,
|
||||
/// Whether explicit operator confirmation is required.
|
||||
pub(crate) require_operator_confirmation: bool,
|
||||
}
|
||||
|
||||
/// Initial options shown by the Solana Core execution demo.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSolanaCoreOptionsPayload {
|
||||
/// Devnet profiles compatible with the execution laboratory.
|
||||
pub(crate) profiles: std::vec::Vec<crate::DemoExecutionSolanaCoreProfileOption>,
|
||||
/// Suggested profile name.
|
||||
pub(crate) default_profile_name: std::option::Option<std::string::String>,
|
||||
/// Default transfer amount suitable for a new zero-data account on Devnet.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) default_transfer_lamports: u64,
|
||||
/// Whether one execution is currently running.
|
||||
pub(crate) running: bool,
|
||||
}
|
||||
|
||||
/// Request sent by the execution demo.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreRequest.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSolanaCoreRequest {
|
||||
/// Selected Devnet profile.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Recipient public key.
|
||||
pub(crate) recipient: std::string::String,
|
||||
/// Lamports transferred by the System instruction.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) lamports: u64,
|
||||
/// Optional bounded faucet request when the source wallet is underfunded.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) airdrop_lamports: u64,
|
||||
/// Whether the transaction should be signed and submitted after simulation.
|
||||
pub(crate) submit: bool,
|
||||
/// Explicit operator confirmation for signed submission.
|
||||
pub(crate) operator_confirmed: bool,
|
||||
/// Whether post-execution core/decode outputs should be force-replayed.
|
||||
pub(crate) force_post_validation_replay: bool,
|
||||
/// Whether compatible materializers should run after decode replay.
|
||||
pub(crate) materialize_after_decode: bool,
|
||||
}
|
||||
|
||||
/// Request sent by the Memo v4 Devnet execution panel.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionMemoRequest.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionMemoRequest {
|
||||
/// Selected Devnet profile.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Exact UTF-8 Memo payload.
|
||||
pub(crate) message: std::string::String,
|
||||
/// Whether the wallet is supplied as a readonly Memo signer account.
|
||||
pub(crate) include_wallet_as_memo_signer: bool,
|
||||
/// Whether the transaction should be signed and submitted after simulation.
|
||||
pub(crate) submit: bool,
|
||||
/// Explicit operator confirmation for signed submission.
|
||||
pub(crate) operator_confirmed: bool,
|
||||
/// Whether post-execution core and first decode outputs should be force-replayed.
|
||||
pub(crate) force_post_validation_replay: bool,
|
||||
}
|
||||
|
||||
/// Public key generated for a disposable Devnet recipient.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreGeneratedRecipientPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSolanaCoreGeneratedRecipientPayload {
|
||||
/// Base58 public key. The private key is not persisted or exposed.
|
||||
pub(crate) public_key: std::string::String,
|
||||
}
|
||||
|
||||
/// Progress event emitted during execution and post-validation.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProgressPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSolanaCoreProgressPayload {
|
||||
/// RFC 3339 timestamp.
|
||||
pub(crate) timestamp: std::string::String,
|
||||
/// Stable severity code.
|
||||
pub(crate) level: std::string::String,
|
||||
/// Stable stage code.
|
||||
pub(crate) stage: std::string::String,
|
||||
/// Human-readable message.
|
||||
pub(crate) message: std::string::String,
|
||||
/// Transaction signature when already available.
|
||||
pub(crate) signature: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
/// UI-safe result of one execution orchestration.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreSummaryPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSolanaCoreSummaryPayload {
|
||||
/// Profile used by the execution.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Classified cluster code.
|
||||
pub(crate) cluster: std::string::String,
|
||||
/// Genesis hash returned by the endpoint.
|
||||
pub(crate) genesis_hash: std::string::String,
|
||||
/// Source wallet public key.
|
||||
pub(crate) wallet_public_key: std::string::String,
|
||||
/// Recipient public key.
|
||||
pub(crate) recipient: std::string::String,
|
||||
/// Whether the recipient account existed before execution.
|
||||
pub(crate) recipient_existed_before: bool,
|
||||
/// Recipient balance before execution when present.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) recipient_balance_before_lamports: std::option::Option<u64>,
|
||||
/// Rent-exempt minimum required for a new zero-data account.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) recipient_minimum_balance_lamports: u64,
|
||||
/// Source balance before optional funding.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) balance_before_lamports: u64,
|
||||
/// Source balance after optional funding.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) balance_after_funding_lamports: u64,
|
||||
/// Estimated transaction fee.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) fee_lamports: std::option::Option<u64>,
|
||||
/// Whether exact simulation succeeded.
|
||||
pub(crate) simulation_success: bool,
|
||||
/// Runtime simulation error when present.
|
||||
pub(crate) simulation_error: std::option::Option<std::string::String>,
|
||||
/// Runtime simulation logs.
|
||||
pub(crate) simulation_logs: std::vec::Vec<std::string::String>,
|
||||
/// Faucet signature when funding was requested.
|
||||
pub(crate) airdrop_signature: std::option::Option<std::string::String>,
|
||||
/// Submitted transaction signature.
|
||||
pub(crate) transaction_signature: std::option::Option<std::string::String>,
|
||||
/// Terminal confirmation status.
|
||||
pub(crate) confirmation_status: std::option::Option<std::string::String>,
|
||||
/// Canonical rows inserted after submission.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) canonical_inserted: std::option::Option<u64>,
|
||||
/// Core transactions extracted after submission.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) core_extracted: std::option::Option<u64>,
|
||||
/// Contextual decode inputs completed after submission.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) decode_completed: std::option::Option<u64>,
|
||||
/// Contextual decode input failures after submission.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) decode_failed_inputs: std::option::Option<u64>,
|
||||
/// Pretty JSON representation of the exact execution plan.
|
||||
pub(crate) plan_json: std::string::String,
|
||||
/// Pretty JSON representation of the simulation result.
|
||||
pub(crate) simulation_json: std::string::String,
|
||||
/// Pretty JSON representation of confirmation and post-validation diagnostics.
|
||||
pub(crate) diagnostics_json: std::string::String,
|
||||
}
|
||||
|
||||
/// UI-safe result of one Memo v4 Devnet execution orchestration.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionMemoSummaryPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionMemoSummaryPayload {
|
||||
/// Profile used by the execution.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Classified cluster code.
|
||||
pub(crate) cluster: std::string::String,
|
||||
/// Genesis hash returned by the endpoint.
|
||||
pub(crate) genesis_hash: std::string::String,
|
||||
/// Source wallet public key.
|
||||
pub(crate) wallet_public_key: std::string::String,
|
||||
/// Exact wallet balance rendered without JSON integer precision loss.
|
||||
pub(crate) balance_lamports: std::string::String,
|
||||
/// Estimated transaction fee rendered without JSON integer precision loss.
|
||||
pub(crate) fee_lamports: std::option::Option<std::string::String>,
|
||||
/// Whether exact simulation succeeded.
|
||||
pub(crate) simulation_success: bool,
|
||||
/// Runtime simulation error when present.
|
||||
pub(crate) simulation_error: std::option::Option<std::string::String>,
|
||||
/// Submitted transaction signature.
|
||||
pub(crate) transaction_signature: std::option::Option<std::string::String>,
|
||||
/// Terminal confirmation status.
|
||||
pub(crate) confirmation_status: std::option::Option<std::string::String>,
|
||||
/// Whether canonical hydration was validated.
|
||||
pub(crate) canonical_inserted: bool,
|
||||
/// Whether core extraction was validated.
|
||||
pub(crate) core_extracted: bool,
|
||||
/// Whether Memo decode replay was validated.
|
||||
pub(crate) decode_replayed: bool,
|
||||
/// Whether the committed transaction annotation was validated.
|
||||
pub(crate) materialized: bool,
|
||||
/// Number of exact annotation rows returned by the bounded query.
|
||||
pub(crate) annotation_count: u32,
|
||||
/// Whether the second replay produced no failures or new materialized output.
|
||||
pub(crate) idempotence_validated: bool,
|
||||
/// Pretty JSON representation of the exact execution plan.
|
||||
pub(crate) plan_json: std::string::String,
|
||||
/// Pretty JSON representation of the simulation result.
|
||||
pub(crate) simulation_json: std::string::String,
|
||||
/// Pretty JSON representation of annotations and post-validation diagnostics.
|
||||
pub(crate) diagnostics_json: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) struct DemoExecutionSolanaCoreObserver<'a> {
|
||||
pub(crate) app_handle: tauri::AppHandle,
|
||||
pub(crate) cancel_requested: &'a std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
impl crate::DemoExecutionSolanaCoreObserver<'_> {
|
||||
fn emit(
|
||||
&self,
|
||||
timestamp: std::string::String,
|
||||
level: &str,
|
||||
stage: &str,
|
||||
message: std::string::String,
|
||||
signature: std::option::Option<std::string::String>,
|
||||
) {
|
||||
let payload = crate::DemoExecutionSolanaCoreProgressPayload {
|
||||
timestamp,
|
||||
level: level.to_string(),
|
||||
stage: stage.to_string(),
|
||||
message,
|
||||
signature,
|
||||
};
|
||||
let emit_result = self.app_handle.emit_to(
|
||||
"demo_execution_solana_core",
|
||||
"demo-execution-solana-core-progress",
|
||||
payload,
|
||||
);
|
||||
if let std::result::Result::Err(error) = emit_result {
|
||||
tracing::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "emit_execution_progress",
|
||||
error = %error,
|
||||
"cannot emit Solana Core execution progress"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn cancelled(&self) -> bool {
|
||||
return self.cancel_requested.load(std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl kb_pipeline::BackfillObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
|
||||
fn on_progress(&self, event: &kb_pipeline::BackfillProgressEvent) {
|
||||
self.emit(
|
||||
event.timestamp.clone(),
|
||||
event.level.code(),
|
||||
"canonical_insert",
|
||||
event.message.clone(),
|
||||
std::option::Option::None,
|
||||
);
|
||||
}
|
||||
|
||||
fn is_cancelled(&self) -> bool {
|
||||
return self.cancelled();
|
||||
}
|
||||
}
|
||||
|
||||
impl kb_pipeline::CoreExtractionObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
|
||||
fn on_progress(&self, event: &kb_pipeline::CoreExtractionProgressEvent) {
|
||||
self.emit(
|
||||
event.timestamp.clone(),
|
||||
event.level.code(),
|
||||
"core_extraction",
|
||||
event.message.clone(),
|
||||
std::option::Option::None,
|
||||
);
|
||||
}
|
||||
|
||||
fn is_cancelled(&self) -> bool {
|
||||
return self.cancelled();
|
||||
}
|
||||
}
|
||||
|
||||
impl kb_pipeline::DecodeReplayObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
|
||||
fn on_progress(&self, event: &kb_pipeline::DecodeReplayProgressEvent) {
|
||||
self.emit(
|
||||
event.timestamp.clone(),
|
||||
event.level.code(),
|
||||
"decode_replay",
|
||||
event.message.clone(),
|
||||
std::option::Option::None,
|
||||
);
|
||||
}
|
||||
|
||||
fn is_cancelled(&self) -> bool {
|
||||
return self.cancelled();
|
||||
}
|
||||
}
|
||||
|
||||
impl kb_pipeline_demo_scenarios::SolanaExecutionObserver
|
||||
for crate::DemoExecutionSolanaCoreObserver<'_>
|
||||
{
|
||||
fn on_execution_progress(
|
||||
&self,
|
||||
event: &kb_pipeline_demo_scenarios::SolanaExecutionProgressEvent,
|
||||
) {
|
||||
self.emit(
|
||||
event.timestamp.clone(),
|
||||
event.level.code(),
|
||||
event.stage.as_str(),
|
||||
event.message.clone(),
|
||||
event.signature.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
fn is_execution_cancelled(&self) -> bool {
|
||||
return self.cancelled();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DemoExecutionSolanaCoreRunGuard<'a> {
|
||||
pub(crate) running: &'a std::sync::atomic::AtomicBool,
|
||||
}
|
||||
impl std::ops::Drop for crate::DemoExecutionSolanaCoreRunGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.running.store(false, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn devnet_profile_options(
|
||||
config: &kb_config::AppConfig,
|
||||
) -> std::vec::Vec<crate::DemoExecutionSolanaCoreProfileOption> {
|
||||
let mut output = std::vec::Vec::new();
|
||||
for profile in &config.profiles {
|
||||
if profile.wallet.cluster != "devnet"
|
||||
|| !profile.wallet.temporary_wallet_enabled
|
||||
|| !profile.wallet.temporary_wallet_persist
|
||||
{
|
||||
continue;
|
||||
}
|
||||
output.push(crate::DemoExecutionSolanaCoreProfileOption {
|
||||
name: profile.name.clone(),
|
||||
wallet_alias: profile.wallet.temporary_wallet_alias.clone(),
|
||||
max_spend_lamports: profile.execution.devnet_max_spend_lamports,
|
||||
max_airdrop_lamports: profile.execution.devnet_airdrop_max_lamports,
|
||||
send_enabled: profile.wallet.devnet_send_enabled,
|
||||
require_operator_confirmation: profile.execution.require_operator_confirmation,
|
||||
});
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
pub(crate) fn select_devnet_profile(
|
||||
config: &kb_config::AppConfig,
|
||||
profile_name: &str,
|
||||
) -> std::result::Result<kb_config::ProfileConfig, std::string::String> {
|
||||
for profile in &config.profiles {
|
||||
if profile.name == profile_name
|
||||
&& profile.wallet.cluster == "devnet"
|
||||
&& profile.wallet.temporary_wallet_enabled
|
||||
&& profile.wallet.temporary_wallet_persist
|
||||
{
|
||||
return std::result::Result::Ok(profile.clone());
|
||||
}
|
||||
}
|
||||
return std::result::Result::Err(format!(
|
||||
"Devnet execution profile '{profile_name}' is unavailable"
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) fn demo_execution_solana_core_summary_payload(
|
||||
summary: kb_pipeline_demo_scenarios::DevnetSystemTransferSummary,
|
||||
) -> crate::DemoExecutionSolanaCoreSummaryPayload {
|
||||
let plan_json = crate::pretty_json(&summary.plan);
|
||||
let simulation_json = crate::pretty_json(&summary.simulation);
|
||||
let transaction_signature =
|
||||
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
|
||||
let confirmation_status = summary.confirmation.as_ref().map(|value| {
|
||||
return crate::confirmation_status_code(value.status);
|
||||
});
|
||||
let canonical_inserted = summary.backfill.as_ref().map(|value| return value.canonical_inserted);
|
||||
let core_extracted = summary.core_extraction.as_ref().map(|value| return value.extracted);
|
||||
let decode_completed = summary.decode_replay.as_ref().map(|value| return value.completed);
|
||||
let decode_failed_inputs =
|
||||
summary.decode_replay.as_ref().map(|value| return value.failed_inputs);
|
||||
let diagnostics_value = serde_json::json!({
|
||||
"airdropConfirmation": summary.airdrop_confirmation.as_ref(),
|
||||
"confirmation": summary.confirmation.as_ref(),
|
||||
"postExecution": summary.post_execution.as_ref(),
|
||||
"backfill": summary.backfill.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"canonicalInserted": value.canonical_inserted,
|
||||
"canonicalSkipped": value.canonical_skipped,
|
||||
"existingSkipped": value.existing_skipped,
|
||||
"missing": value.missing,
|
||||
"failed": value.failed
|
||||
});
|
||||
}),
|
||||
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"selected": value.selected,
|
||||
"extracted": value.extracted,
|
||||
"skipped": value.skipped,
|
||||
"failed": value.failed
|
||||
});
|
||||
}),
|
||||
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"campaignId": value.campaign_id,
|
||||
"selected": value.selected,
|
||||
"completed": value.completed,
|
||||
"unmatched": value.unmatched,
|
||||
"failedInputs": value.failed_inputs,
|
||||
"cancelled": value.cancelled
|
||||
});
|
||||
})
|
||||
});
|
||||
let diagnostics_json = crate::pretty_json(&diagnostics_value);
|
||||
let cluster = crate::execution_cluster_code(summary.cluster);
|
||||
return crate::DemoExecutionSolanaCoreSummaryPayload {
|
||||
profile_name: summary.profile_name,
|
||||
cluster,
|
||||
genesis_hash: summary.genesis_hash,
|
||||
wallet_public_key: summary.wallet.public_key,
|
||||
recipient: summary.recipient.0,
|
||||
recipient_existed_before: summary.recipient_existed_before,
|
||||
recipient_balance_before_lamports: summary.recipient_balance_before_lamports,
|
||||
recipient_minimum_balance_lamports: summary.recipient_minimum_balance_lamports,
|
||||
balance_before_lamports: summary.balance_before_lamports,
|
||||
balance_after_funding_lamports: summary.balance_after_funding_lamports,
|
||||
fee_lamports: summary.fee.fee_lamports,
|
||||
simulation_success: summary.simulation.success,
|
||||
simulation_error: summary.simulation.error,
|
||||
simulation_logs: summary.simulation.logs,
|
||||
airdrop_signature: summary.airdrop_signature.map(|value| return value.0),
|
||||
transaction_signature,
|
||||
confirmation_status,
|
||||
canonical_inserted,
|
||||
core_extracted,
|
||||
decode_completed,
|
||||
decode_failed_inputs,
|
||||
plan_json,
|
||||
simulation_json,
|
||||
diagnostics_json,
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn demo_execution_solana_core_memo_summary_payload(
|
||||
summary: kb_pipeline_demo_scenarios::DevnetMemoExecutionSummary,
|
||||
) -> crate::DemoExecutionMemoSummaryPayload {
|
||||
let plan_json = crate::pretty_json(&summary.plan);
|
||||
let simulation_json = crate::pretty_json(&summary.simulation);
|
||||
let transaction_signature =
|
||||
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
|
||||
let confirmation_status = summary.confirmation.as_ref().map(|value| {
|
||||
return crate::confirmation_status_code(value.status);
|
||||
});
|
||||
let canonical_inserted = summary
|
||||
.post_execution
|
||||
.as_ref()
|
||||
.is_some_and(|value| return value.canonical_inserted);
|
||||
let core_extracted =
|
||||
summary.post_execution.as_ref().is_some_and(|value| return value.core_extracted);
|
||||
let decode_replayed = summary
|
||||
.post_execution
|
||||
.as_ref()
|
||||
.is_some_and(|value| return value.decode_replayed);
|
||||
let materialized =
|
||||
summary.post_execution.as_ref().is_some_and(|value| return value.materialized);
|
||||
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
|
||||
return replay.failed_inputs == 0
|
||||
&& !replay.cancelled
|
||||
&& replay.processors.iter().all(|processor| {
|
||||
return processor.failed == 0
|
||||
&& processor.materialized_outputs == 0
|
||||
&& processor.materialization_refused == 0;
|
||||
})
|
||||
&& replay.processors.iter().map(|processor| return processor.skipped).sum::<u64>()
|
||||
>= 1;
|
||||
});
|
||||
let annotation_count = match u32::try_from(summary.annotations.len()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => u32::MAX,
|
||||
};
|
||||
let diagnostics_value = serde_json::json!({
|
||||
"confirmation": summary.confirmation.as_ref(),
|
||||
"postExecution": summary.post_execution.as_ref(),
|
||||
"backfill": summary.backfill.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"canonicalInserted": value.canonical_inserted,
|
||||
"canonicalSkipped": value.canonical_skipped,
|
||||
"existingSkipped": value.existing_skipped,
|
||||
"missing": value.missing,
|
||||
"failed": value.failed
|
||||
});
|
||||
}),
|
||||
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"selected": value.selected,
|
||||
"extracted": value.extracted,
|
||||
"skipped": value.skipped,
|
||||
"failed": value.failed
|
||||
});
|
||||
}),
|
||||
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"campaignId": value.campaign_id,
|
||||
"selected": value.selected,
|
||||
"completed": value.completed,
|
||||
"failedInputs": value.failed_inputs,
|
||||
"processors": value.processors.iter().map(|processor| {
|
||||
return serde_json::json!({
|
||||
"name": processor.processor_name,
|
||||
"version": processor.processor_version,
|
||||
"decoded": processor.decoded,
|
||||
"skipped": processor.skipped,
|
||||
"failed": processor.failed,
|
||||
"materializedOutputs": processor.materialized_outputs,
|
||||
"materializationRefused": processor.materialization_refused
|
||||
});
|
||||
}).collect::<std::vec::Vec<_>>()
|
||||
});
|
||||
}),
|
||||
"idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"campaignId": value.campaign_id,
|
||||
"selected": value.selected,
|
||||
"completed": value.completed,
|
||||
"failedInputs": value.failed_inputs,
|
||||
"processors": value.processors.iter().map(|processor| {
|
||||
return serde_json::json!({
|
||||
"name": processor.processor_name,
|
||||
"version": processor.processor_version,
|
||||
"decoded": processor.decoded,
|
||||
"skipped": processor.skipped,
|
||||
"failed": processor.failed,
|
||||
"materializedOutputs": processor.materialized_outputs,
|
||||
"materializationRefused": processor.materialization_refused
|
||||
});
|
||||
}).collect::<std::vec::Vec<_>>()
|
||||
});
|
||||
}),
|
||||
"annotations": summary.annotations
|
||||
});
|
||||
let diagnostics_json = crate::pretty_json(&diagnostics_value);
|
||||
return crate::DemoExecutionMemoSummaryPayload {
|
||||
profile_name: summary.profile_name,
|
||||
cluster: crate::execution_cluster_code(summary.cluster),
|
||||
genesis_hash: summary.genesis_hash,
|
||||
wallet_public_key: summary.wallet.public_key,
|
||||
balance_lamports: summary.balance_lamports.to_string(),
|
||||
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
|
||||
simulation_success: summary.simulation.success,
|
||||
simulation_error: summary.simulation.error,
|
||||
transaction_signature,
|
||||
confirmation_status,
|
||||
canonical_inserted,
|
||||
core_extracted,
|
||||
decode_replayed,
|
||||
materialized,
|
||||
annotation_count,
|
||||
idempotence_validated,
|
||||
plan_json,
|
||||
simulation_json,
|
||||
diagnostics_json,
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn execution_cluster_code(
|
||||
cluster: kb_lib::ExApiExecutionCluster,
|
||||
) -> std::string::String {
|
||||
return match cluster {
|
||||
kb_lib::ExApiExecutionCluster::Localnet => "localnet".to_string(),
|
||||
kb_lib::ExApiExecutionCluster::Devnet => "devnet".to_string(),
|
||||
kb_lib::ExApiExecutionCluster::Testnet => "testnet".to_string(),
|
||||
kb_lib::ExApiExecutionCluster::Mainnet => "mainnet".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn confirmation_status_code(
|
||||
status: kb_lib::ExApiExecutionConfirmationStatus,
|
||||
) -> std::string::String {
|
||||
return match status {
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Processed => "processed".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Confirmed => "confirmed".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Finalized => "finalized".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Failed => "failed".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::Expired => "expired".to_string(),
|
||||
kb_lib::ExApiExecutionConfirmationStatus::TimedOut => "timed_out".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn pretty_json<T>(value: &T) -> std::string::String
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
return match serde_json::to_string_pretty(value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
serde_json::json!({"serializationError": error.to_string()}).to_string()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn example_config_exposes_one_devnet_execution_profile() {
|
||||
let config =
|
||||
match kb_config::parse_config_json(include_str!("../../config/example.config.json")) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
|
||||
};
|
||||
let profiles = crate::devnet_profile_options(&config);
|
||||
assert_eq!(profiles.len(), 1);
|
||||
assert_eq!(profiles[0].name, "local_devnet");
|
||||
assert!(profiles[0].send_enabled);
|
||||
assert!(profiles[0].max_spend_lamports >= 1_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_selection_rejects_mainnet_profiles() {
|
||||
let config =
|
||||
match kb_config::parse_config_json(include_str!("../../config/example.config.json")) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
|
||||
};
|
||||
assert!(crate::select_devnet_profile(&config, "local_devnet").is_ok());
|
||||
assert!(crate::select_devnet_profile(&config, "mainnet").is_err());
|
||||
}
|
||||
}
|
||||
71
kb-app-demo-desktop/src/demo_execution_spl.rs
Normal file
71
kb-app-demo-desktop/src/demo_execution_spl.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
// file: kb-app-demo-desktop/src/demo_execution_spl.rs
|
||||
// version: 3
|
||||
|
||||
//! Dedicated Tauri window for bounded SPL execution on Devnet.
|
||||
|
||||
/// Frontend payload for one independent Devnet SPL validation scenario.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_spl/DevnetSplValidationScenarioPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DevnetSplValidationScenarioPayload {
|
||||
/// Stable scenario identifier.
|
||||
pub id: std::string::String,
|
||||
/// Operator-visible label.
|
||||
pub label: std::string::String,
|
||||
/// Stable family code.
|
||||
pub family: std::string::String,
|
||||
/// Stable executor operation code.
|
||||
pub operation_code: std::string::String,
|
||||
/// Current implementation status.
|
||||
pub implementation_status: std::string::String,
|
||||
/// Whether the scenario requires proof material.
|
||||
pub proof_required: bool,
|
||||
/// Ordered required fixture variables.
|
||||
pub required_fixture_variables: std::vec::Vec<std::string::String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[test]
|
||||
fn memo_summary_keeps_lamports_as_json_strings() {
|
||||
let payload = crate::DemoExecutionMemoSummaryPayload {
|
||||
profile_name: "local_devnet".to_string(),
|
||||
cluster: "devnet".to_string(),
|
||||
genesis_hash: "EtWTRABZaYq6iMfeYKouRu166VU2xqa1".to_string(),
|
||||
wallet_public_key: kb_program_ids::SYSTEM_PROGRAM_ID.to_string(),
|
||||
balance_lamports: u64::MAX.to_string(),
|
||||
fee_lamports: std::option::Option::Some(u64::MAX.to_string()),
|
||||
simulation_success: true,
|
||||
simulation_error: std::option::Option::None,
|
||||
transaction_signature: std::option::Option::None,
|
||||
confirmation_status: std::option::Option::None,
|
||||
canonical_inserted: false,
|
||||
core_extracted: false,
|
||||
decode_replayed: false,
|
||||
materialized: false,
|
||||
annotation_count: 0,
|
||||
idempotence_validated: false,
|
||||
plan_json: "{}".to_string(),
|
||||
simulation_json: "{}".to_string(),
|
||||
diagnostics_json: "{}".to_string(),
|
||||
};
|
||||
let json = match serde_json::to_value(payload) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
assert_eq!(
|
||||
std::option::Option::Some(error.to_string()),
|
||||
std::option::Option::None,
|
||||
"Memo summary serialization failed"
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
assert!(json["balanceLamports"].is_string());
|
||||
assert!(json["feeLamports"].is_string());
|
||||
assert_eq!(json["balanceLamports"], u64::MAX.to_string());
|
||||
}
|
||||
}
|
||||
438
kb-app-demo-desktop/src/demo_spl_ata.rs
Normal file
438
kb-app-demo-desktop/src/demo_spl_ata.rs
Normal file
@@ -0,0 +1,438 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_ata.rs
|
||||
// version: 4
|
||||
|
||||
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// Request for one representative ATA creation on Devnet.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaRequest.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSplAtaRequest {
|
||||
/// Selected Devnet profile.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Wallet owner used by canonical derivation.
|
||||
pub(crate) wallet_owner: std::string::String,
|
||||
/// Mint account.
|
||||
pub(crate) mint: std::string::String,
|
||||
/// `classic` or `token2022`.
|
||||
pub(crate) token_program: std::string::String,
|
||||
/// `create` or `create_idempotent`.
|
||||
pub(crate) mode: std::string::String,
|
||||
/// Whether to sign and submit after simulation.
|
||||
pub(crate) submit: bool,
|
||||
/// Explicit operator confirmation.
|
||||
pub(crate) operator_confirmed: bool,
|
||||
/// Whether post-execution outputs should be force-replayed.
|
||||
pub(crate) force_post_validation_replay: bool,
|
||||
}
|
||||
|
||||
/// Request used to derive the representative wallet ATA before execution.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationRequest.ts"
|
||||
)]
|
||||
pub(crate) struct DemoSplAtaDerivationRequest {
|
||||
/// Selected Devnet profile.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Mint account.
|
||||
pub(crate) mint: std::string::String,
|
||||
/// `classic` or `token2022`.
|
||||
pub(crate) token_program: std::string::String,
|
||||
}
|
||||
|
||||
/// Readonly payer, wallet, Token Program and derived ATA values.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoSplAtaDerivationPayload {
|
||||
/// Profile wallet used as payer.
|
||||
pub(crate) payer: std::string::String,
|
||||
/// Wallet owner selected by the representative panel.
|
||||
pub(crate) wallet_owner: std::string::String,
|
||||
/// Exact Token Program ID.
|
||||
pub(crate) token_program_id: std::string::String,
|
||||
/// Canonically derived ATA.
|
||||
pub(crate) associated_token_account: std::string::String,
|
||||
}
|
||||
|
||||
/// UI-safe ATA execution result.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaSummaryPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSplAtaSummaryPayload {
|
||||
/// Stable operation code.
|
||||
pub(crate) operation: std::string::String,
|
||||
/// Profile name.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Profile wallet public key.
|
||||
pub(crate) wallet_public_key: std::string::String,
|
||||
/// Target Token Program ID.
|
||||
pub(crate) token_program_id: std::string::String,
|
||||
/// Derived ATA from the exact plan.
|
||||
pub(crate) associated_token_account: std::string::String,
|
||||
/// Wallet balance as lossless JSON text.
|
||||
pub(crate) balance_lamports: std::string::String,
|
||||
/// Fee estimate as lossless JSON text.
|
||||
pub(crate) fee_lamports: std::option::Option<std::string::String>,
|
||||
/// Aggregate stateful readiness.
|
||||
pub(crate) readiness_status: std::string::String,
|
||||
/// Exact simulation outcome.
|
||||
pub(crate) simulation_success: bool,
|
||||
/// Runtime simulation error.
|
||||
pub(crate) simulation_error: std::option::Option<std::string::String>,
|
||||
/// Submitted signature.
|
||||
pub(crate) transaction_signature: std::option::Option<std::string::String>,
|
||||
/// Terminal confirmation status.
|
||||
pub(crate) confirmation_status: std::option::Option<std::string::String>,
|
||||
/// Number of ATA-owned materialized facts.
|
||||
pub(crate) materialization_count: u32,
|
||||
/// Whether the second replay created no output.
|
||||
pub(crate) idempotence_validated: bool,
|
||||
/// Exact plan JSON.
|
||||
pub(crate) plan_json: std::string::String,
|
||||
/// Stateful readiness JSON.
|
||||
pub(crate) readiness_json: std::string::String,
|
||||
/// Simulation JSON.
|
||||
pub(crate) simulation_json: std::string::String,
|
||||
/// Confirmation and replay diagnostics JSON.
|
||||
pub(crate) diagnostics_json: std::string::String,
|
||||
}
|
||||
|
||||
/// Bounded ATA lifecycle journal request.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRequest.ts"
|
||||
)]
|
||||
pub(crate) struct DemoSplAtaJournalRequest {
|
||||
/// Selected Devnet profile.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Optional partial signature.
|
||||
pub(crate) signature_contains: std::option::Option<std::string::String>,
|
||||
/// Optional exact mint.
|
||||
pub(crate) mint: std::option::Option<std::string::String>,
|
||||
/// Optional exact ATA.
|
||||
pub(crate) associated_token_account: std::option::Option<std::string::String>,
|
||||
/// Optional exact materialized operation.
|
||||
pub(crate) operation: std::option::Option<std::string::String>,
|
||||
/// Maximum returned rows.
|
||||
pub(crate) limit: u32,
|
||||
}
|
||||
|
||||
/// One UI-safe ATA lifecycle journal row.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRow.ts"
|
||||
)]
|
||||
pub(crate) struct DemoSplAtaJournalRow {
|
||||
/// Transaction signature.
|
||||
pub(crate) signature: std::string::String,
|
||||
/// Slot as lossless JSON text.
|
||||
pub(crate) slot: std::string::String,
|
||||
/// Materialized family.
|
||||
pub(crate) family: std::string::String,
|
||||
/// Lifecycle operation.
|
||||
pub(crate) operation: std::option::Option<std::string::String>,
|
||||
/// Exact mint when present.
|
||||
pub(crate) mint: std::option::Option<std::string::String>,
|
||||
/// Exact ATA when present.
|
||||
pub(crate) associated_token_account: std::option::Option<std::string::String>,
|
||||
/// Full bounded payload JSON.
|
||||
pub(crate) payload_json: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) async fn load_profile_wallet(
|
||||
profile: &kb_config::ProfileConfig,
|
||||
) -> std::result::Result<kb_wallet::TemporaryWallet, std::string::String> {
|
||||
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
||||
let directory = if configured.is_absolute() {
|
||||
configured
|
||||
} else {
|
||||
crate::workspace_root_dir().join(configured)
|
||||
};
|
||||
let store = match kb_wallet::TemporaryWalletStore::new(directory) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let alias = match kb_wallet::WalletAlias::parse(profile.wallet.temporary_wallet_alias.clone()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return match store.load_or_create(alias).await {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn parse_token_program(
|
||||
value: &str,
|
||||
) -> std::result::Result<kb_lib::ExSplAssociatedTokenProgram, std::string::String> {
|
||||
return match value.trim() {
|
||||
"classic" => std::result::Result::Ok(kb_lib::ExSplAssociatedTokenProgram::Classic),
|
||||
"token2022" => std::result::Result::Ok(kb_lib::ExSplAssociatedTokenProgram::Token2022),
|
||||
_ => std::result::Result::Err("Token Program must be classic or token2022".to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn derive_ata(
|
||||
wallet: &str,
|
||||
mint: &str,
|
||||
token_program: &str,
|
||||
) -> std::result::Result<std::string::String, std::string::String> {
|
||||
let wallet: solana_pubkey::Pubkey = match wallet.parse() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!("invalid wallet: {error}"));
|
||||
},
|
||||
};
|
||||
let mint: solana_pubkey::Pubkey = match mint.parse() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!("invalid mint: {error}"));
|
||||
},
|
||||
};
|
||||
let token_program: solana_pubkey::Pubkey = match token_program.parse() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!("invalid Token Program: {error}"));
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(
|
||||
spl_associated_token_account_interface::address::get_associated_token_address_with_program_id(
|
||||
&wallet,
|
||||
&mint,
|
||||
&token_program,
|
||||
)
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn demo_spl_ata_summary_payload(
|
||||
summary: kb_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionSummary,
|
||||
) -> crate::DemoExecutionSplAtaSummaryPayload {
|
||||
let (associated_token_account, token_program_id) = match summary.plan.instructions.first() {
|
||||
std::option::Option::Some(instruction) => {
|
||||
let ata = match instruction.accounts.get(1) {
|
||||
std::option::Option::Some(account) => account.pubkey.0.clone(),
|
||||
std::option::Option::None => std::string::String::new(),
|
||||
};
|
||||
let token_program = match instruction.accounts.last() {
|
||||
std::option::Option::Some(account) => account.pubkey.0.clone(),
|
||||
std::option::Option::None => std::string::String::new(),
|
||||
};
|
||||
(ata, token_program)
|
||||
},
|
||||
std::option::Option::None => (std::string::String::new(), std::string::String::new()),
|
||||
};
|
||||
let transaction_signature =
|
||||
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
|
||||
let confirmation_status = summary.confirmation.as_ref().map(|value| {
|
||||
return crate::confirmation_status_code(value.status);
|
||||
});
|
||||
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
|
||||
return replay.failed_inputs == 0
|
||||
&& replay.processors.iter().all(|processor| {
|
||||
return processor.failed == 0
|
||||
&& processor.materialized_outputs == 0
|
||||
&& processor.materialization_refused == 0;
|
||||
});
|
||||
});
|
||||
let materialization_count = match u32::try_from(summary.materializations.len()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => u32::MAX,
|
||||
};
|
||||
let diagnostics = serde_json::json!({
|
||||
"confirmation": summary.confirmation.as_ref(),
|
||||
"postStateValidation": summary.post_state_validation.as_ref(),
|
||||
"postExecution": summary.post_execution.as_ref(),
|
||||
"backfill": summary.backfill.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"candidatesCompleted": value.candidates_completed,
|
||||
"candidatesCancelled": value.candidates_cancelled,
|
||||
"candidatesNotStarted": value.candidates_not_started,
|
||||
"canonicalInserted": value.canonical_inserted,
|
||||
"canonicalSkipped": value.canonical_skipped,
|
||||
"existingSkipped": value.existing_skipped,
|
||||
"missing": value.missing,
|
||||
"failed": value.failed
|
||||
});
|
||||
}),
|
||||
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"selected": value.selected,
|
||||
"extracted": value.extracted,
|
||||
"skipped": value.skipped,
|
||||
"failed": value.failed,
|
||||
"cancelled": value.cancelled
|
||||
});
|
||||
}),
|
||||
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"campaignId": value.campaign_id,
|
||||
"selected": value.selected,
|
||||
"completed": value.completed,
|
||||
"unmatched": value.unmatched,
|
||||
"failedInputs": value.failed_inputs,
|
||||
"cancelled": value.cancelled
|
||||
});
|
||||
}),
|
||||
"idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"campaignId": value.campaign_id,
|
||||
"selected": value.selected,
|
||||
"completed": value.completed,
|
||||
"unmatched": value.unmatched,
|
||||
"failedInputs": value.failed_inputs,
|
||||
"cancelled": value.cancelled,
|
||||
"materializedOutputs": value.processors.iter().map(|processor| {
|
||||
return processor.materialized_outputs;
|
||||
}).sum::<u64>(),
|
||||
"materializationRefused": value.processors.iter().map(|processor| {
|
||||
return processor.materialization_refused;
|
||||
}).sum::<u64>()
|
||||
});
|
||||
}),
|
||||
"materializations": summary.materializations.iter().map(|value| {
|
||||
return serde_json::json!({
|
||||
"processorName": value.processor_name,
|
||||
"outputKey": value.output_key,
|
||||
"family": value.materialized_family,
|
||||
"signature": value.signature,
|
||||
"slot": value.slot.to_string(),
|
||||
"payload": value.payload_json
|
||||
});
|
||||
}).collect::<std::vec::Vec<_>>()
|
||||
});
|
||||
let plan_json = crate::pretty_json(&summary.plan);
|
||||
let readiness_json = crate::pretty_json(&summary.stateful_readiness);
|
||||
let simulation_json = crate::pretty_json(&summary.simulation);
|
||||
let diagnostics_json = crate::pretty_json(&diagnostics);
|
||||
return crate::DemoExecutionSplAtaSummaryPayload {
|
||||
operation: summary.stateful_readiness.operation_code,
|
||||
profile_name: summary.profile_name,
|
||||
wallet_public_key: summary.wallet.public_key,
|
||||
token_program_id,
|
||||
associated_token_account,
|
||||
balance_lamports: summary.balance_lamports.to_string(),
|
||||
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
|
||||
readiness_status: match summary.stateful_readiness.status {
|
||||
kb_pipeline::SplAssociatedTokenAccountStatefulReadinessStatus::Ready => {
|
||||
"ready".to_string()
|
||||
},
|
||||
kb_pipeline::SplAssociatedTokenAccountStatefulReadinessStatus::Blocked => {
|
||||
"blocked".to_string()
|
||||
},
|
||||
},
|
||||
simulation_success: summary.simulation.success,
|
||||
simulation_error: summary.simulation.error,
|
||||
transaction_signature,
|
||||
confirmation_status,
|
||||
materialization_count,
|
||||
idempotence_validated,
|
||||
plan_json,
|
||||
readiness_json,
|
||||
simulation_json,
|
||||
diagnostics_json,
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn journal_matches(
|
||||
payload: &serde_json::Value,
|
||||
request: &crate::DemoSplAtaJournalRequest,
|
||||
) -> bool {
|
||||
for (expected, key) in [
|
||||
(&request.operation, "operation"),
|
||||
(&request.mint, "mint"),
|
||||
(&request.associated_token_account, "associatedTokenAccount"),
|
||||
] {
|
||||
if expected.as_ref().is_some_and(|value| {
|
||||
return payload.get(key).and_then(serde_json::Value::as_str)
|
||||
!= std::option::Option::Some(value.as_str());
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub(crate) fn journal_row(row: kb_store::MaterializedEventQueryRow) -> crate::DemoSplAtaJournalRow {
|
||||
let text = |key: &str| {
|
||||
return row
|
||||
.payload_json
|
||||
.get(key)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(std::string::ToString::to_string);
|
||||
};
|
||||
let operation = text("operation");
|
||||
let mint = text("mint");
|
||||
let associated_token_account = text("associatedTokenAccount");
|
||||
let payload_json = crate::pretty_json(&row.payload_json);
|
||||
return crate::DemoSplAtaJournalRow {
|
||||
signature: row.signature,
|
||||
slot: row.slot.to_string(),
|
||||
family: row.materialized_family,
|
||||
operation,
|
||||
mint,
|
||||
associated_token_account,
|
||||
payload_json,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn classic_and_token2022_derivations_are_distinct_and_canonical() {
|
||||
let wallet = kb_program_ids::SYSTEM_PROGRAM_ID;
|
||||
let mint = "So11111111111111111111111111111111111111112";
|
||||
let classic = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_PROGRAM_ID)
|
||||
.unwrap_or_else(|error| panic!("classic derivation failed: {error}"));
|
||||
let token2022 = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN2022_PROGRAM_ID)
|
||||
.unwrap_or_else(|error| panic!("Token-2022 derivation failed: {error}"));
|
||||
assert_eq!(classic, "aqxoAhCwpy3oB1BpNw9hL1HdLYLgPpbPjzxDrrQj3Fs");
|
||||
assert_eq!(token2022, "2sZUUBGq1i6aE47ZoxCaCW89jmYm2EXLPPmNMgMDXHMS");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lifecycle_journal_filters_are_exact() {
|
||||
let request = crate::DemoSplAtaJournalRequest {
|
||||
profile_name: "local_devnet".to_string(),
|
||||
signature_contains: std::option::Option::None,
|
||||
mint: std::option::Option::Some("mint111".to_string()),
|
||||
associated_token_account: std::option::Option::Some("ata111".to_string()),
|
||||
operation: std::option::Option::Some("create_idempotent".to_string()),
|
||||
limit: 100,
|
||||
};
|
||||
assert!(crate::journal_matches(
|
||||
&serde_json::json!({
|
||||
"operation": "create_idempotent",
|
||||
"mint": "mint111",
|
||||
"associatedTokenAccount": "ata111"
|
||||
}),
|
||||
&request,
|
||||
));
|
||||
assert!(!crate::journal_matches(
|
||||
&serde_json::json!({
|
||||
"operation": "create",
|
||||
"mint": "mint111",
|
||||
"associatedTokenAccount": "ata111"
|
||||
}),
|
||||
&request,
|
||||
));
|
||||
}
|
||||
}
|
||||
493
kb-app-demo-desktop/src/demo_spl_token.rs
Normal file
493
kb-app-demo-desktop/src/demo_spl_token.rs
Normal file
@@ -0,0 +1,493 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_token.rs
|
||||
// version: 2
|
||||
|
||||
//! Thin Tauri adapter for classic SPL Token execution and materialized journals.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// Request for one checked classic SPL Token transfer on Devnet.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenRequest.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSplTokenRequest {
|
||||
/// Selected Devnet profile.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Source token account.
|
||||
pub(crate) source: std::string::String,
|
||||
/// Exact mint account carried by `TransferChecked`.
|
||||
pub(crate) mint: std::string::String,
|
||||
/// Destination token account.
|
||||
pub(crate) destination: std::string::String,
|
||||
/// Simple authority resolved by the selected profile wallet.
|
||||
pub(crate) authority: std::string::String,
|
||||
/// Exact raw amount represented as a decimal string.
|
||||
pub(crate) amount_raw: std::string::String,
|
||||
/// Expected mint decimals carried by the wire.
|
||||
pub(crate) decimals: u8,
|
||||
/// Whether the transaction should be signed and submitted after simulation.
|
||||
pub(crate) submit: bool,
|
||||
/// Explicit operator confirmation for signed submission.
|
||||
pub(crate) operator_confirmed: bool,
|
||||
/// Whether post-execution core and decode outputs should be force-replayed.
|
||||
pub(crate) force_post_validation_replay: bool,
|
||||
}
|
||||
|
||||
/// UI-safe result of one checked SPL Token transfer orchestration.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenSummaryPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSplTokenSummaryPayload {
|
||||
/// Stable operation code.
|
||||
pub(crate) operation: std::string::String,
|
||||
/// Profile used by the orchestration.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Classified cluster code.
|
||||
pub(crate) cluster: std::string::String,
|
||||
/// Genesis hash returned by the endpoint.
|
||||
pub(crate) genesis_hash: std::string::String,
|
||||
/// Source wallet public key.
|
||||
pub(crate) wallet_public_key: std::string::String,
|
||||
/// Exact wallet balance rendered without JSON integer precision loss.
|
||||
pub(crate) balance_lamports: std::string::String,
|
||||
/// Estimated fee rendered without JSON integer precision loss.
|
||||
pub(crate) fee_lamports: std::option::Option<std::string::String>,
|
||||
/// Exact raw token amount.
|
||||
pub(crate) amount_raw: std::string::String,
|
||||
/// Expected mint decimals.
|
||||
pub(crate) decimals: u8,
|
||||
/// Aggregate stateful preflight status.
|
||||
pub(crate) readiness_status: std::string::String,
|
||||
/// Whether exact simulation succeeded.
|
||||
pub(crate) simulation_success: bool,
|
||||
/// Runtime simulation error when present.
|
||||
pub(crate) simulation_error: std::option::Option<std::string::String>,
|
||||
/// Submitted transaction signature.
|
||||
pub(crate) transaction_signature: std::option::Option<std::string::String>,
|
||||
/// Terminal confirmation status.
|
||||
pub(crate) confirmation_status: std::option::Option<std::string::String>,
|
||||
/// Whether canonical hydration was validated.
|
||||
pub(crate) canonical_inserted: bool,
|
||||
/// Whether core extraction was validated.
|
||||
pub(crate) core_extracted: bool,
|
||||
/// Whether SPL Token decode replay was validated.
|
||||
pub(crate) decode_replayed: bool,
|
||||
/// Number of materialized rows for the submitted instruction.
|
||||
pub(crate) materialization_count: u32,
|
||||
/// Whether the second replay produced no failure or new output.
|
||||
pub(crate) idempotence_validated: bool,
|
||||
/// Pretty JSON representation of the exact execution plan.
|
||||
pub(crate) plan_json: std::string::String,
|
||||
/// Pretty JSON representation of stateful readiness.
|
||||
pub(crate) readiness_json: std::string::String,
|
||||
/// Pretty JSON representation of the exact simulation result.
|
||||
pub(crate) simulation_json: std::string::String,
|
||||
/// Pretty JSON representation of confirmation and replay diagnostics.
|
||||
pub(crate) diagnostics_json: std::string::String,
|
||||
}
|
||||
|
||||
/// Bounded exact filters for the classic SPL Token materialized journal.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRequest.ts"
|
||||
)]
|
||||
pub(crate) struct DemoSplTokenJournalRequest {
|
||||
/// Devnet profile whose PostgreSQL store is queried.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Optional partial signature handled by the bounded store query.
|
||||
pub(crate) signature_contains: std::option::Option<std::string::String>,
|
||||
/// Optional exact mint account.
|
||||
pub(crate) mint: std::option::Option<std::string::String>,
|
||||
/// Optional exact account occurring in the ordered account list.
|
||||
pub(crate) account: std::option::Option<std::string::String>,
|
||||
/// Optional exact materialized family.
|
||||
pub(crate) family: std::option::Option<std::string::String>,
|
||||
/// Optional exact operation code without the `spl_token.` prefix.
|
||||
pub(crate) operation: std::option::Option<std::string::String>,
|
||||
/// Maximum number of rows returned after typed filtering.
|
||||
pub(crate) limit: u32,
|
||||
}
|
||||
|
||||
/// UI-safe materialized classic SPL Token journal row.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRow.ts"
|
||||
)]
|
||||
pub(crate) struct DemoSplTokenJournalRow {
|
||||
/// Materializer processor name.
|
||||
pub(crate) processor_name: std::string::String,
|
||||
/// Materializer processor version.
|
||||
pub(crate) processor_version: std::string::String,
|
||||
/// Stable processor-owned output key.
|
||||
pub(crate) output_key: std::string::String,
|
||||
/// Transaction signature.
|
||||
pub(crate) signature: std::string::String,
|
||||
/// Decimal slot rendered as text to preserve JSON precision.
|
||||
pub(crate) slot: std::string::String,
|
||||
/// Materialized family code.
|
||||
pub(crate) family: std::string::String,
|
||||
/// Exact operation when present.
|
||||
pub(crate) operation: std::option::Option<std::string::String>,
|
||||
/// Exact mint account when explicitly available.
|
||||
pub(crate) mint: std::option::Option<std::string::String>,
|
||||
/// Exact raw amount when carried by the materialized fact.
|
||||
pub(crate) amount_raw: std::option::Option<std::string::String>,
|
||||
/// Stable outer or inner instruction path when present.
|
||||
pub(crate) instruction_path: std::option::Option<std::string::String>,
|
||||
/// Ordered account keys preserved by the decoder.
|
||||
pub(crate) account_keys: std::vec::Vec<std::string::String>,
|
||||
/// Complete bounded typed payload rendered as JSON text.
|
||||
pub(crate) payload_json: std::string::String,
|
||||
/// Database creation timestamp.
|
||||
pub(crate) created_at: std::string::String,
|
||||
/// Database replacement timestamp.
|
||||
pub(crate) updated_at: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) fn demo_spl_token_summary_payload(
|
||||
summary: kb_pipeline_demo_scenarios::DevnetSplTokenExecutionSummary,
|
||||
amount_raw: std::string::String,
|
||||
decimals: u8,
|
||||
) -> crate::DemoExecutionSplTokenSummaryPayload {
|
||||
let plan_json = crate::pretty_json(&summary.plan);
|
||||
let readiness_json = crate::pretty_json(&summary.stateful_readiness);
|
||||
let simulation_json = crate::pretty_json(&summary.simulation);
|
||||
let transaction_signature =
|
||||
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
|
||||
let confirmation_status = summary.confirmation.as_ref().map(|value| {
|
||||
return crate::confirmation_status_code(value.status);
|
||||
});
|
||||
let post_execution = summary.post_execution.as_ref();
|
||||
let canonical_inserted = post_execution.is_some_and(|value| return value.canonical_inserted);
|
||||
let core_extracted = post_execution.is_some_and(|value| return value.core_extracted);
|
||||
let decode_replayed = post_execution.is_some_and(|value| return value.decode_replayed);
|
||||
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
|
||||
return replay.failed_inputs == 0
|
||||
&& !replay.cancelled
|
||||
&& replay.processors.iter().all(|processor| {
|
||||
return processor.failed == 0
|
||||
&& processor.materialized_outputs == 0
|
||||
&& processor.materialization_refused == 0;
|
||||
});
|
||||
});
|
||||
let materialization_count = match u32::try_from(summary.materializations.len()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => u32::MAX,
|
||||
};
|
||||
let diagnostics = serde_json::json!({
|
||||
"confirmation": summary.confirmation,
|
||||
"postExecution": summary.post_execution,
|
||||
"backfill": summary.backfill.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"canonicalInserted": value.canonical_inserted,
|
||||
"canonicalSkipped": value.canonical_skipped,
|
||||
"existingSkipped": value.existing_skipped,
|
||||
"missing": value.missing,
|
||||
"failed": value.failed
|
||||
});
|
||||
}),
|
||||
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
|
||||
return serde_json::json!({
|
||||
"selected": value.selected,
|
||||
"extracted": value.extracted,
|
||||
"skipped": value.skipped,
|
||||
"failed": value.failed,
|
||||
"cancelled": value.cancelled
|
||||
});
|
||||
}),
|
||||
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
|
||||
return replay_diagnostics(value);
|
||||
}),
|
||||
"idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| {
|
||||
return replay_diagnostics(value);
|
||||
}),
|
||||
"materializations": summary.materializations
|
||||
});
|
||||
return crate::DemoExecutionSplTokenSummaryPayload {
|
||||
operation: summary.stateful_readiness.operation_code,
|
||||
profile_name: summary.profile_name,
|
||||
cluster: crate::execution_cluster_code(summary.cluster),
|
||||
genesis_hash: summary.genesis_hash,
|
||||
wallet_public_key: summary.wallet.public_key,
|
||||
balance_lamports: summary.balance_lamports.to_string(),
|
||||
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
|
||||
amount_raw,
|
||||
decimals,
|
||||
readiness_status: match summary.stateful_readiness.status {
|
||||
kb_pipeline::SplTokenStatefulReadinessStatus::Ready => "ready".to_string(),
|
||||
kb_pipeline::SplTokenStatefulReadinessStatus::Blocked => "blocked".to_string(),
|
||||
},
|
||||
simulation_success: summary.simulation.success,
|
||||
simulation_error: summary.simulation.error,
|
||||
transaction_signature,
|
||||
confirmation_status,
|
||||
canonical_inserted,
|
||||
core_extracted,
|
||||
decode_replayed,
|
||||
materialization_count,
|
||||
idempotence_validated,
|
||||
plan_json,
|
||||
readiness_json,
|
||||
simulation_json,
|
||||
diagnostics_json: crate::pretty_json(&diagnostics),
|
||||
};
|
||||
}
|
||||
|
||||
fn replay_diagnostics(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json::Value {
|
||||
return serde_json::json!({
|
||||
"campaignId": summary.campaign_id,
|
||||
"selected": summary.selected,
|
||||
"completed": summary.completed,
|
||||
"failedInputs": summary.failed_inputs,
|
||||
"cancelled": summary.cancelled,
|
||||
"processors": summary.processors.iter().map(|processor| {
|
||||
return serde_json::json!({
|
||||
"name": processor.processor_name,
|
||||
"version": processor.processor_version,
|
||||
"decoded": processor.decoded,
|
||||
"skipped": processor.skipped,
|
||||
"failed": processor.failed,
|
||||
"materializedOutputs": processor.materialized_outputs,
|
||||
"materializationRefused": processor.materialization_refused
|
||||
});
|
||||
}).collect::<std::vec::Vec<_>>()
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn validate_journal_request(
|
||||
request: crate::DemoSplTokenJournalRequest,
|
||||
) -> std::result::Result<crate::DemoSplTokenJournalRequest, std::string::String> {
|
||||
if request.limit == 0 || request.limit > kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS {
|
||||
return std::result::Result::Err(format!(
|
||||
"journal limit must be between 1 and {}",
|
||||
kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS
|
||||
));
|
||||
}
|
||||
if request.profile_name.trim().is_empty() {
|
||||
return std::result::Result::Err("journal profile name must not be empty".to_string());
|
||||
}
|
||||
let signature_contains =
|
||||
match bounded_optional(request.signature_contains, "signature filter", 128) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mint = match bounded_optional(request.mint, "mint filter", 64) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let account = match bounded_optional(request.account, "account filter", 64) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let operation = match bounded_optional(request.operation, "operation filter", 64) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let family = match bounded_optional(request.family, "family filter", 32) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if family.as_ref().is_some_and(|value| {
|
||||
return !matches!(value.as_str(), "token_account" | "admin" | "risk");
|
||||
}) {
|
||||
return std::result::Result::Err(
|
||||
"journal family must be token_account, admin or risk".to_string(),
|
||||
);
|
||||
}
|
||||
return std::result::Result::Ok(crate::DemoSplTokenJournalRequest {
|
||||
profile_name: request.profile_name.trim().to_string(),
|
||||
signature_contains,
|
||||
mint,
|
||||
account,
|
||||
family,
|
||||
operation,
|
||||
limit: request.limit,
|
||||
});
|
||||
}
|
||||
|
||||
fn bounded_optional(
|
||||
value: std::option::Option<std::string::String>,
|
||||
label: &str,
|
||||
maximum_length: usize,
|
||||
) -> std::result::Result<std::option::Option<std::string::String>, std::string::String> {
|
||||
let trimmed = value.map(|text| return text.trim().to_string());
|
||||
let trimmed = trimmed.filter(|text| return !text.is_empty());
|
||||
if trimmed.as_ref().is_some_and(|text| return text.len() > maximum_length) {
|
||||
return std::result::Result::Err(format!("{label} must not exceed {maximum_length} bytes"));
|
||||
}
|
||||
return std::result::Result::Ok(trimmed);
|
||||
}
|
||||
|
||||
pub(crate) fn payload_matches(
|
||||
payload: &serde_json::Value,
|
||||
request: &crate::DemoSplTokenJournalRequest,
|
||||
) -> bool {
|
||||
if request.operation.as_ref().is_some_and(|expected| {
|
||||
return payload.get("operation").and_then(serde_json::Value::as_str)
|
||||
!= std::option::Option::Some(expected.as_str());
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
if request.mint.as_ref().is_some_and(|expected| {
|
||||
return payload_mint(payload).as_deref() != std::option::Option::Some(expected.as_str());
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
if request.account.as_ref().is_some_and(|expected| {
|
||||
return !payload_account_keys(payload).iter().any(|value| return value == expected);
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub(crate) fn demo_spl_token_journal_row(
|
||||
row: kb_store::MaterializedEventQueryRow,
|
||||
) -> DemoSplTokenJournalRow {
|
||||
let operation = row
|
||||
.payload_json
|
||||
.get("operation")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(std::string::ToString::to_string);
|
||||
let amount_raw = row
|
||||
.payload_json
|
||||
.get("amountRaw")
|
||||
.or_else(|| {
|
||||
return row
|
||||
.payload_json
|
||||
.get("parameters")
|
||||
.and_then(|value| return value.get("amountRaw"));
|
||||
})
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(std::string::ToString::to_string);
|
||||
let instruction_path = row
|
||||
.payload_json
|
||||
.get("instructionPath")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(std::string::ToString::to_string);
|
||||
let payload_json = crate::pretty_json(&row.payload_json);
|
||||
return DemoSplTokenJournalRow {
|
||||
processor_name: row.processor_name,
|
||||
processor_version: row.processor_version,
|
||||
output_key: row.output_key,
|
||||
signature: row.signature,
|
||||
slot: row.slot.to_string(),
|
||||
family: row.materialized_family,
|
||||
operation,
|
||||
mint: payload_mint(&row.payload_json),
|
||||
amount_raw,
|
||||
instruction_path,
|
||||
account_keys: payload_account_keys(&row.payload_json),
|
||||
payload_json,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
fn payload_mint(payload: &serde_json::Value) -> std::option::Option<std::string::String> {
|
||||
let direct = payload.get("mint").and_then(serde_json::Value::as_str);
|
||||
if let std::option::Option::Some(value) = direct {
|
||||
return std::option::Option::Some(value.to_string());
|
||||
}
|
||||
return payload.get("accounts").and_then(serde_json::Value::as_array).and_then(|rows| {
|
||||
return rows.iter().find_map(|row| {
|
||||
if row.get("role").and_then(serde_json::Value::as_str)
|
||||
!= std::option::Option::Some("mint")
|
||||
{
|
||||
return std::option::Option::None;
|
||||
}
|
||||
return row
|
||||
.get("accountKey")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(std::string::ToString::to_string);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn payload_account_keys(payload: &serde_json::Value) -> std::vec::Vec<std::string::String> {
|
||||
let values = payload.get("accounts").and_then(serde_json::Value::as_array).map(|rows| {
|
||||
return rows
|
||||
.iter()
|
||||
.filter_map(|row| {
|
||||
return row
|
||||
.get("accountKey")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(std::string::ToString::to_string);
|
||||
})
|
||||
.collect();
|
||||
});
|
||||
return match values {
|
||||
std::option::Option::Some(values) => values,
|
||||
std::option::Option::None => std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn journal_filters_are_exact_bounded_and_preserve_raw_amounts() {
|
||||
let request = crate::DemoSplTokenJournalRequest {
|
||||
profile_name: "local_devnet".to_string(),
|
||||
signature_contains: std::option::Option::None,
|
||||
mint: std::option::Option::Some("mint111".to_string()),
|
||||
account: std::option::Option::Some("source111".to_string()),
|
||||
family: std::option::Option::Some("token_account".to_string()),
|
||||
operation: std::option::Option::Some("transfer_checked".to_string()),
|
||||
limit: 25,
|
||||
};
|
||||
let payload = serde_json::json!({
|
||||
"operation": "transfer_checked",
|
||||
"mint": "mint111",
|
||||
"amountRaw": "18446744073709551615",
|
||||
"accounts": [
|
||||
{"role": "source", "accountKey": "source111"},
|
||||
{"role": "mint", "accountKey": "mint111"}
|
||||
]
|
||||
});
|
||||
assert!(crate::payload_matches(&payload, &request));
|
||||
assert_eq!(
|
||||
payload.get("amountRaw").and_then(serde_json::Value::as_str),
|
||||
std::option::Option::Some("18446744073709551615")
|
||||
);
|
||||
let row = crate::demo_spl_token_journal_row(kb_store::MaterializedEventQueryRow {
|
||||
processor_name: "spl_token_accounts".to_string(),
|
||||
processor_version: "0.4.4".to_string(),
|
||||
input_key: "input".to_string(),
|
||||
output_key: "output".to_string(),
|
||||
source_event_key: "event".to_string(),
|
||||
source_decoder_name: "spl_token".to_string(),
|
||||
source_decoder_version: "0.4.4".to_string(),
|
||||
signature: "signature".to_string(),
|
||||
slot: u64::MAX,
|
||||
materialized_family: "token_account".to_string(),
|
||||
payload_json: payload,
|
||||
created_at: "created".to_string(),
|
||||
updated_at: "updated".to_string(),
|
||||
});
|
||||
assert_eq!(row.slot, u64::MAX.to_string());
|
||||
assert_eq!(row.amount_raw, std::option::Option::Some(u64::MAX.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn journal_rejects_unbounded_or_unknown_family_requests() {
|
||||
let request = crate::DemoSplTokenJournalRequest {
|
||||
profile_name: "local_devnet".to_string(),
|
||||
signature_contains: std::option::Option::None,
|
||||
mint: std::option::Option::None,
|
||||
account: std::option::Option::None,
|
||||
family: std::option::Option::Some("fee".to_string()),
|
||||
operation: std::option::Option::None,
|
||||
limit: 501,
|
||||
};
|
||||
assert!(crate::validate_journal_request(request).is_err());
|
||||
}
|
||||
}
|
||||
399
kb-app-demo-desktop/src/demo_spl_token2022.rs
Normal file
399
kb-app-demo-desktop/src/demo_spl_token2022.rs
Normal file
@@ -0,0 +1,399 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_token2022.rs
|
||||
// version: 4
|
||||
|
||||
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// Public values loaded from one persisted Token-2022 validation fixture.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token2022/DemoSplToken2022FixturePayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoSplToken2022FixturePayload {
|
||||
/// Fixture file used by the application.
|
||||
pub(crate) fixture_path: std::string::String,
|
||||
/// Token-2022 program ID.
|
||||
pub(crate) program_id: std::string::String,
|
||||
/// Mint account.
|
||||
pub(crate) mint: std::string::String,
|
||||
/// Source token account.
|
||||
pub(crate) source: std::string::String,
|
||||
/// Destination token account.
|
||||
pub(crate) destination: std::string::String,
|
||||
/// Dedicated empty account reserved for CloseAccount validation.
|
||||
pub(crate) close_account: std::string::String,
|
||||
/// Delegate account.
|
||||
pub(crate) delegate: std::string::String,
|
||||
/// Profile-wallet authority.
|
||||
pub(crate) authority: std::string::String,
|
||||
/// Freeze authority when configured on the mint.
|
||||
pub(crate) freeze_authority: std::string::String,
|
||||
/// Mint decimals.
|
||||
pub(crate) decimals: u8,
|
||||
/// Default raw amount for the selected scenario.
|
||||
pub(crate) default_amount_raw: std::string::String,
|
||||
}
|
||||
|
||||
/// Request for one independent public Token-2022 Devnet scenario.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022Request.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSplToken2022Request {
|
||||
/// Selected Devnet profile.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Stable scenario identifier.
|
||||
pub(crate) scenario_id: std::string::String,
|
||||
/// Source or target token account depending on the selected operation.
|
||||
pub(crate) source: std::string::String,
|
||||
/// Exact mint account.
|
||||
pub(crate) mint: std::string::String,
|
||||
/// Destination token account or close-account lamport destination.
|
||||
pub(crate) destination: std::string::String,
|
||||
/// Delegate account used by `ApproveChecked`.
|
||||
pub(crate) delegate: std::string::String,
|
||||
/// Owner or mint authority resolved by the selected profile wallet.
|
||||
pub(crate) authority: std::string::String,
|
||||
/// Freeze authority used by freeze and thaw scenarios.
|
||||
pub(crate) freeze_authority: std::string::String,
|
||||
/// Exact raw amount represented as a decimal string.
|
||||
pub(crate) amount_raw: std::string::String,
|
||||
/// Expected mint decimals carried by checked instructions.
|
||||
pub(crate) decimals: u8,
|
||||
/// Whether the transaction should be signed and submitted after simulation.
|
||||
pub(crate) submit: bool,
|
||||
/// Explicit operator confirmation for signed submission.
|
||||
pub(crate) operator_confirmed: bool,
|
||||
/// Whether post-execution core and decode outputs should be force-replayed.
|
||||
pub(crate) force_post_validation_replay: bool,
|
||||
}
|
||||
|
||||
/// UI-safe result of one public Token-2022 Devnet orchestration.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token2022/DemoExecutionSplToken2022SummaryPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoExecutionSplToken2022SummaryPayload {
|
||||
/// Stable scenario identifier.
|
||||
pub(crate) scenario_id: std::string::String,
|
||||
/// Stable operation code.
|
||||
pub(crate) operation: std::string::String,
|
||||
/// Profile used by the orchestration.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Classified cluster code.
|
||||
pub(crate) cluster: std::string::String,
|
||||
/// Genesis hash returned by the endpoint.
|
||||
pub(crate) genesis_hash: std::string::String,
|
||||
/// Source wallet public key.
|
||||
pub(crate) wallet_public_key: std::string::String,
|
||||
/// Exact wallet balance rendered without JSON integer precision loss.
|
||||
pub(crate) balance_lamports: std::string::String,
|
||||
/// Estimated fee rendered without JSON integer precision loss.
|
||||
pub(crate) fee_lamports: std::option::Option<std::string::String>,
|
||||
/// Whether exact simulation succeeded.
|
||||
pub(crate) simulation_success: bool,
|
||||
/// Runtime simulation error when present.
|
||||
pub(crate) simulation_error: std::option::Option<std::string::String>,
|
||||
/// Submitted transaction signature.
|
||||
pub(crate) transaction_signature: std::option::Option<std::string::String>,
|
||||
/// Terminal confirmation status.
|
||||
pub(crate) confirmation_status: std::option::Option<std::string::String>,
|
||||
/// Whether canonical hydration was validated.
|
||||
pub(crate) canonical_inserted: bool,
|
||||
/// Whether core extraction was validated.
|
||||
pub(crate) core_extracted: bool,
|
||||
/// Whether Token-2022 decode replay was validated.
|
||||
pub(crate) decode_replayed: bool,
|
||||
/// Number of materialized rows for the submitted instruction.
|
||||
pub(crate) materialization_count: u32,
|
||||
/// Whether the second replay produced no failure or new output.
|
||||
pub(crate) idempotence_validated: bool,
|
||||
/// Pretty JSON representation of the exact execution plan.
|
||||
pub(crate) plan_json: std::string::String,
|
||||
/// Pretty JSON representation of stateful preflight.
|
||||
pub(crate) preflight_json: std::string::String,
|
||||
/// Pretty JSON representation of the exact simulation result.
|
||||
pub(crate) simulation_json: std::string::String,
|
||||
/// Pretty JSON representation of confirmation and replay diagnostics.
|
||||
pub(crate) diagnostics_json: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) fn operation_from_request(
|
||||
request: &crate::DemoExecutionSplToken2022Request,
|
||||
) -> std::result::Result<kb_lib::ExSplToken2022Operation, std::string::String> {
|
||||
let authority = kb_lib::ExSplTokenAuthority {
|
||||
authority: kb_lib::MdPubkey(request.authority.trim().to_string()),
|
||||
multisig_signers: std::vec::Vec::new(),
|
||||
};
|
||||
let freeze_authority = kb_lib::ExSplTokenAuthority {
|
||||
authority: kb_lib::MdPubkey(request.freeze_authority.trim().to_string()),
|
||||
multisig_signers: std::vec::Vec::new(),
|
||||
};
|
||||
let value = match request.scenario_id.trim() {
|
||||
"token2022_mint_to_checked" => kb_lib::ExSplTokenSingleOperation::MintToChecked {
|
||||
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
|
||||
destination: kb_lib::MdPubkey(request.source.trim().to_string()),
|
||||
authority,
|
||||
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
|
||||
decimals: request.decimals,
|
||||
},
|
||||
"token2022_transfer_checked" => kb_lib::ExSplTokenSingleOperation::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,
|
||||
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
|
||||
decimals: request.decimals,
|
||||
},
|
||||
"token2022_approve_checked" => kb_lib::ExSplTokenSingleOperation::ApproveChecked {
|
||||
source: kb_lib::MdPubkey(request.source.trim().to_string()),
|
||||
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
|
||||
delegate: kb_lib::MdPubkey(request.delegate.trim().to_string()),
|
||||
authority,
|
||||
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
|
||||
decimals: request.decimals,
|
||||
},
|
||||
"token2022_revoke" => kb_lib::ExSplTokenSingleOperation::Revoke {
|
||||
source: kb_lib::MdPubkey(request.source.trim().to_string()),
|
||||
authority,
|
||||
},
|
||||
"token2022_burn_checked" => kb_lib::ExSplTokenSingleOperation::BurnChecked {
|
||||
source: kb_lib::MdPubkey(request.source.trim().to_string()),
|
||||
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
|
||||
authority,
|
||||
amount: kb_lib::ExSplTokenAmount(request.amount_raw.trim().to_string()),
|
||||
decimals: request.decimals,
|
||||
},
|
||||
"token2022_freeze_account" => kb_lib::ExSplTokenSingleOperation::FreezeAccount {
|
||||
account: kb_lib::MdPubkey(request.source.trim().to_string()),
|
||||
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
|
||||
authority: freeze_authority,
|
||||
},
|
||||
"token2022_thaw_account" => kb_lib::ExSplTokenSingleOperation::ThawAccount {
|
||||
account: kb_lib::MdPubkey(request.source.trim().to_string()),
|
||||
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
|
||||
authority: freeze_authority,
|
||||
},
|
||||
"token2022_close_destination" => kb_lib::ExSplTokenSingleOperation::CloseAccount {
|
||||
account: kb_lib::MdPubkey(request.source.trim().to_string()),
|
||||
destination: kb_lib::MdPubkey(request.destination.trim().to_string()),
|
||||
authority,
|
||||
},
|
||||
other => {
|
||||
return std::result::Result::Err(format!(
|
||||
"unsupported public Token-2022 Devnet scenario {other}"
|
||||
));
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(kb_lib::ExSplToken2022Operation::Instruction {
|
||||
value: std::boxed::Box::new(value),
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn demo_spl_token2022_summary_payload(
|
||||
scenario_id: std::string::String,
|
||||
operation: std::string::String,
|
||||
summary: kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionSummary,
|
||||
) -> crate::DemoExecutionSplToken2022SummaryPayload {
|
||||
let plan_json = crate::pretty_json(&summary.plan);
|
||||
let preflight_json = crate::pretty_json(&summary.stateful_preflight);
|
||||
let simulation_json = crate::pretty_json(&summary.simulation);
|
||||
let transaction_signature =
|
||||
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
|
||||
let confirmation_status = summary.confirmation.as_ref().map(|value| {
|
||||
return crate::confirmation_status_code(value.status);
|
||||
});
|
||||
let post_execution = summary.post_execution.as_ref();
|
||||
let canonical_inserted = post_execution.is_some_and(|value| return value.canonical_inserted);
|
||||
let core_extracted = post_execution.is_some_and(|value| return value.core_extracted);
|
||||
let decode_replayed = post_execution.is_some_and(|value| return value.decode_replayed);
|
||||
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
|
||||
return replay.failed_inputs == 0
|
||||
&& !replay.cancelled
|
||||
&& replay.processors.iter().all(|processor| {
|
||||
return processor.failed == 0
|
||||
&& processor.materialized_outputs == 0
|
||||
&& processor.materialization_refused == 0;
|
||||
});
|
||||
});
|
||||
let materialization_count = match u32::try_from(summary.materializations.len()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => u32::MAX,
|
||||
};
|
||||
let diagnostics = serde_json::json!({
|
||||
"confirmation": summary.confirmation,
|
||||
"postExecution": summary.post_execution,
|
||||
"backfill": summary.backfill.as_ref().map(backfill_summary_json),
|
||||
"coreExtraction": summary.core_extraction.as_ref().map(core_extraction_summary_json),
|
||||
"decodeReplay": summary.decode_replay.as_ref().map(decode_replay_summary_json),
|
||||
"idempotenceReplay": summary.idempotence_replay.as_ref().map(decode_replay_summary_json),
|
||||
"materializations": summary.materializations
|
||||
});
|
||||
return crate::DemoExecutionSplToken2022SummaryPayload {
|
||||
scenario_id,
|
||||
operation,
|
||||
profile_name: summary.profile_name,
|
||||
cluster: crate::execution_cluster_code(summary.cluster),
|
||||
genesis_hash: summary.genesis_hash,
|
||||
wallet_public_key: summary.wallet.public_key,
|
||||
balance_lamports: summary.balance_lamports.to_string(),
|
||||
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
|
||||
simulation_success: summary.simulation.success,
|
||||
simulation_error: summary.simulation.error,
|
||||
transaction_signature,
|
||||
confirmation_status,
|
||||
canonical_inserted,
|
||||
core_extracted,
|
||||
decode_replayed,
|
||||
materialization_count,
|
||||
idempotence_validated,
|
||||
plan_json,
|
||||
preflight_json,
|
||||
simulation_json,
|
||||
diagnostics_json: crate::pretty_json(&diagnostics),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn parse_fixture(
|
||||
contents: &str,
|
||||
) -> std::collections::BTreeMap<std::string::String, std::string::String> {
|
||||
let mut values = std::collections::BTreeMap::new();
|
||||
for line in contents.lines() {
|
||||
let trimmed = line.trim();
|
||||
let assignment = match trimmed.strip_prefix("export ") {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => continue,
|
||||
};
|
||||
let (name, raw_value) = match assignment.split_once('=') {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => continue,
|
||||
};
|
||||
let value = raw_value.trim().trim_matches('\'').trim_matches('"').to_string();
|
||||
values.insert(name.trim().to_string(), value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
fn backfill_summary_json(summary: &kb_pipeline::BackfillSummary) -> serde_json::Value {
|
||||
return serde_json::json!({
|
||||
"captureSessionId": summary.capture_session_id,
|
||||
"filterCode": summary.filter_code,
|
||||
"role": summary.role,
|
||||
"provider": summary.provider,
|
||||
"endpointCode": summary.endpoint_code,
|
||||
"pagesFetched": summary.pages_fetched,
|
||||
"candidatesSelected": summary.candidates_selected,
|
||||
"candidatesStarted": summary.candidates_started,
|
||||
"candidatesCompleted": summary.candidates_completed,
|
||||
"candidatesCancelled": summary.candidates_cancelled,
|
||||
"candidatesNotStarted": summary.candidates_not_started,
|
||||
"transactionsReceived": summary.transactions_received,
|
||||
"canonicalInserted": summary.canonical_inserted,
|
||||
"canonicalSkipped": summary.canonical_skipped,
|
||||
"existingSkipped": summary.existing_skipped,
|
||||
"missing": summary.missing,
|
||||
"failed": summary.failed,
|
||||
"observationsInserted": summary.observations_inserted,
|
||||
"attempts": summary.attempts,
|
||||
"cancelled": summary.cancelled,
|
||||
"resumeBeforeSignature": summary.resume_before_signature,
|
||||
"startedAt": summary.started_at,
|
||||
"finishedAt": summary.finished_at
|
||||
});
|
||||
}
|
||||
|
||||
fn core_extraction_summary_json(summary: &kb_pipeline::CoreExtractionSummary) -> serde_json::Value {
|
||||
return serde_json::json!({
|
||||
"processorVersion": summary.processor_version,
|
||||
"selected": summary.selected,
|
||||
"started": summary.started,
|
||||
"completed": summary.completed,
|
||||
"skipped": summary.skipped,
|
||||
"extracted": summary.extracted,
|
||||
"failed": summary.failed,
|
||||
"cancelledCandidates": summary.cancelled_candidates,
|
||||
"notStarted": summary.not_started,
|
||||
"cancelled": summary.cancelled,
|
||||
"startedAt": summary.started_at,
|
||||
"finishedAt": summary.finished_at
|
||||
});
|
||||
}
|
||||
|
||||
fn decode_replay_summary_json(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json::Value {
|
||||
let processors = summary
|
||||
.processors
|
||||
.iter()
|
||||
.map(|processor| {
|
||||
return serde_json::json!({
|
||||
"name": processor.processor_name,
|
||||
"version": processor.processor_version,
|
||||
"dispatched": processor.dispatched,
|
||||
"skipped": processor.skipped,
|
||||
"decoded": processor.decoded,
|
||||
"ignored": processor.ignored,
|
||||
"unsupported": processor.unsupported,
|
||||
"failed": processor.failed,
|
||||
"materializedOutputs": processor.materialized_outputs,
|
||||
"materializationRefused": processor.materialization_refused
|
||||
});
|
||||
})
|
||||
.collect::<std::vec::Vec<serde_json::Value>>();
|
||||
return serde_json::json!({
|
||||
"campaignId": summary.campaign_id,
|
||||
"pipelineVersion": summary.pipeline_version,
|
||||
"selected": summary.selected,
|
||||
"started": summary.started,
|
||||
"completed": summary.completed,
|
||||
"unmatched": summary.unmatched,
|
||||
"notStarted": summary.not_started,
|
||||
"failedInputs": summary.failed_inputs,
|
||||
"cancelled": summary.cancelled,
|
||||
"processors": processors,
|
||||
"startedAt": summary.started_at,
|
||||
"finishedAt": summary.finished_at
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn request(scenario_id: &str) -> crate::DemoExecutionSplToken2022Request {
|
||||
return crate::DemoExecutionSplToken2022Request {
|
||||
profile_name: "local_devnet".to_string(),
|
||||
scenario_id: scenario_id.to_string(),
|
||||
source: "source".to_string(),
|
||||
mint: "mint".to_string(),
|
||||
destination: "destination".to_string(),
|
||||
delegate: "delegate".to_string(),
|
||||
authority: "authority".to_string(),
|
||||
freeze_authority: "freeze".to_string(),
|
||||
amount_raw: "1".to_string(),
|
||||
decimals: 9,
|
||||
submit: false,
|
||||
operator_confirmed: false,
|
||||
force_post_validation_replay: true,
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_public_scenario_builds_one_typed_operation() {
|
||||
for scenario in [
|
||||
"token2022_mint_to_checked",
|
||||
"token2022_transfer_checked",
|
||||
"token2022_approve_checked",
|
||||
"token2022_revoke",
|
||||
"token2022_burn_checked",
|
||||
"token2022_freeze_account",
|
||||
"token2022_thaw_account",
|
||||
"token2022_close_destination",
|
||||
] {
|
||||
assert!(crate::operation_from_request(&request(scenario)).is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/lib.rs
|
||||
// version: 7
|
||||
// version: 9
|
||||
|
||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||
|
||||
@@ -13,7 +13,12 @@ mod demo_backfill;
|
||||
mod demo_config;
|
||||
mod demo_core_extraction;
|
||||
mod demo_decode_replay;
|
||||
mod demo_execution_solana_core;
|
||||
mod demo_execution_spl;
|
||||
mod demo_http;
|
||||
mod demo_spl_ata;
|
||||
mod demo_spl_token;
|
||||
mod demo_spl_token2022;
|
||||
mod demo_sql_common;
|
||||
mod demo_sql_diag;
|
||||
mod demo_sql_pg_core;
|
||||
@@ -54,6 +59,100 @@ pub(crate) use self::demo_backfill::demo_backfill_summary_payload;
|
||||
pub(crate) use self::demo_config::DemoConfigPayload;
|
||||
/// Builds the configuration payload from shared application state.
|
||||
pub(crate) use self::demo_config::demo_config_payload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionObserver;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionOptionsPayload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionProgressPayload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionRequest;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionRunGuard;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionSummaryPayload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::build_demo_core_extraction_pipeline_request;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::demo_core_extraction_summary_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeCoverageSummaryPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeDiagnosticsPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeProcessorSummaryPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayDecoderOption;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayObserver;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayOptionsPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayProgressPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayRequest;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayRunGuard;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplaySummaryPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRequest;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRow;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::annotation_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::available_decoders;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::available_materializers;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::build_demo_decode_replay_pipeline_request;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::coverage_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::demo_decode_replay_summary_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::optional_line_count;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::register_active_campaign;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::text_sample;
|
||||
/// Request sent by the Memo v4 Devnet execution panel.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoRequest;
|
||||
/// UI-safe result of one Memo v4 Devnet execution orchestration.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoSummaryPayload;
|
||||
/// Public key generated for a disposable Devnet recipient.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreGeneratedRecipientPayload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreObserver;
|
||||
/// Initial options shown by the Solana Core execution demo.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreOptionsPayload;
|
||||
/// One Devnet execution profile exposed to the demo window.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProfileOption;
|
||||
/// Progress event emitted during execution and post-validation.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProgressPayload;
|
||||
/// Request sent by the execution demo.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRequest;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRunGuard;
|
||||
/// UI-safe result of one execution orchestration.
|
||||
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreSummaryPayload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::confirmation_status_code;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_memo_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::devnet_profile_options;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::execution_cluster_code;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::pretty_json;
|
||||
/// ????
|
||||
pub(crate) use self::demo_execution_solana_core::select_devnet_profile;
|
||||
/// Frontend payload for one independent Devnet SPL validation scenario.
|
||||
pub(crate) use self::demo_execution_spl::DevnetSplValidationScenarioPayload;
|
||||
/// HTTP demo response payload.
|
||||
pub(crate) use self::demo_http::DemoHttpExecutionPayload;
|
||||
/// One selectable HTTP JSON-RPC method.
|
||||
@@ -70,12 +169,66 @@ pub(crate) use self::demo_http::build_http_method_options;
|
||||
pub(crate) use self::demo_http::build_http_role_options;
|
||||
/// Executes one raw HTTP JSON-RPC request.
|
||||
pub(crate) use self::demo_http::demo_http_execute_request_inner;
|
||||
/// Request for one representative ATA creation on Devnet.
|
||||
pub(crate) use self::demo_spl_ata::DemoExecutionSplAtaRequest;
|
||||
/// UI-safe ATA execution result.
|
||||
pub(crate) use self::demo_spl_ata::DemoExecutionSplAtaSummaryPayload;
|
||||
/// Readonly payer, wallet, Token Program and derived ATA values.
|
||||
pub(crate) use self::demo_spl_ata::DemoSplAtaDerivationPayload;
|
||||
/// Request used to derive the representative wallet ATA before execution.
|
||||
pub(crate) use self::demo_spl_ata::DemoSplAtaDerivationRequest;
|
||||
/// Bounded ATA lifecycle journal request.
|
||||
pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRequest;
|
||||
/// One UI-safe ATA lifecycle journal row.
|
||||
pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRow;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::demo_spl_ata_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::derive_ata;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::journal_matches;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::journal_row;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::load_profile_wallet;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_ata::parse_token_program;
|
||||
/// Request for one checked classic SPL Token transfer on Devnet.
|
||||
pub(crate) use self::demo_spl_token::DemoExecutionSplTokenRequest;
|
||||
/// UI-safe result of one checked SPL Token transfer orchestration.
|
||||
pub(crate) use self::demo_spl_token::DemoExecutionSplTokenSummaryPayload;
|
||||
/// Bounded exact filters for the classic SPL Token materialized journal.
|
||||
pub(crate) use self::demo_spl_token::DemoSplTokenJournalRequest;
|
||||
/// UI-safe materialized classic SPL Token journal row.
|
||||
pub(crate) use self::demo_spl_token::DemoSplTokenJournalRow;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token::demo_spl_token_journal_row;
|
||||
/// Executes one checked SPL Token simulation or explicitly authorized Devnet submission.
|
||||
pub(crate) use self::demo_spl_token::demo_spl_token_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token::payload_matches;
|
||||
/// Loads a bounded, typed journal of committed classic SPL Token projections.
|
||||
pub(crate) use self::demo_spl_token::validate_journal_request;
|
||||
/// Request for one independent public Token-2022 Devnet scenario.
|
||||
pub(crate) use self::demo_spl_token2022::DemoExecutionSplToken2022Request;
|
||||
/// UI-safe result of one public Token-2022 Devnet orchestration.
|
||||
pub(crate) use self::demo_spl_token2022::DemoExecutionSplToken2022SummaryPayload;
|
||||
/// Public values loaded from one persisted Token-2022 validation fixture.
|
||||
pub(crate) use self::demo_spl_token2022::DemoSplToken2022FixturePayload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token2022::demo_spl_token2022_summary_payload;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token2022::operation_from_request;
|
||||
/// ????
|
||||
pub(crate) use self::demo_spl_token2022::parse_fixture;
|
||||
/// UI-safe SQL table diagnostic snapshot.
|
||||
pub(crate) use self::demo_sql_common::DemoSqlTableSnapshot;
|
||||
/// Connects to the configured PostgreSQL store.
|
||||
pub(crate) use self::demo_sql_common::connect_postgres_store;
|
||||
/// Formats diagnostic statuses.
|
||||
pub(crate) use self::demo_sql_common::debug_status;
|
||||
/// Initializes the PostgreSQL schema during startup.
|
||||
pub(crate) use self::demo_sql_common::initialize_postgres_schema_for_startup;
|
||||
/// Opens or focuses one SQL demo window.
|
||||
pub(crate) use self::demo_sql_common::open_sql_demo_window;
|
||||
/// Builds PostgreSQL options from the active profile.
|
||||
@@ -154,6 +307,8 @@ pub(crate) use self::demo_ws::demo_ws_disconnect_inner;
|
||||
pub(crate) use self::demo_ws::demo_ws_status_inner;
|
||||
/// Unsubscribes one active WebSocket subscription.
|
||||
pub(crate) use self::demo_ws::demo_ws_unsubscribe_inner;
|
||||
/// Disconnects the persistent WebSocket session during application shutdown.
|
||||
pub(crate) use self::demo_ws::disconnect_demo_ws_app_state;
|
||||
/// Frontend logging payload.
|
||||
pub(crate) use self::frontend_log::FrontendLogPayload;
|
||||
/// Emits one normalized frontend log event.
|
||||
@@ -175,64 +330,6 @@ pub(crate) use self::splash::emit_splash_order;
|
||||
/// Waits for the minimum splash duration.
|
||||
pub(crate) use self::splash::wait_until_minimum;
|
||||
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionObserver;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionOptionsPayload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionProgressPayload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionRequest;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionRunGuard;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::DemoCoreExtractionSummaryPayload;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::build_demo_core_extraction_pipeline_request;
|
||||
/// Internal demo core extraction item.
|
||||
pub(crate) use self::demo_core_extraction::demo_core_extraction_summary_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeCoverageSummaryPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeDiagnosticsPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeProcessorSummaryPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayDecoderOption;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayObserver;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayOptionsPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayProgressPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayRequest;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplayRunGuard;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoDecodeReplaySummaryPayload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRequest;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRow;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::annotation_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::available_decoders;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::available_materializers;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::build_demo_decode_replay_pipeline_request;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::coverage_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::demo_decode_replay_summary_payload;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::optional_line_count;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::register_active_campaign;
|
||||
/// Internal demo decode replay item.
|
||||
pub(crate) use self::demo_decode_replay::text_sample;
|
||||
// Keep the canonical tracing target as the final facade export.
|
||||
/// Canonical tracing target for the desktop demo application.
|
||||
pub(crate) use self::constants::TRACING_TARGET;
|
||||
|
||||
@@ -64,6 +64,21 @@ pub fn run() -> kb_core::Result<()> {
|
||||
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,
|
||||
@@ -1307,3 +1322,867 @@ async fn load_demo_sql_replay_entities(
|
||||
}
|
||||
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()),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user