> ## Documentation Index
> Fetch the complete documentation index at: https://luminouslabs-cc5545c6-swen-add-code-runner.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Close Light Token Account

> Program guide to close light-token accounts via CPI. Includes step-by-step implementation and full code examples.

export const CompressibleRentCalculator = () => {
  const [hours, setHours] = useState(24);
  const [lamportsPerWrite, setLamportsPerWrite] = useState(776);
  const [showCustomHours, setShowCustomHours] = useState(false);
  const [showCustomLamports, setShowCustomLamports] = useState(false);
  const [showFormula, setShowFormula] = useState(false);
  const DATA_LEN = 260;
  const BASE_RENT = 128;
  const LAMPORTS_PER_BYTE_PER_EPOCH = 1;
  const MINUTES_PER_EPOCH = 90;
  const COMPRESSION_INCENTIVE = 11000;
  const LAMPORTS_PER_SOL = 1_000_000_000;
  const HOURS_MAX = 36;
  const LAMPORTS_MAX = 6400;
  const numEpochs = Math.ceil(hours * 60 / MINUTES_PER_EPOCH);
  const rentPerEpoch = BASE_RENT + DATA_LEN * LAMPORTS_PER_BYTE_PER_EPOCH;
  const totalPrepaidRent = rentPerEpoch * numEpochs;
  const totalCreationCost = totalPrepaidRent + COMPRESSION_INCENTIVE;
  const handleHoursChange = value => {
    const num = Math.max(3, Math.min(168, Number.parseInt(value) || 3));
    setHours(num);
  };
  const handleLamportsChange = value => {
    const num = Math.max(0, Math.min(100000, Number.parseInt(value) || 0));
    setLamportsPerWrite(num);
  };
  const hoursPresets = [24];
  const lamportsPresets = [776];
  const SliderMarkers = ({max, step}) => {
    const marks = [];
    for (let i = step; i < max; i += step) {
      const percent = i / max * 100;
      marks.push(<div key={i} className="absolute top-1/2 -translate-y-1/2 w-px h-2 bg-zinc-300 dark:bg-white/30" style={{
        left: `${percent}%`
      }} />);
    }
    return <>{marks}</>;
  };
  return <div className="p-5 rounded-3xl not-prose mt-4 dark:bg-white/5 backdrop-blur-xl border border-black/[0.04] dark:border-white/10 shadow-lg" style={{
    fontFamily: 'Inter, sans-serif'
  }}>
      <div className="space-y-5">
        {}
        <div className="space-y-2 px-3">
          <div className="flex justify-between items-center">
            <span className="text-sm text-zinc-700 dark:text-white/80">Prepaid Epochs in Hours</span>
            <div className="flex items-center gap-1.5">
              {hoursPresets.map(h => <button key={h} onClick={() => {
    setHours(h);
    setShowCustomHours(false);
  }} className={`px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all ${hours === h && !showCustomHours ? 'bg-blue-500/20 border-blue-500/50 text-blue-600 dark:text-blue-400' : 'bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]'}`}>
                  {h === 24 ? 'Default' : `${h}h`}
                </button>)}
              {showCustomHours ? <input type="number" min="3" max="168" value={hours} onChange={e => handleHoursChange(e.target.value)} className="w-16 px-2 py-1 text-right text-xs font-mono font-medium bg-blue-500/10 dark:bg-blue-500/20 border border-blue-500/50 rounded-lg backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50" autoFocus /> : <button onClick={() => setShowCustomHours(true)} className="px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]">
                  Custom
                </button>}
            </div>
          </div>
          <div className="flex items-center">
            <span className="w-1/3 text-xs text-zinc-500 dark:text-white/50 whitespace-nowrap">
              ≈ {(hours * 60 / MINUTES_PER_EPOCH).toFixed(1)} epochs / {hours.toFixed(1)}h
            </span>
            <div className="w-2/3 relative">
              <SliderMarkers max={HOURS_MAX} step={2} />
              <input type="range" min="3" max={HOURS_MAX} value={Math.min(hours, HOURS_MAX)} onChange={e => {
    setHours(Number.parseInt(e.target.value));
    setShowCustomHours(false);
  }} className="relative w-full h-1.5 bg-black/[0.03] dark:bg-white/20 rounded-full appearance-none cursor-pointer backdrop-blur-sm z-10" />
            </div>
          </div>
        </div>

        {}
        <div className="space-y-2 px-3">
          <div className="flex justify-between items-center">
            <span className="text-sm text-zinc-700 dark:text-white/80">Lamports per Write</span>
            <div className="flex items-center gap-1.5">
              {lamportsPresets.map(l => <button key={l} onClick={() => {
    setLamportsPerWrite(l);
    setShowCustomLamports(false);
  }} className={`px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all ${lamportsPerWrite === l && !showCustomLamports ? 'bg-blue-500/20 border-blue-500/50 text-blue-600 dark:text-blue-400' : 'bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]'}`}>
                  {l === 776 ? 'Default' : l.toLocaleString()}
                </button>)}
              {showCustomLamports ? <input type="number" min="0" max="100000" value={lamportsPerWrite} onChange={e => handleLamportsChange(e.target.value)} className="w-20 px-2 py-1 text-right text-xs font-mono font-medium bg-blue-500/10 dark:bg-blue-500/20 border border-blue-500/50 rounded-lg backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50" autoFocus /> : <button onClick={() => setShowCustomLamports(true)} className="px-2.5 py-1 text-xs font-medium rounded-lg border backdrop-blur-sm transition-all bg-black/[0.015] dark:bg-white/5 border-black/[0.04] dark:border-white/20 text-zinc-600 dark:text-white/70 hover:bg-black/[0.03]">
                  Custom
                </button>}
            </div>
          </div>
          <div className="flex items-center">
            <span className="w-1/3 text-xs text-zinc-500 dark:text-white/50 whitespace-nowrap">
              ≈ {(lamportsPerWrite / rentPerEpoch).toFixed(1)} epochs / {(lamportsPerWrite / rentPerEpoch * MINUTES_PER_EPOCH / 60).toFixed(1)}h
            </span>
            <div className="w-2/3 relative">
              <SliderMarkers max={LAMPORTS_MAX} step={800} />
              <input type="range" min="0" max={LAMPORTS_MAX} step="100" value={Math.min(lamportsPerWrite, LAMPORTS_MAX)} onChange={e => {
    setLamportsPerWrite(Number.parseInt(e.target.value));
    setShowCustomLamports(false);
  }} className="relative w-full h-1.5 bg-black/[0.03] dark:bg-white/20 rounded-full appearance-none cursor-pointer backdrop-blur-sm z-10" />
            </div>
          </div>
        </div>

        {}
        <div className="grid grid-cols-2 gap-3">
          <div className="p-4 bg-black/[0.015] dark:bg-white/5 backdrop-blur-md rounded-2xl text-center border border-black/[0.04] dark:border-white/10 shadow-sm">
            <div className="text-xs text-zinc-500 dark:text-white/50 mb-1 uppercase tracking-wide">Total Creation Cost</div>
            <div className="text-xl font-mono font-semibold text-zinc-900 dark:text-white">
              {totalCreationCost.toLocaleString()}
            </div>
            <div className="text-xs text-zinc-400 dark:text-white/40">lamports</div>
            <div className="text-xs text-zinc-500 dark:text-white/50 mt-1">≈ {(totalCreationCost / LAMPORTS_PER_SOL).toFixed(6)} SOL</div>
          </div>

          <div className="p-4 bg-black/[0.015] dark:bg-white/5 backdrop-blur-md rounded-2xl text-center border border-black/[0.04] dark:border-white/10 shadow-sm">
            <div className="text-xs text-zinc-500 dark:text-white/50 mb-1 uppercase tracking-wide">Top-up Amount</div>
            <div className="text-xl font-mono font-semibold text-zinc-900 dark:text-white">
              {lamportsPerWrite.toLocaleString()}
            </div>
            <div className="text-xs text-zinc-400 dark:text-white/40">lamports</div>
            <div className="text-xs text-zinc-500 dark:text-white/50 mt-1">≈ {(lamportsPerWrite / LAMPORTS_PER_SOL).toFixed(6)} SOL</div>
          </div>
        </div>

        {}
        <div className="pt-3 border-t border-black/[0.04] dark:border-white/10">
          <button onClick={() => setShowFormula(!showFormula)} className="flex items-center gap-2 text-xs text-zinc-500 dark:text-white/50 hover:text-zinc-700 dark:hover:text-white/70 transition-colors">
            <span className={`transition-transform ${showFormula ? 'rotate-90' : ''}`}>▶</span>
            Show formula
          </button>
          {showFormula && <div className="text-xs font-mono text-zinc-500 dark:text-white/40 mt-3">
              <div className="text-zinc-600 dark:text-white/60 mb-2">Total cost for {DATA_LEN}-byte light-token account:</div>
              total_creation_cost = prepaid_rent + compression_incentive<br /><br />
              rent_per_epoch = base_rent + (data_len × lamports_per_byte_per_epoch)<br />
              rent_per_epoch = {BASE_RENT} + ({DATA_LEN} × {LAMPORTS_PER_BYTE_PER_EPOCH}) = {rentPerEpoch} lamports<br />
              compression_incentive = {COMPRESSION_INCENTIVE.toLocaleString()} lamports
            </div>}
        </div>
      </div>
    </div>;
};

