Skip to content

RPC proxy — Reference

The complete reference for the fusion endpoint: the request flow, the config model, route matching, caching, request forms, and the Blurt routing map. For a gentle introduction, start with the Quick guide.

The proxy is a generalized JSON-RPC reverse proxy (Saboin/temp-rpc-proxy) — Python / Sanic, config-driven, HTTP and WebSocket. It is not Blurt-specific; Blurt supplies a routing config that fuses blurtd, nexus-go, and the wallet indexer behind one endpoint.

Request flow

text
  client ──► POST /  (JSON-RPC 2.0)
          ├─ parse method  ("api.method" or the legacy "call" form)
          ├─ match the first route whose regex matches the method
          ├─ cache hit? return cached response          (Redis, per-route TTL)
          └─ else forward to the route's target, cache, and return

A request is a JSON-RPC 2.0 object (jsonrpc, method, params, id) sent to POST /. WebSocket connections are served on the same endpoint with the same routing.

Configuration model

Routing is a single JSON file with four top-level keys:

jsonc
{
  "targets":  { "<name>": "<backend URL>", ... },   // named backends
  "timeouts": { "<name>": <seconds>, ... },         // per-target request timeout
  "routes":   { "<method regex>": { "target": "<name>", "cache": <sec>, "translate_to_app_base": <bool> }, ... },
  "default-cache": <seconds>                          // fallback TTL (0 = no cache)
}
  • targets — a name → backend-URL map. The URL scheme selects the transport (http(s)://, ws(s)://, or sock://).
  • timeouts — per-target request timeout in seconds.
  • routes — an ordered map of method regex → route config.
  • default-cache — TTL applied when a route sets no cache.

Route config

KeyMeaning
targetwhich named target to forward to
cacheresponse TTL in seconds for this route (omitted → default-cache)
translate_to_app_baserewrite the call to the node's legacy call form before forwarding (see Request forms)

Route matching

The proxy takes the JSON-RPC method (e.g. bridge.get_ranked_posts) and matches it against the routes regexes in declaration order; the first match wins. Because matching is first-wins, specific routes must come before catch-alls. A typical namespace ends with a catch-all so nothing is unrouted:

jsonc
"^condenser_api.get_content(.*)$": { "target": "nexus",  "cache": 3 },  // specific → social
"^condenser_api.broadcast_(.*)$":  { "target": "blurtd"              },  // specific → node
"^condenser_api.(.*)$":            { "target": "blurtd"              }   // catch-all → node

Request forms

The proxy accepts both JSON-RPC call styles and normalizes them:

jsonc
// modern: dotted method
{ "jsonrpc": "2.0", "id": 1, "method": "condenser_api.get_content", "params": [...] }

// legacy: "call" with [api, method, params]
{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": ["condenser_api", "get_content", [...]] }

translate_to_app_base handles the reverse: for routes that set it, the proxy rewrites a dotted request into the legacy call form ("method": "call", "params": ["condenser_api", <method>, <params>]) that the node expects for certain namespaces.

Caching

Responses are cached in Redis (via aiocache) with a per-route TTL in seconds. default-cache (usually 0) applies when a route omits cache. Read methods that are hot and tolerate slight staleness (feeds, blogs, trending, witness lists) carry a small TTL; broadcast and consensus-critical methods carry no cache. Caching shields the backends from repeated identical reads without changing correctness beyond the TTL window.

Transports

The target URL scheme selects how the proxy reaches the backend:

SchemeTransport
http:// / https://HTTP
ws:// / wss://WebSocket
sock://Unix socket

Clients may connect over HTTP POST or WebSocket; routing is identical.

The Blurt routing map

Blurt's config fuses three backends — the node (blurtd), the social layer (nexus-go, as nexus), and the wallet indexer (wallet_indexer). The routing follows one principle: consensus and broadcast go to the node; social reads go to nexus-go; wallet history goes to the wallet indexer.

Method patternTargetWhy
bridge.*nexus-gothe social API (feeds, communities, profiles, …)
wallet_history.*wallet indexeraccount history and reward screens
tags_api.*, follow_api.*, hive.*nexus-gosocial/discovery reads
condenser_api.get_content*, get_discussions_by*, get_blog*, get_followers/following/follow_countnexus-gothe social subset of the classic API
condenser_api.broadcast_*, network_broadcast_api.*blurtdwrites to the chain
condenser_api.get_account_history, account_history_api.*blurtdchain history (the wallet indexer accelerates the wallet's slice)
condenser_api.* (catch-all), database_api.*, block_api.*, chain_api.*, witness/key APIsblurtdconsensus and chain state

This is exactly the split the wallet indexer and nexus-go pages describe from the other side: a front-end uses one RPC URL, and the proxy sends each method to the service built for it.

Adding a backend is a config change

Fronting a new service (as Blurt did for the wallet indexer) is just a new targets entry plus a route above the catch-alls — no client change, since the front-end keeps using the same single URL.

Configuration (runtime)

VariableDefaultPurpose
HOST127.0.0.1bind address (front with a web server for TLS)
PORT5002listen port
WORKERS4Sanic worker processes
DEBUGFalsedebug mode

Redis must be reachable (default 127.0.0.1:6379) for the cache. The routing config is passed with --config. Deployment specifics (service unit, TLS termination, backend addresses) are operator-specific and live with the deployment; the repository ships a systemd unit and example configs.

Released under the MIT License.