Lamports & Rent

Understanding Solana's economic model is crucial for building cost-effective applications. This chapter covers lamports (Solana's atomic unit) and the rent system that governs account storage.

What Are Lamports?

A lamport is the smallest unit of SOL, named after Leslie Lamport (computer scientist, Turing Award winner, creator of LaTeX and Paxos).

Text
UNIT CONVERSION
═══════════════

1 SOL = 1,000,000,000 lamports (1 billion)
1 lamport = 0.000000001 SOL

Common amounts:
├── 1 SOL      = 1,000,000,000 lamports
├── 0.1 SOL    = 100,000,000 lamports
├── 0.01 SOL   = 10,000,000 lamports
├── 0.001 SOL  = 1,000,000 lamports
└── 0.000001 SOL = 1,000 lamports (typical tx fee)

Comparison to other blockchains:
├── 1 SOL = 10^9 lamports
├── 1 ETH = 10^18 wei
└── 1 BTC = 10^8 satoshis

Working with Lamports

TypeScript
import { LAMPORTS_PER_SOL } from "@solana/web3.js";

// Constants
console.log("Lamports per SOL:", LAMPORTS_PER_SOL); // 1_000_000_000

// Conversions
function solToLamports(sol: number): number {
  return Math.floor(sol * LAMPORTS_PER_SOL);
}

function lamportsToSol(lamports: number): number {
  return lamports / LAMPORTS_PER_SOL;
}

// Examples
const oneSol = solToLamports(1); // 1_000_000_000
const halfSol = solToLamports(0.5); // 500_000_000
const backToSol = lamportsToSol(oneSol); // 1

// Safe handling of large numbers
const balance = BigInt(1_000_000_000_000); // 1000 SOL in lamports
const asNumber = Number(balance) / LAMPORTS_PER_SOL;

// Formatting for display
function formatSol(lamports: number | bigint): string {
  const sol = Number(lamports) / LAMPORTS_PER_SOL;
  return (
    sol.toLocaleString("en-US", {
      minimumFractionDigits: 2,
      maximumFractionDigits: 9,
    }) + " SOL"
  );
}

console.log(formatSol(2_500_000_000)); // "2.50 SOL"

The Rent System

Solana uses rent to manage blockchain storage. Every account must either pay rent or maintain a minimum balance (rent-exempt).

Why Rent Exists

Text
THE STORAGE PROBLEM
═══════════════════

Blockchains have unlimited growth:
├── Users create accounts forever
├── Each account costs validators storage
├── Storage costs add up
└── Eventually unsustainable

Ethereum's approach:
├── No rent (historically)
├── State bloat problem
├── State expiry proposals (complex)

Solana's approach:
├── Rent: Pay per byte per epoch
├── Rent-exemption: Deposit covers "forever"
├── Garbage collection: Accounts can be closed
└── Economic incentive to clean up

Rent Calculation

TypeScript
import { Connection } from "@solana/web3.js";

const connection = new Connection("https://api.mainnet-beta.solana.com");

// Current rent rate
// ~0.00000348 SOL per byte per year (as of 2024)
// Or ~6.96 lamports per byte per epoch

// Get minimum balance for rent exemption
async function getRentExemption(dataSize: number): Promise<number> {
  return await connection.getMinimumBalanceForRentExemption(dataSize);
}

// Examples
const sizes = [0, 100, 1000, 10000, 100000];

for (const size of sizes) {
  const lamports = await getRentExemption(size);
  console.log(`${size} bytes: ${lamports} lamports (${lamports / 1e9} SOL)`);
}

/*
Example output:
0 bytes: 890880 lamports (0.00089088 SOL)
100 bytes: 1447680 lamports (0.00144768 SOL)  
1000 bytes: 7986240 lamports (0.00798624 SOL)
10000 bytes: 72385920 lamports (0.07238592 SOL)
100000 bytes: 718080000 lamports (0.71808 SOL)
*/

Rent Formula

Text
RENT-EXEMPT MINIMUM FORMULA
═══════════════════════════

minimum_balance = (account_size + 128) × rent_rate × exemption_threshold

Where:
├── account_size: Bytes of data
├── 128: Account metadata overhead
├── rent_rate: Lamports per byte per epoch
└── exemption_threshold: ~2 years worth of rent

Current values (approximate):
├── rent_rate: ~3.48 lamports per byte per epoch
├── exemption_threshold: ~2 years = ~730 epochs
├── Per byte: ~2540 lamports (~5 lamports × 730 × factor)

