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.
| Slug | sol |
| Address format | Base58 Ed25519 public key |
| Account path | m/44'/501'/{i}'/0' |
| Cloud stores | An address pool. No extended public key exists |
| Derivation | ❌ Edge only |
| Confirmations | finalized (about 32 slots) |
| Tokens | SPL |
| Explorer | https://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 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.
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.
| Account | Rent-exempt minimum |
|---|---|
| A basic account | 890,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.
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:
| Token | Mint | Decimals |
|---|---|---|
| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | 6 |
| USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB | 6 |
| PYUSD | 2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo | 6 |
| EURC | HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr | 6 |
| cNGN | 3jiqwBQVRC5zRwHyqvnkQurebJ5RNxg3F5fXMwaxgkv8 | 6 |
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.
ataRentLamportsappears 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 | |
|---|---|
baseFeeLamports | 5,000 per signature. Usually one signer |
computeUnits | Compute budget for the transaction |
priorityFeeMicroLamports | The tip, per compute unit |
ataRentLamports | Destination 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 anIdempotency-Keyso a retry of a transaction that did land does not send twice.