antegen_thread_program/instructions/
thread_create.rs1use crate::{
2 errors::AntegenThreadError,
3 state::{compile_instruction, Schedule, SerializableInstruction, Signal, ThreadSeeds, Trigger},
4 utils::next_timestamp,
5 *,
6};
7use anchor_lang::{
8 prelude::*,
9 solana_program::instruction::Instruction,
10 system_program::{create_nonce_account, transfer, CreateNonceAccount, Transfer},
11 InstructionData, ToAccountMetas,
12};
13use solana_nonce::state::State;
14
15#[derive(Accounts)]
20#[instruction(amount: u64, id: ThreadId)]
21pub struct ThreadCreate<'info> {
22 #[account()]
24 pub authority: Signer<'info>,
25
26 #[account(mut)]
28 pub payer: Signer<'info>,
29
30 #[account(
32 init_if_needed,
33 seeds = [
34 SEED_THREAD,
35 authority.key().as_ref(),
36 id.as_ref(),
37 ],
38 bump,
39 payer = payer,
40 space = 8 + Thread::INIT_SPACE
41 )]
42 pub thread: Account<'info, Thread>,
43
44 #[account(mut)]
47 pub nonce_account: Option<Signer<'info>>,
48
49 pub recent_blockhashes: Option<UncheckedAccount<'info>>,
51
52 pub rent: Option<UncheckedAccount<'info>>,
54
55 pub system_program: Program<'info, System>,
56
57 #[account(mut)]
59 pub fiber: Option<UncheckedAccount<'info>>,
60
61 pub fiber_program: Option<Program<'info, antegen_fiber_program::program::AntegenFiber>>,
63}
64
65pub fn thread_create(
66 ctx: Context<ThreadCreate>,
67 amount: u64,
68 id: ThreadId,
69 trigger: Trigger,
70 paused: Option<bool>,
71 instruction: Option<SerializableInstruction>,
72 priority_fee: Option<u64>,
73 lookup_tables: Vec<Pubkey>,
74) -> Result<()> {
75 let authority: &Signer = &ctx.accounts.authority;
76 let payer: &Signer = &ctx.accounts.payer;
77 let thread: &mut Account<Thread> = &mut ctx.accounts.thread;
78
79 let create_durable_thread = ctx.accounts.nonce_account.is_some();
81
82 if create_durable_thread {
83 let nonce_account = ctx.accounts.nonce_account.as_ref().unwrap();
85 let recent_blockhashes = ctx.accounts.recent_blockhashes.as_ref().ok_or(error!(
86 crate::errors::AntegenThreadError::InvalidNonceAccount
87 ))?;
88 let rent_program = ctx.accounts.rent.as_ref().ok_or(error!(
89 crate::errors::AntegenThreadError::InvalidNonceAccount
90 ))?;
91
92 let rent: Rent = Rent::get()?;
93 let nonce_account_size: usize = State::size();
94 let nonce_lamports: u64 = rent.minimum_balance(nonce_account_size);
95
96 create_nonce_account(
97 CpiContext::new(
98 anchor_lang::system_program::ID,
99 CreateNonceAccount {
100 from: payer.to_account_info(),
101 nonce: nonce_account.to_account_info(),
102 recent_blockhashes: recent_blockhashes.to_account_info(),
103 rent: rent_program.to_account_info(),
104 },
105 ),
106 nonce_lamports,
107 &thread.key(),
108 )?;
109
110 thread.nonce_account = nonce_account.key();
111 } else {
112 thread.nonce_account = crate::ID;
113 }
114
115 let clock = Clock::get().unwrap();
117 let current_timestamp = clock.unix_timestamp;
118
119 thread.version = CURRENT_THREAD_VERSION;
120 thread.authority = authority.key();
121 thread.bump = ctx.bumps.thread;
122 thread.created_at = current_timestamp;
123 thread.name = id.to_name();
124 thread.id = id.into();
125 thread.paused = paused.unwrap_or(false);
126 thread.trigger = trigger.clone();
127
128 let thread_pubkey = thread.key();
131 thread.schedule = match &trigger {
132 Trigger::Account { .. } => Schedule::OnChange { prev: 0 },
133 Trigger::Cron {
134 schedule, jitter, ..
135 } => {
136 let base_next =
137 next_timestamp(current_timestamp, schedule.clone()).unwrap_or(current_timestamp);
138 let jitter_offset =
139 crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
140 let next = base_next.saturating_add(jitter_offset);
141 Schedule::Timed {
142 prev: current_timestamp,
143 next,
144 }
145 }
146 Trigger::Immediate { .. } => Schedule::Timed {
147 prev: current_timestamp,
148 next: current_timestamp,
149 },
150 Trigger::Slot { slot } => Schedule::Block {
151 prev: clock.slot,
152 next: *slot,
153 },
154 Trigger::Epoch { epoch } => Schedule::Block {
155 prev: clock.epoch,
156 next: *epoch,
157 },
158 Trigger::Interval {
159 seconds, jitter, ..
160 } => {
161 let base_next = current_timestamp.saturating_add(*seconds);
162 let jitter_offset =
163 crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
164 let next = base_next.saturating_add(jitter_offset);
165 Schedule::Timed {
166 prev: current_timestamp,
167 next,
168 }
169 }
170 Trigger::Timestamp { unix_ts, .. } => Schedule::Timed {
171 prev: current_timestamp,
172 next: *unix_ts,
173 },
174 };
175
176 thread.exec_count = 0;
177 thread.last_executor = Pubkey::default();
178 thread.fiber_signal = Signal::None;
179
180 let close_ix = Instruction {
182 program_id: crate::ID,
183 accounts: crate::accounts::ThreadClose {
184 authority: thread_pubkey, close_to: thread.authority, thread: thread_pubkey,
187 fiber_program: Some(antegen_fiber_program::ID),
188 }
189 .to_account_metas(None),
190 data: crate::instruction::CloseThread {}.data(),
191 };
192
193 let compiled = compile_instruction(close_ix)?;
194 thread.close_fiber = borsh::to_vec(&compiled)?;
195
196 transfer(
199 CpiContext::new(
200 anchor_lang::system_program::ID,
201 Transfer {
202 from: payer.to_account_info(),
203 to: thread.to_account_info(),
204 },
205 ),
206 amount,
207 )?;
208
209 if let Some(instruction) = instruction {
211 if instruction.program_id.eq(&crate::ID)
213 && instruction.data.len().ge(&8)
214 && instruction.data[..8].eq(crate::instruction::DeleteThread::DISCRIMINATOR)
215 {
216 return Err(AntegenThreadError::InvalidInstruction.into());
217 }
218
219 let fiber = ctx
221 .accounts
222 .fiber
223 .as_ref()
224 .ok_or(AntegenThreadError::MissingFiberAccount)?;
225 let fiber_program = ctx
226 .accounts
227 .fiber_program
228 .as_ref()
229 .ok_or(AntegenThreadError::MissingFiberAccount)?;
230
231 let priority_fee = priority_fee.unwrap_or(0);
232
233 if fiber.to_account_info().data_len() == 0 {
235 let space = 8 + antegen_fiber_program::state::FiberVersionedState::INIT_SPACE;
236 let rent_lamports = Rent::get()?.minimum_balance(space);
237 **thread.to_account_info().try_borrow_mut_lamports()? -= rent_lamports;
238 **fiber.to_account_info().try_borrow_mut_lamports()? += rent_lamports;
239 }
240
241 thread.sign(|seeds| {
242 antegen_fiber_program::cpi::create(
243 CpiContext::new_with_signer(
244 fiber_program.key(),
245 antegen_fiber_program::cpi::accounts::Create {
246 thread: thread.to_account_info(),
247 fiber: fiber.to_account_info(),
248 system_program: ctx.accounts.system_program.to_account_info(),
249 },
250 &[seeds],
251 ),
252 0, instruction.clone(),
254 priority_fee,
255 lookup_tables.clone(),
256 )
257 })?;
258
259 thread.fiber_next_id = 1;
260 thread.fiber_ids = vec![0];
261 thread.fiber_cursor = 0;
262 } else {
263 thread.fiber_next_id = 0;
265 thread.fiber_ids = Vec::new();
266 thread.fiber_cursor = 0;
267 }
268
269 Ok(())
270}