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_initialized);
37
38 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_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}