v0.4.8-pre.010

This commit is contained in:
2026-08-06 17:26:14 +02:00
parent b314bfc244
commit 3167297283
40 changed files with 2556 additions and 1125 deletions

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_devnet_common.rs
// version: 2
// version: 3
//! Shared Devnet demo UI contracts.
@@ -41,6 +41,18 @@ pub(crate) fn devnet_store_readiness_payload(
};
}
/// Releases the shared Devnet execution slot when one workflow terminates.
pub(crate) struct DemoExecutionRunGuard<'a> {
/// Shared running flag reset on drop.
pub(crate) running: &'a std::sync::atomic::AtomicBool,
}
impl std::ops::Drop for crate::DemoExecutionRunGuard<'_> {
fn drop(&mut self) {
self.running.store(false, std::sync::atomic::Ordering::Release);
}
}
/// Serializes a diagnostic value to indented JSON without panicking.
pub(crate) fn pretty_json<T>(value: &T) -> std::string::String
where

View File

@@ -1,840 +1,158 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata.rs
// version: 14
// version: 15
//! Thin desktop adapters for metadata execution demo scenarios.
//! Shared desktop contracts for metadata execution demos.
/// UI-safe Metaplex scenario row.
use tauri::Emitter; // rust-rules: trait-import
/// UI-safe progress event shared by Metadata execution workflows.
#[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_metadata/DemoExecutionMetadataScenarioPayload.ts"
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataProgressPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataScenarioPayload {
/// Stable scenario identifier.
pub id: std::string::String,
/// Operator-visible scenario label.
pub label: std::string::String,
/// Stable asset-family code.
pub asset_family: std::string::String,
/// Stable fixture graph code.
pub fixture_kind: std::string::String,
/// Required initial lifecycle state.
pub initial_state: std::string::String,
/// Expected terminal lifecycle state.
pub resulting_state: std::string::String,
/// Stable execution-mode code.
pub mode: std::string::String,
/// Ordered operation codes.
pub operation_codes: std::vec::Vec<std::string::String>,
/// Whether a collection account is required.
pub requires_collection: bool,
/// Whether programmable authorization rules are required.
pub requires_programmable_rules: bool,
/// Whether postconditions are mandatory.
pub requires_postcondition: bool,
/// Current validation status from the canonical matrix.
pub validation_status: std::string::String,
/// Required evidence kinds.
pub required_evidence: std::vec::Vec<std::string::String>,
/// Canonical account projections expected when materialization is enabled.
pub materialization_targets: std::vec::Vec<std::string::String>,
pub(crate) struct DemoExecutionMetadataProgressPayload {
/// RFC3339 event timestamp.
pub timestamp: std::string::String,
/// Stable lowercase log level.
pub level: std::string::String,
/// Stable execution stage.
pub stage: std::string::String,
/// Operator-visible message.
pub message: std::string::String,
/// Optional transaction signature.
pub signature: std::option::Option<std::string::String>,
}
/// UI-safe result of one prepared Metaplex Create fixture.
#[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_metadata/DemoExecutionMetadataCreateFixturePayload.ts"
)]
pub(crate) struct DemoExecutionMetadataCreateFixturePayload {
/// Fixture mint address.
pub mint: std::string::String,
/// Canonical metadata PDA.
pub metadata: std::string::String,
/// Canonical master-edition PDA.
pub master_edition: std::string::String,
/// Wallet authority.
pub authority: std::string::String,
/// Generated typed Create operation JSON.
pub operation_json: std::string::String,
/// Whether the mint account was newly created.
pub mint_created: bool,
/// Metadata-window observer shared by Metaplex and Solana Program Metadata workflows.
pub(crate) struct DemoExecutionMetadataObserver<'a> {
/// Tauri application handle used for targeted progress events.
pub(crate) app_handle: tauri::AppHandle,
/// Shared cooperative cancellation flag.
pub(crate) cancel_requested: &'a std::sync::atomic::AtomicBool,
}
/// UI-safe prepared step for one coherent Metaplex Devnet 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_metadata/DemoExecutionMetadataPreparedStepPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataPreparedStepPayload {
/// Stable scenario identifier.
pub scenario_id: std::string::String,
/// Zero-based step index.
pub step_index: usize,
/// Stable operation name accepted by the executor JSON contract.
pub operation: std::string::String,
/// Operator-visible step label.
pub label: std::string::String,
/// Prepared typed operation JSON.
pub operation_json: std::string::String,
/// Automatically generated bounded preflight reads.
pub preflight_reads_json: std::string::String,
/// Automatically generated bounded postcondition reads.
pub postcondition_reads_json: std::string::String,
/// Whether this step can be simulated immediately.
pub simulation_ready: bool,
/// Whether this step can be submitted immediately.
pub submission_ready: bool,
/// Fixture state used to prepare the step.
pub fixture_state: std::string::String,
impl crate::DemoExecutionMetadataObserver<'_> {
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::DemoExecutionMetadataProgressPayload {
timestamp,
level: level.to_string(),
stage: stage.to_string(),
message,
signature,
};
let emit_result = self.app_handle.emit_to(
"demo_execution_metadata",
"demo-execution-metadata-progress",
payload,
);
if let std::result::Result::Err(error) = emit_result {
tracing::warn!(
target: crate::TRACING_TARGET,
action = "emit_metadata_execution_progress",
error = %error,
"cannot emit Metadata execution progress"
);
}
}
fn cancelled(&self) -> bool {
return self.cancel_requested.load(std::sync::atomic::Ordering::Relaxed);
}
}
/// Request for one real Devnet Metaplex execution from the desktop panel.
#[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_metadata/DemoExecutionMetadataRequest.ts"
)]
pub(crate) struct DemoExecutionMetadataRequest {
/// Devnet profile selected by the operator.
pub profile_name: std::string::String,
/// Stable execution identifier shown in diagnostics.
pub intent_id: std::string::String,
/// Serialized typed current Metaplex operation.
pub operation_json: std::string::String,
/// Serialized bounded preflight read requests.
pub preflight_reads_json: std::string::String,
/// Serialized bounded postcondition read requests.
pub postcondition_reads_json: std::string::String,
/// Whether signing and submission are explicitly requested.
pub submit: bool,
/// Explicit operator confirmation for submission.
pub operator_confirmed: bool,
/// Materializes confirmed Metaplex account snapshots after submission.
pub materialize_after_confirmation: bool,
impl kb_pipeline::BackfillObserver for crate::DemoExecutionMetadataObserver<'_> {
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();
}
}
/// UI-safe result of one real Devnet Metaplex execution.
#[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_metadata/DemoExecutionMetadataSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataSummaryPayload {
/// Selected profile name.
pub profile_name: std::string::String,
/// Exact cluster code.
pub cluster: std::string::String,
/// Genesis hash observed from RPC.
pub genesis_hash: std::string::String,
/// Wallet public key used by the runner.
pub wallet_public_key: std::string::String,
/// Wallet balance before execution.
pub balance_lamports: std::string::String,
/// Whether the real RPC simulation succeeded.
pub simulation_success: bool,
/// Optional submitted signature.
pub transaction_signature: std::option::Option<std::string::String>,
/// Optional terminal confirmation status.
pub confirmation_status: std::option::Option<std::string::String>,
/// Exact prepared plan as formatted JSON.
pub plan_json: std::string::String,
/// Stateful preflight as formatted JSON.
pub preflight_json: std::string::String,
/// Real RPC simulation as formatted JSON.
pub simulation_json: std::string::String,
/// Before/after state and readiness diagnostics as formatted JSON.
pub evidence_json: std::string::String,
/// Whether post-confirmation account materialization was requested.
pub materialization_requested: bool,
/// Number of canonical post-confirmation snapshots materialized.
pub materialized_snapshot_count: usize,
impl kb_pipeline::CoreExtractionObserver for crate::DemoExecutionMetadataObserver<'_> {
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();
}
}
/// Returns Devnet profile options dedicated to the Metadata execution panel.
impl kb_pipeline::DecodeReplayObserver for crate::DemoExecutionMetadataObserver<'_> {
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::DemoExecutionMetadataObserver<'_>
{
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();
}
}
/// Returns Devnet profile options shared by the Metadata execution panel.
pub(crate) fn demo_execution_metadata_options(
state: tauri::State<'_, crate::AppState>,
) -> crate::DemoExecutionSolanaCoreOptionsPayload {
return crate::demo_execution_solana_core_options(state);
}
/// Executes one real Devnet Metaplex simulation or explicitly confirmed submission.
pub(crate) async fn demo_execution_metadata_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataRequest,
) -> std::result::Result<crate::DemoExecutionMetadataSummaryPayload, 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 mut execution_request = match kb_pipeline_demo_scenarios::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json(
request.intent_id,
request.operation_json.as_str(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
execution_request.preflight_reads =
match parse_stateful_reads(request.preflight_reads_json.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
execution_request.postcondition_reads =
match parse_stateful_reads(request.postcondition_reads_json.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
execution_request.submit = request.submit;
execution_request.operator_confirmed = request.operator_confirmed;
execution_request.materialize_after_confirmation = request.materialize_after_confirmation;
if let std::result::Result::Err(error) = execution_request.validate() {
return std::result::Result::Err(error.to_string());
}
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_metaplex_token_metadata(
&http_pool,
&profile,
workspace_root.as_path(),
&execution_request,
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status =
summary.confirmation.as_ref().map(|value| return format!("{:?}", value.status));
let evidence_json = crate::pretty_json(&serde_json::json!({
"before": &summary.before,
"readiness": &summary.readiness,
"sendResult": &summary.send_result,
"confirmation": &summary.confirmation,
"after": &summary.after,
"materializationRequested": summary.materialization_requested,
"materializedSnapshots": &summary.materialized_snapshots
}));
return std::result::Result::Ok(crate::DemoExecutionMetadataSummaryPayload {
profile_name: summary.profile_name,
cluster: format!("{:?}", summary.cluster).to_lowercase(),
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
balance_lamports: summary.balance_lamports.to_string(),
simulation_success: summary.simulation.success,
transaction_signature,
confirmation_status,
plan_json: crate::pretty_json(&summary.plan),
preflight_json: crate::pretty_json(&summary.stateful_preflight),
simulation_json: crate::pretty_json(&summary.simulation),
evidence_json,
materialization_requested: summary.materialization_requested,
materialized_snapshot_count: summary.materialized_snapshots.len(),
});
}
fn parse_stateful_reads(
value: &str,
) -> std::result::Result<
std::vec::Vec<kb_pipeline::MetaplexTokenMetadataStatefulReadRequest>,
std::string::String,
> {
if value.trim().is_empty() {
return std::result::Result::Ok(std::vec::Vec::new());
}
return match serde_json::from_str(value) {
std::result::Result::Ok(parsed) => std::result::Result::Ok(parsed),
std::result::Result::Err(error) => {
std::result::Result::Err(format!("invalid Metaplex stateful reads JSON: {error}"))
},
};
}
/// Creates or reuses the Devnet mint fixture used by the named Create scenario.
pub(crate) async fn demo_execution_metadata_prepare_create_fixture(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
) -> std::result::Result<crate::DemoExecutionMetadataCreateFixturePayload, 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 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 workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::prepare_metaplex_create_fixture(
&http_pool,
&profile,
workspace_root.as_path(),
&kb_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir),
)
.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::DemoExecutionMetadataCreateFixturePayload {
mint: summary.mint,
metadata: summary.metadata,
master_edition: summary.master_edition,
authority: summary.authority,
operation_json: summary.operation_json,
mint_created: summary.mint_created,
});
}
fn adapt_create_operation_json(
operation_json: &str,
asset_family: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily,
) -> std::result::Result<std::string::String, std::string::String> {
let mut value: serde_json::Value = match serde_json::from_str(operation_json) {
std::result::Result::Ok(parsed) => parsed,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"invalid generated Metaplex Create JSON: {error}"
));
},
};
if matches!(
asset_family,
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft
| kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible
) {
value["master_edition"] = serde_json::Value::Null;
}
let create = match value
.get_mut("create_args")
.and_then(|args| return args.get_mut("V1"))
.and_then(serde_json::Value::as_object_mut)
{
std::option::Option::Some(found) => found,
std::option::Option::None => {
return std::result::Result::Err(
"generated Metaplex Create JSON has no create_args.V1 object".to_string(),
);
},
};
match asset_family {
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet NFT"));
create.insert("symbol".to_string(), serde_json::json!("KHNFT"));
create.insert("token_standard".to_string(), serde_json::json!("NonFungible"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::Value::Null);
create.insert("print_supply".to_string(), serde_json::json!("Zero"));
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet SFT"));
create.insert("symbol".to_string(), serde_json::json!("KHSFT"));
create.insert("token_standard".to_string(), serde_json::json!("FungibleAsset"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::json!(0));
create.insert("print_supply".to_string(), serde_json::Value::Null);
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Fungible"));
create.insert("symbol".to_string(), serde_json::json!("KHFUN"));
create.insert("token_standard".to_string(), serde_json::json!("Fungible"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::json!(0));
create.insert("print_supply".to_string(), serde_json::Value::Null);
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Collection"));
create.insert("symbol".to_string(), serde_json::json!("KHCOL"));
create.insert("token_standard".to_string(), serde_json::json!("NonFungible"));
create.insert("collection_details".to_string(), serde_json::json!({"V1": {"size": 0}}));
create.insert("decimals".to_string(), serde_json::Value::Null);
create.insert("print_supply".to_string(), serde_json::json!("Zero"));
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet pNFT"));
create.insert("symbol".to_string(), serde_json::json!("KHPNFT"));
create
.insert("token_standard".to_string(), serde_json::json!("ProgrammableNonFungible"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("rule_set".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::Value::Null);
create.insert("print_supply".to_string(), serde_json::json!("Zero"));
},
}
return std::result::Result::Ok(crate::pretty_json(&value));
}
/// Prepares one coherent scenario step and all of its stateful reads.
pub(crate) async fn demo_execution_metadata_prepare_step(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
scenario_id: std::string::String,
step_index: usize,
previous_operation_json: std::option::Option<std::string::String>,
) -> std::result::Result<crate::DemoExecutionMetadataPreparedStepPayload, std::string::String> {
let scenario = kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios()
.into_iter()
.find(|candidate| return candidate.id == scenario_id);
let scenario = match scenario {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(format!(
"unknown Metaplex Devnet scenario `{scenario_id}`"
));
},
};
let operation_code = match scenario.operation_codes.get(step_index) {
std::option::Option::Some(value) => value.clone(),
std::option::Option::None => {
return std::result::Result::Err(format!(
"scenario `{}` has no step at index {step_index}",
scenario.id
));
},
};
let operation_suffix = match operation_code.rsplit('.').next() {
std::option::Option::Some(value) => value,
std::option::Option::None => operation_code.as_str(),
};
let operation = if operation_suffix == "update" {
"update_as_update_authority_v2".to_string()
} else {
operation_suffix.to_string()
};
if operation != "create" && operation != "update_as_update_authority_v2" {
return std::result::Result::Err(format!(
"scenario step `{operation}` is declared but its Devnet fixture builder is not implemented yet"
));
}
let (fixture_mint, fixture_metadata, fixture_master_edition, operation_json, fixture_state) =
if operation == "create" {
let fixture =
match crate::demo_execution_metadata_prepare_create_fixture(state, profile_name)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let operation_json = match adapt_create_operation_json(
fixture.operation_json.as_str(),
scenario.asset_family,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
(
fixture.mint,
fixture.metadata,
fixture.master_edition,
operation_json,
if fixture.mint_created {
"mint_prepared".to_string()
} else {
"mint_reused".to_string()
},
)
} else {
let previous = match previous_operation_json {
std::option::Option::Some(value) if !value.trim().is_empty() => value,
_ => {
return std::result::Result::Err(
"prepare and submit the previous NFT lifecycle step before continuing"
.to_string(),
);
},
};
let previous_value: serde_json::Value = match serde_json::from_str(previous.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"invalid previous scenario operation JSON: {error}"
));
},
};
let string_field = |name: &str| -> std::result::Result<
std::string::String,
std::string::String,
> {
return match previous_value.get(name).and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => std::result::Result::Ok(value.to_string()),
std::option::Option::None => std::result::Result::Err(format!(
"previous scenario step does not contain `{name}`"
)),
};
};
let mint = match string_field("mint") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let metadata = match string_field("metadata") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let authority = match string_field("authority") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let master_edition =
match previous_value.get("master_edition").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => value.to_string(),
std::option::Option::None => std::string::String::new(),
};
let immutable_transition = scenario.asset_family
== kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft
&& step_index == 2;
let value = serde_json::json!({
"operation": "update_as_update_authority_v2",
"authority": authority.clone(),
"mint": mint.clone(),
"metadata": metadata.clone(),
"edition": null,
"token": null,
"authorization_rules_program": null,
"authorization_rules": null,
"update_args": {
"AsUpdateAuthorityV2": {
"new_update_authority": null,
"data": null,
"primary_sale_happened": if immutable_transition {
serde_json::Value::Null
} else {
serde_json::json!(true)
},
"is_mutable": if immutable_transition {
serde_json::json!(false)
} else {
serde_json::Value::Null
},
"collection": "None",
"collection_details": "None",
"uses": "None",
"rule_set": "None",
"token_standard": null,
"authorization_data": null
}
}
});
(
mint,
metadata,
master_edition,
crate::pretty_json(&value),
if immutable_transition {
"metadata_immutable".to_string()
} else {
"metadata_created".to_string()
},
)
};
let metadata_read = serde_json::json!({
"query_role": "http_queries",
"account": fixture_metadata.clone(),
"kind": { "kind": "metadata" },
"min_context_slot": null,
"max_data_bytes": 65536
});
let edition_read = serde_json::json!({
"query_role": "http_queries",
"account": fixture_master_edition.clone(),
"kind": { "kind": "edition", "mint": fixture_mint.clone() },
"min_context_slot": null,
"max_data_bytes": 65536
});
let preflight_reads = if operation == "create" {
serde_json::json!([])
} else {
serde_json::json!([metadata_read.clone()])
};
let create_reads_edition = matches!(
scenario.asset_family,
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft
| kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection
| kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft
);
let postcondition_reads = if operation == "create" && create_reads_edition {
serde_json::json!([metadata_read, edition_read])
} else {
serde_json::json!([metadata_read])
};
return std::result::Result::Ok(crate::DemoExecutionMetadataPreparedStepPayload {
scenario_id: scenario.id,
step_index,
operation: operation.clone(),
label: if scenario.asset_family
== kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft
&& step_index == 2
{
"Étape 3 — rendre les metadata immutables".to_string()
} else {
format!("Étape {}{}", step_index + 1, operation)
},
operation_json,
preflight_reads_json: crate::pretty_json(&preflight_reads),
postcondition_reads_json: crate::pretty_json(&postcondition_reads),
simulation_ready: true,
submission_ready: true,
fixture_state,
});
}
/// Returns current operation names accepted by automatic Devnet campaigns.
pub(crate) fn demo_execution_metadata_current_operations() -> std::vec::Vec<std::string::String> {
return kb_pipeline_demo_scenarios::metaplex_token_metadata_current_operation_names()
.iter()
.map(|value| return (*value).to_string())
.collect();
}
/// Returns one generated typed JSON template for a named current operation.
pub(crate) fn demo_execution_metadata_operation_template(
operation: std::string::String,
) -> std::result::Result<std::string::String, std::string::String> {
return match kb_pipeline_demo_scenarios::metaplex_token_metadata_current_operation_json_template(
operation.as_str(),
) {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
/// Returns the canonical Metaplex scenario inventory for the desktop panel.
pub(crate) fn demo_execution_metadata_scenarios() -> std::result::Result<
std::vec::Vec<crate::DemoExecutionMetadataScenarioPayload>,
std::string::String,
> {
let matrix = match kb_pipeline_demo_scenarios::load_metaplex_token_metadata_validation_matrix()
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let statuses = matrix
.scenarios
.into_iter()
.map(|scenario| return (scenario.id, (scenario.status, scenario.required_evidence)))
.collect::<std::collections::BTreeMap<
std::string::String,
(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus,
std::vec::Vec<std::string::String>,
),
>>();
let mut payloads = std::vec::Vec::new();
for scenario in kb_pipeline_demo_scenarios::metaplex_token_metadata_synthetic_scenarios()
.into_iter()
.chain(kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios())
{
let status = statuses.get(scenario.id.as_str());
let validation_status = match status {
std::option::Option::Some((value, _)) => validation_status_code(*value),
std::option::Option::None => "not_run".to_string(),
};
let required_evidence = match status {
std::option::Option::Some((_, evidence)) => evidence.clone(),
std::option::Option::None => std::vec::Vec::new(),
};
payloads.push(crate::DemoExecutionMetadataScenarioPayload {
id: scenario.id,
label: scenario.label,
asset_family: asset_family_code(scenario.asset_family),
fixture_kind: fixture_kind_code(scenario.fixture_kind),
initial_state: fixture_state_code(scenario.initial_state),
resulting_state: fixture_state_code(scenario.resulting_state),
mode: scenario_mode_code(scenario.mode),
operation_codes: scenario.operation_codes,
requires_collection: scenario.requires_collection,
requires_programmable_rules: scenario.requires_programmable_rules,
requires_postcondition: scenario.requires_postcondition,
validation_status,
required_evidence,
materialization_targets: scenario.materialization_targets,
});
}
return std::result::Result::Ok(payloads);
}
fn asset_family_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily,
) -> std::string::String {
return match value {
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => "nft".to_string(),
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => "sft".to_string(),
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => {
"fungible".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => {
"collection".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => {
"programmable_nft".to_string()
},
};
}
fn fixture_kind_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataFixtureKind,
) -> std::string::String {
return format!("{value:?}").to_lowercase();
}
fn fixture_state_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataFixtureState,
) -> std::string::String {
return format!("{value:?}").to_lowercase();
}
fn scenario_mode_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode,
) -> std::string::String {
return match value {
kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::Synthetic => {
"synthetic".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSimulation => {
"network_simulation".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSubmission => {
"network_submission".to_string()
},
};
}
fn validation_status_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus,
) -> std::string::String {
return match value {
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::NotRun => {
"not_run".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::SyntheticValidated => {
"synthetic_validated".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Simulated => {
"simulated".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Submitted => {
"submitted".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Confirmed => {
"confirmed".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Unavailable => {
"unavailable".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Failed => {
"failed".to_string()
},
};
}
#[cfg(test)]
mod tests {
#[test]
fn desktop_inventory_matches_reusable_scenarios_and_matrix() {
let payloads = match crate::demo_execution_metadata_scenarios() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
assert_eq!(std::option::Option::Some(error), std::option::Option::None);
return;
},
};
let expected_count =
kb_pipeline_demo_scenarios::metaplex_token_metadata_synthetic_scenarios().len()
+ kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios().len();
assert_eq!(payloads.len(), expected_count);
assert!(payloads.iter().all(|payload| return !payload.operation_codes.is_empty()));
assert!(payloads.iter().all(|payload| return payload.requires_postcondition));
assert!(
payloads
.iter()
.all(|payload| return payload.validation_status != "missing_from_matrix")
);
assert_eq!(
payloads
.iter()
.filter(|payload| return payload.mode == "network_simulation")
.count(),
5
);
}
#[test]
fn empty_stateful_reads_are_accepted() {
let parsed = super::parse_stateful_reads(" ");
assert!(parsed.is_ok());
let values = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
assert_eq!(std::option::Option::Some(error), std::option::Option::None);
return;
},
};
assert_eq!(values.len(), 0);
}
#[test]
fn metadata_frontend_contains_every_required_runtime_control() {
let source = std::include_str!("../frontend/demo_execution_metadata.html");
for id in [
"metadataProfileSelect",
"metadataDevnetScenarioSelect",
"metadataStepSelect",
"prepareMetadataScenarioStepButton",
"metadataExecutionLogOutput",
"metadataProfileHelp",
"simulateMetadataButton",
"submitMetadataButton",
] {
assert!(source.contains(format!("id=\"{id}\"").as_str()));
}
}
#[test]
fn programmable_and_collection_requirements_remain_explicit() {
let payloads = match crate::demo_execution_metadata_scenarios() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
assert_eq!(std::option::Option::Some(error), std::option::Option::None);
return;
},
};
assert!(payloads.iter().any(|payload| return payload.requires_collection));
assert!(payloads.iter().any(|payload| return payload.requires_programmable_rules));
fn metadata_frontend_listens_to_the_metadata_specific_progress_event() {
let source = std::include_str!("../frontend/ts/demo_execution_metadata.ts");
assert!(source.contains("demo-execution-metadata-progress"));
assert!(!source.contains(
"listen<DemoExecutionMetadataProgressPayload>(\"demo-execution-solana-core-progress\""
));
}
}

View File

@@ -0,0 +1,852 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_metaplex_token_metadata.rs
// version: 1
//! Desktop adapters for Metaplex Token Metadata execution demo scenarios.
/// UI-safe Metaplex scenario row.
#[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_metadata/DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload {
/// Stable scenario identifier.
pub id: std::string::String,
/// Operator-visible scenario label.
pub label: std::string::String,
/// Stable asset-family code.
pub asset_family: std::string::String,
/// Stable fixture graph code.
pub fixture_kind: std::string::String,
/// Required initial lifecycle state.
pub initial_state: std::string::String,
/// Expected terminal lifecycle state.
pub resulting_state: std::string::String,
/// Stable execution-mode code.
pub mode: std::string::String,
/// Ordered operation codes.
pub operation_codes: std::vec::Vec<std::string::String>,
/// Whether a collection account is required.
pub requires_collection: bool,
/// Whether programmable authorization rules are required.
pub requires_programmable_rules: bool,
/// Whether postconditions are mandatory.
pub requires_postcondition: bool,
/// Current validation status from the canonical matrix.
pub validation_status: std::string::String,
/// Required evidence kinds.
pub required_evidence: std::vec::Vec<std::string::String>,
/// Canonical account projections expected when materialization is enabled.
pub materialization_targets: std::vec::Vec<std::string::String>,
}
/// UI-safe result of one prepared Metaplex Create fixture.
#[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_metadata/DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload {
/// Fixture mint address.
pub mint: std::string::String,
/// Canonical metadata PDA.
pub metadata: std::string::String,
/// Canonical master-edition PDA.
pub master_edition: std::string::String,
/// Wallet authority.
pub authority: std::string::String,
/// Generated typed Create operation JSON.
pub operation_json: std::string::String,
/// Whether the mint account was newly created.
pub mint_created: bool,
}
/// UI-safe prepared step for one coherent Metaplex Devnet 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_metadata/DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload {
/// Stable scenario identifier.
pub scenario_id: std::string::String,
/// Zero-based step index.
pub step_index: usize,
/// Stable operation name accepted by the executor JSON contract.
pub operation: std::string::String,
/// Operator-visible step label.
pub label: std::string::String,
/// Prepared typed operation JSON.
pub operation_json: std::string::String,
/// Automatically generated bounded preflight reads.
pub preflight_reads_json: std::string::String,
/// Automatically generated bounded postcondition reads.
pub postcondition_reads_json: std::string::String,
/// Whether this step can be simulated immediately.
pub simulation_ready: bool,
/// Whether this step can be submitted immediately.
pub submission_ready: bool,
/// Fixture state used to prepare the step.
pub fixture_state: std::string::String,
}
/// Request for one real Devnet Metaplex execution from the desktop panel.
#[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_metadata/DemoExecutionMetadataMetaplexTokenMetadataRequest.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataRequest {
/// Devnet profile selected by the operator.
pub profile_name: std::string::String,
/// Stable execution identifier shown in diagnostics.
pub intent_id: std::string::String,
/// Serialized typed current Metaplex operation.
pub operation_json: std::string::String,
/// Serialized bounded preflight read requests.
pub preflight_reads_json: std::string::String,
/// Serialized bounded postcondition read requests.
pub postcondition_reads_json: std::string::String,
/// Whether signing and submission are explicitly requested.
pub submit: bool,
/// Explicit operator confirmation for submission.
pub operator_confirmed: bool,
/// Materializes confirmed Metaplex account snapshots after submission.
pub materialize_after_confirmation: bool,
}
/// UI-safe result of one real Devnet Metaplex execution.
#[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_metadata/DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload {
/// Selected profile name.
pub profile_name: std::string::String,
/// Exact cluster code.
pub cluster: std::string::String,
/// Genesis hash observed from RPC.
pub genesis_hash: std::string::String,
/// Wallet public key used by the runner.
pub wallet_public_key: std::string::String,
/// Wallet balance before execution.
pub balance_lamports: std::string::String,
/// Whether the real RPC simulation succeeded.
pub simulation_success: bool,
/// Optional submitted signature.
pub transaction_signature: std::option::Option<std::string::String>,
/// Optional terminal confirmation status.
pub confirmation_status: std::option::Option<std::string::String>,
/// Exact prepared plan as formatted JSON.
pub plan_json: std::string::String,
/// Stateful preflight as formatted JSON.
pub preflight_json: std::string::String,
/// Real RPC simulation as formatted JSON.
pub simulation_json: std::string::String,
/// Before/after state and readiness diagnostics as formatted JSON.
pub evidence_json: std::string::String,
/// Whether post-confirmation account materialization was requested.
pub materialization_requested: bool,
/// Number of canonical post-confirmation snapshots materialized.
pub materialized_snapshot_count: usize,
}
/// Executes one real Devnet Metaplex simulation or explicitly confirmed submission.
pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataMetaplexTokenMetadataRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload,
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::DemoExecutionRunGuard {
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 mut execution_request = match kb_pipeline_demo_scenarios::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json(
request.intent_id,
request.operation_json.as_str(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
execution_request.preflight_reads =
match parse_stateful_reads(request.preflight_reads_json.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
execution_request.postcondition_reads =
match parse_stateful_reads(request.postcondition_reads_json.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
execution_request.submit = request.submit;
execution_request.operator_confirmed = request.operator_confirmed;
execution_request.materialize_after_confirmation = request.materialize_after_confirmation;
if let std::result::Result::Err(error) = execution_request.validate() {
return std::result::Result::Err(error.to_string());
}
let observer = crate::DemoExecutionMetadataObserver {
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_metaplex_token_metadata(
&http_pool,
&profile,
workspace_root.as_path(),
&execution_request,
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status =
summary.confirmation.as_ref().map(|value| return format!("{:?}", value.status));
let evidence_json = crate::pretty_json(&serde_json::json!({
"before": &summary.before,
"readiness": &summary.readiness,
"sendResult": &summary.send_result,
"confirmation": &summary.confirmation,
"after": &summary.after,
"materializationRequested": summary.materialization_requested,
"materializedSnapshots": &summary.materialized_snapshots
}));
return std::result::Result::Ok(
crate::DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload {
profile_name: summary.profile_name,
cluster: format!("{:?}", summary.cluster).to_lowercase(),
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
balance_lamports: summary.balance_lamports.to_string(),
simulation_success: summary.simulation.success,
transaction_signature,
confirmation_status,
plan_json: crate::pretty_json(&summary.plan),
preflight_json: crate::pretty_json(&summary.stateful_preflight),
simulation_json: crate::pretty_json(&summary.simulation),
evidence_json,
materialization_requested: summary.materialization_requested,
materialized_snapshot_count: summary.materialized_snapshots.len(),
},
);
}
fn parse_stateful_reads(
value: &str,
) -> std::result::Result<
std::vec::Vec<kb_pipeline::MetaplexTokenMetadataStatefulReadRequest>,
std::string::String,
> {
if value.trim().is_empty() {
return std::result::Result::Ok(std::vec::Vec::new());
}
return match serde_json::from_str(value) {
std::result::Result::Ok(parsed) => std::result::Result::Ok(parsed),
std::result::Result::Err(error) => {
std::result::Result::Err(format!("invalid Metaplex stateful reads JSON: {error}"))
},
};
}
/// Creates or reuses the Devnet mint fixture used by the named Create scenario.
pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload,
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 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 workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::prepare_metaplex_create_fixture(
&http_pool,
&profile,
workspace_root.as_path(),
&kb_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir),
)
.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::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload {
mint: summary.mint,
metadata: summary.metadata,
master_edition: summary.master_edition,
authority: summary.authority,
operation_json: summary.operation_json,
mint_created: summary.mint_created,
},
);
}
fn adapt_create_operation_json(
operation_json: &str,
asset_family: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily,
) -> std::result::Result<std::string::String, std::string::String> {
let mut value: serde_json::Value = match serde_json::from_str(operation_json) {
std::result::Result::Ok(parsed) => parsed,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"invalid generated Metaplex Create JSON: {error}"
));
},
};
if matches!(
asset_family,
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft
| kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible
) {
value["master_edition"] = serde_json::Value::Null;
}
let create = match value
.get_mut("create_args")
.and_then(|args| return args.get_mut("V1"))
.and_then(serde_json::Value::as_object_mut)
{
std::option::Option::Some(found) => found,
std::option::Option::None => {
return std::result::Result::Err(
"generated Metaplex Create JSON has no create_args.V1 object".to_string(),
);
},
};
match asset_family {
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet NFT"));
create.insert("symbol".to_string(), serde_json::json!("KHNFT"));
create.insert("token_standard".to_string(), serde_json::json!("NonFungible"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::Value::Null);
create.insert("print_supply".to_string(), serde_json::json!("Zero"));
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet SFT"));
create.insert("symbol".to_string(), serde_json::json!("KHSFT"));
create.insert("token_standard".to_string(), serde_json::json!("FungibleAsset"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::json!(0));
create.insert("print_supply".to_string(), serde_json::Value::Null);
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Fungible"));
create.insert("symbol".to_string(), serde_json::json!("KHFUN"));
create.insert("token_standard".to_string(), serde_json::json!("Fungible"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::json!(0));
create.insert("print_supply".to_string(), serde_json::Value::Null);
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet Collection"));
create.insert("symbol".to_string(), serde_json::json!("KHCOL"));
create.insert("token_standard".to_string(), serde_json::json!("NonFungible"));
create.insert("collection_details".to_string(), serde_json::json!({"V1": {"size": 0}}));
create.insert("decimals".to_string(), serde_json::Value::Null);
create.insert("print_supply".to_string(), serde_json::json!("Zero"));
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => {
create.insert("name".to_string(), serde_json::json!("Khadhroony Devnet pNFT"));
create.insert("symbol".to_string(), serde_json::json!("KHPNFT"));
create
.insert("token_standard".to_string(), serde_json::json!("ProgrammableNonFungible"));
create.insert("collection_details".to_string(), serde_json::Value::Null);
create.insert("rule_set".to_string(), serde_json::Value::Null);
create.insert("decimals".to_string(), serde_json::Value::Null);
create.insert("print_supply".to_string(), serde_json::json!("Zero"));
},
}
return std::result::Result::Ok(crate::pretty_json(&value));
}
/// Prepares one coherent scenario step and all of its stateful reads.
pub(crate) async fn demo_execution_metadata_metaplex_token_metadata_prepare_step(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
scenario_id: std::string::String,
step_index: usize,
previous_operation_json: std::option::Option<std::string::String>,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload,
std::string::String,
> {
let scenario = kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios()
.into_iter()
.find(|candidate| return candidate.id == scenario_id);
let scenario = match scenario {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(format!(
"unknown Metaplex Devnet scenario `{scenario_id}`"
));
},
};
let operation_code = match scenario.operation_codes.get(step_index) {
std::option::Option::Some(value) => value.clone(),
std::option::Option::None => {
return std::result::Result::Err(format!(
"scenario `{}` has no step at index {step_index}",
scenario.id
));
},
};
let operation_suffix = match operation_code.rsplit('.').next() {
std::option::Option::Some(value) => value,
std::option::Option::None => operation_code.as_str(),
};
let operation = if operation_suffix == "update" {
"update_as_update_authority_v2".to_string()
} else {
operation_suffix.to_string()
};
if operation != "create" && operation != "update_as_update_authority_v2" {
return std::result::Result::Err(format!(
"scenario step `{operation}` is declared but its Devnet fixture builder is not implemented yet"
));
}
let (fixture_mint, fixture_metadata, fixture_master_edition, operation_json, fixture_state) =
if operation == "create" {
let fixture =
match crate::demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture(
state,
profile_name,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let operation_json = match adapt_create_operation_json(
fixture.operation_json.as_str(),
scenario.asset_family,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
(
fixture.mint,
fixture.metadata,
fixture.master_edition,
operation_json,
if fixture.mint_created {
"mint_prepared".to_string()
} else {
"mint_reused".to_string()
},
)
} else {
let previous = match previous_operation_json {
std::option::Option::Some(value) if !value.trim().is_empty() => value,
_ => {
return std::result::Result::Err(
"prepare and submit the previous NFT lifecycle step before continuing"
.to_string(),
);
},
};
let previous_value: serde_json::Value = match serde_json::from_str(previous.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"invalid previous scenario operation JSON: {error}"
));
},
};
let string_field = |name: &str| -> std::result::Result<
std::string::String,
std::string::String,
> {
return match previous_value.get(name).and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => std::result::Result::Ok(value.to_string()),
std::option::Option::None => std::result::Result::Err(format!(
"previous scenario step does not contain `{name}`"
)),
};
};
let mint = match string_field("mint") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let metadata = match string_field("metadata") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let authority = match string_field("authority") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let master_edition =
match previous_value.get("master_edition").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => value.to_string(),
std::option::Option::None => std::string::String::new(),
};
let immutable_transition = scenario.asset_family
== kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft
&& step_index == 2;
let value = serde_json::json!({
"operation": "update_as_update_authority_v2",
"authority": authority.clone(),
"mint": mint.clone(),
"metadata": metadata.clone(),
"edition": null,
"token": null,
"authorization_rules_program": null,
"authorization_rules": null,
"update_args": {
"AsUpdateAuthorityV2": {
"new_update_authority": null,
"data": null,
"primary_sale_happened": if immutable_transition {
serde_json::Value::Null
} else {
serde_json::json!(true)
},
"is_mutable": if immutable_transition {
serde_json::json!(false)
} else {
serde_json::Value::Null
},
"collection": "None",
"collection_details": "None",
"uses": "None",
"rule_set": "None",
"token_standard": null,
"authorization_data": null
}
}
});
(
mint,
metadata,
master_edition,
crate::pretty_json(&value),
if immutable_transition {
"metadata_immutable".to_string()
} else {
"metadata_created".to_string()
},
)
};
let metadata_read = serde_json::json!({
"query_role": "http_queries",
"account": fixture_metadata.clone(),
"kind": { "kind": "metadata" },
"min_context_slot": null,
"max_data_bytes": 65536
});
let edition_read = serde_json::json!({
"query_role": "http_queries",
"account": fixture_master_edition.clone(),
"kind": { "kind": "edition", "mint": fixture_mint.clone() },
"min_context_slot": null,
"max_data_bytes": 65536
});
let preflight_reads = if operation == "create" {
serde_json::json!([])
} else {
serde_json::json!([metadata_read.clone()])
};
let create_reads_edition = matches!(
scenario.asset_family,
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft
| kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection
| kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft
);
let postcondition_reads = if operation == "create" && create_reads_edition {
serde_json::json!([metadata_read, edition_read])
} else {
serde_json::json!([metadata_read])
};
return std::result::Result::Ok(
crate::DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload {
scenario_id: scenario.id,
step_index,
operation: operation.clone(),
label: if scenario.asset_family
== kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft
&& step_index == 2
{
"Étape 3 — rendre les metadata immutables".to_string()
} else {
format!("Étape {}{}", step_index + 1, operation)
},
operation_json,
preflight_reads_json: crate::pretty_json(&preflight_reads),
postcondition_reads_json: crate::pretty_json(&postcondition_reads),
simulation_ready: true,
submission_ready: true,
fixture_state,
},
);
}
/// Returns current operation names accepted by automatic Devnet campaigns.
pub(crate) fn demo_execution_metadata_metaplex_token_metadata_current_operations()
-> std::vec::Vec<std::string::String> {
return kb_pipeline_demo_scenarios::metaplex_token_metadata_current_operation_names()
.iter()
.map(|value| return (*value).to_string())
.collect();
}
/// Returns one generated typed JSON template for a named current operation.
pub(crate) fn demo_execution_metadata_metaplex_token_metadata_operation_template(
operation: std::string::String,
) -> std::result::Result<std::string::String, std::string::String> {
return match kb_pipeline_demo_scenarios::metaplex_token_metadata_current_operation_json_template(
operation.as_str(),
) {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
/// Returns the canonical Metaplex scenario inventory for the desktop panel.
pub(crate) fn demo_execution_metadata_metaplex_token_metadata_scenarios() -> std::result::Result<
std::vec::Vec<crate::DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload>,
std::string::String,
> {
let matrix = match kb_pipeline_demo_scenarios::load_metaplex_token_metadata_validation_matrix()
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let statuses = matrix
.scenarios
.into_iter()
.map(|scenario| return (scenario.id, (scenario.status, scenario.required_evidence)))
.collect::<std::collections::BTreeMap<
std::string::String,
(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus,
std::vec::Vec<std::string::String>,
),
>>();
let mut payloads = std::vec::Vec::new();
for scenario in kb_pipeline_demo_scenarios::metaplex_token_metadata_synthetic_scenarios()
.into_iter()
.chain(kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios())
{
let status = statuses.get(scenario.id.as_str());
let validation_status = match status {
std::option::Option::Some((value, _)) => validation_status_code(*value),
std::option::Option::None => "not_run".to_string(),
};
let required_evidence = match status {
std::option::Option::Some((_, evidence)) => evidence.clone(),
std::option::Option::None => std::vec::Vec::new(),
};
payloads.push(crate::DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload {
id: scenario.id,
label: scenario.label,
asset_family: asset_family_code(scenario.asset_family),
fixture_kind: fixture_kind_code(scenario.fixture_kind),
initial_state: fixture_state_code(scenario.initial_state),
resulting_state: fixture_state_code(scenario.resulting_state),
mode: scenario_mode_code(scenario.mode),
operation_codes: scenario.operation_codes,
requires_collection: scenario.requires_collection,
requires_programmable_rules: scenario.requires_programmable_rules,
requires_postcondition: scenario.requires_postcondition,
validation_status,
required_evidence,
materialization_targets: scenario.materialization_targets,
});
}
return std::result::Result::Ok(payloads);
}
fn asset_family_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily,
) -> std::string::String {
return match value {
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft => "nft".to_string(),
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft => "sft".to_string(),
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible => {
"fungible".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection => {
"collection".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft => {
"programmable_nft".to_string()
},
};
}
fn fixture_kind_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataFixtureKind,
) -> std::string::String {
return format!("{value:?}").to_lowercase();
}
fn fixture_state_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataFixtureState,
) -> std::string::String {
return format!("{value:?}").to_lowercase();
}
fn scenario_mode_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode,
) -> std::string::String {
return match value {
kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::Synthetic => {
"synthetic".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSimulation => {
"network_simulation".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataScenarioMode::NetworkSubmission => {
"network_submission".to_string()
},
};
}
fn validation_status_code(
value: kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus,
) -> std::string::String {
return match value {
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::NotRun => {
"not_run".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::SyntheticValidated => {
"synthetic_validated".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Simulated => {
"simulated".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Submitted => {
"submitted".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Confirmed => {
"confirmed".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Unavailable => {
"unavailable".to_string()
},
kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationStatus::Failed => {
"failed".to_string()
},
};
}
#[cfg(test)]
mod tests {
#[test]
fn desktop_inventory_matches_reusable_scenarios_and_matrix() {
let payloads = match crate::demo_execution_metadata_metaplex_token_metadata_scenarios() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
assert_eq!(std::option::Option::Some(error), std::option::Option::None);
return;
},
};
let expected_count =
kb_pipeline_demo_scenarios::metaplex_token_metadata_synthetic_scenarios().len()
+ kb_pipeline_demo_scenarios::metaplex_token_metadata_devnet_scenarios().len();
assert_eq!(payloads.len(), expected_count);
assert!(payloads.iter().all(|payload| return !payload.operation_codes.is_empty()));
assert!(payloads.iter().all(|payload| return payload.requires_postcondition));
assert!(
payloads
.iter()
.all(|payload| return payload.validation_status != "missing_from_matrix")
);
assert_eq!(
payloads
.iter()
.filter(|payload| return payload.mode == "network_simulation")
.count(),
5
);
}
#[test]
fn empty_stateful_reads_are_accepted() {
let parsed = super::parse_stateful_reads(" ");
assert!(parsed.is_ok());
let values = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
assert_eq!(std::option::Option::Some(error), std::option::Option::None);
return;
},
};
assert_eq!(values.len(), 0);
}
#[test]
fn metadata_frontend_contains_every_required_runtime_control() {
let source = std::include_str!("../frontend/demo_execution_metadata.html");
for id in [
"metadataProfileSelect",
"metadataDevnetScenarioSelect",
"metadataStepSelect",
"prepareMetadataScenarioStepButton",
"metadataExecutionLogOutput",
"metadataProfileHelp",
"simulateMetadataButton",
"submitMetadataButton",
] {
assert!(source.contains(format!("id=\"{id}\"").as_str()));
}
}
#[test]
fn programmable_and_collection_requirements_remain_explicit() {
let payloads = match crate::demo_execution_metadata_metaplex_token_metadata_scenarios() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
assert_eq!(std::option::Option::Some(error), std::option::Option::None);
return;
},
};
assert!(payloads.iter().any(|payload| return payload.requires_collection));
assert!(payloads.iter().any(|payload| return payload.requires_programmable_rules));
}
}

View File

@@ -0,0 +1,338 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_solana_program.rs
// version: 2
//! Thin desktop adapter for complete Solana Program Metadata Devnet campaigns.
/// UI-safe step in one Solana Program Metadata Devnet journey.
#[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_metadata/DemoExecutionMetadataSolanaProgramScenarioStepPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataSolanaProgramScenarioStepPayload {
/// Stable scenario-step identifier.
pub id: std::string::String,
/// Operator-visible label.
pub label: std::string::String,
/// Stable executor operation code.
pub operation_code: std::string::String,
/// Expected state before execution.
pub initial_state: std::string::String,
/// Expected state after confirmed execution.
pub resulting_state: std::string::String,
/// Whether explicit approval is required.
pub requires_explicit_approval: bool,
/// Evidence kinds required by the validation contract.
pub required_evidence: std::vec::Vec<std::string::String>,
}
/// UI-safe Solana Program Metadata Devnet journey.
#[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_metadata/DemoExecutionMetadataSolanaProgramScenarioPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataSolanaProgramScenarioPayload {
/// Stable scenario identifier.
pub id: std::string::String,
/// Operator-visible label.
pub label: std::string::String,
/// Fixture account selected by the journey.
pub fixture_account: std::string::String,
/// Ordered steps.
pub steps: std::vec::Vec<crate::DemoExecutionMetadataSolanaProgramScenarioStepPayload>,
}
/// Request for one complete confirmed Solana Program Metadata Devnet campaign.
#[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_metadata/DemoExecutionMetadataSolanaProgramCampaignRequest.ts"
)]
pub(crate) struct DemoExecutionMetadataSolanaProgramCampaignRequest {
/// Selected Devnet profile.
pub profile_name: std::string::String,
/// Explicit operator confirmation for prefunding and all nine mutations.
pub operator_confirmed: bool,
}
/// UI-safe result of one complete Solana Program Metadata Devnet campaign.
#[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_metadata/DemoExecutionMetadataSolanaProgramCampaignSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataSolanaProgramCampaignSummaryPayload {
/// Profile used by the campaign.
pub profile_name: std::string::String,
/// Profile-wallet authority.
pub authority: std::string::String,
/// Executable program described by both non-canonical PDAs.
pub described_program: std::string::String,
/// Fresh Buffer PDA.
pub buffer: std::string::String,
/// Confirmed Buffer prefunding signature.
pub buffer_prefund_signature: std::string::String,
/// Fresh Metadata PDA.
pub metadata: std::string::String,
/// Confirmed Metadata prefunding signature.
pub metadata_prefund_signature: std::string::String,
/// Total number of stable operations executed.
pub step_count: usize,
/// Number of operations reaching confirmed or finalized state.
pub confirmed_step_count: usize,
/// Total number of canonical account snapshots materialized.
pub materialized_snapshot_count: usize,
/// Whether all nine steps completed with required evidence.
pub completed: bool,
/// Complete fixture as formatted JSON.
pub fixture_json: std::string::String,
/// Ordered execution evidence as formatted JSON.
pub steps_json: std::string::String,
}
/// Returns the two canonical Solana Program Metadata Devnet journeys.
pub(crate) fn demo_execution_metadata_solana_program_scenarios()
-> std::vec::Vec<crate::DemoExecutionMetadataSolanaProgramScenarioPayload> {
return kb_pipeline_demo_scenarios::solana_program_metadata_devnet_scenarios()
.into_iter()
.map(|scenario| {
return crate::DemoExecutionMetadataSolanaProgramScenarioPayload {
id: scenario.id,
label: scenario.label,
fixture_account: scenario.fixture_account,
steps: scenario
.steps
.into_iter()
.map(|step| {
return crate::DemoExecutionMetadataSolanaProgramScenarioStepPayload {
id: step.id,
label: step.label,
operation_code: step.operation_code,
initial_state: fixture_state_code(step.initial_state),
resulting_state: fixture_state_code(step.resulting_state),
requires_explicit_approval: step.requires_explicit_approval,
required_evidence: step.required_evidence,
};
})
.collect(),
};
})
.collect();
}
/// Executes the complete eleven-transaction Solana Program Metadata Devnet campaign.
pub(crate) async fn demo_execution_metadata_solana_program_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataSolanaProgramCampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataSolanaProgramCampaignSummaryPayload,
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::DemoExecutionRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
if !request.operator_confirmed {
return std::result::Result::Err(
"Solana Program Metadata campaign requires explicit operator confirmation".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 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 mut options =
kb_pipeline_demo_scenarios::SolanaProgramMetadataFixturePreparationOptions::new();
options.operator_confirmed = true;
let observer = crate::DemoExecutionMetadataObserver {
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_solana_program_metadata_campaign(
&http_pool,
&profile,
workspace_root.as_path(),
&options,
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(campaign_summary_payload(summary));
}
fn campaign_summary_payload(
summary: kb_pipeline_demo_scenarios::DevnetSolanaProgramMetadataCampaignSummary,
) -> crate::DemoExecutionMetadataSolanaProgramCampaignSummaryPayload {
let mut confirmed_step_count = 0_usize;
let mut materialized_snapshot_count = 0_usize;
let mut step_values = std::vec::Vec::with_capacity(summary.steps.len());
for step in &summary.steps {
let confirmation_status = step
.execution
.confirmation
.as_ref()
.map(|value| return format!("{:?}", value.status).to_lowercase());
let confirmed = match step.execution.confirmation.as_ref() {
std::option::Option::Some(value) => matches!(
value.status,
kb_lib::ExApiExecutionConfirmationStatus::Confirmed
| kb_lib::ExApiExecutionConfirmationStatus::Finalized
),
std::option::Option::None => false,
};
if confirmed {
confirmed_step_count = confirmed_step_count.saturating_add(1);
}
materialized_snapshot_count =
materialized_snapshot_count.saturating_add(step.execution.materialized_snapshots.len());
let operation_code = summary
.fixture
.prepared_steps
.iter()
.find(|prepared| return prepared.step_id == step.step_id)
.map(|prepared| return prepared.operation.operation_code().to_string());
step_values.push(serde_json::json!({
"scenarioId": &step.scenario_id,
"stepId": &step.step_id,
"stepIndex": step.step_index,
"operationCode": operation_code,
"simulationSuccess": step.execution.simulation.success,
"transactionSignature": step.execution.send_result.as_ref().map(|value| return value.signature.0.clone()),
"confirmationStatus": confirmation_status,
"before": &step.execution.before,
"statefulPreflight": &step.execution.stateful_preflight,
"plan": &step.execution.plan,
"simulation": &step.execution.simulation,
"readiness": &step.execution.readiness,
"after": &step.execution.after,
"postExecution": &step.execution.post_execution,
"materializedSnapshots": &step.execution.materialized_snapshots
}));
}
let expected_step_count = kb_lib::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES.len();
let completed = summary.steps.len() == expected_step_count
&& confirmed_step_count == expected_step_count
&& summary.steps.iter().all(|step| {
let close_step = match summary
.fixture
.prepared_steps
.iter()
.find(|prepared| return prepared.step_id == step.step_id)
{
std::option::Option::Some(prepared) => {
prepared.operation.operation_code() == kb_lib::EX_METADATA_SPM_CLOSE_OPERATION
},
std::option::Option::None => false,
};
return step.execution.post_execution.is_some()
&& (!step.execution.materialized_snapshots.is_empty() || close_step);
});
let fixture_json = crate::pretty_json(&serde_json::json!({
"profileName": &summary.fixture.profile_name,
"authority": &summary.fixture.authority,
"describedProgram": &summary.fixture.described_program,
"bufferSeed": &summary.fixture.buffer_seed,
"buffer": &summary.fixture.buffer,
"bufferPrefundLamports": summary.fixture.buffer_prefund_lamports.to_string(),
"bufferPrefundSignature": &summary.fixture.buffer_prefund_signature,
"metadataSeed": &summary.fixture.metadata_seed,
"metadata": &summary.fixture.metadata,
"metadataPrefundLamports": summary.fixture.metadata_prefund_lamports.to_string(),
"metadataPrefundSignature": &summary.fixture.metadata_prefund_signature,
"preparedSteps": &summary.fixture.prepared_steps
}));
return crate::DemoExecutionMetadataSolanaProgramCampaignSummaryPayload {
profile_name: summary.fixture.profile_name.clone(),
authority: summary.fixture.authority.clone(),
described_program: summary.fixture.described_program.clone(),
buffer: summary.fixture.buffer.clone(),
buffer_prefund_signature: summary.fixture.buffer_prefund_signature.clone(),
metadata: summary.fixture.metadata.clone(),
metadata_prefund_signature: summary.fixture.metadata_prefund_signature.clone(),
step_count: summary.steps.len(),
confirmed_step_count,
materialized_snapshot_count,
completed,
fixture_json,
steps_json: crate::pretty_json(&step_values),
};
}
fn fixture_state_code(
value: kb_pipeline_demo_scenarios::SolanaProgramMetadataFixtureState,
) -> std::string::String {
return match value {
kb_pipeline_demo_scenarios::SolanaProgramMetadataFixtureState::Prefunded => {
"prefunded".to_string()
},
kb_pipeline_demo_scenarios::SolanaProgramMetadataFixtureState::Buffer => {
"buffer".to_string()
},
kb_pipeline_demo_scenarios::SolanaProgramMetadataFixtureState::MutableMetadata => {
"mutable_metadata".to_string()
},
kb_pipeline_demo_scenarios::SolanaProgramMetadataFixtureState::ImmutableMetadata => {
"immutable_metadata".to_string()
},
kb_pipeline_demo_scenarios::SolanaProgramMetadataFixtureState::Closed => {
"closed".to_string()
},
};
}
#[cfg(test)]
mod tests {
#[test]
fn desktop_inventory_matches_the_two_reusable_journeys() {
let payloads = crate::demo_execution_metadata_solana_program_scenarios();
assert_eq!(payloads.len(), 2);
assert_eq!(
payloads.iter().map(|scenario| return scenario.steps.len()).sum::<usize>(),
kb_lib::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES.len()
);
assert!(payloads.iter().flat_map(|scenario| return scenario.steps.iter()).all(|step| {
return !step.required_evidence.is_empty()
&& kb_lib::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES
.contains(&step.operation_code.as_str());
}));
}
#[test]
fn metadata_frontend_contains_solana_program_campaign_controls() {
let source = std::include_str!("../frontend/demo_execution_metadata.html");
for id in [
"solanaProgramMetadataScenarioSelect",
"solanaProgramMetadataScenarioOutput",
"solanaProgramMetadataOperatorConfirmed",
"executeSolanaProgramMetadataCampaignButton",
] {
assert!(source.contains(format!("id=\"{id}\"").as_str()));
}
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
// version: 12
// version: 13
//! Tauri adapter for bounded Solana Core execution on Devnet.
@@ -359,15 +359,6 @@ impl kb_pipeline_demo_scenarios::SolanaExecutionObserver
}
}
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 select_devnet_profile(
config: &kb_config::AppConfig,
profile_name: &str,
@@ -467,7 +458,7 @@ pub(crate) async fn demo_execution_solana_core_execute(
if acquire_result.is_err() {
return std::result::Result::Err("a Solana Core execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
let _run_guard = crate::DemoExecutionRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
@@ -552,7 +543,7 @@ pub(crate) async fn demo_execution_spl_memo_execute(
if acquire_result.is_err() {
return std::result::Result::Err("a Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
let _run_guard = crate::DemoExecutionRunGuard {
running: state.demo_execution_solana_core_running(),
};
state

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_ata.rs
// version: 11
// version: 12
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
@@ -204,7 +204,7 @@ pub(crate) async fn demo_execution_spl_ata_execute(
if acquired.is_err() {
return std::result::Result::Err("a Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
let _run_guard = crate::DemoExecutionRunGuard {
running: state.demo_execution_solana_core_running(),
};
state

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token.rs
// version: 8
// version: 9
//! Thin Tauri adapter for classic SPL Token execution and materialized journals.
@@ -168,7 +168,7 @@ pub(crate) async fn demo_execution_spl_token_execute(
if acquire_result.is_err() {
return std::result::Result::Err("a Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
let _run_guard = crate::DemoExecutionRunGuard {
running: state.demo_execution_solana_core_running(),
};
state

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token_2022.rs
// version: 11
// version: 12
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
@@ -146,7 +146,7 @@ pub(crate) async fn demo_execution_spl_token_2022_execute(
if acquire_result.is_err() {
return std::result::Result::Err("a Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
let _run_guard = crate::DemoExecutionRunGuard {
running: state.demo_execution_solana_core_running(),
};
state

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/lib.rs
// version: 29
// version: 31
//! Tauri desktop demo application for `khadhroony-bot3`.
@@ -15,6 +15,8 @@ mod demo_core_extraction;
mod demo_decode_replay;
mod demo_devnet_common;
mod demo_execution_metadata;
mod demo_execution_metadata_metaplex_token_metadata;
mod demo_execution_metadata_solana_program;
mod demo_execution_solana_core;
mod demo_execution_spl;
mod demo_http;
@@ -123,34 +125,52 @@ pub(crate) use self::demo_decode_replay::demo_decode_replay_execute;
pub(crate) use self::demo_decode_replay::demo_decode_replay_options;
/// Readiness report for one Devnet profile PostgreSQL store.
pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayload;
/// Restores the shared single-execution state flag when a Devnet run finishes.
pub(crate) use self::demo_devnet_common::DemoExecutionRunGuard;
/// Converts the reusable scenario readiness report to the desktop TS-RS payload.
pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload;
/// Serializes a diagnostic value to indented JSON without panicking.
pub(crate) use self::demo_devnet_common::pretty_json;
/// UI-safe result of one prepared Metaplex Create fixture.
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataCreateFixturePayload;
/// Prepared coherent Metaplex scenario step.
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataPreparedStepPayload;
/// Request for one real Devnet Metaplex execution from the desktop panel.
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataRequest;
/// UI-safe Metaplex scenario row.
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataScenarioPayload;
/// UI-safe result of one real Devnet Metaplex execution.
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataSummaryPayload;
/// Returns current Metaplex operation names accepted by Devnet campaigns.
pub(crate) use self::demo_execution_metadata::demo_execution_metadata_current_operations;
/// Executes one real Devnet Metaplex simulation or submission.
pub(crate) use self::demo_execution_metadata::demo_execution_metadata_execute;
/// Returns one generated operation JSON template.
pub(crate) use self::demo_execution_metadata::demo_execution_metadata_operation_template;
/// Returns Devnet profile options dedicated to the Metadata execution panel.
/// Bridges Metadata execution progress to the Metadata desktop window.
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataObserver;
/// Progress event emitted by Metadata execution workflows.
pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataProgressPayload;
/// Returns Devnet profile options shared by the Metadata execution panel.
pub(crate) use self::demo_execution_metadata::demo_execution_metadata_options;
/// Creates or reuses the Devnet mint fixture used by the Create scenario.
pub(crate) use self::demo_execution_metadata::demo_execution_metadata_prepare_create_fixture;
/// Prepares one coherent scenario step and its automatic stateful reads.
pub(crate) use self::demo_execution_metadata::demo_execution_metadata_prepare_step;
/// Returns the canonical Metaplex scenario inventory for the desktop panel.
pub(crate) use self::demo_execution_metadata::demo_execution_metadata_scenarios;
/// UI-safe result of one prepared Metaplex Token Metadata Create fixture.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload;
/// Prepared coherent Metaplex Token Metadata scenario step.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload;
/// Request for one real Devnet Metaplex Token Metadata execution.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataRequest;
/// UI-safe Metaplex Token Metadata scenario row.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload;
/// UI-safe result of one real Devnet Metaplex Token Metadata execution.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload;
/// Returns current Metaplex Token Metadata operation names accepted by Devnet campaigns.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_token_metadata_current_operations;
/// Executes one real Devnet Metaplex Token Metadata simulation or submission.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_token_metadata_execute;
/// Returns one generated Metaplex Token Metadata operation JSON template.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_token_metadata_operation_template;
/// Creates or reuses the Devnet mint fixture used by a Metaplex Token Metadata Create scenario.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture;
/// Prepares one coherent Metaplex Token Metadata scenario step and its stateful reads.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_token_metadata_prepare_step;
/// Returns the canonical Metaplex Token Metadata scenario inventory.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_token_metadata_scenarios;
/// Request for one complete Solana Program Metadata Devnet campaign.
pub(crate) use self::demo_execution_metadata_solana_program::DemoExecutionMetadataSolanaProgramCampaignRequest;
/// UI-safe result of one complete Solana Program Metadata Devnet campaign.
pub(crate) use self::demo_execution_metadata_solana_program::DemoExecutionMetadataSolanaProgramCampaignSummaryPayload;
/// UI-safe Solana Program Metadata Devnet journey.
pub(crate) use self::demo_execution_metadata_solana_program::DemoExecutionMetadataSolanaProgramScenarioPayload;
/// UI-safe step in one Solana Program Metadata Devnet journey.
pub(crate) use self::demo_execution_metadata_solana_program::DemoExecutionMetadataSolanaProgramScenarioStepPayload;
/// Executes the complete Solana Program Metadata Devnet campaign.
pub(crate) use self::demo_execution_metadata_solana_program::demo_execution_metadata_solana_program_execute_campaign;
/// Returns the two canonical Solana Program Metadata Devnet journeys.
pub(crate) use self::demo_execution_metadata_solana_program::demo_execution_metadata_solana_program_scenarios;
/// 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.
@@ -167,8 +187,6 @@ pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProfileO
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProgressPayload;
/// Request sent by the execution demo.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRequest;
/// Restores the single-execution state flag when a run finishes.
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;
/// Maps a confirmation status to its stable UI code.

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/tauri.rs
// version: 30
// version: 32
//! Tauri runtime assembly and private command wrappers.
@@ -89,13 +89,15 @@ pub fn run() -> kb_core::Result<()> {
demo_execution_spl_token_2022_execute,
demo_spl_token_2022_fixture,
open_demo_execution_metadata_window,
demo_execution_metadata_scenarios,
demo_execution_metadata_metaplex_token_metadata_scenarios,
demo_execution_metadata_options,
demo_execution_metadata_current_operations,
demo_execution_metadata_operation_template,
demo_execution_metadata_prepare_create_fixture,
demo_execution_metadata_prepare_step,
demo_execution_metadata_execute,
demo_execution_metadata_metaplex_token_metadata_current_operations,
demo_execution_metadata_metaplex_token_metadata_operation_template,
demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture,
demo_execution_metadata_metaplex_token_metadata_prepare_step,
demo_execution_metadata_metaplex_token_metadata_execute,
demo_execution_metadata_solana_program_scenarios,
demo_execution_metadata_solana_program_execute_campaign,
]);
builder = builder.on_window_event(|window, event| {
if window.label() != "main" || !matches!(event, tauri::WindowEvent::Destroyed) {
@@ -629,42 +631,53 @@ fn demo_execution_metadata_options(
}
#[tauri::command]
fn demo_execution_metadata_scenarios() -> std::result::Result<
std::vec::Vec<crate::DemoExecutionMetadataScenarioPayload>,
fn demo_execution_metadata_metaplex_token_metadata_scenarios() -> std::result::Result<
std::vec::Vec<crate::DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload>,
std::string::String,
> {
return crate::demo_execution_metadata_scenarios();
return crate::demo_execution_metadata_metaplex_token_metadata_scenarios();
}
#[tauri::command]
fn demo_execution_metadata_current_operations() -> std::vec::Vec<std::string::String> {
return crate::demo_execution_metadata_current_operations();
fn demo_execution_metadata_metaplex_token_metadata_current_operations()
-> std::vec::Vec<std::string::String> {
return crate::demo_execution_metadata_metaplex_token_metadata_current_operations();
}
#[tauri::command]
fn demo_execution_metadata_operation_template(
fn demo_execution_metadata_metaplex_token_metadata_operation_template(
operation: std::string::String,
) -> std::result::Result<std::string::String, std::string::String> {
return crate::demo_execution_metadata_operation_template(operation);
return crate::demo_execution_metadata_metaplex_token_metadata_operation_template(operation);
}
#[tauri::command]
async fn demo_execution_metadata_prepare_create_fixture(
async fn demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
) -> std::result::Result<crate::DemoExecutionMetadataCreateFixturePayload, std::string::String> {
return crate::demo_execution_metadata_prepare_create_fixture(state, profile_name).await;
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexTokenMetadataCreateFixturePayload,
std::string::String,
> {
return crate::demo_execution_metadata_metaplex_token_metadata_prepare_create_fixture(
state,
profile_name,
)
.await;
}
#[tauri::command]
async fn demo_execution_metadata_prepare_step(
async fn demo_execution_metadata_metaplex_token_metadata_prepare_step(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
scenario_id: std::string::String,
step_index: usize,
previous_operation_json: std::option::Option<std::string::String>,
) -> std::result::Result<crate::DemoExecutionMetadataPreparedStepPayload, std::string::String> {
return crate::demo_execution_metadata_prepare_step(
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload,
std::string::String,
> {
return crate::demo_execution_metadata_metaplex_token_metadata_prepare_step(
state,
profile_name,
scenario_id,
@@ -675,12 +688,39 @@ async fn demo_execution_metadata_prepare_step(
}
#[tauri::command]
async fn demo_execution_metadata_execute(
async fn demo_execution_metadata_metaplex_token_metadata_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataRequest,
) -> std::result::Result<crate::DemoExecutionMetadataSummaryPayload, std::string::String> {
return crate::demo_execution_metadata_execute(app_handle, state, request).await;
request: crate::DemoExecutionMetadataMetaplexTokenMetadataRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload,
std::string::String,
> {
return crate::demo_execution_metadata_metaplex_token_metadata_execute(
app_handle, state, request,
)
.await;
}
#[tauri::command]
fn demo_execution_metadata_solana_program_scenarios()
-> std::vec::Vec<crate::DemoExecutionMetadataSolanaProgramScenarioPayload> {
return crate::demo_execution_metadata_solana_program_scenarios();
}
#[tauri::command]
async fn demo_execution_metadata_solana_program_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataSolanaProgramCampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataSolanaProgramCampaignSummaryPayload,
std::string::String,
> {
return crate::demo_execution_metadata_solana_program_execute_campaign(
app_handle, state, request,
)
.await;
}
#[tauri::command]