use mpl_token_metadata::utils::assert_derivation;
use {
crate::{errors::ErrorCode, state::*},
anchor_lang::prelude::*,
anchor_spl::token::Mint,
mpl_token_metadata::state::Metadata,
solana_program::{program::invoke, system_instruction::transfer},
};
#[derive(Accounts)]
#[instruction(amount: u64)]
pub struct HandleNativePaymentWithRoyaltiesCtx<'info> {
#[account(mut)]
payment_manager: Box<Account<'info, PaymentManager>>,
#[account(mut, constraint = fee_collector.key() == payment_manager.fee_collector @ ErrorCode::InvalidFeeCollector)]
fee_collector: UncheckedAccount<'info>,
#[account(mut)]
payment_target: UncheckedAccount<'info>,
#[account(mut)]
payer: Signer<'info>,
mint: Box<Account<'info, Mint>>,
mint_metadata: AccountInfo<'info>,
system_program: Program<'info, System>,
}
pub fn handler<'key, 'accounts, 'remaining, 'info>(ctx: Context<'key, 'accounts, 'remaining, 'info, HandleNativePaymentWithRoyaltiesCtx<'info>>, payment_amount: u64) -> Result<()> {
let payment_manager = &mut ctx.accounts.payment_manager;
let maker_fee = payment_amount
.checked_mul(payment_manager.maker_fee_basis_points.into())
.expect("Multiplication error")
.checked_div(BASIS_POINTS_DIVISOR.into())
.expect("Division error");
let taker_fee = payment_amount
.checked_mul(payment_manager.taker_fee_basis_points.into())
.expect("Multiplication error")
.checked_div(BASIS_POINTS_DIVISOR.into())
.expect("Division error");
let mut total_fees = maker_fee.checked_add(taker_fee).expect("Add error");
assert_derivation(
&mpl_token_metadata::id(),
&ctx.accounts.mint_metadata.to_account_info(),
&[mpl_token_metadata::state::PREFIX.as_bytes(), mpl_token_metadata::id().as_ref(), ctx.accounts.mint.key().as_ref()],
)?;
let mut fees_paid_out: u64 = 0;
let remaining_accs = &mut ctx.remaining_accounts.iter();
if !ctx.accounts.mint_metadata.data_is_empty() {
if ctx.accounts.mint_metadata.to_account_info().owner.key() != mpl_token_metadata::id() {
return Err(error!(ErrorCode::InvalidMintMetadataOwner));
}
let mint_metadata_data = ctx.accounts.mint_metadata.try_borrow_mut_data().expect("Failed to borrow data");
let mint_metadata = Metadata::deserialize(&mut mint_metadata_data.as_ref()).expect("Failed to deserialize metadata");
if mint_metadata.mint != ctx.accounts.mint.key() {
return Err(error!(ErrorCode::InvalidMintMetadata));
}
let seller_fee = if payment_manager.include_seller_fee_basis_points {
payment_amount
.checked_mul(mint_metadata.data.seller_fee_basis_points.into())
.expect("Multiplication error")
.checked_div(BASIS_POINTS_DIVISOR.into())
.expect("Division error")
} else {
0
};
let total_creators_fee = total_fees
.checked_mul(payment_manager.royalty_fee_share.unwrap_or(DEFAULT_ROYALTY_FEE_SHARE))
.unwrap()
.checked_div(BASIS_POINTS_DIVISOR.into())
.expect("Div error")
.checked_add(seller_fee)
.expect("Add error");
total_fees = total_fees.checked_add(seller_fee).expect("Add error");
if let Some(creators) = mint_metadata.data.creators {
let creator_amounts: Vec<u64> = creators
.clone()
.into_iter()
.map(|creator| total_creators_fee.checked_mul(u64::try_from(creator.share).expect("Could not cast u8 to u64")).unwrap())
.collect();
let creator_amounts_sum: u64 = creator_amounts.iter().sum();
let mut creators_fee_remainder = total_creators_fee.checked_sub(creator_amounts_sum.checked_div(100).expect("Div error")).expect("Sub error");
for creator in creators {
if creator.share != 0 {
let creator_info = next_account_info(remaining_accs)?;
let share = u64::try_from(creator.share).expect("Could not cast u8 to u64");
let creator_fee_remainder_amount = u64::from(creators_fee_remainder > 0);
let creator_fee_amount = total_creators_fee
.checked_mul(share)
.unwrap()
.checked_div(100)
.expect("Div error")
.checked_add(creator_fee_remainder_amount)
.expect("Add error");
creators_fee_remainder = creators_fee_remainder.checked_sub(creator_fee_remainder_amount).expect("Sub error");
if creator_fee_amount > 0 {
fees_paid_out = fees_paid_out.checked_add(creator_fee_amount).expect("Add error");
invoke(
&transfer(&ctx.accounts.payer.key(), &creator_info.key(), creator_fee_amount),
&[ctx.accounts.payer.to_account_info(), creator_info.to_account_info(), ctx.accounts.system_program.to_account_info()],
)?;
}
}
}
}
}
let buy_side_fee = payment_amount
.checked_mul(DEFAULT_BUY_SIDE_FEE_SHARE)
.unwrap()
.checked_div(BASIS_POINTS_DIVISOR.into())
.expect("Div error");
let mut fee_collector_fee = total_fees.checked_add(buy_side_fee).expect("Add error").checked_sub(fees_paid_out).expect("Sub error");
let buy_side_info = next_account_info(remaining_accs);
if buy_side_info.is_ok() {
let buy_side = buy_side_info?;
invoke(
&transfer(&ctx.accounts.payer.key(), &buy_side.key(), buy_side_fee),
&[ctx.accounts.payer.to_account_info(), buy_side.to_account_info(), ctx.accounts.system_program.to_account_info()],
)?;
fee_collector_fee = fee_collector_fee.checked_sub(buy_side_fee).expect("Sub error");
}
if fee_collector_fee > 0 {
invoke(
&transfer(&ctx.accounts.payer.key(), &ctx.accounts.fee_collector.key(), fee_collector_fee),
&[
ctx.accounts.payer.to_account_info(),
ctx.accounts.fee_collector.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
)?;
}
invoke(
&transfer(
&ctx.accounts.payer.key(),
&ctx.accounts.payment_target.key(),
payment_amount
.checked_add(taker_fee)
.expect("Add error")
.checked_sub(total_fees)
.expect("Sub error")
.checked_sub(buy_side_fee)
.expect("Sub error"),
),
&[
ctx.accounts.payer.to_account_info(),
ctx.accounts.payment_target.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
)?;
Ok(())
}