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