use crate::{
state::{compile_instruction, Schedule, SerializableInstruction, Signal, Trigger},
utils::next_timestamp,
*,
};
use anchor_lang::{
prelude::*,
solana_program::instruction::Instruction,
system_program::{create_nonce_account, transfer, CreateNonceAccount, Transfer},
InstructionData, ToAccountMetas,
};
use solana_nonce::state::State;
#[derive(Accounts)]
#[instruction(amount: u64, id: ThreadId, trigger: Trigger, initial_instruction: Option<SerializableInstruction>, priority_fee: Option<u64>)]
pub struct ThreadCreate<'info> {
#[account()]
pub authority: Signer<'info>,
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
seeds = [
SEED_THREAD,
authority.key().as_ref(),
id.as_ref(),
],
bump,
payer = payer,
space = 8 + Thread::INIT_SPACE
)]
pub thread: Account<'info, Thread>,
#[account(mut)]
pub nonce_account: Option<Signer<'info>>,
pub recent_blockhashes: Option<UncheckedAccount<'info>>,
pub rent: Option<UncheckedAccount<'info>>,
pub system_program: Program<'info, System>,
}
pub fn thread_create(
ctx: Context<ThreadCreate>,
amount: u64,
id: ThreadId,
trigger: Trigger,
initial_instruction: Option<SerializableInstruction>,
priority_fee: Option<u64>,
) -> Result<()> {
let authority: &Signer = &ctx.accounts.authority;
let payer: &Signer = &ctx.accounts.payer;
let thread: &mut Account<Thread> = &mut ctx.accounts.thread;
let create_durable_thread = ctx.accounts.nonce_account.is_some();
if create_durable_thread {
let nonce_account = ctx.accounts.nonce_account.as_ref().unwrap();
let recent_blockhashes = ctx.accounts.recent_blockhashes.as_ref().ok_or(error!(
crate::errors::AntegenThreadError::InvalidNonceAccount
))?;
let rent_program = ctx.accounts.rent.as_ref().ok_or(error!(
crate::errors::AntegenThreadError::InvalidNonceAccount
))?;
let rent: Rent = Rent::get()?;
let nonce_account_size: usize = State::size();
let nonce_lamports: u64 = rent.minimum_balance(nonce_account_size);
create_nonce_account(
CpiContext::new(
anchor_lang::system_program::ID,
CreateNonceAccount {
from: payer.to_account_info(),
nonce: nonce_account.to_account_info(),
recent_blockhashes: recent_blockhashes.to_account_info(),
rent: rent_program.to_account_info(),
},
),
nonce_lamports,
&thread.key(),
)?;
thread.nonce_account = nonce_account.key();
} else {
thread.nonce_account = crate::ID;
}
let clock = Clock::get().unwrap();
let current_timestamp = clock.unix_timestamp;
thread.version = CURRENT_THREAD_VERSION;
thread.authority = authority.key();
thread.bump = ctx.bumps.thread;
thread.created_at = current_timestamp;
thread.name = id.to_name();
thread.id = id.into();
thread.paused = false;
thread.trigger = trigger.clone();
let thread_pubkey = thread.key();
thread.schedule = match &trigger {
Trigger::Account { .. } => Schedule::OnChange { prev: 0 },
Trigger::Cron {
schedule, jitter, ..
} => {
let base_next =
next_timestamp(current_timestamp, schedule.clone()).unwrap_or(current_timestamp);
let jitter_offset =
crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
let next = base_next.saturating_add(jitter_offset);
Schedule::Timed {
prev: current_timestamp, next,
}
}
Trigger::Immediate { .. } => Schedule::Timed {
prev: current_timestamp, next: current_timestamp,
},
Trigger::Slot { slot } => Schedule::Block {
prev: clock.slot, next: *slot,
},
Trigger::Epoch { epoch } => Schedule::Block {
prev: clock.epoch, next: *epoch,
},
Trigger::Interval {
seconds, jitter, ..
} => {
let base_next = current_timestamp.saturating_add(*seconds);
let jitter_offset =
crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
let next = base_next.saturating_add(jitter_offset);
Schedule::Timed {
prev: current_timestamp, next,
}
}
Trigger::Timestamp { unix_ts, .. } => Schedule::Timed {
prev: current_timestamp, next: *unix_ts,
},
};
let signer_seeds = vec![vec![
SEED_THREAD.to_vec(),
thread.authority.to_bytes().to_vec(),
thread.id.clone(),
]];
if let Some(instruction) = initial_instruction {
let instruction: Instruction = instruction.into();
let compiled = compile_instruction(instruction, signer_seeds.clone())?;
let compiled_bytes = borsh::to_vec(&compiled)?;
thread.default_fiber = Some(compiled_bytes);
thread.default_fiber_priority_fee = priority_fee.unwrap_or(0);
thread.fiber_next_id = 1; thread.fiber_ids = vec![0]; } else {
thread.default_fiber = None;
thread.default_fiber_priority_fee = 0;
thread.fiber_next_id = 0; thread.fiber_ids = Vec::new();
}
thread.fiber_cursor = 0;
thread.exec_count = 0; thread.last_executor = Pubkey::default();
thread.fiber_signal = Signal::None;
let delete_ix = Instruction {
program_id: crate::ID,
accounts: crate::accounts::ThreadDelete {
authority: thread_pubkey, close_to: thread.authority, thread: thread_pubkey,
}
.to_account_metas(None),
data: crate::instruction::DeleteThread {}.data(),
};
let compiled = compile_instruction(delete_ix, signer_seeds)?;
thread.close_fiber = borsh::to_vec(&compiled)?;
transfer(
CpiContext::new(
anchor_lang::system_program::ID,
Transfer {
from: payer.to_account_info(),
to: thread.to_account_info(),
},
),
amount,
)?;
Ok(())
}