Api

Setup Guide

Install and run Hydra App with Docker Compose

This guide walks you through running Hydra App and connecting to it from a client. The app is the gRPC server that exposes every API documented in this section.

Prerequisites

  • Docker & Docker Compose
  • ~2 GB free disk for the data directory
  • A funded testnet wallet for whatever networks you enable (Bitcoin Signet, Ethereum Sepolia, Arbitrum Sepolia)

Building a bot or trading agent? After completing the setup below, jump to the Bot Quickstart for a minimum runnable template.


Architecture

Your Application (gRPC client)
          ↓
    gRPC over HTTP/2  (or gRPC-Web)
          ↓
Hydra App  (your machine, default :5003)
          ↓
   ├─ authentication-service  (auth tokens)
   ├─ liquidity-service        (channel leases — see Lease API)
   ├─ orderbook                (markets & matching)
   ├─ hydra-proxy              (Electrum / Esplora / Web3 / Subgraph proxy)
   └─ Local data store         (fjall key-value DB on disk)

Hydra App brokers everything. You never talk to the orderbook, liquidity service, or chain RPCs directly — the app does it for you and exposes a single gRPC surface.

Authentication is handled by Hydra App, not by your client. The app obtains and refreshes tokens with the authentication service automatically, using the seed/MNEMONIC you set up below. Clients connect to the local app over plain gRPC with no API keys, no headers, no auth handshake. The auth_grpc_url / auth_ws_url and proxy_auth fields in config.yaml configure Hydra App's upstream connection — not your client. Your client never sees those URLs.


Step 1: Configuration files

Create a working directory and two files: config.yaml and .env.

mkdir hydra-app && cd hydra-app

config.yaml

Endpoints redacted. The staging endpoints, subgraph URLs, and Lithium contract addresses below are placeholders. The real values are distributed privately to approved testers — open a ticket in our Discord to request access and do not redistribute. The structure of this file (every field name, every nesting level) is accurate; only the access-controlled values are masked.

The block below is the staging template. Copy it, fill in the placeholders (anywhere you see <...>) with the values you receive, and adjust ports / tokens to taste.

settings:
  data_path_name: hydra-app-3
  critical_db_option: fjall
  db_option: fjall
  compression:
    type: zstd
    preset: balanced
  server_port: 5003
  metrics_port: 9090

authentication_config:
  auth_grpc_url: <auth-grpc-url>             # provided by the team
  auth_ws_url:   <auth-ws-url>               # provided by the team

proxy_url: <hydra-proxy-url>                  # provided by the team

# backup_config:
#   Uncomment if you want server-side wallet backups. NOT recommended for
#   liquidity providers — adds latency on high-volume nodes.
#   grpc_url: <backup-service-url>

liquidity_config:
  liquidity_url: <liquidity-service-url>     # provided by the team

orderbook_config:
  orderbook_url: <orderbook-url>             # provided by the team

networks:
  - protocol: bitcoin
    network: bitcoin-signet
    blockchain:
      type: electrum
      urls:
        - <electrum-ws-url>                   # provided by the team
      esplora_url:    <esplora-url>           # provided by the team
      waterfalls_url: <waterfalls-url>        # provided by the team
      proxy_auth: true
    lightning_tcp_port: 19735
    lightning_ws_port: 19736
    gossip_sync:
      type: rgs
      rgs_server_url: https://rapidsync.lightningdevkit.org

  - protocol: evm
    network: ethereum-sepolia
    web3_provider:
      url: <ethereum-sepolia-web3-ws-url>     # provided by the team
      proxy_auth: true
    lithium:
      subgraph:
        http_url: <ethereum-subgraph-url>     # provided by the team
      contract_address: <lithium-eth-contract>  # provided by the team
      tcp_quic_port: 29980
      ws_port: 29981
    tokens:
      - "ERC20:0x8cd0dA3d001b013336918b8Bc4e56D9DDa1347E0"  # USDC on Sepolia (Circle public faucet)

  - protocol: evm
    network: arbitrum-sepolia
    web3_provider:
      url: <arbitrum-sepolia-web3-ws-url>     # provided by the team
      proxy_auth: true
    lithium:
      subgraph:
        http_url: <arbitrum-subgraph-url>     # provided by the team
      contract_address: <lithium-arb-contract>  # provided by the team
      tcp_quic_port: 29982
      ws_port: 29983
    tokens:
      # Add the asset contracts you want active on this network.
      # The team provides a list alongside the endpoint values.
      - "ERC20:<arbitrum-asset-1>"
      - "ERC20:<arbitrum-asset-2>"