Simplified:
minimum ≈ (data_size + 128) × 6960 lamports

Rent Exemption

Most Solana accounts are rent-exempt, meaning they have enough lamports to cover 2+ years of rent.

TypeScript
// Check if account is rent-exempt
async function isRentExempt(
  connection: Connection,
  address: PublicKey,
): Promise<{
  isExempt: boolean;
  balance: number;
  required: number;
  deficit: number;
}> {
  const account = await connection.getAccountInfo(address);

  if (!account) {
    throw new Error("Account not found");
  }

  const required = await connection.getMinimumBalanceForRentExemption(
    account.data.length,
  );

  const isExempt = account.lamports >= required;
  const deficit = isExempt ? 0 : required - account.lamports;

  return {
    isExempt,
    balance: account.lamports,
    required,
    deficit,
  };
}

// Example usage
const result = await isRentExempt(connection, myAccountAddress);
if (!result.isExempt) {
  console.log(`Account needs ${result.deficit} more lamports`);
}

What Happens Without Rent Exemption

Text
RENT COLLECTION SCENARIO (RARE)
═══════════════════════════════

Epoch 100: Account created with 500,000 lamports (not enough)
           Data size: 100 bytes
           Rent per epoch: ~700 lamports

Epoch 101: Rent collected
           Balance: 500,000 - 700 = 499,300 lamports

Epoch 200: After 100 epochs
           Balance: 500,000 - 70,000 = 430,000 lamports

Epoch 714: Balance reaches zero
           Account is GARBAGE COLLECTED
           Data is LOST forever

PREVENTION:
├── Always make accounts rent-exempt
├── Check minimum before creating
└── Add buffer for safety

Creating Rent-Exempt Accounts

TypeScript
import {
  Connection,
  Keypair,
  PublicKey,
  SystemProgram,
  Transaction,
  sendAndConfirmTransaction,
} from "@solana/web3.js";

async function createRentExemptAccount(
  connection: Connection,
  payer: Keypair,
  space: number,
  programId: PublicKey,
): Promise<Keypair> {
  // 1. Calculate required lamports
  const lamports = await connection.getMinimumBalanceForRentExemption(space);

  // 2. Generate new account keypair
  const newAccount = Keypair.generate();

  // 3. Create instruction
  const createAccountIx = SystemProgram.createAccount({
    fromPubkey: payer.publicKey,
    newAccountPubkey: newAccount.publicKey,
    lamports,
    space,
    programId,
  });

  // 4. Send transaction
  const transaction = new Transaction().add(createAccountIx);
  await sendAndConfirmTransaction(connection, transaction, [payer, newAccount]);

  console.log(`Created account: ${newAccount.publicKey.toBase58()}`);
  console.log(`Allocated: ${space} bytes`);
  console.log(`Deposited: ${lamports} lamports (${lamports / 1e9} SOL)`);

  return newAccount;
}

Closing Accounts (Recovering Rent)

When you no longer need an account, you can close it and recover the lamports:

TypeScript
// Closing an account recovers all lamports
async function closeAccount(
  connection: Connection,
  account: PublicKey,
  destination: PublicKey,
  owner: Keypair,
  programId: PublicKey,
): Promise<string> {
  // The program must implement close functionality
  // This is a conceptual example - actual implementation varies by program

  const closeIx = new TransactionInstruction({
    keys: [
      { pubkey: account, isSigner: false, isWritable: true },
      { pubkey: destination, isSigner: false, isWritable: true },
      { pubkey: owner.publicKey, isSigner: true, isWritable: false },
    ],
    programId,
    data: Buffer.from([
      /* close instruction discriminator */
    ]),
  });

  const transaction = new Transaction().add(closeIx);
  return await sendAndConfirmTransaction(connection, transaction, [owner]);
}

Anchor Close Pattern

Rust
// In Anchor, closing is straightforward
#[derive(Accounts)]
pub struct CloseAccount<'info> {
    #[account(
        mut,
        close = destination,  // Close and send lamports here
        has_one = authority,  // Verify owner
    )]
    pub account_to_close: Account<'info, MyData>,

    /// CHECK: Receives lamports
    #[account(mut)]
    pub destination: AccountInfo<'info>,

    pub authority: Signer<'info>,
}

// When this instruction runs:
// 1. Account data is zeroed
// 2. All lamports transferred to destination
// 3. Account effectively deleted (will be garbage collected)

Cost Optimization Strategies

1. Right-Size Accounts

