Config
Providers
AI agents: fetch https://docs.erpc.cloud/config/projects/providers.llms.txt for the complete machine-readable version of this page (full configuration schema, defaults, worked examples, and source links). Append `.llms.txt` to any docs URL for the same treatment.AIFor agents: /config/projects/providers.llms.txt

Providers & vendors

One API key, every chain. Instead of wiring one upstream per (vendor, network) pair, you declare a single provider and eRPC lazy-generates concrete upstreams the moment a new network is first requested. 23 vendor integrations are built in — from general-purpose node providers to ERC-4337 bundlers to public-endpoint catalogs — and a project with no config at all gets a sensible zero-cost default automatically.

What you get

  • One line to cover all chains a vendor supports — no manual network list.
  • Hot-path chain discovery with no I/O: static maps and lock-free remote caches keep routing decisions off the slow path.
  • Zero-config fallback: eRPC auto-injects repository + envio providers when no upstreams or providers are configured.

Quick taste

Illustrative, not a tuned production config — one key, every Alchemy chain:

projects[].upstreams[]
erpc.yaml
projects:  - id: main    upstreams:      - # URL shorthand — one line, all Alchemy chains        endpoint: alchemy://YOUR_API_KEY

Agent reference

Copy one of these prompts into your AI agent session (Claude Code, Cursor, …) — each one points the agent at this page's machine-readable reference so it can do the work correctly:

Prompt Example #1: set up providers from scratch
I want to configure eRPC providers so a single API key per vendor covers all EVM
chains automatically. Add Alchemy and dRPC as providers, restrict Alchemy to the
chains we actually need (mainnet, Arbitrum, Base, Polygon), and leave everything
else to dRPC. Work with my existing eRPC config. Read the full reference first:
https://docs.erpc.cloud/config/projects/providers.llms.txt
Prompt Example #2: add per-network overrides to an existing provider
My eRPC config already has providers for Alchemy and QuickNode.
I need Arbitrum (evm:42161) to pre-split eth_getLogs at 1000 blocks and have a
lower score multiplier for QuickNode on mainnet. Apply these as provider overrides
without touching the upstreams list. Reference:
https://docs.erpc.cloud/config/projects/providers.llms.txt
Prompt Example #3: debug a provider not generating upstreams on cold start
My eRPC instance logs ErrNetworkInitializing for evm:130 on every first request
after a redeploy, even though I have a conduit provider configured. Explain why
conduit returns ErrRemoteCacheCold on cold start and what I can pair it with to
avoid the first-request delay. Work with my existing eRPC config. Reference:
https://docs.erpc.cloud/config/projects/providers.llms.txt
Prompt Example #4: route ERC-4337 bundler traffic to pimlico
Add Pimlico as a provider in my eRPC config so all bundler
methods (eth_sendUserOperation, pimlico_*, pm_*) route to Pimlico while
general eth_* methods still hit Alchemy. Explain the method allow-list that
Pimlico injects automatically. Reference:
https://docs.erpc.cloud/config/projects/providers.llms.txt
Prompt Example #5: split QuickNode endpoint groups by tag
I have two logical QuickNode endpoint groups: one tagged "archive" and one
tagged "realtime". Wire them as separate providers in my eRPC config so each
group's endpoints are used for the right traffic, and explain the apiKey
cache-key footgun so I don't accidentally share snapshots. Reference:
https://docs.erpc.cloud/config/projects/providers.llms.txt
Providers & vendors — full agent referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.

How it works

Two distinct concepts. A vendor (common.Vendor interface) is stateless integration logic: Name(), OwnsUpstream(cfg), SupportsNetwork(ctx, logger, settings, networkId), GenerateConfigs(...), and GetVendorSpecificErrorIfAny(...). A provider is a configured instance of a vendor: ProviderConfig{Id, Vendor, Settings, OnlyNetworks, IgnoreNetworks, UpstreamIdTemplate, Overrides}. All 23 vendors are registered in a global VendorsRegistry at startup; a per-project ProvidersRegistry resolves each providers[].vendor string and fails startup if the name is unknown. thirdparty/vendors_registry.go:L9-36 thirdparty/providers_registry.go:L15-34

Lazy upstream generation. When a request arrives for a network with no prepared upstreams, UpstreamsRegistry.PrepareUpstreamsForNetwork schedules — exactly once per network via sync.Once — one bootstrap task per declared provider named network/<networkId>/provider/<providerId>. Each task: (1) calls Provider.SupportsNetwork, applying ignoreNetworks deny-list first, then onlyNetworks allow-list, then delegating to the vendor; (2) calls Provider.GenerateUpstreamConfigs, which copies the first wildcard-matching overrides entry (or creates a fresh defaults-filled config), stamps VendorName, renders the upstream id from upstreamIdTemplate, and runs os.ExpandEnv over every generated endpoint; (3) registers the resulting configs as ordinary upstream bootstrap tasks. The caller waits up to 30 seconds (polling every 200 ms) for at least one upstream to become ready. While tasks run, clients receive the retryable ErrNetworkInitializing; if all tasks finish without producing any upstreams, the error becomes the retryable ErrNetworkNotSupported. Failed tasks auto-retry with exponential backoff (factor 1.5, 3 s → 130 s). upstream/registry.go:L155-293

