Hermoddocs

Solana

Calling the Solana Mainnet RPC gateway — HTTP JSON-RPC + WebSocket, auth, finalized-immutable caching, and code samples for @solana/web3.js / cURL.

Solana speaks standard JSON-RPC over HTTP, plus a WebSocket subscription API. Hermod fronts it with one HTTP endpoint and one WebSocket endpoint per network — your key embedded in the path, header, or query — and a response cache tuned to Solana's finalized-immutable data.

If you're migrating from another provider, the win is drop-in compatibility: the request/response shape is vanilla Solana RPC, so usually only the host changes.

Endpoints

TransportURL
HTTPhttps://infra.originstake.com/v1/solana/<network>/<KEY>
WebSocketwss://infra.originstake.com/v1/solana/<network>/<KEY>

<network> is mainnet. (The segment is informational — all Solana traffic routes to the same mainnet upstream pool today.)

The key can also be passed by header or query instead of the path — see Authentication.

Quick start

@solana/web3.js

import { Connection } from "@solana/web3.js";
 
const connection = new Connection(
  "https://infra.originstake.com/v1/solana/mainnet/" + process.env.ORIGINRPC_KEY,
  {
    commitment: "confirmed",
    wsEndpoint:
      "wss://infra.originstake.com/v1/solana/mainnet/" + process.env.ORIGINRPC_KEY,
  },
);
 
const slot = await connection.getSlot();
console.log("slot", slot);
 
// WebSocket subscription — fires on every new slot.
const subId = connection.onSlotChange((s) => console.log("slot", s.slot));

Prefer the header form? Pass it via httpHeaders and keep the key out of the URL:

const connection = new Connection(
  "https://infra.originstake.com/v1/solana/mainnet",
  { httpHeaders: { "X-OriginRPC-Token": process.env.ORIGINRPC_KEY! } },
);

Migrating from Helius (one-line swap)

We accept the same ?api-key= query shape, so a Helius URL migrates by changing only the host:

- https://mainnet.helius-rpc.com/?api-key=YOUR_KEY
+ https://infra.originstake.com/v1/solana/mainnet/?api-key=YOUR_ORIGINRPC_KEY

cURL

# key in path
curl -X POST https://infra.originstake.com/v1/solana/mainnet/$ORIGINRPC_KEY \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getSlot"}'
 
# key in header (path can be /v1/solana/mainnet or /v1/solana/mainnet/rpc)
curl -X POST https://infra.originstake.com/v1/solana/mainnet \
  -H "Content-Type: application/json" \
  -H "X-OriginRPC-Token: $ORIGINRPC_KEY" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getLatestBlockhash","params":[{"commitment":"finalized"}]}'

Authentication

Four accepted forms, in priority order:

  1. Header: X-OriginRPC-Token: <key> (recommended for SDKs)
  2. Bearer: Authorization: Bearer <key>
  3. Path: a segment matching originrpc_live_… or os_live_…
  4. Query: ?api-key=<key> (Helius-compatible)

The key must be entitled to Solana. Grant it in the dashboard (Keys → New key → tick Solana) — no config or SQL needed.

WebSocket connections authenticate via the key in the path (browsers can't set custom headers on a WS handshake).

Methods

Standard Solana JSON-RPC is forwarded as-is — getAccountInfo, getBalance, getMultipleAccounts, getProgramAccounts, getTokenAccountsByOwner, getLatestBlockhash, getSlot, getBlock, getTransaction, getSignaturesForAddress, getEpochInfo, getRecentPrioritizationFees, sendTransaction, simulateTransaction, and the rest of the official RPC API.

Batch requests are supported when your upstream plan allows them.

Caching

The gateway caches only data that is immutable once finalized — never hot-account reads (which change every ~400 ms slot) and never writes. This cuts latency and upstream cost without ever serving you stale state.

MethodTTLWhy
getBlock (finalized)7 daysImmutable once finalized
getTransaction (finalized)7 daysImmutable once finalized
getBlockTime24hImmutable
getGenesisHash, getEpochSchedule24hStatic
getEpochInfo5sSlow-changing
getRecentPrioritizationFees2sFast-changing but burst-absorbing
getAccountInfo, getBalance, getMultipleAccountsno cacheChange every slot
sendTransaction, simulateTransactionno cacheWrites

Caching applies only when the request's commitment is finalized (or unspecified — the default for getBlock/getTransaction is finalized). A confirmed/processed request always goes to the upstream live.

Responses carry an X-Origin-Cache: HIT or MISS header. To force a fresh fetch, send X-Cache-Bypass: true.

curl -sD - -o /dev/null -X POST \
  https://infra.originstake.com/v1/solana/mainnet/$ORIGINRPC_KEY \
  -d '{"jsonrpc":"2.0","id":1,"method":"getGenesisHash"}' | grep -i x-origin-cache
# → X-Origin-Cache: HIT  (on the second call)

WebSocket subscriptions

Connect to the wss:// endpoint and use the standard Solana subscription methods — slotSubscribe, accountSubscribe, logsSubscribe, programSubscribe, signatureSubscribe, blockSubscribe, rootSubscribe. Each client connection is forwarded 1:1 to the upstream.

const ws = new WebSocket(
  "wss://infra.originstake.com/v1/solana/mainnet/" + process.env.ORIGINRPC_KEY,
);
ws.onopen = () =>
  ws.send(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "slotSubscribe" }));
ws.onmessage = (e) => console.log(JSON.parse(e.data));

Rate limits

Per-IP for anonymous traffic, per-key for authenticated. Heavy methods carry their own lower sub-limits:

TierDefault RPSgetProgramAccountsgetTokenAccountsByOwner
Anonymous5 per IP1 RPS1 RPS
Free202 RPS5 RPS
Pro20020 RPS50 RPS
Scale1,000——

Over the limit returns HTTP 429 with a Retry-After header and a JSON-RPC error (code: -32005). See rate limits for retry guidance.

Pricing

Solana calls draw from the same monthly CU allowance as every other chain, with their own weights:

MethodCU
Most methods (reads, getTransaction, getBlock, …)75
sendTransaction (sent to every healthy upstream)150
getProgramAccounts750

That is about $7 per million calls on the Pro plan and about $6 on Scale. Solana is weighted higher than EVM chains because the nodes behind it are bought per call rather than run by us.

Status

Solana endpoint health (upstream slot lag, error rate) is published on the public status page. The gateway demotes an upstream that lags more than a few slots or errors repeatedly, and routes around it while it recovers.

On this page