Why redacted? During the alpha, the staging endpoints are not public. Every URL marked <...> and every <lithium-*-contract> is access-controlled. Public values (chain IDs, RGS gossip server, well-known testnet token contracts) are kept inline because they're already publicly known.

What each block does

BlockPurpose
settings.server_portThe gRPC port your client connects to (here 5003). Map this in Docker.
settings.metrics_portPrometheus-style metrics. Optional — expose if you want monitoring.
settings.data_path_nameOn-disk directory name for the wallet/DB. Bump this (e.g. hydra-app-3hydra-app-4) to start fresh.
settings.db_option / critical_db_optionStorage backend. fjall is the current default.
settings.compressionOn-disk compression. zstd + balanced is sensible.
authentication_configHydranet auth service. Hydra App handles tokens for you.
proxy_urlSingle endpoint for the proxy that fronts Electrum, Esplora, Web3, and Subgraph requests.
liquidity_configProvider for service-backed channel liquidity (used by the Lease API).
orderbook_configThe matching engine.
networks[]Per-network blockchain config. Each entry is a network the app will activate.

Per-network blocks

For Bitcoin networks:

FieldDescription
networkOne of bitcoin-mainnet, bitcoin-signet, bitcoin-testnet, bitcoin-regtest
blockchain.typeelectrum is currently the only supported type
blockchain.urlsElectrum WebSocket endpoints
blockchain.esplora_urlEsplora HTTP endpoint for richer queries
blockchain.waterfalls_urlWaterfalls (block explorer) endpoint
blockchain.proxy_authIf true, Hydra App authenticates with the proxy automatically
lightning_tcp_portLightning peer TCP port
lightning_ws_portLightning peer WebSocket port (for browser clients)
gossip_syncLightning gossip source. rgs (Rapid Gossip Sync) is the default.

For EVM networks:

FieldDescription
networke.g. ethereum-mainnet, ethereum-sepolia, arbitrum-one, arbitrum-sepolia
web3_provider.urlWebSocket Web3 RPC endpoint
web3_provider.proxy_authSame as Bitcoin — proxy auth handshake
lithium.subgraph.http_urlSubgraph endpoint Hydra App uses to index Lithium events
lithium.contract_addressLithium contract on the network
lithium.tcp_quic_port / ws_portLithium peer ports
tokensList of ERC20:<contract> strings for tokens you want active

The token list determines which assets you can hold balances of and trade. To add a token after first run, add it here, restart the app, then call asset.AddToken for it.

.env

RUST_LOG=info,hydra=debug
MNEMONIC=
PASSWORD=
GRPC_PORT=5003
DATA_PATH_NAME=hydra-app-3
VariablePurpose
RUST_LOGLog filter. info is fine for daily use; debug only for troubleshooting.
MNEMONICOptional seed phrase. Empty = interactive mode (CLI prompt). Set = daemon mode. See security note below.
PASSWORDEncryption password for the on-disk wallet. Required in daemon mode.
GRPC_PORTMust match settings.server_port in config.yaml.
DATA_PATH_NAMEMust match settings.data_path_name.

⚠️ Mnemonic & password security

Putting MNEMONIC and PASSWORD in plain .env is convenient for testnet but never do this on mainnet without protections. At minimum:

  • Mount the .env from a secret manager (Docker secrets, Kubernetes secrets, HashiCorp Vault, AWS Secrets Manager).
  • Restrict file permissions (chmod 600 .env).
  • Never commit .env — add it to .gitignore.
  • Prefer interactive mode for development; reserve daemon mode for hardened production hosts.

Step 2: Choose a mode

ModeWhen to useHow
InteractiveLocal dev, exploration, first runLeave MNEMONIC="". The app prompts you on stdin to create or unlock a wallet. Requires tty: true and stdin_open: true in compose.
DaemonBots, servers, CISet MNEMONIC and PASSWORD. The app boots the wallet automatically, no terminal needed.

For a trading bot, you almost always want daemon mode. For your first run on a new machine, use interactive mode once to confirm the wallet generates and the networks initialize cleanly.


Step 3: docker-compose.yml

services:
  hydra-app:
    image: ghcr.io/offchain-dex/hydra-app-tester:latest
    volumes:
      - ./config.yaml:/app/config.yaml
      - hydra-data:/app/data
    env_file:
      - .env
    environment:
      - GRPC_PORT=${GRPC_PORT}
      - DATA_PATH_NAME=${DATA_PATH_NAME}
    ports:
      - "5003:5003"      # gRPC — match server_port
      - "9090:9090"      # metrics — optional, drop if you don't need it
      - "19735:19735"    # Bitcoin Lightning TCP
      - "19736:19736"    # Bitcoin Lightning WS
      - "29980:29980"    # Ethereum Lithium TCP/QUIC
      - "29981:29981"    # Ethereum Lithium WS
      - "29982:29982"    # Arbitrum Lithium TCP/QUIC
      - "29983:29983"    # Arbitrum Lithium WS
    tty: true
    stdin_open: true
    restart: unless-stopped

