Integrate RelayGuard

RelayGuard is a drop-in replacement for your RPC URL. You keep your own providers and your existing code — you change one thing: the endpoint your tooling points at. This guide shows how to wire it into common stacks, where to keep the endpoint, and how to actually see the integrity benefits in your app.

New here? Do the Quickstart first (create a key, add providers). The full request/response spec is in the API reference.

The endpoint

One stable URL per chain:

https://gateway.0xrelayguard.com/rpc/<chain>

# examples
https://gateway.0xrelayguard.com/rpc/ethereum
https://gateway.0xrelayguard.com/rpc/base
https://gateway.0xrelayguard.com/rpc/arbitrum

Use the lowercase chain slug from the Networks reference (ethereum, base, arbitrum, optimism, polygon, …, plus testnets like ethereum-sepolia, base-sepolia). Authenticate with your rg_live_… key — three ways:

# 1. Authorization header  (preferred — keeps the key out of URLs/logs)
Authorization: Bearer rg_live_xxxxxxxxxxxxxxxx

# 2. Custom header
X-RelayGuard-Key: rg_live_xxxxxxxxxxxxxxxx

# 3. Query parameter  (for libraries that only accept a URL)
https://gateway.0xrelayguard.com/rpc/ethereum?key=rg_live_xxxxxxxxxxxxxxxx

Migrating from a single provider

Already pointing your app at one provider (an Alchemy/Infura URL, your own node, anything)? Moving to RelayGuard is a swap, not a rewrite — you keep that provider and add RelayGuard in front of it.

  1. Add your current provider in the dashboard — paste the exact RPC URL you use today. Then add one or two more (ideally from different operators) so failover and quorum have something to work with. Your providers, your keys.
  2. Repoint your existing env var. Whatever holds your RPC URL today (e.g. ETHEREUM_RPC_URL) — change its value to the RelayGuard endpoint (https://gateway.0xrelayguard.com/rpc/ethereum + your rg_live_ key). No application code changes.
  3. Deploy/restart and verify parityeth_blockNumber / eth_chainId match and your app behaves identically. You now get health-checked routing and read failover for free.
  4. Turn on Security Mode for the reads that move money (deposits, receipts, oracle/contract reads). Now those answers are cross-checked across your independent providers before your app acts on them.

Zero lock-in: to roll back, point the env var at your old provider URL again. Keep your old provider as one of the configured providers — RelayGuard routes through it plus the others. Quorum is only as independent as the providers you add, so prefer different operators/infra for the ones that matter.

Where to keep the endpoint

Treat the URL/key like any other credential — in an environment variable or repo/platform secret, never hardcoded in source.

# .env  (server-side: prefer the header so the key never appears in a URL)
RELAYGUARD_KEY=rg_live_xxxxxxxxxxxxxxxx
ETHEREUM_RPC_URL=https://gateway.0xrelayguard.com/rpc/ethereum

# or, for tools that only take a URL string:
ETHEREUM_RPC_URL=https://gateway.0xrelayguard.com/rpc/ethereum?key=rg_live_xxxxxxxxxxxxxxxx
  • Backends, indexers, bots, scripts: store it as a secret (GitHub Actions secret, Vercel/Netlify/Fly env, Docker/Kubernetes secret) and inject at runtime. Use the Authorization header so the key isn't logged in request URLs.
  • Browser dApps / mobile apps: the key ships in your bundle and is effectively public — that's normal for client-side RPC. Use a separate key per client app (never your server key), keep that workspace/environment's rate limits sensible, and rotate from the dashboard if it's abused.
  • One key per environment. Create distinct keys/environments for dev, staging, and prod so you can rotate or revoke one without touching the others.

Setting the secret on your host

  • Vercel: Project → Settings → Environment Variables (set per environment: Production / Preview / Development), or vercel env add ETHEREUM_RPC_URL. Only values read in the browser need the NEXT_PUBLIC_ prefix — keep server-only keys un-prefixed.
  • Netlify: Site configuration → Environment variables, or netlify env:set ETHEREUM_RPC_URL "https://…".
  • Fly.io: fly secrets set ETHEREUM_RPC_URL="https://…" -a your-app (encrypted at rest; triggers a rolling restart).
  • Docker: pass at run time with --env-file .env or -e ETHEREUM_RPC_URL=…. Don't bake the key into an image layer (it's recoverable from the image history).
  • Kubernetes: store it in a Secret (not a ConfigMap) and mount via envFrom / secretKeyRef.
  • GitHub Actions: repo → Settings → Secrets and variables → Actions, then reference ${{ secrets.ETHEREUM_RPC_URL }} in your workflow.
  • Everywhere: never commit .env (gitignore it); check in a .env.example with placeholder values so teammates know what to set.

Tooling snippets

viem

import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

// Simplest: key in the URL via env var.
export const client = createPublicClient({
  chain: mainnet,
  transport: http(process.env.ETHEREUM_RPC_URL),
});

