use anyhow::Result;
use mpl_token_metadata::{
instructions::{UpdateV1, UpdateV1InstructionArgs},
types::{
AuthorizationData, CollectionDetailsToggle, CollectionToggle, Data, ProgrammableConfig,
RuleSetToggle, TokenStandard, UsesToggle,
},
};
use solana_client::rpc_client::RpcClient;
use solana_program::{instruction::Instruction, pubkey::Pubkey};
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
signature::{Keypair, Signature},
signer::Signer,
};
use crate::{
data::{Asset, Priority, UPDATE_COMPUTE_UNITS},
decode::ToPubkey,
nft::get_nft_token_account,
transaction::{get_compute_units, send_and_confirm_tx},
};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct V1UpdateArgs {
pub new_update_authority: Option<Pubkey>,
pub data: Option<Data>,
pub primary_sale_happened: Option<bool>,
pub is_mutable: Option<bool>,
pub collection: CollectionToggle,
pub collection_details: CollectionDetailsToggle,
pub uses: UsesToggle,
pub rule_set: RuleSetToggle,
pub authorization_data: Option<AuthorizationData>,
}
impl Default for V1UpdateArgs {
fn default() -> Self {
Self {
new_update_authority: None,
data: None,
primary_sale_happened: None,
is_mutable: None,
collection: CollectionToggle::None,
collection_details: CollectionDetailsToggle::None,
uses: UsesToggle::None,
rule_set: RuleSetToggle::None,
authorization_data: None,
}
}
}
impl From<V1UpdateArgs> for UpdateV1InstructionArgs {
fn from(args: V1UpdateArgs) -> Self {
let V1UpdateArgs {
new_update_authority,
data,
primary_sale_happened,
is_mutable,
collection,
collection_details,
uses,
rule_set,
authorization_data,
} = args;
Self {
new_update_authority,
data,
primary_sale_happened,
is_mutable,
collection,
collection_details,
uses,
rule_set,
authorization_data,
}
}
}
pub enum UpdateAssetArgs<'a, P1, P2, P3: ToPubkey> {
V1 {
payer: Option<&'a Keypair>,
authority: &'a Keypair,
mint: P1,
token: Option<P2>,
delegate_record: Option<P3>,
update_args: V1UpdateArgs,
priority: Priority,
},
}
pub fn update_asset<P1, P2, P3>(
client: &RpcClient,
args: UpdateAssetArgs<P1, P2, P3>,
) -> Result<Signature>
where
P1: ToPubkey,
P2: ToPubkey,
P3: ToPubkey,
{
match args {
UpdateAssetArgs::V1 { .. } => update_asset_v1(client, args),
}
}
pub fn update_asset_ix<P1, P2, P3>(
client: &RpcClient,
args: UpdateAssetArgs<P1, P2, P3>,
) -> Result<Instruction>
where
P1: ToPubkey,
P2: ToPubkey,
P3: ToPubkey,
{
match args {
UpdateAssetArgs::V1 { .. } => update_asset_v1_ix(client, args),
}
}
fn update_asset_v1<P1, P2, P3>(
client: &RpcClient,
args: UpdateAssetArgs<P1, P2, P3>,
) -> Result<Signature>
where
P1: ToPubkey,
P2: ToPubkey,
P3: ToPubkey,
{
let UpdateAssetArgs::V1 {
payer,
authority,
ref priority,
..
} = args;
let payer = payer.unwrap_or(authority);
let micro_lamports = match priority {
Priority::None => 20, Priority::Low => 20_000, Priority::Medium => 200_000, Priority::High => 1_000_000, Priority::Max => 2_000_000, };
let signers = vec![payer, authority];
let update_ix = update_asset_v1_ix(client, args)?;
let units = get_compute_units(client, std::slice::from_ref(&update_ix), &signers)?
.unwrap_or(UPDATE_COMPUTE_UNITS as u64);
let instructions = vec![
ComputeBudgetInstruction::set_compute_unit_limit(units as u32),
ComputeBudgetInstruction::set_compute_unit_price(micro_lamports),
update_ix,
];
send_and_confirm_tx(client, &signers, &instructions)
}
fn update_asset_v1_ix<P1, P2, P3>(
client: &RpcClient,
args: UpdateAssetArgs<P1, P2, P3>,
) -> Result<Instruction>
where
P1: ToPubkey,
P2: ToPubkey,
P3: ToPubkey,
{
let UpdateAssetArgs::V1 {
payer,
authority,
mint,
token,
delegate_record,
update_args,
..
} = args;
let payer = payer.unwrap_or(authority);
let mint = mint.to_pubkey()?;
let mut asset = Asset::new(mint);
let md = asset.get_metadata(client)?;
let token = token.map(|t| t.to_pubkey()).transpose()?;
let delegate_record = delegate_record.map(|t| t.to_pubkey()).transpose()?;
let token =
if md.token_standard == Some(TokenStandard::ProgrammableNonFungible) && token.is_none() {
Some(get_nft_token_account(client, &mint.to_string())?)
} else {
None
};
if matches!(
md.token_standard,
Some(
TokenStandard::NonFungible
| TokenStandard::NonFungibleEdition
| TokenStandard::ProgrammableNonFungible
) | None
) {
asset.add_edition();
};
let (authorization_rules, authorization_rules_program) = if let Some(ProgrammableConfig::V1 {
rule_set: Some(rule_set),
}) = md.programmable_config
{
(
Some(rule_set),
Some(crate::constants::AUTH_RULES_PROGRAM_ID),
)
} else {
(None, None)
};
let update_ix = UpdateV1 {
payer: payer.pubkey(),
authority: authority.pubkey(),
mint: asset.mint,
metadata: asset.metadata,
delegate_record,
token,
edition: asset.edition,
system_program: crate::constants::SYSTEM_PROGRAM_ID,
sysvar_instructions: solana_program::sysvar::instructions::ID,
authorization_rules,
authorization_rules_program,
}
.instruction(update_args.into());
Ok(update_ix)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_v1_update_args_default() {
let args = V1UpdateArgs::default();
assert_eq!(args.new_update_authority, None);
assert_eq!(args.data, None);
assert_eq!(args.primary_sale_happened, None);
assert_eq!(args.is_mutable, None);
assert_eq!(args.collection, CollectionToggle::None);
assert_eq!(args.collection_details, CollectionDetailsToggle::None);
assert_eq!(args.uses, UsesToggle::None);
assert_eq!(args.rule_set, RuleSetToggle::None);
assert_eq!(args.authorization_data, None);
}
#[test]
fn test_v1_update_args_into_instruction_args() {
let args = V1UpdateArgs {
is_mutable: Some(false),
primary_sale_happened: Some(true),
..V1UpdateArgs::default()
};
let ix_args: UpdateV1InstructionArgs = args.into();
assert_eq!(ix_args.is_mutable, Some(false));
assert_eq!(ix_args.primary_sale_happened, Some(true));
assert_eq!(ix_args.new_update_authority, None);
assert_eq!(ix_args.data, None);
assert_eq!(ix_args.collection, CollectionToggle::None);
assert_eq!(ix_args.collection_details, CollectionDetailsToggle::None);
assert_eq!(ix_args.uses, UsesToggle::None);
assert_eq!(ix_args.rule_set, RuleSetToggle::None);
assert_eq!(ix_args.authorization_data, None);
}
}