antegen_thread_program/instructions/
thread_delete.rs

1use {
2    crate::{errors::AntegenThreadError, state::FiberState, *},
3    anchor_lang::prelude::*,
4};
5
6/// Accounts required by the `thread_delete` instruction.
7///
8/// External fiber accounts (FiberState PDAs) should be passed via remaining_accounts.
9/// All external fibers must be provided - partial deletion is not allowed.
10#[derive(Accounts)]
11pub struct ThreadDelete<'info> {
12    /// The authority (owner) of the thread OR the thread itself (for self-deletion via CPI).
13    #[account(
14        constraint = authority.key().eq(&thread.authority) || authority.key().eq(&thread.key())
15    )]
16    pub authority: Signer<'info>,
17
18    /// The address to return the data rent lamports to.
19    #[account(mut)]
20    pub close_to: SystemAccount<'info>,
21
22    /// The thread to be deleted.
23    #[account(
24        mut,
25        close = close_to,
26        seeds = [
27            SEED_THREAD,
28            thread.authority.as_ref(),
29            thread.id.as_slice(),
30        ],
31        bump = thread.bump
32    )]
33    pub thread: Account<'info, Thread>,
34}
35
36pub fn thread_delete(ctx: Context<ThreadDelete>) -> Result<()> {
37    let thread = &mut ctx.accounts.thread;
38    let close_to = &ctx.accounts.close_to;
39    let thread_key = thread.key();
40
41    // Process each fiber account from remaining_accounts
42    for account in ctx.remaining_accounts.iter() {
43        // Deserialize to validate it's a FiberState
44        let fiber = FiberState::try_deserialize(&mut &account.data.borrow()[..])?;
45
46        // Validate fiber belongs to this thread
47        require!(
48            fiber.thread == thread_key,
49            AntegenThreadError::InvalidFiberAccount
50        );
51
52        // Remove fiber_index from fiber_ids (fails if not found/duplicate)
53        let pos = thread
54            .fiber_ids
55            .iter()
56            .position(|&idx| idx == fiber.fiber_index)
57            .ok_or(AntegenThreadError::InvalidFiberAccount)?;
58        thread.fiber_ids.remove(pos);
59
60        // Transfer lamports to close_to
61        let lamports = account.lamports();
62        **account.try_borrow_mut_lamports()? = 0;
63        **close_to.to_account_info().try_borrow_mut_lamports()? += lamports;
64
65        // Zero account data & reassign owner to system program
66        account.try_borrow_mut_data()?.fill(0);
67        account.assign(&anchor_lang::system_program::ID);
68    }
69
70    // Validate ALL external fibers were closed
71    // After processing: fiber_ids should only contain inline fiber (0) or be empty
72    let valid_end_state = if thread.default_fiber.is_some() {
73        thread.fiber_ids == vec![0]
74    } else {
75        thread.fiber_ids.is_empty()
76    };
77    require!(valid_end_state, AntegenThreadError::MissingFiberAccounts);
78
79    // Anchor's close = close_to handles the thread account
80    Ok(())
81}