Cronos RPC: A Developer's Guide
Cronos is Crypto.com's EVM chain: Solidity contracts, eth_* methods, MetaMask, viem and ethers all work unchanged. Underneath, it is a Cosmos SDK chain with an EVM module, and that shows up in exactly the places where Ethereum habits break: how fast blocks arrive, how much history a node keeps, and what the error messages look like when you ask for something it has already thrown away.
Plenty of documentation still describes Cronos as a 5–6 second chain. Our own chain page said so until this post. The chain I measured today produces a block roughly every 0.42 seconds, which changes how you should poll, index and cache.
Everything below was measured on 2026-09-23 through https://rpc.swiftnodes.io/rpc/cronos. Re-run any of it.
The essentials
| Chain ID | 25 (0x19); net_version also returns 25 |
| Native token | CRO, 18 decimals |
| Block time | ~0.42 s: 0.410 s over 100 blocks, 0.422 over 1,000, 0.419 over 10,000, 0.421 over 100,000, 0.428 over 1,000,000 |
| Head at time of writing | 95,687,161 (2026-09-23 14:03:29 UTC) |
| Finality | latest = safe = finalized, in every batched read |
| Block gas limit | 60,000,000 |
| Base fee | 375 gwei, flat across the last 11 blocks; eth_maxPriorityFeePerGas 3.75 gwei |
eth_getLogs range |
10,000 blocks (about 70 minutes of chain time), and an address is required |
| Historical state | ~100 blocks (under a minute) |
debug_* / trace_* |
Not available (-32601) |
| WebSocket | eth_subscribe("newHeads") works |
| RPC | https://rpc.swiftnodes.io/rpc/cronos?key=YOUR_API_KEY |
Connecting
Any EVM client works. viem does not need a custom chain definition, because it ships cronos:
import { createPublicClient, http, webSocket } from "viem";
import { cronos } from "viem/chains";
const client = createPublicClient({
chain: cronos,
transport: http("https://rpc.swiftnodes.io/rpc/cronos?key=YOUR_API_KEY"),
});
console.log(await client.getChainId()); // 25
console.log(await client.getBlockNumber()); // 95687161n at time of writing
curl -s -X POST "https://rpc.swiftnodes.io/rpc/cronos?key=YOUR_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
The two token contracts most integrations touch first, both read back with eth_call today:
| Token | Address | decimals() |
|---|---|---|
| WCRO | 0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23 |
18 |
| USDC | 0xc21223249CA28397B4B6541dfFaEcC539BfF0c59 |
6 |
USDC's 6 decimals is the usual trap for code that assumes 18 on an EVM chain.
0.42-second blocks change your polling math
At ~0.42 s per block, Cronos produces roughly 205,000 blocks per day. A million blocks is under five days. Three consequences:
- Polling
eth_blockNumberevery second skips blocks. Each poll sees the head move by two or three. That's fine if you only need the tip. If you process every block, fetch ranges (fromBlock→toBlock), not "the next one". - Block-count thresholds from Ethereum are far too short here. "Wait 12 blocks" is five seconds on Cronos. If you express staleness, cache TTLs or confirmation UX in blocks, convert them from seconds first.
- Subscriptions beat polling. Over WebSocket,
newHeadsdelivered 10 consecutive heads in 4.4 seconds after subscribing, the first one 675 ms in:
const ws = createPublicClient({
chain: cronos,
transport: webSocket("wss://rpc.swiftnodes.io/ws/cronos?key=YOUR_API_KEY"),
});
ws.watchBlocks({
onBlock: (b) => console.log(b.number, b.transactions.length),
});
Handle reconnects without losing events. At this block rate, a 30-second disconnect is about 70 blocks. The pattern is in WebSocket reconnects without losing events.
Finality: there is nothing to wait for
Cronos uses CometBFT-style consensus, so a committed block is final. I read latest, safe and finalized in a single JSON-RPC batch three times, so all three came from the same instant. They returned the same height every time (95,687,363, then 95,687,365, then 95,687,367).
You can drop reorg handling written for probabilistic chains. Keep a sanity check anyway — the reasoning is in handling chain reorgs in an indexer — but you do not need a confirmation depth.
Gas: flat base fee, near-empty blocks
Over the last 10 blocks, eth_feeHistory returned a base fee of exactly 375 gwei for every block. The highest gasUsedRatio was 0.0083 (0.83% of the 60M gas limit), and several blocks were empty. eth_gasPrice returned 378.75 gwei, which is the base fee plus the 3.75 gwei suggested tip.
Blocks are this empty, so the tip is not buying you anything. A real type-2 transaction I pulled from the chain paid an effective gas price of 375,000,000,001 wei: the base fee plus a 1-wei tip. Basic cost figures:
| Operation | Gas (measured) | Cost at 375 gwei |
|---|---|---|
| Plain CRO transfer | 21,000 (eth_estimateGas → 0x5208) |
0.007875 CRO |
| Sample contract call (from chain) | 127,908 | ~0.048 CRO |
If you hard-code gas prices from Ethereum-mainnet habits (a few gwei), your transactions will never be included, because the base fee here is 375 gwei. For the general method, see estimating gas with eth_estimateGas and EIP-1559.
eth_getLogs: 10,000 blocks, address required
I queried USDC Transfer events over growing ranges:
| Range (blocks) | Result |
|---|---|
| 100 | 4 events, 158 ms |
| 1,000 | 41 events, 359 ms |
| 5,000 | 217 events, 369 ms |
| 10,000 | 413 events, 1,016 ms |
| 50,000 | maximum [from, to] blocks distance: 10000 |
| 100,000 | -32701 exceed maximum block range: 50000 |
Two different limits came back from two different upstream nodes. Code to the smaller one: 10,000 blocks. On Cronos, that covers about 70 minutes, so backfilling a single day takes about 21 requests and a month about 620. The chunking loop from eth_getLogs range caps applies, with one change: chunk size in blocks has to be sized for a fast chain.
A query with only a topic and no address was refused three times out of three with -32701 and the message "Please specify an address in your request". Wide event scans ("every ERC-20 Transfer on the chain") are not something this endpoint will serve. Filter by contract.
History: plan for a node that forgets
This is the part that will break an Ethereum-shaped indexer. The Cosmos SDK nodes behind this endpoint prune aggressively.
State (balances, eth_call at a past block) lasts about 100 blocks. eth_getBalance at 20 and 50 blocks behind the head succeeded. At about 100 blocks behind, and at every depth from there out to 3,000,000, it failed with:
codespace sdk code 18: invalid request: failed to load state at height 91687369;
version mismatch on immutable IAVL tree; version does not exist.
Version has either been pruned, or is for a future block height
A hundred blocks is about 40 seconds. "Read the balance as of this morning" is not a query this endpoint can answer. If you need historical state, you derive it from events, or you run an archive node. We explain that trade-off in full node vs archive node.
Blocks and logs go back further, but the floor depends on which node answers. One upstream reported its lowest height as 90,850,001, and block 90,850,001 (2026-08-30) came back intact. Another reported 95,187,533, about 500,000 blocks or ~2.4 days behind the head. Design backfills around the shorter window, and treat "not available" as a per-node answer.
Pre-floor blocks return null, not an error. eth_getBlockByNumber for block 1, 1,000,000 and 50,000,000 returned result: null, and so did the earliest tag. Code that treats null as "block does not exist yet" will misreport pruned history as the future.
What works and what doesn't
| Method | Result |
|---|---|
eth_call, eth_estimateGas, eth_feeHistory, eth_maxPriorityFeePerGas |
Works |
eth_getBlockReceipts |
Works (all receipts for a block in one call) |
eth_call with a state-override object |
Works (see state overrides) |
txpool_status |
Works (pending / queued) |
eth_createAccessList |
Works only with gas set; without it: gas must be set when using authorization list |
debug_traceTransaction, debug_traceBlockByNumber, trace_transaction |
Not available: -32601 method does not exist/is not available |
eth_subscribe("newHeads") over wss:// |
Works |
The eth_createAccessList error is misleading: my call had no authorization list, and passing any gas value (I used 200,000) made it succeed. If your library calls it without gas, add one.
No tracing means no call trees for debugging reverts or tracking internal CRO transfers over RPC. To find out where a transaction failed, re-run it with eth_call at the latest block before state moves on — which, at ~100 blocks of state, is less than a minute.
What this means for your stack
Think in seconds, not blocks. Convert every Ethereum-tuned block count (confirmations, TTLs, backoff) to wall-clock time first. At 0.42 s, ported block numbers are about 30x too short.
Chunk eth_getLogs at 10,000 blocks and always pass an address.
Read state at the head, and derive history from events. A shared endpoint keeps about 40 seconds of state.
Handle null for pruned blocks and expect the floor to change with the node that answers.
Skip confirmation depth, keep a sanity check. Finality is single-block.
The short version
Cronos is EVM chain 25 with ~0.42-second blocks (stable from 100 to 1,000,000-block windows) and single-block finality (latest = safe = finalized). The base fee sits at a flat 375 gwei with blocks under 1% full. eth_getLogs is capped at 10,000 blocks (about 70 minutes) and needs an address. Historical state survives about 100 blocks, block history between ~2.4 and ~24 days depending on the node, and pruned blocks come back as null. There is no debug_/trace_ namespace, eth_createAccessList needs gas, and newHeads subscriptions deliver every block.
https://rpc.swiftnodes.io/rpc/cronos?key=YOUR_API_KEY
wss://rpc.swiftnodes.io/ws/cronos?key=YOUR_API_KEY
The Cronos RPC page shows the live status of these endpoints. One key covers all 86 chains, and the free tier is 2 requests per second — enough to re-run every probe in this post.
John Sullivan covers RPC infrastructure, node operations, and multi-chain development at SwiftNodes — what it actually takes to keep endpoints fast, fresh, and reliable across EVM and non-EVM networks.
Related posts
- Babylon RPC: A Developer's Guide
How to query Babylon Genesis over RPC and LCD — CometBFT 0.38.22 on chain bbn-1, ~9.6 second blocks, 68 validators, blocks filled with finality signatures, why tx_search refuses ranges, and the pruning floor that gave three different answers in six calls.
- Hyperliquid RPC: A Developer's Guide
How to query HyperEVM over JSON-RPC — chain 999, ~1s blocks, a near-fixed 0.1 gwei base fee, a 1000-block and 1 MiB eth_getLogs cap, no WebSocket, and the one that will cost you money: state methods accept a block number and then ignore it, so historical reads silently return the latest state.
- Neutron RPC: A Developer's Guide
How to query Neutron over JSON-RPC — CometBFT 0.38 on chain neutron-1, why there is no numeric chain ID, the header fields that disappeared in 0.38, a tx_search that times out instead of answering, a pruning floor that differs between nodes, and a block time that measured 7s this week against a 2s baseline.