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, driller_info, system_program] = accounts else {
74
75/// Set up automation for a driller. If the driller doesn't exist yet, pass a referrer to set it.
76/// If a referrer is provided and the driller 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_driller: bool,
89) -> Instruction {
90    let automation_address = automation_pda(signer).0;
91    let driller_address = driller_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(driller_address, false),
99            AccountMeta::new_readonly(system_program::ID, false),
100    ];
101    
102    // Add referral account if referrer is provided and driller is new (for incrementing total_referred)
103    if is_new_driller && 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 driller has a referrer, 1.0% of the claim goes to the referrer.
128/// 
129/// Account structure:
130/// - Base: signer, driller, system_program
131/// - If driller has referrer (required): [driller_referrer, referral_referrer]
132pub fn claim_sol(
133    signer: Pubkey,
134    referrer_driller: Option<Pubkey>, // Referrer's driller PDA (if driller has referrer)
135    referrer_referral: Option<Pubkey>, // Referrer's referral PDA (if driller has referrer)
136) -> Instruction {
137    let driller_address = driller_pda(signer).0;
138    
139    let mut accounts = vec![
140        AccountMeta::new(signer, true),
141        AccountMeta::new(driller_address, false),
142        AccountMeta::new_readonly(system_program::ID, false),
143    ];
144    
145    // Add referrer accounts if provided (required if driller has referrer)
146    if let (Some(driller_pubkey), Some(referral_pubkey)) = (referrer_driller, referrer_referral) {
147        accounts.push(AccountMeta::new(driller_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, driller_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 driller has a referrer, 1.0% of the claim goes to the referrer.
163/// 
164/// Account structure:
165/// - Base: signer, driller, mint, recipient, treasury, treasury_tokens, system_program, token_program, associated_token_program
166/// - If driller has referrer (required): [driller_referrer, referral_referrer, referral_referrer_oil_ata]
167pub fn claim_oil(
168    signer: Pubkey,
169    referrer_driller: Option<Pubkey>, // Referrer's driller PDA (if driller has referrer)
170    referrer_referral: Option<Pubkey>, // Referrer's referral PDA (if driller has referrer)
171    referrer_referral_oil_ata: Option<Pubkey>, // Referrer's referral OIL ATA (if driller has referrer)
172) -> Instruction {
173    let driller_address = driller_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(driller_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 driller has referrer)
191    if let (Some(driller_pubkey), Some(referral_pubkey), Some(oil_ata_pubkey)) = 
192        (referrer_driller, referrer_referral, referrer_referral_oil_ata) {
193        accounts.push(AccountMeta::new(driller_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 drillers 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 driller_address = driller_pda(authority).0;
239    let round_address = round_pda(round_id).0;
240    let entropy_var_address = entropy_rng_api::state::var_pda(board_address, 0).0;
241
242    // Convert array of 25 booleans into a 32-bit mask where each bit represents whether
243    // that square index is selected (1) or not (0)
244    let mut mask: u32 = 0;
245    for (i, &square) in squares.iter().enumerate() {
246        if square {
247            mask |= 1 << i;
248        }
249    }
250    
251    // Get referrer bytes (default to zero pubkey if no referrer).
252    let referrer_pubkey = referrer.unwrap_or(Pubkey::default());
253    let referrer_bytes = referrer_pubkey.to_bytes();
254
255    let mut accounts = vec![
256        AccountMeta::new(signer, true),
257        AccountMeta::new(authority, false),
258        AccountMeta::new(automation_address, false),
259        AccountMeta::new(board_address, false),
260        AccountMeta::new(config_address, false),
261        AccountMeta::new(driller_address, false),
262        AccountMeta::new(round_address, false),
263        AccountMeta::new_readonly(system_program::ID, false),
264        AccountMeta::new_readonly(crate::ID, false),
265        // Entropy accounts.
266        AccountMeta::new(entropy_var_address, false),
267        AccountMeta::new_readonly(entropy_rng_api::ID, false),
268    ];
269    
270    // Add referral account if referrer is provided (for incrementing total_referred on new drillers).
271    if referrer_pubkey != Pubkey::default() {
272        let referral_address = referral_pda(referrer_pubkey).0;
273        accounts.push(AccountMeta::new(referral_address, false));
274    }
275
276    Instruction {
277        program_id: crate::ID,
278        accounts,
279        data: Deploy {
280            amount: amount.to_le_bytes(),
281            squares: mask.to_le_bytes(),
282            referrer: referrer_bytes,
283            pooled: if pooled { 1 } else { 0 },
284        }
285        .to_bytes(),
286    }
287}
288
289// let [pool, user_source_token, user_destination_token, a_vault, b_vault, a_token_vault, b_token_vault, a_vault_lp_mint, b_vault_lp_mint, a_vault_lp, b_vault_lp, protocol_token_fee, user_key, vault_program, token_program] =
290
291pub fn buyback(signer: Pubkey, swap_accounts: &[AccountMeta], swap_data: &[u8]) -> Instruction {
292    let board_address = board_pda().0;
293    let config_address = config_pda().0;
294    let mint_address = MINT_ADDRESS;
295    let treasury_address = TREASURY_ADDRESS;
296    let treasury_oil_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
297    let treasury_sol_address = get_associated_token_address(&treasury_address, &SOL_MINT);
298    let mut accounts = vec![
299        AccountMeta::new(signer, true),
300        AccountMeta::new(board_address, false),
301        AccountMeta::new_readonly(config_address, false),
302        AccountMeta::new(mint_address, false),
303        AccountMeta::new(treasury_address, false),
304        AccountMeta::new(treasury_oil_address, false),
305        AccountMeta::new(treasury_sol_address, false),
306        AccountMeta::new_readonly(spl_token::ID, false),
307        AccountMeta::new_readonly(crate::ID, false),
308    ];
309    for account in swap_accounts.iter() {
310        let mut acc_clone = account.clone();
311        acc_clone.is_signer = false;
312        accounts.push(acc_clone);
313    }
314    let mut data = Buyback {}.to_bytes();
315    data.extend_from_slice(swap_data);
316    Instruction {
317        program_id: crate::ID,
318        accounts,
319        data,
320    }
321}
322
323// let [signer_info, board_info, config_info, fee_collector_info, mint_info, round_info, round_next_info, top_driller_info, treasury_info, treasury_tokens_info, system_program, token_program, oil_program, slot_hashes_sysvar] =
324
325pub fn reset(
326    signer: Pubkey,
327    fee_collector: Pubkey,
328    round_id: u64,
329    top_driller: Pubkey,
330    var_address: Pubkey,
331) -> Instruction {
332    let board_address = board_pda().0;
333    let config_address = config_pda().0;
334    let mint_address = MINT_ADDRESS;
335    let round_address = round_pda(round_id).0;
336    let round_next_address = round_pda(round_id + 1).0;
337    let top_driller_address = driller_pda(top_driller).0;
338    let treasury_address = TREASURY_ADDRESS;
339    let treasury_tokens_address = treasury_tokens_address();
340    let pool_address = pool_pda().0;
341    let mint_authority_address = oil_mint_api::state::authority_pda().0;
342    Instruction {
343        program_id: crate::ID,
344        accounts: vec![
345            AccountMeta::new(signer, true),
346            AccountMeta::new(board_address, false),
347            AccountMeta::new(config_address, false),
348            AccountMeta::new(fee_collector, false),
349            AccountMeta::new(mint_address, false),
350            AccountMeta::new(round_address, false),
351            AccountMeta::new(round_next_address, false),
352            AccountMeta::new(top_driller_address, false),
353            AccountMeta::new(treasury_address, false),
354            AccountMeta::new(pool_address, false),
355            AccountMeta::new(treasury_tokens_address, false),
356            AccountMeta::new_readonly(system_program::ID, false),
357            AccountMeta::new_readonly(spl_token::ID, false),
358            AccountMeta::new_readonly(crate::ID, false),
359            AccountMeta::new_readonly(sysvar::slot_hashes::ID, false),
360            // Entropy accounts.
361            AccountMeta::new(var_address, false),
362            AccountMeta::new_readonly(entropy_rng_api::ID, false),
363            // Mint accounts.
364            AccountMeta::new(mint_authority_address, false),
365            AccountMeta::new_readonly(oil_mint_api::ID, false),
366        ],
367        data: Reset {}.to_bytes(),
368    }
369}
370    
371// let [signer_info, automation_info, board_info, driller_info, round_info, treasury_info, system_program] =
372
373pub fn checkpoint(signer: Pubkey, authority: Pubkey, round_id: u64) -> Instruction {
374    let driller_address = driller_pda(authority).0;
375    let board_address = board_pda().0;
376    let round_address = round_pda(round_id).0;
377    let treasury_address = TREASURY_ADDRESS;
378    Instruction {
379        program_id: crate::ID,
380        accounts: vec![
381            AccountMeta::new(signer, true),
382            AccountMeta::new(board_address, false),
383            AccountMeta::new(driller_address, false),
384            AccountMeta::new(round_address, false),
385            AccountMeta::new(treasury_address, false),
386            AccountMeta::new_readonly(system_program::ID, false),
387        ],
388        data: Checkpoint {}.to_bytes(),
389    }
390}
391
392pub fn set_admin(signer: Pubkey, admin: Pubkey) -> Instruction {
393    let config_address = config_pda().0;
394    Instruction {
395        program_id: crate::ID,
396        accounts: vec![
397            AccountMeta::new(signer, true),
398            AccountMeta::new(config_address, false),
399            AccountMeta::new_readonly(system_program::ID, false),
400        ],
401        data: SetAdmin {
402            admin: admin.to_bytes(),
403        }
404        .to_bytes(),
405    }
406}
407
408pub fn set_admin_fee(signer: Pubkey, admin_fee: u64) -> Instruction {
409    let config_address = config_pda().0;
410    Instruction {
411        program_id: crate::ID,
412        accounts: vec![
413            AccountMeta::new(signer, true),
414            AccountMeta::new(config_address, false),
415            AccountMeta::new_readonly(system_program::ID, false),
416        ],
417        data: SetAdminFee {
418            admin_fee: admin_fee.to_le_bytes(),
419        }
420        .to_bytes(),
421    }
422}
423
424pub fn set_fee_collector(signer: Pubkey, fee_collector: Pubkey) -> Instruction {
425    let config_address = config_pda().0;
426    Instruction {
427        program_id: crate::ID,
428        accounts: vec![
429            AccountMeta::new(signer, true),
430            AccountMeta::new(config_address, false),
431            AccountMeta::new_readonly(system_program::ID, false),
432        ],
433        data: SetFeeCollector {
434            fee_collector: fee_collector.to_bytes(),
435        }
436        .to_bytes(),
437    }
438}
439
440pub fn set_auction(
441    signer: Pubkey,
442    halving_period_seconds: u64,
443    last_halving_time: u64,
444    base_mining_rates: [u64; 4],
445    auction_duration_seconds: u64,
446    starting_prices: [u64; 4],
447    min_pool_contribution: u64,
448    _well_id: u64, // Kept for backwards compatibility, but not used (always updates auction only)
449) -> Instruction {
450    let config_address = config_pda().0;
451    let auction_address = auction_pda().0;
452    
453    Instruction {
454        program_id: crate::ID,
455        accounts: vec![
456            AccountMeta::new(signer, true),
457            AccountMeta::new_readonly(config_address, false),
458            AccountMeta::new(auction_address, false),
459        ],
460        data: SetAuction {
461            halving_period_seconds: halving_period_seconds.to_le_bytes(),
462            last_halving_time: last_halving_time.to_le_bytes(),
463            base_mining_rates: [
464                base_mining_rates[0].to_le_bytes(),
465                base_mining_rates[1].to_le_bytes(),
466                base_mining_rates[2].to_le_bytes(),
467                base_mining_rates[3].to_le_bytes(),
468            ],
469            auction_duration_seconds: auction_duration_seconds.to_le_bytes(),
470            starting_prices: [
471                starting_prices[0].to_le_bytes(),
472                starting_prices[1].to_le_bytes(),
473                starting_prices[2].to_le_bytes(),
474                starting_prices[3].to_le_bytes(),
475            ],
476            min_pool_contribution: min_pool_contribution.to_le_bytes(),
477            well_id: 4u64.to_le_bytes(), // Always use 4 to indicate auction-only update
478        }
479        .to_bytes(),
480    }
481}
482
483// let [signer_info, mint_info, sender_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
484
485pub fn deposit(signer: Pubkey, payer: Pubkey, amount: u64, lock_duration_days: u64, stake_id: u64) -> Instruction {
486    let mint_address = MINT_ADDRESS;
487    let stake_address = stake_pda_with_id(signer, stake_id).0;
488    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
489    let sender_address = get_associated_token_address(&signer, &MINT_ADDRESS);
490    let pool_address = pool_pda().0;
491    let pool_tokens_address = pool_tokens_address();
492    Instruction {
493        program_id: crate::ID,
494        accounts: vec![
495            AccountMeta::new(signer, true),
496            AccountMeta::new(payer, true),
497            AccountMeta::new(mint_address, false),
498            AccountMeta::new(sender_address, false),
499            AccountMeta::new(stake_address, false),
500            AccountMeta::new(stake_tokens_address, false),
501            AccountMeta::new(pool_address, false),
502            AccountMeta::new(pool_tokens_address, false),
503            AccountMeta::new_readonly(system_program::ID, false),
504            AccountMeta::new_readonly(spl_token::ID, false),
505            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
506        ],
507        data: Deposit {
508            amount: amount.to_le_bytes(),
509            lock_duration_days: lock_duration_days.to_le_bytes(),
510            stake_id: stake_id.to_le_bytes(),
511        }
512        .to_bytes(),
513    }
514}
515
516// let [signer_info, mint_info, recipient_info, stake_info, stake_tokens_info, treasury_info, system_program, token_program, associated_token_program] =
517
518pub fn withdraw(signer: Pubkey, amount: u64, stake_id: u64) -> Instruction {
519    let stake_address = stake_pda_with_id(signer, stake_id).0;
520    let stake_tokens_address = get_associated_token_address(&stake_address, &MINT_ADDRESS);
521    let mint_address = MINT_ADDRESS;
522    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
523    let pool_address = pool_pda().0;
524    let pool_tokens_address = pool_tokens_address();
525    Instruction {
526        program_id: crate::ID,
527        accounts: vec![
528            AccountMeta::new(signer, true),
529            AccountMeta::new(mint_address, false),
530            AccountMeta::new(recipient_address, false),
531            AccountMeta::new(stake_address, false),
532            AccountMeta::new(stake_tokens_address, false),
533            AccountMeta::new(pool_address, false),
534            AccountMeta::new(pool_tokens_address, false),
535            AccountMeta::new_readonly(system_program::ID, false),
536            AccountMeta::new_readonly(spl_token::ID, false),
537            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
538        ],
539        data: Withdraw {
540            amount: amount.to_le_bytes(),
541            stake_id: stake_id.to_le_bytes(),
542        }
543        .to_bytes(),
544    }
545}
546
547// let [signer_info, automation_info, driller_info, system_program] = accounts else {
548
549/// Reload SOL from driller account to automation balance with single-tier referral system.
550/// 
551/// If the driller has a referrer, 1.0% of the claim goes to the referrer.
552/// 
553/// Account structure:
554/// - Base: signer, automation, driller, system_program
555/// - If driller has referrer (required): [driller_referrer, referral_referrer]
556pub fn reload_sol(
557    signer: Pubkey,
558    authority: Pubkey,
559    referrer_driller: Option<Pubkey>,
560    referrer_referral: Option<Pubkey>,
561) -> Instruction {
562    let automation_address = automation_pda(authority).0;
563    let driller_address = driller_pda(authority).0;
564    
565    let mut accounts = vec![
566        AccountMeta::new(signer, true),
567        AccountMeta::new(automation_address, false),
568        AccountMeta::new(driller_address, false),
569        AccountMeta::new_readonly(system_program::ID, false),
570    ];
571    
572    // Add referral accounts if provided (required when driller has referrer)
573    if let (Some(driller_ref), Some(referral_ref)) = (referrer_driller, referrer_referral) {
574        accounts.push(AccountMeta::new(driller_ref, false));
575        accounts.push(AccountMeta::new(referral_ref, false));
576    }
577    
578    Instruction {
579        program_id: crate::ID,
580        accounts,
581        data: ReloadSOL {}.to_bytes(),
582    }
583}
584
585// let [signer_info, mint_info, recipient_info, stake_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] =
586
587/// Claim SOL yield from staking. Stakers earn SOL rewards (2% of round winnings), not OIL.
588pub fn claim_yield(signer: Pubkey, amount: u64, stake_id: u64) -> Instruction {
589    let stake_address = stake_pda_with_id(signer, stake_id).0;
590    let recipient_address = signer; // SOL recipient (wallet, not token account)
591    let pool_address = pool_pda().0;
592    Instruction {
593        program_id: crate::ID,
594        accounts: vec![
595            AccountMeta::new(signer, true),
596            AccountMeta::new(recipient_address, false), // SOL recipient wallet
597            AccountMeta::new(stake_address, false),
598            AccountMeta::new(pool_address, false),
599            AccountMeta::new_readonly(system_program::ID, false),
600        ],
601        data: ClaimYield {
602            amount: amount.to_le_bytes(),
603        }
604        .to_bytes(),
605    }
606}
607
608pub fn new_var(
609    signer: Pubkey,
610    provider: Pubkey,
611    id: u64,
612    commit: [u8; 32],
613    samples: u64,
614) -> Instruction {
615    let board_address = board_pda().0;
616    let config_address = config_pda().0;
617    let var_address = entropy_rng_api::state::var_pda(board_address, id).0;
618    Instruction {
619        program_id: crate::ID,
620        accounts: vec![
621            AccountMeta::new(signer, true),
622            AccountMeta::new(board_address, false),
623            AccountMeta::new(config_address, false),
624            AccountMeta::new(provider, false),
625            AccountMeta::new(var_address, false),
626            AccountMeta::new_readonly(system_program::ID, false),
627            AccountMeta::new_readonly(entropy_rng_api::ID, false),
628        ],
629        data: NewVar {
630            id: id.to_le_bytes(),
631            commit: commit,
632            samples: samples.to_le_bytes(),
633        }
634        .to_bytes(),
635    }
636}
637
638pub fn set_swap_program(signer: Pubkey, new_program: Pubkey) -> Instruction {
639    let config_address = config_pda().0;
640    Instruction {
641        program_id: crate::ID,
642        accounts: vec![
643            AccountMeta::new(signer, true),
644            AccountMeta::new(config_address, false),
645            AccountMeta::new_readonly(new_program, false),
646        ],
647        data: SetSwapProgram {}.to_bytes(),
648    }
649}
650
651pub fn set_var_address(signer: Pubkey, new_var_address: Pubkey) -> Instruction {
652    let board_address = board_pda().0;
653    let config_address = config_pda().0;
654    Instruction {
655        program_id: crate::ID,
656        accounts: vec![
657            AccountMeta::new(signer, true),
658            AccountMeta::new(board_address, false),
659            AccountMeta::new(config_address, false),
660            AccountMeta::new(new_var_address, false),
661        ],
662        data: SetVarAddress {}.to_bytes(),
663    }
664}
665
666/// Migrate a Round account to add pool fields.
667/// Anyone can call this (signer pays for rent increase).
668pub fn migrate(signer: Pubkey, well_id: u64) -> Instruction {
669    let well_address = well_pda(well_id).0;
670    Instruction {
671        program_id: crate::ID,
672        accounts: vec![
673            AccountMeta::new(signer, true),
674            AccountMeta::new(well_address, false),
675            AccountMeta::new_readonly(system_program::ID, false),
676        ],
677        data: Migrate {}.to_bytes(),
678    }
679}
680
681/// Migrate a Treasury account (no-op, kept for backwards compatibility).
682/// Stakers earn SOL rewards (2% of round winnings), not OIL.
683pub fn migrate_treasury(signer: Pubkey) -> Instruction {
684    let treasury_address = treasury_pda().0;
685    Instruction {
686        program_id: crate::ID,
687        accounts: vec![
688            AccountMeta::new(signer, true),
689            AccountMeta::new(treasury_address, false),
690            AccountMeta::new_readonly(system_program::ID, false),
691        ],
692        data: Migrate {}.to_bytes(),
693    }
694}
695
696/// Create a referral account to become a referrer.
697pub fn create_referral(signer: Pubkey) -> Instruction {
698    let referral_address = referral_pda(signer).0;
699    Instruction {
700        program_id: crate::ID,
701        accounts: vec![
702            AccountMeta::new(signer, true),
703            AccountMeta::new(referral_address, false),
704            AccountMeta::new_readonly(system_program::ID, false),
705        ],
706        data: CreateReferral {}.to_bytes(),
707    }
708}
709
710/// Claim pending referral rewards (both SOL and OIL).
711
712pub fn claim_referral(signer: Pubkey) -> Instruction {
713    let referral_address = referral_pda(signer).0;
714    let referral_oil_address = get_associated_token_address(&referral_address, &MINT_ADDRESS);
715    let recipient_oil_address = get_associated_token_address(&signer, &MINT_ADDRESS);
716    Instruction {
717        program_id: crate::ID,
718        accounts: vec![
719            AccountMeta::new(signer, true),
720            AccountMeta::new(referral_address, false),
721            AccountMeta::new(referral_oil_address, false), // Referral account's OIL ATA
722            AccountMeta::new(MINT_ADDRESS, false),
723            AccountMeta::new(recipient_oil_address, false), // Recipient's OIL ATA
724            AccountMeta::new_readonly(system_program::ID, false),
725            AccountMeta::new_readonly(spl_token::ID, false),
726            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
727        ],
728        data: ClaimReferral {}.to_bytes(),
729    }
730}
731
732/// Direct solo bid on an auction well (seize ownership).
733/// The bid amount is calculated on-chain as current_price + 1 lamport.
734/// User must have enough SOL in their wallet to cover the bid.
735/// 
736/// Account structure:
737/// - Base: signer, square, fund (optional), auction, treasury, treasury_tokens, mint, mint_authority, mint_program, staking_pool, fee_collector, config, token_program, system_program
738/// - If previous owner exists (optional): [previous_owner_driller, previous_owner]
739pub fn set_bid(
740    signer: Pubkey,
741    square_id: u64,
742    fee_collector: Pubkey,
743    pool_account: Option<Pubkey>, // Pool account for current epoch (optional, if pool exists)
744    previous_owner_driller: Option<Pubkey>, // Previous owner's driller PDA (if previous owner exists)
745    previous_owner: Option<Pubkey>, // Previous owner pubkey (if previous owner exists)
746) -> Instruction {
747    let well_address = well_pda(square_id).0;
748    let auction_address = auction_pda().0;
749    let treasury_address = treasury_pda().0;
750    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
751    let staking_pool_address = pool_pda().0;
752    let config_address = config_pda().0;
753    let mint_authority_address = oil_mint_api::state::authority_pda().0;
754    
755    let mut accounts = vec![
756        AccountMeta::new(signer, true),
757        AccountMeta::new(well_address, false),
758    ];
759    
760    // Add pool account if provided (for current epoch)
761    if let Some(pool_pubkey) = pool_account {
762        accounts.push(AccountMeta::new(pool_pubkey, false));
763    } else {
764        // Add placeholder (account order must be consistent)
765        accounts.push(AccountMeta::new_readonly(system_program::ID, false)); // Placeholder, will be ignored
766    }
767    
768    accounts.extend_from_slice(&[
769        AccountMeta::new(auction_address, false),
770        AccountMeta::new(treasury_address, false),
771        AccountMeta::new(treasury_tokens_address, false),
772        AccountMeta::new(MINT_ADDRESS, false),
773        AccountMeta::new(mint_authority_address, false),
774        AccountMeta::new_readonly(oil_mint_api::ID, false),
775        AccountMeta::new(staking_pool_address, false),
776        AccountMeta::new(fee_collector, false),
777        AccountMeta::new(config_address, false),
778        AccountMeta::new_readonly(spl_token::ID, false),
779        AccountMeta::new_readonly(system_program::ID, false),
780        AccountMeta::new_readonly(crate::ID, false), // oil_program
781    ]);
782    
783    // Add previous owner accounts if provided
784    if let (Some(driller_pubkey), Some(owner_pubkey)) = (previous_owner_driller, previous_owner) {
785        accounts.push(AccountMeta::new(driller_pubkey, false));
786        accounts.push(AccountMeta::new(owner_pubkey, false));
787    }
788    
789    Instruction {
790        program_id: crate::ID,
791        accounts,
792        data: instruction::SetBid {
793            square_id: square_id.to_le_bytes(),
794        }
795        .to_bytes(),
796    }
797}
798
799/// Contribute SOL to an auction pool for a specific well.
800/// 
801/// Account structure:
802/// - Base: signer, bid, square, fund (may need to create), auction, system_program
803/// - If pool wins (optional): [treasury, treasury_tokens, mint, mint_authority, mint_program, staking_pool, fee_collector, config, token_program, previous_owner_driller, previous_owner]
804pub fn join_auction_pool(
805    signer: Pubkey,
806    square_id: u64,
807    epoch_id: u64, // Current epoch ID (from Well account)
808    amount: u64, // SOL amount in lamports
809    auction_pool_account: Pubkey, // Auction pool account for current epoch (may need to create)
810    fee_collector: Option<Pubkey>, // Required if pool wins
811    previous_owner_driller: Option<Pubkey>, // Required if pool wins and previous owner exists
812    previous_owner: Option<Pubkey>, // Required if pool wins and previous owner exists
813) -> Instruction {
814    let driller_address = driller_pda(signer).0;
815    let bid_address = bid_pda(signer, square_id, epoch_id).0;
816    let well_address = well_pda(square_id).0;
817    let auction_address = auction_pda().0;
818    
819    let mut accounts = vec![
820        AccountMeta::new(signer, true),
821        AccountMeta::new(driller_address, false), // For tracking pool contributions
822        AccountMeta::new(bid_address, false),
823        AccountMeta::new(well_address, false),
824        AccountMeta::new(auction_pool_account, false), // May need to create
825        AccountMeta::new(auction_address, false),
826        AccountMeta::new_readonly(system_program::ID, false),
827        AccountMeta::new_readonly(crate::ID, false), // oil_program
828    ];
829    
830    // Add accounts needed if pool wins
831    if let Some(fee_collector_pubkey) = fee_collector {
832        let treasury_address = treasury_pda().0;
833        let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
834        let staking_pool_address = pool_pda().0;
835        let config_address = config_pda().0;
836        let mint_authority_address = oil_mint_api::state::authority_pda().0;
837        
838        accounts.push(AccountMeta::new(treasury_address, false));
839        accounts.push(AccountMeta::new(treasury_tokens_address, false));
840        accounts.push(AccountMeta::new(MINT_ADDRESS, false));
841        accounts.push(AccountMeta::new(mint_authority_address, false));
842        accounts.push(AccountMeta::new_readonly(oil_mint_api::ID, false));
843        accounts.push(AccountMeta::new(staking_pool_address, false));
844        accounts.push(AccountMeta::new(fee_collector_pubkey, false));
845        accounts.push(AccountMeta::new(config_address, false));
846        accounts.push(AccountMeta::new_readonly(spl_token::ID, false));
847        
848        // Add previous owner accounts if provided
849        if let (Some(driller_pubkey), Some(owner_pubkey)) = (previous_owner_driller, previous_owner) {
850            accounts.push(AccountMeta::new(driller_pubkey, false));
851            accounts.push(AccountMeta::new(owner_pubkey, false));
852        }
853    }
854    
855    Instruction {
856        program_id: crate::ID,
857        accounts,
858        data: JoinAuctionPool {
859            square_id: square_id.to_le_bytes(),
860            amount: amount.to_le_bytes(),
861        }
862        .to_bytes(),
863    }
864}
865
866/// Claim auction-based OIL rewards
867/// - OIL rewards: from current ownership and previous ownership (pre-minted)
868/// 
869/// Account structure:
870/// - Base: signer, driller, 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
871/// - 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)
872pub fn claim_auction_oil(
873    signer: Pubkey,
874    well_mask: u8, // Bitmask: bit 0 = well 0, bit 1 = well 1, etc.
875    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (required for wells in mask)
876    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for pool contributors)
877    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for pool contributors, must include epoch_id in PDA)
878) -> Instruction {
879    let driller_address = driller_pda(signer).0;
880    let auction_address = auction_pda().0;
881    let treasury_address = treasury_pda().0;
882    let treasury_tokens_address = get_associated_token_address(&treasury_address, &MINT_ADDRESS);
883    let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS);
884    let mint_authority_address = oil_mint_api::state::authority_pda().0;
885    
886    let mut accounts = vec![
887        AccountMeta::new(signer, true),
888        AccountMeta::new(driller_address, false),
889    ];
890    
891    // Add well accounts for wells in mask (all 4 required, but only wells in mask are used)
892    for well_opt in well_accounts.iter() {
893        if let Some(well_pubkey) = well_opt {
894            accounts.push(AccountMeta::new(*well_pubkey, false));
895        } else {
896            // Use a placeholder account if well is not provided
897            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
898        }
899    }
900    
901    // Add auction pool accounts if provided (optional, for pool contributors)
902    if let Some(auction_pool_pdas) = auction_pool_accounts {
903        for auction_pool_pda_opt in auction_pool_pdas.iter() {
904            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
905                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
906            } else {
907                // Use a placeholder account if auction pool is not provided
908                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
909            }
910        }
911    } else {
912        // Add 4 placeholder accounts if auction_pool_accounts is None
913        for _ in 0..4 {
914            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
915        }
916    }
917    
918    accounts.extend_from_slice(&[
919        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
920        AccountMeta::new(treasury_address, false),
921        AccountMeta::new(treasury_tokens_address, false),
922        AccountMeta::new(MINT_ADDRESS, false), // Must be writable for mint_oil CPI
923        AccountMeta::new(mint_authority_address, false), // Must be writable for mint_oil CPI
924        AccountMeta::new_readonly(oil_mint_api::ID, false),
925        AccountMeta::new(recipient_address, false),
926        AccountMeta::new_readonly(spl_token::ID, false),
927        AccountMeta::new_readonly(spl_associated_token_account::ID, false),
928        AccountMeta::new_readonly(system_program::ID, false),
929        AccountMeta::new_readonly(crate::ID, false), // oil_program
930    ]);
931    
932    // Add bid accounts if provided (for pool contributors)
933    if let Some(bid_pdas) = bid_accounts {
934        for bid_pda_opt in bid_pdas.iter() {
935            if let Some(bid_pubkey) = bid_pda_opt {
936                accounts.push(AccountMeta::new(*bid_pubkey, false));
937            }
938        }
939    }
940    
941    Instruction {
942        program_id: crate::ID,
943        accounts,
944        data: ClaimAuctionOIL {
945            well_mask,
946        }
947        .to_bytes(),
948    }
949}
950
951/// Claim auction-based SOL rewards
952/// - SOL rewards: from being outbid and refunds from abandoned pools
953/// 
954/// Account structure:
955/// - Base: signer, driller, well accounts (one per well 0-3, for checking abandoned pools), auction pool accounts (optional, one per well), auction, treasury, system_program, oil_program
956/// - Bid accounts (one per well, required for refunds): [bid_0, bid_1, bid_2, bid_3] (must include epoch_id in PDA)
957pub fn claim_auction_sol(
958    signer: Pubkey,
959    well_accounts: [Option<Pubkey>; 4], // Well PDAs for wells 0-3 (for checking abandoned pools)
960    auction_pool_accounts: Option<[Option<Pubkey>; 4]>, // Auction Pool PDAs for wells 0-3 (optional, for refunds)
961    bid_accounts: Option<[Option<Pubkey>; 4]>, // Bid PDAs for wells 0-3 (required for refunds, must include epoch_id in PDA)
962) -> Instruction {
963    let driller_address = driller_pda(signer).0;
964    let (auction_address, _) = auction_pda();
965    let treasury_address = treasury_pda().0;
966    
967    let mut accounts = vec![
968        AccountMeta::new(signer, true),
969        AccountMeta::new(driller_address, false),
970    ];
971    
972    // Add well accounts (all 4 wells for checking abandoned pools)
973    for well_opt in well_accounts.iter() {
974        if let Some(well_pubkey) = well_opt {
975            accounts.push(AccountMeta::new(*well_pubkey, false));
976        } else {
977            // Use a placeholder account if well is not provided
978            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
979        }
980    }
981    
982    // Add auction pool accounts if provided (optional, for refunds)
983    if let Some(auction_pool_pdas) = auction_pool_accounts {
984        for auction_pool_pda_opt in auction_pool_pdas.iter() {
985            if let Some(auction_pool_pubkey) = auction_pool_pda_opt {
986                accounts.push(AccountMeta::new(*auction_pool_pubkey, false));
987            } else {
988                // Use a placeholder account if auction pool is not provided
989                accounts.push(AccountMeta::new_readonly(system_program::ID, false));
990            }
991        }
992    } else {
993        // Add 4 placeholder accounts if auction_pool_accounts is None
994        for _ in 0..4 {
995            accounts.push(AccountMeta::new_readonly(system_program::ID, false));
996        }
997    }
998    
999    accounts.extend_from_slice(&[
1000        AccountMeta::new(auction_address, false), // Must be writable for auction_program_log CPI
1001        AccountMeta::new(treasury_address, false),
1002        AccountMeta::new_readonly(system_program::ID, false),
1003        AccountMeta::new_readonly(crate::ID, false), // oil_program
1004    ]);
1005    
1006    // Add bid accounts if provided (for refunds)
1007    if let Some(bid_pdas) = bid_accounts {
1008        for bid_pda_opt in bid_pdas.iter() {
1009            if let Some(bid_pubkey) = bid_pda_opt {
1010                accounts.push(AccountMeta::new(*bid_pubkey, false));
1011            }
1012        }
1013    }
1014    
1015    Instruction {
1016        program_id: crate::ID,
1017        accounts,
1018        data: ClaimAuctionSOL {
1019            _reserved: 0,
1020        }
1021        .to_bytes(),
1022    }
1023}