// Or keep the key in a header (recommended server-side):
export const clientHeaderAuth = createPublicClient({
  chain: mainnet,
  transport: http("https://gateway.0xrelayguard.com/rpc/ethereum", {
    fetchOptions: { headers: { Authorization: "Bearer " + process.env.RELAYGUARD_KEY } },
  }),
});

ethers.js (v6)

import { JsonRpcProvider, FetchRequest } from "ethers";

// URL with key:
const provider = new JsonRpcProvider(process.env.ETHEREUM_RPC_URL);

// Header auth:
const req = new FetchRequest("https://gateway.0xrelayguard.com/rpc/ethereum");
req.setHeader("Authorization", "Bearer " + process.env.RELAYGUARD_KEY);
const providerHeaderAuth = new JsonRpcProvider(req);

wagmi

import { http, createConfig } from "wagmi";
import { mainnet, base } from "wagmi/chains";

export const config = createConfig({
  chains: [mainnet, base],
  transports: {
    [mainnet.id]: http(process.env.NEXT_PUBLIC_ETHEREUM_RPC_URL),
    [base.id]: http(process.env.NEXT_PUBLIC_BASE_RPC_URL),
  },
});

web3.py

import os
from web3 import Web3

w3 = Web3(Web3.HTTPProvider(
    "https://gateway.0xrelayguard.com/rpc/ethereum",
    request_kwargs={"headers": {"Authorization": "Bearer " + os.environ["RELAYGUARD_KEY"]}},
))
print(w3.eth.block_number)

Foundry (cast / forge)

# one-off
export ETH_RPC_URL="https://gateway.0xrelayguard.com/rpc/ethereum?key=$RELAYGUARD_KEY"
cast block-number

# foundry.toml
[rpc_endpoints]
ethereum = "https://gateway.0xrelayguard.com/rpc/ethereum?key=${RELAYGUARD_KEY}"
base     = "https://gateway.0xrelayguard.com/rpc/base?key=${RELAYGUARD_KEY}"

Hardhat

// hardhat.config.ts
networks: {
  mainnet: { url: process.env.ETHEREUM_RPC_URL ?? "" },
  base:    { url: process.env.BASE_RPC_URL ?? "" },
}

go-ethereum

import "github.com/ethereum/go-ethereum/ethclient"

client, err := ethclient.Dial(os.Getenv("ETHEREUM_RPC_URL")) // URL includes ?key=...

Raw JSON-RPC (curl)

curl -X POST "https://gateway.0xrelayguard.com/rpc/ethereum" \
  -H "Authorization: Bearer $RELAYGUARD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

Seeing the benefits

Once you're routing through RelayGuard you immediately get health-checked routing and read failover — no code change. To get the integrity benefits, enable Security Mode in the dashboard for the methods (or contract functions) that matter, then have your app pay attention to the outcome.

Every Security Mode response carries metadata in headers (never in the JSON-RPC body):

X-RelayGuard-Verified: true
X-RelayGuard-Outcome: met            # met | absent | not_converged | divergence | unavailable
X-RelayGuard-Quorum: 3/3             # agreeing / compared
X-RelayGuard-Independent-Groups: 3
X-RelayGuard-Receipt: <base64url>    # signed receipt (see Verify & webhooks)
X-RelayGuard-Signature: ed25519=<...>

And map the HTTP status — 409 means stop, 503 means retry — so a contested or unavailable read never gets treated as a good one:

const res = await fetch("https://gateway.0xrelayguard.com/rpc/ethereum", {
  method: "POST",
  headers: { Authorization: "Bearer " + process.env.RELAYGUARD_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ jsonrpc: "2.0", method: "eth_getTransactionReceipt", params: [txHash], id: 1 }),
});

if (res.status === 409) {
  // Providers returned different concrete answers — the read is contested.
  throw new Error("RPC divergence: do not act on this result");
}
if (res.status === 503) {
  // Retryable: quorum temporarily unavailable or not converged — back off and retry.
  return retryWithBackoff();
}

const verified = res.headers.get("X-RelayGuard-Verified"); // "true"
const data = await res.json();
  • Verify the receipt (optional, for the strongest guarantee): base64url-decode X-RelayGuard-Receipt and check the Ed25519 signature against the public key at /receipts/public-key. Full how-to in Verify & webhooks.
  • Watch the dashboard: live provider health, the integrity feed (reads verified, bad data caught), per-provider trust scores, and usage.
  • Get alerted: configure webhooks/Slack (Team) so a divergence or unhealthy provider pages you — signatures verifiable per Verify & webhooks.

Best practices

  • Prefer the Authorization header server-side; reserve the ?key= form for libraries that can't set headers, and avoid logging full request URLs.
  • One key (or environment) per app and per stage (dev/staging/prod); rotate from the dashboard.
  • Scope Security Mode to the reads that move money (deposits, receipts, oracle/contract reads) — leave high-volume reads on the fast path so you only pay the cross-check where it matters.
  • Batch JSON-RPC calls (top-level array) where your tooling supports it; each call is billed and policy-checked individually.
  • eth_sendRawTransaction is never auto-retried or failed over — your transactions are submitted exactly once.

Questions or stuck? See Support, and include the X-Request-Id from the response — it lets us trace your call instantly.