***

1. Closing a light-token account transfers remaining lamports to a destination account and the rent sponsor can reclaim sponsored rent.
2. Light token accounts can be closed
   * by the account owner at any time.
   * by the <Tooltip tip="The `compression_authority` is a PDA of the Light Registry program. It claims rent for the rent sponsor from compressible accounts that are compressed and closed.">`compression_authority`</Tooltip>
     when the account becomes <Tooltip tip="An account is compressible when it has rent for less than 1 epoch (360 lamports for a 260-byte light-token account).">compressible</Tooltip>. The account is <Tooltip tip="You can think of compressed light-token accounts as storage of inactive accounts that can be reactivated."> compressed and closed</Tooltip> - it can be reinstated with the same state (decompressed).

## Get Started

<Tabs>
  <Tab title="Rust Client">
    1. The example creates a light-token account and mint.
    2. Build the instruction with `CloseCTokenAccount`:

    ```rust theme={null}
    let close_instruction = CloseCTokenAccount::new(
        CTOKEN_PROGRAM_ID,
        account.pubkey(),
        payer.pubkey(), // Destination for remaining lamports
        owner,
    )
    .instruction()
    ```

    <Steps>
      <Step>
        ### Prerequisites

        <Accordion title="Dependencies">
          ```toml Cargo.toml theme={null}
          [dependencies]
          light-compressed-token-sdk = "0.1"
          light-client = "0.1"
          light-ctoken-types = "0.1"
          solana-sdk = "2.2"
          borsh = "0.10"
          tokio = { version = "1.36", features = ["full"] }

          [dev-dependencies]
          light-program-test = "0.1"  # For in-memory tests with LiteSVM
          ```
        </Accordion>

        <Accordion title="Developer Environment">
          <Tabs>
            <Tab title="In-Memory (LightProgramTest)">
              Test with Lite-SVM (...)

              ```bash theme={null}
              # Initialize project
              cargo init my-light-project
              cd my-light-project

              # Run tests
              cargo test
              ```

              ```rust theme={null}
              use light_program_test::{LightProgramTest, ProgramTestConfig};
              use solana_sdk::signer::Signer;

              #[tokio::test]
              async fn test_example() {
                  // In-memory test environment 
                  let mut rpc = LightProgramTest::new(ProgramTestConfig::default())
                      .await
                      .unwrap();

                  let payer = rpc.get_payer().insecure_clone();
                  println!("Payer: {}", payer.pubkey());
              }
              ```
            </Tab>

            <Tab title="Localnet (LightClient)">
              Connects to a local test validator.

              <Tabs>
                <Tab title="npm">
                  ```bash theme={null}
                  npm install -g @lightprotocol/zk-compression-cli@alpha
                  ```
                </Tab>

                <Tab title="yarn">
                  ```bash theme={null}
                  yarn global add @lightprotocol/zk-compression-cli@alpha
                  ```
                </Tab>

                <Tab title="pnpm">
                  ```bash theme={null}
                  pnpm add -g @lightprotocol/zk-compression-cli@alpha
                  ```
                </Tab>
              </Tabs>

              ```bash theme={null}
              # Initialize project
              cargo init my-light-project
              cd my-light-project

              # Start local test validator (in separate terminal)
              light test-validator
              ```

              ```rust theme={null}
              use light_client::rpc::{LightClient, LightClientConfig, Rpc};

              #[tokio::main]
              async fn main() -> Result<(), Box<dyn std::error::Error>> {
                  // Connects to http://localhost:8899
                  let rpc = LightClient::new(LightClientConfig::local()).await?;

                  let slot = rpc.get_slot().await?;
                  println!("Current slot: {}", slot);

                  Ok(())
              }
              ```
            </Tab>

            <Tab title="Devnet (LightClient)">
              Replace `<your-api-key>` with your actual API key. [Get your API key here](https://www.helius.dev/zk-compression).

              ```rust theme={null}
              use light_client::rpc::{LightClient, LightClientConfig, Rpc};

              #[tokio::main]
              async fn main() -> Result<(), Box<dyn std::error::Error>> {
                  let rpc_url = "https://devnet.helius-rpc.com?api-key=<your_api_key>";
                  let rpc = LightClient::new(
                      LightClientConfig::new(rpc_url.to_string(), None, None)
                  ).await?;

                  println!("Connected to Devnet");
                  Ok(())
              }
              ```
            </Tab>
          </Tabs>
        </Accordion>
      </Step>

      <Step>
        ### Close light-token Account

        ```rust theme={null}
        use borsh::BorshDeserialize;
        use light_client::indexer::{AddressWithTree, Indexer};
        use light_client::rpc::{LightClient, LightClientConfig, Rpc};
        use light_ctoken_sdk::ctoken::{
            CloseCTokenAccount, CreateCMint, CreateCMintParams, CreateCTokenAccount, CTOKEN_PROGRAM_ID,
        };
        use light_ctoken_interface::state::CToken;
        use serde_json;
        use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer};
        use std::convert::TryFrom;
        use std::env;
        use std::fs;

        #[tokio::test(flavor = "multi_thread")]
        async fn test_close_ctoken_account() {
            dotenvy::dotenv().ok();

            let keypair_path = env::var("KEYPAIR_PATH")
                .unwrap_or_else(|_| format!("{}/.config/solana/id.json", env::var("HOME").unwrap()));
            let payer = load_keypair(&keypair_path).expect("Failed to load keypair");

            let api_key = env::var("api_key") // Set api_key in your .env
                .expect("api_key environment variable must be set");

            let config = LightClientConfig::devnet(
                Some("https://devnet.helius-rpc.com".to_string()),
                Some(api_key),
            );
            let mut rpc = LightClient::new_with_retry(config, None)
                .await
                .expect("Failed to initialize LightClient");

            // Step 1: Create compressed mint (prerequisite)
            let (mint, _compression_address) = create_compressed_mint(&mut rpc, &payer, 9).await;

            // Step 2: Create cToken account with 0 balance
            let account = Keypair::new();
            let owner = payer.pubkey();

            let create_instruction =
                CreateCTokenAccount::new(payer.pubkey(), account.pubkey(), mint, owner)
                    .instruction()
                    .unwrap();

            rpc.create_and_send_transaction(&[create_instruction], &payer.pubkey(), &[&payer, &account])
                .await
                .unwrap();

            // Step 3: Verify account exists before closing
            let account_before_close = rpc.get_account(account.pubkey()).await.unwrap();
            assert!(
                account_before_close.is_some(),
                "Account should exist before closing"
            );

            let ctoken_state =
                CToken::deserialize(&mut &account_before_close.unwrap().data[..]).unwrap();
            assert_eq!(ctoken_state.amount, 0, "Account balance must be 0 to close");

            // Step 4: Build close instruction using SDK builder
            let close_instruction = CloseCTokenAccount::new(
                CTOKEN_PROGRAM_ID,
                account.pubkey(),
                payer.pubkey(), // Destination for remaining lamports
                owner,
            )
            .instruction()
            .unwrap();

            // Step 5: Send close transaction
            rpc.create_and_send_transaction(&[close_instruction], &payer.pubkey(), &[&payer])
                .await
                .unwrap();

            // Step 6: Verify account is closed
            let account_after_close = rpc.get_account(account.pubkey()).await.unwrap();
            assert!(
                account_after_close.is_none(),
                "Account should be closed and no longer exist"
            );
        }

        pub async fn create_compressed_mint<R: Rpc + Indexer>(
            rpc: &mut R,
            payer: &Keypair,
            decimals: u8,
        ) -> (Pubkey, [u8; 32]) {
            let mint_signer = Keypair::new();
            let address_tree = rpc.get_address_tree_v2();

            // Fetch active state trees for devnet
            let _ = rpc.get_latest_active_state_trees().await;
            let output_pubkey = match rpc
                .get_random_state_tree_info()
                .ok()
                .or_else(|| rpc.get_random_state_tree_info_v1().ok())
            {
                Some(info) => info
                    .get_output_pubkey()
                    .expect("Invalid state tree type for output"),
                None => {
                    let queues = rpc
                        .indexer_mut()
                        .expect("IndexerNotInitialized")
                        .get_queue_info(None)
                        .await
                        .expect("Failed to fetch queue info")
                        .value
                        .queues;
                    queues
                        .get(0)
                        .map(|q| q.queue)
                        .expect("NoStateTreesAvailable: no active state trees returned")
                }
            };

            // Derive compression address
            let compression_address = light_ctoken_sdk::ctoken::derive_cmint_compressed_address(
                &mint_signer.pubkey(),
                &address_tree.tree,
            );

            let mint_pda = light_ctoken_sdk::ctoken::find_cmint_address(&mint_signer.pubkey()).0;

            // Get validity proof for the address
            let rpc_result = rpc
                .get_validity_proof(
                    vec![],
                    vec![AddressWithTree {
                        address: compression_address,
                        tree: address_tree.tree,
                    }],
                    None,
                )
                .await
                .unwrap()
                .value;

            // Build params
            let params = CreateCMintParams {
                decimals,
                address_merkle_tree_root_index: rpc_result.addresses[0].root_index,
                mint_authority: payer.pubkey(),
                proof: rpc_result.proof.0.unwrap(),
                compression_address,
                mint: mint_pda,
                freeze_authority: None,
                extensions: None,
            };

            // Create instruction
            let create_cmint = CreateCMint::new(
                params,
                mint_signer.pubkey(),
                payer.pubkey(),
                address_tree.tree,
                output_pubkey,
            );
            let instruction = create_cmint.instruction().unwrap();

            // Send transaction
            rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_signer])
                .await
                .unwrap();

            (mint_pda, compression_address)
        }

        fn load_keypair(path: &str) -> Result<Keypair, Box<dyn std::error::Error>> {
            let path = if path.starts_with("~") {
                path.replace("~", &env::var("HOME").unwrap_or_default())
            } else {
                path.to_string()
            };
            let file = fs::read_to_string(&path)?;
            let bytes: Vec<u8> = serde_json::from_str(&file)?;
            Ok(Keypair::try_from(&bytes[..])?)
        }
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Program Guide">
    <Note>
      Find [a full code example at the end](#full-code-example).
    </Note>

    <Steps>
      <Step>
        ### Build Account Infos and CPI the light token program

        1. Define the light-token account to close and where remaining lamports should go
        2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer.

        <Tabs>
          <Tab title="invoke (External Signer)">
            ```rust theme={null}
            use light_ctoken_sdk::ctoken::CloseCTokenAccountCpi;

            CloseCTokenAccountCpi {
                token_program: token_program.clone(),
                account: account.clone(),
                destination: destination.clone(),
                owner: owner.clone(),
                rent_sponsor: Some(rent_sponsor.clone()),
            }
            .invoke()?;
            ```
          </Tab>

          <Tab title="invoke_signed (PDA Owner)">
            ```rust theme={null}
            use light_ctoken_sdk::ctoken::CloseCTokenAccountCpi;

            let close_account_cpi = CloseCTokenAccountCpi {
                token_program: token_program.clone(),
                account: account.clone(),
                destination: destination.clone(),
                owner: owner.clone(),
                rent_sponsor: Some(rent_sponsor.clone()),
            };

            let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]];
            close_account_cpi.invoke_signed(&[signer_seeds])?;
            ```
          </Tab>
        </Tabs>

        <Accordion title="Account List">
          <table>
            <colgroup>
              <col style={{ width: "25%", textAlign: "left" }} />

              <col style={{ width: "55%" }} />
            </colgroup>

            <thead>
              <tr>
                <th style={{ textAlign: "left" }} />

                <th style={{ textAlign: "left" }} />

                <th style={{ textAlign: "left" }} />
              </tr>
            </thead>

            <tbody>
              <tr>
                <td style={{ textAlign: "left" }}>
                  <strong>Token Program</strong>
                </td>

                <td>-</td>
                <td>The light token program for CPI.</td>
              </tr>

              <tr>
                <td style={{ textAlign: "left" }}>
                  <strong>Account</strong>
                </td>

                <td>mutable</td>
                <td>The light-token account to close.</td>
              </tr>

              <tr>
                <td style={{ textAlign: "left" }}>
                  <strong>Destination</strong>
                </td>

                <td>mutable</td>
                <td>Receives remaining lamports from the closed account.</td>
              </tr>

              <tr>
                <td style={{ textAlign: "left" }}>
                  <strong>Owner</strong>
                </td>

                <td>signer\*</td>

                <td>
                  * Owner of the light-token account.
                    <br />- \*Must be signer for `invoke()`. For `invoke_signed()`, program
                    signs via PDA seeds.
                </td>
              </tr>

              <tr>
                <td style={{ textAlign: "left" }}>
                  <strong>Rent Sponsor</strong>
                </td>

                <td>mutable, optional</td>

                <td>
                  * light token program PDA that fronts rent exemption at creation. \*
                    Claims rent when account compresses and/or is closed.
                </td>
              </tr>
            </tbody>
          </table>
        </Accordion>
      </Step>
    </Steps>

    # Full Code Example

    <Info>
      Find the source code
      [here](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-tests/sdk-ctoken-test/src/close.rs).
    </Info>

    ```rust expandable theme={null}
    use light_ctoken_sdk::ctoken::CloseCTokenAccountCpi;
    use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};

    use crate::{ID, TOKEN_ACCOUNT_SEED};

    /// Handler for closing a compressed token account (invoke)
    ///
    /// Account order:
    /// - accounts[0]: token_program (ctoken program)
    /// - accounts[1]: account to close (writable)
    /// - accounts[2]: destination for lamports (writable)
    /// - accounts[3]: owner/authority (signer)
    /// - accounts[4]: rent_sponsor (optional, writable)
    pub fn process_close_account_invoke(accounts: &[AccountInfo]) -> Result<(), ProgramError> {
        if accounts.len() < 4 {
            return Err(ProgramError::NotEnoughAccountKeys);
        }

        let rent_sponsor = if accounts.len() > 4 {
            Some(accounts[4].clone())
        } else {
            None
        };

        CloseCTokenAccountCpi {
            token_program: accounts[0].clone(),
            account: accounts[1].clone(),
            destination: accounts[2].clone(),
            owner: accounts[3].clone(),
            rent_sponsor,
        }
        .invoke()?;

        Ok(())
    }

    /// Handler for closing a PDA-owned compressed token account (invoke_signed)
    ///
    /// Account order:
    /// - accounts[0]: token_program (ctoken program)
    /// - accounts[1]: account to close (writable)
    /// - accounts[2]: destination for lamports (writable)
    /// - accounts[3]: PDA owner/authority (not signer, program signs)
    /// - accounts[4]: rent_sponsor (optional, writable)
    pub fn process_close_account_invoke_signed(accounts: &[AccountInfo]) -> Result<(), ProgramError> {
        if accounts.len() < 4 {
            return Err(ProgramError::NotEnoughAccountKeys);
        }

        // Derive the PDA for the authority
        let (pda, bump) = Pubkey::find_program_address(&[TOKEN_ACCOUNT_SEED], &ID);

        // Verify the authority account is the PDA we expect
        if &pda != accounts[3].key {
            return Err(ProgramError::InvalidSeeds);
        }

        let rent_sponsor = if accounts.len() > 4 {
            Some(accounts[4].clone())
        } else {
            None
        };

        let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]];
        CloseCTokenAccountCpi {
            token_program: accounts[0].clone(),
            account: accounts[1].clone(),
            destination: accounts[2].clone(),
            owner: accounts[3].clone(),
            rent_sponsor,
        }
        .invoke_signed(&[signer_seeds])?;

        Ok(())
    }
    ```
  </Tab>
</Tabs>

# Next Steps

{" "}

<Card title="Go back to the overview for the light-token standard." icon="chevron-right" color="#0066ff" href="/light-token/welcome" horizontal />
