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

# Create Light Token Account

> Client and program guide to create light-token accounts. Includes step-by-step implementation and full code examples.

***

1. Light token accounts are Solana accounts that hold token balances of light, SPL, or Token 2022 mints.
2. Light token accounts implement a default rent config:
   1. At account creation, you pay \~17,208 lamports <Tooltip tip="24 h = 16 epochs, where 1 rent-epoch ≈ 1.5h ≈ 13,500 slots at 400ms per slot">for 24h of rent</Tooltip> <br />and <Tooltip tip="Covers transaction cost to compress accounts (10,000) and protocol incentive (1,000). Transaction cost might vary.">compression incentive</Tooltip> (the rent-exemption is sponsored by the protocol)
   2. Transfers keep the account funded <Tooltip tip="2 epochs = 3h">with rent for 3h</Tooltip> via top-ups. The transaction payer tops up 776 lamports when the account's rent is below 3h.

<Accordion title="Light Rent Config Explained">
  1) The Light Token Program pays the rent-exemption cost for the account.
  2) Transaction fee payers bump a virtual rent balance when writing to the account, which keeps the account "hot".
  3) "Cold" accounts virtual rent balance below threshold (eg 24h without write bump) get auto-compressed.
  4) The cold account's state is cryptographically preserved on the Solana ledger.
     Users can load a cold account into hot state in-flight when using the account
     again.
</Accordion>

## Get Started

