v0.4.7-pre.003

This commit is contained in:
2026-08-02 09:04:23 +02:00
parent 7a50ddd531
commit 1bddeb834d
22 changed files with 1656 additions and 369 deletions

View File

@@ -3067,9 +3067,7 @@ mod tests {
assert_eq!(entries.len(), 58);
let mut discriminators = std::collections::BTreeSet::new();
for entry in entries {
let discriminator = match entry
.get("discriminator")
.and_then(serde_json::Value::as_u64)
let discriminator = match entry.get("discriminator").and_then(serde_json::Value::as_u64)
{
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("instruction discriminator missing"),
@@ -3077,14 +3075,18 @@ mod tests {
assert!(discriminators.insert(discriminator));
assert!(entry.get("accounts").is_some_and(serde_json::Value::is_array));
assert!(entry.get("signers").is_some_and(serde_json::Value::is_array));
assert!(entry
.get("coverage_status")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return value != "pending" && !value.is_empty()));
assert!(entry
.get("execute")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return value != "pending" && !value.is_empty()));
assert!(
entry
.get("coverage_status")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return value != "pending" && !value.is_empty())
);
assert!(
entry
.get("execute")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return value != "pending" && !value.is_empty())
);
}
assert_eq!(discriminators, (0u64..58u64).collect());
}
@@ -3095,13 +3097,11 @@ mod tests {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("matrix parse failed: {error}"),
};
let facts = match matrix
.get("fact_ownership_contract")
.and_then(serde_json::Value::as_array)
{
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("fact_ownership_contract missing"),
};
let facts =
match matrix.get("fact_ownership_contract").and_then(serde_json::Value::as_array) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("fact_ownership_contract missing"),
};
let mut names = std::collections::BTreeSet::new();
for entry in facts {
let fact = match entry.get("fact").and_then(serde_json::Value::as_str) {
@@ -3109,10 +3109,12 @@ mod tests {
std::option::Option::None => panic!("fact name missing"),
};
assert!(names.insert(fact));
assert!(entry
.get("owner")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return !value.is_empty()));
assert!(
entry
.get("owner")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return !value.is_empty())
);
}
let forbidden = match matrix
.pointer("/fact_ownership_proof/cross_domain_merge_forbidden")

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/executor.rs
// version: 9
// version: 11
//! Consolidated executor modules.
@@ -208,12 +208,28 @@ pub use self::lending::ExLendingKaminoExecutor;
pub use self::lending::ExLendingMarginfiV2Executor;
/// Exposes the reserved `lock/raydium_lp` executor.
pub use self::lock::ExLockRaydiumLpExecutor;
/// Exposes the reserved `metadata/metaplex_token_metadata` executor.
/// Stable operation code for the current Metaplex Create wrapper.
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION;
/// Deprecated Metaplex operation codes requiring explicit approval.
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES;
/// Stable deprecated Metaplex PuffMetadata operation code.
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION;
/// Stable deprecated Metaplex SetTokenStandard operation code.
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION;
/// Current and deprecated Metaplex Token Metadata operations.
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES;
/// Stable operation code for the current Metaplex Update wrapper.
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION;
/// Exposes the Metaplex Token Metadata executor.
pub use self::metadata::ExMetadataMetaplexTokenMetadataExecutor;
/// Reserved `kb-lib.executor.metadata.solana_program_metadata` executor.
pub use self::metadata::ExMetadataSolanaProgramMetadataExecutor;
/// Exposes the reserved `metadata/spl_name_service` executor.
pub use self::metadata::ExMetadataSplNameServiceExecutor;
/// Exposes the complete Metaplex Token Metadata execution intent.
pub use self::metadata::ExMetaplexTokenMetadataExecutionIntent;
/// Exposes one current Metaplex Token Metadata operation.
pub use self::metadata::ExMetaplexTokenMetadataOperation;
/// Exposes the reserved `nft/metaplex_bubblegum` executor.
pub use self::nft::ExNftMetaplexBubblegumExecutor;
/// Reserved `kb-lib.executor.nft.tensor_cnft` executor.
@@ -915,6 +931,8 @@ pub use self::wallet::ExWalletJupiterApeproSmartWalletExecutor;
/// Exposes the reserved `weighted/swap_stabble` executor.
pub use self::weighted::ExWeightedSwapStabbleExecutor;
/// Internal Metaplex Token Metadata prepared-plan builder.
pub(crate) use self::metadata::executor_metaplex_token_metadata_build_prepared_plan;
/// Stable operation code for the nonce advance injected by durable transaction assembly.
pub(crate) use self::solana::EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION;
/// Maximum over-the-wire size of a Solana transaction packet.
@@ -998,7 +1016,7 @@ pub(crate) use self::spl::executor_spl_token_build_prepared_plan;
mod reserved_executor_tests {
#[test]
fn every_reserved_executor_is_registered_and_builds_only_an_empty_plan() {
let executors: [&dyn crate::ExApiInstructionExecutor; 105] = [
let executors: [&dyn crate::ExApiInstructionExecutor; 104] = [
&crate::ExAdapterSaberDecimalWrapperExecutor,
&crate::ExAdapterSplTokenWrapExecutor,
&crate::ExGovernanceSquadsMultisigExecutor,
@@ -1067,7 +1085,6 @@ mod reserved_executor_tests {
&crate::ExLendingKaminoExecutor,
&crate::ExLendingMarginfiV2Executor,
&crate::ExLockRaydiumLpExecutor,
&crate::ExMetadataMetaplexTokenMetadataExecutor,
&crate::ExMetadataSplNameServiceExecutor,
&crate::ExNftMetaplexBubblegumExecutor,
&crate::ExOrderbookJupiterLimitOrderExecutor,
@@ -1105,7 +1122,7 @@ mod reserved_executor_tests {
&crate::ExWalletJupiterApeproSmartWalletExecutor,
&crate::ExWeightedSwapStabbleExecutor,
];
assert_eq!(executors.len(), 105);
assert_eq!(executors.len(), 104);
for executor in executors {
assert!(!executor.program_ids().is_empty());
for program_id in executor.program_ids() {

View File

@@ -1,14 +1,24 @@
// file: kb-lib/src/executor/metadata.rs
// version: 2
// version: 5
//! `metadata` executor family.
mod metaplex_token_metadata;
mod solana_program_metadata;
mod spl_name_service;
/// Exposes the reserved `metadata/metaplex_token_metadata` executor.
/// Exposes the Metaplex Token Metadata execution surface.
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION;
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES;
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION;
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION;
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES;
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION;
pub use self::metaplex_token_metadata::ExMetadataMetaplexTokenMetadataExecutor;
pub use self::metaplex_token_metadata::ExMetaplexTokenMetadataExecutionIntent;
pub use self::metaplex_token_metadata::ExMetaplexTokenMetadataOperation;
/// Exposes the reserved `metadata/solana_program_metadata` executor.
pub use self::solana_program_metadata::ExMetadataSolanaProgramMetadataExecutor;
/// Exposes the reserved `metadata/spl_name_service` executor.
pub use self::spl_name_service::ExMetadataSplNameServiceExecutor;
pub(crate) use self::metaplex_token_metadata::executor_metaplex_token_metadata_build_prepared_plan;

View File

@@ -1,49 +1,29 @@
// file: kb-lib/src/executor/metadata/metaplex_token_metadata.rs
// version: 4
// version: 7
//! Reserved executor for `metadata_metaplex_token_metadata`.
//! Metaplex Token Metadata typed execution surface.
/// Reserved executor for the `metadata_metaplex_token_metadata` program surface.
#[derive(Clone, Debug, Default)]
pub struct ExMetadataMetaplexTokenMetadataExecutor;
mod builder;
mod executor;
mod intent;
impl crate::ExApiInstructionExecutor for crate::ExMetadataMetaplexTokenMetadataExecutor {
fn executor_name(&self) -> &'static str {
return "kb-lib.executor.metadata.metaplex_token_metadata";
}
/// Exposes the Metaplex Token Metadata executor.
pub use self::executor::ExMetadataMetaplexTokenMetadataExecutor;
/// Stable operation code for the current Create wrapper.
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION;
/// Deprecated operation codes requiring explicit operator approval.
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES;
/// Stable deprecated operation code for PuffMetadata.
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION;
/// Stable deprecated operation code for SetTokenStandard.
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION;
/// Current and deprecated operations implemented by this prerelease.
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES;
/// Stable operation code for the current Update wrapper.
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION;
/// Exposes the complete Metaplex Token Metadata execution intent.
pub use self::intent::ExMetaplexTokenMetadataExecutionIntent;
/// Exposes the current and deprecated Metaplex Token Metadata operation intent.
pub use self::intent::ExMetaplexTokenMetadataOperation;
fn executor_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn program_ids(&self) -> &'static [&'static str] {
return &[kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID];
}
fn supports_request(
&self,
request: &crate::ExApiExecutionRequest,
) -> crate::ExApiExecutionSupport {
if crate::ExApiInstructionExecutor::handles_program_id(self, &request.program_id) {
return crate::ExApiExecutionSupport::Maybe;
}
return crate::ExApiExecutionSupport::No;
}
fn build_plan(
&self,
_request: &crate::ExApiExecutionRequest,
) -> kb_core::Result<crate::ExApiExecutionPlan> {
let payload_json = match crate::executor_api_serialize_payload_json(
&serde_json::json!({"status":"reserved_executor","surface":"metadata.metaplex_token_metadata"}),
) {
std::result::Result::Ok(serialized) => serialized,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::ExApiExecutionPlan {
executor_name: "kb-lib.executor.metadata.metaplex_token_metadata".to_string(),
instruction_count: 0,
payload_json,
});
}
}
pub(crate) use self::builder::executor_metaplex_token_metadata_build_prepared_plan;

View File

@@ -0,0 +1,637 @@
// file: kb-lib/src/executor/metadata/metaplex_token_metadata/builder.rs
// version: 4
//! Official Metaplex builders and conservative plan validation.
use std::str::FromStr; // rust-rules: trait-import
macro_rules! parse_official_pubkey {
($value:expr, $field:expr) => {{
match std::str::FromStr::from_str($value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_pubkey_invalid",
format!("invalid {} public key: {}", $field, error),
));
},
}
}};
}
macro_rules! convert_official_instruction {
($instruction:expr) => {{
let instruction = $instruction;
let program_id = match parse_str_pubkey(&instruction.program_id.to_string(), "program_id") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut accounts = std::vec::Vec::with_capacity(instruction.accounts.len());
for account in instruction.accounts {
let pubkey = match parse_str_pubkey(&account.pubkey.to_string(), "instruction_account")
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
accounts.push(solana_instruction::AccountMeta {
pubkey,
is_signer: account.is_signer,
is_writable: account.is_writable,
});
}
solana_instruction::Instruction {
program_id,
accounts,
data: instruction.data,
}
}};
}
#[allow(deprecated)]
pub(crate) fn executor_metaplex_token_metadata_build_prepared_plan(
intent: &crate::ExMetaplexTokenMetadataExecutionIntent,
) -> kb_core::Result<crate::ExApiPreparedExecutionPlan> {
if let std::result::Result::Err(error) = validate_intent(intent) {
return std::result::Result::Err(error);
}
let instruction_result = match &intent.operation {
crate::ExMetaplexTokenMetadataOperation::Create {
metadata,
master_edition,
mint,
mint_as_signer,
authority,
update_authority,
update_authority_as_signer,
spl_token_program,
create_args,
} => build_create(
intent,
metadata,
master_edition.as_ref(),
mint,
*mint_as_signer,
authority,
update_authority,
*update_authority_as_signer,
spl_token_program.as_ref(),
create_args,
),
crate::ExMetaplexTokenMetadataOperation::UpdateAsUpdateAuthorityV2 {
authority,
mint,
metadata,
edition,
token,
authorization_rules_program,
authorization_rules,
update_args,
} => build_update(
intent,
authority,
mint,
metadata,
edition.as_ref(),
token.as_ref(),
authorization_rules_program.as_ref(),
authorization_rules.as_ref(),
update_args,
),
crate::ExMetaplexTokenMetadataOperation::PuffMetadata { metadata } => {
build_puff_metadata(metadata)
},
crate::ExMetaplexTokenMetadataOperation::SetTokenStandard {
metadata,
update_authority,
mint,
edition,
} => build_set_token_standard(metadata, update_authority, mint, edition.as_ref()),
};
let instruction = match instruction_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let required_signers = required_signers(&intent.fee_payer, &instruction.accounts);
if let std::result::Result::Err(error) = validate_authorized_signers(intent, &required_signers)
{
return std::result::Result::Err(error);
}
return std::result::Result::Ok(crate::ExApiPreparedExecutionPlan {
executor_name: "kb-lib.executor.metadata.metaplex_token_metadata".to_string(),
executor_version: env!("CARGO_PKG_VERSION").to_string(),
intent_id: intent.intent_id.clone(),
operation_code: intent.operation.operation_code().to_string(),
fee_payer: intent.fee_payer.clone(),
instructions: vec![planned_instruction(intent.operation.operation_code(), &instruction)],
required_signers,
policy: intent.policy.clone(),
requested_spend_lamports: intent.max_rent_lamports,
requested_compute_unit_price_micro_lamports: std::option::Option::None,
});
}
fn validate_intent(intent: &crate::ExMetaplexTokenMetadataExecutionIntent) -> kb_core::Result<()> {
if intent.intent_id.trim().is_empty() {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_intent_id_empty",
"Metaplex Token Metadata execution intent id must not be empty",
));
}
if intent.operation.is_deprecated() && !intent.allow_deprecated_operation {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_deprecated_operation_not_approved",
"deprecated Metaplex Token Metadata operation requires explicit operator approval",
));
}
if intent.policy.simulation != crate::ExApiExecutionSimulationPolicy::Required {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_simulation_required",
"Metaplex Token Metadata execution requires simulation before signing or sending",
));
}
if !intent.policy.dry_run {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_dry_run_required",
"Metaplex Token Metadata pre.003 intents are simulation-only",
));
}
match intent.policy.cost_limit.max_fee_lamports {
std::option::Option::Some(value) if value > 0 => {},
std::option::Option::Some(_) | std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_fee_limit_missing",
"Metaplex Token Metadata execution requires a positive transaction fee ceiling",
));
},
}
match intent.policy.cost_limit.max_spend_lamports {
std::option::Option::Some(value) if value >= intent.max_rent_lamports => {},
std::option::Option::Some(_) | std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_spend_limit_insufficient",
"Metaplex Token Metadata execution spend ceiling must cover max_rent_lamports",
));
},
}
return std::result::Result::Ok(());
}
#[allow(clippy::too_many_arguments)]
fn build_create(
intent: &crate::ExMetaplexTokenMetadataExecutionIntent,
metadata: &crate::MdPubkey,
master_edition: std::option::Option<&crate::MdPubkey>,
mint: &crate::MdPubkey,
mint_as_signer: bool,
authority: &crate::MdPubkey,
update_authority: &crate::MdPubkey,
update_authority_as_signer: bool,
spl_token_program: std::option::Option<&crate::MdProgramId>,
create_args: &mpl_token_metadata::types::CreateArgs,
) -> kb_core::Result<solana_instruction::Instruction> {
let metadata = match parse_pubkey(metadata, "metadata") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mint = match parse_pubkey(mint, "mint") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::result::Result::Err(error) = validate_metadata_pda(&metadata, &mint) {
return std::result::Result::Err(error);
}
let master_edition = match parse_optional_pubkey(master_edition, "master_edition") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::option::Option::Some(value) = master_edition {
if let std::result::Result::Err(error) = validate_edition_pda(&value, &mint) {
return std::result::Result::Err(error);
}
}
if !matches!(create_args, mpl_token_metadata::types::CreateArgs::V1 { .. }) {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_create_args_unsupported",
"only CreateArgs::V1 is current and supported",
));
}
let official = mpl_token_metadata::instructions::Create {
metadata: parse_official_pubkey!(&metadata.to_string(), "metadata"),
master_edition: match master_edition.as_ref() {
std::option::Option::Some(value) => std::option::Option::Some(parse_official_pubkey!(
&value.to_string(),
"master_edition"
)),
std::option::Option::None => std::option::Option::None,
},
mint: (parse_official_pubkey!(&mint.to_string(), "mint"), mint_as_signer),
authority: parse_official_pubkey!(&authority.0, "authority"),
payer: parse_official_pubkey!(&intent.fee_payer.0, "fee_payer"),
update_authority: (
parse_official_pubkey!(&update_authority.0, "update_authority"),
update_authority_as_signer,
),
system_program: parse_official_pubkey!(kb_program_ids::SYSTEM_PROGRAM_ID, "system_program"),
sysvar_instructions: parse_official_pubkey!(
kb_program_ids::SYSVAR_INSTRUCTIONS_PROGRAM_ID,
"sysvar_instructions"
),
spl_token_program: match spl_token_program {
std::option::Option::Some(value) => {
std::option::Option::Some(parse_official_pubkey!(&value.0, "spl_token_program"))
},
std::option::Option::None => std::option::Option::None,
},
}
.instruction(mpl_token_metadata::instructions::CreateInstructionArgs {
create_args: create_args.clone(),
});
return std::result::Result::Ok(convert_official_instruction!(official));
}
#[allow(clippy::too_many_arguments)]
fn build_update(
intent: &crate::ExMetaplexTokenMetadataExecutionIntent,
authority: &crate::MdPubkey,
mint: &crate::MdPubkey,
metadata: &crate::MdPubkey,
edition: std::option::Option<&crate::MdPubkey>,
token: std::option::Option<&crate::MdPubkey>,
authorization_rules_program: std::option::Option<&crate::MdProgramId>,
authorization_rules: std::option::Option<&crate::MdPubkey>,
update_args: &mpl_token_metadata::types::UpdateArgs,
) -> kb_core::Result<solana_instruction::Instruction> {
if !matches!(update_args, mpl_token_metadata::types::UpdateArgs::AsUpdateAuthorityV2 { .. }) {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_update_variant_not_yet_enabled",
"pre.003 enables only UpdateArgs::AsUpdateAuthorityV2; delegate variants are scheduled later",
));
}
let mint = match parse_pubkey(mint, "mint") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let metadata = match parse_pubkey(metadata, "metadata") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::result::Result::Err(error) = validate_metadata_pda(&metadata, &mint) {
return std::result::Result::Err(error);
}
let edition = match parse_optional_pubkey(edition, "edition") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::option::Option::Some(value) = edition {
if let std::result::Result::Err(error) = validate_edition_pda(&value, &mint) {
return std::result::Result::Err(error);
}
}
if authorization_rules_program.is_some() != authorization_rules.is_some() {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_rule_set_accounts_incomplete",
"authorization rules program and account must be supplied together",
));
}
let official = mpl_token_metadata::instructions::Update {
authority: parse_official_pubkey!(&authority.0, "authority"),
delegate_record: std::option::Option::None,
token: match token {
std::option::Option::Some(value) => {
std::option::Option::Some(parse_official_pubkey!(&value.0, "token"))
},
std::option::Option::None => std::option::Option::None,
},
mint: parse_official_pubkey!(&mint.to_string(), "mint"),
metadata: parse_official_pubkey!(&metadata.to_string(), "metadata"),
edition: match edition.as_ref() {
std::option::Option::Some(value) => {
std::option::Option::Some(parse_official_pubkey!(&value.to_string(), "edition"))
},
std::option::Option::None => std::option::Option::None,
},
payer: parse_official_pubkey!(&intent.fee_payer.0, "fee_payer"),
system_program: parse_official_pubkey!(kb_program_ids::SYSTEM_PROGRAM_ID, "system_program"),
sysvar_instructions: parse_official_pubkey!(
kb_program_ids::SYSVAR_INSTRUCTIONS_PROGRAM_ID,
"sysvar_instructions"
),
authorization_rules_program: match authorization_rules_program {
std::option::Option::Some(value) => std::option::Option::Some(parse_official_pubkey!(
&value.0,
"authorization_rules_program"
)),
std::option::Option::None => std::option::Option::None,
},
authorization_rules: match authorization_rules {
std::option::Option::Some(value) => {
std::option::Option::Some(parse_official_pubkey!(&value.0, "authorization_rules"))
},
std::option::Option::None => std::option::Option::None,
},
}
.instruction(mpl_token_metadata::instructions::UpdateInstructionArgs {
update_args: update_args.clone(),
});
return std::result::Result::Ok(convert_official_instruction!(official));
}
fn build_puff_metadata(
metadata: &crate::MdPubkey,
) -> kb_core::Result<solana_instruction::Instruction> {
let metadata = match parse_pubkey(metadata, "metadata") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let official = mpl_token_metadata::instructions::PuffMetadata {
metadata: parse_official_pubkey!(&metadata.to_string(), "metadata"),
}
.instruction();
return std::result::Result::Ok(convert_official_instruction!(official));
}
fn build_set_token_standard(
metadata: &crate::MdPubkey,
update_authority: &crate::MdPubkey,
mint: &crate::MdPubkey,
edition: std::option::Option<&crate::MdPubkey>,
) -> kb_core::Result<solana_instruction::Instruction> {
let metadata = match parse_pubkey(metadata, "metadata") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mint = match parse_pubkey(mint, "mint") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::result::Result::Err(error) = validate_metadata_pda(&metadata, &mint) {
return std::result::Result::Err(error);
}
let edition = match parse_optional_pubkey(edition, "edition") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::option::Option::Some(value) = edition {
if let std::result::Result::Err(error) = validate_edition_pda(&value, &mint) {
return std::result::Result::Err(error);
}
}
let official = mpl_token_metadata::instructions::SetTokenStandard {
metadata: parse_official_pubkey!(&metadata.to_string(), "metadata"),
update_authority: parse_official_pubkey!(&update_authority.0, "update_authority"),
mint: parse_official_pubkey!(&mint.to_string(), "mint"),
edition: match edition.as_ref() {
std::option::Option::Some(value) => {
std::option::Option::Some(parse_official_pubkey!(&value.to_string(), "edition"))
},
std::option::Option::None => std::option::Option::None,
},
}
.instruction();
return std::result::Result::Ok(convert_official_instruction!(official));
}
fn validate_metadata_pda(
metadata: &solana_pubkey::Pubkey,
mint: &solana_pubkey::Pubkey,
) -> kb_core::Result<()> {
let program_id = match parse_str_pubkey(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
"program_id",
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let (expected, _) = solana_pubkey::Pubkey::find_program_address(
&[b"metadata", program_id.as_ref(), mint.as_ref()],
&program_id,
);
if *metadata != expected {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_metadata_pda_mismatch",
format!("metadata PDA {} does not match expected {}", metadata, expected),
));
}
return std::result::Result::Ok(());
}
fn validate_edition_pda(
edition: &solana_pubkey::Pubkey,
mint: &solana_pubkey::Pubkey,
) -> kb_core::Result<()> {
let program_id = match parse_str_pubkey(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
"program_id",
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let (expected, _) = solana_pubkey::Pubkey::find_program_address(
&[b"metadata", program_id.as_ref(), mint.as_ref(), b"edition"],
&program_id,
);
if *edition != expected {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_edition_pda_mismatch",
format!("edition PDA {} does not match expected {}", edition, expected),
));
}
return std::result::Result::Ok(());
}
fn validate_authorized_signers(
intent: &crate::ExMetaplexTokenMetadataExecutionIntent,
required: &[crate::ExApiRequiredSigner],
) -> kb_core::Result<()> {
for signer in required {
if !intent.policy.authorized_signers.contains(&signer.pubkey) {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_signer_not_authorized",
format!("required signer {} is not authorized", signer.pubkey.0),
));
}
}
return std::result::Result::Ok(());
}
fn parse_pubkey(value: &crate::MdPubkey, field: &str) -> kb_core::Result<solana_pubkey::Pubkey> {
return parse_str_pubkey(&value.0, field);
}
fn parse_str_pubkey(value: &str, field: &str) -> kb_core::Result<solana_pubkey::Pubkey> {
return solana_pubkey::Pubkey::from_str(value).map_err(|error| {
return kb_core::Error::new(
"execution_metaplex_pubkey_invalid",
format!("invalid {field} public key: {error}"),
);
});
}
fn parse_optional_pubkey(
value: std::option::Option<&crate::MdPubkey>,
field: &str,
) -> kb_core::Result<std::option::Option<solana_pubkey::Pubkey>> {
return match value {
std::option::Option::Some(value) => {
parse_pubkey(value, field).map(std::option::Option::Some)
},
std::option::Option::None => std::result::Result::Ok(std::option::Option::None),
};
}
fn planned_instruction(
operation_code: &str,
instruction: &solana_instruction::Instruction,
) -> crate::ExApiPlannedInstruction {
return crate::ExApiPlannedInstruction {
program_id: crate::MdProgramId(instruction.program_id.to_string()),
operation_code: operation_code.to_string(),
accounts: instruction
.accounts
.iter()
.map(|account| {
return crate::ExApiPlannedAccount {
pubkey: crate::MdPubkey(account.pubkey.to_string()),
is_signer: account.is_signer,
is_writable: account.is_writable,
};
})
.collect(),
data: instruction.data.clone(),
};
}
fn required_signers(
fee_payer: &crate::MdPubkey,
accounts: &[solana_instruction::AccountMeta],
) -> std::vec::Vec<crate::ExApiRequiredSigner> {
let mut required = vec![crate::ExApiRequiredSigner {
pubkey: fee_payer.clone(),
role: "fee_payer".to_string(),
}];
for account in accounts {
if !account.is_signer {
continue;
}
let pubkey = crate::MdPubkey(account.pubkey.to_string());
if required.iter().any(|value| return value.pubkey == pubkey) {
continue;
}
required.push(crate::ExApiRequiredSigner {
pubkey,
role: "metaplex_authority".to_string(),
});
}
return required;
}
#[cfg(test)]
mod tests {
fn approved_puff_intent() -> crate::ExMetaplexTokenMetadataExecutionIntent {
let fee_payer = crate::MdPubkey("11111111111111111111111111111111".to_string());
let mut policy = crate::ExApiExecutionPolicy::default();
policy.cost_limit.max_fee_lamports = std::option::Option::Some(5_000);
policy.cost_limit.max_spend_lamports = std::option::Option::Some(10_000);
policy.authorized_signers = vec![fee_payer.clone()];
return crate::ExMetaplexTokenMetadataExecutionIntent {
intent_id: "metaplex-puff-test".to_string(),
fee_payer,
max_rent_lamports: 10_000,
policy,
allow_deprecated_operation: true,
operation: crate::ExMetaplexTokenMetadataOperation::PuffMetadata {
metadata: crate::MdPubkey(
"SysvarRent111111111111111111111111111111111".to_string(),
),
},
};
}
#[test]
#[allow(deprecated)]
fn deprecated_operation_requires_explicit_approval() {
let mut intent = approved_puff_intent();
intent.allow_deprecated_operation = false;
let result = super::validate_intent(&intent);
assert_eq!(
result.as_ref().err().map(kb_core::Error::code),
std::option::Option::Some("execution_metaplex_deprecated_operation_not_approved"),
);
}
#[test]
#[allow(deprecated)]
fn policy_requires_dry_run_and_sufficient_limits() {
let mut intent = approved_puff_intent();
intent.policy.dry_run = false;
let dry_run_result = super::validate_intent(&intent);
assert_eq!(
dry_run_result.as_ref().err().map(kb_core::Error::code),
std::option::Option::Some("execution_metaplex_dry_run_required"),
);
intent.policy.dry_run = true;
intent.policy.cost_limit.max_spend_lamports = std::option::Option::Some(9_999);
let spend_result = super::validate_intent(&intent);
assert_eq!(
spend_result.as_ref().err().map(kb_core::Error::code),
std::option::Option::Some("execution_metaplex_spend_limit_insufficient"),
);
}
#[test]
fn metadata_and_edition_pda_validation_is_exact() {
let program_id_result = super::parse_str_pubkey(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
"program_id",
);
assert!(program_id_result.is_ok());
let program_id = match program_id_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let mint = solana_pubkey::Pubkey::new_unique();
let (metadata, _) = solana_pubkey::Pubkey::find_program_address(
&[b"metadata", program_id.as_ref(), mint.as_ref()],
&program_id,
);
let (edition, _) = solana_pubkey::Pubkey::find_program_address(
&[b"metadata", program_id.as_ref(), mint.as_ref(), b"edition"],
&program_id,
);
assert!(super::validate_metadata_pda(&metadata, &mint).is_ok());
assert!(super::validate_edition_pda(&edition, &mint).is_ok());
let wrong = solana_pubkey::Pubkey::new_unique();
let metadata_result = super::validate_metadata_pda(&wrong, &mint);
assert_eq!(
metadata_result.as_ref().err().map(kb_core::Error::code),
std::option::Option::Some("execution_metaplex_metadata_pda_mismatch"),
);
let edition_result = super::validate_edition_pda(&wrong, &mint);
assert_eq!(
edition_result.as_ref().err().map(kb_core::Error::code),
std::option::Option::Some("execution_metaplex_edition_pda_mismatch"),
);
}
#[test]
#[allow(deprecated)]
fn puff_metadata_builds_one_exact_instruction_and_deduplicates_fee_payer() {
let intent = approved_puff_intent();
let result = super::executor_metaplex_token_metadata_build_prepared_plan(&intent);
assert!(result.is_ok());
let plan = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(plan.operation_code, crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,);
assert_eq!(plan.instructions.len(), 1);
assert_eq!(
plan.instructions[0].program_id.0,
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
);
assert_eq!(plan.instructions[0].data.first().copied(), std::option::Option::Some(14),);
assert_eq!(plan.required_signers.len(), 1);
assert_eq!(plan.required_signers[0].pubkey, intent.fee_payer);
assert_eq!(plan.required_signers[0].role, "fee_payer");
}
}

