antegen_thread_program/instructions/
thread_create.rs1use crate::{
2 state::{compile_instruction, Schedule, SerializableInstruction, Signal, Trigger},
3 utils::next_timestamp,
4 *,
5};
6use anchor_lang::{
7 prelude::*,
8 solana_program::instruction::Instruction,
9 system_program::{create_nonce_account, transfer, CreateNonceAccount, Transfer},
10 InstructionData, ToAccountMetas,
11};
12use solana_nonce::state::State;
13
14#[derive(Accounts)]
19#[instruction(amount: u64, id: ThreadId, trigger: Trigger, initial_instruction: Option<SerializableInstruction>, priority_fee: Option<u64>)]
20pub struct ThreadCreate<'info> {
21 #[account()]
23 pub authority: Signer<'info>,
24
25 #[account(mut)]
27 pub payer: Signer<'info>,
28
29 #[account(
31 init,
32 seeds = [
33 SEED_THREAD,
34 authority.key().as_ref(),
35 id.as_ref(),
36 ],
37 bump,
38 payer = payer,
39 space = 8 + Thread::INIT_SPACE
40 )]
41 pub thread: Account<'info, Thread>,
42
43 #[account(mut)]
46 pub nonce_account: Option<Signer<'info>>,
47
48 pub recent_blockhashes: Option<UncheckedAccount<'info>>,
50
51 pub rent: Option<UncheckedAccount<'info>>,
53
54 pub system_program: Program<'info, System>,
55}
56
57pub fn thread_create(
58 ctx: Context<ThreadCreate>,
59 amount: u64,
60 id: ThreadId,
61 trigger: Trigger,
62 initial_instruction: Option<SerializableInstruction>,
63 priority_fee: Option<u64>,
64) -> Result<()> {
65 let authority: &Signer = &ctx.accounts.authority;
66 let payer: &Signer = &ctx.accounts.payer;
67 let thread: &mut Account<Thread> = &mut ctx.accounts.thread;
68
69 let create_durable_thread = ctx.accounts.nonce_account.is_some();
71
72 if create_durable_thread {
73 let nonce_account = ctx.accounts.nonce_account.as_ref().unwrap();
75 let recent_blockhashes = ctx.accounts.recent_blockhashes.as_ref().ok_or(error!(
76 crate::errors::AntegenThreadError::InvalidNonceAccount
77 ))?;
78 let rent_program = ctx.accounts.rent.as_ref().ok_or(error!(
79 crate::errors::AntegenThreadError::InvalidNonceAccount
80 ))?;
81
82 let rent: Rent = Rent::get()?;
83 let nonce_account_size: usize = State::size();
84 let nonce_lamports: u64 = rent.minimum_balance(nonce_account_size);
85
86 create_nonce_account(
87 CpiContext::new(
88 anchor_lang::system_program::ID,
89 CreateNonceAccount {
90 from: payer.to_account_info(),
91 nonce: nonce_account.to_account_info(),
92 recent_blockhashes: recent_blockhashes.to_account_info(),
93 rent: rent_program.to_account_info(),
94 },
95 ),
96 nonce_lamports,
97 &thread.key(),
98 )?;
99
100 thread.nonce_account = nonce_account.key();
101 } else {
102 thread.nonce_account = crate::ID;
103 }
104
105 let clock = Clock::get().unwrap();
107 let current_timestamp = clock.unix_timestamp;
108
109 thread.version = CURRENT_THREAD_VERSION;
110 thread.authority = authority.key();
111 thread.bump = ctx.bumps.thread;
112 thread.created_at = current_timestamp;
113 thread.name = id.to_name();
114 thread.id = id.into();
115 thread.paused = false;
116 thread.trigger = trigger.clone();
117
118 let thread_pubkey = thread.key();
121 thread.schedule = match &trigger {
122 Trigger::Account { .. } => Schedule::OnChange { prev: 0 },
123 Trigger::Cron {
124 schedule, jitter, ..
125 } => {
126 let base_next =
127 next_timestamp(current_timestamp, schedule.clone()).unwrap_or(current_timestamp);
128 let jitter_offset =
130 crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
131 let next = base_next.saturating_add(jitter_offset);
132 Schedule::Timed {
133 prev: current_timestamp, next,
135 }
136 }
137 Trigger::Immediate { .. } => Schedule::Timed {
138 prev: current_timestamp, next: current_timestamp,
140 },
141 Trigger::Slot { slot } => Schedule::Block {
142 prev: clock.slot, next: *slot,
144 },
145 Trigger::Epoch { epoch } => Schedule::Block {
146 prev: clock.epoch, next: *epoch,
148 },
149 Trigger::Interval {
150 seconds, jitter, ..
151 } => {
152 let base_next = current_timestamp.saturating_add(*seconds);
153 let jitter_offset =
155 crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
156 let next = base_next.saturating_add(jitter_offset);
157 Schedule::Timed {
158 prev: current_timestamp, next,
160 }
161 }
162 Trigger::Timestamp { unix_ts, .. } => Schedule::Timed {
163 prev: current_timestamp, next: *unix_ts,
165 },
166 };
167
168 let signer_seeds = vec![vec![
170 SEED_THREAD.to_vec(),
171 thread.authority.to_bytes().to_vec(),
172 thread.id.clone(),
173 ]];
174
175 if let Some(instruction) = initial_instruction {
177 let instruction: Instruction = instruction.into();
179
180 let compiled = compile_instruction(instruction, signer_seeds.clone())?;
182 let compiled_bytes = borsh::to_vec(&compiled)?;
183
184 thread.default_fiber = Some(compiled_bytes);
186 thread.default_fiber_priority_fee = priority_fee.unwrap_or(0);
187 thread.fiber_next_id = 1; thread.fiber_ids = vec![0]; } else {
190 thread.default_fiber = None;
192 thread.default_fiber_priority_fee = 0;
193 thread.fiber_next_id = 0; thread.fiber_ids = Vec::new();
195 }
196
197 thread.fiber_cursor = 0;
198 thread.exec_count = 0; thread.last_executor = Pubkey::default(); thread.fiber_signal = Signal::None;
203
204 let delete_ix = Instruction {
206 program_id: crate::ID,
207 accounts: crate::accounts::ThreadDelete {
208 authority: thread_pubkey, close_to: thread.authority, thread: thread_pubkey,
211 }
212 .to_account_metas(None),
213 data: crate::instruction::DeleteThread {}.data(),
214 };
215
216 let compiled = compile_instruction(delete_ix, signer_seeds)?;
217 thread.close_fiber = borsh::to_vec(&compiled)?;
218
219 transfer(
221 CpiContext::new(
222 anchor_lang::system_program::ID,
223 Transfer {
224 from: payer.to_account_info(),
225 to: thread.to_account_info(),
226 },
227 ),
228 amount,
229 )?;
230
231 Ok(())
232}