TypeScript
// BAD: Over-allocating
const wastefulAccount = await createAccount({
  space: 10000, // 10KB when you only need 100 bytes
  // Costs: ~0.07 SOL
});

// GOOD: Exact sizing
interface MyData {
  counter: bigint; // 8 bytes
  owner: PublicKey; // 32 bytes
  active: boolean; // 1 byte
}
const EXACT_SIZE = 8 + 32 + 1; // 41 bytes
// Costs: ~0.001 SOL

// Even better with discriminator (Anchor adds 8-byte discriminator)
const ANCHOR_SIZE = 8 + 41; // 49 bytes

2. Reuse Accounts

Rust
// Instead of creating new accounts for each operation,
// reuse accounts by resetting their state

pub fn reset_account(ctx: Context<ResetAccount>) -> Result<()> {
    let account = &mut ctx.accounts.my_account;

    // Reset to initial state instead of closing and recreating
    account.counter = 0;
    account.data = [0u8; 32];
    account.active = false;

    Ok(())
}

3. Account Compression

For large datasets, consider compression:

TypeScript
import * as zlib from "zlib";

// Compress before storing
function compressData(data: Buffer): Buffer {
  return zlib.gzipSync(data);
}

// Decompress when reading
function decompressData(compressed: Buffer): Buffer {
  return zlib.gunzipSync(compressed);
}

// Note: Adds CPU cost, reduces storage cost
// Good for large, infrequently accessed data

4. Off-Chain Storage

TypeScript
// Store large data off-chain, reference on-chain
interface OnChainReference {
  // Store hash of data on-chain (32 bytes)
  dataHash: Uint8Array;

  // Store pointer to off-chain location
  arweaveId: string; // Or IPFS CID, etc.

  // Timestamp for verification
  updatedAt: bigint;
}

// On-chain: ~100 bytes
// Off-chain: Unlimited

// Verification
async function verifyOffChainData(
  onChainRef: OnChainReference,
  offChainData: Buffer,
): Promise<boolean> {
  const hash = sha256(offChainData);
  return Buffer.compare(hash, Buffer.from(onChainRef.dataHash)) === 0;
}

Transaction Fees vs Rent

Don't confuse transaction fees with rent:

Text
TRANSACTION FEES
═══════════════
├── Paid per transaction
├── Based on signatures (5000 lamports per sig)
├── Plus priority fee (optional)
├── Covers computation
└── 50% burned, 50% to validator

RENT
════
├── Paid for storage space
├── Based on bytes stored
├── One-time deposit (rent-exempt)
├── Recoverable when closing
└── Prevents state bloat

EXAMPLE TRANSACTION:
├── Base fee: 5000 lamports (0.000005 SOL)
├── Priority fee: 10000 lamports (optional)
├── Rent for new account: 1447680 lamports
└── Total: ~1,462,680 lamports (~0.0015 SOL)

Monitoring Account Economics

TypeScript
// Comprehensive account cost analysis
async function analyzeAccountCosts(
  connection: Connection,
  address: PublicKey,
): Promise<{
  address: string;
  size: number;
  balance: number;
  rentExemptMin: number;
  isExempt: boolean;
  storageEfficiency: number;
  estimatedAnnualRent: number;
}> {
  const account = await connection.getAccountInfo(address);

  if (!account) {
    throw new Error("Account not found");
  }

  const rentExemptMin = await connection.getMinimumBalanceForRentExemption(
    account.data.length,
  );

  // Calculate storage efficiency (actual data vs zeros)
  const nonZeroBytes = account.data.filter((b) => b !== 0).length;
  const storageEfficiency = nonZeroBytes / account.data.length;

  // Estimate annual rent if not exempt
  const epochsPerYear = 365 * 2; // ~730 epochs
  const rentPerEpoch = account.data.length * 3.48; // lamports
  const estimatedAnnualRent = rentPerEpoch * epochsPerYear;

  return {
    address: address.toBase58(),
    size: account.data.length,
    balance: account.lamports,
    rentExemptMin,
    isExempt: account.lamports >= rentExemptMin,
    storageEfficiency,
    estimatedAnnualRent,
  };
}

Key Takeaways

  1. 1 SOL = 1 billion lamports - always work in lamports for precision
  2. Rent-exemption is standard - deposit ~2 years of rent upfront
  3. Minimum balance scales with size - ~0.007 SOL per KB
  4. Closing accounts recovers lamports - always clean up unused accounts
  5. Right-size accounts - don't over-allocate storage

Next: Owner & Authority - Understanding account permissions and control.