General API
The General API exposes Hydra App's AppService — application-level operations including network discovery, public key retrieval, payment preimage lookup, archive retention, and invite / referral management.
JSON-RPC namespace: app
Endpoints
The Network type
Most other APIs accept a Network to identify a specific blockchain. It has just two fields:
| Field | Type | Description |
|---|---|---|
protocol | Protocol enum | PROTOCOL_BITCOIN (1) or PROTOCOL_EVM (2) |
id | string | Network identifier — magic bytes (hex) for Bitcoin, decimal chain ID for EVM. Staging values: "0a03cf40" (Bitcoin Signet), "11155111" (Ethereum Sepolia), "421614" (Arbitrum Sepolia). |
See the network identifier table in the Setup Guide for every supported network.
Get Networks
Returns the list of blockchain networks that have been initialized and are currently active in this application instance.
Method: GetNetworks
Parameters: None
Response:
| Field | Type | Description |
|---|---|---|
networks | Network[] | Active networks |
Example Request:
import { AppServiceClient } from './proto/AppServiceClientPb'
import { GetNetworksRequest } from './proto/app_pb'
const client = new AppServiceClient('http://localhost:5001')
const response = await client.getNetworks(new GetNetworksRequest(), {})
response.getNetworksList().forEach(n => {
console.log(`Protocol ${n.getProtocol()} / id ${n.getId()}`)
})
Example Response:
{
"networks": [
{ "protocol": 1, "id": "0a03cf40" },
{ "protocol": 2, "id": "11155111" },
{ "protocol": 2, "id": "421614" }
]
}
Get Public Key
Returns the Ed25519 public key of this application instance. The key uniquely identifies the application and is derived from the user's mnemonic seed. It does not depend on a network.
Method: GetPublicKey
Parameters: None
Response:
| Field | Type | Description |
|---|---|---|
public_key | bytes | Ed25519 public key (32 bytes) |
Example Request:
import { GetPublicKeyRequest } from './proto/app_pb'
const response = await client.getPublicKey(new GetPublicKeyRequest(), {})
const pubkey = response.getPublicKey_asU8()
console.log('Public key (hex):', Buffer.from(pubkey).toString('hex'))
Example Response:
{
"public_key": "AbCdEf0123...="
}
The bytes field is base64 in JSON-RPC; in gRPC it's a raw
bytes.
Get Preimage
Looks up a payment preimage by its hash. Returns the preimage if it has been revealed or registered, or empty if the preimage is not yet known.
Method: GetPreimage
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
payment_hash | bytes | YES | Payment hash to look up (32 bytes) |
Response:
| Field | Type | Description |
|---|---|---|
payment_preimage | bytes (optional) | The preimage (32 bytes), or empty if not yet known |
Example Request:
import { GetPreimageRequest } from './proto/app_pb'
const request = new GetPreimageRequest()
request.setPaymentHash(Buffer.from('a1b2c3...32bytes...', 'hex'))
const response = await client.getPreimage(request, {})
const preimage = response.getPaymentPreimage_asU8()
if (preimage && preimage.length > 0) {
console.log('Preimage:', Buffer.from(preimage).toString('hex'))
} else {
console.log('Preimage not yet known')
}
Example Response:
{ "payment_preimage": "9f8e7d6c..." }
Archive Prune
Redesigned 2026-07-08 — was the synchronous PruneArchive (added 2026-05-26).
Operator-driven retention for archive-side settled history — settled wallet transactions and settled payments — bounded by max age and/or max count. Pending entries are never pruned. As of 2026-07-08 pruning is an asynchronous job, not a single blocking call: you start a job, then poll its status or subscribe to its events. At most one prune job runs per network.
Breaking (2026-07-08): the old
PruneArchiveRPC (withtotal_pruned/per_table) was removed. Migrate toStartArchivePrune+GetArchivePruneStatus(orSubscribeArchivePruneEvents).Pruning a settled payment also deletes its stored preimage — a pruned payment's preimage is no longer served by
GetPreimage.
Start Archive Prune
Method: StartArchivePrune
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | YES | Network whose archive to prune (Bitcoin and EVM networks each have their own archive instance) |
max_age_secs | uint64 | NO* | Keep only entries younger than this many seconds (from server now) |
max_items | uint64 | NO* | Keep at most this many newest entries per archive table (≥ 1) |
*At least one filter must be set. Filters compose intersectively — an entry is kept only if it satisfies every set filter.
Response: job (ArchivePruneJob) + newly_started (bool). When newly_started is false, a job was already running and its (possibly different) descriptor is returned instead of starting a new one.
Get Archive Prune Status
Method: GetArchivePruneStatus — parameters: network (Network, YES). The authoritative state.
Response: running (RunningArchivePrune, optional — the live job + an ArchivePruneProgress snapshot + cancel_requested) and last (ArchivePruneRecord, optional — the most recently finished job since process start, with a completed / failed / cancelled outcome). Both unset ⇒ no prune has run since start.
Cancel Archive Prune
Method: CancelArchivePrune — parameters: network (Network, YES). Requests cancellation; the job stops at its next chunk boundary and finishes with a cancelled outcome. Returns cancelled = false when no job is running. Idempotent.
Subscribe Archive Prune Events
Method: SubscribeArchivePruneEvents (server-streaming) — no parameters; the stream covers all networks. Each ArchivePruneEvent carries its ArchivePruneJob and one of started / progress / completed / failed / cancelled. Delivery is best-effort — on stream end, re-subscribe and reconcile with GetArchivePruneStatus.
Example — start and poll:
import { StartArchivePruneRequest, GetArchivePruneStatusRequest } from './proto/app_pb'
const start = new StartArchivePruneRequest()
start.setNetwork({ protocol: 1, id: '0a03cf40' })
start.setMaxAgeSecs(60 * 60 * 24 * 90) // keep 90 days
start.setMaxItems(100_000) // and at most 100k newest per table
const { job } = (await client.startArchivePrune(start, {})).toObject()
console.log(`prune job ${job.jobId} started`)
// Poll until the running job clears.
const statusReq = new GetArchivePruneStatusRequest()
statusReq.setNetwork({ protocol: 1, id: '0a03cf40' })
for (;;) {
const status = (await client.getArchivePruneStatus(statusReq, {})).toObject()
if (!status.running) {
const last = status.last
console.log('done:', last?.counts, last?.completed ? 'completed' : last?.failed ? 'failed' : 'cancelled')
break
}
console.log(` phase ${status.running.progress?.phase}, ${status.running.progress?.chunks} chunks`)
await new Promise(r => setTimeout(r, 1000))
}
Prefer configuring
settings.auto_pruneinconfig.yaml(periodic auto-prune per network) over calling this by hand — see the Setup Guide. The RPCs are for on-demand / operator-driven runs.
Create Invite
Added 2026-07-08. Requires referral_config.referral_service_url in config.yaml.
Mints a fresh bearer invite code to share with another user out-of-band. The application signs the server-issued challenge with its identity key internally — no parameters are required.
Method: CreateInvite — Parameters: None.
Response: code (string) — the bearer invite code (URL-safe base64). Anyone holding it can redeem it, so share it privately.
Redeem Invite
Added 2026-07-08.
Redeems an invite code received from another user. The application signs the redemption internally.
Method: RedeemInvite
| Name | Type | Required | Description |
|---|---|---|---|
code | string | YES | The bearer invite code received from an inviter |
Response: referral_public_key (bytes, 32) — the inviter's Ed25519 public key, so the UI can surface "you were invited by X".
Get Referral
Added 2026-07-08.
Returns the application's current referrer, if one has been set (e.g. via RedeemInvite).
Method: GetReferral — Parameters: None.
Response: referral_public_key (bytes, optional) — the referrer's Ed25519 public key, or absent if none is set.
Common Workflows
Initialize and discover networks
async function initializeApp(client: AppServiceClient) {
const networks = (await client.getNetworks(new GetNetworksRequest(), {})).getNetworksList()
const pubkey = (await client.getPublicKey(new GetPublicKeyRequest(), {})).getPublicKey_asU8()
return {
publicKey: Buffer.from(pubkey).toString('hex'),
networks: networks.map(n => ({ protocol: n.getProtocol(), id: n.getId() }))
}
}
Protocol Types
| Value | Constant | Description |
|---|---|---|
0 | PROTOCOL_UNSPECIFIED | Invalid default — never use |
1 | PROTOCOL_BITCOIN | Bitcoin mainnet, testnet, signet, regtest |
2 | PROTOCOL_EVM | Ethereum, Arbitrum, Polygon, BSC, etc. |
Common Network IDs
Bitcoin (PROTOCOL_BITCOIN, magic bytes hex)
id | Network |
|---|---|
f9beb4d9 | Bitcoin Mainnet |
0b110907 | Bitcoin Testnet3 |
0a03cf40 | Bitcoin Signet |
fabfb5da | Bitcoin Regtest |
EVM (PROTOCOL_EVM, decimal chain ID)
id | Network |
|---|---|
1 | Ethereum Mainnet |
11155111 | Sepolia Testnet |
137 | Polygon |
42161 | Arbitrum One |
10 | Optimism |
Best Practices
- Cache the network list. Active networks rarely change; refresh on app start.
- Treat the public key as stable. It's derived from the seed and won't change for the application's lifetime.
- Check
GetPreimageresults for emptiness. A successful response can still mean "not yet known" — it's not an error. - Prefer config-driven
auto_pruneover callingStartArchivePruneby hand. Setsettings.auto_pruneinconfig.yamlfor periodic retention; use the RPCs for on-demand runs, and pollGetArchivePruneStatus(or subscribe) rather than assuming a start call finished.