airdrop 0.1.0

Mint and Airdrop Framework on Solana for Sovereign Individuals
Documentation
use airdrop_api::Config;
use airdrop_api::{
    consts::{CONFIG, FEE_ACCOUNT, INITIALIZER_ADDRESS},
    instruction::Initialize,
    loaders::AirdropAccountInfoValidation,
    pda::fee_account_pda,
};
use solana_program::clock::Clock;
use steel::*;

pub fn process_initialize(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
    // Parse instruction data
    let args = Initialize::try_from_bytes(data)?;
    let admin = args.admin;
    let mint_fee_lamports = u64::from_le_bytes(args.mint_fee_lamports);
    let allocation_fee_per_recipient_lamports =
        u64::from_le_bytes(args.allocation_fee_per_recipient_lamports);
    let merkle_fee_per_recipient_lamports =
        u64::from_le_bytes(args.merkle_fee_per_recipient_lamports);
    let direct_transfer_fee_per_recipient_lamports =
        u64::from_le_bytes(args.direct_transfer_fee_per_recipient_lamports);

    // Load accounts
    let [initializer_info, config_info, fee_account_info, system_program, rent_sysvar] = accounts
    else {
        return Err(ProgramError::NotEnoughAccountKeys);
    };

    // Validate accounts
    initializer_info
        .is_signer()?
        .has_address(&INITIALIZER_ADDRESS)?;
    config_info.is_empty()?.is_writable()?.is_config()?;
    fee_account_info
        .is_empty()?
        .is_writable()?
        .has_seeds(&[FEE_ACCOUNT], &airdrop_api::ID)?;
    system_program.is_program(&system_program::ID)?;
    rent_sysvar.is_sysvar(&sysvar::rent::ID)?;

    // Get fee account bump for later use
    let (_expected_fee_account, fee_bump) = fee_account_pda();

    // Create config account
    create_program_account::<Config>(
        config_info,
        system_program,
        initializer_info,
        &airdrop_api::ID,
        &[CONFIG],
    )?;

    // Initialize fee account PDA (system-owned, 0 bytes data - for SOL transfers)
    // Note: Program-owned accounts with data cannot be used for SOL transfers per Solana rules
    // Following escrow-copy pattern: system-owned PDA with 0 bytes of data for SOL storage
    allocate_account_with_bump(
        fee_account_info,
        system_program,
        initializer_info,
        0,                   // 0 bytes of data (just lamports, like escrow-copy's sol_storage)
        &system_program::ID, // System-owned (required for SOL transfers)
        &[FEE_ACCOUNT],
        fee_bump,
    )?;

    // Initialize config data
    let clock = Clock::get()?;
    let config = config_info.as_account_mut::<Config>(&airdrop_api::ID)?;
    config.admin = admin;
    config.mint_fee_lamports = mint_fee_lamports;
    config.allocation_fee_per_recipient_lamports = allocation_fee_per_recipient_lamports;
    config.merkle_fee_per_recipient_lamports = merkle_fee_per_recipient_lamports;
    config.direct_transfer_fee_per_recipient_lamports = direct_transfer_fee_per_recipient_lamports;
    config.fee_account = *fee_account_info.key;
    config.total_fees_collected = 0;
    config.updated_at = clock.unix_timestamp;

    // Emit event
    airdrop_api::event::ConfigInitializedEvent {
        admin,
        mint_fee_lamports,
        allocation_fee_per_recipient_lamports,
        fee_account: *fee_account_info.key,
        initialized_at: clock.unix_timestamp,
    }
    .log();

    Ok(())
}