| name | evm-rpc |
| description | Query EVM JSON-RPC methods for transaction debugging and endpoint triage, including eth_getTransactionReceipt and debug_traceTransaction. Use when the user asks to inspect tx status, retrieve receipts, run tracers, compare EVM vs Cosmos outcomes, or choose between EVM RPC and Comet RPC endpoints. |
EVM RPC: Receipt and Trace Playbook
Use this skill for EVM transaction debugging with JSON-RPC.
Quick Start
Set a hash and endpoint, then run receipt first and trace second.
evm_tx="0xYOUR_evm_tx"
EVM_RPC="https://evm-rpc.archive.testnet-2.nibiru.fi"
curl -s -X POST \
-H "Content-Type: application/json" \
--data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionReceipt\",\"params\":[\"$evm_tx\"],\"id\":1}" \
"$EVM_RPC" | jq
curl -s -X POST \
-H "Content-Type: application/json" \
--data "{\"jsonrpc\":\"2.0\",\"method\":\"debug_traceTransaction\",\"params\":[\"$evm_tx\",{\"tracer\":\"callTracer\"}],\"id\":1}" \
"$EVM_RPC" | jq
Endpoint Map - Nibiru Examples
Chain-agnostic rule: methods determine endpoint family.
| Purpose | Example endpoint | Methods |
|---|
| EVM JSON-RPC (mainnet) | https://evm-rpc.nibiru.fi | eth_*, debug_*, net_*, web3_* |
| EVM JSON-RPC archive (mainnet) | https://evm-rpc.archive.nibiru.fi | historical reads, tracing |
| EVM JSON-RPC archive (testnet) | https://evm-rpc.archive.testnet-2.nibiru.fi | testnet tracing and historical reads |
| Comet RPC archive (mainnet) | https://rpc.archive.nibiru.fi | /block_results, /consensus_params |
| Comet RPC archive (testnet) | https://rpc.archive.testnet-2.nibiru.fi | /block_results, /consensus_params |
Rules:
- Run
eth_* and debug_* only on EVM RPC endpoints.
- Run
/block_results and /consensus_params only on Comet RPC endpoints.
- Prefer archive endpoints for old heights/hashes and tracing workflows.
Basic Queries (Health / Identity)
web3_clientVersion
Queries the traceability info like the client name, version, Git commit, Go version,
runtime architecture, and build tags.
curl -X POST https://evm-rpc.nibiru.fi \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "web3_clientVersion",
"params": [],
"id": 1
}'
The response looks like this: "Nibiru 2.6.0: Compiled at Git commit 3cf97e3468f8ce922c8760f839f8f336ca0c37f4 using Go go1.24.5, arch amd64, and build tags (netgo osusergo ledger static rocksdb pebbledb muslc)"
eth_chainId
Queries the EIP-155 replay-protection chain id for the current ethereum blockchain (Nibiru).
curl -X POST https://evm-rpc.nibiru.fi \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_chainId",
"params": [],
"id": 1
}'
Transaction Triage Workflow
Use this sequence to avoid false conclusions:
eth_getTransactionByHash
- Confirms the tx exists and gives gas fields, type, input, and block refs.
eth_getTransactionReceipt
- Authoritative EVM execution result.
debug_traceTransaction (usually callTracer)
- Explains internal call path and where execution diverged.
- Optional cross-layer checks on Comet RPC
block_results?height=..., consensus_params?height=....
Concrete Command Cookbook
1 - Get Transaction by Hash
evm_tx="0xYOUR_evm_tx"
EVM_RPC="https://evm-rpc.archive.testnet-2.nibiru.fi"
curl -s -X POST \
-H "Content-Type: application/json" \
--data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionByHash\",\"params\":[\"$evm_tx\"],\"id\":1}" \
"$EVM_RPC" | jq
2 - Get Transaction Receipt - Authoritative Status
evm_tx="0xYOUR_evm_tx"
EVM_RPC="https://evm-rpc.archive.testnet-2.nibiru.fi"
curl -s -X POST \
-H "Content-Type: application/json" \
--data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionReceipt\",\"params\":[\"$evm_tx\"],\"id\":1}" \
"$EVM_RPC" | jq
Interpretation:
result == null: Not mined or rejected. Either the tx is still in the mempool, it was dropped, or it failed consensus/validation (e.g., bad nonce, invalid signature, insufficient funds for gas). It is not in any block.
status == "0x1": Full Success. The transaction was executed successfully, and all state changes were applied.
status == "0x0": Execution Failure (Revert). The transaction was successfully mined and included in a block (consuming gas), but the EVM execution was reverted. No state changes occurred except for the gas fee deduction from the sender.
blockNumber present: Mined/included in a block.
logs present: Events emitted (decode by address, topics, data). Note that a reverted tx (0x0) will generally have an empty logs array, even if it attempted to emit them before reverting.
3 - Run Debug Tracer - Call Tree View
evm_tx="0xYOUR_evm_tx"
EVM_RPC="https://evm-rpc.archive.testnet-2.nibiru.fi"
curl -s -X POST \
-H "Content-Type: application/json" \
--data "{\"jsonrpc\":\"2.0\",\"method\":\"debug_traceTransaction\",\"params\":[\"$evm_tx\",{\"tracer\":\"callTracer\"}],\"id\":1}" \
"$EVM_RPC" | jq
Interpretation:
- Inspect top-level
from, to, gas, gasUsed.
- Walk
calls[] for internal call chain and outputs.
- Use trace to explain behavior; use receipt status to decide success/failure.
4 - Cross-Layer Checks with Comet RPC
HEIGHT="5959008"
TM_RPC="https://rpc.archive.testnet-2.nibiru.fi"
curl -s "$TM_RPC/block_results?height=$HEIGHT" | jq
curl -s "$TM_RPC/consensus_params?height=$HEIGHT" | jq
Use when EVM and Cosmos observations appear inconsistent.
5 - Check Deployed Bytecode for an ABI Mismatch
Use this when a transaction reverts very early, the trace only shows a top-level
execution reverted, and the calldata targets a function that may not exist in
the deployed runtime bytecode. This catches frontend ABI or artifact drift, such
as calling a newly added function against an older deployed contract.
The method is:
- Compute the 4-byte selector from the canonical function signature.
- Fetch runtime bytecode with
eth_getCode.
- Search the runtime bytecode for the selector without the
0x prefix.
- Compare against known selectors that should exist on the same contract.
CONTRACT_ADDR="0xCONTRACT_ADDR"
FUNCTION_SIG="claimReferrerRewardsToEvm(uint256[])"
EVM_RPC="https://evm-rpc.archive.nibiru.fi"
BLOCK_TAG="latest"
bun - <<'TS'
import { id } from "ethers"
const contractAddr = process.env.CONTRACT_ADDR
const functionSig = process.env.FUNCTION_SIG
const rpc = process.env.EVM_RPC
const blockTag = process.env.BLOCK_TAG ?? "latest"
if (!contractAddr || !functionSig || !rpc) {
throw new Error("set CONTRACT_ADDR, FUNCTION_SIG, and EVM_RPC")
}
const selector = id(functionSig).slice(0, 10)
const resp = await fetch(rpc, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getCode",
params: [contractAddr, blockTag],
id: 1,
}),
})
const json = await resp.json()
if (json.error) {
throw new Error(JSON.stringify(json.error))
}
const code = String(json.result ?? "0x").toLowerCase()
const selectorNoPrefix = selector.slice(2).toLowerCase()
console.log({
contractAddr,
blockTag,
functionSig,
selector,
byteLength: Math.max(0, (code.length - 2) / 2),
selectorPresent: code.includes(selectorNoPrefix),
})
TS
To compare multiple selectors, use the same runtime bytecode and check a list.
This is useful when one new selector is missing but older selectors are present.
CONTRACT_ADDR="0xCONTRACT_ADDR"
EVM_RPC="https://evm-rpc.archive.nibiru.fi"
BLOCK_TAG="latest"
bun - <<'TS'
import { id } from "ethers"
const contractAddr = process.env.CONTRACT_ADDR
const rpc = process.env.EVM_RPC
const blockTag = process.env.BLOCK_TAG ?? "latest"
const signatures = [
"claimReferrerRewardsToEvm(uint256[])",
"executeSimpleFunctions(bytes)",
"getDenomOfCollateralIndex(uint256)",
]
if (!contractAddr || !rpc) {
throw new Error("set CONTRACT_ADDR and EVM_RPC")
}
const resp = await fetch(rpc, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_getCode",
params: [contractAddr, blockTag],
id: 1,
}),
})
const json = await resp.json()
if (json.error) {
throw new Error(JSON.stringify(json.error))
}
const code = String(json.result ?? "0x").toLowerCase()
for (const sig of signatures) {
const selector = id(sig).slice(0, 10)
console.log({
sig,
selector,
present: code.includes(selector.slice(2).toLowerCase()),
})
}
TS
Interpretation:
selectorPresent: true: The runtime bytecode likely has a dispatcher entry
for that function. Continue debugging arguments, require checks, internal
calls, and traces.
selectorPresent: false while older selectors are present: The deployed
contract likely does not implement the ABI function being called. Update the
address, deploy the newer contract, or call an older available function.
- Very low
gasUsed with status: "0x0" and a missing selector often means
the call hit the dispatcher fallback/revert before meaningful contract logic.
- For proxies, minimal proxies, diamonds, or custom fallback dispatchers, the
selector may be absent from the proxy runtime bytecode. Check the implementation
address or facet bytecode instead.
Receipt and Trace Rules of Thumb
- Receipt status is the authoritative EVM truth for success vs. failure.
- Mined does not mean successful: A transaction can be in a block and consume gas (
0x0), but have zero effect on state. Always check the status field.
- Null receipt implies non-existence: If
eth_getTransactionReceipt returns null, the transaction was never mined. This is usually due to a consensus/mempool error (invalid signature, wrong chain ID, bad nonce, or insufficient funds to cover the max gas cost).
- Consensus failures don't have receipts: Errors like "insufficient funds" or "bad nonce" prevent a transaction from even entering a block, so no EVM status is ever generated.
- A non-empty
logs array (at the receipt level) generally only exists for successful transactions (0x1).
- Gas consumption: Even in failure (
0x0), the gasUsed is still charged to the sender up to the point of reversion.
- Gas Comparison: Compare receipt
gasUsed with the gas (limit) from eth_getTransactionByHash to see if the transaction ran out of gas.
- Tracing Failures: If tracing fails, verify the provider supports the
debug namespace and retry on an archive endpoint.
Endpoint Selection Guide
Choose endpoint by method and historical depth:
- Latest, simple reads (
eth_blockNumber, recent receipt):
- Standard EVM RPC is usually enough.
- Historical receipts, old block state, trace/debug:
- Tendermint/Comet block metadata and app-level event correlation:
Common Pitfalls
- Calling Comet paths on EVM RPC host (or the reverse).
- Using the wrong network endpoint for a valid tx hash.
- Assuming
result == null always means pending; it may be wrong chain/provider.
- Treating trace output alone as final status without reading receipt.
- Ignoring timeout/retry behavior for freshly broadcast transactions.
Reusable Triage Template
evm_tx="0xYOUR_evm_tx"
EVM_RPC="https://evm-rpc.archive.testnet-2.nibiru.fi"
TM_RPC="https://rpc.archive.testnet-2.nibiru.fi"
curl -s -X POST -H "Content-Type: application/json" \
--data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionByHash\",\"params\":[\"$evm_tx\"],\"id\":1}" \
"$EVM_RPC" | jq
curl -s -X POST -H "Content-Type: application/json" \
--data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionReceipt\",\"params\":[\"$evm_tx\"],\"id\":1}" \
"$EVM_RPC" | jq
curl -s -X POST -H "Content-Type: application/json" \
--data "{\"jsonrpc\":\"2.0\",\"method\":\"debug_traceTransaction\",\"params\":[\"$evm_tx\",{\"tracer\":\"callTracer\"}],\"id\":1}" \
"$EVM_RPC" | jq
Additional Resources
- Zero-gas debug walkthrough:
/home/realu/ki/boku/epics/epic-evm/26-02-zero-gas/26-02-10-zero-gas-debug.md
- Existing endpoint conventions:
/home/realu/ki/boku/nibi/cook/index.ts