Vendor chain-support strategies. Vendors answer "do you support this network?" in four ways:

  1. Static in-code map (ankr, blastapi, blockdaemon, blockpi, dwellir, infura, llama, onfinality, etherspot): instant, no I/O, works on cold start.
  2. Remote catalog with static fallback (alchemy — 134-chain map; drpc — 150-chain map): fetches on warm paths; cold start falls back to the built-in map so the first request never blocks on I/O.
  3. Remote catalog, no fallback (conduit, superchain, tenderly, quicknode, chainstack, repository): cold start returns the retryable sentinel ErrRemoteCacheCold; the initializer retries with 3 s → 130 s exponential backoff until the first fetch lands.
  4. Live eth_chainId probe (envio for unknown chains, erpc, pimlico for unknown chains, routemesh, thirdweb): sends a real JSON-RPC call to the candidate endpoint with a 10 s timeout; probe clients are cached per chain id in sync.Map.

The request-path safety rule. Any vendor consulting remote data on the hot path uses the shared RemoteDataCache[T]: reads are a single atomic.Pointer load — never a mutex, never I/O. Staleness triggers a single-flight async refresh in a goroutine with its own 90 s timeout, fully decoupled from the caller's context. thirdparty/remote_cache.go:L13-78

URL shorthand scheme. Any upstream whose endpoint scheme is not http, https, grpc, or grpc+bds is converted to a ProviderConfig at config-defaults time. The evm+ prefix is stripped first (evm+alchemy:// equals alchemy://). The original upstream config (failsafe, rate limits, method filters, etc.) is placed into overrides: {"*": ...} so it applies to every generated upstream. The provider id defaults to <vendor>-<n> (process-wide counter per vendor name). Unknown scheme is a config-load error. common/defaults.go:L1113-1241

Default providers. A project with zero upstreams, zero providers, and no CLI --endpoint flags automatically gets {id:"public", vendor:"repository"} + {id:"envio", vendor:"envio"} injected with all defaults, and logs a warning. Setting providers: [] explicitly still satisfies the zero-providers condition — the injection fires unless you have at least one upstream or one CLI endpoint. common/defaults.go:L1113-1146

Vendors also serve statically-configured upstreams. At NewUpstream, LookupByUpstream finds a vendor by explicit upstream.vendorName (exact match, wins — if the name is unknown, OwnsUpstream is NOT consulted and the result is nil) or the first vendor whose OwnsUpstream matches the endpoint (in registration order). If no vendor matches, vendorName is guessed as unknown-<rootDomain> from the endpoint host. The vendor's GenerateConfigs is invoked with settings == nil to apply vendor defaults (e.g. dRPC forcing autoIgnoreUnsupportedMethods: false, envio's method allow-list). Vendor-specific JSON-RPC error mapping fires before generic normalization: GetVendorSpecificErrorIfAny is called on the upstream's attached vendor first. architecture/evm/error_normalizer.go:L878-900 thirdparty/vendors_registry.go:L54-71

Config schema

projects[].providers[] — ProviderConfig

fieldtypedefaultbehavior / footguns
idstringvendor name (p.Id = p.Vendor)Unique handle; appears in <PROVIDER> placeholder and bootstrap task names network/<networkId>/provider/<id>. Shorthand-converted providers get <vendor>-<n> (process-wide counter). No duplicate-id check — two providers with the same default id both run and may produce upstream-id collisions. common/defaults.go:L1474-1476 util/ids.go:L32-41
vendorstring— (required)Must exactly match one of 23 registered Name() strings: goldsky, alchemy, blastapi, conduit, drpc, dwellir, envio, etherspot, infura, pimlico, quicknode, llama, thirdweb, repository, superchain, tenderly, chainstack, onfinality, erpc, blockpi, ankr, routemesh, blockdaemon. Unknown vendor → startup error. thirdparty/vendors_registry.go:L12-34
settingsmap[string]any (VendorSettings)nilFree-form, vendor-interpreted. Redacted in JSON/YAML marshaling (never echoed in admin dump). See per-vendor tables below. common/config.go:L667-700
settings.creditUnitsmap[string]int64nilVendor-generic (works for every vendor): per-method credit-unit overrides for the cost accounting behind X-ERPC-Credits (server.costHeaders). Merged per method over the vendor's built-in CreditUnitsProvider table (alchemy and quicknode ship their public pricing); "*" = the vendor's fallback for unlisted methods. Vendors with no table default to a flat 1 credit per request — set "*": 0 to opt a vendor out entirely. Overriding one method keeps the rest; copied onto every upstream the provider generates. thirdparty/provider.go:L59-77
onlyNetworks[]stringnilExhaustive allow-list (evm:<positiveInt> format, validated). If set, vendor's own SupportsNetwork is not called. Mutually exclusive with ignoreNetworks (validation error). thirdparty/provider.go:L42-49
ignoreNetworks[]stringnilExact-match deny-list checked before onlyNetworks and before vendor. Same evm:<positiveInt> validation. Omitted from MarshalJSON (present in YAML marshal). thirdparty/provider.go:L34-40
upstreamIdTemplatestring"<PROVIDER>-<NETWORK>"Placeholders: <VENDOR>, <PROVIDER>, <NETWORK> (full network id e.g. evm:1), <EVM_CHAIN_ID> (numeric; N/A for non-evm networks). Validation requires non-empty. common/defaults.go:L1477-1479
overridesmap[string]*UpstreamConfignilKeys are wildcard patterns (WildcardMatch grammar: *, ?, | OR, & AND, ! NOT). First match from Go map iteration (nondeterministic for overlapping keys) is copied as the base config; no match → fresh UpstreamConfig with defaults. Pattern-match errors silently skipped. Each override validated with endpoint check skipped. thirdparty/provider.go:L77-101

