Skip to main content

airdrop_api/
instruction.rs

1use solana_program::pubkey::Pubkey;
2use steel::*;
3
4/// Initialize program instruction data
5#[repr(C)]
6#[derive(Clone, Copy, Debug, Pod, Zeroable)]
7pub struct Initialize {
8    /// Program admin authority
9    pub admin: Pubkey,
10
11    /// Fee for creating a new mint (in lamports)
12    pub mint_fee_lamports: [u8; 8],
13
14    /// Fee per recipient allocation (in lamports)
15    pub allocation_fee_per_recipient_lamports: [u8; 8],
16
17    /// Fee per recipient for merkle tree campaigns (in lamports)
18    /// Recommended default: 0.0001 SOL = 100_000 lamports
19    pub merkle_fee_per_recipient_lamports: [u8; 8],
20
21    /// Fee per recipient for direct transfers (in lamports)
22    /// Recommended default: 0.0005 SOL = 500_000 lamports
23    pub direct_transfer_fee_per_recipient_lamports: [u8; 8],
24}
25
26/// CreateMint instruction data - creates a new SPL token mint with metadata
27/// This creates ONLY the mint, not a campaign. Use CreateCampaign to create campaigns.
28#[repr(C)]
29#[derive(Clone, Copy, Debug, Pod, Zeroable)]
30pub struct CreateMint {
31    /// Mint ID (unique identifier for this mint, max 32 bytes)
32    /// Used to derive mint PDA if mint address is not provided
33    pub mint_id: [u8; 32],
34
35    /// Noise for mint PDA derivation (for address grinding/pattern matching)
36    /// Allows users to grind for custom mint addresses (e.g., starts-with "Kol..." or ends-with "...XYZ")
37    /// Example: [0, 0, 0, 0] = no noise (default)
38    ///          [174, 198, 166, 179] = noise that produces desired pattern
39    ///
40    /// How it works:
41    /// - PDA seeds: [MINT, mint_id, noise] (see crate::consts::MINT)
42    /// - Users can try different noise values offline until they find one that produces desired address pattern
43    /// - This becomes a value-add service: "Custom mint addresses with patterns"
44    pub noise: [u8; 4],
45
46    /// Mint address (optional: if provided, use this address; if zero, create new PDA)
47    pub mint: Pubkey,
48
49    /// Token name (truncated to 32 bytes if needed)
50    /// Padding: Trailing null bytes (`\0`)
51    /// Example: "My Token" -> [b'M', b'y', b' ', b'T', ..., 0, 0, ...]
52    pub name: [u8; 32],
53
54    /// Token symbol (truncated to 10 bytes if needed)
55    /// Padding: Trailing null bytes (`\0`)
56    /// Example: "USDC" -> [b'U', b'S', b'D', b'C', 0, 0, 0, 0, 0, 0]
57    /// Example: "MAT" -> [b'M', b'A', b'T', 0, 0, 0, 0, 0, 0, 0]
58    pub symbol: [u8; 10],
59
60    /// Metadata URI pointing to JSON file with token metadata
61    /// Padding: Trailing null bytes (`\0`)
62    /// JSON format (Metaplex standard):
63    /// {
64    ///   "name": "Token Name",
65    ///   "symbol": "SYMBOL",
66    ///   "description": "Token description",
67    ///   "image": "https://example.com/icon.png",
68    ///   "category": "image",
69    ///   "properties": {
70    ///     "files": [{"uri": "...", "type": "image/png"}]
71    ///   }
72    /// }
73    /// Required fields: name, description, image, category
74    /// Recommended: symbol (should match on-chain), properties.files
75    pub uri: [u8; 128],
76
77    /// Token decimals (0-9)
78    pub decimals: u8,
79
80    /// Max supply (must be > 0)
81    /// All tokens up to max_supply will be minted upfront to mint treasury
82    /// Mint authority will be revoked after minting (immutable cap, standard SPL way)
83    pub max_supply: [u8; 8],
84
85    /// Padding
86    pub _padding: [u8; 3],
87}
88
89/// CreateCampaign instruction data - creates a new campaign using an existing mint
90#[repr(C)]
91#[derive(Clone, Copy, Debug, Pod, Zeroable)]
92pub struct CreateCampaign {
93    /// Campaign ID (unique identifier, max 32 bytes)
94    pub campaign_id: [u8; 32],
95
96    /// Mint address (must be existing mint created by CreateMint)
97    pub mint: Pubkey,
98
99    /// Merkle root for this campaign (single merkle tree per campaign)
100    /// Zero = on-chain allocation mode (no merkle tree)
101    pub merkle_root: [u8; 32],
102
103    /// Recipient count (required if merkle_root != 0, for fee calculation)
104    /// Only used for merkle fee calculation, not a campaign limit
105    pub recipient_count: [u8; 8],
106
107    /// Amount to transfer from mint treasury to campaign treasury (optional)
108    /// Tokens are already minted in mint treasury, this just transfers them
109    pub initial_supply: [u8; 8],
110
111    /// Maximum supply cap for this campaign (0 = unlimited)
112    /// Note: Tokens are already minted in mint treasury, this is for tracking/documentation
113    pub max_supply: [u8; 8],
114
115    /// Padding
116    pub _padding: [u8; 7],
117}
118
119/// Replenish instruction data - allows campaign owner to transfer more tokens from mint treasury to campaign treasury
120#[repr(C)]
121#[derive(Clone, Copy, Debug, Pod, Zeroable)]
122pub struct Replenish {
123    /// Campaign ID
124    pub campaign_id: [u8; 32],
125
126    /// Amount to transfer from mint treasury to campaign treasury
127    pub amount: [u8; 8],
128
129    /// Padding
130    pub _padding: [u8; 7],
131}
132
133/// MintTokens instruction data - allows campaign owner to mint more tokens
134#[repr(C)]
135#[derive(Clone, Copy, Debug, Pod, Zeroable)]
136pub struct MintTokens {
137    /// Campaign ID
138    pub campaign_id: [u8; 32],
139
140    /// Amount to mint to campaign treasury
141    pub amount: [u8; 8],
142
143    /// Padding
144    pub _padding: [u8; 7],
145}
146
147/// Allocate instruction data - creates on-chain allocation accounts
148#[repr(C)]
149#[derive(Clone, Copy, Debug, Pod, Zeroable)]
150pub struct Allocate {
151    /// Campaign ID
152    pub campaign_id: [u8; 32],
153
154    /// Number of allocations (for variable-length data)
155    pub count: u8,
156
157    /// Padding
158    pub _padding: [u8; 7],
159    // Variable-length data follows:
160    // Vec<(Pubkey recipient, u64 amount)> - count * (32 + 8) bytes
161}
162
163/// Claim instruction data
164/// Claim methods align with campaign fulfill_method:
165/// - Method 0: fulfill_method='allocation' - Claim against on-chain Allocation account
166/// - Method 1: fulfill_method='record' - Claim against off-chain DB record (with signature)
167/// - Method 2: fulfill_method='merkle' - Claim against merkle proof
168/// Note: fulfill_method='transfer' uses DirectTransfer instruction (not a claim method)
169#[repr(C)]
170#[derive(Clone, Copy, Debug, Pod, Zeroable)]
171pub struct Claim {
172    /// Campaign ID
173    pub campaign_id: [u8; 32],
174
175    /// Claim method (0=allocation, 1=record, 2=merkle)
176    pub method: u8,
177
178    /// Amount to claim (for record/merkle modes)
179    pub amount: [u8; 8],
180
181    /// Merkle proof length (for merkle mode, 0-255)
182    pub merkle_proof_length: u8,
183
184    /// Padding
185    pub _padding: [u8; 6],
186    // Variable-length data follows:
187    // For merkle mode: merkle_proof_path (Vec<[u8; 32]>) + merkle_proof_indices (Vec<u8>)
188    // For record mode: [u8; 64] signature
189}
190
191/// UpdateConfig instruction data
192#[repr(C)]
193#[derive(Clone, Copy, Debug, Pod, Zeroable)]
194pub struct UpdateConfig {
195    /// New admin (Pubkey::default() = keep current)
196    pub new_admin: Pubkey,
197
198    /// New mint fee (u64::MAX = keep current)
199    pub mint_fee_lamports: [u8; 8],
200
201    /// New allocation fee per recipient (u64::MAX = keep current)
202    pub allocation_fee_per_recipient_lamports: [u8; 8],
203
204    /// New merkle fee per recipient (u64::MAX = keep current)
205    pub merkle_fee_per_recipient_lamports: [u8; 8],
206
207    /// New direct transfer fee per recipient (u64::MAX = keep current)
208    pub direct_transfer_fee_per_recipient_lamports: [u8; 8],
209}
210
211/// DirectTransfer instruction data - allows campaign owner to transfer tokens directly to recipients
212/// Batch size limited to 10 recipients per transaction (Solana tx size limit)
213#[repr(C)]
214#[derive(Clone, Copy, Debug, Pod, Zeroable)]
215pub struct DirectTransfer {
216    /// Campaign ID
217    pub campaign_id: [u8; 32],
218
219    /// Number of recipients (1-10)
220    pub count: u8,
221
222    /// Padding
223    pub _padding: [u8; 7],
224    // Variable-length data follows:
225    // Vec<(Pubkey recipient, u64 amount)> - count * (32 + 8) bytes
226}
227#[repr(C)]
228#[derive(Clone, Copy, Debug, Pod, Zeroable)]
229pub struct WithdrawFees {
230    /// Amount to withdraw (0 = withdraw all)
231    pub amount: [u8; 8],
232}
233
234/// Instruction enum
235/// Instructions are split into two groups:
236/// - Public instructions (0-99): Can be called by any user (including campaign owners)
237/// - Admin instructions (100+): Require program admin authority
238#[repr(u8)]
239#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
240pub enum AirdropInstruction {
241    // Public Instructions (0-99)
242    CreateMint = 0,
243    CreateCampaign = 1,
244    Allocate = 2,       // On-chain allocation (creates accounts)
245    Claim = 3,          // Claim tokens (on-chain, off-chain, or merkle)
246    DirectTransfer = 4, // Direct batch transfer
247    Replenish = 5,      // Transfer from mint treasury to campaign treasury
248    MintTokens = 6,     // Mint additional tokens (if authority not revoked)
249
250    // Admin Instructions (100+)
251    Initialize = 100,   // Initialize program
252    UpdateConfig = 101, // Update config and fees
253    WithdrawFees = 102, // Withdraw accumulated fees
254}
255
256instruction!(AirdropInstruction, CreateMint);
257instruction!(AirdropInstruction, CreateCampaign);
258instruction!(AirdropInstruction, Allocate);
259instruction!(AirdropInstruction, Claim);
260instruction!(AirdropInstruction, DirectTransfer);
261instruction!(AirdropInstruction, Replenish);
262instruction!(AirdropInstruction, MintTokens);
263instruction!(AirdropInstruction, Initialize);
264instruction!(AirdropInstruction, UpdateConfig);
265instruction!(AirdropInstruction, WithdrawFees);