#![allow(deprecated)]
use anchor_lang::solana_program;
use anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES};
use anchor_spl::token::{self, Mint, SetAuthority, Token, TokenAccount};
use vipers::prelude::*;
mod proxy_seeds;
declare_id!("UBEBk5idELqykEEaycYtQ7iBVrCg6NmvFSzMpdr22mL");
pub const PROXY_STATE_ACCOUNT: Pubkey =
static_pubkey::static_pubkey!("9qRjwMQYrkd5JvsENaYYxSCgwEuVhK4qAo5kCFHSmdmL");
pub const PROXY_MINT_AUTHORITY: Pubkey =
static_pubkey::static_pubkey!("GyktbGXbH9kvxP8RGfWsnFtuRgC7QCQo2WBqpo3ryk7L");
#[cfg(feature = "cpi")]
pub fn invoke_perform_mint<'a, 'b, 'c, 'info>(
ctx: CpiContext<'a, 'b, 'c, 'info, crate::cpi::accounts::PerformMint<'info>>,
mint_proxy_state: AccountInfo<'info>,
amount: u64,
) -> Result<()> {
let ix = {
let ix = crate::instruction::state::PerformMint { amount };
let data = anchor_lang::InstructionData::data(&ix);
let mut accounts = ctx.to_account_metas(None);
accounts.insert(0, AccountMeta::new_readonly(mint_proxy_state.key(), false));
anchor_lang::solana_program::instruction::Instruction {
program_id: crate::ID,
accounts,
data,
}
};
let mut acc_infos = ctx.to_account_infos();
acc_infos.insert(0, mint_proxy_state);
anchor_lang::solana_program::program::invoke_signed(&ix, &acc_infos, ctx.signer_seeds)?;
Ok(())
}
#[program]
pub mod mint_proxy {
use super::*;
#[state]
pub struct MintProxy {
pub nonce: u8,
pub hard_cap: u64,
pub proxy_mint_authority: Pubkey,
pub owner: Pubkey,
pub pending_owner: Pubkey,
pub state_associated_account: Pubkey,
pub token_mint: Pubkey,
}
impl MintProxy {
pub fn new(ctx: Context<Initialize>, nonce: u8, hard_cap: u64) -> Result<Self> {
require!(
ctx.accounts.token_mint.freeze_authority.is_none(),
InvalidFreezeAuthority
);
let proxy_signer_seeds = proxy_seeds::gen_signer_seeds(&nonce, &PROXY_STATE_ACCOUNT);
require!(
vipers::validate_derived_address(
ctx.accounts.proxy_mint_authority.key,
ctx.program_id,
&proxy_signer_seeds[..],
),
InvalidProxyAuthority
);
let proxy_mint_authority = *ctx.accounts.proxy_mint_authority.key;
let cpi_ctx = new_set_authority_cpi_context(
&ctx.accounts.mint_authority,
&ctx.accounts.token_mint.to_account_info(),
&ctx.accounts.token_program,
);
token::set_authority(
cpi_ctx,
spl_token::instruction::AuthorityType::MintTokens,
Some(proxy_mint_authority),
)?;
Ok(Self {
nonce,
proxy_mint_authority,
owner: *ctx.accounts.owner.key,
pending_owner: Pubkey::default(),
state_associated_account: PROXY_STATE_ACCOUNT,
token_mint: *ctx.accounts.token_mint.to_account_info().key,
hard_cap,
})
}
#[access_control(only_owner(self, &ctx.accounts))]
pub fn transfer_ownership(&mut self, ctx: Context<Auth>, next_owner: Pubkey) -> Result<()> {
self.pending_owner = next_owner;
Ok(())
}
pub fn accept_ownership(&mut self, ctx: Context<Auth>) -> Result<()> {
require!(ctx.accounts.owner.is_signer, Unauthorized);
require!(
self.pending_owner == *ctx.accounts.owner.key,
PendingOwnerMismatch
);
self.owner = self.pending_owner;
self.pending_owner = Pubkey::default();
Ok(())
}
#[access_control(only_owner(self, &ctx.accounts.auth))]
pub fn minter_add(&self, ctx: Context<MinterAdd>, allowance: u64) -> Result<()> {
let minter_info = &mut ctx.accounts.minter_info;
minter_info.minter = ctx.accounts.minter.key();
minter_info.allowance = allowance;
minter_info.__nonce = *unwrap_int!(ctx.bumps.get("minter_info"));
Ok(())
}
#[access_control(only_owner(self, &ctx.accounts.auth))]
pub fn minter_update(&self, ctx: Context<MinterUpdate>, allowance: u64) -> Result<()> {
let minter_info = &mut ctx.accounts.minter_info;
minter_info.allowance = allowance;
Ok(())
}
#[access_control(only_owner(self, &ctx.accounts.auth))]
pub fn minter_remove(&self, ctx: Context<MinterRemove>) -> Result<()> {
Ok(())
}
pub fn perform_mint(&self, ctx: Context<PerformMint>, amount: u64) -> Result<()> {
ctx.accounts.validate(self)?;
let minter_info = &mut ctx.accounts.minter_info;
require!(minter_info.allowance >= amount, MinterAllowanceExceeded);
let new_supply = unwrap_int!(ctx.accounts.token_mint.supply.checked_add(amount),);
require!(new_supply <= self.hard_cap, HardcapExceeded);
minter_info.allowance = unwrap_int!(minter_info.allowance.checked_sub(amount));
let seeds = proxy_seeds::gen_signer_seeds(&self.nonce, &self.state_associated_account);
let proxy_signer = &[&seeds[..]];
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
token::MintTo {
mint: ctx.accounts.token_mint.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
authority: ctx.accounts.proxy_mint_authority.to_account_info(),
},
proxy_signer,
);
token::mint_to(cpi_ctx, amount)?;
Ok(())
}
#[access_control(only_owner(self, &ctx.accounts.auth))]
pub fn set_mint_authority(
&self,
ctx: Context<SetMintAuthority>,
new_authority: Pubkey,
) -> Result<()> {
let mut proxy_mint_authority = ctx.accounts.proxy_mint_authority.to_account_info();
proxy_mint_authority.is_signer = true;
let seeds = proxy_seeds::gen_signer_seeds(&self.nonce, &self.state_associated_account);
let proxy_signer = &[&seeds[..]];
let cpi_ctx = new_set_authority_cpi_context(
&proxy_mint_authority,
&ctx.accounts.token_mint.to_account_info(),
&ctx.accounts.token_program,
)
.with_signer(proxy_signer);
token::set_authority(
cpi_ctx,
spl_token::instruction::AuthorityType::MintTokens,
Some(new_authority),
)?;
Ok(())
}
}
}
#[derive(Accounts)]
pub struct Auth<'info> {
pub owner: Signer<'info>,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
pub mint_authority: Signer<'info>,
#[account(address = PROXY_MINT_AUTHORITY)]
pub proxy_mint_authority: UncheckedAccount<'info>,
pub owner: UncheckedAccount<'info>,
#[account(mut)]
pub token_mint: Account<'info, Mint>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct SetMintAuthority<'info> {
pub auth: Auth<'info>,
#[account(address = PROXY_MINT_AUTHORITY)]
pub proxy_mint_authority: UncheckedAccount<'info>,
#[account(mut)]
pub token_mint: Account<'info, Mint>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct MinterAdd<'info> {
pub auth: Auth<'info>,
pub minter: UncheckedAccount<'info>,
#[account(
init,
seeds = [
b"anchor".as_ref(),
minter.key().as_ref()
],
bump,
space = 8 + MinterInfo::LEN,
payer = payer
)]
pub minter_info: Account<'info, MinterInfo>,
#[account(mut)]
pub payer: Signer<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct MinterRemove<'info> {
pub auth: Auth<'info>,
pub minter: UncheckedAccount<'info>,
#[account(mut, has_one = minter, close = payer)]
pub minter_info: Account<'info, MinterInfo>,
#[account(mut)]
pub payer: UncheckedAccount<'info>,
}
#[derive(Accounts)]
pub struct MinterUpdate<'info> {
pub auth: Auth<'info>,
#[account(mut)]
pub minter_info: Account<'info, MinterInfo>,
}
#[derive(Accounts)]
pub struct PerformMint<'info> {
pub proxy_mint_authority: UncheckedAccount<'info>,
pub minter: Signer<'info>,
#[account(mut)]
pub token_mint: Account<'info, Mint>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
#[account(mut, has_one = minter)]
pub minter_info: Account<'info, MinterInfo>,
pub token_program: Program<'info, Token>,
}
impl<'info> PerformMint<'info> {
fn validate(&self, state: &MintProxy) -> Result<()> {
assert_keys_eq!(self.proxy_mint_authority, PROXY_MINT_AUTHORITY);
require!(self.minter.is_signer, Unauthorized);
assert_keys_eq!(self.minter_info.minter, self.minter, Unauthorized);
assert_keys_eq!(state.token_mint, self.token_mint);
Ok(())
}
}
#[account]
#[derive(Default)]
pub struct MinterInfo {
pub minter: Pubkey,
pub allowance: u64,
__nonce: u8,
}
impl MinterInfo {
pub const LEN: usize = PUBKEY_BYTES + 8 + 1;
}
#[account]
#[derive(Default)]
pub struct MintProxyInfo {
pub nonce: u8,
pub hard_cap: u64,
pub proxy_mint_authority: Pubkey,
pub owner: Pubkey,
pub pending_owner: Pubkey,
pub state_associated_account: Pubkey,
pub token_mint: Pubkey,
}
fn only_owner(state: &MintProxy, auth: &Auth) -> Result<()> {
require!(
auth.owner.is_signer && state.owner == *auth.owner.key,
Unauthorized
);
Ok(())
}
fn new_set_authority_cpi_context<'a, 'b, 'c, 'info>(
current_authority: &AccountInfo<'info>,
mint: &AccountInfo<'info>,
token_program: &AccountInfo<'info>,
) -> CpiContext<'a, 'b, 'c, 'info, SetAuthority<'info>> {
let cpi_accounts = SetAuthority {
account_or_mint: mint.clone(),
current_authority: current_authority.clone(),
};
let cpi_program = token_program.clone();
CpiContext::new(cpi_program, cpi_accounts)
}
#[error_code]
pub enum ErrorCode {
#[msg("You are not authorized to perform this action.")]
Unauthorized,
#[msg("Cannot mint over hard cap.")]
HardcapExceeded,
#[msg("Provided token mint has a freeze authority")]
InvalidFreezeAuthority,
#[msg("Provided token mint was invalid.")]
InvalidTokenMint,
#[msg("Provided proxy authority was invalid.")]
InvalidProxyAuthority,
#[msg("Not enough remaining accounts in relay context.")]
NotEnoughAccounts,
#[msg("Whitelist entry already exists.")]
WhitelistEntryAlreadyExists,
#[msg("Whitelist entry not found.")]
WhitelistEntryNotFound,
#[msg("Whitelist is full.")]
WhitelistFull,
#[msg("Invalid token program ID.")]
TokenProgramIDMismatch,
#[msg("Pending owner mismatch.")]
PendingOwnerMismatch,
#[msg("Minter allowance exceeded.")]
MinterAllowanceExceeded,
#[msg("U64 overflow.")]
U64Overflow,
}