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);