v0.4.7-pre.003
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
637
kb-lib/src/executor/metadata/metaplex_token_metadata/builder.rs
Normal file
637
kb-lib/src/executor/metadata/metaplex_token_metadata/builder.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
251
kb-lib/src/executor/metadata/metaplex_token_metadata/executor.rs
Normal file
251
kb-lib/src/executor/metadata/metaplex_token_metadata/executor.rs
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
204
kb-lib/src/executor/metadata/metaplex_token_metadata/intent.rs
Normal file
204
kb-lib/src/executor/metadata/metaplex_token_metadata/intent.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user