Skip to main content

antegen_thread_program/instructions/
thread_close.rs

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