URL shorthand derivation

shorthandderived settings
goldsky://<secret>[?tier=&secret=]{secret: authority} + tier/secret from query (host is always edge.goldsky.com)
alchemy://<key>{apiKey: key}
ankr://<key>{apiKey: key}
blastapi://<key>{apiKey: key}
satelink://free@polygonfree tier, no key
satelink://<key>@polygon{apiKey: key}
blockdaemon://<key>{apiKey: key}
blockpi://<key>{apiKey: key}
chainstack://<key>[?project=&region=&…]{apiKey: key} + all query params copied verbatim
conduit://<key>{apiKey: key}
drpc://<key>{apiKey: key}
dwellir://<key>{apiKey: key}
envio://<rootDomain>{rootDomain: host}
erpc://<host>[/<path>][?secret=]{endpoint: "https://"+host+"/"+path} + secret from query
etherspot://<key>{apiKey: key}
infura://<key>{apiKey: key}
llama://<key>{apiKey: key}
onfinality://<key>{apiKey: key}
pimlico://<key>{apiKey: key}
quicknode://<key>[?tagIds=1,2&tagLabels=a,b]{apiKey: key} + parsed tagIds/tagLabels
repository://<host>/<path>[?query]{repositoryUrl: "https://"+host+"/"+path+"?"+query}
routemesh://<host>/rpc/<chainId>/<apiKey>{baseURL: host, apiKey: pathParts[2]} — path must match exactly
superchain://<spec>{registryUrl: host+path}
tenderly://<key>{apiKey: key}
thirdweb://<clientId>{clientId: host}
unknown schemeconfig-load error: unsupported vendor name in vendor.settings: <scheme>

Vendor catalog

goldsky

Goldsky Edge (opens in a new tab) — a multi-region public RPC service built on eRPC. One secret token covers every chain Goldsky serves; upstreams are lazy-generated per network. SupportsNetwork probes live eth_chainId against https://edge.goldsky.com/{tier}/evm/{chainId}?secret={secret} (10 s timeout) and confirms the returned chain id matches — so the secret's plan authoritatively decides chain support. A missing secret makes SupportsNetwork return (false, nil) (silent skip, no retry storm). tier defaults to standard; endpoint defaults to https://edge.goldsky.com and may be overridden for enterprise/custom deployments (an explicit path on the base URL is respected verbatim and the tier route is not injected). The secret can also be supplied out-of-band via the X-ERPC-Secret-Token header on a preset endpoint. Probe clients are cached per "{url}-{chainId}". OwnsUpstream: goldsky:///evm+goldsky:// prefix, vendorName=="goldsky", or endpoint contains edge.goldsky.com.

keyrequireddefault
secretyes (auth fails without it)
tiernostandard
endpointnohttps://edge.goldsky.com
thirdparty/goldsky.go:L31-249

alchemy

