use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token::{Mint, Token, TokenAccount},
token_interface,
};
use crate::{
constants,
states::Store,
utils::{internal, token::must_be_uninitialized},
};
#[derive(Accounts)]
pub struct InitializeMarketVault<'info> {
#[account(mut)]
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
pub mint: Account<'info, Mint>,
#[account(
init_if_needed,
payer = authority,
token::mint = mint,
// We use the store as the authority of the token account.
token::authority = store,
seeds = [
constants::MARKET_VAULT_SEED,
store.key().as_ref(),
mint.key().as_ref(),
],
bump,
)]
pub vault: Account<'info, TokenAccount>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
#[allow(unused_variables)]
pub(crate) fn unchecked_initialize_market_vault(ctx: Context<InitializeMarketVault>) -> Result<()> {
Ok(())
}
impl<'info> internal::Authentication<'info> for InitializeMarketVault<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
#[instruction(timestamp: i64)]
pub struct UseClaimableAccount<'info> {
#[account(mut)]
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
pub mint: Account<'info, Mint>,
pub owner: UncheckedAccount<'info>,
#[account(
init_if_needed,
payer = authority,
token::mint = mint,
// We use the store as the authority of the token account.
token::authority = store,
seeds = [
constants::CLAIMABLE_ACCOUNT_SEED,
store.key().as_ref(),
mint.key().as_ref(),
owner.key().as_ref(),
&store.load()?.claimable_time_key(timestamp)?,
],
bump,
)]
pub account: Account<'info, TokenAccount>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
pub(crate) fn unchecked_use_claimable_account(
ctx: Context<UseClaimableAccount>,
_timestamp: i64,
amount: u64,
) -> Result<()> {
if ctx.accounts.account.delegate.is_none() || ctx.accounts.account.delegated_amount != amount {
anchor_spl::token::approve(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token::Approve {
to: ctx.accounts.account.to_account_info(),
delegate: ctx.accounts.owner.to_account_info(),
authority: ctx.accounts.store.to_account_info(),
},
&[&ctx.accounts.store.load()?.signer_seeds()],
),
amount,
)?;
}
Ok(())
}
impl<'info> internal::Authentication<'info> for UseClaimableAccount<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
#[instruction(timestamp: i64)]
pub struct CloseEmptyClaimableAccount<'info> {
#[account(mut)]
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
pub mint: Account<'info, Mint>,
pub owner: UncheckedAccount<'info>,
#[account(
mut,
seeds = [
constants::CLAIMABLE_ACCOUNT_SEED,
store.key().as_ref(),
mint.key().as_ref(),
owner.key().as_ref(),
&store.load()?.claimable_time_key(timestamp)?,
],
bump,
)]
pub account: UncheckedAccount<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
pub(crate) fn unchecked_close_empty_claimable_account(
ctx: Context<CloseEmptyClaimableAccount>,
_timestamp: i64,
) -> Result<()> {
if must_be_uninitialized(&ctx.accounts.account) {
return Ok(());
}
let account = ctx.accounts.account.to_account_info();
let amount = anchor_spl::token::accessor::amount(&account)?;
if amount == 0 {
anchor_spl::token::close_account(CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token::CloseAccount {
account: ctx.accounts.account.to_account_info(),
destination: ctx.accounts.authority.to_account_info(),
authority: ctx.accounts.store.to_account_info(),
},
&[&ctx.accounts.store.load()?.signer_seeds()],
))?;
}
Ok(())
}
impl<'info> internal::Authentication<'info> for CloseEmptyClaimableAccount<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct PrepareAssociatedTokenAccount<'info> {
#[account(mut)]
pub payer: Signer<'info>,
pub owner: UncheckedAccount<'info>,
pub mint: InterfaceAccount<'info, token_interface::Mint>,
#[account(
init_if_needed,
payer = payer,
associated_token::mint = mint,
associated_token::authority = owner,
)]
pub account: InterfaceAccount<'info, token_interface::TokenAccount>,
pub system_program: Program<'info, System>,
pub token_program: Interface<'info, token_interface::TokenInterface>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
pub(crate) fn prepare_associated_token_account(
_ctx: Context<PrepareAssociatedTokenAccount>,
) -> Result<()> {
Ok(())
}
#[derive(Accounts)]
pub struct CreateTokenMetadata<'info> {
#[account(mut)]
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut)]
pub mint: InterfaceAccount<'info, token_interface::Mint>,
#[account(mut)]
pub metadata: UncheckedAccount<'info>,
pub system_program: Program<'info, System>,
pub sysvar_instructions: UncheckedAccount<'info>,
pub metadata_program: Program<'info, anchor_spl::metadata::Metadata>,
}
impl CreateTokenMetadata<'_> {
pub(crate) fn invoke_unchecked(
ctx: Context<Self>,
name: String,
symbol: String,
uri: String,
) -> Result<()> {
ctx.accounts.create_token_metadata(name, symbol, uri)?;
Ok(())
}
fn create_token_metadata(&self, name: String, symbol: String, uri: String) -> Result<()> {
use mpl_token_metadata::{instructions::CreateV1CpiBuilder, types::TokenStandard};
let store = self.store.load()?;
let signer_seeds = store.signer_seeds();
CreateV1CpiBuilder::new(&self.metadata_program)
.metadata(&self.metadata)
.mint(self.mint.as_ref(), false)
.authority(self.store.as_ref())
.payer(&self.authority)
.update_authority(self.store.as_ref(), true)
.system_program(&self.system_program)
.sysvar_instructions(&self.sysvar_instructions)
.name(name)
.symbol(symbol)
.uri(uri)
.seller_fee_basis_points(0)
.token_standard(TokenStandard::Fungible)
.invoke_signed(&[&signer_seeds])?;
Ok(())
}
}
impl<'info> internal::Authentication<'info> for CreateTokenMetadata<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct UpdateTokenMetadata<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut)]
pub metadata: UncheckedAccount<'info>,
pub metadata_program: Program<'info, anchor_spl::metadata::Metadata>,
}
impl UpdateTokenMetadata<'_> {
pub(crate) fn invoke_unchecked(
ctx: Context<Self>,
name: String,
symbol: String,
uri: String,
) -> Result<()> {
use anchor_spl::metadata::mpl_token_metadata::types::DataV2;
ctx.accounts.update_token_metadata(DataV2 {
name,
symbol,
uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
})?;
Ok(())
}
fn update_token_metadata(
&self,
data: anchor_spl::metadata::mpl_token_metadata::types::DataV2,
) -> Result<()> {
use anchor_spl::metadata;
let store = self.store.load()?;
let signer_seeds = store.signer_seeds();
let ctx = CpiContext::new(
self.metadata_program.to_account_info(),
metadata::UpdateMetadataAccountsV2 {
metadata: self.metadata.to_account_info(),
update_authority: self.store.to_account_info(),
},
);
metadata::update_metadata_accounts_v2(
ctx.with_signer(&[&signer_seeds]),
None,
Some(data),
None,
None,
)?;
Ok(())
}
}
impl<'info> internal::Authentication<'info> for UpdateTokenMetadata<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}