Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ conditional_vault = "VLTX1ishMBbcX3rdBWGssxawAo1Q2X2qxYFYqiGodVg"
futarchy = "FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq"
launchpad = "MooNyh4CBUYEKyXVnjGYQ8mEiJDpGvJMdvrZx1iGeHV"
launchpad_v7 = "moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM"
mint_governor = "gvnr27cVeyW3AVf3acL7VCJ5WjGAphytnsgcK1feHyH"
price_based_performance_package = "pbPPQH7jyKoSLu8QYs3rSY3YkDRXEBojKbTgnUg7NDS"

[registry]
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions programs/mint_governor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "mint_governor"
version = "0.7.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "mint_governor"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
production = []

[dependencies]
anchor-lang = { version = "0.29.0", features = ["init-if-needed", "event-cpi"] }
anchor-spl = "0.29.0"
solana-security-txt = "1.1.1"
2 changes: 2 additions & 0 deletions programs/mint_governor/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub const MINT_GOVERNOR_SEED: &[u8] = b"mint_governor";
pub const MINT_AUTHORITY_SEED: &[u8] = b"mint_authority";
16 changes: 16 additions & 0 deletions programs/mint_governor/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use super::*;

#[error_code]
pub enum MintGovernorError {
#[msg("Unauthorized: signer is not the admin")]
UnauthorizedAdmin,

#[msg("Unauthorized: signer is not the authorized minter")]
UnauthorizedMinter,

#[msg("Mint mismatch: mint_governor.mint does not match provided mint")]
MintMismatch,

#[msg("Mint limit exceeded: would exceed max_total")]
MintLimitExceeded,
}
78 changes: 78 additions & 0 deletions programs/mint_governor/src/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use anchor_lang::prelude::*;

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct CommonFields {
pub slot: u64,
pub unix_timestamp: i64,
pub mint_governor_seq_num: u64,
}

#[event]
pub struct MintGovernorInitializedEvent {
pub common: CommonFields,
pub mint_governor: Pubkey,
pub mint: Pubkey,
pub admin: Pubkey,
pub create_key: Pubkey,
pub pda_bump: u8,
}

#[event]
pub struct MintAuthorityTransferredEvent {
pub common: CommonFields,
pub mint_governor: Pubkey,
pub mint: Pubkey,
}

#[event]
pub struct MintAuthorityAddedEvent {
pub common: CommonFields,
pub mint_governor: Pubkey,
pub mint_authority: Pubkey,
pub authorized_minter: Pubkey,
pub max_total: Option<u64>,
}

#[event]
pub struct TokensMintedEvent {
pub common: CommonFields,
pub mint_governor: Pubkey,
pub mint: Pubkey,
pub authorized_minter: Pubkey,
pub destination_ata: Pubkey,
pub amount: u64,
pub post_total_minted: u64,
pub post_mint_supply: u64,
}

#[event]
pub struct MintAuthorityUpdatedEvent {
pub common: CommonFields,
pub mint_governor: Pubkey,
pub mint_authority: Pubkey,
pub authorized_minter: Pubkey,
pub max_total: Option<u64>,
}

#[event]
pub struct MintAuthorityRemovedEvent {
pub common: CommonFields,
pub mint_governor: Pubkey,
pub authorized_minter: Pubkey,
pub total_minted: u64,
}

#[event]
pub struct MintGovernorAdminUpdatedEvent {
pub common: CommonFields,
pub mint_governor: Pubkey,
pub new_admin: Pubkey,
}

#[event]
pub struct MintAuthorityReclaimedEvent {
pub common: CommonFields,
pub mint_governor: Pubkey,
pub mint: Pubkey,
pub new_authority: Pubkey,
}
74 changes: 74 additions & 0 deletions programs/mint_governor/src/instructions/add_mint_authority.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use anchor_lang::prelude::*;

use crate::{
CommonFields, MintAuthority, MintAuthorityAddedEvent, MintGovernor, MintGovernorError,
MINT_AUTHORITY_SEED,
};

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct AddMintAuthorityArgs {
pub max_total: Option<u64>,
}

#[derive(Accounts)]
pub struct AddMintAuthority<'info> {
#[account(mut)]
pub mint_governor: Account<'info, MintGovernor>,

#[account(
init,
payer = payer,
space = 8 + MintAuthority::INIT_SPACE,
seeds = [MINT_AUTHORITY_SEED, mint_governor.key().as_ref(), authorized_minter.key().as_ref()],
bump
)]
pub mint_authority: Account<'info, MintAuthority>,

#[account(address = mint_governor.admin @ MintGovernorError::UnauthorizedAdmin)]
pub admin: Signer<'info>,

/// CHECK: This is the address receiving minting rights, no validation needed
pub authorized_minter: UncheckedAccount<'info>,

#[account(mut)]
pub payer: Signer<'info>,

pub system_program: Program<'info, System>,
}

impl AddMintAuthority<'_> {
pub fn validate(&self, _args: &AddMintAuthorityArgs) -> Result<()> {
Ok(())
}

