# Azul — Base RPC Documentation > The fastest, most reliable RPC for Base transaction submission. Parallel race-to-land submission, sandwich-resistant by design, no logs, no API key required. Endpoint: `https://rpc.baseazul.dev` Chain: Base (chainId 8453, hex 0x2105) Native currency: ETH Block explorer: https://basescan.org This file is a flat, machine-readable mirror of https://baseazul.dev/docs intended for LLMs and coding agents. Sections match the on-page anchors. A short index suitable for indexing is available at https://baseazul.dev/llms.txt. --- ## Getting started ### The endpoint One HTTPS URL serves all standard JSON-RPC methods. Public traffic does not require an API key. Privileged keys exist for higher limits and access to admin-only namespaces. ``` POST https://rpc.baseazul.dev Content-Type: application/json ``` The endpoint is standard JSON-RPC 2.0. Body is a single request object or an array (batch). Responses are gzip/zstd compressed when the client advertises support. ### First request Sanity-check with a chainId call. You should see `0x2105` (decimal 8453). ```bash curl -X POST https://rpc.baseazul.dev \ -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' # → {"jsonrpc":"2.0","result":"0x2105","id":1} ``` ### CORS The endpoint sets `Access-Control-Allow-Origin: *`. Browser fetch works from any origin. Preflight (`OPTIONS`) responds with `204` and a 24-hour max-age. --- ## Reference ### Method support All standard `eth_*`, `net_*`, and `web3_*` methods are available on the public path. Heavy/admin namespaces are blocked unless you authenticate with an API key. | Namespace | Status | Notes | |-----------|--------|-------| | `eth_*` | full | All standard reads, writes, filters | | `net_*` | full | — | | `web3_*` | full | — | | `eth_sendRawTransaction` | full | Always hits local reth (mempool retention) | | `eth_getLogs` | full | Up to 5,000-block window per call | | `debug_*` | none | Blocked on public path. Available with API key. | | `trace_*` | none | Parity-style traces not exposed. | | `txpool_*` | none | Blocked on public path. | | `admin_*` | none | Never exposed. | Need `debug_*` or `trace_*` for production analytics? Reach out for an API key. ### Rate limits Per-IP limits on anonymous traffic. API-key holders bypass all of these. | Bucket | Limit | |--------|-------| | Standard methods | 100 req/s sustained · burst 400 | | Heavy methods | 10 req/s sustained — `eth_getLogs`, large `eth_call` | | Batch size | 50 requests per batched call | | Max response body | 50 MB · HTTP 413 if exceeded | | Daily egress (anon) | 1 TB raw JSON / day · HTTP 503 once exhausted | | API key | Bypasses every cap above | Hitting the cap returns `-32005` with a `Retry-After` header. Back off with jitter; don't spin. ### Batching Batch up to **50** requests in a single POST. Useful for fan-out reads (multicall warmup, ENS resolution, parallel `eth_call`). One TLS handshake, lower tail latency. ```json [ {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}, {"jsonrpc":"2.0","method":"eth_getBalance","params":["0xabc…","latest"],"id":2}, {"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":3} ] ``` ### eth_getLogs window Max range is **5,000** blocks per call. Wider ranges return `-32602`. Page through history by chunking the range and parallelizing requests. ### Error codes JSON-RPC errors follow standard codes. HTTP-level errors come back as raw responses. | Code | Trigger | |------|---------| | `-32000` | Method blocked on public path (`debug_`, `trace_`, `txpool_`, `admin_`). | | `-32005` | Rate limit exceeded. Back off or use an API key. | | `-32601` | Method not found on the underlying node. | | `-32602` | Invalid params. `eth_getLogs` window > 5,000 blocks is the usual culprit. | | `-32603` | Internal error / upstream pool exhausted. | | `HTTP 413` | Response too large (>50 MB). Trim your request. | | `HTTP 503` | Daily egress quota exhausted (anonymous only). Use a key. | --- ## Guides ### Add a fallback Azul is single-region. For production, pair it with a paid provider. `viem` has a first-class fallback transport: first URL wins; second takes over on error or timeout. ```typescript import { createPublicClient, http, fallback } from 'viem'; import { base } from 'viem/chains'; export const client = createPublicClient({ chain: base, transport: fallback([ http('https://rpc.baseazul.dev'), // Azul — primary, fast, no logging http('https://mainnet.base.org'), // Coinbase default — last resort ], { retryCount: 1, retryDelay: 100 }), }); ``` ### Subscribe to new blocks Don't poll `eth_blockNumber` in a tight loop. WebSocket subscriptions are available with an API key — request one if your workload depends on live tip-follow. ```typescript import { createPublicClient, webSocket } from 'viem'; import { base } from 'viem/chains'; const client = createPublicClient({ chain: base, transport: webSocket('wss://rpc.baseazul.dev/ws?api_key=YOUR_KEY'), }); const unwatch = client.watchBlockNumber({ onBlockNumber: (n) => console.log('new tip', n), }); ``` ### Page through eth_getLogs Chunk wide ranges into 5,000-block windows and parallelize. The proxy handles concurrent batches up to your per-IP cap. ```typescript async function getLogsRange(client, address, fromBlock, toBlock) { const PAGE = 5000; const tasks = []; for (let start = fromBlock; start <= toBlock; start += PAGE) { const end = Math.min(start + PAGE - 1, toBlock); tasks.push(client.getLogs({ address, fromBlock: start, toBlock: end })); } return (await Promise.all(tasks)).flat(); } ``` ### Drop-in snippets (other libraries) ```typescript // viem (public client) import { createPublicClient, http } from 'viem'; import { base } from 'viem/chains'; export const client = createPublicClient({ chain: base, transport: http('https://rpc.baseazul.dev') }); ``` ```typescript // ethers v6 import { JsonRpcProvider } from 'ethers'; const provider = new JsonRpcProvider('https://rpc.baseazul.dev'); ``` ```typescript // wagmi import { createConfig, http } from 'wagmi'; import { base } from 'wagmi/chains'; export const config = createConfig({ chains: [base], transports: { [base.id]: http('https://rpc.baseazul.dev') }, }); ``` ```python # web3.py from web3 import Web3 w3 = Web3(Web3.HTTPProvider("https://rpc.baseazul.dev")) ``` --- ## Architecture ### Read/write split The proxy treats reads and writes very differently. Reads load-balance across a pool of 16 Base RPCs and *never* touch the local node. Writes always hit the local reth and the top-2 upstreams in parallel. This is deliberate: reads stay off the local node so your traffic pattern never leaks into the box that holds the bot. Writes go to local first so the proxy retains the transaction in its mempool for downstream observers. ``` reads (eth_call, eth_getBalance, eth_getLogs, …) → upstream pool — 16 Base RPCs, top-3 latency-weighted writes (eth_sendRawTransaction, eth_sendTransaction) → local reth ALWAYS (mempool retention) + top-2 upstreams in parallel (fast inclusion) ``` ### Sequencer propagation For writes, the parallel fan-out is the key edge. The proxy submits your raw tx to local reth and to the two fastest healthy upstreams **at the same time**, so it reaches the Base sequencer over whichever path is fastest right now. Compared to a single-upstream submission, this trims tail-latency for inclusion and removes a class of dropped-tx failure modes. Read-side network routing follows the same principle: every 15s health-check picks the top-3 fastest healthy upstreams and serves reads from a random one. A pool member that slows down or starts erroring drops out automatically. ### Sandwich resistance A sandwich attack needs your transaction to sit pending long enough for a searcher to wedge their own tx ahead of it. The longer it lingers in any public mempool or relay, the bigger the window. Azul attacks the window from both sides. Race-to-land submission (every tier) cuts the time your tx is observable: the first of N parallel paths to include it wins, the rest are deduped. There's no single relay holding your tx for inspection. Edge customers can additionally enable a **private submission lane** that skips the public upstream pool entirely — the tx goes only to the co-located node's direct path to the sequencer. Zero mempool footprint, no race needed. Caveat: nothing protects against a sequencer-internal observer. Anti-sandwich here means "minimize the public surface where someone can see your tx and react." ### Upstream pool The read pool is 16 known public Base RPCs. Latency-weighted top-3 random selection. Health checks every 15s. Failed upstreams auto-degrade and auto-recover after 60s. If the entire pool is exhausted, requests return `-32603` with no local fallback — by design, so reads never accidentally hit the local node. --- ## Performance Measured server-side at the proxy (does not include client → Virginia network RTT): - p50 latency: 13ms - p95 latency: 38ms - p99 latency: 99ms Region: us-east-1 (Virginia). Live metrics are at https://baseazul.dev/status. --- ## Privacy The RPC path retains nothing per-request: - No IP addresses - No request bodies (so: no wallet addresses, no contract addresses) - No response bodies - No cookies, sessions, or cross-request identifiers We do count anonymous aggregate bytes/day in memory for the egress cap. That counter resets daily and isn't attributed to any IP or request. Full posture statement: https://baseazul.dev/privacy. --- ## Canonical URLs - Home: https://baseazul.dev/ - For users: https://baseazul.dev/users - For developers: https://baseazul.dev/developers - Full docs (HTML): https://baseazul.dev/docs - Full docs (markdown, this file): https://baseazul.dev/llms-full.txt - Short docs index: https://baseazul.dev/llms.txt - Live status: https://baseazul.dev/status - Privacy: https://baseazul.dev/privacy Last updated: 2026-05-26.