Skip to main content

oil_api/
utils.rs

1use solana_program::program_error::ProgramError;
2use solana_program::program::invoke;
3use solana_program::sysvar::clock::Clock;
4use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
5use steel::*;
6use crate::state::Config;
7
8/// Macro to extract accounts from an iterator with concise syntax.
9/// 
10/// Usage: `extract_accounts!(accounts_iter, [s, a, ps, pay, ...])`
11/// 
12/// This reduces repetitive `accounts_iter.next().unwrap()` calls.
13#[macro_export]
14macro_rules! extract_accounts {
15    ($iter:expr, [$($var:ident),*]) => {
16        $(
17            let $var = $iter.next().unwrap();
18        )*
19    };
20}
21
22/// Creates a wrapped SOL ATA if it doesn't exist, otherwise validates it.
23pub fn create_or_validate_wrapped_sol_ata<'a>(
24    ata_info: &AccountInfo<'a>,
25    owner_info: &AccountInfo<'a>,
26    mint_info: &AccountInfo<'a>,
27    payer_info: &AccountInfo<'a>,
28    system_program: &AccountInfo<'a>,
29    token_program: &AccountInfo<'a>,
30    ata_program: &AccountInfo<'a>,
31    _log_message: Option<&str>,
32) -> Result<(), ProgramError> {
33    ata_info.is_writable()?;
34    
35    // CRITICAL: Check if account is already owned by token program FIRST
36    // If it is, it was created by a previous instruction (e.g., session wrap)
37    // and we must NOT try to create it again (this causes "Transfer: from must not carry data" error)
38    let is_empty = ata_info.data_is_empty();
39    // Only check ownership if account is not empty (has_owner fails on empty accounts)
40    let is_owned_by_token_program = !is_empty && ata_info.has_owner(token_program.key).is_ok();
41    
42    if is_owned_by_token_program {
43        // Account is owned by token program - it exists, just validate it
44        // Don't try to create it, even if validation initially fails
45        ata_info.as_associated_token_account(owner_info.key, mint_info.key).map(|_| ())
46    } else if is_empty {
47        // Account doesn't exist - create it using idempotent instruction
48        let create_ix = create_associated_token_account_idempotent(
49            payer_info.key,
50            owner_info.key,
51            mint_info.key,
52            token_program.key,
53        );
54        
55        invoke(
56            &create_ix,
57            &[
58                payer_info.clone(),
59                ata_info.clone(),
60                owner_info.clone(),
61                mint_info.clone(),
62                system_program.clone(),
63                token_program.clone(),
64                ata_program.clone(),
65            ],
66        )?;
67        
68        // After creation, validate to ensure it's correct
69        ata_info.as_associated_token_account(owner_info.key, mint_info.key)?;
70        Ok(())
71    } else {
72        // Account has data but is not owned by token program
73        // Try to validate - if it fails, it's an error
74        ata_info.as_associated_token_account(owner_info.key, mint_info.key).map(|_| ())
75    }
76}
77
78/// Checks if pre-mine phase is currently active.
79/// 
80/// Pre-mine is active when:
81/// - `config.tge_timestamp > 0` (TGE timestamp is set)
82/// - `clock.unix_timestamp < config.tge_timestamp` (current time is before TGE)
83/// 
84/// Returns `true` if pre-mine is active, `false` otherwise.
85pub fn is_premine_active(config: &Config, clock: &Clock) -> bool {
86    config.tge_timestamp > 0 && clock.unix_timestamp < config.tge_timestamp
87}