Skip to main content

Program Functions

This page documents all the on-chain functions available in the Confidential SPL Token program.

Mint Operations

initialize_mint

Creates a new confidential token mint.
pub fn initialize_mint(
    ctx: Context<InitializeMint>,
    decimals: u8,                    // Token precision (e.g., 9)
    mint_authority: Pubkey,          // Authority to mint tokens
    freeze_authority: Option<Pubkey> // Authority to freeze accounts
) -> Result<()>

mint_to

Mints confidential tokens to an account using encrypted ciphertext.
/// remaining_accounts:
///   [0] allowance_account (mut) - PDA for granting decrypt access
///   [1] owner_address (readonly) - The owner to grant access to
pub fn mint_to<'info>(
    ctx: Context<'_, '_, '_, 'info, IncoMintTo<'info>>,
    ciphertext: Vec<u8>,  // Encrypted amount
    input_type: u8        // Encryption type identifier
) -> Result<()>
Pass remaining_accounts to automatically grant decryption access to the owner for the new balance handle. The allowance PDA must be derived from [new_handle.to_le_bytes(), owner_address]. See Access Control for the simulation pattern to get the handle before the transaction.

Account Operations

initialize_account

Creates a new confidential token account.
pub fn initialize_account(ctx: Context<InitializeAccount>) -> Result<()>

create

Creates an associated token account using PDA derivation.
pub fn create(ctx: Context<Create>) -> Result<()>

create_idempotent

Creates an associated token account, succeeding silently if it already exists.
pub fn create_idempotent(ctx: Context<CreateIdempotent>) -> Result<()>
Use create_idempotent when you want to ensure the account exists without failing if it’s already created. This is useful for user-facing applications.

close_account

Closes a token account and reclaims the rent.
pub fn close_account(ctx: Context<CloseAccount>) -> Result<()>
The account must have a zero balance to be closed. Balance verification should be done client-side before calling this function.

Transfer Operations

transfer

Transfers confidential tokens between accounts using encrypted ciphertext.
/// remaining_accounts:
///   [0] source_allowance_account (mut) - PDA for source owner's new balance
///   [1] source_owner_address (readonly)
///   [2] dest_allowance_account (mut) - PDA for destination owner's new balance
///   [3] dest_owner_address (readonly)
pub fn transfer<'info>(
    ctx: Context<'_, '_, '_, 'info, IncoTransfer<'info>>,
    ciphertext: Vec<u8>,  // Encrypted transfer amount
    input_type: u8        // Encryption type identifier
) -> Result<()>
Pass remaining_accounts to grant decryption access to both source and destination owners for their new balance handles. Both source and destination get new handles after a transfer. See Access Control for the simulation pattern.

Delegation Operations

approve

Allows a delegate to spend tokens on behalf of the owner.
/// remaining_accounts:
///   [0] allowance_account (mut) - PDA for granting decrypt access to delegate
///   [1] delegate_address (readonly)
pub fn approve<'info>(
    ctx: Context<'_, '_, '_, 'info, IncoApprove<'info>>,
    ciphertext: Vec<u8>,  // Encrypted allowance amount
    input_type: u8        // Encryption type identifier
) -> Result<()>
Pass remaining_accounts to grant decryption access to the delegate for the delegated amount handle.

revoke

Revokes delegate permissions.
pub fn revoke(ctx: Context<IncoRevoke>) -> Result<()>

Burn Operations

burn

Burns (destroys) tokens from an account.
/// remaining_accounts:
///   [0] allowance_account (mut) - PDA for granting decrypt access to owner
///   [1] owner_address (readonly)
pub fn burn<'info>(
    ctx: Context<'_, '_, '_, 'info, IncoBurn<'info>>,
    ciphertext: Vec<u8>,  // Encrypted burn amount
    input_type: u8        // Encryption type identifier
) -> Result<()>
Pass remaining_accounts to grant decryption access to the owner for their new balance handle after burning.

Freeze/Thaw Operations

freeze_account

Freezes a token account, preventing any transfers.
pub fn freeze_account(ctx: Context<FreezeAccount>) -> Result<()>

thaw_account

