Skip to main content

JSON-RPC Reference

The node exposes a WebSocket JSON-RPC API at ws://host:port/ws. Apps usually go through the Hub API instead of calling these methods directly, but they are documented here for node operators, explorers, and tooling.

All requests follow JSON-RPC 2.0:

{
"jsonrpc": "2.0",
"id": 1,
"method": "get_account_balance",
"params": { "address": "0x..." }
}

Responses return result on success or error with code and message.

params is a bare JSON object, not a positional array — the exceptions are send_raw_transaction and send_wallet_transaction, which take a bare hex string. Array-wrapped params fail with invalid-params on methods that require fields, but the list_* methods silently ignore them and return unfiltered results instead of erroring.

Write methods​

send_raw_transaction​

Submit a signed, RLP-encoded transaction as hex.

{
"method": "send_raw_transaction",
"params": "0xe60188..."
}

Result: the literal string "Transaction imported" — the node returns no hash or inclusion info; compute the transaction hash client-side. The node verifies the secp256k1 signature, nonce, chain_id against its own, and that the sender's balance covers the transaction's direct debit plus the flat tx_fee (transactions are no longer free — see CLT Economics) before pooling it.

send_transaction​

Submit a structured transaction object as params (validator / dev tooling). Prefer send_raw_transaction from apps. Also returns "Transaction imported".

send_wallet_transaction​

Submit a transfer signed by MetaMask or another Ethereum wallet: the signed legacy Ethereum transaction (EIP-155, type 0) as hex, exactly as eth_sendRawTransaction carries it. The Hub API's /rpc endpoint calls this for wallets; apps do not need it.

{
"method": "send_wallet_transaction",
"params": "0xf86e80850b2d05e000825208941111..."
}

Result: { "hash": "0x…" }, the Ethereum transaction hash. The node stores the transfer under that same hash, so the wallet, the explorer and get_transaction_by_hash all show one hash. See WalletTransfer for what is checked and refused.

Read methods​

get_transaction_by_hash​

{ "method": "get_transaction_by_hash", "params": { "hash": "0x2d22…c445" } }

Result: { "transaction": {…}, "block_index": 812, "block_hash": "…" }; block_index and block_hash are null while the transaction waits in the pool, and the result is null when the node does not know the hash. The hash matches with or without 0x, in any case.

get_next_nonce​

{ "method": "get_next_nonce", "params": { "address": "0x<account>" } }

Result: { "nonce": n } — the next nonce to use. Fetch this before building a transaction so the signed payload carries the correct nonce.

get_account_balance​

{ "method": "get_account_balance", "params": { "address": "0x<account>" } }

Result: { "balance": n } — integer CLT balance.

get_account_balance_effects​

Returns the ordered history of balance changes (credits/debits) for an account. Useful for explorers reconstructing activity.

get_block_by_index​

{ "method": "get_block_by_index", "params": { "index": 12 } }

Result: block at the given height, including transactions.

get_chain_info​

No params. Returns the consensus parameters committed by the genesis ChainInit transaction, plus current chain totals and this node's own sync state:

{
"chain_id": 2077,
"is_testnet": true,
"tx_fee": 1000,
"ride_request_referrer_fee_bps": 200,
"ride_offer_referrer_fee_bps": 200,
"mint_authority": "0x...",
"ride_auto_release_secs": 300,
"total_supply": "1000000000000000",
"latest_block_index": 42,
"is_syncing": false,
"best_peer_block_index": 42,
"blocks_behind": 0
}

total_supply is a decimal string; every other field is a bare JSON number. At this release's peg (1 USD = 1,000,000 CLT), total_supply is the one field that can realistically exceed 2^53 (roughly $9B circulating) — a JSON number would silently round past that, and a reconciliation process treats a rounded supply as a serious incident. chain_id, tx_fee, both referrer-fee rates, and the block/sync fields can't approach that magnitude (a block a second would need hundreds of millions of years), so there's no reason to pay the string-parsing cost on those fields too.

ride_auto_release_secs is how long after a ride is accepted a cancel still refunds the rider; after it, a cancel pays the held fare to the driver. 0 means the chain does not do that at all. See Ride Lifecycle.

is_syncing, best_peer_block_index, and blocks_behind describe this node's own position relative to its peers, not a consensus parameter — without them, a node that's still catching up answers get_chain_info indistinguishably from one that's fully synced, and a caller has no way to tell it's reading a partial chain. best_peer_block_index is 0 when no peer has been heard from yet, meaning unknown, not "the tip is at block zero"; a lone node reports is_syncing: false in that case because it has no peer to be behind, not because it's caught up to anything.

The node has no HTTP health endpoint — only /metrics on serve_metric_addr (see Configuration), which is a Prometheus scrape target, not a simple up/down check. get_chain_info's sync fields are the closest thing to a liveness and sync check available over JSON-RPC, and the only one that tells you whether the node you're talking to is actually caught up.

List methods​

list_ride_requests​

Open ride requests (not yet accepted). Accepts optional map bounds as a bare object — the bounds keys are camelCase:

{
"method": "list_ride_requests",
"params": { "minLat": 35.6, "maxLat": 35.9, "minLng": 51.3, "maxLng": 51.6 }
}

Wrapping the bounds in an array drops the filter silently and returns every open request — no error. Omit params (or send {}) when you want no filter.

list_ride_offers​

{
"method": "list_ride_offers",
"params": { "ride_request_tx_hash": "0x<rideRequestTxHash>" }
}

Driver offers for a specific ride request. With no params, all open offers are returned.

list_active_trips​

Trips accepted but not fully paid or cancelled. Optional driver_address / passenger_address filters:

{
"method": "list_active_trips",
"params": { "driver_address": "0x<driver>" }
}

Filter keys are snake_case only — driverAddress / passengerAddress are silently ignored and you get unfiltered results.

list_completed_trips​

Trips where the full fare has been paid. Same optional driver_address / passenger_address filters.

list_recent_trips​

Finished trips: completed (full fare paid) or cancelled. Includes trip_status. Same optional driver_address / passenger_address filters.

Error handling​

CauseTypical behavior
Invalid signatureTx rejected before mempool
Stale nonceRejected; fetch fresh nonce via get_next_nonce
Wrong chain_idRejected during mempool admission — the transaction was signed for a different chain
Insufficient balance for the transaction's debit plus tx_feeRejected during mempool admission, before pooling
Unknown methodJSON-RPC error with method-not-found code
Malformed paramsJSON-RPC error with invalid-params code

Apps integrating directly should retry with backoff on transient WebSocket disconnects and re-fetch the nonce after any rejected send.