Skip to main content

generic_token_faucet/
lib.rs

1use anchor_lang::{
2    prelude::*,
3    solana_program::{instruction::Instruction, sysvar},
4    InstructionData,
5};
6use anchor_spl::token::{self, Mint, Token, TokenAccount};
7
8declare_id!("FF2UnZt7Lce3S65tW5cMVKz8iVAPoCS8ETavmUhsWLJB");
9
10pub fn get_mint_address(ticker: &str) -> Pubkey {
11    Pubkey::find_program_address(
12        &["mint".as_bytes(), ticker.to_lowercase().as_ref()],
13        &crate::ID,
14    )
15    .0
16}
17
18pub fn get_mint_authority_address(ticker: &str) -> Pubkey {
19    Pubkey::find_program_address(
20        &["mint-authority".as_bytes(), ticker.to_lowercase().as_ref()],
21        &crate::ID,
22    )
23    .0
24}
25
26#[program]
27pub mod generic_token_faucet {
28    use anchor_spl::token::MintTo;
29
30    use super::*;
31
32    pub fn create_mint(ctx: Context<CreateMint>, ticker: String, decimals: u8) -> Result<()> {
33        let mint_authority = &mut ctx.accounts.mint_authority;
34
35        // Assert mint_authority is not already initialized
36        assert!(!mint_authority.is_initialized);
37
38        //Store the mint information in the PDA
39        mint_authority.mint = ctx.accounts.mint.key();
40        mint_authority.is_initialized = true;
41        mint_authority.bump = *ctx.bumps.get("mint_authority").unwrap();
42        mint_authority.decimals = decimals;
43        mint_authority.ticker_len = ticker.len() as u8;
44        mint_authority.ticker[..ticker.len()].copy_from_slice(&ticker.to_lowercase().as_bytes());
45
46        Ok(())
47    }
48
49    pub fn airdrop_spl(ctx: Context<AirdropSpl>, amount: u64) -> Result<()> {
50        let AirdropSpl {
51            mint,
52            mint_authority,
53            destination,
54            token_program,
55        } = ctx.accounts;
56
57        let ticker = mint_authority.ticker[..mint_authority.ticker_len as usize].to_vec();
58
59        // Assert that the token account matches the mint account
60        assert_eq!(destination.mint, mint.key());
61
62        token::mint_to(
63            CpiContext::new_with_signer(
64                token_program.to_account_info(),
65                MintTo {
66                    authority: mint_authority.to_account_info(),
67                    to: destination.to_account_info(),
68                    mint: mint.to_account_info(),
69                },
70                &[&[
71                    "mint-authority".as_bytes(),
72                    ticker.as_ref(),
73                    &[mint_authority.bump],
74                ]],
75            ),
76            amount.min(u64::MAX / 1_000_000),
77        )?;
78
79        msg!("Tokens minted!");
80
81        Ok(())
82    }
83}
84
85#[derive(Accounts)]
86#[instruction(ticker: String, decimals: u8)]
87pub struct CreateMint<'info> {
88    #[account(
89        init,
90        seeds = ["mint".as_bytes(), ticker.to_lowercase().as_ref()],
91        bump,
92        payer = payer,
93        mint::decimals = decimals,
94        mint::authority = mint_authority,
95    )]
96    pub mint: Account<'info, Mint>,
97    #[account(
98        init,
99        payer = payer,
100        seeds = [b"mint-authority".as_ref(), ticker.to_lowercase().as_ref()],
101        bump,
102        space = 8 + 32 + 1 + 1 + 1 + 1 + 16
103    )]
104    pub mint_authority: Account<'info, MintData>,
105    #[account(mut)]
106    pub payer: Signer<'info>,
107    pub token_program: Program<'info, Token>,
108    pub system_program: Program<'info, System>,
109    pub rent: Sysvar<'info, Rent>,
110}
111
112#[derive(Accounts)]
113pub struct AirdropSpl<'info> {
114    #[account(
115        mut,
116        seeds = ["mint".as_bytes(), mint_authority.ticker[..mint_authority.ticker_len as usize].as_ref()],
117        bump
118    )]
119    pub mint: Account<'info, Mint>,
120    #[account(
121        seeds = ["mint-authority".as_bytes(), mint_authority.ticker[..mint_authority.ticker_len as usize].as_ref()],
122        bump,
123    )]
124    pub mint_authority: Account<'info, MintData>,
125    #[account(mut)]
126    pub destination: Account<'info, TokenAccount>,
127    pub token_program: Program<'info, Token>,
128}
129
130#[account]
131#[derive(Debug)]
132pub struct MintData {
133    pub mint: Pubkey,
134    pub bump: u8,
135    pub is_initialized: bool,
136    pub decimals: u8,
137    pub ticker_len: u8,
138    pub ticker: [u8; 16],
139}
140
141pub fn create_mint_ix(
142    program_id: Pubkey,
143    payer: Pubkey,
144    ticker: String,
145    decimals: u8,
146) -> Instruction {
147    let (mint, _) = Pubkey::find_program_address(
148        &["mint".as_bytes(), ticker.to_lowercase().as_ref()],
149        &program_id,
150    );
151
152    let (mint_authority, _) = Pubkey::find_program_address(
153        &["mint-authority".as_bytes(), ticker.to_lowercase().as_ref()],
154        &program_id,
155    );
156
157    let accounts = accounts::CreateMint {
158        mint,
159        mint_authority,
160        payer,
161        token_program: token::ID,
162        system_program: System::id(),
163        rent: sysvar::rent::id(),
164    };
165
166    Instruction {
167        program_id,
168        accounts: accounts.to_account_metas(None),
169        data: instruction::CreateMint {
170            ticker: ticker,
171            decimals: decimals,
172        }
173        .data(),
174    }
175}
176
177pub fn airdrop_spl_with_ticker_ix(
178    program_id: &Pubkey,
179    ticker: String,
180    payer: &Pubkey,
181    amount: u64,
182) -> Instruction {
183    let mint = get_mint_address(&ticker);
184    let mint_authority = get_mint_authority_address(&ticker);
185
186    let destination = spl_associated_token_account::get_associated_token_address(&payer, &mint);
187
188    let accounts = accounts::AirdropSpl {
189        mint,
190        mint_authority,
191        destination,
192        token_program: token::ID,
193    };
194
195    Instruction {
196        program_id: *program_id,
197        accounts: accounts.to_account_metas(None),
198        data: instruction::AirdropSpl { amount }.data(),
199    }
200}
201
202pub fn airdrop_spl_with_mint_pdas_ix(
203    program_id: &Pubkey,
204    mint: &Pubkey,
205    mint_authority: &Pubkey,
206    payer: &Pubkey,
207    amount: u64,
208) -> Instruction {
209    let destination = spl_associated_token_account::get_associated_token_address(&payer, &mint);
210
211    let accounts = accounts::AirdropSpl {
212        mint: *mint,
213        mint_authority: *mint_authority,
214        destination,
215        token_program: token::ID,
216    };
217
218    Instruction {
219        program_id: *program_id,
220        accounts: accounts.to_account_metas(None),
221        data: instruction::AirdropSpl { amount }.data(),
222    }
223}
224
225#[cfg(test)]
226mod ix_tests {
227    use super::*;
228
229    #[test]
230    fn test_creat_mint_ix() {
231        let program_id = Pubkey::new_unique();
232        let payer = Pubkey::new_unique();
233        let ticker = "SOL".to_string();
234        let decimals: u8 = 9;
235
236        let instruction = create_mint_ix(program_id, payer, ticker.clone(), decimals);
237        assert_eq!(instruction.program_id, program_id);
238        assert_eq!(instruction.accounts.len(), 6);
239        assert_eq!(
240            instruction.data,
241            instruction::CreateMint {
242                ticker: ticker,
243                decimals: decimals
244            }
245            .data()
246        );
247    }
248
249    #[test]
250    fn test_airdrop_spl_ix() {
251        let program_id = Pubkey::new_unique();
252        let payer = Pubkey::new_unique();
253        let ticker = "SOL".to_string();
254        let amount: u64 = 10;
255
256        let instruction = airdrop_spl_with_ticker_ix(&program_id, ticker.clone(), &payer, amount);
257        assert_eq!(instruction.program_id, program_id);
258        assert_eq!(instruction.accounts.len(), 4);
259        assert_eq!(instruction.data, instruction::AirdropSpl { amount }.data())
260    }
261}