Unfreezes a previously frozen token account.
pub fn thaw_account(ctx: Context<ThawAccount>) -> Result<()>
Only the freeze authority can freeze or thaw accounts. Frozen accounts cannot send or receive tokens until thawed.

Authority Management

set_mint_authority

Changes the mint authority.
pub fn set_mint_authority(
    ctx: Context<SetMintAuthority>,
    new_authority: Option<Pubkey>
) -> Result<()>
Setting the mint authority to None permanently disables minting. This action cannot be undone.

set_freeze_authority

Changes the freeze authority.
pub fn set_freeze_authority(
    ctx: Context<SetFreezeAuthority>,
    new_authority: Option<Pubkey>
) -> Result<()>

set_account_owner

Changes account ownership.
pub fn set_account_owner(
    ctx: Context<SetAccountOwner>,
    new_owner: Pubkey
) -> Result<()>

set_close_authority

Changes the close authority for an account.
pub fn set_close_authority(
    ctx: Context<SetCloseAuthority>,
    new_authority: Option<Pubkey>
) -> Result<()>

Account Structures

COption

A C-compatible option type used for optional fields:
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq)]
pub enum COption<T> {
    None,
    Some(T),
}

AccountState

Token account states:
#[repr(u8)]
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq)]
pub enum AccountState {
    Uninitialized = 0,
    Initialized = 1,
    Frozen = 2,
}

IncoMint

The mint account structure:
#[account]
pub struct IncoMint {
    /// Optional authority used to mint new tokens
    pub mint_authority: COption<Pubkey>,
    /// Total supply of tokens (encrypted)
    pub supply: Euint128,
    /// Number of base 10 digits to the right of the decimal place
    pub decimals: u8,
    /// Is `true` if this structure has been initialized
    pub is_initialized: bool,
    /// Optional authority to freeze token accounts
    pub freeze_authority: COption<Pubkey>,
}

impl IncoMint {
    pub const LEN: usize = 36 + 32 + 1 + 1 + 36; // 106 bytes
}

IncoAccount

The token account structure:
#[account]
pub struct IncoAccount {
    /// The mint associated with this account
    pub mint: Pubkey,
    /// The owner of this account
    pub owner: Pubkey,
    /// The amount of tokens this account holds (encrypted)
    pub amount: Euint128,
    /// If `delegate` is `Some` then `delegated_amount` represents
    /// the amount authorized by the delegate
    pub delegate: COption<Pubkey>,
    /// The account's state
    pub state: AccountState,
    /// If is_some, this is a native token, and the value logs
    /// the rent-exempt reserve
    pub is_native: COption<u64>,
    /// The amount delegated (encrypted)
    pub delegated_amount: Euint128,
    /// Optional authority to close the account
    pub close_authority: COption<Pubkey>,
}

impl IncoAccount {
    pub const LEN: usize = 32 + 32 + 32 + 36 + 1 + 12 + 32 + 36; // 213 bytes
}

Instruction Account Contexts

InitializeMint

#[derive(Accounts)]
pub struct InitializeMint<'info> {
    #[account(init, payer = payer, space = 8 + IncoMint::LEN)]
    pub mint: Account<'info, IncoMint>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
    /// Inco Lightning program for encrypted operations
    #[account(address = INCO_LIGHTNING_ID)]
    pub inco_lightning_program: AccountInfo<'info>,
}

InitializeAccount

#[derive(Accounts)]
pub struct InitializeAccount<'info> {
    #[account(init, payer = payer, space = 8 + IncoAccount::LEN)]
    pub account: Account<'info, IncoAccount>,
    #[account(constraint = mint.is_initialized @ CustomError::UninitializedState)]
    pub mint: Account<'info, IncoMint>,
    /// CHECK: This is just used for account initialization
    pub owner: UncheckedAccount<'info>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
    /// Inco Lightning program for encrypted operations
    #[account(address = INCO_LIGHTNING_ID)]
    pub inco_lightning_program: AccountInfo<'info>,
}

IncoMintTo

