Skip to main content

antegen_thread_program/instructions/
thread_close.rs

1use {
2    crate::{errors::AntegenThreadError, *},
3    anchor_lang::prelude::*,
4    antegen_fiber_program::{
5        cpi::close,
6        state::{Fiber, FiberState},
7    },
8};
9
10/// Accounts required by the `thread_close` instruction.
11///
12/// External fiber accounts (FiberState PDAs) should be passed via remaining_accounts.
13/// All external fibers must be provided - partial deletion is not allowed.
14#[derive(Accounts)]
15pub struct ThreadClose<'info> {
16    /// The authority (owner) of the thread OR the thread itself (for self-deletion via CPI).
17    #[account(
18        constraint = authority.key().eq(&thread.authority) || authority.key().eq(&thread.key())
19    )]
20    pub authority: Signer<'info>,
21
22    /// The address to return the data rent lamports to.
23    #[account(mut)]
24    pub close_to: SystemAccount<'info>,
25
26    /// The thread to be closed.
27    #[account(
28        mut,
29        close = close_to,
30        seeds = [
31            SEED_THREAD,
32            thread.authority.as_ref(),
33            thread.id.as_slice(),
34        ],
35        bump = thread.bump
36    )]
37    pub thread: Account<'info, Thread>,
38
39    /// The Fiber Program (required when closing fibers via remaining_accounts)
40    pub fiber_program: Option<Program<'info, antegen_fiber_program::program::AntegenFiber>>,
41}
42
43pub fn thread_close<'info>(ctx: Context<'info, ThreadClose<'info>>) -> Result<()> {
44    let thread = &mut ctx.accounts.thread;
45    let thread_key = thread.key();
46
47    // Process each fiber account from remaining_accounts via CPI to Fiber Program
48    for account in ctx.remaining_accounts.iter() {
49        // Validate the slot holds a fiber (legacy or V1) belonging to this thread.
50        let fiber_read = Fiber::try_deserialize(&mut &account.data.borrow()[..])?;
51        require!(
52            fiber_read.thread() == thread_key,
53            AntegenThreadError::InvalidFiberAccount
54        );
55
56        // Find which fiber_id this account corresponds to by checking PDA derivation
57        let account_key = account.key();
58        let pos = thread
59            .fiber_ids
60            .iter()
61            .position(|&idx| FiberState::pubkey(thread_key, idx) == account_key)
62            .ok_or(AntegenThreadError::InvalidFiberAccount)?;
63        thread.fiber_ids.remove(pos);
64
65        // CPI to Fiber Program's close_fiber (rent returns to thread PDA)
66        let fiber_program = ctx
67            .accounts
68            .fiber_program
69            .as_ref()
70            .ok_or(AntegenThreadError::MissingFiberAccounts)?;
71
72        thread.sign(|seeds| {
73            close(CpiContext::new_with_signer(
74                fiber_program.key(),
75                antegen_fiber_program::cpi::accounts::Close {
76                    thread: thread.to_account_info(),
77                    fiber: account.to_account_info(),
78                },
79                &[seeds],
80            ))
81        })?;
82    }
83
84    // Validate ALL fibers were closed
85    require!(
86        thread.fiber_ids.is_empty(),
87        AntegenThreadError::MissingFiberAccounts
88    );
89
90    // Anchor's close = close_to handles the thread account
91    // (fiber rent returned to thread PDA is included in the transfer)
92    Ok(())
93}