pub fn handle(ctx: Context<Self>, args: AddMintAuthorityArgs) -> Result<()> {
let mint_governor = &mut ctx.accounts.mint_governor;
let mint_authority = &mut ctx.accounts.mint_authority;

mint_authority.set_inner(MintAuthority {
mint_governor: mint_governor.key(),
authorized_minter: ctx.accounts.authorized_minter.key(),
max_total: args.max_total,
total_minted: 0,
bump: ctx.bumps.mint_authority,
});

mint_governor.seq_num += 1;

let clock = Clock::get()?;

emit!(MintAuthorityAddedEvent {
common: CommonFields {
slot: clock.slot,
unix_timestamp: clock.unix_timestamp,
mint_governor_seq_num: mint_governor.seq_num,
},
mint_governor: mint_governor.key(),
mint_authority: ctx.accounts.mint_authority.key(),
authorized_minter: ctx.accounts.authorized_minter.key(),
max_total: args.max_total,
});

Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use anchor_lang::prelude::*;
use anchor_spl::token::Mint;

use crate::{CommonFields, MintGovernor, MintGovernorInitializedEvent, MINT_GOVERNOR_SEED};

#[derive(Accounts)]
pub struct InitializeMintGovernor<'info> {
pub mint: Account<'info, Mint>,

#[account(
init,
payer = payer,
space = 8 + MintGovernor::INIT_SPACE,
seeds = [MINT_GOVERNOR_SEED, mint.key().as_ref(), create_key.key().as_ref()],
bump
)]
pub mint_governor: Account<'info, MintGovernor>,

pub create_key: Signer<'info>,

/// CHECK: This is the future admin, no validation needed
pub admin: UncheckedAccount<'info>,

#[account(mut)]
pub payer: Signer<'info>,

pub system_program: Program<'info, System>,
}

impl InitializeMintGovernor<'_> {
pub fn validate(&self) -> Result<()> {
Ok(())
}

pub fn handle(ctx: Context<Self>) -> Result<()> {
ctx.accounts.mint_governor.set_inner(MintGovernor {
mint: ctx.accounts.mint.key(),
admin: ctx.accounts.admin.key(),
create_key: ctx.accounts.create_key.key(),
seq_num: 0,
bump: ctx.bumps.mint_governor,
});

let clock = Clock::get()?;
let mint_governor = &ctx.accounts.mint_governor;

emit!(MintGovernorInitializedEvent {
common: CommonFields {
slot: clock.slot,
unix_timestamp: clock.unix_timestamp,
mint_governor_seq_num: mint_governor.seq_num,
},
mint_governor: mint_governor.key(),
mint: mint_governor.mint,
admin: mint_governor.admin,
create_key: mint_governor.create_key,
pda_bump: mint_governor.bump,
});

Ok(())
}
}
113 changes: 113 additions & 0 deletions programs/mint_governor/src/instructions/mint_tokens.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Mint, MintTo, Token, TokenAccount};

use crate::{
CommonFields, MintAuthority, MintGovernor, MintGovernorError, TokensMintedEvent,
MINT_GOVERNOR_SEED,
};

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct MintTokensArgs {
pub amount: u64,
}

#[derive(Accounts)]
pub struct MintTokens<'info> {
#[account(mut)]
pub mint_governor: Account<'info, MintGovernor>,

#[account(
mut,
has_one = mint_governor
)]
pub mint_authority: Account<'info, MintAuthority>,

#[account(
mut,
address = mint_governor.mint @ MintGovernorError::MintMismatch
)]
pub mint: Account<'info, Mint>,

#[account(mut, has_one = mint)]
pub destination_ata: Account<'info, TokenAccount>,

#[account(address = mint_authority.authorized_minter @ MintGovernorError::UnauthorizedMinter)]
pub authorized_minter: Signer<'info>,

pub token_program: Program<'info, Token>,
}

impl MintTokens<'_> {
pub fn validate(&self, args: &MintTokensArgs) -> Result<()> {
// Check mint limit if max_total is set
if let Some(max_total) = self.mint_authority.max_total {
require_gte!(
max_total,
self.mint_authority.total_minted + args.amount,
MintGovernorError::MintLimitExceeded
);
}

Ok(())
}

pub fn handle(ctx: Context<Self>, args: MintTokensArgs) -> Result<()> {
let mint_governor = &mut ctx.accounts.mint_governor;
let mint_authority = &mut ctx.accounts.mint_authority;

// Build PDA signer seeds
let mint_key = mint_governor.mint;
let create_key = mint_governor.create_key;
let bump = mint_governor.bump;
let seeds = &[
MINT_GOVERNOR_SEED,
mint_key.as_ref(),
create_key.as_ref(),
&[bump],
];
let signer_seeds = &[&seeds[..]];

// CPI to mint_to with mint_governor PDA as authority
token::mint_to(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.destination_ata.to_account_info(),
authority: mint_governor.to_account_info(),
},
signer_seeds,
),
args.amount,
)?;

// Update total_minted
mint_authority.total_minted += args.amount;

// Increment seq_num
mint_governor.seq_num += 1;

// Emit event
let clock = Clock::get()?;

// Reload mint to get post-mint supply
ctx.accounts.mint.reload()?;

emit!(TokensMintedEvent {
common: CommonFields {
slot: clock.slot,
unix_timestamp: clock.unix_timestamp,
mint_governor_seq_num: mint_governor.seq_num,
},
mint_governor: mint_governor.key(),
mint: mint_governor.mint,
authorized_minter: ctx.accounts.authorized_minter.key(),
destination_ata: ctx.accounts.destination_ata.key(),
amount: args.amount,
post_total_minted: mint_authority.total_minted,
post_mint_supply: ctx.accounts.mint.supply,
});

Ok(())
}
}
Loading
Loading