A lightweight Go reverse proxy for JSON-RPC endpoints with authentication, method allowlisting, rate limiting, and structured JSON logging.
Client ──POST──▶ :8080
│
├─ POST guard (405 if not POST)
├─ Rate limiter (429 if over 10 req/s, burst 20)
├─ Auth check (401 if missing/wrong Bearer token)
├─ Method allowlist (403 if method not allowed)
│
▼
TARGET_RPC_URL
go build -o json-proxy-exp .
TARGET_RPC_URL=https://eth.llamarpc.com ./json-proxy-expOr without building:
TARGET_RPC_URL=https://eth.llamarpc.com go run main.goThe server listens on :8080 and forwards allowed JSON-RPC requests to the target.
| Variable | Required | Description |
|---|---|---|
TARGET_RPC_URL |
Yes | Upstream JSON-RPC endpoint to proxy |
Start the proxy in one terminal:
TARGET_RPC_URL=https://eth.llamarpc.com go run main.goThen run each test in a second terminal.
Verifies that non-POST requests are rejected before reaching any middleware.
curl -i http://localhost:8080/Expected: 405 Method Not Allowed
Server logs: "msg":"method_not_allowed","client_ip":"127.0.0.1","http_method":"GET".
Fires 25 rapid requests to exceed the burst capacity of 20.
for i in $(seq 1 25); do
curl -s -o /dev/null -w "$i: %{http_code}\n" -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
doneExpected: First ~20 return 401 (pass rate limiter, fail auth). Remaining return 429 Too Many Requests.
Server logs: "msg":"rate_limit_exceeded" entries for the 429 responses.
curl -i -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'Expected: 401 Unauthorized with body {"error":"unauthorized"}.
Server logs: "auth":"failed","policy":"deny".
curl -i -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wrong-key" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'Expected: 401 Unauthorized.
Server logs: "auth":"failed","policy":"deny".
Sends a valid auth but a method not in the allowlist.
curl -i -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer test-key-123" \
-d '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[],"id":1}'Expected: 403 Forbidden with a JSON-RPC error:
{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"method not allowed: eth_sendTransaction"}}Server logs: "auth":"success","policy":"deny".
curl -s -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer test-key-123" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'Expected: 200 OK with response:
{"jsonrpc":"2.0","id":1,"result":"0x1"}Server logs: "auth":"success","policy":"allow".
curl -s -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer test-key-123" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":2}'Expected: 200 OK with a hex block number in the result, e.g.:
{"jsonrpc":"2.0","id":2,"result":"0x134a1c0"}Server logs: "auth":"success","policy":"allow".
Every request that reaches the gateway produces a structured JSON log line:
{"time":"2026-03-03T12:00:00Z","level":"INFO","msg":"rpc_request","client_ip":"127.0.0.1","method":"eth_chainId","auth":"success","policy":"allow"}| Field | Values | Description |
|---|---|---|
client_ip |
IP address | From X-Forwarded-For or direct |
method |
JSON-RPC method string | e.g. eth_chainId |
auth |
success / failed |
Authorization header check result |
policy |
allow / deny |
Final access control decision |
| Method |
|---|
eth_chainId |
eth_blockNumber |
eth_call |
All other methods are denied by default.