Reference
JSON-RPC Reference
Every eth_*, net_* and web3_* method a node answers · Estimated read time: 12 minutes
Accurate as of node 0.8.1.
TL;DR
Every node answers Ethereum-style JSON-RPC 2.0 on POST /. It is a read layer: blocks, balances, nonces, receipts, logs and contract reads work with familiar tools. Writing does not: there is no eth_sendRawTransaction. Signed transactions go to POST /tx (see the Node API Reference).
eth_blockNumberand"latest"blocks are the finalized height. Use the non-standard"tip"tag for the newest block.- State reads (
eth_getBalance,eth_call, ...) read the tip unless you pass"finalized"or"safe". There are no historical state reads. - Addresses: the node accepts ase1 or 0x hex. Responses mix both. viem works as is; ethers v6 high-level block, transaction and receipt reads fail (see ethers and viem).
- No WebSocket, no subscriptions, no mempool methods. Poll.
For in-block speed (showing a transaction before it is final) read Fast Confirmations. The REST routes are often simpler; see the Node API Reference.
Endpoint
| URL | Notes |
|---|---|
| https://testnet.asentum.com | POST /. Public gateway, about 25 requests a second per IP (burst 100). |
| https://explorer.asentum.com | POST / (GET / is the explorer website). Same node pool. |
| http://<your-node>:8545 | Your own node. Operator app: http://127.0.0.1:9545. |
- Chain id: 1423 (
0x58f). - Batches: send an array of requests, get an array back in the same order. An empty array answers
{"error":{"code":-32600,"message":"empty batch"}}. - Rate limits: the block methods (
eth_getBlockByNumber,eth_getBlockByHash,eth_getBlockTransactionCountByNumberandByHash) share the node's per-IP block-read budget with the REST block and receipt routes: a burst of 240, then 4 a second, one token per call inside a batch. Over the limit the node answers HTTP 429 with a JSONerrorandRetry-After; the gateway answers 429 with an HTML page. Details in Rate limits and caching. - CORS is open (
Access-Control-Allow-Origin: *).
curl -s -X POST https://testnet.asentum.com -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
{"jsonrpc":"2.0","id":1,"result":"0x8b78c"}Addresses and encodings
Every address parameter takes an ase1 address or the 0x hex of the same 20 bytes. Responses are mixed, so convert before showing anything to a user:
| Field | Format |
|---|---|
| Transaction from, receipt from, block miner, log address | ase1 |
| Transaction to, receipt to, receipt contractAddress | 0x hex (null for a deploy's to) |
| Hashes, input, log data, topics | 0x hex |
| Numbers | Hex quantities (0x...), as in Ethereum |
- Hashes are BLAKE3, not Keccak. A block hash is BLAKE3 of the SSZ header; a transaction hash is BLAKE3 of the SSZ signed transaction.
- Placeholders:
v,r,sare zero (signatures are ML-DSA-65 and do not fit).logsBloomis all zeros,difficulty0,sha3Unclesthe empty-uncles constant,typealways0x2,effectiveGasPrice0x1. - Gas and fees: the base fee is 1 wei per gas.
gasPricein a transaction is itsmaxFeePerGas.
Supported methods
This is the complete list. Anything else answers -32601 method not found.
| Method | Params | Returns and notes |
|---|---|---|
| web3_clientVersion | [] | "asentum/v0.0.0-alpha.0". A fixed string, not the node release. |
| net_version | [] | "1423". |
| eth_chainId | [] | "0x58f". |
| eth_syncing | [] | Always false, even on a node that is catching up. Compare /v3/heads with the public RPC instead. |
| eth_accounts | [] | Always []. Nodes hold no user keys. |
| eth_blockNumber | [] | Finalized height (see Block tags). |
| eth_gasPrice | [] | The base fee of the finalized block: 0x1. |
| eth_maxPriorityFeePerGas | [] | Always 0x0. |
| eth_feeHistory | [count, newest, percentiles?] | Placeholder data: every base fee 0x1, every gasUsedRatio 0, every reward 0x0. count is capped at 1,024. Fine for wallets that need an answer; not real history. |
| eth_estimateGas | [tx] | Always 0x186a0 (100,000). It does not simulate. Contract calls need at least 50,000 gas to be admitted; heavy calls need more. |
| eth_getBalance | [address, tag?] | Balance in wei, read at the tip ("finalized"/"safe" for the finalized state). |
| eth_getTransactionCount | [address, tag?] | Nonce at the tip. "pending" adds this sender's queued transactions in this node's mempool: sign your next transaction with that. |
| eth_getBlockByNumber | [tag | number, fullTx] | Block, or null. Extra field finalized. "tip" for the newest block. |
| eth_getBlockByHash | [hash, fullTx] | Block, or null. Finalized blocks only, and no finalized field. |
| eth_getBlockTransactionCountByNumber | [tag | number] | Hex count, or null. |
| eth_getBlockTransactionCountByHash | [hash] | Hex count, or null. |
| eth_getTransactionByHash | [hash] | Transaction in an included block (tip blocks too), or null. Pending mempool transactions are not visible. |
| eth_getTransactionReceipt | [hash, "finalized"?] | Receipt with extra fields finalized and confirmations, or null. See below. |
| eth_getCode | [address, tag?] | The contract's JavaScript source as hex-encoded UTF-8 (not bytecode). "0x" for an account without code. The tag is ignored. |
| eth_getStorageAt | [address, key, tag?] | key is a contract storage key as a plain string (for example "self"), not a 32-byte slot. Returns the stored string as hex-encoded UTF-8, or "0x". The tag is ignored. |
| eth_call | [{to, data}, tag?] | Read-only contract call. data is hex-encoded JSON, not ABI. See below. |
| eth_getLogs | [filter] | Logs from finalized blocks. At most 10,000 blocks per call. See below. |
| eth_newFilter | [filter] | Filter id. Poll it with eth_getFilterChanges. |
| eth_newBlockFilter | [] | Filter id. eth_getFilterChanges returns new finalized block hashes. |
| eth_getFilterChanges | [id] | Logs or block hashes since the last poll. |
| eth_getFilterLogs | [id] | All logs for the filter's range. |
| eth_uninstallFilter | [id] | true or false. |
Filters live in the memory of the node that created them: they expire after 5 minutes without a poll, at most 1,024 exist per node, and they are lost when the node restarts. Behind a load balancer, keep polling through the same host.
eth_getTransactionReceipt
Returns the receipt as soon as the transaction is in a block on the best chain, flagged with two extra fields. Pass "finalized" (or "safe") as a second param to get final receipts only.
curl -s -X POST https://testnet.asentum.com -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["0x3261a75d9804a1f92a08d88ca0878ad5edac0bf1e63c408d8f2181a6ffcd7ea1"]}'
{"jsonrpc":"2.0","id":1,"result":{
"transactionHash":"0x3261a75d9804a1f92a08d88ca0878ad5edac0bf1e63c408d8f2181a6ffcd7ea1",
"transactionIndex":"0x0",
"blockHash":"0x5c21659e3aa85b139eece5ac1e7dca7f184695cd2ee377427ddd8e69f481e90d",
"blockNumber":"0x8b774",
"from":"ase1r3m77x6eyxt0emxg9jepm8ah7gy8kxmw90w5ty",
"to":"0x63f640a9b98b6f28e06cb5cc0fce5d72b6c0eabd",
"cumulativeGasUsed":"0x159fd","gasUsed":"0x159fd","contractAddress":null,
"logs":[{"address":"ase1v0myp2de3dhj3crvkhxqlnjaw2mvp64a9efkpl",
"topics":["0x81d0fb6aba18eb38f50c7301578cf6521aa9b049f0cd2ce926ffb8f51653727b"],
"data":"0x7b2266726f6d223a...","logIndex":"0x0","removed":false, ...}],
"logsBloom":"0x000...000","status":"0x1","effectiveGasPrice":"0x1","type":"0x2",
"finalized":true,"confirmations":"0x8"}}statusis"0x1"(success) or"0x0"(failed). A failed one also hasrevertReason. (The REST/receiptsroute usessuccess: true/falseanderrorinstead.)finalized: falsemeans the block can still lose to a sibling at the same height. Then the receipt goes away (null), the transaction returns to the mempool and usually lands in a later block with a differentblockNumber. Onlyfinalized: trueis permanent.confirmationsis a hex quantity: tip minus block number, plus 1.- null means not in a block yet, or in a block this node does not have (nodes only keep history since their snapshot).
eth_call
Contracts are JavaScript, so there is no ABI. data is the hex of a UTF-8 JSON string {"method":"...","args":[...]}, and the result is the hex of the JSON-encoded return value. POST /view does the same thing without the hex, and is easier.
// {"method":"getPool","args":["3"]} as hex
curl -s -X POST https://testnet.asentum.com -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"ase1v0myp2de3dhj3crvkhxqlnjaw2mvp64a9efkpl","data":"0x7b226d6574686f64223a22676574506f6f6c222c2261726773223a5b2233225d7d"},"latest"]}'
{"jsonrpc":"2.0","id":1,"result":"0x227b5c2269645c223a5c22335c222c..."}const data = '0x' + Buffer.from(JSON.stringify({ method: 'getPool', args: ['3'] })).toString('hex');
const res = await fetch('https://testnet.asentum.com', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_call', params: [{ to: 'ase1v0myp2de3dhj3crvkhxqlnjaw2mvp64a9efkpl', data }, 'latest'] }),
});
const { result } = await res.json();
const value = JSON.parse(Buffer.from(result.slice(2), 'hex').toString()); // the method's return value- Reads the tip.
"finalized"or"safe"read the finalized state. Block numbers are ignored. from,valueandgasin the call object are ignored.- Address arguments in
argsare passed through unchanged. Contracts store accounts as lowercase 0x hex, so pass the 0x form. - ABI-encoded data (for example
0x70a08231...) fails with-32602 data field must be hex-encoded JSON {method, args}. Empty data returns"0x". - A method that throws, or a method that does not exist, fails with
-32603 eth_call failed: ...and the contract's message, for exampleeth_call failed: contract method 'unbond' failed: not a bonded validator.
eth_getLogs and filters
Contract events become logs. address is the contract (ase1), topics[0] is BLAKE3 of the event name (not Keccak of a Solidity signature), there are no further topics, and data is the hex of the event's JSON payload.
curl -s -X POST https://testnet.asentum.com -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getLogs","params":[{"fromBlock":"0x8b784","toBlock":"0x8b798","address":"ase1v0myp2de3dhj3crvkhxqlnjaw2mvp64a9efkpl","topics":["0x055073c2b735a03c49665d6df1f3c03dbd639aaf4c06fbdb8de121db2a981ab4"]}]}'
{"jsonrpc":"2.0","id":1,"result":[{"address":"ase1v0myp2de3dhj3crvkhxqlnjaw2mvp64a9efkpl",
"topics":["0x055073c2b735a03c49665d6df1f3c03dbd639aaf4c06fbdb8de121db2a981ab4"],
"data":"0x7b22706f6f6c4964223a2235222c2270617965...","blockNumber":"0x8b784",
"transactionHash":"0x32a3ffd2705658a4252c1b9ed29573df34c759e2c9ff42be7c7362d8f22c0dc2","transactionIndex":"0x2",
"blockHash":"0xf823465b455c652a6a0cda2ee9b91b35822e9a11bc12b117fa3e4e94223ba40b","logIndex":"0x1","removed":false}, ...]}- To filter on an event, compute BLAKE3 of the UTF-8 bytes of its name and put it in
topics[0].Swapis0x055073c2...981ab4(the example above) andTransferis0x81d0fb6a...53727b. DecodedatawithJSON.parseof the UTF-8 bytes. - Finalized blocks only. Logs from blocks above the finalized head are never returned, so a log poller runs a few blocks behind the tip. Tags like
"tip"are not accepted in a filter. - At most 10,000 blocks per call (inclusive). A wider range fails with
-32602 log scan: requested range N exceeds 10000-block limit. Heights this node does not store are skipped, not reported. logIndexcounts across the whole block, so (blockHash, logIndex) is unique.- Defaults:
fromBlockandtoBlockare"latest"(the finalized head).
Not supported
These answer -32601 method not found on every node:
| Method | Use instead |
|---|---|
| eth_sendRawTransaction, eth_sendTransaction | POST /tx with {"rawTx":"0x..."} (signed by a native wallet, the SDK or @asentum/connect). |
| eth_sign, eth_signTransaction, personal_* | Sign in the wallet. Nodes hold no keys. |
| eth_subscribe, eth_unsubscribe (WebSocket) | Poll. There is no WebSocket endpoint; /ws answers 404. |
| txpool_content, txpool_status, eth_pendingTransactions | None. There is no way to list the mempool. /balance pendingNonce and eth_getTransactionCount "pending" show your own queued count. |
| eth_getRawTransactionByHash | eth_getTransactionByHash, or GET /block/<n> for decoded transactions. |
| eth_getBlockReceipts | GET /block/<n>/receipts. |
| eth_getProof, eth_createAccessList, debug_*, trace_* | None. |
| asentum_* (any name, for example asentum_viewCall) | There are no asentum_* methods. Use POST /view and the other REST routes. |
Errors
| Code | When |
|---|---|
| -32600 | method is not a string, or an empty batch. |
| -32601 | Unknown or unsupported method. |
| -32602 | Bad params: wrong type, bad hash length, bad block tag, eth_call data that is not JSON, log range over 10,000 blocks, unknown filter id. |
| -32603 | eth_call failed (contract error or missing method), filter store full, or any other server error. The message says why. |
- A body that is not valid JSON does not get a JSON-RPC error: the node answers HTTP 500
{"error":"internal","message":"invalid JSON body: ..."}. - Codes
-32000and-32010are never used. - Rate limits are HTTP 429, not a JSON-RPC error.
ethers and viem
Tested with viem 2.48 and ethers 6.16 against the public RPC.
| Call | viem | ethers v6 |
|---|---|---|
| Block number, chain id | Works | Works |
| Balance of a 0x address | Works | Works |
| Balance of an ase1 address | Type check fails: pass the 0x form | Fails (treated as an ENS name): use provider.send |
| getBlock, getTransaction, getTransactionReceipt | Works | Fails: "invalid address" on the ase1 miner or from field |
| getFeeData | Works | Fails (reads a block) |
| getLogs | Works | Fails on the ase1 log address: use provider.send |
| call (eth_call) | Works (data is hex JSON, see eth_call) | Works |
| Sending a transaction | Not possible | Not possible |
// viem: read-only works as is
import { createPublicClient, http } from 'viem';
const client = createPublicClient({ transport: http('https://testnet.asentum.com') });
const finalized = await client.getBlockNumber(); // finalized height
const receipt = await client.getTransactionReceipt({ hash }); // receipt.status === 'success'
// ethers v6: use provider.send for anything that returns a block, tx, receipt or log
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider('https://testnet.asentum.com', 1423, { staticNetwork: true });
const height = await provider.getBlockNumber();
const r = await provider.send('eth_getTransactionReceipt', [hash]);
const ok = r && r.status === '0x1';
const balance = BigInt(await provider.send('eth_getBalance', ['ase1...', 'latest']));- Both libraries wait for finality in their wait helpers, because
eth_blockNumberis the finalized height. For in-block speed, poll the receipt yourself (see Fast Confirmations). - MetaMask and other EVM wallets can show the network but cannot sign Asentum transactions. See Ethereum Compatibility.
- For signing and sending, use the SDK or @asentum/connect.
Read next