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.