JSON-RPC Interface
Hydra App speaks JSON-RPC 2.0 on the same port as the gRPC server. Use it from any HTTP client without generating protobuf stubs.
If you can run a gRPC client (Node, Go, Rust, Python), prefer that — it's typed and has streaming. JSON-RPC is the right fit for shell scripts, lightweight integrations, debugging, and languages without good gRPC tooling.
Overview
| Property | Value |
|---|---|
| Protocol | JSON-RPC 2.0 |
| Transport | HTTP POST |
| URL | http://<host>:<settings.server_port> (default http://127.0.0.1:5003) |
| Path | Anything — /, /jsonrpc, /rpc all route to the same handler |
| Content-Type | application/json |
| Streaming methods | Not supported over JSON-RPC. Calls to event_subscribe*, orderbook_subscribe*, swap_subscribeSimpleSwaps return -32603 Internal error. Use the gRPC interface for streams. |
Examples on this page assume http://127.0.0.1:5003. If you're going through an SSH tunnel, replace with whatever local port the tunnel forwards.
Quick start
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"app_getNetworks"}'
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"networks": [
{ "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" },
{ "protocol": "PROTOCOL_EVM", "id": "11155111" },
{ "protocol": "PROTOCOL_EVM", "id": "421614" }
]
}
}
Params shape — important
Hydra App's JSON-RPC handler uses OpenRPC by-name params. There are two valid forms; bare object params don't work.
{
"jsonrpc": "2.0",
"id": 1,
"method": "wallet_getBalances",
"params": [
{ "network": { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" } }
]
}
The third form returns
-32602 Invalid params: missing field 'req'. Use positional — it's idiomatic JSON-RPC and matches whatrpc.discoveradvertises.
For methods with no params (app_getNetworks, app_getPublicKey, app_getReferral, orderbook_cancelAllOrders, etc.) you can omit the params field entirely or send [].
Enum encoding
Proto enum values are accepted as either the full constant name (string) or the numeric value (int):
// Both of these work:
{ "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" }
{ "protocol": 1, "id": "0a03cf40" }
Responses use the string form. If you compare returned values, expect "PROTOCOL_BITCOIN" not 1.
| Enum | String value | Int value |
|---|---|---|
Protocol | PROTOCOL_BITCOIN | 1 |
Protocol | PROTOCOL_EVM | 2 |
OrderSide | ORDER_SIDE_BUY | 1 |
OrderSide | ORDER_SIDE_SELL | 2 |
See the proto files in /proto for the complete enum tables.
Discovering methods
Every Hydra App build exposes the full OpenRPC schema via rpc.discover:
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"rpc.discover"}' \
| jq '.result.methods | length'
# → 119
The result includes every method's JSON-Schema for params and result. This is the authoritative reference — it always matches the proto your app was built from. Use it to discover param fields, type structures, and return shapes:
# Pretty-print the schema for one method
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"rpc.discover"}' \
| jq '.result.methods[] | select(.name == "wallet_getBalances")'
Naming convention
Methods follow <service>_<methodCamelCase>. The service name is the lowercase JSON-RPC namespace (the same one shown in each per-service doc page); the method name is the gRPC method in lowerCamelCase.
| gRPC | JSON-RPC |
|---|---|
AppService.GetNetworks | app_getNetworks |
WalletService.GetBalances | wallet_getBalances |
OrderbookService.CreateOrder | orderbook_createOrder |
LiquidityService.RequestChannelLiquidity | liquidity_requestChannelLiquidity |
WatchOnlyNodeService.GetChannel | watchOnlyNode_getChannel |
The
liquiditynamespace is the renamed rental → lease flow; older docs that mentionrental_*methods are out of date — those methods don't exist on current builds.
Field-name conversion
Proto fields use snake_case; JSON-RPC requests and responses use camelCase:
| Proto field | JSON field |
|---|---|
payment_request | paymentRequest |
lease_duration_seconds | leaseDurationSeconds |
priority_fee_per_unit | priorityFeePerUnit |
When in doubt, run rpc.discover for the method and read the schema.
Worked examples
Examples below are verified against a live Hydra App on Bitcoin Signet + Ethereum Sepolia + Arbitrum Sepolia. Copy verbatim and substitute your endpoint port if different from 5003.
Get balances on Bitcoin Signet
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "wallet_getBalances",
"params": [
{ "network": { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" } }
]
}' | jq
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"balances": {
"0x0000000000000000000000000000000000000000000000000000000000000000": {
"onchain": {
"confirmed": { "value": "1.66883943" },
"trustedPending": { "value": "0.00000000" },
"pending": { "value": "0.00000000" }
},
"offchain": {
"freeLocal": { "value": "0.24623440" },
"freeRemote": { "value": "0.24376560" },
"pendingLocal": { "value": "0" },
"pendingRemote": { "value": "0" },
"unavailableLocal": { "value": "0" },
"unavailableRemote": { "value": "0" },
"payingLocal": { "value": "0.00000000" },
"payingRemote": { "value": "0.00000000" },
"unspendableLocalReserve": { "value": "0.00500660" },
"unspendableRemoteReserve":{ "value": "0.00499340" }
}
}
}
}
}
Map keys are asset IDs (zero-padded hash for native assets, ERC-20 contract for tokens). All values are
DecimalString— human-readable BTC, not satoshis. See Common Patterns: Amounts & Decimals.
Get the latest block number on Sepolia
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "blockchain_getBlockNumber",
"params": [
{ "network": { "protocol": "PROTOCOL_EVM", "id": "11155111" } }
]
}'
# → {"jsonrpc":"2.0","id":2,"result":{"blockNumber":"10755913"}}
Get a Signet deposit address
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "wallet_getDepositAddress",
"params": [
{ "network": { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" } }
]
}'
# → {"jsonrpc":"2.0","id":3,"result":{"address":"tb1p..."}}
Get the application's public key
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":4,"method":"app_getPublicKey"}'
# → {"jsonrpc":"2.0","id":4,"result":{"publicKey":"<base64-32-bytes>"}}
Place a market sell order on the BTC/USDC pair
Streaming the fill confirmation is not available on JSON-RPC — for that you'll need to call
orderbook_subscribeMarketEventsover gRPC. The example below just places the order; the order ID it returns can be polled withorderbook_getOrder.
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 5,
"method": "orderbook_createOrder",
"params": [{
"orderVariant": {
"marketOrder": {
"base": { "protocol": "PROTOCOL_BITCOIN", "networkId": "0a03cf40", "assetId": "BTC" },
"quote": { "protocol": "PROTOCOL_EVM", "networkId": "11155111", "assetId": "ERC20:0x8cd0dA3d001b013336918b8Bc4e56D9DDa1347E0" },
"side": "ORDER_SIDE_SELL",
"amount": { "base": { "amount": { "value": "0.0005" } } }
}
}
}]
}'
# → {"jsonrpc":"2.0","id":5,"result":{"orderId":"..."}}
Provision a service-backed channel via the Lease API
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 6,
"method": "liquidity_requestChannelLiquidity",
"params": [{
"network": { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" },
"operation": {
"open": {
"assetLiquidity": {
"BTC": {
"serverAmount": { "value": "0" },
"clientAmount": { "value": "0.001" }
}
}
}
},
"txFeeRate": {
"maxFeePerUnit": { "value": "50" },
"priorityFeePerUnit": { "value": "5" }
},
"paymentNetwork": { "protocol": "PROTOCOL_BITCOIN", "id": "0a03cf40" },
"paymentAssetId": "BTC",
"offchainFeePayment": {}
}]
}'
# → {"jsonrpc":"2.0","id":6,"result":{"txid":"...","channelId":"..."}}
Idiomatic clients
class HydraClient {
private nextId = 0
constructor(private url = 'http://127.0.0.1:5003') {}
async call<R = unknown>(method: string, ...positionalArgs: unknown[]): Promise<R> {
const body = {
jsonrpc: '2.0',
id: ++this.nextId,
method,
...(positionalArgs.length ? { params: positionalArgs } : {}),
}
const resp = await fetch(this.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
const json = await resp.json()
if (json.error) {
const e = new Error(`${json.error.code}: ${json.error.message}`)
;(e as any).rpcError = json.error
throw e
}
return json.result as R
}
}
const hydra = new HydraClient()
const { networks } = await hydra.call<{ networks: Array<{ protocol: string; id: string }> }>(
'app_getNetworks',
)
console.log(networks)
const balances = await hydra.call(
'wallet_getBalances',
{ network: { protocol: 'PROTOCOL_BITCOIN', id: '0a03cf40' } },
)
console.log(balances)
Batched requests
JSON-RPC 2.0 batching is supported — send an array of requests, get an array of responses (order may differ; match by id).
curl -s -X POST http://127.0.0.1:5003 \
-H "Content-Type: application/json" \
-d '[
{"jsonrpc":"2.0","id":1,"method":"app_getNetworks"},
{"jsonrpc":"2.0","id":2,"method":"app_getPublicKey"}
]'
Errors
Standard JSON-RPC 2.0 error envelope:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid params",
"data": "missing field `req` at line 1 column 42"
}
}
| Code | Meaning | What it usually indicates for Hydra |
|---|---|---|
-32700 | Parse error | Malformed JSON |
-32600 | Invalid Request | Missing jsonrpc / method |
-32601 | Method not found | Method name is wrong (typo, or a method that's only on a newer build — check rpc.discover) |
-32602 | Invalid params | Wrong shape — most common cause is a flat {...} instead of [{...}] or { "req": {...} }; second most common is a missing required field |
-32603 | Internal error | Server-side problem; check Hydra App logs |
Application-level errors (insufficient balance, channel not active, peer unreachable, lease provider not initialized, etc.) come back as JSON-RPC errors in the -32000 to -32099 range — -32010 is the most commonly observed for runtime preconditions. Example:
{ "jsonrpc": "2.0", "id": 6,
"error": { "code": -32010, "message": "Liquidity manager not initialized" } }
The message mirrors the underlying gRPC status text. See Errors for retry guidance per gRPC status — the same rules apply here.
See also
- Setup Guide — including how
server_portmaps to the JSON-RPC URL - Bot Quickstart — same flow over native gRPC
- Common Patterns —
DecimalString, fee structures, amounts - Errors — full gRPC status code catalog with retry guidance
rpc.discover— authoritative method reference baked into every Hydra App build