Chain discovery: remote catalog (default https://app-api.alchemy.com/trpc/config.getNetworkConfig) keyed by URL via RemoteDataCache; cold-start fallback to a 134-chain built-in map; after a successful fetch the API result is merged with static defaults (API wins on conflict, static fills gaps). URL template: https://{subdomain}.g.alchemy.com/v2/{apiKey}. Error mapping: -32600 + authenticated message → ErrEndpointUnauthorized; code 3 → EVM revert.

keyrequireddefault
apiKeyyes
chainsUrlnohttps://app-api.alchemy.com/trpc/config.getNetworkConfig
recheckIntervalno24h
creditUnitsnoAlchemy's documented CU table

Cost accounting: implements CreditUnitsProvider with Alchemy's documented compute-unit costs (opens in a new tab) (eth_call 26, eth_getLogs 60, eth_blockNumber 10, "*" 20, …). The table is auto-fetched from the docs' Markdown endpoint (compute-unit-costs.md) and cached ~1 week (refreshed off the hot path from SupportsNetwork/GenerateConfigs), so pricing tracks Alchemy without a redeploy; the built-in table is the cold-start / fetch-failure fallback. settings.creditUnits overrides per method. thirdparty/alchemy.go:L186-320

OwnsUpstream: alchemy:///evm+alchemy:// prefix, vendorName=="alchemy", or endpoint contains .alchemy.com/.alchemyapi.io. thirdparty/alchemy.go:L156-326

ankr

Static map of 47 chains. URL: https://rpc.ankr.com/{netName}/{apiKey} (URL-escaped). OwnsUpstream: scheme prefixes or rpc.ankr.com.

keyrequireddefault
apiKeyyes
thirdparty/ankr.go:L15-144

blastapi

Static map of 79 chains. URL: https://{netName}.blastapi.io/{apiKey}; Avalanche appends /ext/bc/C/rpc. OwnsUpstream: scheme prefixes or .blastapi.io.

keyrequireddefault
apiKeyyes
thirdparty/blastapi.go:L15-181

blockdaemon

Static map of 18 chains with heterogeneous path shapes. Auth is always header-based: apiKey is injected as Authorization: Bearer <apiKey> on every request regardless of whether the endpoint was preset or generated — a static upstream pointing at svc.blockdaemon.com fails at NewUpstream without apiKey. HTTP 401 → ErrEndpointUnauthorized.

keyrequireddefault
apiKeyyes (even with preset endpoint)
thirdparty/blockdaemon.go:L24-156

blockpi

Static map of 56 chains (exported BlockPiNetworkNames). URL: https://{netName}.blockpi.network/v1/rpc/{escapedKey}; chain movement uses /rpc/v1/{escapedKey}/v1. Error mapping: message containing apikey is on another chainErrEndpointUnauthorized. OwnsUpstream: scheme prefixes or .blockpi.network.

keyrequireddefault
apiKeyyes
thirdparty/blockpi.go:L15-173

chainstack

Account-level node discovery: paginated GET https://api.chainstack.com/v1/nodes/ with Authorization: Bearer {apiKey} and optional filter params; chain ids discovered by eth_chainId probe per node (concurrency-limited, semaphore 10, 10 s client timeout). Cache key includes all filter values so providers sharing an apiKey but different filters get separate snapshots. Missing apiKey(false, nil) (silent skip). Cold cache → ErrRemoteCacheCold. GenerateConfigs fans out one upstream per matching running node; upstream endpoint = https_endpoint + "/" + auth_key; id suffix -<nodeID>. Node decode is intentionally lenient: each node is decoded individually from the paginated response and malformed nodes (type mismatches, missing fields) are skipped rather than failing the entire refresh. OwnsUpstream: scheme prefixes or .core.chainstack.com.

keyrequireddefault
apiKeyyes
recheckIntervalno1h
projectno""
organizationno""
regionno""
providerno""
typeno""
thirdparty/chainstack.go:L51-429

conduit

Dynamic-only discovery from networksUrl; no static fallback → cold start returns ErrRemoteCacheCold. URL: {httpEndpoint}/{apiKey}. OwnsUpstream: scheme prefixes or vendorName=="conduit" only (no domain heuristic — chains live on customer domains). Preset endpoint bypasses discovery entirely. Error mapping: -32600 + auth/api-key message → ErrEndpointUnauthorized; message "limit exceeded"/"capacity limit"ErrEndpointCapacityExceeded; code in range [-32099, -32000]ErrEndpointServerSideException (note: as written the condition code >= -32000 && code <= -32099 is never true — same impossible-range pattern as alchemy, so this branch is dead code).

keyrequireddefault
apiKeyyes
networksUrlnohttps://api.conduit.xyz/public/network/all
recheckIntervalno24h
thirdparty/conduit.go:L16-222

drpc

Remote catalog (default https://lb.drpc.org/networks) with a 150-chain static fallback; after a successful fetch the fallback is fully replaced (no merge). Forces autoIgnoreUnsupportedMethods: false on every config (even preset endpoints) because dRPC transiently returns method-not-found responses that must stay retryable. URL: https://lb.drpc.org/ogrpc?network={name}&dkey={apiKey}. Catalog filters to premium EVM JSON-RPC chains only (blockchain_type=="eth" && api_type=="jsonrpc" && has_premium); when multiple names map to one chain id, the highest priority value wins. Error mapping: message "token is invalid"ErrEndpointUnauthorized; "ChainException: Unexpected error (code=40000)" or "invalid block range"ErrEndpointMissingData.

keyrequireddefault
apiKeyyes
chainsUrlnohttps://lb.drpc.org/networks
recheckIntervalno24h
creditUnitsnodRPC's flat CU table

Cost accounting: implements CreditUnitsProvider with dRPC's documented compute-unit costs (opens in a new tab) — a flat 20 CU for every billable method (debug/trace included, no multiplier tier), 0 for informational methods (eth_chainId, net_version, web3_clientVersion, …), and "*" = 20 for anything unlisted; settings.creditUnits overrides per method. thirdparty/drpc.go:L207-240

thirdparty/drpc.go:L19-455

dwellir

Static map of 71 chains → subdomain prefixes. URL: https://{subdomain}.dwellir.com/{apiKey}. OwnsUpstream: only dwellir:// prefix (NOT evm+dwellir://) or .dwellir.com — the evm+ form still works because shorthand conversion runs first. Avalanche appends /ext/bc/C/rpc to the URL. SupportsNetwork quirk: an unparseable numeric chain id part returns (false, nil) rather than an error (unlike every other static-map vendor which propagates the parse error).

keyrequireddefault
apiKeyyes
thirdparty/dwellir.go:L15-180

envio

Method allow-list injected on every upstream (even preset static ones): ignoreMethods: ["*"] + 14 read-oriented methods (eth_getLogs, eth_blockNumber, eth_getBlockByNumber, etc.). SupportsNetwork: 61 known chain ids short-circuit true; unknown chains are live-probed via eth_chainId (10 s timeout; TLS error with "failed to verify certificate" → false). URL: https://{chainId}.{rootDomain}[/{apiKey}].

keyrequireddefault
rootDomainnorpc.hypersync.xyz
apiKeyno""
thirdparty/envio.go:L20-258

erpc

Chains to another eRPC instance. SupportsNetwork probes live eth_chainId. parseEndpointURL appends /{chainId} and optionally sets ?secret=. Port-based scheme inference applies in settings.endpoint; the shorthand erpc://host:8080 forces HTTPS. Probe clients are cached per "{url}-{chainId}".

keyrequireddefault
endpointyes
secretno""
thirdparty/erpc.go:L35-265

etherspot

ERC-4337 bundler (Skandha). Method allow-list injected: ignoreMethods: ["*"] + 6 bundler methods (skandha_*, eth_sendUserOperation, etc.). Static map of 15 mainnets + 11 testnets. URL shape differs by network type: mainnet https://{networkName}-bundler.etherspot.io/, testnet https://testnet-rpc.etherspot.io/v1/{chainId}; ?apikey= appended unless apiKey is "public" or empty.

keyrequireddefault
apiKeyyes
thirdparty/etherspot.go:L15-146

infura

Static map of 31 chains. URL: https://{netName}.infura.io/v3/{apiKey}. Error mapping: -32600 auth message → unauthorized; -32001/-32004 → unsupported; -32005 → capacity exceeded.

keyrequireddefault
apiKeyyes
thirdparty/infura.go:L15-163

llama

Static map of 6 chains (eth, arbitrum, base, binance, optimism, polygon). URL: https://{netName}.llamarpc.com/{apiKey}. OwnsUpstream matches .llamarpc.com domain only (no llama:// scheme check — shorthand still works because conversion runs first). Cloudflare rate-limit message (code: 1015) → ErrEndpointCapacityExceeded.

keyrequireddefault
apiKeyyes
thirdparty/llama.go:L14-97

onfinality

Static map of 25 chains. URL: https://{netName}.api.onfinality.io/rpc?apikey={escapedKey} (apiKey as query param). OwnsUpstream: scheme prefixes or .api.onfinality.io.

keyrequireddefault
apiKeyyes
thirdparty/onfinality.go:L15-122

pimlico

ERC-4337 bundler/paymaster. Method allow-list injected: ignoreMethods: ["*"] + 11 bundler/paymaster methods (pimlico_*, pm_*, eth_sendUserOperation, etc.). SupportsNetwork: 67-chain static set short-circuits true; unknown chains probe live eth_chainIdmissing apiKey is an error (unlike quicknode/chainstack which return false, nil). URL: "public"https://public.pimlico.io/v2/{chainId}/rpc; else https://api.pimlico.io/v2/{chainId}/rpc?apikey={apiKey}.

keyrequireddefault
apiKeyyes (error if missing, even for SupportsNetwork)
thirdparty/pimlico.go:L21-252

quicknode

Account endpoint discovery: paginated GET https://api.quicknode.com/v0/endpoints?limit=100&offset=N with x-api-key header and optional tag filters; a response-level error field aborts the refresh; chain ids discovered by eth_chainId probe per endpoint (concurrency-limited, semaphore 10, 10 s timeout). Cache key is apiKey only — two providers sharing an apiKey but different tagIds/tagLabels share one endpoint snapshot. Missing apiKey(false, nil). Cold cache → ErrRemoteCacheCold. GenerateConfigs fans out one upstream per matching endpoint; id {upstreamId}-{endpointID}. Full error mapping: -32614 or (eth_getLogs + message "limited to") → ErrEndpointRequestTooLarge; -32009/-32007 → capacity exceeded; -32612/-32613ErrEndpointUnsupported; message "failed to parse" → client-side, not retryable toward network; -32010 (tx cost exceeds gas limit) → client-side but network-retryable (per-client gas caps differ); -32602 + "cannot unmarshal hex string" → invalid-argument client-side, not retryable; message "UNAUTHORIZED" → unauthorized; code 3 → EVM revert. Note: the error handler builds a fresh local details map, so per-request details passed by the caller are not modified.

keyrequireddefault
apiKeyyes
recheckIntervalno1h
tagIdsnonil
tagLabelsnonil
creditUnitsnoQuickNode's published credit model

Cost accounting: implements CreditUnitsProvider. When the account apiKey is set, the account-accurate per-method table is auto-fetched from QuickNode's Admin API (GET /v0/api-credits/{chain} (opens in a new tab), x-api-key; the chain slug is resolved from the already-discovered endpoint list) and cached ~1 week off the hot path. The built-in published model (opens in a new tab) (base 20 on EVM, 2x debug/trace = 40, 4x trace replays = 80) is the fallback when no key is set or the fetch fails. settings.creditUnits overrides per method. thirdparty/quicknode.go:L28-120

thirdparty/quicknode.go:L44-452

repository

Fetches repositoryUrl returning {"<chainId>": {"endpoints": [...]}}. No static fallback → ErrRemoteCacheCold on cold start. Defaults autoIgnoreUnsupportedMethods: true — public RPCs are inconsistent in method support. GenerateConfigs fans out one upstream per http endpoint (http/https prefix only; ws/wss skipped); ids embed RedactEndpoint(ep) (https#redacted=ab12c) so dashboards can distinguish endpoints without exposing them. Each generated upstream gets deep copies of Evm, JsonRpc, Failsafe (per-element), RateLimitAutoTune, and Tags from the base config, and inherits IgnoreMethods, AllowMethods, and RateLimitBudget. OwnsUpstream: only repository:///evm+repository:// scheme prefixes (no domain matching — generated upstreams have arbitrary third-party hosts). One half of the implicit default provider pair.

keyrequireddefault
repositoryUrlnohttps://evm-public-endpoints.erpc.cloud
recheckIntervalno1h
thirdparty/repository.go:L17-232

routemesh

No chain catalog — SupportsNetwork always probes live eth_chainId. Missing apiKey → error (provider task retries forever). URL: https://{baseURL}/rpc/{chainId}/{apiKey}. Shorthand requires exact path shape /rpc/<chainId>/<apiKey> or config-load error.

keyrequireddefault
apiKeyyes (error if missing)
baseURLnolb.routemes.sh
thirdparty/routemesh.go:L20-167

superchain

OP Superchain registry. parseSuperchainSpec accepts: github.com/org/repo (defaults branch main, file chainList.json), github.com/org/repo/branch, github.com/org/repo/branch/path/to/file.json, GitHub UI blob/tree URLs (the /blob/ or /tree/ segment is stripped), full http(s):// URLs passed through, or bare domain/path (https prepended). GitHub specs are rewritten to raw.githubusercontent.com. Registry JSON is an array of {chainId, rpc:[...]}; entries with chainId ≤ 0 or empty rpc list dropped; no static fallback → ErrRemoteCacheCold. GenerateConfigs fans out one upstream per rpc URL with id suffix -{index} (e.g. {id}-0, {id}-1, …). OwnsUpstream: scheme prefixes or vendorName=="superchain" only (no domain matching).

keyrequireddefault
registryUrlnohttps://raw.githubusercontent.com/ethereum-optimism/superchain-registry/main/chainList.json
recheckIntervalno24h
thirdparty/superchain.go:L16-312

tenderly

Remote catalog from hardcoded https://api.tenderly.co/api/v1/supported-networks (not overridable via settings — there is no chainsUrl-style override). No static fallback → ErrRemoteCacheCold. Catalog decodes chain_id + network_slugs; slug preference: node_rpc_slug > vnet_rpc_slug > explorer_slug; entries with no usable slug or unparseable chain id are skipped. URL: https://{slug}.gateway.tenderly.co/{apiKey}. OwnsUpstream: scheme prefixes, vendorName=="tenderly", or .gateway.tenderly.co.

keyrequireddefault
apiKeyyes
recheckIntervalno24h
thirdparty/tenderly.go:L34-285

thirdweb

No chain catalog — SupportsNetwork always probes live eth_chainId. Missing clientId → error. URL: https://{chainId}.rpc.thirdweb.com/{clientId}.

keyrequireddefault
clientIdyes (error if missing)
thirdparty/thirdweb.go:L35-141

Worked examples

All patterns below are distilled from real production fleets; comments explain the non-obvious choices.

1. Multi-vendor setup with score de-weighting. Production fleets use Alchemy as the premium primary and cheaper providers (dRPC, Envio) as volume fillers. The scoreMultipliers overall factor deliberately de-weights weaker vendors so the scorer favors Alchemy on latency-sensitive methods while still using them as backfill:

projects[].providers[]
erpc.yaml
projects:  - id: main    providers:      - id: alchemy        vendor: alchemy        settings:          # premium vendor — no score penalty          apiKey: ${ALCHEMY_KEY}        # keep Alchemy away from chains where it adds no value or costs more        ignoreNetworks: [evm:999]        overrides:          # catch-all: apply rate limit budget + light de-weight so fast vendors win          "evm:*":            rateLimitBudget: alchemy-global            routing:              scoreMultipliers:                - network: ""                  method: ""                  # 0.2 = use as 20% of baseline score — Alchemy only wins when                  # others are slow, erroring, or behind on block head                  overall: 0.2      - id: drpc        vendor: drpc        settings:          apiKey: ${DRPC_KEY}      - id: envio        vendor: envio        settings:          rootDomain: rpc.hypersync.xyz

2. Per-chain getLogs splitting via provider overrides. High-throughput chains (Arbitrum, Sei) routinely hit block-range limits when indexers issue large eth_getLogs queries. Production sets getLogsAutoSplittingRangeThreshold per chain inside the provider's overrides so every upstream that vendor auto-generates for that chain inherits the limit without wiring individual upstreams:

projects[].providers[]
erpc.yaml
projects:  - id: main    providers:      - id: quicknode        vendor: quicknode        settings:          apiKey: ${QN_KEY}        ignoreNetworks: [evm:1, evm:8453, evm:42161]        overrides:          # catch-all: slow down state polling on every QN chain (it polls fast by default)          "evm:*":            evm:              # 10s is a safe floor; most chains don't need sub-second head tracking              statePollerInterval: 10s            routing:              scoreMultipliers:                - network: ""                  method: ""                  # 0.3 = mid-tier weight; QN is good but not the fastest in tests                  overall: 0.3          # Arbitrum produces enormous logs — split at 1000 blocks before upstream rejects          "evm:42161":            evm:              getLogsAutoSplittingRangeThreshold: 1000          # Sei has aggressive range limits — split at 500          "evm:1329":            evm:              getLogsAutoSplittingRangeThreshold: 500

3. Provider-level method exclusions to avoid bad upstreams. Some vendors (dwellir, blockpi) do not support trace_filter reliably on testnets. Production adds ignoreMethods per-chain inside overrides so auto-generated upstreams for those chains never receive trace requests that would 405-error and burn retry budget:

projects[].providers[]
erpc.yaml
projects:  - id: main    providers:      - id: dwellir        vendor: dwellir        settings:          apiKey: ${DWELLIR_KEY}        # skip chains covered by better vendors — avoids redundant SupportsNetwork probes        ignoreNetworks: [evm:1, evm:137, evm:42161, evm:8453]        overrides:          # Sepolia and BSC testnet: trace_filter returns errors on dwellir — exclude it          # so the scorer doesn't keep retrying it          "evm:11155111":            ignoreMethods: [trace_filter]          "evm:97":            ignoreMethods: [trace_filter]

4. ERC-4337 bundler alongside general RPC. Pimlico and etherspot auto-inject method allow-lists so only bundler methods route to them — combine with a general-purpose provider for all other methods. No manual allowMethods needed; the vendor injects it at NewUpstream time:

projects[].upstreams[]
erpc.yaml
projects:  - id: main    upstreams:      - endpoint: alchemy://${ALCHEMY_KEY}      # pimlico auto-injects: ignoreMethods: ["*"] + allowMethods: [eth_sendUserOperation,      # pimlico_*, pm_*] — bundler traffic routes here automatically      - endpoint: pimlico://${PIMLICO_KEY}

5. QuickNode with tag-based endpoint groups. Use separate apiKey values per tag group — the RemoteDataCache is keyed by apiKey only, so two providers sharing one key with different tagIds/tagLabels both see whichever snapshot the first refresh produced:

projects[].providers[]
erpc.yaml
projects:  - id: main    providers:      # archive-grade endpoints tagged in the QuickNode dashboard as "archive"      - id: quicknode-archive        vendor: quicknode        settings:          # must be a DIFFERENT apiKey than the realtime group —          # same apiKey + different tagLabels still shares one endpoint snapshot          apiKey: ${QN_ARCHIVE_KEY}          tagLabels: archive      # fast tip-of-chain endpoints on a separate key      - id: quicknode-realtime        vendor: quicknode        settings:          apiKey: ${QN_REALTIME_KEY}          tagLabels: realtime

Best practices

  • Use URL shorthands (alchemy://KEY) for the common case — they are equivalent to a full providers[] entry with a "*" override and avoid boilerplate.
  • Use onlyNetworks to restrict expensive vendors to the chains you actually need; it also prevents the vendor's SupportsNetwork from being called for irrelevant chains.
  • Use exact evm:<chainId> keys in overrides — never mix a catch-all "*" with more-specific patterns in the same map. Go map iteration is nondeterministic; overlapping keys produce different results across restarts.
  • For QuickNode with multiple logical endpoint groups, use a separate apiKey per group (provisioned in the QuickNode dashboard). Sharing one apiKey with different tagIds/tagLabels shares one endpoint snapshot due to the apiKey-only cache key.
  • recheckInterval cannot be set from YAML or TypeScript config. The type assertion .(time.Duration) fails on YAML strings and JSON numbers. Use programmatic Go config (common.VendorSettings{"recheckInterval": 12 * time.Hour}) if you need a custom freshness window.
  • Vendors with no static fallback (conduit, superchain, tenderly, quicknode, chainstack, repository) return ErrRemoteCacheCold on cold start. The initializer retries with backoff, but first requests for those networks will see ErrNetworkInitializing until the remote catalog fetches. Pair these vendors with a fallback provider that has a static map if cold-start latency matters.
  • Do not use providers: [] to "disable" auto-injection — an explicit empty providers list still triggers default injection if upstreams is also empty. Provide at least one upstream or one provider to take full control.

Edge cases & gotchas

  1. recheckInterval from YAML is silently ignored — every vendor reads it with .(time.Duration). YAML scalars decode to string/int64 so the assertion fails and the vendor default applies. Only Go programmatic config can set it. thirdparty/repository.go:L56-59
  2. Cold-start contract differs by vendor: alchemy/drpc fall back to built-in maps; conduit/superchain/tenderly/quicknode/chainstack/repository return ErrRemoteCacheCold and block until the first remote fetch lands. thirdparty/remote_cache.go:L85
  3. drpc replaces its fallback after a successful fetch (no merge); alchemy merges fetched data with its static defaults (API wins on conflict, static fills gaps). thirdparty/drpc.go:L360-422 thirdparty/alchemy.go:L386-405
  4. Missing-apiKey behavior is inconsistent: pimlico/routemesh/thirdweb SupportsNetwork return an error (provider task retries forever); quicknode/chainstack return (false, nil) and are silently skipped. thirdparty/pimlico.go:L120-123
  5. blockdaemon requires apiKey even with a preset endpoint — static upstreams pointing at svc.blockdaemon.com fail at NewUpstream because GenerateConfigs is called with settings=nil. thirdparty/blockdaemon.go:L85-88
  6. Impossible range conditions in alchemy/conduit error mapping: alchemy's code >= -32099 && code <= -32599 can never be true; only the message-match and code-3 branches actually fire. thirdparty/alchemy.go:L275-326
  7. infura's Avalanche /ext/bc/C/rpc branch is unreachable — map keys are avalanche-mainnet/avalanche-fuji but branch tests ava-mainnet/ava-testnet. thirdparty/infura.go:L90-93
  8. overrides first-match is Go map-iteration order (nondeterministic for overlapping patterns). Pattern-match errors are silently skipped. thirdparty/provider.go:L81-91
  9. quicknode cache key is apiKey only — two providers with the same apiKey but different tagIds/tagLabels share one endpoint snapshot. Chainstack includes filters in its cache key and is safe. thirdparty/quicknode.go:L184-205
  10. erpc://host:8080 shorthand forces HTTPSbuildProviderSettings hardcodes https://. Use providers[].settings.endpoint with an explicit URL for non-TLS targets. common/defaults.go:L1389-1404
  11. onlyNetworks/ignoreNetworks are exact string matches (not wildcards), require evm:<positiveInt>, and are mutually exclusive. ignoreNetworks is dropped from MarshalJSON. thirdparty/provider.go:L34-49
  12. os.ExpandEnv runs on every provider-generated endpoint — a literal $ in an API key is substituted from the environment (often to empty). Static upstreams do not get this treatment. thirdparty/provider.go:L63-71
  13. Provider ids must be unique only by convention — no duplicate-id check; two providers with the same default id both run and may produce upstream-id collisions. thirdparty/providers_registry.go:L15-34
  14. repository upstream ids embed a redacted endpoint (https#redacted=ab12c = scheme + 5-char sha256 prefix) so dashboards can distinguish public endpoints without exposing them. util/redact.go:L10-36
  15. OP-stack "sender is over rate limit" is the only capacity error with retryableTowardNetwork=false — all OP providers proxy the same sequencer, so retrying on a different upstream hits the same limit. Expect this error to surface to clients on OP-stack chains. architecture/evm/error_normalizer.go:L125-139
  16. Tenderly "not found" becomes ErrEndpointClientSideException (retryable), not ErrEndpointMissingData — the "Unsupported" block in the normalizer intentionally runs after "Not found" to avoid misclassifying Tenderly's bare "not found". architecture/evm/error_normalizer.go:L394-477
  17. dwellir OwnsUpstream only matches dwellir:// prefix (not evm+dwellir://) or .dwellir.com domain; the evm+ shorthand still works because shorthand conversion runs before vendor lookup. thirdparty/dwellir.go:L173-180
  18. Live-probe vendors dial out during routing decisions: envio/erpc/pimlico/routemesh/thirdweb perform a real eth_chainId HTTP call (10 s timeout) on the provider bootstrap path. Probe clients are cached forever in sync.Maps keyed by chain id or "{url}-{chainId}".
  19. PrepareUpstreamsForNetwork succeeds as soon as one upstream registers (minReady=1). Slow providers continue adding upstreams in the background without blocking the first request. If all provider tasks finish with zero upstreams, there is a 100 ms grace re-check before the retryable ErrNetworkNotSupported is returned; if tasks are still running, the caller receives ErrNetworkInitializing. upstream/registry.go:L188-293
  20. LookupByUpstream with an explicit but non-existent vendorName returns nil — it does not fall through to OwnsUpstream matching. If upstream.vendorName is set to an unregistered name, vendor error-mapping is silently lost. Registration order of the 23 vendors determines which vendor wins when multiple OwnsUpstream checks return true for the same endpoint. thirdparty/vendors_registry.go:L54-71
  21. llama and routemesh OwnsUpstream do not check their own scheme prefixes: llama.OwnsUpstream only matches .llamarpc.com; routemesh.OwnsUpstream only matches vendorName=="routemesh" or endpoint containing routemes.sh. Both shorthands (llama://, routemesh://) work only because shorthand conversion in common/defaults.go runs before vendor lookup. thirdparty/llama.go:L95-97 thirdparty/routemesh.go:L135-140
  22. dwellir SupportsNetwork returns (false, nil) for an unparseable chain id — unlike every other static-map vendor which propagates the parse error. thirdparty/dwellir.go:L109-113

Observability

No provider-specific Prometheus metrics exist. The vendor label on all upstream-level metrics is populated from the attached vendor's Name(), the config's vendorName, or unknown-<rootDomain> for unrecognized endpoints.

metrictypekey labelswhen it fires
erpc_upstream_request_totalcounterproject, vendor, network, upstream, category, attempt, composite, finality, user, agent_nameEvery upstream request attempt
erpc_upstream_request_duration_secondshistogramproject, vendor, network, upstream, category, composite, finality, userUpstream round-trip latency
erpc_upstream_request_errors_totalcounterproject, vendor, network, upstream, category, error, severity, composite, finality, user, agent_nameFailed upstream requests

Bootstrap task progress is visible in initializer task names: network/<networkId>/provider/<providerId> (scheduling) and network/<networkId>/upstream/<upstreamId> (per-generated upstream). upstream/registry.go:L423-481

Trace spans: buildProviderBootstrapTask emits a detail span per provider task; buildUpstreamBootstrapTask emits one for each registered upstream. upstream/registry.go:L423-485

Notable log messages:

  • warn no providers or upstreams found in project; will use default 'public' endpoints repository
  • debug attempting to create upstream(s) from provider / debug provider does not support network; skipping upstream creation / info registering N upstream(s) from provider
  • error failed to execute provider bootstrap tasks / info provider bootstrap tasks executed successfully
  • warn vendor remote-data refresh failed; keeping previous snapshot with vendor + cacheKey fields
  • error panic recovered during vendor remote-data async refresh
  • warn some quicknode chain ID fetches failed; continuing with available data / warn failed to fetch chain IDs for some QuickNode endpoints
  • warn some chainstack chain ID fetches failed; continuing with available data / warn failed to fetch chain IDs for some Chainstack nodes
  • info successfully fetched dRPC network names (with count + url)
  • debug generated upstream from alchemy provider / generated upstream from conduit provider / generated upstreams from repository provider / generated upstreams from superchain provider

Source code entry points

Related pages

  • Upstreams — static upstream entries; providers generate these dynamically.
  • Selection policies — controls which generated upstream handles each request.
  • Failsafe — configure retry/hedge/timeout inside overrides per network.
  • Rate limiters — cap per-vendor request rates to stay within API budgets.
  • Auth — protect your eRPC instance when proxying to paid vendor endpoints.