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    authority: Pubkey,
80    amount: u64,
81    deposit: u64,
82    executor: Pubkey,
83    fee: u64,
84    mask: u64,
85    strategy: u8,
86    reload: bool,
87    referrer: Option<Pubkey>,
88    pooled: bool,
89    is_new_miner: bool,
90    access_code_hash: Option<[u8; 32]>,
91) -> Instruction {
92    let automation_address = automation_pda(authority).0;
93    let miner_address = miner_pda(authority).0;
94    let config_address = config_pda().0;
95    let referrer_pk = referrer.unwrap_or(Pubkey::default());
96    
97    let mut accounts = vec![
98            AccountMeta::new(signer, true), // 0: signer (payer)
99            AccountMeta::new(authority, false), // 1: authority (user's wallet)
100            AccountMeta::new(automation_address, false), // 2: automation
101            AccountMeta::new(executor, false), // 3: executor
102            AccountMeta::new(miner_address, false), // 4: miner
103            AccountMeta::new_readonly(system_program::ID, false), // 5: system_program
104            AccountMeta::new_readonly(crate::ID, false), // 6: oil_program
105            AccountMeta::new_readonly(config_address, false), // 7: config
106    ];
107    
108    // Token accounts (user_wrapped_sol, automation_wrapped_sol, token_program, program_signer (optional), payer (optional), mint, ata_program)
109    // These are added by the client, not here in the SDK
110    
111    // Add referral account if referrer is provided and miner is new (for incrementing total_referred)
112    if is_new_miner && referrer.is_some() && referrer_pk != Pubkey::default() {
113        let referral_address = referral_pda(referrer_pk).0;
114        accounts.push(AccountMeta::new(referral_address, false));
115    }
116    
117    // Add Whitelist account if access_code_hash is provided
118    let access_code_hash_bytes = access_code_hash.unwrap_or([0; 32]);
119    if access_code_hash_bytes != [0; 32] {
120        let (whitelist_address, _) = Whitelist::pda(access_code_hash_bytes);
121        accounts.push(AccountMeta::new(whitelist_address, false)); // whitelist (optional)
122    }
123    
124    Instruction {
125        program_id: crate::ID,
126        accounts,
127        data: Automate {
128            amount: amount.to_le_bytes(),
129            deposit: deposit.to_le_bytes(),
130            fee: fee.to_le_bytes(),
131            mask: mask.to_le_bytes(),
132            strategy: strategy as u8,
133            reload: (reload as u64).to_le_bytes(),
134            referrer: referrer_pk.to_bytes(),
135            pooled: pooled as u8,
136            access_code_hash: access_code_hash_bytes,
137        }
138        .to_bytes(),
139    }
140}
141
142/// Claim SOL rewards with single-tier referral system.
143/// 
144/// If the miner has a referrer, 1.0% of the claim goes to the referrer.
145/// 
146/// Account structure:
147/// - Base: signer, miner, system_program
148/// - If miner has referrer (required): [miner_referrer, referral_referrer]
149pub fn claim_sol(
150    signer: Pubkey,
151    referrer_miner: Option<Pubkey>, // Referrer's miner PDA (if miner has referrer)
152    referrer_referral: Option<Pubkey>, // Referrer's referral PDA (if miner has referrer)
153) -> Instruction {
154    let miner_address = miner_pda(signer).0;
155    
156    let mut accounts = vec![
157        AccountMeta::new(signer, true),
158        AccountMeta::new(miner_address, false),
159        AccountMeta::new_readonly(system_program::ID, false),
160    ];
161    
162    // Add referrer accounts if provided (required if miner has referrer)
163    if let (Some(miner_pubkey), Some(referral_pubkey)) = (referrer_miner, referrer_referral) {
164        accounts.push(AccountMeta::new(miner_pubkey, false));
165        accounts.push(AccountMeta::new(referral_pubkey, false));
166    }
167    
168    Instruction {
169        program_id: crate::ID,
170        accounts,
171        data: ClaimSOL {}.to_bytes(),
172    }
173}
174
175// let [signer_info, miner_info, mint_info, recipient_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] =
176
177/// Claim OIL rewards with single-tier referral system.
178/// 
179/// If the miner has a referrer, 1.0% of the claim goes to the referrer.
180/// 
181/// Account structure:
182/// - Base: signer, miner, mint, recipient, treasury, treasury_tokens, system_program, token_program, associated_token_program
183/// - If miner has referrer (required): [miner_referrer, referral_referrer, referral_referrer_oil_ata]
184pub fn claim_oil(
185    signer: Pubkey,
186    referrer_miner: Option<Pubkey>, // Referrer's miner PDA (if miner has referrer)
187    referrer_referral: Option<Pubkey>, // Referrer's referral PDA (if miner has referrer)
188    referrer_referral_oil_ata: Option<Pubkey>, // Referrer's referral OIL ATA (if miner has referrer)
189) -> Instruction {
190    let miner_address = miner_pda(signer).0;
191    let treasury_address = treasury_pda().0;
192    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
193    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
194    
195    let mut accounts = vec![
196        AccountMeta::new(signer, true),
197        AccountMeta::new(miner_address, false),
198        AccountMeta::new(MINT_ADDRESS, false),
199        AccountMeta::new(recipient_address, false),
200        AccountMeta::new(treasury_address, false),
201        AccountMeta::new(treasury_tokens_address, false),
202        AccountMeta::new_readonly(system_program::ID, false),
203        AccountMeta::new_readonly(spl_token::ID, false),
204        AccountMeta::new_readonly(spl_associated_token_account::ID, false),
205    ];
206    
207    // Add referrer accounts if provided (required if miner has referrer)
208    if let (Some(miner_pubkey), Some(referral_pubkey), Some(oil_ata_pubkey)) = 
209        (referrer_miner, referrer_referral, referrer_referral_oil_ata) {
210        accounts.push(AccountMeta::new(miner_pubkey, false));
211        accounts.push(AccountMeta::new(referral_pubkey, false));
212        accounts.push(AccountMeta::new(oil_ata_pubkey, false));
213    }
214    
215    Instruction {
216        program_id: crate::ID,
217        accounts,
218        data: ClaimOIL {}.to_bytes(),
219    }
220}
221
222
223pub fn close(signer: Pubkey, round_id: u64, rent_payer: Pubkey) -> Instruction {
224    let board_address = board_pda().0;
225    let round_address = round_pda(round_id).0;
226    let treasury_address = TREASURY_ADDRESS;
227    Instruction {
228        program_id: crate::ID,
229        accounts: vec![
230            AccountMeta::new(signer, true),
231            AccountMeta::new(board_address, false),
232            AccountMeta::new(rent_payer, false),
233            AccountMeta::new(round_address, false),
234            AccountMeta::new(treasury_address, false),
235            AccountMeta::new_readonly(system_program::ID, false),
236        ],
237        data: Close {}.to_bytes(),
238    }
239}
240
241/// Deploy SOL to prospect on squares (for FOGO sessions or regular wallets with wrapped SOL).
242/// 
243/// This function includes wrapped SOL ATAs and is used for:
244/// - FOGO sessions (signer != authority, with wrapped SOL transfers)
245/// - Regular wallets using wrapped SOL
246/// 
247/// For automations (bot-executed deploys using native SOL from automation account balance),
248/// use `deploy_auto` instead.
249/// 
250/// Pass a referrer pubkey for new miners to set up referral.
251/// Set `pooled` to true to join the mining pool (rewards shared proportionally).
252pub fn deploy(
253    signer: Pubkey,
254    authority: Pubkey,
255    amount: u64,
256    round_id: u64,
257    squares: [bool; 25],
258    referrer: Option<Pubkey>,
259    pooled: bool,
260    access_code_hash: Option<[u8; 32]>,
261) -> Instruction {
262    let automation_address = automation_pda(authority).0;
263    let board_address = board_pda().0;
264    let config_address = config_pda().0;
265    let miner_address = miner_pda(authority).0;
266    let round_address = round_pda(round_id).0;
267    let entropy_var_address = entropy_rng_api::state::var_pda(board_address, 0).0;
268
269    // Convert array of 25 booleans into a 32-bit mask where each bit represents whether
270    // that square index is selected (1) or not (0)
271    let mut mask: u32 = 0;
272    for (i, &square) in squares.iter().enumerate() {
273        if square {
274            mask |= 1 << i;
275        }
276    }
277    
278    // Get referrer bytes (default to zero pubkey if no referrer).
279    let referrer_pubkey = referrer.unwrap_or(Pubkey::default());
280    let referrer_bytes = referrer_pubkey.to_bytes();
281
282    // Derive wrapped SOL ATAs
283    let user_wrapped_sol_ata = get_associated_token_address(&authority, &SOL_MINT);
284    let round_wrapped_sol_ata = get_associated_token_address(&round_address, &SOL_MINT);
285
286    // Build accounts list - must match program structure:
287    // Oil accounts: base (9) + token (5) + optional referral (1) = 14-15
288    // Entropy accounts: var + program = 2 (always exactly 2)
289    let mut accounts = vec![
290        // Base accounts (9)
291        AccountMeta::new(signer, true), // 0: signer
292        AccountMeta::new(authority, false), // 1: authority
293        AccountMeta::new(automation_address, false), // 2: automation
294        AccountMeta::new(board_address, false), // 3: board
295        AccountMeta::new_readonly(config_address, false), // 4: config
296        AccountMeta::new(miner_address, false), // 5: miner
297        AccountMeta::new(round_address, false), // 6: round
298        AccountMeta::new_readonly(system_program::ID, false), // 7: system_program
299        AccountMeta::new_readonly(crate::ID, false), // 8: oil_program
300        // Token accounts (5)
301        AccountMeta::new(user_wrapped_sol_ata, false), // 9: user_wrapped_sol
302        AccountMeta::new(round_wrapped_sol_ata, false), // 10: round_wrapped_sol
303        AccountMeta::new_readonly(spl_token::ID, false), // 11: token_program
304        AccountMeta::new_readonly(SOL_MINT, false), // 12: mint (SOL_MINT)
305        AccountMeta::new_readonly(spl_associated_token_account::ID, false), // 13: associated_token_program
306    ];
307    
308    // Add referral account if referrer is provided (in oil_accounts, before whitelist account)
309    if referrer_pubkey != Pubkey::default() {
310        let referral_address = referral_pda(referrer_pubkey).0;
311        accounts.push(AccountMeta::new(referral_address, false)); // referral (optional, in oil_accounts)
312    }
313    
314    // Add Whitelist account if access_code_hash is provided (in oil_accounts, after referral if present)
315    let access_code_hash_bytes = access_code_hash.unwrap_or([0; 32]);
316    if access_code_hash_bytes != [0; 32] {
317        let (whitelist_address, _) = Whitelist::pda(access_code_hash_bytes);
318        accounts.push(AccountMeta::new(whitelist_address, false)); // whitelist (optional, in oil_accounts)
319    }
320    
321    // Entropy accounts (always exactly 2, come after all oil_accounts)
322    accounts.push(AccountMeta::new(entropy_var_address, false)); // entropy_var
323    accounts.push(AccountMeta::new_readonly(entropy_rng_api::ID, false)); // entropy_program
324
325    Instruction {
326        program_id: crate::ID,
327        accounts,
328        data: Deploy {
329            amount: amount.to_le_bytes(),
330            squares: mask.to_le_bytes(),
331            referrer: referrer_bytes,
332            pooled: if pooled { 1 } else { 0 },
333            access_code_hash: access_code_hash_bytes,
334        }
335        .to_bytes(),
336    }
337}
338
339/// Deploy instruction for automations (omits wrapped token accounts since automation uses native SOL).
340/// 
341/// This function is used when:
342/// - Bot executor (signer) != user authority (automation pattern)
343/// - Automation account has native SOL balance to deploy
344/// - No wrapped SOL ATAs needed (uses native SOL directly)
345/// 
346/// Account structure: base (9) + token programs only (3) + optional referral (1) + optional whitelist (1) + entropy (2) = 12-16 accounts
347/// 
348/// For FOGO sessions or regular wallets with wrapped SOL, use `deploy` instead.
349pub fn deploy_auto(
350    signer: Pubkey,
351    authority: Pubkey,
352    amount: u64,
353    round_id: u64,
354    squares: [bool; 25],
355    referrer: Option<Pubkey>,
356    pooled: bool,
357    access_code_hash: Option<[u8; 32]>,
358) -> Instruction {
359    let automation_address = automation_pda(authority).0;
360    let board_address = board_pda().0;
361    let config_address = config_pda().0;
362    let miner_address = miner_pda(authority).0;
363    let round_address = round_pda(round_id).0;
364    let entropy_var_address = entropy_rng_api::state::var_pda(board_address, 0).0;
365
366    // Convert array of 25 booleans into a 32-bit mask where each bit represents whether
367    // that square index is selected (1) or not (0)
368    let mut mask: u32 = 0;
369    for (i, &square) in squares.iter().enumerate() {
370        if square {
371            mask |= 1 << i;
372        }
373    }
374    
375    // Get referrer bytes (default to zero pubkey if no referrer).
376    let referrer_pubkey = referrer.unwrap_or(Pubkey::default());
377    let referrer_bytes = referrer_pubkey.to_bytes();
378
379    // Build accounts list - for automation, omit wrapped token ATAs:
380    // Base (9) + token programs only (3) + optional referral (1) = 12-13
381    // Entropy accounts: var + program = 2 (always exactly 2)
382    // Note: Program allows 12 accounts even when signer != authority (automation pattern)
383    let mut accounts = vec![
384        // Base accounts (9)
385        AccountMeta::new(signer, true), // 0: signer
386        AccountMeta::new(authority, false), // 1: authority
387        AccountMeta::new(automation_address, false), // 2: automation
388        AccountMeta::new(board_address, false), // 3: board
389        AccountMeta::new_readonly(config_address, false), // 4: config
390        AccountMeta::new(miner_address, false), // 5: miner
391        AccountMeta::new(round_address, false), // 6: round
392        AccountMeta::new_readonly(system_program::ID, false), // 7: system_program
393        AccountMeta::new_readonly(crate::ID, false), // 8: oil_program
394        // Token programs only (3) - no ATAs for automation
395        AccountMeta::new_readonly(spl_token::ID, false), // 9: token_program
396        AccountMeta::new_readonly(SOL_MINT, false), // 10: mint (SOL_MINT)
397        AccountMeta::new_readonly(spl_associated_token_account::ID, false), // 11: associated_token_program
398    ];
399    
400    // Add referral account if referrer is provided (in oil_accounts, before whitelist account)
401    if referrer_pubkey != Pubkey::default() {
402        let referral_address = referral_pda(referrer_pubkey).0;
403        accounts.push(AccountMeta::new(referral_address, false)); // referral (optional, in oil_accounts)
404    }
405    
406    // Add Whitelist account if access_code_hash is provided (in oil_accounts, after referral if present)
407    let access_code_hash_bytes = access_code_hash.unwrap_or([0; 32]);
408    if access_code_hash_bytes != [0; 32] {
409        let (whitelist_address, _) = Whitelist::pda(access_code_hash_bytes);
410        accounts.push(AccountMeta::new(whitelist_address, false)); // whitelist (optional, in oil_accounts)
411    }
412    
413    // Entropy accounts (always exactly 2, come after all oil_accounts)
414    accounts.push(AccountMeta::new(entropy_var_address, false)); // entropy_var
415    accounts.push(AccountMeta::new_readonly(entropy_rng_api::ID, false)); // entropy_program
416
417    Instruction {
418        program_id: crate::ID,
419        accounts,
420        data: Deploy {
421            amount: amount.to_le_bytes(),
422            squares: mask.to_le_bytes(),
423            referrer: referrer_bytes,
424            pooled: if pooled { 1 } else { 0 },
425            access_code_hash: access_code_hash_bytes,
426        }
427        .to_bytes(),
428    }
429}
430
431// 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] =
432
433pub fn buyback(signer: Pubkey, swap_accounts: &[AccountMeta], swap_data: &[u8]) -> Instruction {
434    let board_address = board_pda().0;
435    let config_address = config_pda().0;
436    let mint_address = MINT_ADDRESS;
437    let treasury_address = TREASURY_ADDRESS;
438    let treasury_oil_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
439    let treasury_sol_address = get_associated_token_address(&treasury_address, &SOL_MINT);
440    let mut accounts = vec![
441        AccountMeta::new(signer, true),
442        AccountMeta::new(board_address, false),
443        AccountMeta::new_readonly(config_address, false),
444        AccountMeta::new(mint_address, false),
445        AccountMeta::new(treasury_address, false),
446        AccountMeta::new(treasury_oil_address, false),
447        AccountMeta::new(treasury_sol_address, false),
448        AccountMeta::new_readonly(spl_token::ID, false),
449        AccountMeta::new_readonly(crate::ID, false),
450    ];
451    for account in swap_accounts.iter() {
452        let mut acc_clone = account.clone();
453        acc_clone.is_signer = false;
454        accounts.push(acc_clone);
455    }
456    let mut data = Buyback {}.to_bytes();
457    data.extend_from_slice(swap_data);
458    Instruction {
459        program_id: crate::ID,
460        accounts,
461        data,
462    }
463}
464
465// 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] =
466
467pub fn reset(
468    signer: Pubkey,
469    fee_collector: Pubkey,
470    round_id: u64,
471    top_miner: Pubkey,
472    var_address: Pubkey,
473) -> Instruction {
474    reset_with_miners(signer, fee_collector, round_id, top_miner, var_address, &[])
475}
476
477pub fn reset_with_miners(
478    signer: Pubkey,
479    fee_collector: Pubkey,
480    round_id: u64,
481    top_miner: Pubkey,
482    var_address: Pubkey,
483    miner_accounts: &[Pubkey],
484) -> Instruction {
485    let board_address = board_pda().0;
486    let config_address = config_pda().0;
487    let mint_address = MINT_ADDRESS;
488    let round_address = round_pda(round_id).0;
489    let round_next_address = round_pda(round_id + 1).0;
490    let top_miner_address = miner_pda(top_miner).0;
491    let treasury_address = TREASURY_ADDRESS;
492    let treasury_tokens_address = treasury_tokens_address();
493    let pool_address = pool_pda().0;
494    let mint_authority_address = oil_mint_api::state::authority_pda().0;
495    let mut reset_instruction = Instruction {
496        program_id: crate::ID,
497        accounts: vec![
498            AccountMeta::new(signer, true),
499            AccountMeta::new(board_address, false),
500            AccountMeta::new(config_address, false),
501            AccountMeta::new(fee_collector, false),
502            AccountMeta::new(mint_address, false),
503            AccountMeta::new(round_address, false),
504            AccountMeta::new(round_next_address, false),
505            AccountMeta::new(top_miner_address, false),
506            AccountMeta::new(treasury_address, false),
507            AccountMeta::new(pool_address, false),
508            AccountMeta::new(treasury_tokens_address, false),
509            AccountMeta::new_readonly(system_program::ID, false),
510            AccountMeta::new_readonly(spl_token::ID, false),
511            AccountMeta::new_readonly(crate::ID, false),
512            AccountMeta::new_readonly(sysvar::slot_hashes::ID, false),
513            AccountMeta::new_readonly(SOL_MINT, false),
514            // Entropy accounts (these are in "other_accounts" after the split)
515            AccountMeta::new(var_address, false),
516            AccountMeta::new_readonly(entropy_rng_api::ID, false),
517            // Mint accounts.
518            AccountMeta::new(mint_authority_address, false),
519            AccountMeta::new_readonly(oil_mint_api::ID, false),
520        ],
521        data: Reset {}.to_bytes(),
522    };
523    
524    // Add miner accounts for seeker rewards (optional)
525    for miner_pubkey in miner_accounts {
526        reset_instruction.accounts.push(AccountMeta::new(
527            miner_pda(*miner_pubkey).0,
528            false,
529        ));
530    }
531    
532    reset_instruction
533}
534    
535// let [signer_info, automation_info, board_info, miner_info, round_info, treasury_info, system_program] =
536
537pub fn checkpoint(signer: Pubkey, authority: Pubkey, round_id: u64) -> Instruction {
538    let miner_address = miner_pda(authority).0;
539    let board_address = board_pda().0;
540    let round_address = round_pda(round_id).0;
541    let treasury_address = TREASURY_ADDRESS;
542    Instruction {
543        program_id: crate::ID,
544        accounts: vec![
545            AccountMeta::new(signer, true), // payer (session payer or regular wallet, receives bot fee)
546            AccountMeta::new(authority, false), // authority (user's wallet, for PDA derivation - must be writable when combined with deploy)
547            AccountMeta::new(board_address, false),
548            AccountMeta::new(miner_address, false),
549            AccountMeta::new(round_address, false),
550            AccountMeta::new(treasury_address, false),
551            AccountMeta::new_readonly(system_program::ID, false),
552        ],
553        data: Checkpoint {}.to_bytes(),
554    }
555}
556
557pub fn set_admin(signer: Pubkey, admin: Pubkey) -> Instruction {
558    let config_address = config_pda().0;
559    Instruction {
560        program_id: crate::ID,
561        accounts: vec![
562            AccountMeta::new(signer, true),
563            AccountMeta::new(config_address, false),
564            AccountMeta::new_readonly(system_program::ID, false),
565        ],
566        data: SetAdmin {
567            admin: admin.to_bytes(),
568        }
569        .to_bytes(),
570    }
571}
572
573pub fn set_admin_fee(signer: Pubkey, admin_fee: u64) -> Instruction {
574    let config_address = config_pda().0;
575    Instruction {
576        program_id: crate::ID,
577        accounts: vec![
578            AccountMeta::new(signer, true),
579            AccountMeta::new(config_address, false),
580            AccountMeta::new_readonly(system_program::ID, false),
581        ],
582        data: SetAdminFee {
583            admin_fee: admin_fee.to_le_bytes(),
584        }
585        .to_bytes(),
586    }
587}
588
589pub fn set_fee_collector(signer: Pubkey, fee_collector: Pubkey) -> Instruction {
590    let config_address = config_pda().0;
591    Instruction {
592        program_id: crate::ID,
593        accounts: vec![
594            AccountMeta::new(signer, true),
595            AccountMeta::new(config_address, false),
596            AccountMeta::new_readonly(system_program::ID, false),
597        ],
598        data: SetFeeCollector {
599            fee_collector: fee_collector.to_bytes(),
600        }
601        .to_bytes(),
602    }
603}
604
605/// Sets the TGE (Token Generation Event) timestamp.
606/// If current time < tge_timestamp, pre-mine is active.
607/// Set to 0 to disable pre-mine.
608/// Admin-only instruction.
609pub fn set_tge_timestamp(signer: Pubkey, tge_timestamp: i64) -> Instruction {
610    let config_address = config_pda().0;
611    Instruction {
612        program_id: crate::ID,
613        accounts: vec![
614            AccountMeta::new(signer, true),
615            AccountMeta::new(config_address, false),
616            AccountMeta::new_readonly(system_program::ID, false),
617        ],
618        data: SetTgeTimestamp {
619            tge_timestamp: tge_timestamp.to_le_bytes(),
620        }
621        .to_bytes(),
622    }
623}
624
625pub fn set_auction(
626    signer: Pubkey,
627    halving_period_seconds: u64,
628    last_halving_time: u64,
629    base_mining_rates: [u64; 4],
630    auction_duration_seconds: u64,
631    starting_prices: [u64; 4],
632    _well_id: u64, // Kept for backwards compatibility, but not used (always updates auction only)
633) -> Instruction {
634    let config_address = config_pda().0;
635    let auction_address = auction_pda().0;
636    
637    Instruction {
638        program_id: crate::ID,
639        accounts: vec![
640            AccountMeta::new(signer, true),
641            AccountMeta::new_readonly(config_address, false),
642            AccountMeta::new(auction_address, false),
643        ],
644        data: SetAuction {
645            halving_period_seconds: halving_period_seconds.to_le_bytes(),
646            last_halving_time: last_halving_time.to_le_bytes(),
647            base_mining_rates: [
648                base_mining_rates[0].to_le_bytes(),
649                base_mining_rates[1].to_le_bytes(),
650                base_mining_rates[2].to_le_bytes(),
651                base_mining_rates[3].to_le_bytes(),
652            ],
653            auction_duration_seconds: auction_duration_seconds.to_le_bytes(),
654            starting_prices: [
655                starting_prices[0].to_le_bytes(),
656                starting_prices[1].to_le_bytes(),
657                starting_prices[2].to_le_bytes(),
658                starting_prices[3].to_le_bytes(),
659            ],
660            well_id: 4u64.to_le_bytes(), // Always use 4 to indicate auction-only update
661        }
662        .to_bytes(),
663    }
664}
665
666// let [signer_info, mint_info, sender_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
667
668pub fn deposit(signer: Pubkey, authority: Pubkey, amount: u64, lock_duration_days: u64, stake_id: u64) -> Instruction {
669    let mint_address = MINT_ADDRESS;
670    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
671    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
672    let sender_address = get_associated_token_address(&authority, &MINT_ADDRESS); // Authority's ATA
673    let pool_address = pool_pda().0;
674    let pool_tokens_address = pool_tokens_address();
675    let miner_address = miner_pda(authority).0; // Derive from authority
676    Instruction {
677        program_id: crate::ID,
678        accounts: vec![
679            AccountMeta::new(signer, true), // payer (session payer or regular wallet, pays fees)
680            AccountMeta::new(authority, true), // authority (user's wallet, signs token transfer and used for PDA derivation)
681            AccountMeta::new(mint_address, false),
682            AccountMeta::new(sender_address, false),
683            AccountMeta::new(stake_address, false),
684            AccountMeta::new(stake_tokens_address, false),
685            AccountMeta::new(pool_address, false),
686            AccountMeta::new(pool_tokens_address, false),
687            AccountMeta::new(miner_address, false),
688            AccountMeta::new_readonly(system_program::ID, false),
689            AccountMeta::new_readonly(spl_token::ID, false),
690            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
691        ],
692        data: Deposit {
693            amount: amount.to_le_bytes(),
694            lock_duration_days: lock_duration_days.to_le_bytes(),
695            stake_id: stake_id.to_le_bytes(),
696        }
697        .to_bytes(),
698    }
699}
700
701// let [signer_info, mint_info, recipient_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
702
703pub fn withdraw(signer: Pubkey, authority: Pubkey, amount: u64, stake_id: u64) -> Instruction {
704    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
705    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
706    let mint_address = MINT_ADDRESS;
707    let recipient_address = get_associated_token_address(&authority, &MINT_ADDRESS); // Authority's ATA
708    let pool_address = pool_pda().0;
709    let pool_tokens_address = pool_tokens_address();
710    let miner_address = miner_pda(authority).0; // Derive from authority
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, false), // authority (user's wallet, for PDA derivation)
716            AccountMeta::new(mint_address, false),
717            AccountMeta::new(recipient_address, false),
718            AccountMeta::new(stake_address, false),
719            AccountMeta::new(stake_tokens_address, false),
720            AccountMeta::new(pool_address, false),
721            AccountMeta::new(pool_tokens_address, false),
722            AccountMeta::new(miner_address, false),
723            AccountMeta::new_readonly(system_program::ID, false),
724            AccountMeta::new_readonly(spl_token::ID, false),
725            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
726        ],
727        data: Withdraw {
728            amount: amount.to_le_bytes(),
729            stake_id: stake_id.to_le_bytes(),
730        }
731        .to_bytes(),
732    }
733}
734
735// let [signer_info, automation_info, miner_info, system_program] = accounts else {
736
737/// Reload SOL from miner account to automation balance with single-tier referral system.
738/// 
739/// If the miner has a referrer, 1.0% of the claim goes to the referrer.
740/// 
741/// Account structure:
742/// - Base: signer, automation, miner, system_program
743/// - If miner has referrer (required): [miner_referrer, referral_referrer]
744pub fn reload_sol(
745    signer: Pubkey,
746    authority: Pubkey,
747    referrer_miner: Option<Pubkey>,
748    referrer_referral: Option<Pubkey>,
749) -> Instruction {
750    let automation_address = automation_pda(authority).0;
751    let miner_address = miner_pda(authority).0;
752    
753    let mut accounts = vec![
754        AccountMeta::new(signer, true),
755        AccountMeta::new(automation_address, false),
756        AccountMeta::new(miner_address, false),
757        AccountMeta::new_readonly(system_program::ID, false),
758    ];
759    
760    // Add referral accounts if provided (required when miner has referrer)
761    if let (Some(miner_ref), Some(referral_ref)) = (referrer_miner, referrer_referral) {
762        accounts.push(AccountMeta::new(miner_ref, false));
763        accounts.push(AccountMeta::new(referral_ref, false));
764    }
765    
766    Instruction {
767        program_id: crate::ID,
768        accounts,
769        data: ReloadSOL {}.to_bytes(),
770    }
771}
772
773// let [signer_info, mint_info, recipient_info, stake_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] =
774
775/// Claim SOL yield from staking. Stakers earn SOL rewards (2% of round winnings), not OIL.
776pub fn claim_yield(signer: Pubkey, authority: Pubkey, amount: u64, stake_id: u64) -> Instruction {
777    let stake_address = stake_pda_with_id(authority, stake_id).0; // Derive from authority, not signer
778    let pool_address = pool_pda().0;
779    Instruction {
780        program_id: crate::ID,
781        accounts: vec![
782            AccountMeta::new(signer, true), // payer (session payer or regular wallet)
783            AccountMeta::new(authority, true), // authority (user's wallet, receives SOL and used for PDA derivation)
784            AccountMeta::new(stake_address, false),
785            AccountMeta::new(pool_address, false),
786            AccountMeta::new_readonly(system_program::ID, false),
787        ],
788        data: ClaimYield {
789            amount: amount.to_le_bytes(),
790        }
791        .to_bytes(),
792    }
793}
794
795pub fn new_var(
796    signer: Pubkey,
797    provider: Pubkey,
798    id: u64,
799    commit: [u8; 32],
800    samples: u64,
801) -> Instruction {
802    let board_address = board_pda().0;
803    let config_address = config_pda().0;
804    let var_address = entropy_rng_api::state::var_pda(board_address, id).0;
805    Instruction {
806        program_id: crate::ID,
807        accounts: vec![
808            AccountMeta::new(signer, true),
809            AccountMeta::new(board_address, false),
810            AccountMeta::new(config_address, false),
811            AccountMeta::new(provider, false),
812            AccountMeta::new(var_address, false),
813            AccountMeta::new_readonly(system_program::ID, false),
814            AccountMeta::new_readonly(entropy_rng_api::ID, false),
815        ],
816        data: NewVar {
817            id: id.to_le_bytes(),
818            commit: commit,
819            samples: samples.to_le_bytes(),
820        }
821        .to_bytes(),
822    }
823}
824
825pub fn set_swap_program(signer: Pubkey, new_program: Pubkey) -> Instruction {
826    let config_address = config_pda().0;
827    Instruction {
828        program_id: crate::ID,
829        accounts: vec![
830            AccountMeta::new(signer, true),
831            AccountMeta::new(config_address, false),
832            AccountMeta::new_readonly(new_program, false),
833        ],
834        data: SetSwapProgram {}.to_bytes(),
835    }
836}
837
838pub fn set_var_address(signer: Pubkey, new_var_address: Pubkey) -> Instruction {
839    let board_address = board_pda().0;
840    let config_address = config_pda().0;
841    Instruction {
842        program_id: crate::ID,
843        accounts: vec![
844            AccountMeta::new(signer, true),
845            AccountMeta::new(board_address, false),
846            AccountMeta::new(config_address, false),
847            AccountMeta::new(new_var_address, false),
848        ],
849        data: SetVarAddress {}.to_bytes(),
850    }
851}
852
853/// Migrate: Extend Config struct with tge_timestamp field.
854/// This migration ensures the Config account has the new tge_timestamp field available.
855/// Must be called by the admin.
856/// Accounts: signer, config, system_program
857pub fn migrate(signer: Pubkey) -> Instruction {
858    let config_address = config_pda().0;
859    Instruction {
860        program_id: crate::ID,
861        accounts: vec![
862            AccountMeta::new(signer, true),
863            AccountMeta::new(config_address, false),
864            AccountMeta::new_readonly(system_program::ID, false),
865        ],
866        data: Migrate {}.to_bytes(),
867    }
868}
869
870/// Create a referral account to become a referrer.
871pub fn create_referral(signer: Pubkey) -> Instruction {
872    let referral_address = referral_pda(signer).0;
873    Instruction {
874        program_id: crate::ID,
875        accounts: vec![
876            AccountMeta::new(signer, true),
877            AccountMeta::new(referral_address, false),
878            AccountMeta::new_readonly(system_program::ID, false),
879        ],
880        data: CreateReferral {}.to_bytes(),
881    }
882}
883
884/// Creates a Whitelist account for a shared access code.
885/// Admin-only instruction.
886/// Accounts: signer (admin), config, whitelist, system_program
887pub fn create_whitelist(
888    signer: Pubkey,
889    code_hash: [u8; 32],
890) -> Instruction {
891    let config_address = config_pda().0;
892    let (whitelist_address, _) = Whitelist::pda(code_hash);
893    Instruction {
894        program_id: crate::ID,
895        accounts: vec![
896            AccountMeta::new(signer, true), // signer (admin)
897            AccountMeta::new_readonly(config_address, false), // config
898            AccountMeta::new(whitelist_address, false), // whitelist
899            AccountMeta::new_readonly(system_program::ID, false), // system_program
900        ],
901        data: CreateWhitelist {
902            code_hash,
903        }
904        .to_bytes(),
905    }
906}
907
908/// Claim pending referral rewards (both SOL and OIL).
909/// 
910/// Account structure (for Fogo sessions):
911/// - Base: signer (payer), authority (user's wallet), referral, referral_tokens, mint, recipient, system_program, token_program, associated_token_program
912pub fn claim_referral(signer: Pubkey, authority: Pubkey) -> Instruction {
913    let referral_address = referral_pda(authority).0;
914    let referral_oil_address = get_associated_token_address(&referral_address, &MINT_ADDRESS);
915    let recipient_oil_address = get_associated_token_address(&authority, &MINT_ADDRESS);
916    Instruction {
917        program_id: crate::ID,
918        accounts: vec![
919            AccountMeta::new(signer, true), // 0: signer (payer)
920            AccountMeta::new(authority, false), // 1: authority (user's wallet, receives SOL)
921            AccountMeta::new(referral_address, false), // 2: referral
922            AccountMeta::new(referral_oil_address, false), // 3: referral_tokens (Referral account's OIL ATA)
923            AccountMeta::new(MINT_ADDRESS, false), // 4: mint
924            AccountMeta::new(recipient_oil_address, false), // 5: recipient (Recipient's OIL ATA - authority's wallet)
925            AccountMeta::new_readonly(system_program::ID, false), // 6: system_program
926            AccountMeta::new_readonly(spl_token::ID, false), // 7: token_program
927            AccountMeta::new_readonly(spl_associated_token_account::ID, false), // 8: associated_token_program
928        ],
929        data: ClaimReferral {}.to_bytes(),
930    }
931}
932
933/// Direct solo bid on an auction well (seize ownership).
934/// The bid amount is calculated on-chain as current_price + 1 lamport.
935/// User must have enough SOL in their wallet to cover the bid.
936/// 
937/// Account structure:
938/// - Base: signer, authority, program_signer (optional), payer (optional), well, auction, treasury, treasury_tokens, mint, mint_authority, mint_program, staking_pool, fee_collector, config, token_program, system_program, oil_program
939/// - If previous owner exists (optional): [previous_owner_miner, previous_owner]
940/// - If referrer is provided (optional): [referral]
941/// - If access_code_hash is provided (optional): [whitelist]
942pub fn place_bid(
943    signer: Pubkey,
944    authority: Pubkey,
945    square_id: u64,
946    fee_collector: Pubkey,
947    previous_owner_miner: Option<Pubkey>, // Previous owner's miner PDA (if previous owner exists)
948    previous_owner: Option<Pubkey>, // Previous owner pubkey (if previous owner exists)
949    referrer: Option<Pubkey>, // Optional referrer pubkey for new miners
950    access_code_hash: Option<[u8; 32]>, // Optional access code hash for pre-mine
951) -> Instruction {
952    let well_address = well_pda(square_id).0;
953    let auction_address = auction_pda().0;
954    let treasury_address = treasury_pda().0;
955    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
956    let staking_pool_address = pool_pda().0;
957    let config_address = config_pda().0;
958    let mint_authority_address = oil_mint_api::state::authority_pda().0;
959    let bidder_miner_address = miner_pda(authority).0;
960    
961    let mut accounts = vec![
962        AccountMeta::new(signer, true), // 0: signer
963        AccountMeta::new(authority, false), // 1: authority
964    ];
965    
966    // Add program_signer and payer for Fogo sessions (these are added by the client, not here)
967    // For now, we'll add placeholders or the client will add them
968    
969    accounts.extend_from_slice(&[
970        AccountMeta::new(well_address, false), // well
971        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
972        AccountMeta::new(treasury_address, false),
973        AccountMeta::new(treasury_tokens_address, false),
974        AccountMeta::new(MINT_ADDRESS, false),
975        AccountMeta::new(mint_authority_address, false),
976        AccountMeta::new_readonly(oil_mint_api::ID, false),
977        AccountMeta::new(staking_pool_address, false),
978        AccountMeta::new(fee_collector, false),
979        AccountMeta::new_readonly(config_address, false),
980        AccountMeta::new_readonly(spl_token::ID, false),
981        AccountMeta::new_readonly(system_program::ID, false),
982        AccountMeta::new_readonly(crate::ID, false), // oil_program
983        AccountMeta::new(bidder_miner_address, false), // bidder_miner
984    ]);
985    
986    // Add previous owner accounts if provided
987    if let (Some(miner_pubkey), Some(owner_pubkey)) = (previous_owner_miner, previous_owner) {
988        accounts.push(AccountMeta::new(miner_pubkey, false)); // previous_owner_miner
989        accounts.push(AccountMeta::new(owner_pubkey, false)); // previous_owner
990    }
991    
992    // Add referral account if referrer is provided
993    if let Some(referrer_pubkey) = referrer {
994        let referral_address = referral_pda(referrer_pubkey).0;
995        accounts.push(AccountMeta::new(referral_address, false)); // referral
996    }
997    
998    // Add Whitelist account if access_code_hash is provided
999    let access_code_hash_bytes = access_code_hash.unwrap_or([0; 32]);
1000    if access_code_hash_bytes != [0; 32] {
1001        let (whitelist_address, _) = Whitelist::pda(access_code_hash_bytes);
1002        accounts.push(AccountMeta::new(whitelist_address, false)); // whitelist (optional)
1003    }
1004    
1005    // Note: Wrapped token accounts are added by the client:
1006    // - user_wrapped_sol (source)
1007    // - treasury_wrapped_sol (temp ATA for all wrapped SOL - will be closed to get native SOL)
1008    // - token_program, native_mint, ata_program
1009    // The program distributes native SOL from treasury to pool and fee_collector after closing the temp ATA.
1010    
1011    Instruction {
1012        program_id: crate::ID,
1013        accounts,
1014        data: instruction::PlaceBid {
1015            square_id: square_id.to_le_bytes(),
1016            referrer: referrer.unwrap_or(Pubkey::default()).to_bytes(),
1017            access_code_hash: access_code_hash_bytes,
1018        }
1019        .to_bytes(),
1020    }
1021}
1022
1023/// Claim auction-based OIL rewards
1024/// - OIL rewards: from current ownership and previous ownership (pre-minted)
1025/// 
1026/// Account structure:
1027/// - 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
1028/// - 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)
1029pub fn claim_auction_oil(
1030    signer: Pubkey,
1031    well_mask: u8, // Bitmask: bit 0 = well 0, bit 1 = well 1, etc.
1032    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (required for wells in mask)
1033    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for pool contributors)
1034    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for pool contributors, must include epoch_id in PDA)
1035) -> Instruction {
1036    let miner_address = miner_pda(signer).0;
1037    let auction_address = auction_pda().0;
1038    let treasury_address = treasury_pda().0;
1039    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
1040    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
1041    let mint_authority_address = oil_mint_api::state::authority_pda().0;
1042    
1043    let mut accounts = vec![
1044        AccountMeta::new(signer, true),
1045        AccountMeta::new(miner_address, false),
1046    ];
1047    
1048    // Add well accounts for wells in mask (all 4 required, but only wells in mask are used)
1049    for well_opt in well_accounts.iter() {
1050        if let Some(well_pubkey) = well_opt {
1051            accounts.push(AccountMeta::new(*well_pubkey, false));
1052        } else {
1053            // Use a placeholder account if well is not provided
1054            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1055        }
1056    }
1057    
1058    // Add auction pool accounts if provided (optional, for pool contributors)
1059    if let Some(auction_pool_pdas) = auction_pool_accounts {
1060        for auction_pool_pda_opt in auction_pool_pdas.iter() {
1061            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
1062                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
1063            } else {
1064                // Use a placeholder account if auction pool is not provided
1065                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1066            }
1067        }
1068    } else {
1069        // Add 4 placeholder accounts if auction_pool_accounts is None
1070        for _ in 0..4 {
1071            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1072        }
1073    }
1074    
1075    accounts.extend_from_slice(&[
1076        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
1077        AccountMeta::new(treasury_address, false),
1078        AccountMeta::new(treasury_tokens_address, false),
1079        AccountMeta::new(MINT_ADDRESS, false), // Must be writable for mint_oil CPI
1080        AccountMeta::new(mint_authority_address, false), // Must be writable for mint_oil CPI
1081        AccountMeta::new_readonly(oil_mint_api::ID, false),
1082        AccountMeta::new(recipient_address, false),
1083        AccountMeta::new_readonly(spl_token::ID, false),
1084        AccountMeta::new_readonly(spl_associated_token_account::ID, false),
1085        AccountMeta::new_readonly(system_program::ID, false),
1086        AccountMeta::new_readonly(crate::ID, false), // oil_program
1087    ]);
1088    
1089    // Add bid accounts if provided (for pool contributors)
1090    if let Some(bid_pdas) = bid_accounts {
1091        for bid_pda_opt in bid_pdas.iter() {
1092            if let Some(bid_pubkey) = bid_pda_opt {
1093                accounts.push(AccountMeta::new(*bid_pubkey, false));
1094            }
1095        }
1096    }
1097    
1098    Instruction {
1099        program_id: crate::ID,
1100        accounts,
1101        data: ClaimAuctionOIL {
1102            well_mask,
1103        }
1104        .to_bytes(),
1105    }
1106}
1107
1108/// Claim auction-based SOL rewards
1109/// - SOL rewards: from being outbid and refunds from abandoned pools
1110/// 
1111/// Account structure:
1112/// - 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
1113/// - Bid accounts (one per well, required for refunds): [bid_0, bid_1, bid_2, bid_3] (must include epoch_id in PDA)
1114pub fn claim_auction_sol(
1115    signer: Pubkey,
1116    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (for checking abandoned pools)
1117    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for refunds)
1118    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for refunds, must include epoch_id in PDA)
1119) -> Instruction {
1120    let miner_address = miner_pda(signer).0;
1121    let (auction_address, _) = auction_pda();
1122    let treasury_address = treasury_pda().0;
1123    
1124    let mut accounts = vec![
1125        AccountMeta::new(signer, true),
1126        AccountMeta::new(miner_address, false),
1127    ];
1128    
1129    // Add well accounts (all 4 wells for checking abandoned pools)
1130    for well_opt in well_accounts.iter() {
1131        if let Some(well_pubkey) = well_opt {
1132            accounts.push(AccountMeta::new(*well_pubkey, false));
1133        } else {
1134            // Use a placeholder account if well is not provided
1135            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1136        }
1137    }
1138    
1139    // Add auction pool accounts if provided (optional, for refunds)
1140    if let Some(auction_pool_pdas) = auction_pool_accounts {
1141        for auction_pool_pda_opt in auction_pool_pdas.iter() {
1142            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
1143                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
1144            } else {
1145                // Use a placeholder account if auction pool is not provided
1146                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1147            }
1148        }
1149    } else {
1150        // Add 4 placeholder accounts if auction_pool_accounts is None
1151        for _ in 0..4 {
1152            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
1153        }
1154    }
1155    
1156    accounts.extend_from_slice(&[
1157        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
1158        AccountMeta::new(treasury_address, false),
1159        AccountMeta::new_readonly(system_program::ID, false),
1160        AccountMeta::new_readonly(crate::ID, false), // oil_program
1161    ]);
1162    
1163    // Add bid accounts if provided (for refunds)
1164    if let Some(bid_pdas) = bid_accounts {
1165        for bid_pda_opt in bid_pdas.iter() {
1166            if let Some(bid_pubkey) = bid_pda_opt {
1167                accounts.push(AccountMeta::new(*bid_pubkey, false));
1168            }
1169        }
1170    }
1171    
1172    Instruction {
1173        program_id: crate::ID,
1174        accounts,
1175        data: ClaimAuctionSOL {
1176            _reserved: 0,
1177        }
1178        .to_bytes(),
1179    }
1180}
1181
1182pub fn claim_seeker(signer: Pubkey, mint: Pubkey) -> Instruction {
1183    let seeker_address = seeker_pda(mint).0;
1184    let token_account_address = get_associated_token_address(&signer, &mint);
1185    Instruction {
1186        program_id: crate::ID,
1187        accounts: vec![
1188            AccountMeta::new(signer, true),
1189            AccountMeta::new_readonly(mint, false),
1190            AccountMeta::new(seeker_address, false),
1191            AccountMeta::new(token_account_address, false),
1192            AccountMeta::new_readonly(system_program::ID, false),
1193        ],
1194        data: ClaimSeeker {}.to_bytes(),
1195    }
1196}