proof_of_work_faucet/
lib.rs

1use anchor_lang::prelude::*;
2use anchor_lang::solana_program::{
3    entrypoint::ProgramResult,
4    program::{invoke, invoke_signed},
5    system_instruction,
6};
7use bs58::encode;
8
9declare_id!("PoWSNH2hEZogtCg1Zgm51FnkmJperzYDgPK4fvs8taL");
10
11pub fn create_account<'a, 'info>(
12    payer: &'a AccountInfo<'info>,
13    new_account: &'a AccountInfo<'info>,
14    system_program: &'a AccountInfo<'info>,
15    program_owner: &Pubkey,
16    rent: &Rent,
17    space: u64,
18    seeds: Vec<Vec<u8>>,
19) -> ProgramResult {
20    let current_lamports = **new_account.try_borrow_lamports()?;
21    if current_lamports == 0 {
22        // If there are no lamports in the new account, we create it with the create_account instruction
23        invoke_signed(
24            &system_instruction::create_account(
25                payer.key,
26                new_account.key,
27                rent.minimum_balance(space as usize),
28                space,
29                program_owner,
30            ),
31            &[payer.clone(), new_account.clone(), system_program.clone()],
32            &[seeds
33                .iter()
34                .map(|seed| seed.as_slice())
35                .collect::<Vec<&[u8]>>()
36                .as_slice()],
37        )
38    } else {
39        // Fund the account for rent exemption.
40        let required_lamports = rent
41            .minimum_balance(space as usize)
42            .max(1)
43            .saturating_sub(current_lamports);
44        if required_lamports > 0 {
45            invoke(
46                &system_instruction::transfer(payer.key, new_account.key, required_lamports),
47                &[payer.clone(), new_account.clone(), system_program.clone()],
48            )?;
49        }
50        // Allocate space.
51        invoke_signed(
52            &system_instruction::allocate(new_account.key, space),
53            &[new_account.clone(), system_program.clone()],
54            &[seeds
55                .iter()
56                .map(|seed| seed.as_slice())
57                .collect::<Vec<&[u8]>>()
58                .as_slice()],
59        )?;
60        // Assign to the specified program
61        invoke_signed(
62            &system_instruction::assign(new_account.key, program_owner),
63            &[new_account.clone(), system_program.clone()],
64            &[seeds
65                .iter()
66                .map(|seed| seed.as_slice())
67                .collect::<Vec<&[u8]>>()
68                .as_slice()],
69        )
70    }
71}
72
73#[program]
74pub mod proof_of_work_faucet {
75    use super::*;
76
77    pub fn create(ctx: Context<Create>, difficulty: u8, amount: u64) -> Result<()> {
78        ctx.accounts.spec.difficulty = difficulty;
79        ctx.accounts.spec.amount = amount;
80        Ok(())
81    }
82
83    pub fn airdrop(ctx: Context<Airdrop>) -> Result<()> {
84        let Airdrop {
85            payer,
86            signer,
87            receipt,
88            spec,
89            source,
90            system_program,
91        } = ctx.accounts;
92
93        // Count the number of leading A's in the signer's public key.
94        let prefix_len = encode(signer.key().as_ref())
95            .into_string()
96            .chars()
97            .take_while(|ch| ch == &'A')
98            .count();
99
100        if prefix_len < spec.difficulty as usize {
101            msg!(
102                "Public key does not meet difficulty requirement of {}: {}",
103                spec.difficulty,
104                signer.key()
105            );
106            return Err(ProgramError::MissingRequiredSignature.into());
107        }
108
109        msg!("Source wallet balance: {}", source.lamports());
110        msg!(
111            "Airdropping {} lamports to {}",
112            spec.amount.min(source.lamports()),
113            payer.key()
114        );
115
116        invoke_signed(
117            &system_instruction::transfer(
118                &source.key(),
119                &payer.key(),
120                spec.amount.min(source.lamports()),
121            ),
122            &[
123                system_program.to_account_info(),
124                payer.to_account_info(),
125                source.to_account_info(),
126            ],
127            &[&[b"source", spec.key().as_ref(), &[ctx.bumps["source"]]]],
128        )?;
129
130        // Create a receipt account after receiving the airdrop to lower the base SOL requirement.
131        create_account(
132            &payer,
133            &receipt,
134            system_program,
135            ctx.program_id,
136            &Rent::get()?,
137            0,
138            vec![
139                b"receipt".to_vec(),
140                signer.key().to_bytes().to_vec(),
141                spec.difficulty.to_le_bytes().to_vec(),
142                vec![ctx.bumps["receipt"]],
143            ],
144        )?;
145        Ok(())
146    }
147}
148
149#[account]
150pub struct Difficulty {
151    pub difficulty: u8,
152    pub amount: u64,
153}
154
155#[derive(Accounts)]
156#[instruction(difficulty: u8, amount: u64)]
157pub struct Create<'info> {
158    #[account(mut)]
159    pub payer: Signer<'info>,
160    #[account(
161        init,
162        seeds=[b"spec", difficulty.to_le_bytes().as_ref(), amount.to_le_bytes().as_ref()],
163        bump,
164        space=8 + 1 + 8,
165        payer=payer,
166    )]
167    pub spec: Account<'info, Difficulty>,
168    pub system_program: Program<'info, System>,
169}
170
171#[derive(Accounts)]
172pub struct Airdrop<'info> {
173    #[account(mut)]
174    pub payer: Signer<'info>,
175    pub signer: Signer<'info>,
176    /// CHECK: Trust me bro
177    #[account(
178        mut,
179        seeds=[b"receipt", signer.key().as_ref(), spec.difficulty.to_le_bytes().as_ref()],
180        bump,
181    )]
182    pub receipt: UncheckedAccount<'info>,
183    #[account(
184        seeds=[b"spec", spec.difficulty.to_le_bytes().as_ref(), spec.amount.to_le_bytes().as_ref()],
185        bump,
186    )]
187    pub spec: Account<'info, Difficulty>,
188    /// CHECK: Trust me bro
189    #[account(mut, seeds=[b"source", spec.key().as_ref()], bump)]
190    pub source: UncheckedAccount<'info>,
191    pub system_program: Program<'info, System>,
192}