SOL, Lamports & Units
Solana stores all native currency as lamports - unsigned 64-bit integers. Understanding denominations prevents rounding bugs, failed transactions, and incorrect fee calculations.
Search across all documentation pages
Solana stores all native currency as lamports - unsigned 64-bit integers. Understanding denominations prevents rounding bugs, failed transactions, and incorrect fee calculations.
Quick-reference recipe card - copy-paste ready.
import { lamports } from "@solana/kit";
const ONE_SOL = 1_000_000_000n;
const halfSol = lamports(500_000_000n);
function solToLamports(sol: number): bigint {
return BigInt(Math.round(sol * 1e9));
}
function lamportsToSol(lps: bigint): string {
return (Number(lps) / 1e9).toFixed(9);
}When to reach for this:
decimals (separate from native SOL)import { createSolanaRpc, address, lamports } from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
import {
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
pipe,
} from "@solana/kit";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
const sender = address("SENDER_PUBKEY");
const recipient = address("RECIPIENT_PUBKEY");
// User enters "0.25" SOL in a UI form
const userInputSol = 0.25;
const amountLamports = BigInt(Math.round(userInputSol * 1_000_000_000));
const { value: balance } = await rpc.getBalance(sender).send();
if (balance < amountLamports) {
throw new Error(
`Insufficient: have ${Number(lamports(balance)) / 1e9} SOL, need ${userInputSol}`,
);
}
const { value: blockhash } = await rpc.getLatestBlockhash().send();
const transferIx = getTransferSolInstruction({
source: sender,
destination: recipient,
amount: amountLamports,
});
const message = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayer(sender, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m),
(m) => appendTransactionMessageInstruction(transferIx, m),
);
console.log("Transfer message built for", amountLamports, "lamports");What this demonstrates:
bigint lamports before any on-chain comparisongetBalance returns lamports - compare integers, not floatslamports() helper wraps raw values for type clarity| Unit | Lamports | Typical Use |
|---|---|---|
| 1 lamport | 1 | Smallest native unit |
| 1 SOL | 1,000,000,000 | User-facing display |
| Priority fee | microlamports/CU | Compute Budget Program |
| Base fee | 5,000 lamports/signature | Per-signature cost (current default) |
use anchor_lang::prelude::*;
const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
pub fn transfer_amount(ctx: Context<Transfer>, sol: f64) -> Result<()> {
let lamports = (sol * LAMPORTS_PER_SOL as f64) as u64; // avoid in production
// Prefer: user supplies lamports directly, or use a fixed-point crate
require!(lamports > 0, ErrorCode::ZeroAmount);
Ok(())
}f64 to u64 in production financial code - use integer inputanchor_lang provides Lamports type alias in some contextschecked_add, checked_sub for balance arithmetic in programs0.1 + 0.2 !== 0.3 can produce wrong lamport amounts. Fix: parse user strings to bigint via integer math or a decimal library.Number.MAX_SAFE_INTEGER lose precision. Fix: format lamports as bigint or use a string-based formatter.mint.decimals before converting token amounts.getMinimumBalanceForRentExemption RPC and store the exact lamport value.| Alternative | Use When | Don't Use When |
|---|---|---|
bigint in TypeScript | All on-chain amount handling | You need legacy browser support without polyfills |
Decimal.js / bignumber.js | Parsing user decimal strings safely | Inside on-chain Rust programs |
| Integer lamports only (no SOL type) | Program instruction args | Building user-facing wallet UIs |
@solana/kit lamports() helper | Type-safe Kit pipelines | Raw RPC scripts where Kit is overkill |
1,000,000,000 (10^9). This is fixed and never changes.
Deterministic execution across all validators requires exact arithmetic. Floating point varies by hardware; u64 integers do not.
1 lamport. In practice, transaction fees (5,000 lamports per signature by default) make sub-lamport transfers meaningless.
function parseSolToLamports(input: string): bigint {
const [whole, frac = ""] = input.split(".");
const padded = (frac + "000000000").slice(0, 9);
return BigInt(whole) * 1_000_000_000n + BigInt(padded);
}Lamports. Divide by 1e9 only for display - never for on-chain instructions.
1 microlamport = 0.000001 lamports. Priority fees are priced in microlamports per compute unit via the Compute Budget Program.
Theoretically at ~18.4 billion SOL total supply it is not a practical concern, but program logic should still use checked arithmetic.
Based on account data size in bytes. RPC getMinimumBalanceForRentExemption(dataSize) returns the exact lamport deposit required.
Store lamports as integers (or strings for very large values). Convert to SOL only in the presentation layer.
The System Program rejects zero-lamport transfers. Use a meaningful minimum or skip the instruction.
No. SPL tokens use the mint's decimals field. A USDC amount of 1_000_000 means 1 USDC (6 decimals), not lamports.
Divide bigint lamports into whole and fractional parts with string math, or use a library. Avoid Number(lamports) for values you will re-submit on-chain.
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.
Reviewed by Chris St. John·Last updated Jul 16, 2026