Skip to main content

Solana

One of the two chains where Cloud cannot derive addresses. Everything here follows from one fact about Ed25519, and Stellar follows from the same one.

Slugsol
Address formatBase58 Ed25519 public key
Account pathm/44'/501'/{i}'/0'
Cloud storesAn address pool. No extended public key exists
DerivationEdge only
Confirmationsfinalized (about 32 slots)
TokensSPL
Explorerhttps://solscan.io/tx/{txid} · https://solscan.io/account/{address}

Why there is a pool

Ed25519 has no public-key-only child derivation. There is no such thing as a Solana extended public key — not as a limitation of this implementation, but as a property of the curve. Cloud therefore cannot derive Solana addresses at all, no matter what it holds.

So the Edge does it:

The Edge derives a batch of addresses and keeps the private keys; Cloud holds the public addresses as a pool, allocates one per customer, and asks for a refill when the pool falls below 20 percent.

The Edge derives m/44'/501'/{i}'/0' for i in [start, start + EDGE_POOL_SIZE) and sends only the resulting addresses. Cloud allocates from the pool with SELECT … FOR UPDATE SKIP LOCKED.

SKIP LOCKED is what lets a hundred concurrent allocations produce a hundred distinct addresses without serialising the table.

When the pool drops below 20% the readiness indicator turns orange (pool_low) and Cloud asks the Edge for more over the open gRPC stream. Allocation keeps working while that happens.

Solana address issuance needs a live Edge

If the pool is exhausted and no Edge is connected, POST /v1/addresses for sol fails with 423 EDGE_OFFLINE.

That is deliberate. The alternative — issuing an address nobody holds a key for — would take a customer's deposit into a void.

The six secp256k1 chains do not have this dependency: Cloud derives their addresses from stored extended public keys with no Edge involvement. Stellar shares it, for the same reason.

EDGE_POOL_SIZE defaults to 1000 per pooled chain and only the leader replica derives. If you issue Solana addresses in bursts, raise it — the cost is a slightly longer first sync and some rows in a table.

Confirmations

finalized, not a number. Solana's commitment levels are processed, confirmed and finalized, and finality is deterministic at roughly 32 slots — about thirteen seconds.

deposit_detected fires at confirmed. deposit_confirmed fires at finalized. The webhook's threshold field carries the string finalized rather than an integer, which is the one place the event shape differs across chains.

Rent exemption — the floor on every balance

Solana charges rent for account storage, and an account holding less than the rent-exempt minimum can be collected by the runtime. Not "will be charged a fee" — deleted, with its lamports reclaimed.

AccountRent-exempt minimum
A basic account890,880 lamports (≈ 0.00089 SOL)
An associated token account (ATA)2,039,280 lamports each

ChainOS reports that floor as reserved:

{ "confirmed": "1890880", "available": "1000000", "reserved": "890880", "decimals": 9 }

Read available. A send that would take the balance below the floor is refused with INSUFFICIENT_RESERVE rather than succeeding and having the runtime collect the account afterwards.

This was a real defect, fixed

An earlier revision reported available == confirmed with nothing reserved, which would let a caller drain an account below rent-exemption. The floor is now reported the same way XRP's reserve is, and the pre-flight refuses the send.

The figures above are current network parameters, not protocol invariants. They are set by the rent configuration and have changed before.

SPL tokens and the ATA

Monitored contracts (mint addresses) on mainnet:

TokenMintDecimals
USDCEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v6
USDTEs9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB6
PYUSD2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo6
EURCHzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr6
cNGN3jiqwBQVRC5zRwHyqvnkQurebJ5RNxg3F5fXMwaxgkv86

Solana does not hold token balances on the wallet account. Each (wallet, mint) pair has its own associated token account, and that ATA has its own 2,039,280-lamport rent deposit.

Consequences that matter:

  • A customer's first USDC deposit costs 2,039,280 lamports of rent, paid by whoever creates the ATA. Usually the sender's wallet does it automatically. If it does not, the deposit fails at the sender's end rather than arriving.
  • Two tokens at one address means two ATAs, so two rent deposits.
  • ataRentLamports appears in the fee estimate when a send requires creating a destination ATA. That is not a network fee — it is a deposit that stays with the account and is reclaimed if the account is closed. Do not present it to a customer as a fee.
  • Closing an ATA reclaims its rent. Worth doing during a sweep if you are consolidating a customer's token holdings permanently, and not worth doing if they will deposit again.

Fees

fee = 5000 × signers + (computeUnits × priorityFee) / 1e6 + rent
Field
baseFeeLamports5,000 per signature. Usually one signer
computeUnitsCompute budget for the transaction
priorityFeeMicroLamportsThe tip, per compute unit
ataRentLamportsDestination ATA creation, if required. A deposit, not a fee

Base fees are trivially small. Priority fees are what move during congestion, and unlike an EVM chain they are the only lever — a transaction without a competitive priority fee is simply not included, rather than being included late.

Solana has a fee-payer field permitting a third account to pay, and ChainOS does not use it. The sending address pays its own fee, on every chain, because building the product around the single chain that permits an exception would be a poor trade. See Withdrawals.

Operational notes

  • The pool is the thing to watch. Orange readiness means it is under 20%. It replenishes automatically while a leader is connected; a fleet with no leader does not replenish. See High availability.
  • Addresses are Base58 and case-sensitive. No checksum in the format itself — validation checks that the decoded bytes are a valid 32-byte Ed25519 public key on the curve, which catches most typos but not all. Validate before sending.
  • The Edge derives at a hardened path per index (m/44'/501'/{i}'/0'), which is why it and only it can produce these addresses. There is no branch structure to walk publicly.
  • Solana is one of the six chains with no configured RPC endpoint in the current deployment. Derivation and pool allocation work — they need no RPC — while balances, fees and broadcast return 503 CHAIN_UNAVAILABLE.
  • A dropped transaction is normal on Solana in a way it is not elsewhere: a transaction not included before its blockhash expires simply vanishes rather than sitting in a mempool. It surfaces as withdrawal_failed, and retrying is correct — but retry with an Idempotency-Key so a retry of a transaction that did land does not send twice.