Every method below was executed against mainnet while this page was written — the responses are captured output, not illustrations. Read the left column for what a call does, the right column for what it returns.
Robinhood Chain is an Arbitrum Orbit L2 settling to Ethereum. It is EVM-equivalent, so existing tooling — ethers, viem, web3.py, Foundry — works unchanged.
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: '0x1237',
chainName: 'Robinhood Chain',
nativeCurrency: {
name: 'Ether', symbol: 'ETH', decimals: 18
},
rpcUrls: ['https://rpc.mainnet.chain.robinhood.com'],
blockExplorerUrls: ['https://robinhoodchain.blockscout.com']
}]
});
Every call is an HTTP POST with the same JSON body. Only method and params change between calls.
No API key, no account, no headers beyond content type.
parseInt(h,16), or BigInt(h) when the value can exceed 2⁵³.curl -X POST https://rpc.mainnet.chain.robinhood.com \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber",
"params":[],"id":1}'
{ "jsonrpc": "2.0", "id": 1, "result": "0xdec54d" }
Network identifier. Use it to refuse work when a wallet is pointed at the wrong chain.
{ "result": "0x1237" } // 4663Current gas price in wei. On this chain it sits around 0.05 gwei — roughly three orders of magnitude below Ethereum L1.
{ "result": "0x30e9f90" } // 0.0514 gweiReturns 0x0.
{ "result": "0x0" }Node build string. Confirms the chain runs Arbitrum Nitro, which is what makes the Orbit fields further down appear.
{ "result": "nitro/v3.11.3-rc.4-4bed0c5/linux-arm64/go1.25.12" }Fetch a block by height, or by the tag latest.
'latest'true expands transactions into objectsPassing false returns hashes only and is much lighter — use it for polling.
{
"number": "0xdec54d",
"timestamp": "0x6a5dec90",
"gasUsed": "0x71d02",
"baseFeePerGas": "0x3165ff0",
"transactions": [ /* 6 hashes */ ],
// orbit-only, see section E
"l1BlockNumber": "0x1863755",
"sendRoot": "0xf6c227e8…",
"sendCount": "0x227"
}The public endpoint is HTTP only — no WebSocket subscriptions. New blocks are discovered by polling.
let seen = 0;
setInterval(async () => {
const b = await call('eth_getBlockByNumber', ['latest', false]);
const h = parseInt(b.number, 16);
if (h === seen) return;
seen = h;
console.log(h, b.transactions.length, 'tx');
}, 4000);Balance in wei at a given block.
'latest' or hex heightBigInt(hex) and only cast to Number after dividing by 1e18.const wei = await call('eth_getBalance', [addr, 'latest']);
const eth = Number(BigInt(wei)) / 1e18;
// 0.000237Returns deployed bytecode. An empty result (0x) means the address is a plain wallet, not a contract — the cheapest way to tell them apart.
{ "result": "0x" } // EOA, not a contractA transaction has two halves and you need both: what was submitted (the transaction) and what happened (the receipt).
receipt.status === '0x1' before treating anything as done.h = '0xb951c0274bd58288b01dbf3a94965a7efdfed4c2…'
tx = call('eth_getTransactionByHash', [h])
rc = call('eth_getTransactionReceipt', [h])
# from 0x6d9da725… → to 0x24d7cd0d…
# status 0x1 (success)
# gas 189,966
# logs 5Most blocks open with an internal ArbOS transaction: sender equals recipient, value is zero, gasUsed is 0x0.
It is chain bookkeeping, not user activity.
transactions.length inflates every metric you publish by one per block.{
"from": "0x…000a4b05",
"to": "0x…000a4b05", // identical
"value": "0x0",
"gasUsed": "0x0"
}Historical events, filtered by address and topic.
Always bound the range. With ~4s blocks, 2,000 blocks ≈ two hours of history — page through longer windows in chunks rather than widening one query until it times out.
const latest = parseInt(await call('eth_blockNumber'), 16);
const logs = await call('eth_getLogs', [{
fromBlock: '0x' + (latest - 2000).toString(16),
toBlock: 'latest',
address: contract,
topics: [transferTopic]
}]);The Ethereum L1 block this L2 block is anchored to. An L2 block is only as final as the L1 block behind it — read this when you need real finality, not just inclusion.
"l1BlockNumber": "0x1863755" // 25,573,205Messages travelling L2 → L1 (withdrawals, cross-chain calls) accumulate into a merkle tree. sendRoot is its current root; sendCount is the running total of queued messages.
Watch sendCount to detect outbound activity without parsing every transaction.
"sendRoot": "0xf6c227e8bd780bce1fd394c5bcd9513f…",
"sendCount": "0x227" // 551There is no mining and no validator here. The miner address is a readable marker — decode its tail as ASCII and it spells what it is.
const m = '0xa4b000000000000000000073657175656e636572';
Buffer.from(m.slice(-18), 'hex').toString();
// → "sequencer"Not exposed on the public endpoint. Clients that assume it exists will read undefined and carry on silently.
error first. A JSON-RPC failure returns HTTP 200 with an error object — inspect it before touching result.{
"error": {
"message": "the method eth_syncing does not exist/is not available"
}
}Free, keyless, CORS-open — ideal for reading, prototyping and browser apps. It is rate limited and not intended for production.
Move to a dedicated provider once you need archive history, throughput, or an uptime guarantee: Alchemy, QuickNode, Blockdaemon, dRPC, Validation Cloud.
auth none — no key required
cors access-control-allow-origin: *
transport HTTP POST only — no websocket
rate limit yes, unspecified — back off on failure
archive not guaranteed