<Tabs>
  <Tab title="Rust Client">
    1. The example creates a test mint for light-tokens. You can use existing light, SPL or Token 2022 mints as well.
    2. Build the instruction with `CreateTokenAccount`. It automatically includes the default rent config.

    ```rust theme={null}
    use light_token_sdk::token::{CreateTokenAccount};

    let instruction = CreateTokenAccount::new(
        payer.pubkey(),
        account.pubkey(),
        mint,
        owner,
    )
    .instruction()?;
    ```

    3. Send transaction & verify light-token account creation with `get_account`.

    <Steps>
      <Step>
        ### Prerequisites

        <Accordion title="Dependencies">
          ```toml Cargo.toml theme={null}
          [dependencies]
          light-compressed-token-sdk = "0.1"
          light-client = "0.1"
          light-token-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@beta
                  ```
                </Tab>

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

                <Tab title="pnpm">
                  ```bash theme={null}
                  pnpm add -g @lightprotocol/zk-compression-cli@beta
                  ```
                </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>
        ### Create Token Account

        ```rust theme={null}
        use borsh::BorshDeserialize;
        use light_client::indexer::{AddressWithTree, Indexer};
        use light_client::rpc::{LightClient, LightClientConfig, Rpc};
        use light_token_sdk::token::{CreateCMint, CreateCMintParams, CreateTokenAccount};
        use light_token_interface::state::Token;
        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_create_token_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: Generate new keypair for the cToken account
            let account = Keypair::new();
            let owner = payer.pubkey();

            // Step 3: Build instruction using SDK builder
            let instruction = CreateTokenAccount::new(payer.pubkey(), account.pubkey(), mint, owner)
                .instruction()
                .unwrap();

            // Step 4: Send transaction (account keypair must sign)
            rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &account])
                .await
                .unwrap();

            // Step 5: Verify account creation
            let account_data = rpc.get_account(account.pubkey()).await.unwrap().unwrap();
            let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap();

            assert_eq!(token_state.mint, mint.to_bytes(), "Mint should match");
            assert_eq!(token_state.owner, owner.to_bytes(), "Owner should match");
            assert_eq!(token_state.amount, 0, "Initial amount should be 0");
        }

        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_token_sdk::token::derive_cmint_compressed_address(
                &mint_signer.pubkey(),
                &address_tree.tree,
            );

            let mint_pda = light_token_sdk::token::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>
        ### Configure Rent

        ```rust theme={null}
        use light_token_sdk::token::CompressibleParamsInfos;

        let compressible_params = CompressibleParamsInfos::new(
            compressible_config.clone(),
            rent_sponsor.clone(),
            system_program.clone(),
        );
        ```

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

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

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

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

          <tbody>
            <tr>
              <td style={{ textAlign: "left" }}>
                <strong>
                  <Tooltip tip="Owned by LightRegistry program. Stores rent_sponsor, compression_delay, address_space, and rent_config.">
                    Compressible Config
                  </Tooltip>
                </strong>
              </td>

              <td>Protocol PDA that stores account rent config.</td>
            </tr>

            <tr>
              <td style={{ textAlign: "left" }}>
                <strong>
                  <Tooltip tip="light token program PDA that manages rent for compressible accounts.">
                    Rent Sponsor
                  </Tooltip>
                </strong>
              </td>

              <td>
                * light token program PDA that fronts rent exemption at creation.
                  <br />- Claims rent when account compresses.
              </td>
            </tr>

            <tr>
              <td style={{ textAlign: "left" }}>
                <strong>
                  <Tooltip tip="11111111111111111111111111111111" cta="Program ID" href="https://solscan.io/account/11111111111111111111111111111111">
                    System Program
                  </Tooltip>
                </strong>
              </td>

              <td>Solana System Program to create the on-chain account.</td>
            </tr>
          </tbody>
        </table>
      </Step>

      <Step>
        ### Build Account Infos and CPI

        1. Pass the required accounts
        2. Include rent config from `compressible_params`
        3. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer.

        <Tabs>
          <Tab title="invoke (External signer)">
            ```rust theme={null}
            use light_token_sdk::token::CreateTokenAccountCpi;

            CreateTokenAccountCpi {
                payer: payer.clone(),
                account: account.clone(),
                mint: mint.clone(),
                owner: data.owner,
                compressible: Some(compressible_params),
            }
            .invoke()?;
            ```

            <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>Payer</strong></td>
                  <td>signer, mutable</td>

                  <td>
                    * Pays initial rent per epoch, transaction fee and compression incentive.<br />
                    * Does NOT pay rent exemption (fronted by `rent_sponsor`).
                  </td>
                </tr>

                <tr>
                  <td style={{textAlign: 'left'}}><strong>light-token Account</strong></td>
                  <td>signer\*, mutable</td>

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

                <tr>
                  <td style={{textAlign: 'left'}}><strong>Mint</strong></td>
                  <td>-</td>
                  <td>The SPL or light-mint token mint.</td>
                </tr>

                <tr>
                  <td style={{textAlign: 'left'}}><strong>Owner</strong></td>
                  <td>Pubkey</td>
                  <td>The owner of the token account. Controls transfers and other operations.</td>
                </tr>
              </tbody>
            </table>
          </Tab>

          <Tab title="invoke_signed (PDA is signer)">
            ```rust theme={null}
            use light_token_sdk::token::CreateTokenAccountCpi;

            let account_cpi = CreateTokenAccountCpi {
                payer: payer.clone(),
                account: account.clone(),
                mint: mint.clone(),
                owner: data.owner,
                compressible: Some(compressible_params),
            };

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

    # Full Code Example

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

    ```rust expandable theme={null}
    use borsh::{BorshDeserialize, BorshSerialize};
    use light_token_sdk::token::{CompressibleParamsCpi, CreateTokenAccountCpi};
    use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};

    use crate::{ID, TOKEN_ACCOUNT_SEED};

    /// Instruction data for create token account
    #[derive(BorshSerialize, BorshDeserialize, Debug)]
    pub struct CreateTokenAccountData {
        pub owner: Pubkey,
        pub pre_pay_num_epochs: u8,
        pub lamports_per_write: u32,
    }

    /// Handler for creating a compressible token account (invoke)
    ///
    /// Uses the builder pattern from the token module. This demonstrates how to:
    /// 1. Build the account infos struct with compressible params
    /// 2. Call the invoke() method which handles instruction building and CPI
    ///
    /// Account order:
    /// - accounts[0]: payer (signer)
    /// - accounts[1]: account to create (signer)
    /// - accounts[2]: mint
    /// - accounts[3]: compressible_config
    /// - accounts[4]: system_program
    /// - accounts[5]: rent_sponsor
    pub fn process_create_token_account_invoke(
        accounts: &[AccountInfo],
        data: CreateTokenAccountData,
    ) -> Result<(), ProgramError> {
        if accounts.len() < 6 {
            return Err(ProgramError::NotEnoughAccountKeys);
        }

        // Build the compressible params using constructor
        let compressible_params = CompressibleParamsCpi::new(
            accounts[3].clone(),
            accounts[5].clone(),
            accounts[4].clone(),
        );

        // Build the account infos struct
        CreateTokenAccountCpi {
            payer: accounts[0].clone(),
            account: accounts[1].clone(),
            mint: accounts[2].clone(),
            owner: data.owner,
            compressible: Some(compressible_params),
        }
        .invoke()?;

        Ok(())
    }

    /// Handler for creating a compressible token account with PDA ownership (invoke_signed)
    ///
    /// Account order:
    /// - accounts[0]: payer (signer)
    /// - accounts[1]: account to create (PDA, will be derived and verified)
    /// - accounts[2]: mint
    /// - accounts[3]: compressible_config
    /// - accounts[4]: system_program
    /// - accounts[5]: rent_sponsor
    pub fn process_create_token_account_invoke_signed(
        accounts: &[AccountInfo],
        data: CreateTokenAccountData,
    ) -> Result<(), ProgramError> {
        if accounts.len() < 6 {
            return Err(ProgramError::NotEnoughAccountKeys);
        }

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

        // Verify the account to create is the PDA
        if &pda != accounts[1].key {
            return Err(ProgramError::InvalidSeeds);
        }

        // Build the compressible params using constructor
        let compressible_params = CompressibleParamsCpi::new(
            accounts[3].clone(),
            accounts[5].clone(),
            accounts[4].clone(),
        );

        // Build the account infos struct
        let account_cpi = CreateTokenAccountCpi {
            payer: accounts[0].clone(),
            account: accounts[1].clone(),
            mint: accounts[2].clone(),
            owner: data.owner,
            compressible: Some(compressible_params),
        };

        // Invoke with PDA signing
        let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]];
        account_cpi.invoke_signed(&[signer_seeds])?;

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

# Next Steps

<Card title="Learn how to mint tokens to light-token accounts" icon="chevron-right" color="#0066ff" href="/light-token/cookbook/mint-to" horizontal />
