oil_api/
sdk.rs

1use solana_program::pubkey::Pubkey;
2use spl_associated_token_account::get_associated_token_address;
3use steel::*;
4
5use crate::{
6    consts::{AUCTION, BOARD, MINT_ADDRESS, SOL_MINT, TREASURY_ADDRESS},
7    instruction::{self, *},
8    state::*,
9};
10
11pub fn log(signer: Pubkey, msg: &[u8]) -> Instruction {
12    let mut data = Log {}.to_bytes();
13    data.extend_from_slice(msg);
14    Instruction {
15        program_id: crate::ID,
16        accounts: vec![AccountMeta::new(signer, true)],
17        data: data,
18    }
19}
20
21pub fn program_log(accounts: &[AccountInfo], msg: &[u8]) -> Result<(), ProgramError> {
22    // Derive Board PDA to use as signer for log instruction
23    let (board_address, _) = board_pda();
24    invoke_signed(&log(board_address, msg), accounts, &crate::ID, &[BOARD])
25}
26
27/// Log event for auction-based instructions (uses Auction PDA instead of Board)
28pub fn auction_program_log(accounts: &[AccountInfo], msg: &[u8]) -> Result<(), ProgramError> {
29    // Derive Auction PDA to use as signer for log instruction
30    let (auction_address, _) = auction_pda();
31    invoke_signed(&log(auction_address, msg), accounts, &crate::ID, &[AUCTION])
32}
33
34// let [signer_info, board_info, config_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] = accounts else {
35
36// pub fn initialize(
37//     signer: Pubkey,
38//     barrel_authority: Pubkey,
39//     fee_collector: Pubkey,
40//     swap_program: Pubkey,
41//     var_address: Pubkey,
42//     admin_fee: u64,
43// ) -> Instruction {
44//     let board_address = board_pda().0;
45//     let config_address = config_pda().0;
46//     let mint_address = MINT_ADDRESS;
47//     let treasury_address = TREASURY_ADDRESS;
48//     let treasury_tokens_address = treasury_tokens_address();
49//     Instruction {
50//         program_id: crate::ID,
51//         accounts: vec![
52//             AccountMeta::new(signer, true),
53//             AccountMeta::new(board_address, false),
54//             AccountMeta::new(config_address, false),
55//             AccountMeta::new(mint_address, false),
56//             AccountMeta::new(treasury_address, false),
57//             AccountMeta::new(treasury_tokens_address, false),
58//             AccountMeta::new_readonly(system_program::ID, false),
59//             AccountMeta::new_readonly(spl_token::ID, false),
60//             AccountMeta::new_readonly(spl_associated_token_account::ID, false),
61//         ],
62//         data: Initialize {
63//             barrel_authority: barrel_authority.to_bytes(),
64//             fee_collector: fee_collector.to_bytes(),
65//             swap_program: swap_program.to_bytes(),
66//             var_address: var_address.to_bytes(),
67//             admin_fee: admin_fee.to_le_bytes(),
68//         }
69//         .to_bytes(),
70//     }
71// }
72
73// let [signer_info, automation_info, executor_info, miner_info, system_program] = accounts else {
74
75/// Set up automation for a miner. If the miner doesn't exist yet, pass a referrer to set it.
76/// If a referrer is provided and the miner is new, the referral account must be included.
77pub fn automate(
78    signer: Pubkey,
79    amount: u64,
80    deposit: u64,
81    executor: Pubkey,
82    fee: u64,
83    mask: u64,
84    strategy: u8,
85    reload: bool,
86    referrer: Option<Pubkey>,
87    pooled: bool,
88    is_new_miner: bool,
89) -> Instruction {
90    let automation_address = automation_pda(signer).0;
91    let miner_address = miner_pda(signer).0;
92    let referrer_pk = referrer.unwrap_or(Pubkey::default());
93    
94    let mut accounts = vec![
95            AccountMeta::new(signer, true),
96            AccountMeta::new(automation_address, false),
97            AccountMeta::new(executor, false),
98            AccountMeta::new(miner_address, false),
99            AccountMeta::new_readonly(system_program::ID, false),
100    ];
101    
102    // Add referral account if referrer is provided and miner is new (for incrementing total_referred)
103    if is_new_miner && referrer.is_some() && referrer_pk != Pubkey::default() {
104        let referral_address = referral_pda(referrer_pk).0;
105        accounts.push(AccountMeta::new(referral_address, false));
106    }
107    
108    Instruction {
109        program_id: crate::ID,
110        accounts,
111        data: Automate {
112            amount: amount.to_le_bytes(),
113            deposit: deposit.to_le_bytes(),
114            fee: fee.to_le_bytes(),
115            mask: mask.to_le_bytes(),
116            strategy: strategy as u8,
117            reload: (reload as u64).to_le_bytes(),
118            referrer: referrer_pk.to_bytes(),
119            pooled: pooled as u8,
120        }
121        .to_bytes(),
122    }
123}
124
125/// Claim SOL rewards with single-tier referral system.
126/// 
127/// If the miner has a referrer, 1.0% of the claim goes to the referrer.
128/// 
129/// Account structure:
130/// - Base: signer, miner, system_program
131/// - If miner has referrer (required): [miner_referrer, referral_referrer]
132pub fn claim_sol(
133    signer: Pubkey,
134    referrer_miner: Option<Pubkey>, // Referrer's miner PDA (if miner has referrer)
135    referrer_referral: Option<Pubkey>, // Referrer's referral PDA (if miner has referrer)
136) -> Instruction {
137    let miner_address = miner_pda(signer).0;
138    
139    let mut accounts = vec![
140        AccountMeta::new(signer, true),
141        AccountMeta::new(miner_address, false),
142        AccountMeta::new_readonly(system_program::ID, false),
143    ];
144    
145    // Add referrer accounts if provided (required if miner has referrer)
146    if let (Some(miner_pubkey), Some(referral_pubkey)) = (referrer_miner, referrer_referral) {
147        accounts.push(AccountMeta::new(miner_pubkey, false));
148        accounts.push(AccountMeta::new(referral_pubkey, false));
149    }
150    
151    Instruction {
152        program_id: crate::ID,
153        accounts,
154        data: ClaimSOL {}.to_bytes(),
155    }
156}
157
158// let [signer_info, miner_info, mint_info, recipient_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] =
159
160/// Claim OIL rewards with single-tier referral system.
161/// 
162/// If the miner has a referrer, 1.0% of the claim goes to the referrer.
163/// 
164/// Account structure:
165/// - Base: signer, miner, mint, recipient, treasury, treasury_tokens, system_program, token_program, associated_token_program
166/// - If miner has referrer (required): [miner_referrer, referral_referrer, referral_referrer_oil_ata]
167pub fn claim_oil(
168    signer: Pubkey,
169    referrer_miner: Option<Pubkey>, // Referrer's miner PDA (if miner has referrer)
170    referrer_referral: Option<Pubkey>, // Referrer's referral PDA (if miner has referrer)
171    referrer_referral_oil_ata: Option<Pubkey>, // Referrer's referral OIL ATA (if miner has referrer)
172) -> Instruction {
173    let miner_address = miner_pda(signer).0;
174    let treasury_address = treasury_pda().0;
175    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
176    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
177    
178    let mut accounts = vec![
179        AccountMeta::new(signer, true),
180        AccountMeta::new(miner_address, false),
181        AccountMeta::new(MINT_ADDRESS, false),
182        AccountMeta::new(recipient_address, false),
183        AccountMeta::new(treasury_address, false),
184        AccountMeta::new(treasury_tokens_address, false),
185        AccountMeta::new_readonly(system_program::ID, false),
186        AccountMeta::new_readonly(spl_token::ID, false),
187        AccountMeta::new_readonly(spl_associated_token_account::ID, false),
188    ];
189    
190    // Add referrer accounts if provided (required if miner has referrer)
191    if let (Some(miner_pubkey), Some(referral_pubkey), Some(oil_ata_pubkey)) = 
192        (referrer_miner, referrer_referral, referrer_referral_oil_ata) {
193        accounts.push(AccountMeta::new(miner_pubkey, false));
194        accounts.push(AccountMeta::new(referral_pubkey, false));
195        accounts.push(AccountMeta::new(oil_ata_pubkey, false));
196    }
197    
198    Instruction {
199        program_id: crate::ID,
200        accounts,
201        data: ClaimOIL {}.to_bytes(),
202    }
203}
204
205
206pub fn close(signer: Pubkey, round_id: u64, rent_payer: Pubkey) -> Instruction {
207    let board_address = board_pda().0;
208    let round_address = round_pda(round_id).0;
209    let treasury_address = TREASURY_ADDRESS;
210    Instruction {
211        program_id: crate::ID,
212        accounts: vec![
213            AccountMeta::new(signer, true),
214            AccountMeta::new(board_address, false),
215            AccountMeta::new(rent_payer, false),
216            AccountMeta::new(round_address, false),
217            AccountMeta::new(treasury_address, false),
218            AccountMeta::new_readonly(system_program::ID, false),
219        ],
220        data: Close {}.to_bytes(),
221    }
222}
223
224/// Deploy SOL to prospect on squares. Pass a referrer pubkey for new miners to set up referral.
225/// Set `pooled` to true to join the mining pool (rewards shared proportionally).
226pub fn deploy(
227    signer: Pubkey,
228    authority: Pubkey,
229    amount: u64,
230    round_id: u64,
231    squares: [bool; 25],
232    referrer: Option<Pubkey>,
233    pooled: bool,
234) -> Instruction {
235    let automation_address = automation_pda(authority).0;
236    let board_address = board_pda().0;
237    let config_address = config_pda().0;
238    let miner_address = miner_pda(authority).0;
239    let round_address = round_pda(round_id).0;
240    let entropy_var_address = entropy_rng_api::state::var_pda(board_address, 0).0;
241
242    // Convert array of 25 booleans into a 32-bit mask where each bit represents whether
243    // that square index is selected (1) or not (0)
244    let mut mask: u32 = 0;
245    for (i, &square) in squares.iter().enumerate() {
246        if square {
247            mask |= 1 << i;
248        }
249    }
250    
251    // Get referrer bytes (default to zero pubkey if no referrer).
252    let referrer_pubkey = referrer.unwrap_or(Pubkey::default());
253    let referrer_bytes = referrer_pubkey.to_bytes();
254
255    let mut accounts = vec![
256        AccountMeta::new(signer, true),
257        AccountMeta::new(authority, false),
258        AccountMeta::new(automation_address, false),
259        AccountMeta::new(board_address, false),
260        AccountMeta::new(config_address, false),
261        AccountMeta::new(miner_address, false),
262        AccountMeta::new(round_address, false),
263        AccountMeta::new_readonly(system_program::ID, false),
264        AccountMeta::new_readonly(crate::ID, false),
265        // Entropy accounts.
266        AccountMeta::new(entropy_var_address, false),
267        AccountMeta::new_readonly(entropy_rng_api::ID, false),
268    ];
269    
270    // Add referral account if referrer is provided (for incrementing total_referred on new miners).
271    if referrer_pubkey != Pubkey::default() {
272        let referral_address = referral_pda(referrer_pubkey).0;
273        accounts.push(AccountMeta::new(referral_address, false));
274    }
275
276    Instruction {
277        program_id: crate::ID,
278        accounts,
279        data: Deploy {
280            amount: amount.to_le_bytes(),
281            squares: mask.to_le_bytes(),
282            referrer: referrer_bytes,
283            pooled: if pooled { 1 } else { 0 },
284        }
285        .to_bytes(),
286    }
287}
288
289// let [pool, user_source_token, user_destination_token, a_vault, b_vault, a_token_vault, b_token_vault, a_vault_lp_mint, b_vault_lp_mint, a_vault_lp, b_vault_lp, protocol_token_fee, user_key, vault_program, token_program] =
290
291pub fn buyback(signer: Pubkey, swap_accounts: &[AccountMeta], swap_data: &[u8]) -> Instruction {
292    let board_address = board_pda().0;
293    let config_address = config_pda().0;
294    let mint_address = MINT_ADDRESS;
295    let treasury_address = TREASURY_ADDRESS;
296    let treasury_oil_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
297    let treasury_sol_address = get_associated_token_address(&treasury_address, &SOL_MINT);
298    let mut accounts = vec![
299        AccountMeta::new(signer, true),
300        AccountMeta::new(board_address, false),
301        AccountMeta::new_readonly(config_address, false),
302        AccountMeta::new(mint_address, false),
303        AccountMeta::new(treasury_address, false),
304        AccountMeta::new(treasury_oil_address, false),
305        AccountMeta::new(treasury_sol_address, false),
306        AccountMeta::new_readonly(spl_token::ID, false),
307        AccountMeta::new_readonly(crate::ID, false),
308    ];
309    for account in swap_accounts.iter() {
310        let mut acc_clone = account.clone();
311        acc_clone.is_signer = false;
312        accounts.push(acc_clone);
313    }
314    let mut data = Buyback {}.to_bytes();
315    data.extend_from_slice(swap_data);
316    Instruction {
317        program_id: crate::ID,
318        accounts,
319        data,
320    }
321}
322
323// let [signer_info, board_info, config_info, fee_collector_info, mint_info, round_info, round_next_info, top_miner_info, treasury_info, treasury_tokens_info, system_program, token_program, oil_program, slot_hashes_sysvar] =
324
325pub fn reset(
326    signer: Pubkey,
327    fee_collector: Pubkey,
328    round_id: u64,
329    top_miner: Pubkey,
330    var_address: Pubkey,
331) -> Instruction {
332    reset_with_miners(signer, fee_collector, round_id, top_miner, var_address, &[])
333}
334
335pub fn reset_with_miners(
336    signer: Pubkey,
337    fee_collector: Pubkey,
338    round_id: u64,
339    top_miner: Pubkey,
340    var_address: Pubkey,
341    miner_accounts: &[Pubkey],
342) -> Instruction {
343    let board_address = board_pda().0;
344    let config_address = config_pda().0;
345    let mint_address = MINT_ADDRESS;
346    let round_address = round_pda(round_id).0;
347    let round_next_address = round_pda(round_id + 1).0;
348    let top_miner_address = miner_pda(top_miner).0;
349    let treasury_address = TREASURY_ADDRESS;
350    let treasury_tokens_address = treasury_tokens_address();
351    let pool_address = pool_pda().0;
352    let mint_authority_address = oil_mint_api::state::authority_pda().0;
353    let mut reset_instruction = Instruction {
354        program_id: crate::ID,
355        accounts: vec![
356            AccountMeta::new(signer, true),
357            AccountMeta::new(board_address, false),
358            AccountMeta::new(config_address, false),
359            AccountMeta::new(fee_collector, false),
360            AccountMeta::new(mint_address, false),
361            AccountMeta::new(round_address, false),
362            AccountMeta::new(round_next_address, false),
363            AccountMeta::new(top_miner_address, false),
364            AccountMeta::new(treasury_address, false),
365            AccountMeta::new(pool_address, false),
366            AccountMeta::new(treasury_tokens_address, false),
367            AccountMeta::new_readonly(system_program::ID, false),
368            AccountMeta::new_readonly(spl_token::ID, false),
369            AccountMeta::new_readonly(crate::ID, false),
370            AccountMeta::new_readonly(sysvar::slot_hashes::ID, false),
371            // Entropy accounts.
372            AccountMeta::new(var_address, false),
373            AccountMeta::new_readonly(entropy_rng_api::ID, false),
374            // Mint accounts.
375            AccountMeta::new(mint_authority_address, false),
376            AccountMeta::new_readonly(oil_mint_api::ID, false),
377        ],
378        data: Reset {}.to_bytes(),
379    };
380    
381    // Add miner accounts for seeker rewards (optional)
382    for miner_pubkey in miner_accounts {
383        reset_instruction.accounts.push(AccountMeta::new(
384            miner_pda(*miner_pubkey).0,
385            false,
386        ));
387    }
388    
389    reset_instruction
390}
391    
392// let [signer_info, automation_info, board_info, miner_info, round_info, treasury_info, system_program] =
393
394pub fn checkpoint(signer: Pubkey, authority: Pubkey, round_id: u64) -> Instruction {
395    let miner_address = miner_pda(authority).0;
396    let board_address = board_pda().0;
397    let round_address = round_pda(round_id).0;
398    let treasury_address = TREASURY_ADDRESS;
399    Instruction {
400        program_id: crate::ID,
401        accounts: vec![
402            AccountMeta::new(signer, true), // payer (session payer or regular wallet, receives bot fee)
403            AccountMeta::new(authority, false), // authority (user's wallet, for PDA derivation - must be writable when combined with deploy)
404            AccountMeta::new(board_address, false),
405            AccountMeta::new(miner_address, false),
406            AccountMeta::new(round_address, false),
407            AccountMeta::new(treasury_address, false),
408            AccountMeta::new_readonly(system_program::ID, false),
409        ],
410        data: Checkpoint {}.to_bytes(),
411    }
412}
413
414pub fn set_admin(signer: Pubkey, admin: Pubkey) -> Instruction {
415    let config_address = config_pda().0;
416    Instruction {
417        program_id: crate::ID,
418        accounts: vec![
419            AccountMeta::new(signer, true),
420            AccountMeta::new(config_address, false),
421            AccountMeta::new_readonly(system_program::ID, false),
422        ],
423        data: SetAdmin {
424            admin: admin.to_bytes(),
425        }
426        .to_bytes(),
427    }
428}
429
430pub fn set_admin_fee(signer: Pubkey, admin_fee: u64) -> Instruction {
431    let config_address = config_pda().0;
432    Instruction {
433        program_id: crate::ID,
434        accounts: vec![
435            AccountMeta::new(signer, true),
436            AccountMeta::new(config_address, false),
437            AccountMeta::new_readonly(system_program::ID, false),
438        ],
439        data: SetAdminFee {
440            admin_fee: admin_fee.to_le_bytes(),
441        }
442        .to_bytes(),
443    }
444}
445
446pub fn set_fee_collector(signer: Pubkey, fee_collector: Pubkey) -> Instruction {
447    let config_address = config_pda().0;
448    Instruction {
449        program_id: crate::ID,
450        accounts: vec![
451            AccountMeta::new(signer, true),
452            AccountMeta::new(config_address, false),
453            AccountMeta::new_readonly(system_program::ID, false),
454        ],
455        data: SetFeeCollector {
456            fee_collector: fee_collector.to_bytes(),
457        }
458        .to_bytes(),
459    }
460}
461
462pub fn set_auction(
463    signer: Pubkey,
464    halving_period_seconds: u64,
465    last_halving_time: u64,
466    base_mining_rates: [u64; 4],
467    auction_duration_seconds: u64,
468    starting_prices: [u64; 4],
469    _well_id: u64, // Kept for backwards compatibility, but not used (always updates auction only)
470) -> Instruction {
471    let config_address = config_pda().0;
472    let auction_address = auction_pda().0;
473    
474    Instruction {
475        program_id: crate::ID,
476        accounts: vec![
477            AccountMeta::new(signer, true),
478            AccountMeta::new_readonly(config_address, false),
479            AccountMeta::new(auction_address, false),
480        ],
481        data: SetAuction {
482            halving_period_seconds: halving_period_seconds.to_le_bytes(),
483            last_halving_time: last_halving_time.to_le_bytes(),
484            base_mining_rates: [
485                base_mining_rates[0].to_le_bytes(),
486                base_mining_rates[1].to_le_bytes(),
487                base_mining_rates[2].to_le_bytes(),
488                base_mining_rates[3].to_le_bytes(),
489            ],
490            auction_duration_seconds: auction_duration_seconds.to_le_bytes(),
491            starting_prices: [
492                starting_prices[0].to_le_bytes(),
493                starting_prices[1].to_le_bytes(),
494                starting_prices[2].to_le_bytes(),
495                starting_prices[3].to_le_bytes(),
496            ],
497            well_id: 4u64.to_le_bytes(), // Always use 4 to indicate auction-only update
498        }
499        .to_bytes(),
500    }
501}
502
503// let [signer_info, mint_info, sender_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
504
505pub fn deposit(signer: Pubkey, authority: Pubkey, amount: u64, lock_duration_days: u64, stake_id: u64) -> Instruction {
506    let mint_address = MINT_ADDRESS;
507    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
508    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
509    let sender_address = get_associated_token_address(&authority, &MINT_ADDRESS); // Authority's ATA
510    let pool_address = pool_pda().0;
511    let pool_tokens_address = pool_tokens_address();
512    let miner_address = miner_pda(authority).0; // Derive from authority
513    Instruction {
514        program_id: crate::ID,
515        accounts: vec![
516            AccountMeta::new(signer, true), // payer (session payer or regular wallet, pays fees)
517            AccountMeta::new(authority, true), // authority (user's wallet, signs token transfer and used for PDA derivation)
518            AccountMeta::new(mint_address, false),
519            AccountMeta::new(sender_address, false),
520            AccountMeta::new(stake_address, false),
521            AccountMeta::new(stake_tokens_address, false),
522            AccountMeta::new(pool_address, false),
523            AccountMeta::new(pool_tokens_address, false),
524            AccountMeta::new(miner_address, false),
525            AccountMeta::new_readonly(system_program::ID, false),
526            AccountMeta::new_readonly(spl_token::ID, false),
527            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
528        ],
529        data: Deposit {
530            amount: amount.to_le_bytes(),
531            lock_duration_days: lock_duration_days.to_le_bytes(),
532            stake_id: stake_id.to_le_bytes(),
533        }
534        .to_bytes(),
535    }
536}
537
538// let [signer_info, mint_info, recipient_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
539
540pub fn withdraw(signer: Pubkey, authority: Pubkey, amount: u64, stake_id: u64) -> Instruction {
541    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
542    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
543    let mint_address = MINT_ADDRESS;
544    let recipient_address = get_associated_token_address(&authority, &MINT_ADDRESS); // Authority's ATA
545    let pool_address = pool_pda().0;
546    let pool_tokens_address = pool_tokens_address();
547    let miner_address = miner_pda(authority).0; // Derive from authority
548    Instruction {
549        program_id: crate::ID,
550        accounts: vec![
551            AccountMeta::new(signer, true), // payer (session payer or regular wallet)
552            AccountMeta::new(authority, false), // authority (user's wallet, for PDA derivation)
553            AccountMeta::new(mint_address, false),
554            AccountMeta::new(recipient_address, false),
555            AccountMeta::new(stake_address, false),
556            AccountMeta::new(stake_tokens_address, false),
557            AccountMeta::new(pool_address, false),
558            AccountMeta::new(pool_tokens_address, false),
559            AccountMeta::new(miner_address, false),
560            AccountMeta::new_readonly(system_program::ID, false),
561            AccountMeta::new_readonly(spl_token::ID, false),
562            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
563        ],
564        data: Withdraw {
565            amount: amount.to_le_bytes(),
566            stake_id: stake_id.to_le_bytes(),
567        }
568        .to_bytes(),
569    }
570}
571
572// let [signer_info, automation_info, miner_info, system_program] = accounts else {
573
574/// Reload SOL from miner account to automation balance with single-tier referral system.
575/// 
576/// If the miner has a referrer, 1.0% of the claim goes to the referrer.
577/// 
578/// Account structure:
579/// - Base: signer, automation, miner, system_program
580/// - If miner has referrer (required): [miner_referrer, referral_referrer]
581pub fn reload_sol(
582    signer: Pubkey,
583    authority: Pubkey,
584    referrer_miner: Option<Pubkey>,
585    referrer_referral: Option<Pubkey>,
586) -> Instruction {
587    let automation_address = automation_pda(authority).0;
588    let miner_address = miner_pda(authority).0;
589    
590    let mut accounts = vec![
591        AccountMeta::new(signer, true),
592        AccountMeta::new(automation_address, false),
593        AccountMeta::new(miner_address, false),
594        AccountMeta::new_readonly(system_program::ID, false),
595    ];
596    
597    // Add referral accounts if provided (required when miner has referrer)
598    if let (Some(miner_ref), Some(referral_ref)) = (referrer_miner, referrer_referral) {
599        accounts.push(AccountMeta::new(miner_ref, false));
600        accounts.push(AccountMeta::new(referral_ref, false));
601    }
602    
603    Instruction {
604        program_id: crate::ID,
605        accounts,
606        data: ReloadSOL {}.to_bytes(),
607    }
608}
609
610// let [signer_info, mint_info, recipient_info, stake_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] =
611
612/// Claim SOL yield from staking. Stakers earn SOL rewards (2% of round winnings), not OIL.
613pub fn claim_yield(signer: Pubkey, authority: Pubkey, amount: u64, stake_id: u64) -> Instruction {
614    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
615    let pool_address = pool_pda().0;
616    Instruction {
617        program_id: crate::ID,
618        accounts: vec![
619            AccountMeta::new(signer, true), // payer (session payer or regular wallet)
620            AccountMeta::new(authority, true), // authority (user's wallet, receives SOL and used for PDA derivation)
621            AccountMeta::new(stake_address, false),
622            AccountMeta::new(pool_address, false),
623            AccountMeta::new_readonly(system_program::ID, false),
624        ],
625        data: ClaimYield {
626            amount: amount.to_le_bytes(),
627        }
628        .to_bytes(),
629    }
630}
631
632pub fn new_var(
633    signer: Pubkey,
634    provider: Pubkey,
635    id: u64,
636    commit: [u8; 32],
637    samples: u64,
638) -> Instruction {
639    let board_address = board_pda().0;
640    let config_address = config_pda().0;
641    let var_address = entropy_rng_api::state::var_pda(board_address, id).0;
642    Instruction {
643        program_id: crate::ID,
644        accounts: vec![
645            AccountMeta::new(signer, true),
646            AccountMeta::new(board_address, false),
647            AccountMeta::new(config_address, false),
648            AccountMeta::new(provider, false),
649            AccountMeta::new(var_address, false),
650            AccountMeta::new_readonly(system_program::ID, false),
651            AccountMeta::new_readonly(entropy_rng_api::ID, false),
652        ],
653        data: NewVar {
654            id: id.to_le_bytes(),
655            commit: commit,
656            samples: samples.to_le_bytes(),
657        }
658        .to_bytes(),
659    }
660}
661
662pub fn set_swap_program(signer: Pubkey, new_program: Pubkey) -> Instruction {
663    let config_address = config_pda().0;
664    Instruction {
665        program_id: crate::ID,
666        accounts: vec![
667            AccountMeta::new(signer, true),
668            AccountMeta::new(config_address, false),
669            AccountMeta::new_readonly(new_program, false),
670        ],
671        data: SetSwapProgram {}.to_bytes(),
672    }
673}
674
675pub fn set_var_address(signer: Pubkey, new_var_address: Pubkey) -> Instruction {
676    let board_address = board_pda().0;
677    let config_address = config_pda().0;
678    Instruction {
679        program_id: crate::ID,
680        accounts: vec![
681            AccountMeta::new(signer, true),
682            AccountMeta::new(board_address, false),
683            AccountMeta::new(config_address, false),
684            AccountMeta::new(new_var_address, false),
685        ],
686        data: SetVarAddress {}.to_bytes(),
687    }
688}
689
690/// Migrate a Miner account to add total_stake_score field.
691/// Anyone can call this (signer pays for rent increase).
692pub fn migrate(signer: Pubkey, miner_authority: Pubkey) -> Instruction {
693    let miner_address = miner_pda(miner_authority).0;
694    Instruction {
695        program_id: crate::ID,
696        accounts: vec![
697            AccountMeta::new(signer, true),
698            AccountMeta::new(miner_address, false),
699            AccountMeta::new_readonly(system_program::ID, false),
700        ],
701        data: Migrate {}.to_bytes(),
702    }
703}
704
705/// Create a referral account to become a referrer.
706pub fn create_referral(signer: Pubkey) -> Instruction {
707    let referral_address = referral_pda(signer).0;
708    Instruction {
709        program_id: crate::ID,
710        accounts: vec![
711            AccountMeta::new(signer, true),
712            AccountMeta::new(referral_address, false),
713            AccountMeta::new_readonly(system_program::ID, false),
714        ],
715        data: CreateReferral {}.to_bytes(),
716    }
717}
718
719/// Claim pending referral rewards (both SOL and OIL).
720
721pub fn claim_referral(signer: Pubkey) -> Instruction {
722    let referral_address = referral_pda(signer).0;
723    let referral_oil_address = get_associated_token_address(&referral_address, &MINT_ADDRESS);
724    let recipient_oil_address = get_associated_token_address(&signer, &MINT_ADDRESS);
725    Instruction {
726        program_id: crate::ID,
727        accounts: vec![
728            AccountMeta::new(signer, true),
729            AccountMeta::new(referral_address, false),
730            AccountMeta::new(referral_oil_address, false), // Referral account's OIL ATA
731            AccountMeta::new(MINT_ADDRESS, false),
732            AccountMeta::new(recipient_oil_address, false), // Recipient's OIL ATA
733            AccountMeta::new_readonly(system_program::ID, false),
734            AccountMeta::new_readonly(spl_token::ID, false),
735            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
736        ],
737        data: ClaimReferral {}.to_bytes(),
738    }
739}
740
741/// Direct solo bid on an auction well (seize ownership).
742/// The bid amount is calculated on-chain as current_price + 1 lamport.
743/// User must have enough SOL in their wallet to cover the bid.
744/// 
745/// Account structure:
746/// - Base: signer, square, fund (optional), auction, treasury, treasury_tokens, mint, mint_authority, mint_program, staking_pool, fee_collector, config, token_program, system_program
747/// - If previous owner exists (optional): [previous_owner_miner, previous_owner]
748pub fn place_bid(
749    signer: Pubkey,
750    square_id: u64,
751    fee_collector: Pubkey,
752    pool_account: Option<Pubkey>, // Pool account for current epoch (optional, if pool exists)
753    previous_owner_miner: Option<Pubkey>, // Previous owner's miner PDA (if previous owner exists)
754    previous_owner: Option<Pubkey>, // Previous owner pubkey (if previous owner exists)
755    referrer: Option<Pubkey>, // Optional referrer pubkey for new miners
756) -> Instruction {
757    let well_address = well_pda(square_id).0;
758    let auction_address = auction_pda().0;
759    let treasury_address = treasury_pda().0;
760    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
761    let staking_pool_address = pool_pda().0;
762    let config_address = config_pda().0;
763    let mint_authority_address = oil_mint_api::state::authority_pda().0;
764    
765    let mut accounts = vec![
766        AccountMeta::new(signer, true),
767        AccountMeta::new(well_address, false),
768    ];
769    
770    // Add pool account if provided (for current epoch)
771    if let Some(pool_pubkey) = pool_account {
772        accounts.push(AccountMeta::new(pool_pubkey, false));
773    } else {
774        // Add placeholder (account order must be consistent)
775        accounts.push(AccountMeta::new_readonly(system_program::ID, false)); // Placeholder, will be ignored
776    }
777    
778    accounts.extend_from_slice(&[
779        AccountMeta::new(auction_address, false),
780        AccountMeta::new(treasury_address, false),
781        AccountMeta::new(treasury_tokens_address, false),
782        AccountMeta::new(MINT_ADDRESS, false),
783        AccountMeta::new(mint_authority_address, false),
784        AccountMeta::new_readonly(oil_mint_api::ID, false),
785        AccountMeta::new(staking_pool_address, false),
786        AccountMeta::new(fee_collector, false),
787        AccountMeta::new(config_address, false),
788        AccountMeta::new_readonly(spl_token::ID, false),
789        AccountMeta::new_readonly(system_program::ID, false),
790        AccountMeta::new_readonly(crate::ID, false), // oil_program
791    ]);
792    
793    // Add previous owner accounts if provided
794    if let (Some(miner_pubkey), Some(owner_pubkey)) = (previous_owner_miner, previous_owner) {
795        accounts.push(AccountMeta::new(miner_pubkey, false));
796        accounts.push(AccountMeta::new(owner_pubkey, false));
797    }
798    
799    // Add referral account if referrer is provided
800    if let Some(referrer_pubkey) = referrer {
801        let referral_address = referral_pda(referrer_pubkey).0;
802        accounts.push(AccountMeta::new(referral_address, false));
803    }
804    
805    Instruction {
806        program_id: crate::ID,
807        accounts,
808        data: instruction::PlaceBid {
809            square_id: square_id.to_le_bytes(),
810            referrer: referrer.unwrap_or(Pubkey::default()).to_bytes(),
811        }
812        .to_bytes(),
813    }
814}
815
816/// Claim auction-based OIL rewards
817/// - OIL rewards: from current ownership and previous ownership (pre-minted)
818/// 
819/// Account structure:
820/// - Base: signer, miner, well accounts (one per well in mask), auction pool accounts (optional, one per well), auction, treasury, treasury_tokens, mint, mint_authority, mint_program, recipient, token_program, associated_token_program, system_program, oil_program
821/// - Bid accounts (one per well in mask, required for pool contributors): [bid_0, bid_1, bid_2, bid_3] (must include epoch_id in PDA)
822pub fn claim_auction_oil(
823    signer: Pubkey,
824    well_mask: u8, // Bitmask: bit 0 = well 0, bit 1 = well 1, etc.
825    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (required for wells in mask)
826    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for pool contributors)
827    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for pool contributors, must include epoch_id in PDA)
828) -> Instruction {
829    let miner_address = miner_pda(signer).0;
830    let auction_address = auction_pda().0;
831    let treasury_address = treasury_pda().0;
832    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
833    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
834    let mint_authority_address = oil_mint_api::state::authority_pda().0;
835    
836    let mut accounts = vec![
837        AccountMeta::new(signer, true),
838        AccountMeta::new(miner_address, false),
839    ];
840    
841    // Add well accounts for wells in mask (all 4 required, but only wells in mask are used)
842    for well_opt in well_accounts.iter() {
843        if let Some(well_pubkey) = well_opt {
844            accounts.push(AccountMeta::new(*well_pubkey, false));
845        } else {
846            // Use a placeholder account if well is not provided
847            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
848        }
849    }
850    
851    // Add auction pool accounts if provided (optional, for pool contributors)
852    if let Some(auction_pool_pdas) = auction_pool_accounts {
853        for auction_pool_pda_opt in auction_pool_pdas.iter() {
854            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
855                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
856            } else {
857                // Use a placeholder account if auction pool is not provided
858                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
859            }
860        }
861    } else {
862        // Add 4 placeholder accounts if auction_pool_accounts is None
863        for _ in 0..4 {
864            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
865        }
866    }
867    
868    accounts.extend_from_slice(&[
869        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
870        AccountMeta::new(treasury_address, false),
871        AccountMeta::new(treasury_tokens_address, false),
872        AccountMeta::new(MINT_ADDRESS, false), // Must be writable for mint_oil CPI
873        AccountMeta::new(mint_authority_address, false), // Must be writable for mint_oil CPI
874        AccountMeta::new_readonly(oil_mint_api::ID, false),
875        AccountMeta::new(recipient_address, false),
876        AccountMeta::new_readonly(spl_token::ID, false),
877        AccountMeta::new_readonly(spl_associated_token_account::ID, false),
878        AccountMeta::new_readonly(system_program::ID, false),
879        AccountMeta::new_readonly(crate::ID, false), // oil_program
880    ]);
881    
882    // Add bid accounts if provided (for pool contributors)
883    if let Some(bid_pdas) = bid_accounts {
884        for bid_pda_opt in bid_pdas.iter() {
885            if let Some(bid_pubkey) = bid_pda_opt {
886                accounts.push(AccountMeta::new(*bid_pubkey, false));
887            }
888        }
889    }
890    
891    Instruction {
892        program_id: crate::ID,
893        accounts,
894        data: ClaimAuctionOIL {
895            well_mask,
896        }
897        .to_bytes(),
898    }
899}
900
901/// Claim auction-based SOL rewards
902/// - SOL rewards: from being outbid and refunds from abandoned pools
903/// 
904/// Account structure:
905/// - Base: signer, miner, well accounts (one per well 0-3, for checking abandoned pools), auction pool accounts (optional, one per well), auction, treasury, system_program, oil_program
906/// - Bid accounts (one per well, required for refunds): [bid_0, bid_1, bid_2, bid_3] (must include epoch_id in PDA)
907pub fn claim_auction_sol(
908    signer: Pubkey,
909    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (for checking abandoned pools)
910    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for refunds)
911    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for refunds, must include epoch_id in PDA)
912) -> Instruction {
913    let miner_address = miner_pda(signer).0;
914    let (auction_address, _) = auction_pda();
915    let treasury_address = treasury_pda().0;
916    
917    let mut accounts = vec![
918        AccountMeta::new(signer, true),
919        AccountMeta::new(miner_address, false),
920    ];
921    
922    // Add well accounts (all 4 wells for checking abandoned pools)
923    for well_opt in well_accounts.iter() {
924        if let Some(well_pubkey) = well_opt {
925            accounts.push(AccountMeta::new(*well_pubkey, false));
926        } else {
927            // Use a placeholder account if well is not provided
928            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
929        }
930    }
931    
932    // Add auction pool accounts if provided (optional, for refunds)
933    if let Some(auction_pool_pdas) = auction_pool_accounts {
934        for auction_pool_pda_opt in auction_pool_pdas.iter() {
935            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
936                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
937            } else {
938                // Use a placeholder account if auction pool is not provided
939                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
940            }
941        }
942    } else {
943        // Add 4 placeholder accounts if auction_pool_accounts is None
944        for _ in 0..4 {
945            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
946        }
947    }
948    
949    accounts.extend_from_slice(&[
950        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
951        AccountMeta::new(treasury_address, false),
952        AccountMeta::new_readonly(system_program::ID, false),
953        AccountMeta::new_readonly(crate::ID, false), // oil_program
954    ]);
955    
956    // Add bid accounts if provided (for refunds)
957    if let Some(bid_pdas) = bid_accounts {
958        for bid_pda_opt in bid_pdas.iter() {
959            if let Some(bid_pubkey) = bid_pda_opt {
960                accounts.push(AccountMeta::new(*bid_pubkey, false));
961            }
962        }
963    }
964    
965    Instruction {
966        program_id: crate::ID,
967        accounts,
968        data: ClaimAuctionSOL {
969            _reserved: 0,
970        }
971        .to_bytes(),
972    }
973}
974
975pub fn claim_seeker(signer: Pubkey, mint: Pubkey) -> Instruction {
976    let seeker_address = seeker_pda(mint).0;
977    let token_account_address = get_associated_token_address(&signer, &mint);
978    Instruction {
979        program_id: crate::ID,
980        accounts: vec![
981            AccountMeta::new(signer, true),
982            AccountMeta::new_readonly(mint, false),
983            AccountMeta::new(seeker_address, false),
984            AccountMeta::new(token_account_address, false),
985            AccountMeta::new_readonly(system_program::ID, false),
986        ],
987        data: ClaimSeeker {}.to_bytes(),
988    }
989}