volumes:
  hydra-data:

The Lightning and Lithium peer ports are how other nodes connect to you. If you only want to make outgoing channels, you can drop them. If you want to receive channel openings or run as a liquidity provider, expose them publicly.


Step 4: Start the app

docker compose up -d
docker compose logs -f hydra-app

You're ready when you see something like:

INFO  hydra_app::server: gRPC server listening on 0.0.0.0:5003
INFO  hydra_app::network: bitcoin-signet activated
INFO  hydra_app::network: ethereum-sepolia activated
INFO  hydra_app::network: arbitrum-sepolia activated

If you ran in interactive mode without MNEMONIC set, you'll be prompted at this point — docker attach to interact with the prompt:

docker attach hydra-app

Use Ctrl-P Ctrl-Q to detach without stopping the container.


Step 5: Generate a client from the proto files

Hydra App speaks raw gRPC. To call it from your language, you need generated client stubs from the .proto schema files. The schema lives on this docs site as a downloadable bundle, plus individually browsable .proto files.

⬇ Download hydra-protos.zip

Or grab a single file:

curl -O https://docs.hydranet.ai/proto/hydra-protos.zip && unzip hydra-protos.zip -d hydra-protos
# → hydra-protos/{wallet,liquidity,orderbook,...}.proto

Each individual .proto is also at https://docs.hydranet.ai/proto/<name>.proto — handy for import paths or quick browsing.

Generate client stubs

Pick your language. All examples assume the protos are unpacked at ./hydra-protos/.

# Buf is the modern polyglot codegen tool. Install: https://buf.build/docs/installation
# Add a buf.gen.yaml describing your target languages, then:
buf generate hydra-protos
# → produces stubs for every plugin you configured

After running the relevant command, you'll have generated source files (one per .proto) you can import in your project. The exact import path depends on your language — see your generator's output.

Already have a Python client? If you've built one for your own bot, drop it in your repo and skip codegen. Want to share it back? Open a Discord ticket — we may be interested in publishing community SDKs.

Future: post-launch the protos will move to Buf Schema Registry at buf.build/offchain-dex/hydra and codegen will collapse to a one-liner: buf generate buf.build/offchain-dex/hydra. Until then, the static download above is the canonical source.


Step 6: Sanity-check from the client side

Once the app is running and you have generated stubs, hit it from your language. Examples below assume your generated code lives where the imports show.

import { AppServiceClient } from './proto/AppServiceClientPb'
import { GetNetworksRequest } from './proto/app_pb'

const client = new AppServiceClient('http://localhost:5003')

const response = await client.getNetworks(new GetNetworksRequest(), {})
for (const n of response.getNetworksList()) {
  console.log(`Network: protocol=${n.getProtocol()} id=${n.getId()}`)
}

You should see one entry per network you enabled in config.yaml.


Network identifiers (config string ↔ proto fields)

config.yaml uses human-readable network names. The gRPC API uses { protocol, id } integers and hex strings. Map between them:

Bitcoin (protocol: 1 / PROTOCOL_BITCOIN)

config.yaml networkproto id (hex magic bytes)
bitcoin-mainnetf9beb4d9
bitcoin-testnet0b110907
bitcoin-signet0a03cf40
bitcoin-regtestfabfb5da

EVM (protocol: 2 / PROTOCOL_EVM)

config.yaml networkproto id (decimal chain ID)
ethereum-mainnet1
ethereum-sepolia11155111
arbitrum-one42161
arbitrum-sepolia421614
polygon-mainnet137
optimism-mainnet10

When passing a Network to any RPC, use { protocol, id }. The name and chain_id fields you may see in older client code no longer exist.


Troubleshooting

SymptomLikely causeFix
gRPC server listening never appearsConfig parse errorCheck docker compose logs for the YAML parse line
failed to connect to electrumProxy auth failingConfirm your auth credentials and network connectivity to proxy_url
Wallet prompts loop in daemon modeMNEMONIC set but PASSWORD blankSet both, or unset both for interactive
Containers restart-loop on first runPort collisionCheck lsof -i :5003 and the Lightning/Lithium ports
bitcoin-signet activated but no balanceWallet is brand newSend testnet sats to wallet.GetDepositAddress output

Next steps


Copyright © 2025