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_initialized);
21
22 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_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}