Skip to main content

Best Practices

Follow these guidelines when building confidential Solana programs with Inco Lightning.

1. Use e_select for Conditional Logic

Never branch on decrypted values in your program. Use e_select to keep logic encrypted:
// BAD: Leaks information through control flow
if decrypted_balance > amount {
    transfer(amount);
}

// GOOD: Encrypted conditional
let sufficient = e_ge(ctx, balance, amount, 0)?;
let actual = e_select(ctx, sufficient, amount, zero, 0)?;
e_sub(ctx, balance, actual, 0)?;

2. Check Handle Initialization

Always verify handles are initialized before performing operations:
if !handle.is_initialized() {
    // Handle is zero/uninitialized
    return Err(ErrorCode::UninitializedHandle.into());
}

3. Grant Minimal Decryption Permissions

Only allow specific addresses to decrypt values they need:
// Only allow the account owner to decrypt their balance
allow(ctx, balance_handle, true, owner_pubkey)?;

4. Store Handles, Not Ciphertext

Handles are 16 bytes. Store them on-chain; ciphertext lives off-chain:
#[account]
pub struct Vault {
    pub owner: Pubkey,           // 32 bytes
    pub balance: Euint128,       // 16 bytes (handle only!)
    pub bump: u8,                // 1 byte
}

5. Handle Edge Cases with e_select

Use e_select to handle edge cases without revealing them:
// Ensure transfer doesn't exceed balance (clamp to max available)
let cpi_ctx = CpiContext::new(inco.clone(), Operation { signer: signer.clone() });
let exceeds_balance = e_gt(cpi_ctx, transfer_amount, balance, 0)?;

let cpi_ctx = CpiContext::new(inco.clone(), Operation { signer: signer.clone() });
let actual_transfer = e_select(cpi_ctx, exceeds_balance, balance, transfer_amount, 0)?;

Summary

PracticeRationale
Use e_select for conditionalsPrevents information leakage through control flow
Check handle initializationPrevents operations on invalid data
Minimal access grantsPrinciple of least privilege
Store handles onlyMinimize on-chain storage costs