View File

@@ -0,0 +1,251 @@
// file: kb-lib/src/executor/metadata/metaplex_token_metadata/executor.rs
// version: 3
//! Exact Metaplex Token Metadata capability dispatch.
/// Metaplex Token Metadata executor implementation.
#[derive(Clone, Debug, Default)]
pub struct ExMetadataMetaplexTokenMetadataExecutor;
impl crate::ExMetadataMetaplexTokenMetadataExecutor {
fn exact_capability(
&self,
program_id: &crate::MdProgramId,
operation_code: &str,
) -> crate::ExApiExecutionCapability {
if program_id.0 != kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID {
return crate::ExApiExecutionCapability::unsupported(
"execution_metaplex_program_not_owned",
format!("program {} is not Metaplex Token Metadata", program_id.0),
);
}
if crate::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES.contains(&operation_code) {
return crate::ExApiExecutionCapability::supported(operation_code);
}
return crate::ExApiExecutionCapability::unsupported(
"execution_metaplex_operation_unsupported",
format!("Metaplex Token Metadata operation {operation_code} is not enabled"),
);
}
}
impl crate::ExApiTypedInstructionExecutor for crate::ExMetadataMetaplexTokenMetadataExecutor {
type Intent = crate::ExMetaplexTokenMetadataExecutionIntent;
fn capability(
&self,
program_id: &crate::MdProgramId,
operation_code: &str,
) -> crate::ExApiExecutionCapability {
return self.exact_capability(program_id, operation_code);
}
fn build_prepared_plan(
&self,
intent: &Self::Intent,
) -> kb_core::Result<crate::ExApiPreparedExecutionPlan> {
return crate::executor_metaplex_token_metadata_build_prepared_plan(intent);
}
}
impl crate::ExApiInstructionExecutor for crate::ExMetadataMetaplexTokenMetadataExecutor {
fn executor_name(&self) -> &'static str {
return "kb-lib.executor.metadata.metaplex_token_metadata";
}
fn executor_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn program_ids(&self) -> &'static [&'static str] {
return &[kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID];
}
fn supports_request(
&self,
request: &crate::ExApiExecutionRequest,
) -> crate::ExApiExecutionSupport {
return match self.exact_capability(&request.program_id, &request.operation_code) {
crate::ExApiExecutionCapability::Supported { operation_code: _ } => {
crate::ExApiExecutionSupport::Yes
},
crate::ExApiExecutionCapability::Unsupported { reason_code: _, reason: _ } => {
crate::ExApiExecutionSupport::No
},
};
}
fn build_plan(
&self,
request: &crate::ExApiExecutionRequest,
) -> kb_core::Result<crate::ExApiExecutionPlan> {
match self.exact_capability(&request.program_id, &request.operation_code) {
crate::ExApiExecutionCapability::Supported { operation_code: _ } => {},
crate::ExApiExecutionCapability::Unsupported { reason_code, reason } => {
return std::result::Result::Err(kb_core::Error::new(reason_code, reason));
},
}
let intent = match serde_json::from_str::<crate::ExMetaplexTokenMetadataExecutionIntent>(
&request.payload_json,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_intent_deserialize_failed",
error.to_string(),
));
},
};
if intent.operation.operation_code() != request.operation_code {
return std::result::Result::Err(kb_core::Error::new(
"execution_operation_code_mismatch",
"request operation code does not match the typed Metaplex intent",
));
}
let prepared = match crate::executor_metaplex_token_metadata_build_prepared_plan(&intent) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let payload_value = match serde_json::to_value(&prepared) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_metaplex_plan_serialize_failed",
error.to_string(),
));
},
};
let payload_json = match crate::executor_api_serialize_payload_json(&payload_value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::ExApiExecutionPlan {
executor_name: self.executor_name().to_string(),
instruction_count: prepared.instructions.len(),
payload_json,
});
}
}
#[cfg(test)]
mod tests {
fn approved_puff_intent() -> crate::ExMetaplexTokenMetadataExecutionIntent {
let fee_payer = crate::MdPubkey("11111111111111111111111111111111".to_string());
let mut policy = crate::ExApiExecutionPolicy::default();
policy.cost_limit.max_fee_lamports = std::option::Option::Some(5_000);
policy.cost_limit.max_spend_lamports = std::option::Option::Some(10_000);
policy.authorized_signers = vec![fee_payer.clone()];
return crate::ExMetaplexTokenMetadataExecutionIntent {
intent_id: "metaplex-executor-test".to_string(),
fee_payer,
max_rent_lamports: 10_000,
policy,
allow_deprecated_operation: true,
operation: crate::ExMetaplexTokenMetadataOperation::PuffMetadata {
metadata: crate::MdPubkey(
"SysvarRent111111111111111111111111111111111".to_string(),
),
},
};
}
#[test]
fn capability_is_exact_for_program_and_operation() {
let executor = crate::ExMetadataMetaplexTokenMetadataExecutor;
let program_id = crate::MdProgramId(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(),
);
let supported = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiTypedInstructionExecutor>::capability(
&executor,
&program_id,
crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,
);
assert!(matches!(supported, crate::ExApiExecutionCapability::Supported { .. }));
let unknown = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiTypedInstructionExecutor>::capability(
&executor,
&program_id,
"metadata.metaplex_token_metadata.unknown",
);
assert!(matches!(unknown, crate::ExApiExecutionCapability::Unsupported { .. }));
let foreign = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiTypedInstructionExecutor>::capability(
&executor,
&crate::MdProgramId(kb_program_ids::SYSTEM_PROGRAM_ID.to_string()),
crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,
);
assert!(matches!(foreign, crate::ExApiExecutionCapability::Unsupported { .. }));
}
#[test]
#[allow(deprecated)]
fn generic_dispatch_rejects_operation_code_mismatch() {
let executor = crate::ExMetadataMetaplexTokenMetadataExecutor;
let intent = approved_puff_intent();
let payload_result = serde_json::to_string(&intent);
assert!(payload_result.is_ok());
let payload_json = match payload_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let request = crate::ExApiExecutionRequest {
program_id: crate::MdProgramId(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(),
),
operation_code: crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
payload_json,
};
let result = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiInstructionExecutor>::build_plan(
&executor,
&request,
);
assert_eq!(
result.as_ref().err().map(kb_core::Error::code),
std::option::Option::Some("execution_operation_code_mismatch"),
);
}
#[test]
#[allow(deprecated)]
fn typed_and_generic_dispatch_build_the_same_single_instruction_plan() {
let executor = crate::ExMetadataMetaplexTokenMetadataExecutor;
let intent = approved_puff_intent();
let typed_result = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiTypedInstructionExecutor>::build_prepared_plan(
&executor,
&intent,
);
assert!(typed_result.is_ok());
let typed = match typed_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let payload_result = serde_json::to_string(&intent);
assert!(payload_result.is_ok());
let payload_json = match payload_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let request = crate::ExApiExecutionRequest {
program_id: crate::MdProgramId(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(),
),
operation_code: crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION.to_string(),
payload_json,
};
let generic_result = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiInstructionExecutor>::build_plan(
&executor,
&request,
);
assert!(generic_result.is_ok());
let generic = match generic_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(typed.instructions.len(), 1);
assert_eq!(generic.instruction_count, typed.instructions.len());
assert_eq!(generic.executor_name, "kb-lib.executor.metadata.metaplex_token_metadata",);
assert!(
generic
.payload_json
.contains(crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION)
);
}
}

