rpc: talk to node and wallet RPC endpoints
rpc is a small host-side network tool that works as a pipe filter, in the style of netcat. It sends one call, or a stdin batch, to a node or wallet…
rpc is a small host-side network tool that works as a pipe filter, in the style of netcat. It sends one call, or a stdin
batch, to a node or wallet endpoint. The raw response goes to stdout. Errors go to stderr as one JSON line.
It uses the same HTTP client (crates/net, minreq + rustls, no Tokio) and the same per-chain endpoint table
(blocktools_net::ENDPOINTS) as utxo-fetch and tx-broadcast. Like them, it is excluded from the sealed image (Dockerfile,
scripts/docs-check.sh rule 4) and sealed-run never allows it.
rpc has no config or project files. The man page is man/man1/rpc.1, generated from rpc --help by scripts/gen-man.py.
Platform: everything on this page was measured on Linux (x86_64). Nothing here was run on macOS.
Shapes
rpc URL METHOD [PARAMS-JSON] one JSON-RPC call (EVM, bitcoind and forks, Solana)
... | rpc URL JSON-RPC request(s) on stdin: one object or a batch array, sent byte-for-byte
rpc URL /PATH [BODY-JSON] REST: GET, or POST when BODY is given (Tron, Cosmos LCD, Esplora, Blockbook)
rpc URL SHORTCUT [ARG] height | chainid | balance ADDR | nonce ADDR | account ADDR | utxos ADDR | tx ID | receipt ID | fee | blockhash
... | rpc URL sendraw broadcast ONE signed tx; decoded summary + typed code on the terminal
URL can also be a chain name from the shared table (btc, ltc, eth, ada, dot, ton, atom, xlm, algo) or the
EVM chain id 1. For any other EVM chain, pass its RPC URL and pin the chain id as a plain argument: --chain-id 137.
Exit status: 0 ok, 2 bad input, 1 everything else (RPC or HTTP error, refused, aborted): the shared rule in docs/EXIT-CODES.md.
Options: --protocol P, --chain-id N, --chain C (UTXO chain for address checks and sendraw decoding; default btc),
--auth-file FILE, --auth-env NAME, --policy FILE, --allow-wallet-sign, --timeout N (1..300, default 30).
Auto-detection
Detection runs in this order. The first rule that matches wins. --protocol overrides all of them.
- URL substring (
URL_RULESintools/rpc/src/lib.rs):solana,:8899,helius→ soltron,:8090→ tronblockbook,/api/v2→ blockbookesplora,blockstream.info,mempool.space,litecoinspace→ esploracosmos-rest,lcd,:1317,cosmos.directory,polkachu→ cosmos- bitcoind-family ports
:8332 :18332 :18443 :38332 :9332 :19332 :22555 :44555→ btc :8545,infura,alchemy,-rpc.publicnode.com,flashbots,drpc,llamarpc→ evm
- Method name:
eth_/net_/web3_/personal_/wallet_/debug_/txpool_→ evm- a known Solana camelCase method → sol
- all-lowercase → btc (bitcoind, litecoind, dogecoind and other forks)
- Shortcuts only, read-only probes in this order:
eth_chainIdreturns a string → evmgetVersionhassolana-core→ solgetblockchaininfohaschain→ btc- otherwise the call is refused with
protocol_unknown; pass--protocol.
Passthrough calls do not need a protocol. Their bytes are sent as given.
Per-protocol usage
The output below was captured from the binary against scripts/mock-chain.py (port shown as PORT) and a one-shot local mock.
The one public endpoint call is labelled.
EVM JSON-RPC (any chain)
Measured on 2026-10-25 20:39 PDT on the Linux box (x86_64). This is a harmless read-only call to a public endpoint:
$ rpc https://ethereum-rpc.publicnode.com eth_blockNumber
{"jsonrpc":"2.0","result":"0x190e9b3","id":1}
$ rpc eth height
{"height":26274227,"protocol":"evm"}
Mock:
$ rpc --protocol evm http://127.0.0.1:PORT account 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
{"balance":"1000000000000000000","chain_id":1,"max_fee_per_gas":"2100000000","max_priority_fee_per_gas":"100000000","nonce":5}
$ rpc --protocol evm http://127.0.0.1:PORT nonce 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
{"address":"0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf","nonce":5,"protocol":"evm"}
The account shortcut prints the same object as utxo-fetch --chain eth (the file that tx-build --account FILE reads). The
e2e script checks this.
The default shortcut set maps to the read-only EVM methods that SwapChan uses (readchain.ts):
| shortcut | method |
|---|---|
height |
eth_blockNumber |
chainid |
eth_chainId |
balance ADDR |
eth_getBalance [ADDR,"latest"] |
nonce ADDR |
eth_getTransactionCount [ADDR,"pending"] |
tx HASH |
eth_getTransactionByHash |
receipt HASH |
eth_getTransactionReceipt |
fee |
eth_gasPrice + eth_maxPriorityFeePerGas |
account ADDR |
chainId + nonce + balance + fees (utxo-fetch shape) |
eth_call, eth_getStorageAt, eth_getCode, eth_getLogs and eth_getBlockByNumber / eth_getBlockByHash go through
passthrough, for example rpc URL eth_getCode '["0x...","latest"]'.
Public RPCs usually refuse a full-range eth_getLogs, one with no fromBlock or with fromBlock set to earliest/0x0. In
that case rpc prints a getlogs_full_range warning on stderr and still sends the call.
--chain-id N refuses the call (chain_id_mismatch) unless the endpoint's eth_chainId equals N.
Flashbots Protect: https://rpc.flashbots.net/fast is an optional private broadcast URL, for example
... | rpc https://rpc.flashbots.net/fast sendraw. No code is specific to Flashbots. It is simply a URL. Untested: no
broadcast was made.
Bitcoin Core-style JSON-RPC (bitcoind, litecoind, dogecoind, other forks)
rpc --auth-file ~/.bitcoin/.cookie http://127.0.0.1:8332 getblockcount
rpc --auth-file ~/.bitcoin/.cookie http://127.0.0.1:8332 utxos bc1q... # scantxoutset; prints utxo-fetch lines
rpc --chain ltc --auth-file ~/.litecoin/.cookie http://127.0.0.1:9332 balance ltc1q...
utxosturns the node's decimal amounts into integer base units with an exact decimal parse, not floats.0.29becomes29000000.- For legacy inputs,
utxosaddsprev_txusinggetblockhash+getrawtransaction, using the same rule as utxo-fetch. feeusesestimatesmartfee 6and reports sat/vB.
Solana JSON-RPC
rpc https://solana-rpc.publicnode.com getSlot
rpc --protocol sol URL balance ADDR # lamports
rpc --protocol sol URL blockhash
Tron HTTP API
rpc --protocol tron https://tron-rpc.publicnode.com balance TXYZ... # POST wallet/getaccount {"address":..,"visible":true}
rpc https://tron-rpc.publicnode.com /wallet/getnowblock '{}'
Tron quirks:
- Reads work on PublicNode, but broadcast goes to TronGrid.
- Requests need
"visible":trueso that addresses are base58. The shortcuts add it. With raw/PATHcalls you must include it yourself. rpcdoes not broadcast Tron transactions.wallet/broadcasttransactionis refused withbroadcast_needs_sendraw, andsendrawdoes not support Tron (sendraw_unsupported_protocol). Untested.
Cosmos REST / LCD
rpc atom height
rpc atom balance cosmos1... # /cosmos/bank/v1beta1/balances/ADDR
rpc atom nonce cosmos1... # account_number + sequence
rpc atom /cosmos/base/tendermint/v1beta1/node_info
Esplora and Blockbook REST (GET passthrough)
$ rpc --protocol esplora http://127.0.0.1:PORT utxos bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
{"address":"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4","amount":50000,"txid":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","vout":0}
rpc btc /blocks/tip/height
rpc https://btc1.trezor.io/api/v2 utxos bc1q... # blockbook
The Esplora utxos shortcut calls the same blocktools_net::esplora_utxos function as utxo-fetch. The e2e script checks
that both commands print identical bytes.
Per-provider UTXO REST field differences
rpc passes these responses through as raw bytes. Only Esplora, Blockbook and bitcoind are normalised by the utxos shortcut.
Normalise other providers in your own pipeline:
| provider | txid / index fields | amount |
|---|---|---|
| Esplora | txid / vout |
value, integer base units |
| Blockbook | txid / vout |
value, decimal string in base units |
bitcoind scantxoutset |
txid / vout |
amount, coins (decimal) |
| chainz.cryptoid.info | tx_hash / tx_ouput_n (sic, misspelled) |
value, always 1e8 units, whatever the coin's decimals |
| qtum.info | transactionId / outputIndex |
value |
| WhatsOnChain | tx_hash / tx_pos |
value |
| Insight | txid / vout |
satoshis (plus amount in coins) |
Rate limits and pacing
rpc has no hidden retries, and it never runs requests in parallel. Each run makes one request, plus any requests a shortcut
needs, done in sequence. Pace the runs yourself (for example sleep between calls in a loop) and choose providers in this
order:
- novacoin explorers returned 503 at 8 parallel requests. Keep calls sequential.
- cryptoid allows about 1 call per 10 s without a key.
- the GRS explorer returned 429 on 2026-10-25.
- keyless TronGrid returns 429. Try PublicNode first for Tron reads.
These are SwapChan's field observations from /workspace/handoff/rpc-knife-requirements.md. rpc did not measure them
(unmeasured).
Credentials (wallet RPC)
--auth-file FILEor--auth-env NAMEaccepts these forms:user:passor a bitcoind.cookie→Authorization: BasicBearer TOKENor a bare token →Authorization: BearerHeader-Name: valuelines (e.g.TRON-PRO-API-KEY: ...) → those headers
--user,--password,--token,--header,-uand-Hare refused withcredential_on_command_line.- Userinfo in the URL (
http://u:p@host) is refused with the same code. - The raw value, the password, the token and the Base64 form are all redacted (
[REDACTED]) from stdout and from stderr. This covers the case where a server echoes them back. - Values shorter than 4 characters are not redacted, because that would mangle unrelated output. Do not use a password that short.
Safety: what is refused
The agent must never see keys, seeds or passwords. Every refusal exits 1 (status refused), and nothing is sent to the server:
| code | when |
|---|---|
method_exports_secrets |
a method from the table below that prints keys or seeds, or listdescriptors / gethdkeys with private=true |
method_imports_secrets |
the method takes a private key or seed as a parameter |
method_unlocks_wallet |
the method sends a wallet passphrase (walletpassphrase, personal_unlockAccount, createwallet with a passphrase) |
method_wallet_signs |
the wallet signs with its own key (always refused, flag or not) |
method_wallet_spends |
the wallet builds, signs and sends (sendtoaddress, payto, transfer, ...) |
wallet_sign_needs_flag |
eth_sendTransaction / eth_signTypedData_v4 without --allow-wallet-sign |
wallet_sign_undecodable |
the flagged request cannot be decoded completely into a summary |
wallet_sign_chain_mismatch |
the flagged request's chain id differs from the endpoint's eth_chainId (or from --chain-id) |
wallet_sign_chain_unknown |
the flagged request has no chain id and none was given with --chain-id |
method_not_allowlisted |
the method or REST path is not in the protocol's read allowlist (below) and --allow-unlisted was not given |
method_denied_by_policy |
the method is in the --policy file's rpc.deny list |
request_contains_secret_field |
the params contain a field named privateKey, password, mnemonic, seed, wif, xprv, ... |
broadcast_needs_sendraw |
a raw broadcast (eth_sendRawTransaction, sendrawtransaction, POST /tx, Tron broadcast, ...) outside sendraw |
response_contains_secret |
the response has a key-like field (privkey, mnemonic, ...) or an xprv/tprv string. Nothing is printed |
credential_on_command_line |
a credential was given as an argument or in the URL |
chain_id_mismatch |
--chain-id, or a sendraw tx's chain id, differs from the endpoint |
protocol_unknown |
a shortcut could not detect the protocol |
unsupported_for_protocol |
the shortcut does not exist for that protocol |
not_signed, chain_or_signature_mismatch, sendraw_unsupported_protocol |
sendraw input checks |
policy_unknown_key |
the policy file has a key other than version, rpc.deny or chains (exit 2) |
Other statuses on stderr: rpc_error (the server returned a JSON-RPC error, exit 1), http_error (non-2xx, a non-JSON
reply, a redirect, a timeout or an oversized response, exit 1), bad_input (exit 2), and aborted (sendraw got no terminal
or a wrong code, exit 1).
The read allowlist (fail closed)
ALLOW in tools/rpc/src/lib.rs is the only allowlist. A call is sent only if its method (JSON-RPC) or path (REST,
leading / and query string ignored, case ignored) is a row for the endpoint's protocol; when the protocol is not
detected, a row of any protocol counts. … marks a prefix row. Anything else is refused with method_not_allowlisted
and nothing is sent, unless --allow-unlisted is given. --allow-unlisted never opens a deny-list row, a secret-field
request or a broadcast: broadcasts still go only through sendraw. The lib test allow_table_documented fails if a row
is missing below, and the CLI test unlisted_methods_send_zero_bytes sends unlisted and made-up wallet-style methods
(EVM, bitcoind, Solana, Tron, Cosmos, Esplora, and a mixed batch) to a recording mock node and asserts it received
zero connections.
| protocol | allowed reads |
|---|---|
| evm | eth_blocknumber, eth_chainid, eth_getbalance, eth_gettransactioncount, eth_getcode, eth_getstorageat, eth_call, eth_estimategas, eth_gasprice, eth_maxpriorityfeepergas, eth_feehistory, eth_blobbaseprice, eth_getblockbynumber, eth_getblockbyhash, eth_getblockreceipts, eth_getblocktransactioncountbynumber, eth_getblocktransactioncountbyhash, eth_gettransactionbyhash, eth_gettransactionreceipt, eth_gettransactionbyblocknumberandindex, eth_gettransactionbyblockhashandindex, eth_getlogs, eth_getproof, eth_syncing, eth_protocolversion, eth_createaccesslist, net_version, net_listening, net_peercount, web3_clientversion, web3_sha3 |
| btc | getblockchaininfo, getblockcount, getbestblockhash, getblockhash, getblock, getblockheader, getblockstats, getchaintips, getdifficulty, getrawtransaction, decoderawtransaction, decodescript, decodepsbt, gettxout, gettxoutsetinfo, gettxoutproof, scantxoutset, getmempoolinfo, getrawmempool, getmempoolentry, testmempoolaccept, estimatesmartfee, getnetworkinfo, getconnectioncount, getindexinfo, validateaddress, getdescriptorinfo, uptime |
| sol | getaccountinfo, getbalance, getblock, getblockheight, getblocktime, getepochinfo, getfeeformessage, getgenesishash, gethealth, getlatestblockhash, getminimumbalanceforrentexemption, getmultipleaccounts, getprogramaccounts, getrecentprioritizationfees, getsignaturestatuses, getsignaturesforaddress, getslot, gettokenaccountbalance, gettokenaccountsbyowner, gettransaction, getversion, isblockhashvalid, simulatetransaction |
| tron | wallet/getnowblock, wallet/getblockbynum, wallet/getblockbyid, wallet/getaccount, wallet/getaccountresource, wallet/gettransactionbyid, wallet/gettransactioninfobyid, wallet/triggerconstantcontract, wallet/getchainparameters, wallet/getnodeinfo, wallet/validateaddress, walletsolidity/getaccount, walletsolidity/gettransactioninfobyid |
| cosmos | cosmos/base/tendermint/v1beta1/…, cosmos/bank/v1beta1/…, cosmos/auth/v1beta1/accounts/…, cosmos/staking/v1beta1/…, cosmos/distribution/v1beta1/…, cosmos/tx/v1beta1/txs/…, cosmos/tx/v1beta1/txs, cosmos/tx/v1beta1/simulate, cosmos/gov/v1beta1/…, cosmos/gov/v1/… |
| esplora | blocks/…, block/…, block-height/…, tx/…, address/…, scripthash/…, fee-estimates, mempool… |
| blockbook | api/v2, api/v2/tx/…, api/v2/tx-specific/…, api/v2/address/…, api/v2/xpub/…, api/v2/utxo/…, api/v2/block/…, api/v2/block-index/…, api/v2/estimatefee/… |
The deny-list (single source of truth)
DENY in tools/rpc/src/lib.rs is the only table. Matching ignores case and reads the whole method or path. The lib test
deny_table_documented fails if a row is missing below. SwapChan's key-export list is a subset of these rows
(swapchan_key_export_list_is_a_subset): dumpprivkey, dumpwallet, importprivkey, backupwallet, listdescriptors true, sethdseed,
personal_importRawKey, personal_unlockAccount, keystore exports (personal_*, parity_*, account_*), and Electrum
getprivatekeys/getseed.
| method (case-insensitive) | match | refusal code | family |
|---|---|---|---|
dumpprivkey |
exact | method_exports_secrets |
bitcoind |
dumpwallet |
exact | method_exports_secrets |
bitcoind |
backupwallet |
exact | method_exports_secrets |
bitcoind |
dumpmasterprivkey |
exact | method_exports_secrets |
bitcoind fork |
dumphdinfo |
exact | method_exports_secrets |
dashd |
importprivkey |
exact | method_imports_secrets |
bitcoind |
importwallet |
exact | method_imports_secrets |
bitcoind |
importmulti |
exact | method_imports_secrets |
bitcoind |
importdescriptors |
exact | method_imports_secrets |
bitcoind |
sethdseed |
exact | method_imports_secrets |
bitcoind |
upgradetohd |
exact | method_imports_secrets |
dashd |
signrawtransactionwithkey |
exact | method_imports_secrets |
bitcoind |
signmessagewithprivkey |
exact | method_imports_secrets |
bitcoind |
walletpassphrase |
exact | method_unlocks_wallet |
bitcoind |
walletpassphrasechange |
exact | method_unlocks_wallet |
bitcoind |
encryptwallet |
exact | method_unlocks_wallet |
bitcoind |
signrawtransactionwithwallet |
exact | method_wallet_signs |
bitcoind |
signrawtransaction |
exact | method_wallet_signs |
bitcoind (old) |
walletprocesspsbt |
exact | method_wallet_signs |
bitcoind |
signmessage |
exact | method_wallet_signs |
bitcoind |
sendtoaddress |
exact | method_wallet_spends |
bitcoind |
sendmany |
exact | method_wallet_spends |
bitcoind |
send |
exact | method_wallet_spends |
bitcoind |
sendall |
exact | method_wallet_spends |
bitcoind |
sendfrom |
exact | method_wallet_spends |
bitcoind (old) |
bumpfee |
exact | method_wallet_spends |
bitcoind |
personal_unlockaccount |
exact | method_unlocks_wallet |
geth |
personal_importrawkey |
exact | method_imports_secrets |
geth |
personal_exportrawkey |
exact | method_exports_secrets |
geth |
personal_ |
prefix | method_wallet_signs |
geth |
eth_sign |
exact | method_wallet_signs |
geth |
eth_signtransaction |
exact | method_wallet_signs |
geth |
eth_signtypeddata |
exact | method_wallet_signs |
geth |
eth_signtypeddata_v1 |
exact | method_wallet_signs |
wallet |
eth_signtypeddata_v3 |
exact | method_wallet_signs |
wallet |
account_ |
prefix | method_wallet_signs |
clef |
parity_exportaccount |
exact | method_exports_secrets |
openethereum |
parity_ |
prefix | method_wallet_signs |
openethereum |
getprivatekeys |
exact | method_exports_secrets |
electrum |
getseed |
exact | method_exports_secrets |
electrum |
getmasterprivate |
exact | method_exports_secrets |
electrum |
export_privkeys |
exact | method_exports_secrets |
electrum |
importprivkey |
exact | method_imports_secrets |
electrum |
signtransaction |
exact | method_wallet_signs |
electrum |
password |
exact | method_unlocks_wallet |
electrum |
payto |
exact | method_wallet_spends |
electrum |
paytomany |
exact | method_wallet_spends |
electrum |
query_key |
exact | method_exports_secrets |
monero-wallet-rpc |
restore_deterministic_wallet |
exact | method_imports_secrets |
monero-wallet-rpc |
generate_from_keys |
exact | method_imports_secrets |
monero-wallet-rpc |
open_wallet |
exact | method_unlocks_wallet |
monero-wallet-rpc |
transfer |
exact | method_wallet_spends |
monero-wallet-rpc |
sweep_all |
exact | method_wallet_spends |
monero-wallet-rpc |
wallet/easytransfer |
prefix | method_imports_secrets |
tron |
wallet/gettransactionsign |
exact | method_imports_secrets |
tron |
wallet/addtransactionsign |
exact | method_imports_secrets |
tron |
wallet/createaddress |
exact | method_exports_secrets |
tron |
wallet/generateaddress |
exact | method_exports_secrets |
tron |
wallet/getaddressbyprivatekey |
exact | method_imports_secrets |
tron |
wallet/getspendingkey |
exact | method_exports_secrets |
tron |
wallet/getexpandedspendingkey |
exact | method_exports_secrets |
tron |
wallet/getakfromask |
exact | method_exports_secrets |
tron |
wallet/getnkfromnsk |
exact | method_exports_secrets |
tron |
wallet/getnewshieldedaddress |
exact | method_exports_secrets |
tron |
wallet/createshieldedtransaction |
exact | method_imports_secrets |
tron |
wallet/createshieldedcontractparameters |
exact | method_imports_secrets |
tron |
wallet/createspendauthsig |
exact | method_imports_secrets |
tron |
keys |
prefix | method_exports_secrets |
cosmos lcd (legacy) |
eth_sendtransaction |
exact | wallet_sign_needs_flag |
geth / wallet (opened only by --allow-wallet-sign) |
eth_signtypeddata_v4 |
exact | wallet_sign_needs_flag |
geth / wallet (opened only by --allow-wallet-sign) |
A --policy FILE ({"version":1,"rpc":{"deny":["eth_call","debug_*"]}}) can only add refusals. A trailing * makes the
entry a prefix match. No key exists for allowing a method, and unknown keys are refused.
--allow-wallet-sign
Only eth_sendTransaction and eth_signTypedData_v4 can be opened, and only with the flag. With the flag, the wallet's own
UI is the human confirmation. Before anything is sent, rpc:
- Decodes the request completely, or refuses it with
wallet_sign_undecodable.- For sendTransaction, only known keys are accepted.
fromandtomust be 20-byte addresses, and contract creation is refused. Quantities must be hex, anddataandinputmust agree. - For typed data,
types,primaryType,domainandmessageare required, and the message fields must equal the primary type's fields exactly. No hidden or extra fields are allowed.
- For sendTransaction, only known keys are accepted.
- Fetches the endpoint's
eth_chainId. The call is refused if it differs from the request's chain id (or from--chain-id), or if neither one gives a chain id. - Prints this summary on stderr, then sends the exact stdin or argument bytes:
wallet-sign eth_sendTransaction (the wallet will ask you to approve)
from: 0x1111111111111111111111111111111111111111
to: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
value: 0 wei
data: selector 0xa9059cbb, 68 bytes
chain id: 1
endpoint chain id: 1 (matches)
wallet-sign eth_signTypedData_v4 (the wallet will ask you to approve)
signer: 0x1111111111111111111111111111111111111111
domain:
name: USD Coin
chainId: 1
verifyingContract: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
primaryType: Permit
message:
owner: 0x1111111111111111111111111111111111111111
spender: 0x3333333333333333333333333333333333333333
value: 1000000
nonce: 0
deadline: 1767225600
endpoint chain id: 1 (matches)
The flag does not open eth_sign, personal_sign, eth_signTransaction, the other personal_* methods, or
eth_signTypedData v1/v3. Those are always refused. TronLink signing is a browser wallet, not an RPC method, so it is out of
scope.
Untrusted responses
- Response bodies are data only. When stdout is a terminal, control characters are replaced so that escape sequences cannot act on the terminal.
- Responses larger than 8 MiB are refused.
- Redirects are never followed, to any host. A 3xx is an
http_error, and v1 has no flag to change this. - Only https URLs are accepted, except plain http to localhost/127.0.0.1.
- Shortcuts re-check what the server returns: txid hex, amounts, and the prev_tx hex.
Cookbook (mock round trips, run by scripts/e2e.d/rpc.sh)
# BTC: rpc utxos | tx-build | tx-sign | rpc sendraw (esplora mock)
rpc --protocol esplora $U utxos $ADDR > utxos.jsonl
tx-build --chain btc --utxos utxos.jsonl --to $DEST --sweep --fee-rate 5 > unsigned
tx-sign --tx unsigned --expect-to $DEST < key > signed # typed code on the terminal
rpc --protocol esplora $U sendraw < signed # summary + typed code; server id compared with local txid
# EVM: rpc account | tx-build | tx-sign | rpc sendraw
rpc --protocol evm $U account $ADDR > acct.json
tx-build --chain eth --chain-id 1 --account acct.json --to $DEST --sweep > unsigned
tx-sign --tx unsigned --chain-id 1 --expect-to $DEST < key > signed
rpc --protocol evm $U sendraw < signed # also refuses when the tx chain id differs from eth_chainId
Per-chain recipes live in the docs index, which the docs worker owns.
What was run, and what is untested
Run on Linux (x86_64) on 2026-10-25:
cargo test -p rpc: the lib tests, plus CLI tests against an in-process mock that records the raw request bytes.scripts/e2e.d/rpc.sh.- The single public read shown above.
Untested:
- Any real broadcast, including Flashbots, Tron broadcast and every mainnet
sendraw. - Real bitcoind, geth, Solana, Tron, Cosmos and Blockbook servers. Only mocks were used, except for the one publicnode read.
- macOS.
- The provider pacing figures (taken from SwapChan's notes).
- A wasm facade, which is not exposed. The pure logic in
tools/rpc/src/lib.rsneeds no network, but v1 ships only the CLI.