use std::convert::TryFrom;
use anchor_lang::solana_program::program::invoke;
use anchor_spl::{
associated_token::AssociatedToken,
token::{Mint, Token, TokenAccount},
};
use crate::errors::ErrorCode;
use anchor_lang::prelude::borsh;
use anchor_lang::prelude::*;
use mpl_token_auth_rules::payload::{Payload, PayloadType, ProofInfo, SeedsVec};
use mpl_token_metadata::{
self,
instruction::{builders::TransferBuilder, InstructionBuilder, TransferArgs},
processor::AuthorizationData,
state::{Metadata, ProgrammableConfig::V1, TokenMetadataAccount, TokenStandard},
};
use solana_program::program::invoke_signed;
#[derive(Accounts)]
pub struct ProgNftShared<'info> {
#[account(address = mpl_token_metadata::id())]
pub token_metadata_program: UncheckedAccount<'info>,
#[account(address = anchor_lang::solana_program::sysvar::instructions::ID)]
pub instructions: UncheckedAccount<'info>,
#[account(address = mpl_token_auth_rules::id())]
pub authorization_rules_program: UncheckedAccount<'info>,
}
#[inline(never)]
pub fn assert_decode_metadata<'info>(
nft_mint: &Account<'info, Mint>,
metadata_account: &AccountInfo<'info>,
) -> Result<Metadata> {
let (key, _) = Pubkey::find_program_address(
&[
mpl_token_metadata::state::PREFIX.as_bytes(),
mpl_token_metadata::id().as_ref(),
nft_mint.key().as_ref(),
],
&mpl_token_metadata::id(),
);
if key != *metadata_account.key {
return Err(error!(ErrorCode::InvalidMetadata));
}
if *metadata_account.owner != mpl_token_metadata::id() {
return Err(error!(ErrorCode::InvalidMetadata));
}
Ok(Metadata::from_account_info(metadata_account)?)
}
#[allow(clippy::too_many_arguments)]
pub fn send_pnft<'info>(
authority_and_owner: &AccountInfo<'info>,
payer: &AccountInfo<'info>,
source_ata: &Account<'info, TokenAccount>,
dest_ata: &Account<'info, TokenAccount>,
dest_owner: &AccountInfo<'info>,
nft_mint: &Account<'info, Mint>,
nft_metadata: &UncheckedAccount<'info>,
nft_edition: &UncheckedAccount<'info>,
system_program: &Program<'info, System>,
token_program: &Program<'info, Token>,
ata_program: &Program<'info, AssociatedToken>,
instructions: &UncheckedAccount<'info>,
owner_token_record: &UncheckedAccount<'info>,
dest_token_record: &UncheckedAccount<'info>,
authorization_rules_program: &UncheckedAccount<'info>,
rules_acc: Option<&AccountInfo<'info>>,
authorization_data: Option<AuthorizationDataLocal>,
signer_seeds: Option<&[&[u8]]>,
) -> Result<()> {
let mut builder = TransferBuilder::new();
builder
.authority(authority_and_owner.key())
.token_owner(authority_and_owner.key())
.token(source_ata.key())
.destination_owner(dest_owner.key())
.destination(dest_ata.key())
.mint(nft_mint.key())
.metadata(nft_metadata.key())
.edition(nft_edition.key())
.payer(payer.key());
let mut account_infos = vec![
source_ata.to_account_info(),
authority_and_owner.to_account_info(),
dest_ata.to_account_info(),
dest_owner.to_account_info(),
nft_mint.to_account_info(),
nft_metadata.to_account_info(),
nft_edition.to_account_info(),
authority_and_owner.to_account_info(),
payer.to_account_info(),
system_program.to_account_info(),
instructions.to_account_info(),
token_program.to_account_info(),
ata_program.to_account_info(),
];
let metadata = assert_decode_metadata(nft_mint, &nft_metadata.to_account_info())?;
if let Some(standard) = metadata.token_standard {
if standard == TokenStandard::ProgrammableNonFungible {
builder
.owner_token_record(owner_token_record.key())
.destination_token_record(dest_token_record.key());
account_infos.push(owner_token_record.to_account_info());
account_infos.push(dest_token_record.to_account_info());
}
}
if let Some(config) = metadata.programmable_config {
match config {
V1 { rule_set } => {
if let Some(rule_set) = rule_set {
msg!("ruleset triggered");
let rules_acc = rules_acc.unwrap();
if rule_set != *rules_acc.key {
return Err(error!(ErrorCode::BadRuleset));
}
builder.authorization_rules_program(*authorization_rules_program.key);
builder.authorization_rules(*rules_acc.key);
account_infos.push(authorization_rules_program.to_account_info());
account_infos.push(rules_acc.to_account_info());
}
}
}
}
let transfer_ix = builder
.build(TransferArgs::V1 {
amount: 1, authorization_data: authorization_data
.map(|authorization_data| AuthorizationData::try_from(authorization_data).unwrap()),
})
.unwrap()
.instruction();
if let Some(signer_seeds) = signer_seeds {
invoke_signed(&transfer_ix, &account_infos, &[&signer_seeds])?;
} else {
invoke(&transfer_ix, &account_infos)?;
}
msg!("transfer complete");
Ok(())
}
#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
pub struct AuthorizationDataLocal {
pub payload: Vec<TaggedPayload>,
}
impl From<AuthorizationDataLocal> for AuthorizationData {
fn from(val: AuthorizationDataLocal) -> Self {
let mut p = Payload::new();
val.payload.into_iter().for_each(|tp| {
p.insert(tp.name, PayloadType::try_from(tp.payload).unwrap());
});
AuthorizationData { payload: p }
}
}
#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
pub struct TaggedPayload {
name: String,
payload: PayloadTypeLocal,
}
#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
pub enum PayloadTypeLocal {
Pubkey(Pubkey),
Seeds(SeedsVecLocal),
MerkleProof(ProofInfoLocal),
Number(u64),
}
impl From<PayloadTypeLocal> for PayloadType {
fn from(val: PayloadTypeLocal) -> Self {
match val {
PayloadTypeLocal::Pubkey(pubkey) => PayloadType::Pubkey(pubkey),
PayloadTypeLocal::Seeds(seeds) => {
PayloadType::Seeds(SeedsVec::try_from(seeds).unwrap())
}
PayloadTypeLocal::MerkleProof(proof) => {
PayloadType::MerkleProof(ProofInfo::try_from(proof).unwrap())
}
PayloadTypeLocal::Number(number) => PayloadType::Number(number),
}
}
}
#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
pub struct SeedsVecLocal {
pub seeds: Vec<Vec<u8>>,
}
impl From<SeedsVecLocal> for SeedsVec {
fn from(val: SeedsVecLocal) -> Self {
SeedsVec { seeds: val.seeds }
}
}
#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
pub struct ProofInfoLocal {
pub proof: Vec<[u8; 32]>,
}
impl From<ProofInfoLocal> for ProofInfo {
fn from(val: ProofInfoLocal) -> Self {
ProofInfo { proof: val.proof }
}
}