Solana Lamports and SOL Units: Decimal Math for On-Chain Programs
The first Solana program I shipped to mainnet got bricked by a single off-by-one lamport. Our fee estimator returned a SOL value, the frontend rounded it to two decimals, and the backend re-multiplied by 10⁹ to send the transaction. The transaction underpaid the fee by exactly one lamport and silently dropped. It took half a day to track down because every individual line of code looked sensible. That afternoon convinced me that Solana's decimal math deserves the same attention as Ethereum's wei arithmetic — and a much shorter blog post explaining it would have saved me the trouble.
Lamports: Solana's Atomic Unit
On Solana, the protocol's native asset is SOL, but every on-chain balance, fee, and transfer amount is denominated in lamports. The relationship is fixed:
1 SOL = 1,000,000,000 lamports = 10^9 lamports
1 lamport = 0.000000001 SOLThe name comes from Leslie Lamport, the distributed systems researcher whose work underpins many of Solana's consensus ideas. Mechanically, lamports are unsigned 64-bit integers in the runtime — a balance is a u64, a transfer instruction takes a u64, and any time you say "0.5 SOL" you really mean "500,000,000 lamports." The chain has no concept of fractional lamports; everything is integer arithmetic on u64.
Mental model: SOL is the human-facing label, lamports are the on-chain reality. Treat the two like dollars and cents — never store a balance as a floating-point SOL value, only convert to SOL at the very last step before showing it to a user.
The JavaScript Number Trap
JavaScript's Number is a 64-bit IEEE 754 float. It can represent integers exactly only up to Number.MAX_SAFE_INTEGER = 2⁵³ − 1 ≈ 9.007 × 10¹⁵. Above that, integer arithmetic silently loses precision.
For Solana that boundary matters: a balance of 9,007,199 SOL is the last value where the lamport count (≈ 9 × 10¹⁵) fits in a safe integer. Realistic user balances are nowhere near that, but treasury wallets, exchange hot wallets, and circulating supply totals can be. The protocol enforces no soft cap; if your dashboard sums many balances and stores the result in a Number, you will lose lamports.
Use BigInt for Lamport Math
// 안전한 lamport 산술
const LAMPORTS_PER_SOL = 1_000_000_000n; // BigInt 리터럴(n)
function solToLamports(sol: number): bigint {
// 부동소수점 손실 가능: 입력은 사용자 UI에서만 받고
// 검증된 양수·정수 단위 자릿수로 제한할 것
const scaled = Math.round(sol * 1e9);
return BigInt(scaled);
}
function lamportsToSolString(lamports: bigint, decimals = 9): string {
const sign = lamports < 0n ? '-' : '';
const abs = lamports < 0n ? -lamports : lamports;
const whole = abs / LAMPORTS_PER_SOL;
const frac = (abs % LAMPORTS_PER_SOL).toString().padStart(decimals, '0');
return `${sign}${whole.toString()}.${frac}`;
}The @solana/web3.js library exposes LAMPORTS_PER_SOL as a plain Number constant for convenience, but the underlying RPC returns balances as a string or BigInt in newer versions. Stay in BigInt as long as you can.
Anchor and Rust: u64 Arithmetic Patterns
On the program side, Anchor (and raw Solana programs) use Rust's u64 for lamport amounts. Rust panics on overflow in debug builds and wraps silently in release builds — neither behavior is what you want on-chain. Use the checked variants:
// 안전한 lamport 합산 — 모든 산술에서 overflow 명시 처리
let new_balance = current_balance
.checked_add(deposit)
.ok_or(ErrorCode::Overflow)?;
let fee = amount
.checked_mul(fee_bps)
.and_then(|n| n.checked_div(10_000))
.ok_or(ErrorCode::Overflow)?;
// SOL 단위 변환은 클라이언트 책임 — 프로그램은 lamports만 다룬다A common idiom in Anchor programs: define an ErrorCode enum with explicit overflow variants and chain checked_* operations with .ok_or(...)?. This produces a clean transaction failure on overflow rather than a silent wrap.
Transaction Fees: Base + Priority + Compute
Solana fees are not a single number. They are the sum of three components:
- Base fee — 5,000 lamports per signature. Most transactions have one signature, some have several (multisig, fee payer separation).
- Priority fee — optional micro-lamports per compute unit, set by
ComputeBudgetProgram.setComputeUnitPrice. Front-runs lower-priority transactions in congestion. - Compute unit budget — the maximum CUs your transaction can consume, set by
ComputeBudgetProgram.setComputeUnitLimit. Default is 200,000; complex programs may need 400,000+.
총 fee (lamports) = num_signatures * 5_000
+ (compute_unit_price * compute_unit_limit) / 10^6
// 예: signature 1개, 1 micro-lamport/CU, 200_000 CU
// = 1 * 5_000 + (1 * 200_000) / 1_000_000
// = 5_000 + 0.2 ≈ 5_000 lamports (priority 무시 가능 수준)
// 예: signature 1개, 50_000 micro-lamports/CU, 400_000 CU
// = 5_000 + (50_000 * 400_000) / 1_000_000
// = 5_000 + 20_000 = 25_000 lamportsTwo takeaways. First, 1 micro-lamport = 10⁻⁶ lamport — the priority fee unit is a millionth of a lamport, which makes the math weird until you write it out. Second, set both the unit price andthe unit limit deliberately; libraries that auto-bump priority without raising the limit can produce transactions that fail with "ExceededComputeUnitLimit" while paying full priority anyway.
Rent: The Account Storage Tax
Solana accounts have to either pay rent per epoch or hold enough lamports to be rent-exempt. In practice every modern program seeds new accounts with the rent-exempt minimum, so "rent" for application developers usually means "the lamports I have to deposit when I create an account."
// JS 클라이언트
const space = 165; // 토큰 계정 크기 (bytes)
const lamports = await connection.getMinimumBalanceForRentExemption(space);
// → 약 2_039_280 lamports (≈ 0.00204 SOL) — 데이터 크기에 비례
// Anchor 프로그램에서는 #[account(init, payer = user, space = N)]가
// 자동으로 rent-exempt minimum을 계산해 transfer 한다Rent-exempt minimums are roughly proportional to space: a 165-byte SPL token account is around 2 million lamports; a 10 KB account is around 70 million. Closing an account returns its lamports to the destination of your choice, so rent is recoverable — but UX-wise it matters that opening a position costs real SOL upfront.
Address Format: Base58 + Curve Membership
A Solana address is a 32-byte Ed25519 public key encoded as Base58. That gives a roughly 32–44 character string of mixed case alphanumerics, no zero or capital O, no capital I, no lowercase l (the standard Bitcoin-style Base58 alphabet).
Validation has two distinct levels:
- Format check — Base58 decodes to exactly 32 bytes. Cheap, catches typos, accepts random 32-byte values that may not be valid public keys.
- Curve check — the 32-byte value is actually a point on Curve25519. Real wallet addresses always pass this. Program Derived Addresses (PDAs) are deliberately off-curve so they cannot be signed for.
For UI input validation, Base58-decode + 32-byte length is the right level. For protocol-level checks (e.g., "is this a wallet address?"), add the curve check via PublicKey.isOnCurve(bytes).
PDAs Are Different
A Program Derived Address is computed by hashing seeds + a program ID until the result falls off the Ed25519 curve. By construction, no private key exists for a PDA, which means the program itself signs on its behalf via invoke_signed. If you accept "any 32-byte value" as an address, you implicitly accept PDAs — usually fine, but be aware in security-critical paths.
SPL Tokens: Decimals Live on the Mint
Lamports apply to SOL only. SPL tokens (USDC, BONK, your favorite memecoin) carry their own decimals field stored on the mint account. The same balance value means different things across tokens:
| Token | decimals | 1 unit (raw) | Display |
|---|---|---|---|
| SOL (native) | 9 | 1,000,000,000 lamports | 1 SOL |
| USDC | 6 | 1,000,000 base units | 1 USDC |
| BONK | 5 | 100,000 base units | 1 BONK |
| JUP | 6 | 1,000,000 base units | 1 JUP |
Always read the mint's decimals at runtime; do not assume USDC is 6 decimals forever (different chains use different mints with different decimals for the same ticker). Indexers and wallets that hardcode decimals get embarrassing display bugs.
Common Pitfalls
Floating-point SOL math
0.1 + 0.2 !== 0.3 in JavaScript. Sum a list of SOL amounts in floats and your total will drift by lamports per item. Always sum in lamports (BigInt) and convert once at display.
Mixing wei and lamports semantics
Engineers coming from Ethereum often assume Solana follows wei conventions. They are related (both are smallest-unit integers) but Ethereum's wei is 10⁻¹⁸ ETH while Solana's lamport is 10⁻⁹ SOL — nine orders of magnitude difference. Conversion helpers from one chain are not safe to reuse on the other.
Losing the rent on close
When closing an SPL token account, you must specify a destination for the lamports. Some SDK helpers default to the owner; others require explicit specification. Forgetting the argument can leave the lamports stranded in a deleted account — recoverable, but annoying.
Never: trust client-supplied SOL amounts on the server side without re-deriving lamports from a controlled source (oracle, on-chain price, etc.). Phishing flows that send a transaction expecting 0.01 SOL but encoding 1 SOL of lamports rely exactly on this trust.
Try It with BeautiCode Tools
Hand-converting lamports to SOL and validating addresses is one of those tasks that is easier with a calculator next to you. All tools below run in your browser; nothing is sent over the network.
- Solana Lamports Converter — switch instantly between lamports and SOL with full 9-decimal precision. Useful for verifying values pulled from
getBalanceRPC responses. - Solana Address Validator — check that a Base58 string is a well-formed 32-byte public key and whether it sits on Curve25519 (i.e., is a wallet vs a PDA).
- Multi-Chain Address Validator — auto-detects whether a string is a Solana, Ethereum, Bitcoin, or Cosmos address. Good first-pass check before parsing.
- Anchor IDL Decoder — decode an Anchor program's IDL to understand instruction parameters, accounts, and the lamport amounts they expect.
Frequently Asked Questions
How is a lamport different from an Ethereum wei?
Both are the smallest indivisible unit of their native asset, but the scale differs by nine orders of magnitude. 1 ETH = 10¹⁸ wei, 1 SOL = 10⁹ lamports. That makes lamport math much more likely to fit in a JavaScript safe integer for individual user balances, though aggregate sums can still overflow.
Why is the priority fee unit micro-lamports?
It is essentially a fixed-point trick: priority is set per compute unit, and most priority values would round to zero in whole lamports. Using micro-lamports (10⁻⁶ lamport) lets the protocol express small per-CU priorities while keeping the underlying arithmetic in unsigned integers.
Can I send fractional lamports?
No. Lamports are u64on chain. Anything that displays a fractional lamport (e.g., a UI showing "0.5 lamports" for a per-CU priority) is a derived display value, not a transferable amount.
How do I get rent back when an account is closed?
Close instructions take a destination account that receives the lamports held by the account being closed. For SPL token accounts, use closeAccount with the owner as destination. For Anchor accounts, the #[account(close = receiver)] constraint handles it for you.
Will the Solana decimals or unit names ever change?
The 9-decimal lamport unit is baked into the Solana protocol — changing it would be a hard fork on the order of a Bitcoin denomination change (i.e., effectively never). SPL token decimals are per-mint and immutable after mint creation. Treat the lamport count and the mint's decimals as protocol-level constants.
Related Tools
Solana Lamports Converter
Convert between lamports and SOL with precise arithmetic. Works with transaction fees and account rent.
Solana Address Validator
Validate Solana Base58 addresses and detect whether it is a program ID, PDA, or standard wallet key.
Multi-chain Address Validator
Auto-detect and validate BTC, ETH, SOL, Cosmos and dozens of other blockchain addresses in one go.
Anchor IDL Decoder
Paste a Solana Anchor IDL JSON and browse instructions, accounts, types, and PDA seeds interactively.
Related Articles
How to Generate Secure Passwords in 2026: A Complete Guide
Learn why strong passwords matter and how to generate secure passwords using entropy, length, and complexity. Includes practical tips and free tools.
2025-12-15 · 8 min readData FormatsJSON vs YAML: When to Use What — A Developer's Guide
Compare JSON and YAML formats with syntax examples, pros and cons, and use case recommendations for APIs, configs, and CI/CD pipelines.
2025-12-28 · 10 min read