Files
khadhroony-bot3/kb-app-demo-desktop/src/demo_spl_token.rs
2026-07-30 10:27:06 +02:00

498 lines
21 KiB
Rust

// file: kb-app-demo-desktop/src/demo_spl_token.rs
// version: 4
//! 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.processing_error_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.processing_errors == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
});
let materialization_count = match u32::try_from(summary.materializations.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => u32::MAX,
};
let diagnostics = serde_json::json!({
"confirmation": summary.confirmation,
"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,
"processingErrorInputs": summary.processing_error_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,
"processingErrors": processor.processing_errors,
"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: "materializer.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());
}
}