#[derive(Accounts)]
pub struct IncoMintTo<'info> {
    #[account(
        mut,
        constraint = mint.is_initialized @ CustomError::UninitializedState,
    )]
    pub mint: Account<'info, IncoMint>,
    #[account(
        mut,
        constraint = account.state == AccountState::Initialized @ CustomError::UninitializedState,
        constraint = account.mint == mint.key() @ CustomError::MintMismatch,
    )]
    pub account: Account<'info, IncoAccount>,
    #[account(mut)]
    pub mint_authority: Signer<'info>,
    /// Inco Lightning program for encrypted operations
    #[account(address = INCO_LIGHTNING_ID)]
    pub inco_lightning_program: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}
system_program is required when passing remaining_accounts for allowance operations, as the allowance PDA may need to be initialized.

IncoTransfer

#[derive(Accounts)]
pub struct IncoTransfer<'info> {
    #[account(
        mut,
        constraint = source.state == AccountState::Initialized @ CustomError::UninitializedState,
        constraint = source.state != AccountState::Frozen @ CustomError::AccountFrozen,
    )]
    pub source: Account<'info, IncoAccount>,
    #[account(
        mut,
        constraint = destination.state == AccountState::Initialized @ CustomError::UninitializedState,
        constraint = destination.state != AccountState::Frozen @ CustomError::AccountFrozen,
        constraint = destination.mint == source.mint @ CustomError::MintMismatch,
    )]
    pub destination: Account<'info, IncoAccount>,
    #[account(mut)]
    pub authority: Signer<'info>,
    /// Inco Lightning program for encrypted operations
    #[account(address = INCO_LIGHTNING_ID)]
    pub inco_lightning_program: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

IncoApprove

#[derive(Accounts)]
pub struct IncoApprove<'info> {
    #[account(
        mut,
        constraint = source.state == AccountState::Initialized @ CustomError::UninitializedState,
        constraint = source.state != AccountState::Frozen @ CustomError::AccountFrozen,
        constraint = source.owner == owner.key() @ CustomError::OwnerMismatch,
    )]
    pub source: Account<'info, IncoAccount>,
    /// CHECK: This is just stored as a delegate
    pub delegate: UncheckedAccount<'info>,
    #[account(mut)]
    pub owner: Signer<'info>,
    /// Inco Lightning program for encrypted operations
    #[account(address = INCO_LIGHTNING_ID)]
    pub inco_lightning_program: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

IncoRevoke

#[derive(Accounts)]
pub struct IncoRevoke<'info> {
    #[account(
        mut,
        constraint = source.state == AccountState::Initialized @ CustomError::UninitializedState,
        constraint = source.owner == owner.key() @ CustomError::OwnerMismatch,
    )]
    pub source: Account<'info, IncoAccount>,
    #[account(mut)]
    pub owner: Signer<'info>,
    /// Inco Lightning program for encrypted operations
    #[account(address = INCO_LIGHTNING_ID)]
    pub inco_lightning_program: AccountInfo<'info>,
}

IncoBurn

#[derive(Accounts)]
pub struct IncoBurn<'info> {
    #[account(
        mut,
        constraint = account.state == AccountState::Initialized @ CustomError::UninitializedState,
        constraint = account.state != AccountState::Frozen @ CustomError::AccountFrozen,
        constraint = account.mint == mint.key() @ CustomError::MintMismatch,
    )]
    pub account: Account<'info, IncoAccount>,
    #[account(
        mut,
        constraint = mint.is_initialized @ CustomError::UninitializedState,
    )]
    pub mint: Account<'info, IncoMint>,
    #[account(mut)]
    pub authority: Signer<'info>,
    /// Inco Lightning program for encrypted operations
    #[account(address = INCO_LIGHTNING_ID)]
    pub inco_lightning_program: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

FreezeAccount

#[derive(Accounts)]
pub struct FreezeAccount<'info> {
    #[account(
        mut,
        constraint = account.state == AccountState::Initialized @ CustomError::UninitializedState,
        constraint = account.mint == mint.key() @ CustomError::MintMismatch,
    )]
    pub account: Account<'info, IncoAccount>,
    #[account(constraint = mint.is_initialized @ CustomError::UninitializedState)]
    pub mint: Account<'info, IncoMint>,
    #[account(mut)]
    pub freeze_authority: Signer<'info>,
}

