v0.4.8-pre.014

This commit is contained in:
2026-08-09 12:19:06 +02:00
parent 8b831f8692
commit aec7b4b76a
13 changed files with 1938 additions and 408 deletions

View File

@@ -1,7 +1,7 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_metaplex_token_metadata.rs
// version: 4
// version: 6
//! Desktop adapters for Metaplex Token Metadata execution demo scenarios.
//! Desktop adapters for generic and qualified Metaplex Token Metadata execution workflows.
/// UI-safe Metaplex scenario row.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)]
@@ -815,6 +815,876 @@ fn validation_status_code(
};
}
// Qualified Metaplex Devnet campaigns.
/// UI-safe descriptor for one qualified Metaplex 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/DemoExecutionMetadataMetaplexCampaignOptionPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexCampaignOptionPayload {
/// Stable campaign identifier accepted by the desktop executor.
pub id: std::string::String,
/// Operator-visible campaign label.
pub label: std::string::String,
/// Primary asset family exercised by the campaign.
pub asset_family: std::string::String,
/// Target operations qualified by the campaign after any fixture setup.
pub operation_names: std::vec::Vec<std::string::String>,
/// Stable qualification code shown by the desktop.
pub qualification: std::string::String,
/// Human-readable fixture/setup contract.
pub setup: std::string::String,
}
/// Request for one complete qualified Metaplex 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/DemoExecutionMetadataMetaplexCampaignRequest.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexCampaignRequest {
/// Selected Devnet profile.
pub profile_name: std::string::String,
/// Stable campaign identifier returned by the campaign inventory.
pub campaign_id: std::string::String,
/// Explicit authorization for fixture creation and every campaign submission.
pub operator_confirmed: bool,
}
/// UI-safe result of one qualified Metaplex 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/DemoExecutionMetadataMetaplexCampaignSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataMetaplexCampaignSummaryPayload {
/// Profile used by the campaign.
pub profile_name: std::string::String,
/// Stable campaign identifier.
pub campaign_id: std::string::String,
/// Operator-visible campaign label.
pub label: std::string::String,
/// Number of Metaplex operation executions or probes retained in this run.
pub operation_count: usize,
/// Number of Metaplex operations reaching confirmed or finalized state.
pub confirmed_operation_count: usize,
/// Number of simulation-only runtime-unavailable probes retained in this run.
pub unavailable_operation_count: usize,
/// Whether the complete campaign reached its expected terminal contract.
pub completed: bool,
/// Fixture/setup description as formatted JSON.
pub fixture_json: std::string::String,
/// Ordered Metaplex execution evidence as formatted JSON.
pub executions_json: std::string::String,
/// Exact campaign postconditions as formatted JSON.
pub postconditions_json: std::string::String,
}
struct CampaignProjection {
label: std::string::String,
fixture: serde_json::Value,
executions: std::vec::Vec<serde_json::Value>,
postconditions: serde_json::Value,
completed: bool,
}
type CampaignProjectionFuture<'a> = std::pin::Pin<
std::boxed::Box<
dyn std::future::Future<Output = kb_core::Result<CampaignProjection>>
+ std::marker::Send
+ 'a,
>,
>;
/// Returns the qualified Metaplex Devnet campaign inventory exposed by the desktop.
pub(crate) fn demo_execution_metadata_metaplex_campaigns()
-> std::vec::Vec<crate::DemoExecutionMetadataMetaplexCampaignOptionPayload> {
let mut values = std::vec::Vec::new();
for (id, label, family) in [
("create_mint_nft", "Create → Mint — NFT", "nft"),
("create_mint_sft", "Create → Mint — SFT", "sft"),
("create_mint_fungible", "Create → Mint — Fungible", "fungible"),
("create_mint_collection", "Create → Mint — Collection", "collection"),
("create_mint_programmable_nft", "Create → Mint — pNFT", "programmable_nft"),
] {
values.push(crate::DemoExecutionMetadataMetaplexCampaignOptionPayload {
id: id.to_string(),
label: label.to_string(),
asset_family: family.to_string(),
operation_names:
kb_pipeline_demo_scenarios::metaplex_create_mint_campaign_operation_names()
.iter()
.map(|value| return (*value).to_string())
.collect(),
qualification: "confirmed".to_string(),
setup: "fresh classic SPL mint + operator ATA".to_string(),
});
}
values.extend([
campaign_option(
"collection_verify",
"Collection — Verify → Unverify",
"collection",
kb_pipeline_demo_scenarios::metaplex_collection_verify_campaign_operation_names(),
"confirmed",
"fresh collection parent + fresh unverified member",
),
campaign_option(
"print_burn",
"NFT imprimable — Print → Burn",
"nft",
kb_pipeline_demo_scenarios::metaplex_print_burn_campaign_operation_names(),
"confirmed",
"fresh limited-supply master NFT + fresh printed edition",
),
campaign_option(
"pnft_lifecycle",
"pNFT — Delegate → Lock → Unlock → Revoke → Delegate → Transfer",
"programmable_nft",
kb_pipeline_demo_scenarios::metaplex_pnft_lifecycle_campaign_operation_names(),
"confirmed",
"fresh pNFT + delegate wallet + destination owner",
),
campaign_option(
"escrow",
"Token Owned Escrow — Create → TransferOut → Close",
"nft",
kb_pipeline_demo_scenarios::metaplex_escrow_campaign_operation_names(),
"confirmed",
"fresh NFT parent + fresh fungible attribute + escrow ATA deposit",
),
campaign_option(
"maintenance",
"Maintenance — Update + probes réservés/legacy",
"nft",
kb_pipeline_demo_scenarios::metaplex_maintenance_campaign_operation_names(),
"mixed_confirmed_unavailable",
"fresh NFT; Update submitted; Resize/Migrate/Collect/CloseAccounts simulation-only probes",
),
campaign_option(
"use_probe",
"Use — probe runtime",
"nft",
kb_pipeline_demo_scenarios::metaplex_use_probe_operation_names(),
"runtime_unavailable_probe",
"fresh NFT with two bounded Multiple uses; Use is simulation-only when rejected",
),
]);
return values;
}
fn campaign_option<const N: usize>(
id: &str,
label: &str,
asset_family: &str,
operation_names: &'static [&'static str; N],
qualification: &str,
setup: &str,
) -> crate::DemoExecutionMetadataMetaplexCampaignOptionPayload {
return crate::DemoExecutionMetadataMetaplexCampaignOptionPayload {
id: id.to_string(),
label: label.to_string(),
asset_family: asset_family.to_string(),
operation_names: operation_names.iter().map(|value| return (*value).to_string()).collect(),
qualification: qualification.to_string(),
setup: setup.to_string(),
};
}
/// Executes one complete qualified Metaplex Devnet campaign.
pub(crate) async fn demo_execution_metadata_metaplex_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataMetaplexCampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexCampaignSummaryPayload,
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(
"Metaplex qualified 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),
};
if !profile.wallet.devnet_send_enabled {
return std::result::Result::Err(
"selected profile does not authorize Devnet submissions required by Metaplex fixtures"
.to_string(),
);
}
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let workspace_root = crate::workspace_root_dir();
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir
} else {
workspace_root.join(configured_wallet_dir)
};
let observer = crate::DemoExecutionMetadataObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
tracing::info!(
target: crate::TRACING_TARGET,
action = "execute_metadata_metaplex_campaign",
campaign_id = %request.campaign_id,
phase = "campaign_start",
"start qualified Metaplex desktop campaign"
);
let projection = match execute_campaign_projection(
request.campaign_id.as_str(),
wallet_dir,
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
tracing::error!(
target: crate::TRACING_TARGET,
action = "execute_metadata_metaplex_campaign",
campaign_id = %request.campaign_id,
phase = "campaign_failed",
error = %error,
"qualified Metaplex desktop campaign failed"
);
return std::result::Result::Err(error.to_string());
},
};
tracing::info!(
target: crate::TRACING_TARGET,
action = "execute_metadata_metaplex_campaign",
campaign_id = %request.campaign_id,
phase = "campaign_completed",
completed = projection.completed,
execution_count = projection.executions.len(),
"qualified Metaplex desktop campaign completed"
);
let confirmed_operation_count = projection
.executions
.iter()
.filter(|value| {
return value.get("classification").and_then(serde_json::Value::as_str)
== std::option::Option::Some("confirmed");
})
.count();
let unavailable_operation_count = projection
.executions
.iter()
.filter(|value| {
return value.get("classification").and_then(serde_json::Value::as_str)
== std::option::Option::Some("unavailable");
})
.count();
return std::result::Result::Ok(crate::DemoExecutionMetadataMetaplexCampaignSummaryPayload {
profile_name: request.profile_name,
campaign_id: request.campaign_id,
label: projection.label,
operation_count: projection.executions.len(),
confirmed_operation_count,
unavailable_operation_count,
completed: projection.completed,
fixture_json: crate::pretty_json(&projection.fixture),
executions_json: crate::pretty_json(&projection.executions),
postconditions_json: crate::pretty_json(&projection.postconditions),
});
}
fn execute_campaign_projection<'a, S, O>(
campaign_id: &'a str,
wallet_dir: std::path::PathBuf,
http_pool: &'a kb_onchain_transport::HttpEndpointPool,
store: &'a S,
profile: &'a kb_config::ProfileConfig,
workspace_root: &'a std::path::Path,
observer: &'a O,
) -> CampaignProjectionFuture<'a>
where
S: kb_store::RawTransactionStore
+ kb_store::CoreExtractionStore
+ kb_store::DecodePipelineStore
+ Sync
+ 'a,
O: kb_pipeline_demo_scenarios::SolanaExecutionObserver + 'a,
{
return std::boxed::Box::pin(execute_campaign_projection_unboxed(
campaign_id,
wallet_dir,
http_pool,
store,
profile,
workspace_root,
observer,
));
}
async fn execute_campaign_projection_unboxed<S, O>(
campaign_id: &str,
wallet_dir: std::path::PathBuf,
http_pool: &kb_onchain_transport::HttpEndpointPool,
store: &S,
profile: &kb_config::ProfileConfig,
workspace_root: &std::path::Path,
observer: &O,
) -> kb_core::Result<CampaignProjection>
where
S: kb_store::RawTransactionStore
+ kb_store::CoreExtractionStore
+ kb_store::DecodePipelineStore
+ Sync,
O: kb_pipeline_demo_scenarios::SolanaExecutionObserver,
{
let base_options =
kb_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions::new(wallet_dir);
return match campaign_id {
"create_mint_nft" => {
execute_create_mint_projection(
"Create → Mint — NFT",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"create_mint_sft" => {
execute_create_mint_projection(
"Create → Mint — SFT",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Sft,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"create_mint_fungible" => {
execute_create_mint_projection(
"Create → Mint — Fungible",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Fungible,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"create_mint_collection" => {
execute_create_mint_projection(
"Create → Mint — Collection",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Collection,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"create_mint_programmable_nft" => {
execute_create_mint_projection(
"Create → Mint — pNFT",
base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
),
http_pool,
store,
profile,
workspace_root,
observer,
)
.await
},
"collection_verify" => {
let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_collection_verify_campaign(
http_pool,
store,
profile,
workspace_root,
&base_options,
observer,
).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("parent.create", &summary.parent.create),
metaplex_execution_json("parent.mint", &summary.parent.mint),
metaplex_execution_json("member.create", &summary.member.create),
metaplex_execution_json("member.mint", &summary.member.mint),
metaplex_execution_json("verify", &summary.verify),
metaplex_execution_json("unverify", &summary.unverify),
];
std::result::Result::Ok(CampaignProjection {
label: "Collection — Verify → Unverify".to_string(),
fixture: serde_json::json!({
"parent": &summary.parent.fixture,
"member": &summary.member.fixture
}),
completed: executions.iter().all(execution_is_confirmed_json),
executions,
postconditions: serde_json::json!({
"verifiedBefore": summary.state.verified_before,
"verifiedAfterVerify": summary.state.verified_after_verify,
"verifiedAfterUnverify": summary.state.verified_after_unverify,
"collectionSizeBefore": summary.state.collection_size_before,
"collectionSizeAfterVerify": summary.state.collection_size_after_verify,
"collectionSizeAfterUnverify": summary.state.collection_size_after_unverify
}),
})
},
"print_burn" => {
let summary =
match kb_pipeline_demo_scenarios::execute_devnet_metaplex_print_burn_campaign(
http_pool,
store,
profile,
workspace_root,
&base_options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("master.create", &summary.master.create),
metaplex_execution_json("master.mint", &summary.master.mint),
metaplex_execution_json("print", &summary.print),
metaplex_execution_json("burn", &summary.burn),
];
std::result::Result::Ok(CampaignProjection {
label: "NFT imprimable — Print → Burn".to_string(),
fixture: serde_json::json!({
"master": &summary.master.fixture,
"editionMint": &summary.edition_mint,
"editionMetadata": &summary.edition_metadata,
"edition": &summary.edition,
"editionTokenAccount": &summary.edition_token_account,
"editionMarker": &summary.edition_marker
}),
completed: executions.iter().all(execution_is_confirmed_json)
&& summary.state.edition_metadata_closed_after_burn
&& !summary.state.edition_exists_after_burn
&& !summary.state.edition_token_exists_after_burn,
executions,
postconditions: print_burn_state_json(&summary.state),
})
},
"pnft_lifecycle" => {
let options = base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
);
let summary =
match kb_pipeline_demo_scenarios::execute_devnet_metaplex_pnft_lifecycle_campaign(
http_pool,
store,
profile,
workspace_root,
&options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("pnft.create", &summary.pnft.create),
metaplex_execution_json("pnft.mint", &summary.pnft.mint),
metaplex_execution_json("delegate_staking", &summary.delegate_staking),
metaplex_execution_json("lock", &summary.lock),
metaplex_execution_json("unlock", &summary.unlock),
metaplex_execution_json("revoke", &summary.revoke),
metaplex_execution_json("delegate_transfer", &summary.delegate_transfer),
metaplex_execution_json("transfer", &summary.transfer),
];
std::result::Result::Ok(CampaignProjection {
label: "pNFT lifecycle".to_string(),
fixture: serde_json::json!({"pnft": &summary.pnft.fixture}),
completed: executions.iter().all(execution_is_confirmed_json)
&& summary.state.staking_delegate_revoked
&& summary.state.destination_delegate_cleared,
executions,
postconditions: pnft_state_json(&summary.state),
})
},
"escrow" => {
let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_escrow_campaign(
http_pool,
store,
profile,
workspace_root,
&base_options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("parent.create", &summary.parent_fixture.create),
metaplex_execution_json("parent.mint", &summary.parent_fixture.mint),
metaplex_execution_json("attribute.create", &summary.attribute_fixture.create),
metaplex_execution_json("attribute.mint", &summary.attribute_fixture.mint),
metaplex_execution_json("create_escrow", &summary.create_escrow),
metaplex_execution_json("transfer_out", &summary.transfer_out),
metaplex_execution_json("close_escrow", &summary.close_escrow),
];
std::result::Result::Ok(CampaignProjection {
label: "Token Owned Escrow".to_string(),
fixture: serde_json::json!({
"parent": &summary.parent_fixture.fixture,
"attribute": &summary.attribute_fixture.fixture,
"escrow": &summary.escrow,
"escrowAttributeTokenAccount": &summary.escrow_attribute_token_account,
"setup": {
"ataCreationConfirmed": execution_confirmation_is_confirmed(&summary.escrow_attribute_ata_creation.confirmation),
"attributeDepositConfirmed": execution_confirmation_is_confirmed(&summary.attribute_deposit.confirmation),
"ataMaterializations": summary.escrow_attribute_ata_creation.materializations.len(),
"depositMaterializations": summary.attribute_deposit.materializations.len()
}
}),
completed: executions.iter().all(execution_is_confirmed_json)
&& summary.state.escrow_attribute_account_closed
&& summary.state.escrow_account_closed
&& summary.state.parent_token_amount_after_close == 1,
executions,
postconditions: escrow_state_json(&summary.state),
})
},
"maintenance" => {
let options = base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
);
let summary =
match kb_pipeline_demo_scenarios::execute_devnet_metaplex_maintenance_campaign(
http_pool,
store,
profile,
workspace_root,
&options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("fixture.create", &summary.fixture.create),
metaplex_execution_json("fixture.mint", &summary.fixture.mint),
metaplex_execution_json("update", &summary.update),
metaplex_execution_json("resize_probe", &summary.resize_probe),
metaplex_execution_json("migrate_probe", &summary.migrate_probe),
metaplex_execution_json("collect_probe", &summary.collect_probe),
metaplex_execution_json("close_accounts_probe", &summary.close_accounts_probe),
];
let classifications = executions
.iter()
.filter_map(|value| {
return value.get("classification").and_then(serde_json::Value::as_str);
})
.collect::<std::vec::Vec<&str>>();
std::result::Result::Ok(CampaignProjection {
label: "Maintenance — Update + probes".to_string(),
fixture: serde_json::json!({"fixture": &summary.fixture.fixture}),
completed: classifications
== vec![
"confirmed",
"confirmed",
"confirmed",
"unavailable",
"unavailable",
"unavailable",
"unavailable",
]
&& !summary.primary_sale_before_update
&& summary.primary_sale_after_update,
executions,
postconditions: serde_json::json!({
"primarySaleBeforeUpdate": summary.primary_sale_before_update,
"primarySaleAfterUpdate": summary.primary_sale_after_update,
"probePolicy": "simulation_only_no_submission_on_failure"
}),
})
},
"use_probe" => {
let options = base_options.clone().with_asset_family(
kb_pipeline_demo_scenarios::MetaplexTokenMetadataAssetFamily::Nft,
);
let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_use_probe(
http_pool,
store,
profile,
workspace_root,
&options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut executions = vec![
metaplex_execution_json("fixture.create", &summary.fixture.create),
metaplex_execution_json("fixture.mint", &summary.fixture.mint),
metaplex_execution_json("use_probe", &summary.simulation),
];
if let std::option::Option::Some(execution) = summary.execution.as_ref() {
executions.push(metaplex_execution_json("use_execution", execution));
}
let status = format!("{:?}", summary.status).to_lowercase();
std::result::Result::Ok(CampaignProjection {
label: "Use — probe runtime".to_string(),
fixture: serde_json::json!({"fixture": &summary.fixture.fixture}),
completed: executions[0..2].iter().all(execution_is_confirmed_json)
&& (status == "runtimeunavailable" || status == "confirmed"),
executions,
postconditions: serde_json::json!({
"status": status,
"usesBefore": summary.uses_before,
"usesAfter": summary.uses_after,
"unavailableReason": summary.unavailable_reason
}),
})
},
_ => std::result::Result::Err(kb_core::Error::config(format!(
"unknown qualified Metaplex desktop campaign `{campaign_id}`"
))),
};
}
async fn execute_create_mint_projection<S, O>(
label: &str,
options: kb_pipeline_demo_scenarios::MetaplexCreateFixturePreparationOptions,
http_pool: &kb_onchain_transport::HttpEndpointPool,
store: &S,
profile: &kb_config::ProfileConfig,
workspace_root: &std::path::Path,
observer: &O,
) -> kb_core::Result<CampaignProjection>
where
S: kb_store::RawTransactionStore
+ kb_store::CoreExtractionStore
+ kb_store::DecodePipelineStore
+ Sync,
O: kb_pipeline_demo_scenarios::SolanaExecutionObserver,
{
let summary = match kb_pipeline_demo_scenarios::execute_devnet_metaplex_create_mint_campaign(
http_pool,
store,
profile,
workspace_root,
&options,
observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let executions = vec![
metaplex_execution_json("create", &summary.create),
metaplex_execution_json("mint", &summary.mint),
];
return std::result::Result::Ok(CampaignProjection {
label: label.to_string(),
fixture: serde_json::json!({"fixture": &summary.fixture}),
completed: executions.iter().all(execution_is_confirmed_json)
&& summary.token_state.supply_after_mint == summary.token_state.expected_amount_raw
&& summary.token_state.token_amount_after_mint
== summary.token_state.expected_amount_raw,
executions,
postconditions: create_mint_state_json(&summary.token_state),
});
}
fn metaplex_execution_json(
label: &str,
summary: &kb_pipeline_demo_scenarios::DevnetMetaplexTokenMetadataExecutionSummary,
) -> serde_json::Value {
let classification = if execution_confirmation_is_confirmed(&summary.confirmation) {
"confirmed"
} else if !summary.simulation.success && summary.send_result.is_none() {
"unavailable"
} else {
"incomplete"
};
return serde_json::json!({
"label": label,
"classification": classification,
"profileName": &summary.profile_name,
"cluster": format!("{:?}", summary.cluster).to_lowercase(),
"genesisHash": &summary.genesis_hash,
"walletPublicKey": &summary.wallet.public_key,
"balanceLamports": summary.balance_lamports.to_string(),
"plan": &summary.plan,
"statefulPreflight": &summary.stateful_preflight,
"simulation": &summary.simulation,
"simulationContextSlot": summary.simulation_context_slot,
"readiness": &summary.readiness,
"sendResult": &summary.send_result,
"confirmation": &summary.confirmation,
"before": &summary.before,
"after": &summary.after,
"materializationRequested": summary.materialization_requested,
"materializedSnapshots": &summary.materialized_snapshots,
"instructionMaterializations": &summary.materializations,
"postExecution": &summary.post_execution,
"pipelineEvidence": {
"canonicalHydration": summary.backfill.is_some(),
"coreExtraction": summary.core_extraction.is_some(),
"decodeReplay": summary.decode_replay.is_some(),
"idempotenceReplay": summary.idempotence_replay.is_some()
}
});
}
fn execution_confirmation_is_confirmed(
confirmation: &std::option::Option<kb_lib::ExApiExecutionConfirmationResult>,
) -> bool {
return confirmation.as_ref().is_some_and(|value| {
return matches!(
value.status,
kb_lib::ExApiExecutionConfirmationStatus::Confirmed
| kb_lib::ExApiExecutionConfirmationStatus::Finalized
);
});
}
fn execution_is_confirmed_json(value: &serde_json::Value) -> bool {
return value.get("classification").and_then(serde_json::Value::as_str)
== std::option::Option::Some("confirmed");
}
fn create_mint_state_json(
state: &kb_pipeline_demo_scenarios::DevnetMetaplexCreateMintTokenState,
) -> serde_json::Value {
return serde_json::json!({
"expectedAmountRaw": state.expected_amount_raw,
"supplyBeforeMint": state.supply_before_mint,
"tokenAmountBeforeMint": state.token_amount_before_mint,
"tokenAccountStateBeforeMint": state.token_account_state_before_mint,
"supplyAfterMint": state.supply_after_mint,
"tokenAmountAfterMint": state.token_amount_after_mint,
"tokenAccountStateAfterMint": state.token_account_state_after_mint
});
}
fn print_burn_state_json(
state: &kb_pipeline_demo_scenarios::DevnetMetaplexPrintBurnState,
) -> serde_json::Value {
return serde_json::json!({
"editionNumber": state.edition_number,
"masterSupplyBeforePrint": state.master_supply_before_print,
"masterSupplyAfterPrint": state.master_supply_after_print,
"masterSupplyAfterBurn": state.master_supply_after_burn,
"masterMaxSupply": state.master_max_supply,
"editionSupplyAfterPrint": state.edition_supply_after_print,
"editionSupplyAfterBurn": state.edition_supply_after_burn,
"editionTokenAmountAfterPrint": state.edition_token_amount_after_print,
"editionTokenStateAfterPrint": state.edition_token_state_after_print,
"editionTakenAfterPrint": state.edition_taken_after_print,
"editionMetadataExistsAfterBurn": state.edition_metadata_exists_after_burn,
"editionMetadataFeeTombstoneAfterBurn": state.edition_metadata_fee_tombstone_after_burn,
"editionMetadataClosedAfterBurn": state.edition_metadata_closed_after_burn,
"editionExistsAfterBurn": state.edition_exists_after_burn,
"editionTokenExistsAfterBurn": state.edition_token_exists_after_burn,
"editionMarkerExistsAfterBurn": state.edition_marker_exists_after_burn
});
}
fn pnft_state_json(
state: &kb_pipeline_demo_scenarios::DevnetMetaplexPnftLifecycleState,
) -> serde_json::Value {
return serde_json::json!({
"delegate": &state.delegate,
"destinationOwner": &state.destination_owner,
"destinationTokenAccount": &state.destination_token_account,
"destinationTokenRecord": &state.destination_token_record,
"initialState": &state.initial_state,
"stakingDelegateRole": &state.staking_delegate_role,
"lockedState": &state.locked_state,
"unlockedState": &state.unlocked_state,
"stakingDelegateRevoked": state.staking_delegate_revoked,
"transferDelegateRole": &state.transfer_delegate_role,
"sourceTokenAmountAfterTransfer": state.source_token_amount_after_transfer,
"sourceTokenStateAfterTransfer": &state.source_token_state_after_transfer,
"destinationTokenAmountAfterTransfer": state.destination_token_amount_after_transfer,
"destinationTokenStateAfterTransfer": &state.destination_token_state_after_transfer,
"destinationTokenRecordState": &state.destination_token_record_state,
"destinationDelegateCleared": state.destination_delegate_cleared
});
}
fn escrow_state_json(
state: &kb_pipeline_demo_scenarios::DevnetMetaplexEscrowCampaignState,
) -> serde_json::Value {
return serde_json::json!({
"depositAmountRaw": state.deposit_amount_raw,
"operatorAttributeAmountBeforeDeposit": state.operator_attribute_amount_before_deposit,
"operatorAttributeAmountAfterDeposit": state.operator_attribute_amount_after_deposit,
"escrowAttributeAmountAfterDeposit": state.escrow_attribute_amount_after_deposit,
"operatorAttributeAmountAfterTransferOut": state.operator_attribute_amount_after_transfer_out,
"escrowAttributeAccountClosed": state.escrow_attribute_account_closed,
"escrowAccountClosed": state.escrow_account_closed,
"parentTokenAmountAfterClose": state.parent_token_amount_after_close
});
}
#[cfg(test)]
mod tests {
#[test]
@@ -865,13 +1735,16 @@ mod tests {
let source = std::include_str!("../frontend/demo_execution_metadata.html");
for id in [
"metadataProfileSelect",
"metadataDevnetScenarioSelect",
"metadataStepSelect",
"prepareMetadataScenarioStepButton",
"metadataSyntheticScenarioSelect",
"metadataMetaplexCampaignSelect",
"metadataMetaplexCampaignOperatorConfirmed",
"executeMetadataMetaplexCampaignButton",
"metadataExecutionLogOutput",
"metadataProfileHelp",
"simulateMetadataButton",
"submitMetadataButton",
"metadataExecutionDetailsAccordion",
"metadataPreflightCollapse",
"metadataSimulationCollapse",
"metadataPostconditionsCollapse",
] {
assert!(source.contains(format!("id=\"{id}\"").as_str()));
}
@@ -890,3 +1763,42 @@ mod tests {
assert!(payloads.iter().any(|payload| return payload.requires_programmable_rules));
}
}
#[cfg(test)]
mod campaign_tests {
#[test]
fn qualified_campaign_inventory_is_exact_and_keeps_unavailable_operations_inside_probes() {
let campaigns = crate::demo_execution_metadata_metaplex_campaigns();
assert_eq!(campaigns.len(), 11);
let ids = campaigns
.iter()
.map(|value| return value.id.as_str())
.collect::<std::collections::BTreeSet<&str>>();
assert_eq!(ids.len(), campaigns.len());
let maintenance = campaigns.iter().find(|value| return value.id == "maintenance");
assert!(
maintenance
.is_some_and(|value| return value.qualification == "mixed_confirmed_unavailable")
);
let use_probe = campaigns.iter().find(|value| return value.id == "use_probe");
assert!(
use_probe
.is_some_and(|value| return value.qualification == "runtime_unavailable_probe")
);
let source = std::include_str!("demo_execution_metadata_metaplex_token_metadata.rs");
assert!(source.contains("Box::pin(execute_campaign_projection_unboxed("));
}
#[test]
fn metadata_frontend_uses_qualified_metaplex_campaign_controls() {
let source = std::include_str!("../frontend/demo_execution_metadata.html");
for id in [
"metadataMetaplexCampaignSelect",
"metadataMetaplexCampaignOutput",
"metadataMetaplexCampaignOperatorConfirmed",
"executeMetadataMetaplexCampaignButton",
] {
assert!(source.contains(format!("id=\"{id}\"").as_str()));
}
}
}

View File

@@ -0,0 +1,224 @@
// file: kb-app-demo-desktop/src/demo_execution_metadata_token_2022.rs
// version: 1
//! Thin desktop adapter for the complete Token-2022 Token Metadata Devnet campaign.
/// Request for one complete Token-2022 Token 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/DemoExecutionMetadataToken2022CampaignRequest.ts"
)]
pub(crate) struct DemoExecutionMetadataToken2022CampaignRequest {
/// Selected Devnet profile.
pub profile_name: std::string::String,
/// Explicit authorization for fixture creation and all five metadata mutations.
pub operator_confirmed: bool,
}
/// UI-safe result of one complete Token-2022 Token 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/DemoExecutionMetadataToken2022CampaignSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionMetadataToken2022CampaignSummaryPayload {
/// Profile used by the campaign.
pub profile_name: std::string::String,
/// Fresh Token-2022 mint containing the embedded metadata extension.
pub mint: std::string::String,
/// Initial update authority, equal to the selected profile wallet.
pub initial_authority: std::string::String,
/// Fresh authority installed by the final campaign step.
pub final_authority: std::string::String,
/// Confirmed signature that created the Token-2022 fixture mint.
pub preparation_signature: std::string::String,
/// Number of Token Metadata interface operations executed.
pub step_count: usize,
/// Number of operations reaching confirmed or finalized state.
pub confirmed_step_count: usize,
/// Total number of instruction materializations produced by the five operations.
pub materialization_count: usize,
/// Whether the complete five-step campaign and every postcondition succeeded.
pub completed: bool,
/// Complete fixture description as formatted JSON.
pub fixture_json: std::string::String,
/// Ordered execution and postcondition evidence as formatted JSON.
pub steps_json: std::string::String,
}
/// Executes the complete Token-2022 Token Metadata Devnet campaign.
pub(crate) async fn demo_execution_metadata_token_2022_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataToken2022CampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataToken2022CampaignSummaryPayload,
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(
"Token-2022 Token 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 store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let workspace_root = crate::workspace_root_dir();
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
let wallet_dir = if configured_wallet_dir.is_absolute() {
configured_wallet_dir
} else {
workspace_root.join(configured_wallet_dir)
};
let mut options =
kb_pipeline_demo_scenarios::Token2022MetadataFixturePreparationOptions::new(wallet_dir);
options.operator_confirmed = true;
let observer = crate::DemoExecutionMetadataObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let summary = match kb_pipeline_demo_scenarios::execute_devnet_token_2022_metadata_campaign(
&http_pool,
&store,
&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(request.profile_name, summary));
}
fn campaign_summary_payload(
profile_name: std::string::String,
summary: kb_pipeline_demo_scenarios::DevnetToken2022MetadataCampaignSummary,
) -> crate::DemoExecutionMetadataToken2022CampaignSummaryPayload {
let mut confirmed_step_count = 0_usize;
let mut materialization_count = 0_usize;
let mut step_values = std::vec::Vec::with_capacity(summary.steps.len());
for step in &summary.steps {
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);
}
materialization_count =
materialization_count.saturating_add(step.execution.materializations.len());
step_values.push(serde_json::json!({
"stepId": &step.step_id,
"operationCode": &step.operation_code,
"simulation": &step.execution.simulation,
"sendResult": &step.execution.send_result,
"confirmation": &step.execution.confirmation,
"statefulPreflight": &step.execution.stateful_preflight,
"statefulSnapshot": &step.stateful_snapshot,
"emitEvidence": &step.emit_evidence,
"postcondition": &step.postcondition,
"postExecution": &step.execution.post_execution,
"materializations": &step.execution.materializations
}));
}
let expected_step_count =
kb_pipeline_demo_scenarios::token_2022_metadata_campaign_operation_names().len();
let completed = summary.steps.len() == expected_step_count
&& confirmed_step_count == expected_step_count
&& summary.steps.iter().all(|step| {
return step.postcondition.status
== kb_pipeline::Token2022ExecutionPostconditionStatus::Confirmed;
});
let fixture_json = crate::pretty_json(&serde_json::json!({
"mint": &summary.fixture.mint,
"initialAuthority": &summary.fixture.initial_authority,
"finalAuthority": &summary.fixture.final_authority,
"mintSpace": summary.fixture.mint_space,
"rentBudgetSpace": summary.fixture.rent_budget_space,
"rentReserveLamports": summary.fixture.rent_reserve_lamports.to_string(),
"preparationSignature": &summary.fixture.preparation_signature,
"initialSnapshot": &summary.fixture.initial_snapshot
}));
return crate::DemoExecutionMetadataToken2022CampaignSummaryPayload {
profile_name,
mint: summary.fixture.mint.0.clone(),
initial_authority: summary.fixture.initial_authority.0.clone(),
final_authority: summary.fixture.final_authority.0.clone(),
preparation_signature: summary.fixture.preparation_signature.clone(),
step_count: summary.steps.len(),
confirmed_step_count,
materialization_count,
completed,
fixture_json,
steps_json: crate::pretty_json(&step_values),
};
}
#[cfg(test)]
mod tests {
#[test]
fn desktop_campaign_exposes_the_exact_five_token_metadata_operations() {
assert_eq!(
kb_pipeline_demo_scenarios::token_2022_metadata_campaign_operation_names(),
&[
"initialize_token_metadata",
"update_token_metadata_field",
"emit_token_metadata",
"remove_token_metadata_key",
"update_token_metadata_authority",
]
);
}
#[test]
fn metadata_frontend_contains_token_2022_campaign_controls() {
let source = std::include_str!("../frontend/demo_execution_metadata.html");
for id in [
"token2022MetadataProfileSelect",
"token2022MetadataCampaignOutput",
"token2022MetadataOperatorConfirmed",
"executeToken2022MetadataCampaignButton",
] {
assert!(source.contains(format!("id=\"{id}\"").as_str()));
}
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/lib.rs
// version: 31
// version: 34
//! Tauri desktop demo application for `khadhroony-bot3`.
@@ -17,6 +17,7 @@ mod demo_devnet_common;
mod demo_execution_metadata;
mod demo_execution_metadata_metaplex_token_metadata;
mod demo_execution_metadata_solana_program;
mod demo_execution_metadata_token_2022;
mod demo_execution_solana_core;
mod demo_execution_spl;
mod demo_http;
@@ -137,6 +138,12 @@ pub(crate) use self::demo_execution_metadata::DemoExecutionMetadataObserver;
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;
/// UI-safe descriptor for one qualified Metaplex Devnet campaign.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexCampaignOptionPayload;
/// Request for one complete qualified Metaplex Devnet campaign.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexCampaignRequest;
/// UI-safe result of one complete qualified Metaplex Devnet campaign.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecutionMetadataMetaplexCampaignSummaryPayload;
/// 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.
@@ -147,6 +154,10 @@ pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::DemoExecut
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 the qualified Metaplex Devnet campaign inventory.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_campaigns;
/// Executes one complete qualified Metaplex Devnet campaign.
pub(crate) use self::demo_execution_metadata_metaplex_token_metadata::demo_execution_metadata_metaplex_execute_campaign;
/// 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.
@@ -171,6 +182,12 @@ pub(crate) use self::demo_execution_metadata_solana_program::DemoExecutionMetada
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 for the complete Token-2022 Token Metadata Devnet campaign.
pub(crate) use self::demo_execution_metadata_token_2022::DemoExecutionMetadataToken2022CampaignRequest;
/// UI-safe result of the complete Token-2022 Token Metadata Devnet campaign.
pub(crate) use self::demo_execution_metadata_token_2022::DemoExecutionMetadataToken2022CampaignSummaryPayload;
/// Executes the complete Token-2022 Token Metadata Devnet campaign.
pub(crate) use self::demo_execution_metadata_token_2022::demo_execution_metadata_token_2022_execute_campaign;
/// 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.

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/tauri.rs
// version: 32
// version: 34
//! Tauri runtime assembly and private command wrappers.
@@ -90,6 +90,8 @@ pub fn run() -> kb_core::Result<()> {
demo_spl_token_2022_fixture,
open_demo_execution_metadata_window,
demo_execution_metadata_metaplex_token_metadata_scenarios,
demo_execution_metadata_metaplex_campaigns,
demo_execution_metadata_metaplex_execute_campaign,
demo_execution_metadata_options,
demo_execution_metadata_metaplex_token_metadata_current_operations,
demo_execution_metadata_metaplex_token_metadata_operation_template,
@@ -98,6 +100,7 @@ pub fn run() -> kb_core::Result<()> {
demo_execution_metadata_metaplex_token_metadata_execute,
demo_execution_metadata_solana_program_scenarios,
demo_execution_metadata_solana_program_execute_campaign,
demo_execution_metadata_token_2022_execute_campaign,
]);
builder = builder.on_window_event(|window, event| {
if window.label() != "main" || !matches!(event, tauri::WindowEvent::Destroyed) {
@@ -638,6 +641,25 @@ fn demo_execution_metadata_metaplex_token_metadata_scenarios() -> std::result::R
return crate::demo_execution_metadata_metaplex_token_metadata_scenarios();
}
#[tauri::command]
fn demo_execution_metadata_metaplex_campaigns()
-> std::vec::Vec<crate::DemoExecutionMetadataMetaplexCampaignOptionPayload> {
return crate::demo_execution_metadata_metaplex_campaigns();
}
#[tauri::command]
async fn demo_execution_metadata_metaplex_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataMetaplexCampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataMetaplexCampaignSummaryPayload,
std::string::String,
> {
return crate::demo_execution_metadata_metaplex_execute_campaign(app_handle, state, request)
.await;
}
#[tauri::command]
fn demo_execution_metadata_metaplex_token_metadata_current_operations()
-> std::vec::Vec<std::string::String> {
@@ -723,6 +745,19 @@ async fn demo_execution_metadata_solana_program_execute_campaign(
.await;
}
#[tauri::command]
async fn demo_execution_metadata_token_2022_execute_campaign(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMetadataToken2022CampaignRequest,
) -> std::result::Result<
crate::DemoExecutionMetadataToken2022CampaignSummaryPayload,
std::string::String,
> {
return crate::demo_execution_metadata_token_2022_execute_campaign(app_handle, state, request)
.await;
}
#[tauri::command]
async fn demo_execution_spl_token_execute(
app_handle: tauri::AppHandle,