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 top_miner_address = miner_pda(top_miner).0;
439    let treasury_address = TREASURY_ADDRESS;
440    let treasury_tokens_address = treasury_tokens_address();
441    let pool_address = pool_pda().0;
442    let mint_authority_address = oil_mint_api::state::authority_pda().0;
443    let mut reset_instruction = Instruction {
444        program_id: crate::ID,
445        accounts: vec![
446            AccountMeta::new(signer, true),
447            AccountMeta::new(board_address, false),
448            AccountMeta::new(config_address, false),
449            AccountMeta::new(fee_collector, false),
450            AccountMeta::new(mint_address, false),
451            AccountMeta::new(round_address, false),
452            AccountMeta::new(round_next_address, false),
453            AccountMeta::new(top_miner_address, false),
454            AccountMeta::new(treasury_address, false),
455            AccountMeta::new(pool_address, false),
456            AccountMeta::new(treasury_tokens_address, false),
457            AccountMeta::new_readonly(system_program::ID, false),
458            AccountMeta::new_readonly(spl_token::ID, false),
459            AccountMeta::new_readonly(crate::ID, false),
460            AccountMeta::new_readonly(sysvar::slot_hashes::ID, false),
461            AccountMeta::new_readonly(SOL_MINT, false),
462            // Entropy accounts (these are in "other_accounts" after the split)
463            AccountMeta::new(var_address, false),
464            AccountMeta::new_readonly(entropy_rng_api::ID, false),
465            // Mint accounts.
466            AccountMeta::new(mint_authority_address, false),
467            AccountMeta::new_readonly(oil_mint_api::ID, false),
468        ],
469        data: Reset {}.to_bytes(),
470    };
471    
472    // Add miner accounts for seeker rewards (optional)
473    for miner_pubkey in miner_accounts {
474        reset_instruction.accounts.push(AccountMeta::new(
475            miner_pda(*miner_pubkey).0,
476            false,
477        ));
478    }
479    
480    reset_instruction
481}
482    
483// let [signer_info, automation_info, board_info, miner_info, round_info, treasury_info, system_program] =
484
485pub fn checkpoint(signer: Pubkey, authority: Pubkey, round_id: u64) -> Instruction {
486    let miner_address = miner_pda(authority).0;
487    let board_address = board_pda().0;
488    let round_address = round_pda(round_id).0;
489    let treasury_address = TREASURY_ADDRESS;
490    Instruction {
491        program_id: crate::ID,
492        accounts: vec![
493            AccountMeta::new(signer, true), // payer (session payer or regular wallet, receives bot fee)
494            AccountMeta::new(authority, false), // authority (user's wallet, for PDA derivation - must be writable when combined with deploy)
495            AccountMeta::new(board_address, false),
496            AccountMeta::new(miner_address, false),
497            AccountMeta::new(round_address, false),
498            AccountMeta::new(treasury_address, false),
499            AccountMeta::new_readonly(system_program::ID, false),
500        ],
501        data: Checkpoint {}.to_bytes(),
502    }
503}
504
505pub fn set_admin(signer: Pubkey, admin: Pubkey) -> Instruction {
506    let config_address = config_pda().0;
507    Instruction {
508        program_id: crate::ID,
509        accounts: vec![
510            AccountMeta::new(signer, true),
511            AccountMeta::new(config_address, false),
512            AccountMeta::new_readonly(system_program::ID, false),
513        ],
514        data: SetAdmin {
515            admin: admin.to_bytes(),
516        }
517        .to_bytes(),
518    }
519}
520
521pub fn set_admin_fee(signer: Pubkey, admin_fee: u64) -> Instruction {
522    let config_address = config_pda().0;
523    Instruction {
524        program_id: crate::ID,
525        accounts: vec![
526            AccountMeta::new(signer, true),
527            AccountMeta::new(config_address, false),
528            AccountMeta::new_readonly(system_program::ID, false),
529        ],
530        data: SetAdminFee {
531            admin_fee: admin_fee.to_le_bytes(),
532        }
533        .to_bytes(),
534    }
535}
536
537pub fn set_fee_collector(signer: Pubkey, fee_collector: Pubkey) -> Instruction {
538    let config_address = config_pda().0;
539    Instruction {
540        program_id: crate::ID,
541        accounts: vec![
542            AccountMeta::new(signer, true),
543            AccountMeta::new(config_address, false),
544            AccountMeta::new_readonly(system_program::ID, false),
545        ],
546        data: SetFeeCollector {
547            fee_collector: fee_collector.to_bytes(),
548        }
549        .to_bytes(),
550    }
551}
552
553pub fn set_auction(
554    signer: Pubkey,
555    halving_period_seconds: u64,
556    last_halving_time: u64,
557    base_mining_rates: [u64; 4],
558    auction_duration_seconds: u64,
559    starting_prices: [u64; 4],
560    _well_id: u64, // Kept for backwards compatibility, but not used (always updates auction only)
561) -> Instruction {
562    let config_address = config_pda().0;
563    let auction_address = auction_pda().0;
564    
565    Instruction {
566        program_id: crate::ID,
567        accounts: vec![
568            AccountMeta::new(signer, true),
569            AccountMeta::new_readonly(config_address, false),
570            AccountMeta::new(auction_address, false),
571        ],
572        data: SetAuction {
573            halving_period_seconds: halving_period_seconds.to_le_bytes(),
574            last_halving_time: last_halving_time.to_le_bytes(),
575            base_mining_rates: [
576                base_mining_rates[0].to_le_bytes(),
577                base_mining_rates[1].to_le_bytes(),
578                base_mining_rates[2].to_le_bytes(),
579                base_mining_rates[3].to_le_bytes(),
580            ],
581            auction_duration_seconds: auction_duration_seconds.to_le_bytes(),
582            starting_prices: [
583                starting_prices[0].to_le_bytes(),
584                starting_prices[1].to_le_bytes(),
585                starting_prices[2].to_le_bytes(),
586                starting_prices[3].to_le_bytes(),
587            ],
588            well_id: 4u64.to_le_bytes(), // Always use 4 to indicate auction-only update
589        }
590        .to_bytes(),
591    }
592}
593
594// let [signer_info, mint_info, sender_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
595
596pub fn deposit(signer: Pubkey, authority: Pubkey, amount: u64, lock_duration_days: u64, stake_id: u64) -> Instruction {
597    let mint_address = MINT_ADDRESS;
598    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
599    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
600    let sender_address = get_associated_token_address(&authority, &MINT_ADDRESS); // Authority's ATA
601    let pool_address = pool_pda().0;
602    let pool_tokens_address = pool_tokens_address();
603    let miner_address = miner_pda(authority).0; // Derive from authority
604    Instruction {
605        program_id: crate::ID,
606        accounts: vec![
607            AccountMeta::new(signer, true), // payer (session payer or regular wallet, pays fees)
608            AccountMeta::new(authority, true), // authority (user's wallet, signs token transfer and used for PDA derivation)
609            AccountMeta::new(mint_address, false),
610            AccountMeta::new(sender_address, false),
611            AccountMeta::new(stake_address, false),
612            AccountMeta::new(stake_tokens_address, false),
613            AccountMeta::new(pool_address, false),
614            AccountMeta::new(pool_tokens_address, false),
615            AccountMeta::new(miner_address, false),
616            AccountMeta::new_readonly(system_program::ID, false),
617            AccountMeta::new_readonly(spl_token::ID, false),
618            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
619        ],
620        data: Deposit {
621            amount: amount.to_le_bytes(),
622            lock_duration_days: lock_duration_days.to_le_bytes(),
623            stake_id: stake_id.to_le_bytes(),
624        }
625        .to_bytes(),
626    }
627}
628
629// let [signer_info, mint_info, recipient_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
630
631pub fn withdraw(signer: Pubkey, authority: Pubkey, amount: u64, stake_id: u64) -> Instruction {
632    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
633    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
634    let mint_address = MINT_ADDRESS;
635    let recipient_address = get_associated_token_address(&authority, &MINT_ADDRESS); // Authority's ATA
636    let pool_address = pool_pda().0;
637    let pool_tokens_address = pool_tokens_address();
638    let miner_address = miner_pda(authority).0; // Derive from authority
639    Instruction {
640        program_id: crate::ID,
641        accounts: vec![
642            AccountMeta::new(signer, true), // payer (session payer or regular wallet)
643            AccountMeta::new(authority, false), // authority (user's wallet, for PDA derivation)
644            AccountMeta::new(mint_address, false),
645            AccountMeta::new(recipient_address, false),
646            AccountMeta::new(stake_address, false),
647            AccountMeta::new(stake_tokens_address, false),
648            AccountMeta::new(pool_address, false),
649            AccountMeta::new(pool_tokens_address, false),
650            AccountMeta::new(miner_address, false),
651            AccountMeta::new_readonly(system_program::ID, false),
652            AccountMeta::new_readonly(spl_token::ID, false),
653            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
654        ],
655        data: Withdraw {
656            amount: amount.to_le_bytes(),
657            stake_id: stake_id.to_le_bytes(),
658        }
659        .to_bytes(),
660    }
661}
662
663// let [signer_info, automation_info, miner_info, system_program] = accounts else {
664
665/// Reload SOL from miner account to automation balance with single-tier referral system.
666/// 
667/// If the miner has a referrer, 1.0% of the claim goes to the referrer.
668/// 
669/// Account structure:
670/// - Base: signer, automation, miner, system_program
671/// - If miner has referrer (required): [miner_referrer, referral_referrer]
672pub fn reload_sol(
673    signer: Pubkey,
674    authority: Pubkey,
675    referrer_miner: Option<Pubkey>,
676    referrer_referral: Option<Pubkey>,
677) -> Instruction {
678    let automation_address = automation_pda(authority).0;
679    let miner_address = miner_pda(authority).0;
680    
681    let mut accounts = vec![
682        AccountMeta::new(signer, true),
683        AccountMeta::new(automation_address, false),
684        AccountMeta::new(miner_address, false),
685        AccountMeta::new_readonly(system_program::ID, false),
686    ];
687    
688    // Add referral accounts if provided (required when miner has referrer)
689    if let (Some(miner_ref), Some(referral_ref)) = (referrer_miner, referrer_referral) {
690        accounts.push(AccountMeta::new(miner_ref, false));
691        accounts.push(AccountMeta::new(referral_ref, false));
692    }
693    
694    Instruction {
695        program_id: crate::ID,
696        accounts,
697        data: ReloadSOL {}.to_bytes(),
698    }
699}
700
701// let [signer_info, mint_info, recipient_info, stake_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] =
702
703/// Claim SOL yield from staking. Stakers earn SOL rewards (2% of round winnings), not OIL.
704pub fn claim_yield(signer: Pubkey, authority: Pubkey, amount: u64, stake_id: u64) -> Instruction {
705    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
706    let pool_address = pool_pda().0;
707    Instruction {
708        program_id: crate::ID,
709        accounts: vec![
710            AccountMeta::new(signer, true), // payer (session payer or regular wallet)
711            AccountMeta::new(authority, true), // authority (user's wallet, receives SOL and used for PDA derivation)
712            AccountMeta::new(stake_address, false),
713            AccountMeta::new(pool_address, false),
714            AccountMeta::new_readonly(system_program::ID, false),
715        ],
716        data: ClaimYield {
717            amount: amount.to_le_bytes(),
718        }
719        .to_bytes(),
720    }
721}
722
723pub fn new_var(
724    signer: Pubkey,
725    provider: Pubkey,
726    id: u64,
727    commit: [u8; 32],
728    samples: u64,
729) -> Instruction {
730    let board_address = board_pda().0;
731    let config_address = config_pda().0;
732    let var_address = entropy_rng_api::state::var_pda(board_address, id).0;
733    Instruction {
734        program_id: crate::ID,
735        accounts: vec![
736            AccountMeta::new(signer, true),
737            AccountMeta::new(board_address, false),
738            AccountMeta::new(config_address, false),
739            AccountMeta::new(provider, false),
740            AccountMeta::new(var_address, false),
741            AccountMeta::new_readonly(system_program::ID, false),
742            AccountMeta::new_readonly(entropy_rng_api::ID, false),
743        ],
744        data: NewVar {
745            id: id.to_le_bytes(),
746            commit: commit,
747            samples: samples.to_le_bytes(),
748        }
749        .to_bytes(),
750    }
751}
752
753pub fn set_swap_program(signer: Pubkey, new_program: Pubkey) -> Instruction {
754    let config_address = config_pda().0;
755    Instruction {
756        program_id: crate::ID,
757        accounts: vec![
758            AccountMeta::new(signer, true),
759            AccountMeta::new(config_address, false),
760            AccountMeta::new_readonly(new_program, false),
761        ],
762        data: SetSwapProgram {}.to_bytes(),
763    }
764}
765
766pub fn set_var_address(signer: Pubkey, new_var_address: Pubkey) -> Instruction {
767    let board_address = board_pda().0;
768    let config_address = config_pda().0;
769    Instruction {
770        program_id: crate::ID,
771        accounts: vec![
772            AccountMeta::new(signer, true),
773            AccountMeta::new(board_address, false),
774            AccountMeta::new(config_address, false),
775            AccountMeta::new(new_var_address, false),
776        ],
777        data: SetVarAddress {}.to_bytes(),
778    }
779}
780
781/// Migrate: Unwrap wrapped SOL from round's wrapped SOL ATA into native SOL in round account.
782/// This unwraps any wrapped SOL tokens in the round's ATA back to native SOL in the round PDA.
783/// Useful for migrating old rounds that may have wrapped SOL from before the unwrap-on-transfer change.
784pub fn migrate(signer: Pubkey, round_id: u64) -> Instruction {
785    let board_address = board_pda().0;
786    let round_address = round_pda(round_id).0;
787    let round_wrapped_sol_ata = get_associated_token_address(&round_address, &SOL_MINT);
788    Instruction {
789        program_id: crate::ID,
790        accounts: vec![
791            AccountMeta::new(signer, true),
792            AccountMeta::new_readonly(board_address, false), // Board (for validation)
793            AccountMeta::new(round_address, true), // Writable: receives native SOL when unwrapping
794            AccountMeta::new(round_wrapped_sol_ata, true), // Writable: wrapped SOL ATA (source of wrapped SOL)
795            AccountMeta::new_readonly(spl_token::ID, false),
796            AccountMeta::new_readonly(SOL_MINT, false),
797        ],
798        data: Migrate {
799            round_id: round_id.to_le_bytes(),
800        }
801        .to_bytes(),
802    }
803}
804
805/// Create a referral account to become a referrer.
806pub fn create_referral(signer: Pubkey) -> Instruction {
807    let referral_address = referral_pda(signer).0;
808    Instruction {
809        program_id: crate::ID,
810        accounts: vec![
811            AccountMeta::new(signer, true),
812            AccountMeta::new(referral_address, false),
813            AccountMeta::new_readonly(system_program::ID, false),
814        ],
815        data: CreateReferral {}.to_bytes(),
816    }
817}
818
819/// Claim pending referral rewards (both SOL and OIL).
820/// 
821/// Account structure (for Fogo sessions):
822/// - Base: signer (payer), authority (user's wallet), referral, referral_tokens, mint, recipient, system_program, token_program, associated_token_program
823pub fn claim_referral(signer: Pubkey, authority: Pubkey) -> Instruction {
824    let referral_address = referral_pda(authority).0;
825    let referral_oil_address = get_associated_token_address(&referral_address, &MINT_ADDRESS);
826    let recipient_oil_address = get_associated_token_address(&authority, &MINT_ADDRESS);
827    Instruction {
828        program_id: crate::ID,
829        accounts: vec![
830            AccountMeta::new(signer, true), // 0: signer (payer)
831            AccountMeta::new(authority, false), // 1: authority (user's wallet, receives SOL)
832            AccountMeta::new(referral_address, false), // 2: referral
833            AccountMeta::new(referral_oil_address, false), // 3: referral_tokens (Referral account's OIL ATA)
834            AccountMeta::new(MINT_ADDRESS, false), // 4: mint
835            AccountMeta::new(recipient_oil_address, false), // 5: recipient (Recipient's OIL ATA - authority's wallet)
836            AccountMeta::new_readonly(system_program::ID, false), // 6: system_program
837            AccountMeta::new_readonly(spl_token::ID, false), // 7: token_program
838            AccountMeta::new_readonly(spl_associated_token_account::ID, false), // 8: associated_token_program
839        ],
840        data: ClaimReferral {}.to_bytes(),
841    }
842}
843
844/// Direct solo bid on an auction well (seize ownership).
845/// The bid amount is calculated on-chain as current_price + 1 lamport.
846/// User must have enough SOL in their wallet to cover the bid.
847/// 
848/// Account structure:
849/// - Base: signer, square, fund (optional), auction, treasury, treasury_tokens, mint, mint_authority, mint_program, staking_pool, fee_collector, config, token_program, system_program
850/// - If previous owner exists (optional): [previous_owner_miner, previous_owner]
851pub fn place_bid(
852    signer: Pubkey,
853    square_id: u64,
854    fee_collector: Pubkey,
855    pool_account: Option<Pubkey>, // Pool account for current epoch (optional, if pool exists)
856    previous_owner_miner: Option<Pubkey>, // Previous owner's miner PDA (if previous owner exists)
857    previous_owner: Option<Pubkey>, // Previous owner pubkey (if previous owner exists)
858    referrer: Option<Pubkey>, // Optional referrer pubkey for new miners
859) -> Instruction {
860    let well_address = well_pda(square_id).0;
861    let auction_address = auction_pda().0;
862    let treasury_address = treasury_pda().0;
863    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
864    let staking_pool_address = pool_pda().0;
865    let config_address = config_pda().0;
866    let mint_authority_address = oil_mint_api::state::authority_pda().0;
867    
868    let mut accounts = vec![
869        AccountMeta::new(signer, true),
870        AccountMeta::new(well_address, false),
871    ];
872    
873    // Add pool account if provided (for current epoch)
874    if let Some(pool_pubkey) = pool_account {
875        accounts.push(AccountMeta::new(pool_pubkey, false));
876    } else {
877        // Add placeholder (account order must be consistent)
878        accounts.push(AccountMeta::new_readonly(system_program::ID, false)); // Placeholder, will be ignored
879    }
880    
881    accounts.extend_from_slice(&[
882        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
883        AccountMeta::new(treasury_address, false),
884        AccountMeta::new(treasury_tokens_address, false),
885        AccountMeta::new(MINT_ADDRESS, false),
886        AccountMeta::new(mint_authority_address, false),
887        AccountMeta::new_readonly(oil_mint_api::ID, false),
888        AccountMeta::new(staking_pool_address, false),
889        AccountMeta::new(fee_collector, false),
890        AccountMeta::new(config_address, false),
891        AccountMeta::new_readonly(spl_token::ID, false),
892        AccountMeta::new_readonly(system_program::ID, false),
893        AccountMeta::new_readonly(crate::ID, false), // oil_program
894    ]);
895    
896    // Add previous owner accounts if provided
897    if let (Some(miner_pubkey), Some(owner_pubkey)) = (previous_owner_miner, previous_owner) {
898        accounts.push(AccountMeta::new(miner_pubkey, false));
899        accounts.push(AccountMeta::new(owner_pubkey, false));
900    }
901    
902    // Add referral account if referrer is provided
903    if let Some(referrer_pubkey) = referrer {
904        let referral_address = referral_pda(referrer_pubkey).0;
905        accounts.push(AccountMeta::new(referral_address, false));
906    }
907    
908    Instruction {
909        program_id: crate::ID,
910        accounts,
911        data: instruction::PlaceBid {
912            square_id: square_id.to_le_bytes(),
913            referrer: referrer.unwrap_or(Pubkey::default()).to_bytes(),
914        }
915        .to_bytes(),
916    }
917}
918
919/// Claim auction-based OIL rewards
920/// - OIL rewards: from current ownership and previous ownership (pre-minted)
921/// 
922/// Account structure:
923/// - 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
924/// - 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)
925pub fn claim_auction_oil(
926    signer: Pubkey,
927    well_mask: u8, // Bitmask: bit 0 = well 0, bit 1 = well 1, etc.
928    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (required for wells in mask)
929    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for pool contributors)
930    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for pool contributors, must include epoch_id in PDA)
931) -> Instruction {
932    let miner_address = miner_pda(signer).0;
933    let auction_address = auction_pda().0;
934    let treasury_address = treasury_pda().0;
935    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
936    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
937    let mint_authority_address = oil_mint_api::state::authority_pda().0;
938    
939    let mut accounts = vec![
940        AccountMeta::new(signer, true),
941        AccountMeta::new(miner_address, false),
942    ];
943    
944    // Add well accounts for wells in mask (all 4 required, but only wells in mask are used)
945    for well_opt in well_accounts.iter() {
946        if let Some(well_pubkey) = well_opt {
947            accounts.push(AccountMeta::new(*well_pubkey, false));
948        } else {
949            // Use a placeholder account if well is not provided
950            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
951        }
952    }
953    
954    // Add auction pool accounts if provided (optional, for pool contributors)
955    if let Some(auction_pool_pdas) = auction_pool_accounts {
956        for auction_pool_pda_opt in auction_pool_pdas.iter() {
957            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
958                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
959            } else {
960                // Use a placeholder account if auction pool is not provided
961                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
962            }
963        }
964    } else {
965        // Add 4 placeholder accounts if auction_pool_accounts is None
966        for _ in 0..4 {
967            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
968        }
969    }
970    
971    accounts.extend_from_slice(&[
972        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
973        AccountMeta::new(treasury_address, false),
974        AccountMeta::new(treasury_tokens_address, false),
975        AccountMeta::new(MINT_ADDRESS, false), // Must be writable for mint_oil CPI
976        AccountMeta::new(mint_authority_address, false), // Must be writable for mint_oil CPI
977        AccountMeta::new_readonly(oil_mint_api::ID, false),
978        AccountMeta::new(recipient_address, false),
979        AccountMeta::new_readonly(spl_token::ID, false),
980        AccountMeta::new_readonly(spl_associated_token_account::ID, false),
981        AccountMeta::new_readonly(system_program::ID, false),
982        AccountMeta::new_readonly(crate::ID, false), // oil_program
983    ]);
984    
985    // Add bid accounts if provided (for pool contributors)
986    if let Some(bid_pdas) = bid_accounts {
987        for bid_pda_opt in bid_pdas.iter() {
988            if let Some(bid_pubkey) = bid_pda_opt {
989                accounts.push(AccountMeta::new(*bid_pubkey, false));
990            }
991        }
992    }
993    
994    Instruction {
995        program_id: crate::ID,
996        accounts,
997        data: ClaimAuctionOIL {
998            well_mask,
999        }
1000        .to_bytes(),
1001    }
1002}
1003
1004/// Claim auction-based SOL rewards
1005/// - SOL rewards: from being outbid and refunds from abandoned pools
1006/// 
1007/// Account structure:
1008/// - 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
1009/// - Bid accounts (one per well, required for refunds): [bid_0, bid_1, bid_2, bid_3] (must include epoch_id in PDA)
1010pub fn claim_auction_sol(
1011    signer: Pubkey,
1012    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (for checking abandoned pools)
1013    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for refunds)
1014    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for refunds, must include epoch_id in PDA)
1015) -> Instruction {
1016    let miner_address = miner_pda(signer).0;
1017    let (auction_address, _) = auction_pda();
1018    let treasury_address = treasury_pda().0;
1019    
1020    let mut accounts = vec![
1021        AccountMeta::new(signer, true),
1022        AccountMeta::new(miner_address, false),
1023    ];
1024    
1025    // Add well accounts (all 4 wells for checking abandoned pools)
1026    for well_opt in well_accounts.iter() {
1027        if let Some(well_pubkey) = well_opt {
1028            accounts.push(AccountMeta::new(*well_pubkey, false));
1029        } else {
1030            // Use a placeholder account if well is not provided
1031            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1032        }
1033    }
1034    
1035    // Add auction pool accounts if provided (optional, for refunds)
1036    if let Some(auction_pool_pdas) = auction_pool_accounts {
1037        for auction_pool_pda_opt in auction_pool_pdas.iter() {
1038            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
1039                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
1040            } else {
1041                // Use a placeholder account if auction pool is not provided
1042                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1043            }
1044        }
1045    } else {
1046        // Add 4 placeholder accounts if auction_pool_accounts is None
1047        for _ in 0..4 {
1048            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1049        }
1050    }
1051    
1052    accounts.extend_from_slice(&[
1053        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
1054        AccountMeta::new(treasury_address, false),
1055        AccountMeta::new_readonly(system_program::ID, false),
1056        AccountMeta::new_readonly(crate::ID, false), // oil_program
1057    ]);
1058    
1059    // Add bid accounts if provided (for refunds)
1060    if let Some(bid_pdas) = bid_accounts {
1061        for bid_pda_opt in bid_pdas.iter() {
1062            if let Some(bid_pubkey) = bid_pda_opt {
1063                accounts.push(AccountMeta::new(*bid_pubkey, false));
1064            }
1065        }
1066    }
1067    
1068    Instruction {
1069        program_id: crate::ID,
1070        accounts,
1071        data: ClaimAuctionSOL {
1072            _reserved: 0,
1073        }
1074        .to_bytes(),
1075    }
1076}
1077
1078pub fn claim_seeker(signer: Pubkey, mint: Pubkey) -> Instruction {
1079    let seeker_address = seeker_pda(mint).0;
1080    let token_account_address = get_associated_token_address(&signer, &mint);
1081    Instruction {
1082        program_id: crate::ID,
1083        accounts: vec![
1084            AccountMeta::new(signer, true),
1085            AccountMeta::new_readonly(mint, false),
1086            AccountMeta::new(seeker_address, false),
1087            AccountMeta::new(token_account_address, false),
1088            AccountMeta::new_readonly(system_program::ID, false),
1089        ],
1090        data: ClaimSeeker {}.to_bytes(),
1091    }
1092}