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    // Derive wrapped SOL ATAs
256    let user_wrapped_sol_ata = get_associated_token_address(&authority, &SOL_MINT);
257    let round_wrapped_sol_ata = get_associated_token_address(&round_address, &SOL_MINT);
258
259    // Build accounts list - must match program structure:
260    // Oil accounts: base (9) + token (5) + optional referral (1) = 14-15
261    // Entropy accounts: var + program = 2 (always exactly 2)
262    let mut accounts = vec![
263        // Base accounts (9)
264        AccountMeta::new(signer, true), // 0: signer
265        AccountMeta::new(authority, false), // 1: authority
266        AccountMeta::new(automation_address, false), // 2: automation
267        AccountMeta::new(board_address, false), // 3: board
268        AccountMeta::new_readonly(config_address, false), // 4: config
269        AccountMeta::new(miner_address, false), // 5: miner
270        AccountMeta::new(round_address, false), // 6: round
271        AccountMeta::new_readonly(system_program::ID, false), // 7: system_program
272        AccountMeta::new_readonly(crate::ID, false), // 8: oil_program
273        // Token accounts (5)
274        AccountMeta::new(user_wrapped_sol_ata, false), // 9: user_wrapped_sol
275        AccountMeta::new(round_wrapped_sol_ata, false), // 10: round_wrapped_sol
276        AccountMeta::new_readonly(spl_token::ID, false), // 11: token_program
277        AccountMeta::new_readonly(SOL_MINT, false), // 12: mint (SOL_MINT)
278        AccountMeta::new_readonly(spl_associated_token_account::ID, false), // 13: associated_token_program
279    ];
280    
281    // Add referral account if referrer is provided (in oil_accounts, before entropy accounts)
282    if referrer_pubkey != Pubkey::default() {
283        let referral_address = referral_pda(referrer_pubkey).0;
284        accounts.push(AccountMeta::new(referral_address, false)); // 14: referral (optional, in oil_accounts)
285    }
286    
287    // Entropy accounts (always exactly 2, come after all oil_accounts)
288    accounts.push(AccountMeta::new(entropy_var_address, false)); // entropy_var
289    accounts.push(AccountMeta::new_readonly(entropy_rng_api::ID, false)); // entropy_program
290
291    Instruction {
292        program_id: crate::ID,
293        accounts,
294        data: Deploy {
295            amount: amount.to_le_bytes(),
296            squares: mask.to_le_bytes(),
297            referrer: referrer_bytes,
298            pooled: if pooled { 1 } else { 0 },
299        }
300        .to_bytes(),
301    }
302}
303
304/// Deploy instruction for automation (omits wrapped token accounts since automation uses native SOL)
305/// Account structure: base (9) + token programs only (3) + optional referral (1) + entropy (2) = 12-15 accounts
306pub fn deploy_auto(
307    signer: Pubkey,
308    authority: Pubkey,
309    amount: u64,
310    round_id: u64,
311    squares: [bool; 25],
312    referrer: Option<Pubkey>,
313    pooled: bool,
314) -> Instruction {
315    let automation_address = automation_pda(authority).0;
316    let board_address = board_pda().0;
317    let config_address = config_pda().0;
318    let miner_address = miner_pda(authority).0;
319    let round_address = round_pda(round_id).0;
320    let entropy_var_address = entropy_rng_api::state::var_pda(board_address, 0).0;
321
322    // Convert array of 25 booleans into a 32-bit mask where each bit represents whether
323    // that square index is selected (1) or not (0)
324    let mut mask: u32 = 0;
325    for (i, &square) in squares.iter().enumerate() {
326        if square {
327            mask |= 1 << i;
328        }
329    }
330    
331    // Get referrer bytes (default to zero pubkey if no referrer).
332    let referrer_pubkey = referrer.unwrap_or(Pubkey::default());
333    let referrer_bytes = referrer_pubkey.to_bytes();
334
335    // Build accounts list - for automation, omit wrapped token ATAs:
336    // Base (9) + token programs only (3) + optional referral (1) = 12-13
337    // Entropy accounts: var + program = 2 (always exactly 2)
338    // Note: Program allows 12 accounts even when signer != authority (automation pattern)
339    let mut accounts = vec![
340        // Base accounts (9)
341        AccountMeta::new(signer, true), // 0: signer
342        AccountMeta::new(authority, false), // 1: authority
343        AccountMeta::new(automation_address, false), // 2: automation
344        AccountMeta::new(board_address, false), // 3: board
345        AccountMeta::new_readonly(config_address, false), // 4: config
346        AccountMeta::new(miner_address, false), // 5: miner
347        AccountMeta::new(round_address, false), // 6: round
348        AccountMeta::new_readonly(system_program::ID, false), // 7: system_program
349        AccountMeta::new_readonly(crate::ID, false), // 8: oil_program
350        // Token programs only (3) - no ATAs for automation
351        AccountMeta::new_readonly(spl_token::ID, false), // 9: token_program
352        AccountMeta::new_readonly(SOL_MINT, false), // 10: mint (SOL_MINT)
353        AccountMeta::new_readonly(spl_associated_token_account::ID, false), // 11: associated_token_program
354    ];
355    
356    // Add referral account if referrer is provided (in oil_accounts, before entropy accounts)
357    if referrer_pubkey != Pubkey::default() {
358        let referral_address = referral_pda(referrer_pubkey).0;
359        accounts.push(AccountMeta::new(referral_address, false)); // referral (optional, in oil_accounts)
360    }
361    
362    // Entropy accounts (always exactly 2, come after all oil_accounts)
363    accounts.push(AccountMeta::new(entropy_var_address, false)); // entropy_var
364    accounts.push(AccountMeta::new_readonly(entropy_rng_api::ID, false)); // entropy_program
365
366    Instruction {
367        program_id: crate::ID,
368        accounts,
369        data: Deploy {
370            amount: amount.to_le_bytes(),
371            squares: mask.to_le_bytes(),
372            referrer: referrer_bytes,
373            pooled: if pooled { 1 } else { 0 },
374        }
375        .to_bytes(),
376    }
377}
378
379// 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] =
380
381pub fn buyback(signer: Pubkey, swap_accounts: &[AccountMeta], swap_data: &[u8]) -> Instruction {
382    let board_address = board_pda().0;
383    let config_address = config_pda().0;
384    let mint_address = MINT_ADDRESS;
385    let treasury_address = TREASURY_ADDRESS;
386    let treasury_oil_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
387    let treasury_sol_address = get_associated_token_address(&treasury_address, &SOL_MINT);
388    let mut accounts = vec![
389        AccountMeta::new(signer, true),
390        AccountMeta::new(board_address, false),
391        AccountMeta::new_readonly(config_address, false),
392        AccountMeta::new(mint_address, false),
393        AccountMeta::new(treasury_address, false),
394        AccountMeta::new(treasury_oil_address, false),
395        AccountMeta::new(treasury_sol_address, false),
396        AccountMeta::new_readonly(spl_token::ID, false),
397        AccountMeta::new_readonly(crate::ID, false),
398    ];
399    for account in swap_accounts.iter() {
400        let mut acc_clone = account.clone();
401        acc_clone.is_signer = false;
402        accounts.push(acc_clone);
403    }
404    let mut data = Buyback {}.to_bytes();
405    data.extend_from_slice(swap_data);
406    Instruction {
407        program_id: crate::ID,
408        accounts,
409        data,
410    }
411}
412
413// 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] =
414
415pub fn reset(
416    signer: Pubkey,
417    fee_collector: Pubkey,
418    round_id: u64,
419    top_miner: Pubkey,
420    var_address: Pubkey,
421) -> Instruction {
422    reset_with_miners(signer, fee_collector, round_id, top_miner, var_address, &[])
423}
424
425pub fn reset_with_miners(
426    signer: Pubkey,
427    fee_collector: Pubkey,
428    round_id: u64,
429    top_miner: Pubkey,
430    var_address: Pubkey,
431    miner_accounts: &[Pubkey],
432) -> Instruction {
433    let board_address = board_pda().0;
434    let config_address = config_pda().0;
435    let mint_address = MINT_ADDRESS;
436    let round_address = round_pda(round_id).0;
437    let round_next_address = round_pda(round_id + 1).0;
438    let round_next_wrapped_sol_ata = get_associated_token_address(&round_next_address, &SOL_MINT);
439    let top_miner_address = miner_pda(top_miner).0;
440    let treasury_address = TREASURY_ADDRESS;
441    let treasury_tokens_address = treasury_tokens_address();
442    let pool_address = pool_pda().0;
443    let mint_authority_address = oil_mint_api::state::authority_pda().0;
444    let mut reset_instruction = Instruction {
445        program_id: crate::ID,
446        accounts: vec![
447            AccountMeta::new(signer, true),
448            AccountMeta::new(board_address, false),
449            AccountMeta::new(config_address, false),
450            AccountMeta::new(fee_collector, false),
451            AccountMeta::new(mint_address, false),
452            AccountMeta::new(round_address, false),
453            AccountMeta::new(round_next_address, false),
454            AccountMeta::new(top_miner_address, false),
455            AccountMeta::new(treasury_address, false),
456            AccountMeta::new(pool_address, false),
457            AccountMeta::new(treasury_tokens_address, false),
458            AccountMeta::new_readonly(system_program::ID, false),
459            AccountMeta::new_readonly(spl_token::ID, false),
460            AccountMeta::new_readonly(crate::ID, false),
461            AccountMeta::new_readonly(sysvar::slot_hashes::ID, false),
462            // Round wrapped SOL account (for FOGO sessions)
463            AccountMeta::new(round_next_wrapped_sol_ata, false),
464            AccountMeta::new_readonly(SOL_MINT, false), // SOL mint for wrapped SOL account
465            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
466            // Entropy accounts.
467            AccountMeta::new(var_address, false),
468            AccountMeta::new_readonly(entropy_rng_api::ID, false),
469            // Mint accounts.
470            AccountMeta::new(mint_authority_address, false),
471            AccountMeta::new_readonly(oil_mint_api::ID, false),
472        ],
473        data: Reset {}.to_bytes(),
474    };
475    
476    // Add miner accounts for seeker rewards (optional)
477    for miner_pubkey in miner_accounts {
478        reset_instruction.accounts.push(AccountMeta::new(
479            miner_pda(*miner_pubkey).0,
480            false,
481        ));
482    }
483    
484    reset_instruction
485}
486    
487// let [signer_info, automation_info, board_info, miner_info, round_info, treasury_info, system_program] =
488
489pub fn checkpoint(signer: Pubkey, authority: Pubkey, round_id: u64) -> Instruction {
490    let miner_address = miner_pda(authority).0;
491    let board_address = board_pda().0;
492    let round_address = round_pda(round_id).0;
493    let treasury_address = TREASURY_ADDRESS;
494    Instruction {
495        program_id: crate::ID,
496        accounts: vec![
497            AccountMeta::new(signer, true), // payer (session payer or regular wallet, receives bot fee)
498            AccountMeta::new(authority, false), // authority (user's wallet, for PDA derivation - must be writable when combined with deploy)
499            AccountMeta::new(board_address, false),
500            AccountMeta::new(miner_address, false),
501            AccountMeta::new(round_address, false),
502            AccountMeta::new(treasury_address, false),
503            AccountMeta::new_readonly(system_program::ID, false),
504        ],
505        data: Checkpoint {}.to_bytes(),
506    }
507}
508
509pub fn set_admin(signer: Pubkey, admin: Pubkey) -> Instruction {
510    let config_address = config_pda().0;
511    Instruction {
512        program_id: crate::ID,
513        accounts: vec![
514            AccountMeta::new(signer, true),
515            AccountMeta::new(config_address, false),
516            AccountMeta::new_readonly(system_program::ID, false),
517        ],
518        data: SetAdmin {
519            admin: admin.to_bytes(),
520        }
521        .to_bytes(),
522    }
523}
524
525pub fn set_admin_fee(signer: Pubkey, admin_fee: u64) -> Instruction {
526    let config_address = config_pda().0;
527    Instruction {
528        program_id: crate::ID,
529        accounts: vec![
530            AccountMeta::new(signer, true),
531            AccountMeta::new(config_address, false),
532            AccountMeta::new_readonly(system_program::ID, false),
533        ],
534        data: SetAdminFee {
535            admin_fee: admin_fee.to_le_bytes(),
536        }
537        .to_bytes(),
538    }
539}
540
541pub fn set_fee_collector(signer: Pubkey, fee_collector: Pubkey) -> Instruction {
542    let config_address = config_pda().0;
543    Instruction {
544        program_id: crate::ID,
545        accounts: vec![
546            AccountMeta::new(signer, true),
547            AccountMeta::new(config_address, false),
548            AccountMeta::new_readonly(system_program::ID, false),
549        ],
550        data: SetFeeCollector {
551            fee_collector: fee_collector.to_bytes(),
552        }
553        .to_bytes(),
554    }
555}
556
557pub fn set_auction(
558    signer: Pubkey,
559    halving_period_seconds: u64,
560    last_halving_time: u64,
561    base_mining_rates: [u64; 4],
562    auction_duration_seconds: u64,
563    starting_prices: [u64; 4],
564    _well_id: u64, // Kept for backwards compatibility, but not used (always updates auction only)
565) -> Instruction {
566    let config_address = config_pda().0;
567    let auction_address = auction_pda().0;
568    
569    Instruction {
570        program_id: crate::ID,
571        accounts: vec![
572            AccountMeta::new(signer, true),
573            AccountMeta::new_readonly(config_address, false),
574            AccountMeta::new(auction_address, false),
575        ],
576        data: SetAuction {
577            halving_period_seconds: halving_period_seconds.to_le_bytes(),
578            last_halving_time: last_halving_time.to_le_bytes(),
579            base_mining_rates: [
580                base_mining_rates[0].to_le_bytes(),
581                base_mining_rates[1].to_le_bytes(),
582                base_mining_rates[2].to_le_bytes(),
583                base_mining_rates[3].to_le_bytes(),
584            ],
585            auction_duration_seconds: auction_duration_seconds.to_le_bytes(),
586            starting_prices: [
587                starting_prices[0].to_le_bytes(),
588                starting_prices[1].to_le_bytes(),
589                starting_prices[2].to_le_bytes(),
590                starting_prices[3].to_le_bytes(),
591            ],
592            well_id: 4u64.to_le_bytes(), // Always use 4 to indicate auction-only update
593        }
594        .to_bytes(),
595    }
596}
597
598// let [signer_info, mint_info, sender_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
599
600pub fn deposit(signer: Pubkey, authority: Pubkey, amount: u64, lock_duration_days: u64, stake_id: u64) -> Instruction {
601    let mint_address = MINT_ADDRESS;
602    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
603    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
604    let sender_address = get_associated_token_address(&authority, &MINT_ADDRESS); // Authority's ATA
605    let pool_address = pool_pda().0;
606    let pool_tokens_address = pool_tokens_address();
607    let miner_address = miner_pda(authority).0; // Derive from authority
608    Instruction {
609        program_id: crate::ID,
610        accounts: vec![
611            AccountMeta::new(signer, true), // payer (session payer or regular wallet, pays fees)
612            AccountMeta::new(authority, true), // authority (user's wallet, signs token transfer and used for PDA derivation)
613            AccountMeta::new(mint_address, false),
614            AccountMeta::new(sender_address, false),
615            AccountMeta::new(stake_address, false),
616            AccountMeta::new(stake_tokens_address, false),
617            AccountMeta::new(pool_address, false),
618            AccountMeta::new(pool_tokens_address, false),
619            AccountMeta::new(miner_address, false),
620            AccountMeta::new_readonly(system_program::ID, false),
621            AccountMeta::new_readonly(spl_token::ID, false),
622            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
623        ],
624        data: Deposit {
625            amount: amount.to_le_bytes(),
626            lock_duration_days: lock_duration_days.to_le_bytes(),
627            stake_id: stake_id.to_le_bytes(),
628        }
629        .to_bytes(),
630    }
631}
632
633// let [signer_info, mint_info, recipient_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
634
635pub fn withdraw(signer: Pubkey, authority: Pubkey, amount: u64, stake_id: u64) -> Instruction {
636    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
637    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
638    let mint_address = MINT_ADDRESS;
639    let recipient_address = get_associated_token_address(&authority, &MINT_ADDRESS); // Authority's ATA
640    let pool_address = pool_pda().0;
641    let pool_tokens_address = pool_tokens_address();
642    let miner_address = miner_pda(authority).0; // Derive from authority
643    Instruction {
644        program_id: crate::ID,
645        accounts: vec![
646            AccountMeta::new(signer, true), // payer (session payer or regular wallet)
647            AccountMeta::new(authority, false), // authority (user's wallet, for PDA derivation)
648            AccountMeta::new(mint_address, false),
649            AccountMeta::new(recipient_address, false),
650            AccountMeta::new(stake_address, false),
651            AccountMeta::new(stake_tokens_address, false),
652            AccountMeta::new(pool_address, false),
653            AccountMeta::new(pool_tokens_address, false),
654            AccountMeta::new(miner_address, false),
655            AccountMeta::new_readonly(system_program::ID, false),
656            AccountMeta::new_readonly(spl_token::ID, false),
657            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
658        ],
659        data: Withdraw {
660            amount: amount.to_le_bytes(),
661            stake_id: stake_id.to_le_bytes(),
662        }
663        .to_bytes(),
664    }
665}
666
667// let [signer_info, automation_info, miner_info, system_program] = accounts else {
668
669/// Reload SOL from miner account to automation balance with single-tier referral system.
670/// 
671/// If the miner has a referrer, 1.0% of the claim goes to the referrer.
672/// 
673/// Account structure:
674/// - Base: signer, automation, miner, system_program
675/// - If miner has referrer (required): [miner_referrer, referral_referrer]
676pub fn reload_sol(
677    signer: Pubkey,
678    authority: Pubkey,
679    referrer_miner: Option<Pubkey>,
680    referrer_referral: Option<Pubkey>,
681) -> Instruction {
682    let automation_address = automation_pda(authority).0;
683    let miner_address = miner_pda(authority).0;
684    
685    let mut accounts = vec![
686        AccountMeta::new(signer, true),
687        AccountMeta::new(automation_address, false),
688        AccountMeta::new(miner_address, false),
689        AccountMeta::new_readonly(system_program::ID, false),
690    ];
691    
692    // Add referral accounts if provided (required when miner has referrer)
693    if let (Some(miner_ref), Some(referral_ref)) = (referrer_miner, referrer_referral) {
694        accounts.push(AccountMeta::new(miner_ref, false));
695        accounts.push(AccountMeta::new(referral_ref, false));
696    }
697    
698    Instruction {
699        program_id: crate::ID,
700        accounts,
701        data: ReloadSOL {}.to_bytes(),
702    }
703}
704
705// let [signer_info, mint_info, recipient_info, stake_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] =
706
707/// Claim SOL yield from staking. Stakers earn SOL rewards (2% of round winnings), not OIL.
708pub fn claim_yield(signer: Pubkey, authority: Pubkey, amount: u64, stake_id: u64) -> Instruction {
709    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
710    let pool_address = pool_pda().0;
711    Instruction {
712        program_id: crate::ID,
713        accounts: vec![
714            AccountMeta::new(signer, true), // payer (session payer or regular wallet)
715            AccountMeta::new(authority, true), // authority (user's wallet, receives SOL and used for PDA derivation)
716            AccountMeta::new(stake_address, false),
717            AccountMeta::new(pool_address, false),
718            AccountMeta::new_readonly(system_program::ID, false),
719        ],
720        data: ClaimYield {
721            amount: amount.to_le_bytes(),
722        }
723        .to_bytes(),
724    }
725}
726
727pub fn new_var(
728    signer: Pubkey,
729    provider: Pubkey,
730    id: u64,
731    commit: [u8; 32],
732    samples: u64,
733) -> Instruction {
734    let board_address = board_pda().0;
735    let config_address = config_pda().0;
736    let var_address = entropy_rng_api::state::var_pda(board_address, id).0;
737    Instruction {
738        program_id: crate::ID,
739        accounts: vec![
740            AccountMeta::new(signer, true),
741            AccountMeta::new(board_address, false),
742            AccountMeta::new(config_address, false),
743            AccountMeta::new(provider, false),
744            AccountMeta::new(var_address, false),
745            AccountMeta::new_readonly(system_program::ID, false),
746            AccountMeta::new_readonly(entropy_rng_api::ID, false),
747        ],
748        data: NewVar {
749            id: id.to_le_bytes(),
750            commit: commit,
751            samples: samples.to_le_bytes(),
752        }
753        .to_bytes(),
754    }
755}
756
757pub fn set_swap_program(signer: Pubkey, new_program: Pubkey) -> Instruction {
758    let config_address = config_pda().0;
759    Instruction {
760        program_id: crate::ID,
761        accounts: vec![
762            AccountMeta::new(signer, true),
763            AccountMeta::new(config_address, false),
764            AccountMeta::new_readonly(new_program, false),
765        ],
766        data: SetSwapProgram {}.to_bytes(),
767    }
768}
769
770pub fn set_var_address(signer: Pubkey, new_var_address: Pubkey) -> Instruction {
771    let board_address = board_pda().0;
772    let config_address = config_pda().0;
773    Instruction {
774        program_id: crate::ID,
775        accounts: vec![
776            AccountMeta::new(signer, true),
777            AccountMeta::new(board_address, false),
778            AccountMeta::new(config_address, false),
779            AccountMeta::new(new_var_address, false),
780        ],
781        data: SetVarAddress {}.to_bytes(),
782    }
783}
784
785/// Migrate a Miner account to add total_stake_score field.
786/// Anyone can call this (signer pays for rent increase).
787pub fn migrate(signer: Pubkey, miner_authority: Pubkey) -> Instruction {
788    let miner_address = miner_pda(miner_authority).0;
789    Instruction {
790        program_id: crate::ID,
791        accounts: vec![
792            AccountMeta::new(signer, true),
793            AccountMeta::new(miner_address, false),
794            AccountMeta::new_readonly(system_program::ID, false),
795        ],
796        data: Migrate {}.to_bytes(),
797    }
798}
799
800/// Create a referral account to become a referrer.
801pub fn create_referral(signer: Pubkey) -> Instruction {
802    let referral_address = referral_pda(signer).0;
803    Instruction {
804        program_id: crate::ID,
805        accounts: vec![
806            AccountMeta::new(signer, true),
807            AccountMeta::new(referral_address, false),
808            AccountMeta::new_readonly(system_program::ID, false),
809        ],
810        data: CreateReferral {}.to_bytes(),
811    }
812}
813
814/// Claim pending referral rewards (both SOL and OIL).
815/// 
816/// Account structure (for Fogo sessions):
817/// - Base: signer (payer), authority (user's wallet), referral, referral_tokens, mint, recipient, system_program, token_program, associated_token_program
818pub fn claim_referral(signer: Pubkey, authority: Pubkey) -> Instruction {
819    let referral_address = referral_pda(authority).0;
820    let referral_oil_address = get_associated_token_address(&referral_address, &MINT_ADDRESS);
821    let recipient_oil_address = get_associated_token_address(&authority, &MINT_ADDRESS);
822    Instruction {
823        program_id: crate::ID,
824        accounts: vec![
825            AccountMeta::new(signer, true), // 0: signer (payer)
826            AccountMeta::new(authority, false), // 1: authority (user's wallet, receives SOL)
827            AccountMeta::new(referral_address, false), // 2: referral
828            AccountMeta::new(referral_oil_address, false), // 3: referral_tokens (Referral account's OIL ATA)
829            AccountMeta::new(MINT_ADDRESS, false), // 4: mint
830            AccountMeta::new(recipient_oil_address, false), // 5: recipient (Recipient's OIL ATA - authority's wallet)
831            AccountMeta::new_readonly(system_program::ID, false), // 6: system_program
832            AccountMeta::new_readonly(spl_token::ID, false), // 7: token_program
833            AccountMeta::new_readonly(spl_associated_token_account::ID, false), // 8: associated_token_program
834        ],
835        data: ClaimReferral {}.to_bytes(),
836    }
837}
838
839/// Direct solo bid on an auction well (seize ownership).
840/// The bid amount is calculated on-chain as current_price + 1 lamport.
841/// User must have enough SOL in their wallet to cover the bid.
842/// 
843/// Account structure:
844/// - Base: signer, square, fund (optional), auction, treasury, treasury_tokens, mint, mint_authority, mint_program, staking_pool, fee_collector, config, token_program, system_program
845/// - If previous owner exists (optional): [previous_owner_miner, previous_owner]
846pub fn place_bid(
847    signer: Pubkey,
848    square_id: u64,
849    fee_collector: Pubkey,
850    pool_account: Option<Pubkey>, // Pool account for current epoch (optional, if pool exists)
851    previous_owner_miner: Option<Pubkey>, // Previous owner's miner PDA (if previous owner exists)
852    previous_owner: Option<Pubkey>, // Previous owner pubkey (if previous owner exists)
853    referrer: Option<Pubkey>, // Optional referrer pubkey for new miners
854) -> Instruction {
855    let well_address = well_pda(square_id).0;
856    let auction_address = auction_pda().0;
857    let treasury_address = treasury_pda().0;
858    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
859    let staking_pool_address = pool_pda().0;
860    let config_address = config_pda().0;
861    let mint_authority_address = oil_mint_api::state::authority_pda().0;
862    
863    let mut accounts = vec![
864        AccountMeta::new(signer, true),
865        AccountMeta::new(well_address, false),
866    ];
867    
868    // Add pool account if provided (for current epoch)
869    if let Some(pool_pubkey) = pool_account {
870        accounts.push(AccountMeta::new(pool_pubkey, false));
871    } else {
872        // Add placeholder (account order must be consistent)
873        accounts.push(AccountMeta::new_readonly(system_program::ID, false)); // Placeholder, will be ignored
874    }
875    
876    accounts.extend_from_slice(&[
877        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
878        AccountMeta::new(treasury_address, false),
879        AccountMeta::new(treasury_tokens_address, false),
880        AccountMeta::new(MINT_ADDRESS, false),
881        AccountMeta::new(mint_authority_address, false),
882        AccountMeta::new_readonly(oil_mint_api::ID, false),
883        AccountMeta::new(staking_pool_address, false),
884        AccountMeta::new(fee_collector, false),
885        AccountMeta::new(config_address, false),
886        AccountMeta::new_readonly(spl_token::ID, false),
887        AccountMeta::new_readonly(system_program::ID, false),
888        AccountMeta::new_readonly(crate::ID, false), // oil_program
889    ]);
890    
891    // Add previous owner accounts if provided
892    if let (Some(miner_pubkey), Some(owner_pubkey)) = (previous_owner_miner, previous_owner) {
893        accounts.push(AccountMeta::new(miner_pubkey, false));
894        accounts.push(AccountMeta::new(owner_pubkey, false));
895    }
896    
897    // Add referral account if referrer is provided
898    if let Some(referrer_pubkey) = referrer {
899        let referral_address = referral_pda(referrer_pubkey).0;
900        accounts.push(AccountMeta::new(referral_address, false));
901    }
902    
903    Instruction {
904        program_id: crate::ID,
905        accounts,
906        data: instruction::PlaceBid {
907            square_id: square_id.to_le_bytes(),
908            referrer: referrer.unwrap_or(Pubkey::default()).to_bytes(),
909        }
910        .to_bytes(),
911    }
912}
913
914/// Claim auction-based OIL rewards
915/// - OIL rewards: from current ownership and previous ownership (pre-minted)
916/// 
917/// Account structure:
918/// - 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
919/// - 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)
920pub fn claim_auction_oil(
921    signer: Pubkey,
922    well_mask: u8, // Bitmask: bit 0 = well 0, bit 1 = well 1, etc.
923    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (required for wells in mask)
924    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for pool contributors)
925    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for pool contributors, must include epoch_id in PDA)
926) -> Instruction {
927    let miner_address = miner_pda(signer).0;
928    let auction_address = auction_pda().0;
929    let treasury_address = treasury_pda().0;
930    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
931    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
932    let mint_authority_address = oil_mint_api::state::authority_pda().0;
933    
934    let mut accounts = vec![
935        AccountMeta::new(signer, true),
936        AccountMeta::new(miner_address, false),
937    ];
938    
939    // Add well accounts for wells in mask (all 4 required, but only wells in mask are used)
940    for well_opt in well_accounts.iter() {
941        if let Some(well_pubkey) = well_opt {
942            accounts.push(AccountMeta::new(*well_pubkey, false));
943        } else {
944            // Use a placeholder account if well is not provided
945            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
946        }
947    }
948    
949    // Add auction pool accounts if provided (optional, for pool contributors)
950    if let Some(auction_pool_pdas) = auction_pool_accounts {
951        for auction_pool_pda_opt in auction_pool_pdas.iter() {
952            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
953                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
954            } else {
955                // Use a placeholder account if auction pool is not provided
956                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
957            }
958        }
959    } else {
960        // Add 4 placeholder accounts if auction_pool_accounts is None
961        for _ in 0..4 {
962            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
963        }
964    }
965    
966    accounts.extend_from_slice(&[
967        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
968        AccountMeta::new(treasury_address, false),
969        AccountMeta::new(treasury_tokens_address, false),
970        AccountMeta::new(MINT_ADDRESS, false), // Must be writable for mint_oil CPI
971        AccountMeta::new(mint_authority_address, false), // Must be writable for mint_oil CPI
972        AccountMeta::new_readonly(oil_mint_api::ID, false),
973        AccountMeta::new(recipient_address, false),
974        AccountMeta::new_readonly(spl_token::ID, false),
975        AccountMeta::new_readonly(spl_associated_token_account::ID, false),
976        AccountMeta::new_readonly(system_program::ID, false),
977        AccountMeta::new_readonly(crate::ID, false), // oil_program
978    ]);
979    
980    // Add bid accounts if provided (for pool contributors)
981    if let Some(bid_pdas) = bid_accounts {
982        for bid_pda_opt in bid_pdas.iter() {
983            if let Some(bid_pubkey) = bid_pda_opt {
984                accounts.push(AccountMeta::new(*bid_pubkey, false));
985            }
986        }
987    }
988    
989    Instruction {
990        program_id: crate::ID,
991        accounts,
992        data: ClaimAuctionOIL {
993            well_mask,
994        }
995        .to_bytes(),
996    }
997}
998
999/// Claim auction-based SOL rewards
1000/// - SOL rewards: from being outbid and refunds from abandoned pools
1001/// 
1002/// Account structure:
1003/// - 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
1004/// - Bid accounts (one per well, required for refunds): [bid_0, bid_1, bid_2, bid_3] (must include epoch_id in PDA)
1005pub fn claim_auction_sol(
1006    signer: Pubkey,
1007    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (for checking abandoned pools)
1008    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for refunds)
1009    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for refunds, must include epoch_id in PDA)
1010) -> Instruction {
1011    let miner_address = miner_pda(signer).0;
1012    let (auction_address, _) = auction_pda();
1013    let treasury_address = treasury_pda().0;
1014    
1015    let mut accounts = vec![
1016        AccountMeta::new(signer, true),
1017        AccountMeta::new(miner_address, false),
1018    ];
1019    
1020    // Add well accounts (all 4 wells for checking abandoned pools)
1021    for well_opt in well_accounts.iter() {
1022        if let Some(well_pubkey) = well_opt {
1023            accounts.push(AccountMeta::new(*well_pubkey, false));
1024        } else {
1025            // Use a placeholder account if well is not provided
1026            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1027        }
1028    }
1029    
1030    // Add auction pool accounts if provided (optional, for refunds)
1031    if let Some(auction_pool_pdas) = auction_pool_accounts {
1032        for auction_pool_pda_opt in auction_pool_pdas.iter() {
1033            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
1034                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
1035            } else {
1036                // Use a placeholder account if auction pool is not provided
1037                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1038            }
1039        }
1040    } else {
1041        // Add 4 placeholder accounts if auction_pool_accounts is None
1042        for _ in 0..4 {
1043            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1044        }
1045    }
1046    
1047    accounts.extend_from_slice(&[
1048        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
1049        AccountMeta::new(treasury_address, false),
1050        AccountMeta::new_readonly(system_program::ID, false),
1051        AccountMeta::new_readonly(crate::ID, false), // oil_program
1052    ]);
1053    
1054    // Add bid accounts if provided (for refunds)
1055    if let Some(bid_pdas) = bid_accounts {
1056        for bid_pda_opt in bid_pdas.iter() {
1057            if let Some(bid_pubkey) = bid_pda_opt {
1058                accounts.push(AccountMeta::new(*bid_pubkey, false));
1059            }
1060        }
1061    }
1062    
1063    Instruction {
1064        program_id: crate::ID,
1065        accounts,
1066        data: ClaimAuctionSOL {
1067            _reserved: 0,
1068        }
1069        .to_bytes(),
1070    }
1071}
1072
1073pub fn claim_seeker(signer: Pubkey, mint: Pubkey) -> Instruction {
1074    let seeker_address = seeker_pda(mint).0;
1075    let token_account_address = get_associated_token_address(&signer, &mint);
1076    Instruction {
1077        program_id: crate::ID,
1078        accounts: vec![
1079            AccountMeta::new(signer, true),
1080            AccountMeta::new_readonly(mint, false),
1081            AccountMeta::new(seeker_address, false),
1082            AccountMeta::new(token_account_address, false),
1083            AccountMeta::new_readonly(system_program::ID, false),
1084        ],
1085        data: ClaimSeeker {}.to_bytes(),
1086    }
1087}