ThawAccount

#[derive(Accounts)]
pub struct ThawAccount<'info> {
    #[account(
        mut,
        constraint = account.state == AccountState::Frozen @ CustomError::InvalidState,
        constraint = account.mint == mint.key() @ CustomError::MintMismatch,
    )]
    pub account: Account<'info, IncoAccount>,
    #[account(constraint = mint.is_initialized @ CustomError::UninitializedState)]
    pub mint: Account<'info, IncoMint>,
    #[account(mut)]
    pub freeze_authority: Signer<'info>,
}

CloseAccount

#[derive(Accounts)]
pub struct CloseAccount<'info> {
    #[account(
        mut,
        constraint = account.state == AccountState::Initialized @ CustomError::UninitializedState,
    )]
    pub account: Account<'info, IncoAccount>,
    /// CHECK: Destination account that will receive remaining lamports
    #[account(mut)]
    pub destination: AccountInfo<'info>,
    #[account(mut)]
    pub authority: Signer<'info>,
}

SetMintAuthority

#[derive(Accounts)]
pub struct SetMintAuthority<'info> {
    #[account(
        mut,
        constraint = mint.is_initialized @ CustomError::UninitializedState,
    )]
    pub mint: Account<'info, IncoMint>,
    #[account(mut)]
    pub current_authority: Signer<'info>,
}

SetFreezeAuthority

#[derive(Accounts)]
pub struct SetFreezeAuthority<'info> {
    #[account(
        mut,
        constraint = mint.is_initialized @ CustomError::UninitializedState,
    )]
    pub mint: Account<'info, IncoMint>,
    #[account(mut)]
    pub current_authority: Signer<'info>,
}

SetAccountOwner

#[derive(Accounts)]
pub struct SetAccountOwner<'info> {
    #[account(
        mut,
        constraint = account.state == AccountState::Initialized @ CustomError::UninitializedState,
    )]
    pub account: Account<'info, IncoAccount>,
    #[account(mut)]
    pub current_owner: Signer<'info>,
}

SetCloseAuthority

#[derive(Accounts)]
pub struct SetCloseAuthority<'info> {
    #[account(
        mut,
        constraint = account.state == AccountState::Initialized @ CustomError::UninitializedState,
        constraint = account.owner == owner.key() @ CustomError::OwnerMismatch,
    )]
    pub account: Account<'info, IncoAccount>,
    #[account(mut)]
    pub owner: Signer<'info>,
}

Error Codes

#[error_code]
pub enum CustomError {
    #[msg("Lamport balance below rent-exempt threshold")]
    NotRentExempt,
    #[msg("Insufficient funds")]
    InsufficientFunds,
    #[msg("Invalid Mint")]
    InvalidMint,
    #[msg("Account not associated with this Mint")]
    MintMismatch,
    #[msg("Owner does not match")]
    OwnerMismatch,
    #[msg("Fixed supply. Token mint cannot mint additional tokens")]
    FixedSupply,
    #[msg("The account cannot be initialized because it is already being used")]
    AlreadyInUse,
    #[msg("Invalid number of provided signers")]
    InvalidNumberOfProvidedSigners,
    #[msg("Invalid number of required signers")]
    InvalidNumberOfRequiredSigners,
    #[msg("State is uninitialized")]
    UninitializedState,
    #[msg("Instruction does not support native tokens")]
    NativeNotSupported,
    #[msg("Non-native account can only be closed if its balance is zero")]
    NonNativeHasBalance,
    #[msg("Invalid instruction")]
    InvalidInstruction,
    #[msg("Invalid state")]
    InvalidState,
    #[msg("Operation overflowed")]
    Overflow,
    #[msg("Account does not support specified authority type")]
    AuthorityTypeNotSupported,
    #[msg("This token mint cannot freeze accounts")]
    MintCannotFreeze,
    #[msg("The account is frozen")]
    AccountFrozen,
    #[msg("The provided decimals value different from the Mint decimals")]
    MintDecimalsMismatch,
    #[msg("Instruction does not support non-native tokens")]
    NonNativeNotSupported,
}

Next Steps

See Deploy & Test for TypeScript examples and test patterns using these functions.