View File

@@ -0,0 +1,204 @@
// file: kb-lib/src/executor/metadata/metaplex_token_metadata/intent.rs
// version: 3
//! Typed current and deprecated Metaplex Token Metadata execution intents.
use ts_rs::TS; // rust-rules: derive-import
/// Stable operation code for the current Create wrapper, discriminator 42.
pub const EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION: &str =
"metadata.metaplex_token_metadata.create";
/// Stable operation code for the current Update wrapper, discriminator 50.
pub const EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION: &str =
"metadata.metaplex_token_metadata.update";
/// Stable deprecated operation code for PuffMetadata, discriminator 14.
pub const EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION: &str =
"metadata.metaplex_token_metadata.puff_metadata";
/// Stable deprecated operation code for SetTokenStandard, discriminator 35.
pub const EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION: &str =
"metadata.metaplex_token_metadata.set_token_standard";
/// Deprecated operations that require explicit operator approval at runtime.
pub const EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES: &[&str] = &[
EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,
EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION,
];
/// Current and deprecated operations implemented by `0.4.7-pre.003`.
pub const EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES: &[&str] = &[
EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,
EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION,
EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,
EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION,
];
/// One current or explicitly deprecated Metaplex Token Metadata operation.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[serde(tag = "operation", rename_all = "snake_case")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/metaplex_token_metadata/intent/ExMetaplexTokenMetadataOperation.ts"
)]
pub enum ExMetaplexTokenMetadataOperation {
/// Build the current Create wrapper with `CreateArgs::V1`.
Create {
/// Canonical metadata PDA.
metadata: crate::MdPubkey,
/// Optional canonical master edition PDA.
master_edition: std::option::Option<crate::MdPubkey>,
/// Asset mint.
mint: crate::MdPubkey,
/// Whether the mint account signs this create operation.
mint_as_signer: bool,
/// Mint authority signer.
authority: crate::MdPubkey,
/// Update authority.
update_authority: crate::MdPubkey,
/// Whether the update authority signs this create operation.
update_authority_as_signer: bool,
/// Optional SPL Token or Token-2022 program account.
spl_token_program: std::option::Option<crate::MdProgramId>,
/// Official Metaplex create arguments.
#[ts(type = "unknown")]
create_args: mpl_token_metadata::types::CreateArgs,
},
/// Build the current Update wrapper as the update authority.
UpdateAsUpdateAuthorityV2 {
/// Update authority signer.
authority: crate::MdPubkey,
/// Asset mint.
mint: crate::MdPubkey,
/// Canonical metadata PDA.
metadata: crate::MdPubkey,
/// Optional canonical edition PDA.
edition: std::option::Option<crate::MdPubkey>,
/// Optional token account needed by programmable assets.
token: std::option::Option<crate::MdPubkey>,
/// Optional Token Authorization Rules program.
authorization_rules_program: std::option::Option<crate::MdProgramId>,
/// Optional Token Authorization Rules account.
authorization_rules: std::option::Option<crate::MdPubkey>,
/// Official Metaplex update arguments. Only `AsUpdateAuthorityV2` is accepted in this prerelease.
#[ts(type = "unknown")]
update_args: mpl_token_metadata::types::UpdateArgs,
},
/// Build the obsolete PuffMetadata instruction retained for compatibility workflows.
#[deprecated(
since = "0.4.7",
note = "PuffMetadata is obsolete; use only for explicit compatibility workflows"
)]
PuffMetadata {
/// Metadata account to pad to the historical fixed-length representation.
metadata: crate::MdPubkey,
},
/// Build the obsolete SetTokenStandard instruction retained for compatibility workflows.
#[deprecated(
since = "0.4.7",
note = "SetTokenStandard is obsolete; prefer canonical Create or Update flows"
)]
SetTokenStandard {
/// Canonical metadata PDA.
metadata: crate::MdPubkey,
/// Metadata update authority signer.
update_authority: crate::MdPubkey,
/// Asset mint.
mint: crate::MdPubkey,
/// Optional canonical edition PDA.
edition: std::option::Option<crate::MdPubkey>,
},
}
#[allow(deprecated)]
impl crate::ExMetaplexTokenMetadataOperation {
/// Returns the stable generic dispatch operation code.
pub fn operation_code(&self) -> &'static str {
return match self {
Self::Create { .. } => crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,
Self::UpdateAsUpdateAuthorityV2 { .. } => {
crate::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION
},
Self::PuffMetadata { .. } => crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,
Self::SetTokenStandard { .. } => {
crate::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION
},
};
}
/// Returns whether the operation is obsolete and requires explicit operator approval.
pub fn is_deprecated(&self) -> bool {
return matches!(self, Self::PuffMetadata { .. } | Self::SetTokenStandard { .. });
}
}
/// Complete typed intent accepted by the Metaplex Token Metadata executor.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/metaplex_token_metadata/intent/ExMetaplexTokenMetadataExecutionIntent.ts"
)]
pub struct ExMetaplexTokenMetadataExecutionIntent {
/// Stable caller-provided identifier used for logs and replay correlation.
pub intent_id: std::string::String,
/// Transaction fee payer.
pub fee_payer: crate::MdPubkey,
/// Maximum rent or account-growth spend allowed for the operation.
pub max_rent_lamports: u64,
/// Conservative common execution policy.
pub policy: crate::ExApiExecutionPolicy,
/// Explicit operator approval required for every deprecated operation.
pub allow_deprecated_operation: bool,
/// Typed Metaplex operation.
pub operation: crate::ExMetaplexTokenMetadataOperation,
}
#[cfg(test)]
mod tests {
#[test]
#[allow(deprecated)]
fn operation_codes_are_exact_unique_and_partition_deprecated_operations() {
assert_eq!(crate::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES.len(), 4);
let mut unique = std::collections::BTreeSet::new();
for operation_code in crate::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES {
assert!(unique.insert(*operation_code));
}
assert_eq!(crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES.len(), 2);
assert!(
crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES
.contains(&crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,)
);
assert!(
crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES
.contains(&crate::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION,)
);
assert!(
!crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES
.contains(&crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,)
);
assert!(
!crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES
.contains(&crate::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION,)
);
}
#[test]
#[allow(deprecated)]
fn deprecated_operations_report_their_exact_codes_and_status() {
let puff = crate::ExMetaplexTokenMetadataOperation::PuffMetadata {
metadata: crate::MdPubkey("11111111111111111111111111111111".to_string()),
};
let set_token_standard = crate::ExMetaplexTokenMetadataOperation::SetTokenStandard {
metadata: crate::MdPubkey("11111111111111111111111111111111".to_string()),
update_authority: crate::MdPubkey("11111111111111111111111111111111".to_string()),
mint: crate::MdPubkey("11111111111111111111111111111111".to_string()),
edition: std::option::Option::None,
};
assert_eq!(
puff.operation_code(),
crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,
);
assert_eq!(
set_token_standard.operation_code(),
crate::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION,
);
assert!(puff.is_deprecated());
assert!(set_token_standard.is_deprecated());
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/lib.rs
// version: 26
// version: 28
//! Consolidated decoder, executor, materializer and shared model library.
#![warn(missing_docs)]
@@ -358,6 +358,18 @@ pub use self::decoder::decoder_metadata_metaplex_token_metadata_decode_use_autho
pub use self::decoder::decoder_spl_elgamal_registry_parse_elgamal_registry_state;
/// Parses one Token-2022 Mint, Account, or Multisig state.
pub use self::decoder::decoder_spl_token_2022_parse_token_2022_state;
/// Stable operation code for the current Metaplex Create wrapper.
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION;
/// Deprecated Metaplex operation codes requiring explicit approval.
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES;
/// Stable deprecated Metaplex PuffMetadata operation code.
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION;
/// Stable deprecated Metaplex SetTokenStandard operation code.
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION;
/// Current and deprecated Metaplex Token Metadata operations.
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES;
/// Stable operation code for the current Metaplex Update wrapper.
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION;
/// Exposes the Address Lookup Table close operation code.
pub use self::executor::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION;
/// Exposes the Address Lookup Table create operation code.
@@ -1016,12 +1028,16 @@ pub use self::executor::ExLendingKaminoExecutor;
pub use self::executor::ExLendingMarginfiV2Executor;
/// Exposes the reserved `ExLockRaydiumLpExecutor` implementation.
pub use self::executor::ExLockRaydiumLpExecutor;
/// Exposes the reserved `ExMetadataMetaplexTokenMetadataExecutor` implementation.
/// Exposes the Metaplex Token Metadata executor.
pub use self::executor::ExMetadataMetaplexTokenMetadataExecutor;
/// Exposes the reserved `ExMetadataSolanaProgramMetadataExecutor` implementation.
pub use self::executor::ExMetadataSolanaProgramMetadataExecutor;
/// Exposes the reserved `ExMetadataSplNameServiceExecutor` implementation.
pub use self::executor::ExMetadataSplNameServiceExecutor;
/// Exposes the complete Metaplex Token Metadata execution intent.
pub use self::executor::ExMetaplexTokenMetadataExecutionIntent;
/// Exposes one current Metaplex Token Metadata operation.
pub use self::executor::ExMetaplexTokenMetadataOperation;
/// Exposes the reserved `ExNftMetaplexBubblegumExecutor` implementation.
pub use self::executor::ExNftMetaplexBubblegumExecutor;
/// Exposes the reserved `ExNftTensorCnftExecutor` implementation.
@@ -1833,6 +1849,8 @@ pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_MEMO;
pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_TOKEN;
/// Canonical SPL Token-2022 tracing target.
pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_TOKEN_2022;
/// Internal Metaplex Token Metadata prepared-plan builder.
pub(crate) use self::executor::executor_metaplex_token_metadata_build_prepared_plan;
/// Crate-root access to `build_prepared_plan` from `address_lookup_table`.
pub(crate) use self::executor::executor_solana_core_address_lookup_table_build_prepared_plan;
/// Crate-root access to `build_prepared_plan` from `builder`.