| Crates.io | airdrop |
| lib.rs | airdrop |
| version | 0.1.0 |
| created_at | 2025-11-06 14:27:04.482329+00 |
| updated_at | 2025-11-06 14:27:04.482329+00 |
| description | Mint and Airdrop Framework on Solana for Sovereign Individuals |
| homepage | https://miraland.io/airdrop |
| repository | https://github.com/miraland-labs/airdrop |
| max_upload_size | |
| id | 1919736 |
| size | 149,404 |
A flexible, production-ready Solana program for creating SPL tokens and managing airdrop campaigns with multiple allocation modes and fee structures.
The Mint-and-Airdrop Framework provides a comprehensive solution for token creation and distribution on Solana. It supports multiple campaign management strategies, flexible fee structures, and various allocation methods (on-chain accounts, off-chain signatures, and merkle proofs).
Allocate instruction)Claim Method 1, no Allocate needed)Claim Method 2, no Allocate needed)Program Config (Singleton PDA)
├── Program Admin Authority
├── Fee Configuration
│ ├── Mint Fee (lamports)
│ ├── Allocation Fee Per Recipient (lamports)
│ ├── Merkle Fee Per Recipient (lamports)
│ └── Direct Transfer Fee Per Recipient (lamports)
├── Fee Account (PDA)
└── Initialization Status
Campaign Account (Per Campaign PDA)
├── Campaign Owner
├── Mint Address
├── Treasury Token Account
├── Merkle Root (optional)
└── Campaign Status
Allocation Account (Mode 0 Only)
├── Recipient
├── Amount
└── Claim Status
Initialize the program with admin and fee configuration.
pub struct Initialize {
pub admin: Pubkey,
pub mint_fee_lamports: [u8; 8],
pub allocation_fee_per_recipient_lamports: [u8; 8],
pub merkle_fee_per_recipient_lamports: [u8; 8],
pub direct_transfer_fee_per_recipient_lamports: [u8; 8],
}
Accounts:
Create a new SPL token mint with metadata. All tokens up to max_supply are minted upfront to the mint treasury, and mint authority is revoked.
pub struct CreateMint {
pub mint_id: [u8; 32],
pub noise: [u8; 4], // For address grinding
pub mint: Pubkey, // Optional: use provided or derive PDA
pub name: [u8; 32],
pub symbol: [u8; 10],
pub uri: [u8; 128], // Metaplex metadata URI
pub decimals: u8,
pub max_supply: [u8; 8], // Must be > 0
}
Fee: mint_fee_lamports (recommended: 0.1 SOL)
Create a new campaign using an existing mint. Multiple campaigns can share the same mint.
pub struct CreateCampaign {
pub campaign_id: [u8; 32],
pub mint: Pubkey,
pub merkle_root: [u8; 32], // Zero = no merkle mode
pub recipient_count: [u8; 8], // Required if merkle_root != 0
pub initial_supply: [u8; 8], // Transfer from mint treasury
pub max_supply: [u8; 8],
}
Fee: merkle_fee_per_recipient_lamports × recipient_count (if merkle_root set)
Allocate tokens to recipients by creating on-chain allocation accounts.
Mode: On-chain only (creates Allocation accounts for each recipient)
allocation_fee_per_recipient_lamports × countNote: Off-chain allocations are handled via Claim Method 1 (record mode) without a prior Allocate call. Merkle-based claims use Claim Method 2 (merkle mode) without Allocate.
pub struct Allocate {
pub campaign_id: [u8; 32],
pub count: u8,
// Variable: Vec<(Pubkey recipient, u64 amount)>
}
Claim allocated tokens. Supports three methods:
Allocate call)Allocate needed)Allocate needed)pub struct Claim {
pub campaign_id: [u8; 32],
pub method: u8, // 0=allocation, 1=record, 2=merkle
pub amount: [u8; 8],
pub merkle_proof_length: u8,
// Variable: merkle proof data or signature
}
Fee: None (recipient pays transaction fee)
Campaign owner transfers tokens directly to recipients in batches (no claim step required).
pub struct DirectTransfer {
pub campaign_id: [u8; 32],
pub count: u8, // 1-10 recipients per batch
// Variable: Vec<(Pubkey recipient, u64 amount)>
}
Fee: direct_transfer_fee_per_recipient_lamports × count
Batch Size: Limited to 10 recipients per transaction
Transfer additional tokens from mint treasury to campaign treasury.
pub struct Replenish {
pub campaign_id: [u8; 32],
pub amount: [u8; 8],
}
Fee: None
Mint additional tokens to campaign treasury (if mint authority not revoked).
pub struct MintTokens {
pub campaign_id: [u8; 32],
pub amount: [u8; 8],
}
Fee: None
Update fee rates and admin authority (admin-only).
pub struct UpdateConfig {
pub new_admin: Pubkey, // Pubkey::default() = keep current
pub mint_fee_lamports: [u8; 8], // u64::MAX = keep current
pub allocation_fee_per_recipient_lamports: [u8; 8],
pub merkle_fee_per_recipient_lamports: [u8; 8],
pub direct_transfer_fee_per_recipient_lamports: [u8; 8],
}
Withdraw accumulated fees to beneficiary (admin-only).
pub struct WithdrawFees {
pub amount: [u8; 8], // 0 = withdraw all
}
| Fee Type | Amount | Description |
|---|---|---|
| Mint Fee | 0.1 SOL (100M lamports) | Per mint creation |
| Allocation Fee | 0.001 SOL (1M lamports) | Per recipient (Mode 0 only) |
| Merkle Fee | 0.0001 SOL (100K lamports) | Per recipient (when merkle root set) |
| Direct Transfer Fee | 0.0005 SOL (500K lamports) | Per recipient (batch transfers) |
CreateMintAllocate (on-chain allocation accounts)CreateCampaign (if merkle root set)DirectTransferpub struct Config {
pub admin: Pubkey,
pub mint_fee_lamports: u64,
pub allocation_fee_per_recipient_lamports: u64,
pub merkle_fee_per_recipient_lamports: u64,
pub direct_transfer_fee_per_recipient_lamports: u64,
pub fee_account: Pubkey,
pub total_fees_collected: u64,
pub updated_at: i64,
}
pub struct Campaign {
pub owner: Pubkey,
pub mint: Pubkey,
pub treasury: Pubkey, // Campaign treasury token account
pub merkle_root: [u8; 32], // Zero = no merkle mode
pub status: u8,
pub total_allocated: u64,
pub total_claimed: u64,
pub max_supply: u64,
pub created_at: i64,
}
pub struct Allocation {
pub campaign: Pubkey,
pub recipient: Pubkey,
pub amount: u64,
pub claimed: bool,
pub allocated_at: i64,
pub claimed_at: i64,
}
use airdrop_api::sdk;
// 1. Initialize program (one-time setup)
let initialize_ix = sdk::initialize(
&initializer,
admin,
100_000_000, // mint_fee: 0.1 SOL
1_000_000, // allocation_fee: 0.001 SOL per recipient
100_000, // merkle_fee: 0.0001 SOL per recipient
500_000, // direct_transfer_fee: 0.0005 SOL per recipient
)?;
// 2. Create mint
let mint_id = [0u8; 32];
let noise = [0u8; 4];
let create_mint_ix = sdk::create_mint(
&payer,
mint_id,
noise,
Pubkey::default(), // Use PDA derivation
b"My Token",
b"MTK",
b"https://example.com/metadata.json",
9,
1_000_000_000, // max_supply
)?;
// 3. Create campaign (with merkle root)
let campaign_id = [1u8; 32];
let merkle_root = compute_merkle_root(&recipients);
let create_campaign_ix = sdk::create_campaign(
&payer,
&campaign_owner,
campaign_id,
mint_address,
merkle_root,
1000, // recipient_count
100_000_000, // initial_supply
1_000_000_000, // max_supply
)?;
// 4. Allocate tokens (Mode 0 - on-chain)
let allocate_ix = sdk::allocate_onchain(
&campaign_owner,
campaign_id,
vec![
(recipient1, 1000),
(recipient2, 2000),
// ... more recipients
],
)?;
// 5. Claim tokens (Method 0 - on-chain)
let claim_ix = sdk::claim_onchain(
&recipient,
campaign_id,
)?;
// 6. Direct transfer (batch)
let direct_transfer_ix = sdk::direct_transfer(
&campaign_owner,
campaign_id,
vec![
(recipient1, 500),
(recipient2, 500),
// ... up to 10 recipients
],
)?;
// Generate merkle proof off-chain
let merkle_proof = generate_merkle_proof(&recipient, &amount, &merkle_tree)?;
// Claim using merkle proof
let claim_ix = sdk::claim_merkle(
&recipient,
campaign_id,
amount,
merkle_proof.path,
merkle_proof.indices,
)?;
.
├── api/ # API crate (types, SDK, PDA functions)
│ ├── src/
│ │ ├── lib.rs # Main exports
│ │ ├── consts.rs # Constants
│ │ ├── error.rs # Error types
│ │ ├── instruction.rs # Instruction definitions
│ │ ├── event.rs # Event definitions
│ │ ├── sdk.rs # SDK helper functions
│ │ ├── pda.rs # PDA derivation functions
│ │ ├── merkle.rs # Merkle proof utilities
│ │ ├── loaders.rs # Account validation traits
│ │ └── state/ # State structs
│ │ ├── config.rs
│ │ ├── campaign.rs
│ │ └── allocation.rs
│ └── Cargo.toml
├── program/ # Program crate (instruction handlers)
│ ├── src/
│ │ ├── lib.rs # Entrypoint and dispatch
│ │ ├── initialize.rs
│ │ ├── create_mint.rs
│ │ ├── create_campaign.rs
│ │ ├── allocate.rs
│ │ ├── claim.rs
│ │ ├── direct_transfer.rs
│ │ ├── replenish.rs
│ │ ├── mint_tokens.rs
│ │ ├── update_config.rs
│ │ └── withdraw_fees.rs
│ └── Cargo.toml
├── docs/ # Design documentation
├── Cargo.toml # Workspace configuration
└── README.md
# Build all crates
cargo build
# Build program specifically
cargo build-sbf --manifest-path program/Cargo.toml
# Run tests
cargo test
# Deploy to devnet
solana program deploy target/deploy/airdrop_program.so --program-id <PROGRAM_ID>
# Set program ID in code
# Update api/src/lib.rs with actual program ID
"Simple Is Best, Yet Elegant"
This project follows patterns from:
miracle-copy: Merkle tree implementation, account validation patternsescrow-copy: Fee account design, PDA treasury patternssteel-master-copy: Framework idioms and best practicesApache-2.0
Contributions welcome! Please follow the existing code patterns and Steel framework idioms.
✅ Production Ready - All core features implemented and tested
For detailed design documentation, see the docs/ directory.