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) -> Result<()> {
74 let authority: &Signer = &ctx.accounts.authority;
75 let payer: &Signer = &ctx.accounts.payer;
76 let thread: &mut Account<Thread> = &mut ctx.accounts.thread;
77
78 let create_durable_thread = ctx.accounts.nonce_account.is_some();
80
81 if create_durable_thread {
82 let nonce_account = ctx.accounts.nonce_account.as_ref().unwrap();
84 let recent_blockhashes = ctx.accounts.recent_blockhashes.as_ref().ok_or(error!(
85 crate::errors::AntegenThreadError::InvalidNonceAccount
86 ))?;
87 let rent_program = ctx.accounts.rent.as_ref().ok_or(error!(
88 crate::errors::AntegenThreadError::InvalidNonceAccount
89 ))?;
90
91 let rent: Rent = Rent::get()?;
92 let nonce_account_size: usize = State::size();
93 let nonce_lamports: u64 = rent.minimum_balance(nonce_account_size);
94
95 create_nonce_account(
96 CpiContext::new(
97 anchor_lang::system_program::ID,
98 CreateNonceAccount {
99 from: payer.to_account_info(),
100 nonce: nonce_account.to_account_info(),
101 recent_blockhashes: recent_blockhashes.to_account_info(),
102 rent: rent_program.to_account_info(),
103 },
104 ),
105 nonce_lamports,
106 &thread.key(),
107 )?;
108
109 thread.nonce_account = nonce_account.key();
110 } else {
111 thread.nonce_account = crate::ID;
112 }
113
114 let clock = Clock::get().unwrap();
116 let current_timestamp = clock.unix_timestamp;
117
118 thread.version = CURRENT_THREAD_VERSION;
119 thread.authority = authority.key();
120 thread.bump = ctx.bumps.thread;
121 thread.created_at = current_timestamp;
122 thread.name = id.to_name();
123 thread.id = id.into();
124 thread.paused = paused.unwrap_or(false);
125 thread.trigger = trigger.clone();
126
127 let thread_pubkey = thread.key();
130 thread.schedule = match &trigger {
131 Trigger::Account { .. } => Schedule::OnChange { prev: 0 },
132 Trigger::Cron {
133 schedule, jitter, ..
134 } => {
135 let base_next =
136 next_timestamp(current_timestamp, schedule.clone()).unwrap_or(current_timestamp);
137 let jitter_offset =
138 crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
139 let next = base_next.saturating_add(jitter_offset);
140 Schedule::Timed {
141 prev: current_timestamp,
142 next,
143 }
144 }
145 Trigger::Immediate { .. } => Schedule::Timed {
146 prev: current_timestamp,
147 next: current_timestamp,
148 },
149 Trigger::Slot { slot } => Schedule::Block {
150 prev: clock.slot,
151 next: *slot,
152 },
153 Trigger::Epoch { epoch } => Schedule::Block {
154 prev: clock.epoch,
155 next: *epoch,
156 },
157 Trigger::Interval {
158 seconds, jitter, ..
159 } => {
160 let base_next = current_timestamp.saturating_add(*seconds);
161 let jitter_offset =
162 crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
163 let next = base_next.saturating_add(jitter_offset);
164 Schedule::Timed {
165 prev: current_timestamp,
166 next,
167 }
168 }
169 Trigger::Timestamp { unix_ts, .. } => Schedule::Timed {
170 prev: current_timestamp,
171 next: *unix_ts,
172 },
173 };
174
175 thread.exec_count = 0;
176 thread.last_executor = Pubkey::default();
177 thread.fiber_signal = Signal::None;
178
179 let close_ix = Instruction {
181 program_id: crate::ID,
182 accounts: crate::accounts::ThreadClose {
183 authority: thread_pubkey, close_to: thread.authority, thread: thread_pubkey,
186 fiber_program: Some(antegen_fiber_program::ID),
187 }
188 .to_account_metas(None),
189 data: crate::instruction::CloseThread {}.data(),
190 };
191
192 let compiled = compile_instruction(close_ix)?;
193 thread.close_fiber = borsh::to_vec(&compiled)?;
194
195 transfer(
198 CpiContext::new(
199 anchor_lang::system_program::ID,
200 Transfer {
201 from: payer.to_account_info(),
202 to: thread.to_account_info(),
203 },
204 ),
205 amount,
206 )?;
207
208 if let Some(instruction) = instruction {
210 if instruction.program_id.eq(&crate::ID)
212 && instruction.data.len().ge(&8)
213 && instruction.data[..8].eq(crate::instruction::DeleteThread::DISCRIMINATOR)
214 {
215 return Err(AntegenThreadError::InvalidInstruction.into());
216 }
217
218 let fiber = ctx
220 .accounts
221 .fiber
222 .as_ref()
223 .ok_or(AntegenThreadError::MissingFiberAccount)?;
224 let fiber_program = ctx
225 .accounts
226 .fiber_program
227 .as_ref()
228 .ok_or(AntegenThreadError::MissingFiberAccount)?;
229
230 let priority_fee = priority_fee.unwrap_or(0);
231
232 if fiber.to_account_info().data_len() == 0 {
234 let space = 8 + antegen_fiber_program::state::FiberState::INIT_SPACE;
235 let rent_lamports = Rent::get()?.minimum_balance(space);
236 **thread.to_account_info().try_borrow_mut_lamports()? -= rent_lamports;
237 **fiber.to_account_info().try_borrow_mut_lamports()? += rent_lamports;
238 }
239
240 thread.sign(|seeds| {
241 antegen_fiber_program::cpi::create_fiber(
242 CpiContext::new_with_signer(
243 fiber_program.key(),
244 antegen_fiber_program::cpi::accounts::FiberCreate {
245 thread: thread.to_account_info(),
246 fiber: fiber.to_account_info(),
247 system_program: ctx.accounts.system_program.to_account_info(),
248 },
249 &[seeds],
250 ),
251 0, instruction.clone(),
253 priority_fee,
254 )
255 })?;
256
257 thread.fiber_next_id = 1;
258 thread.fiber_ids = vec![0];
259 thread.fiber_cursor = 0;
260 } else {
261 thread.fiber_next_id = 0;
263 thread.fiber_ids = Vec::new();
264 thread.fiber_cursor = 0;
265 }
266
267 Ok(())
268}