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<AccountInfo<'info>>,
50
51 pub rent: Option<AccountInfo<'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 let system_program: &Program<System> = &ctx.accounts.system_program;
69
70 let create_durable_thread = ctx.accounts.nonce_account.is_some();
72
73 if create_durable_thread {
74 let nonce_account = ctx.accounts.nonce_account.as_ref().unwrap();
76 let recent_blockhashes = ctx.accounts.recent_blockhashes.as_ref().ok_or(error!(
77 crate::errors::AntegenThreadError::InvalidNonceAccount
78 ))?;
79 let rent_program = ctx.accounts.rent.as_ref().ok_or(error!(
80 crate::errors::AntegenThreadError::InvalidNonceAccount
81 ))?;
82
83 let rent: Rent = Rent::get()?;
84 let nonce_account_size: usize = State::size();
85 let nonce_lamports: u64 = rent.minimum_balance(nonce_account_size);
86
87 create_nonce_account(
88 CpiContext::new(
89 system_program.to_account_info(),
90 CreateNonceAccount {
91 from: payer.to_account_info(),
92 nonce: nonce_account.to_account_info(),
93 recent_blockhashes: recent_blockhashes.to_account_info(),
94 rent: rent_program.to_account_info(),
95 },
96 ),
97 nonce_lamports,
98 &thread.key(),
99 )?;
100
101 thread.nonce_account = nonce_account.key();
102 } else {
103 thread.nonce_account = crate::ID;
104 }
105
106 let clock = Clock::get().unwrap();
108 let current_timestamp = clock.unix_timestamp;
109
110 thread.version = CURRENT_THREAD_VERSION;
111 thread.authority = authority.key();
112 thread.bump = ctx.bumps.thread;
113 thread.created_at = current_timestamp;
114 thread.name = id.to_name();
115 thread.id = id.into();
116 thread.paused = false;
117 thread.trigger = trigger.clone();
118
119 let thread_pubkey = thread.key();
122 thread.schedule = match &trigger {
123 Trigger::Account { .. } => Schedule::OnChange { prev: 0 },
124 Trigger::Cron {
125 schedule, jitter, ..
126 } => {
127 let base_next =
128 next_timestamp(current_timestamp, schedule.clone()).unwrap_or(current_timestamp);
129 let jitter_offset =
131 crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
132 let next = base_next.saturating_add(jitter_offset);
133 Schedule::Timed {
134 prev: current_timestamp, next,
136 }
137 }
138 Trigger::Immediate { .. } => Schedule::Timed {
139 prev: current_timestamp, next: current_timestamp,
141 },
142 Trigger::Slot { slot } => Schedule::Block {
143 prev: clock.slot, next: *slot,
145 },
146 Trigger::Epoch { epoch } => Schedule::Block {
147 prev: clock.epoch, next: *epoch,
149 },
150 Trigger::Interval {
151 seconds, jitter, ..
152 } => {
153 let base_next = current_timestamp.saturating_add(*seconds);
154 let jitter_offset =
156 crate::utils::calculate_jitter_offset(current_timestamp, &thread_pubkey, *jitter);
157 let next = base_next.saturating_add(jitter_offset);
158 Schedule::Timed {
159 prev: current_timestamp, next,
161 }
162 }
163 Trigger::Timestamp { unix_ts, .. } => Schedule::Timed {
164 prev: current_timestamp, next: *unix_ts,
166 },
167 };
168
169 let signer_seeds = vec![vec![
171 SEED_THREAD.to_vec(),
172 thread.authority.to_bytes().to_vec(),
173 thread.id.clone(),
174 ]];
175
176 if let Some(instruction) = initial_instruction {
178 let instruction: Instruction = instruction.into();
180
181 let compiled = compile_instruction(instruction, signer_seeds.clone())?;
183 let compiled_bytes = compiled.try_to_vec()?;
184
185 thread.default_fiber = Some(compiled_bytes);
187 thread.default_fiber_priority_fee = priority_fee.unwrap_or(0);
188 thread.fiber_next_id = 1; thread.fiber_ids = vec![0]; } else {
191 thread.default_fiber = None;
193 thread.default_fiber_priority_fee = 0;
194 thread.fiber_next_id = 0; thread.fiber_ids = Vec::new();
196 }
197
198 thread.fiber_cursor = 0;
199 thread.exec_count = 0; thread.last_executor = Pubkey::default(); thread.fiber_signal = Signal::None;
204
205 let delete_ix = Instruction {
207 program_id: crate::ID,
208 accounts: crate::accounts::ThreadDelete {
209 authority: thread_pubkey, close_to: thread.authority, thread: thread_pubkey,
212 }
213 .to_account_metas(None),
214 data: crate::instruction::DeleteThread {}.data(),
215 };
216
217 let compiled = compile_instruction(delete_ix, signer_seeds)?;
218 thread.close_fiber = compiled.try_to_vec()?;
219
220 transfer(
222 CpiContext::new(
223 system_program.to_account_info(),
224 Transfer {
225 from: payer.to_account_info(),
226 to: thread.to_account_info(),
227 },
228 ),
229 amount,
230 )?;
231
232 Ok(())
233}