# Matcher syntax > Source: https://docs.erpc.cloud/config/matcher > One pattern engine everywhere — globs, boolean logic, and hex ranges that work identically across cache policies, failsafe rules, rate limits, method filters, and routing directives. > Format: machine-readable markdown export of the docs page above. > All collapsible AI sections are inlined and fully expanded. # Matcher syntax Write one pattern, use it everywhere. eRPC's single `WildcardMatch` engine handles globs (`eth_*`), boolean composition (`eth_* & !eth_call`), and hex numeric ranges (`>=0x100 & <=0x200`) identically across cache policies, failsafe rules, rate limits, method allow/ignore lists, CORS, aliasing, upstream selectors, and routing. There is nothing new to learn when you reach a new config section. ## Quick taste Illustrative — any method, any finality; exclude debug_ from rate limits: **Config path:** `projects[].networks[].failsafe[]` **YAML — `erpc.yaml`:** ```yaml projects: - id: main networks: - architecture: evm evm: { chainId: 1 } failsafe: # glob + boolean: all eth_ methods except eth_call - matchMethod: "eth_* & !eth_call" retry: maxAttempts: 3 ``` **TypeScript — `erpc.ts`:** ```typescript projects: [{ id: "main", networks: [{ architecture: "evm", evm: { chainId: 1 }, failsafe: [{ // glob + boolean: all eth_ methods except eth_call matchMethod: "eth_* & !eth_call", retry: { maxAttempts: 3 }, }], }], }] ``` ## 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: configure failsafe rules for specific methods and finalities** ```text I want to tune my eRPC failsafe config so different retry/hedge policies apply to different methods and finality states — e.g. aggressive retry for finalized eth_getLogs, light retry for realtime, and no hedge on tx-hash lookups. Work with my existing eRPC config. Read the matcher syntax reference first: https://docs.erpc.cloud/config/matcher.llms.txt ``` **Prompt Example #2: build an upstream method whitelist** ```text One of my upstreams is an archive node that should only handle eth_getLogs, eth_getBlockByNumber, eth_getBlockByHash, eth_getTransactionByHash, and eth_getTransactionReceipt — all other methods should fall to other upstreams. Configure ignoreMethods and allowMethods correctly. Work with my existing eRPC config. Reference: https://docs.erpc.cloud/config/matcher.llms.txt ``` **Prompt Example #3: set up cache policies with hex-range and finality scoping** ```text I want eRPC to cache eth_getLogs and block-lookup methods only for finalized and unfinalized requests, using hex block-range patterns in params so only concrete block numbers are cached (not "latest"). Work with my existing eRPC config. Reference: https://docs.erpc.cloud/config/matcher.llms.txt ``` **Prompt Example #4: debug why my matchMethod or matchFinality rule is not matching** ```text My eRPC failsafe rule with matchMethod "eth_get*" and matchFinality [finalized] does not seem to be picked up for some requests. Walk me through how network-scope vs upstream-scope failsafe selection works (4-tier vs config-order), and check my eRPC config for ordering mistakes or wrong finality values. Reference: https://docs.erpc.cloud/config/matcher.llms.txt ``` **Prompt Example #5: add per-method rate-limit rules without accidentally stacking them** ```text I want to add a rate-limit budget rule that caps debug_* and trace_* at 30 RPS and a separate rule that caps eth_getBlockReceipts at 100 RPS, but I don't want a wildcard "*" rule to stack on top and double-count. Explain how GetRulesByMethod works and write the correct rules for my eRPC config. Reference: https://docs.erpc.cloud/config/matcher.llms.txt ``` --- ### Matcher syntax — full agent reference ### How it works **Core engine (`WildcardMatch`).** Every call tokenizes the pattern string into operators (`|`, `&`, `!`, `(`, `)`) and atom tokens, then builds a recursive function AST via Pratt-style recursive-descent parsing (`parseOr → parseAnd → parseUnary → parsePrimary`). The resulting `func(string) bool` closure is invoked immediately against the test value. There is **no compiled- pattern cache** — each call re-tokenizes and re-parses. This is lightweight for short patterns but re-tokenization cost is paid on every forwarded request for failsafe executor selection. **Glob atoms.** Plain atoms (no operator prefix) are dispatched to the `go-wildcard` library: `*` matches any number of characters, `?` matches exactly one. The `` literal is handled specially before the glob library — it matches only the empty string. **Boolean composition.** Operator precedence, highest to lowest: NOT (`!`) > AND (`&`) > OR (`|`). Parentheses override. Both `&` and `|` are left-associative. Spaces are atom separators — `"a | b"` and `"a|b"` are identical. The characters `|`, `&`, `!`, `(`, `)` **cannot appear as literals** in a pattern — there is no escape mechanism. **Hex numeric comparisons.** When the *runtime value* starts with `0x`, the engine tries numeric comparison operators (`>=`, `<=`, `>`, `<`, `=`) as pattern prefixes. Both sides are parsed as hex integers. If the value does **not** start with `0x` (e.g., a decimal block number or a named tag like `"latest"`), numeric operators fall through to glob matching and will not compare as expected. Use `0x*` to match hex-format values, and combine with named tags using `|` for full coverage. **`MatchesSelector` and upstream tag awareness.** The `use-upstream` directive and served-tip partition grouping use `MatchesSelector` rather than plain `WildcardMatch`. The function first tests the pattern against the upstream's `id`. If that matches, it returns `true` immediately. If the pattern contains **no `!` anywhere** (purely-positive), it also iterates the upstream's `tags []string` and returns `true` on any tag match. Any `!` anywhere in the pattern — even `"!other-upstream"` — disables tag matching entirely. This is intentional: a tag match must never rescue an upstream the operator meant to exclude. **`matchMethodPattern` helper.** Inside `SelectExecutor`, method matching uses a helper that treats both empty string and `"*"` as match-anything, and tries exact equality before calling `WildcardMatch` to reduce overhead on common exact-match patterns. Source: [`common/match.go:L83-92`](https://github.com/erpc/erpc/blob/main/common/match.go#L83-L92) **`SelectExecutor` — 4-tier priority for failsafe dispatch.** Given executors with `(matchMethod, matchFinality)` fields and the request's `(method, finality)`, `SelectExecutor` selects the first matching executor across four tiers in priority order (single pass): 1. **Exact** — specific method AND specific finality 2. **Method-only** — specific method, any finality (`matchFinality: []`) 3. **Finality-only** — `matchMethod: "*"`, specific finality 4. **Catch-all** — `matchMethod: "*"`, `matchFinality: []` **Important:** upstream-level and network-level failsafe selection use different algorithms. Upstreams use the 4-tier priority algorithm above. Networks use a simpler **first-match-in- config-order scan** — if an any-method entry appears before a specific-method entry in config, the any-method entry wins. Place most-specific entries *before* catch-all entries in network-scope failsafe. **Cache policy param matching.** `matchParams` is positional: index `i` in the config pattern matches `params[i]` in the request. Matching is recursive: map patterns require all keys to match, array patterns require equal length then per-element matching, string patterns use `WildcardMatch`, and other types are converted to strings via `paramToString` (JSON `float64` numbers become their decimal string form: `1234.0 → "1234"`). **Auth method filter semantics.** `ignoreMethods` is checked first (any match → method blocked), then `allowMethods` overrides (any match → force-allowed). Same semantics at upstream, project, and auth-strategy scopes. **Rate-limit rules.** `GetRulesByMethod` returns **all** rules whose method pattern matches — not just the first. Multiple rules can simultaneously consume token-bucket permits for a single request. ### Config schema The matcher engine has no dedicated config block. The table below covers every config field matched via `WildcardMatch`, `MatchesSelector`, `SelectExecutor`, or `MatchFinalities`, grouped by subsystem. **Failsafe** — `*.failsafe[]` (networks, upstreamDefaults, upstreams, networkDefaults, and cache connector `failsafeForGets[]`/`failsafeForSets[]`) | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `matchMethod` | string | `"*"` (set by `SetDefaults` when empty) | `WildcardMatch` against the request's JSON-RPC method. Empty is rejected by `Validate()`. During defaults inheritance, a default's `matchMethod` is glob-matched against the entry's `matchMethod` to select which default to apply. Source: [`common/config.go:L1280`](https://github.com/erpc/erpc/blob/main/common/config.go#L1280), [`common/defaults.go:L2131-2137`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L2131-L2137) | | `matchFinality` | []DataFinalityState | `[]` (empty = any finality) | Exact set-membership check at dispatch time: `slices.Contains(list, finality)`. Empty list = catch-all finality tier. No wildcard inside the list — each element must be an exact finality name. Source: [`common/config.go:L1281`](https://github.com/erpc/erpc/blob/main/common/config.go#L1281), [`common/match.go:L38-41`](https://github.com/erpc/erpc/blob/main/common/match.go#L38-L41) | **Cache policies** — `database.evmJsonRpcCache.policies[]` | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `network` | string | `"*"` (set by `SetDefaults`) | `WildcardMatch` against the network ID (e.g., `"evm:1"`, `"evm:*"`). Source: [`common/config.go:L320`](https://github.com/erpc/erpc/blob/main/common/config.go#L320), [`data/cache_policy.go:L67`](https://github.com/erpc/erpc/blob/main/data/cache_policy.go#L67) | | `method` | string | `"*"` (set by `SetDefaults`) | `WildcardMatch` against the JSON-RPC method. Source: [`common/config.go:L321`](https://github.com/erpc/erpc/blob/main/common/config.go#L321), [`data/cache_policy.go:L75`](https://github.com/erpc/erpc/blob/main/data/cache_policy.go#L75) | | `params` | []interface{} | `nil` (nil = match any params) | Positional param matching; strings use `WildcardMatch`. Maps/arrays matched recursively. Non-string types converted via `paramToString`. Source: [`common/config.go:L322`](https://github.com/erpc/erpc/blob/main/common/config.go#L322), [`data/cache_policy.go:L166-232`](https://github.com/erpc/erpc/blob/main/data/cache_policy.go#L166-L232) | | `finality` | DataFinalityState | `finalized` (zero value = 0) | For `MatchesForSet`: exact equality. For `MatchesForGet`: `finalized` requests also match `unfinalized` policies; `unknown` finality matches any policy. **Footgun:** omitting `finality` yields a finalized-only policy silently. Source: [`common/config.go:L323`](https://github.com/erpc/erpc/blob/main/common/config.go#L323), [`data/cache_policy.go:L99-150`](https://github.com/erpc/erpc/blob/main/data/cache_policy.go#L99-L150) | **Rate-limit rules** — `rateLimiters.budgets[].rules[]` | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `method` | string | required | `WildcardMatch` against request method. **ALL** matching rules are returned (not just first) — each independently consumes a token-bucket permit. Source: [`common/config.go:L1811`](https://github.com/erpc/erpc/blob/main/common/config.go#L1811), [`upstream/ratelimiter_budget.go:L63-78`](https://github.com/erpc/erpc/blob/main/upstream/ratelimiter_budget.go#L63-L78) | **Upstream method filters** — `upstreams[]`, `projects[]`, `auth.strategies[]` | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `ignoreMethods` | []string | `nil` | `WildcardMatch` per entry; any match → method reported unsupported. Applied before `allowMethods`. Source: [`common/config.go:L731`](https://github.com/erpc/erpc/blob/main/common/config.go#L731), [`upstream/upstream.go:L1375-1385`](https://github.com/erpc/erpc/blob/main/upstream/upstream.go#L1375-L1385) | | `allowMethods` | []string | `nil` | `WildcardMatch` per entry; any match → overrides an `ignoreMethods` hit. To allow only `eth_getLogs`: set `ignoreMethods: ["*"]`, `allowMethods: ["eth_getLogs"]`. Source: [`common/config.go:L732`](https://github.com/erpc/erpc/blob/main/common/config.go#L732), [`upstream/upstream.go:L1388-1399`](https://github.com/erpc/erpc/blob/main/upstream/upstream.go#L1388-L1399) | **CORS, aliasing, headers, selectors** | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `projects[].cors.allowedOrigins[]` | []string | `["*"]` | `WildcardMatch` against `Origin` header; first match allows. Source: [`erpc/http_server.go:L1024`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L1024) | | `server.aliasing.rules[].matchDomain` | string | — | `WildcardMatch` against `Host` header (port stripped); first rule match wins. Source: [`common/config.go:L258`](https://github.com/erpc/erpc/blob/main/common/config.go#L258), [`erpc/http_server.go:L244-257`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L244-L257) | | `projects[].forwardHeaders[]` | []string | `nil` | `WildcardMatch` against incoming header names; matching headers forwarded to upstreams. Source: [`common/config.go:L519`](https://github.com/erpc/erpc/blob/main/common/config.go#L519), [`erpc/http_server.go:L480-493`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L480-L493) | | `upstreams[].id` | string | — | Primary identity matched by `MatchesSelector` against `use-upstream` directive value. Source: [`common/matcher.go:L106-112`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L106-L112) | | `upstreams[].tags[]` | []string | `nil` | Secondary match by `MatchesSelector` if id didn't match and pattern has no `!`. Source: [`common/matcher.go:L73-100`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L73-L100) | **Score multipliers** — `upstreams[].routing.scoreMultipliers[]` | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `network` | string | `""` (empty = any) | `WildcardMatch` against network ID. Source: [`common/config.go:L798`](https://github.com/erpc/erpc/blob/main/common/config.go#L798) | | `method` | string | `""` (empty = any) | `WildcardMatch` against method. Source: [`common/config.go:L799`](https://github.com/erpc/erpc/blob/main/common/config.go#L799) | | `finality` | []DataFinalityState | `[]` (any) | `slices.Contains` check. Source: [`common/config.go:L800`](https://github.com/erpc/erpc/blob/main/common/config.go#L800) | **Tracing force-trace matchers** — `tracing.forceTraceMatchers[]` | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `network` | string | `""` (absent = any) | `WildcardMatch` against network ID. Both `network` AND `method` must match. Source: [`common/config.go:L241`](https://github.com/erpc/erpc/blob/main/common/config.go#L241), [`common/tracing_core.go:L306-329`](https://github.com/erpc/erpc/blob/main/common/tracing_core.go#L306-L329) | | `method` | string | `""` (absent = any) | `WildcardMatch` against method. Source: [`common/config.go:L245`](https://github.com/erpc/erpc/blob/main/common/config.go#L245) | **Served-tip guaranteed methods** — `networks[].evm.servedTip.guaranteedMethods[]` | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `networks[].evm.servedTip.guaranteedMethods[]` | []string | — | Each element validated by `ValidatePattern` at config load (the only config field with startup validation). At runtime, `WildcardMatch` against the request method determines whether the served-tip mechanism applies. Source: [`common/validation.go:L1323-1326`](https://github.com/erpc/erpc/blob/main/common/validation.go#L1323-L1326) | **Provider network overrides** — `providers[].overrides` (map keys) | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `providers[].overrides` (map key) | string | — | `WildcardMatch` pattern against network ID. First matching key wins. Source: [`thirdparty/provider.go:L80-L91`](https://github.com/erpc/erpc/blob/main/thirdparty/provider.go#L80-L91) | **Consensus quota tags** — `consensus/quota.go` Tag membership for quota matching uses `WildcardMatch(pattern, t)` against each tag string in the upstream config's `Tags` slice. Source: [`consensus/quota.go:L96`](https://github.com/erpc/erpc/blob/main/consensus/quota.go#L96) **Static responses** — `networks[].staticResponses[]` (**NOT** WildcardMatch) | Field | Type | Default | Behavior / footguns | |---|---|---|---| | `method` | string | required | **Exact string equality only** — no glob, no boolean operators. `"eth_getBlock*"` will NOT match `eth_getBlockByNumber`. Source: [`common/static_response.go:L16`](https://github.com/erpc/erpc/blob/main/common/static_response.go#L16) | | `params` | []interface{} | `nil` (nil = match any params) | Deep equality with numeric-type tolerance (YAML int vs JSON float64). NOT `WildcardMatch`. Source: [`common/static_response.go:L19-39`](https://github.com/erpc/erpc/blob/main/common/static_response.go#L19-L39) | **`DataFinalityState` values** | YAML string | Go constant | iota | Meaning | |---|---|---|---| | `"finalized"` | `DataFinalityStateFinalized` | 0 | Block ≤ upstream finalized block. Zero value — omitting `finality` in config gives this. | | `"unfinalized"` | `DataFinalityStateUnfinalized` | 1 | Block known and > upstream finalized block. | | `"realtime"` | `DataFinalityStateRealtime` | 2 | Fresh, block-unbound data (e.g., `eth_gasPrice`). | | `"unknown"` | `DataFinalityStateUnknown` | 3 | Block number cannot be determined (trace-by-hash, etc.). | YAML/JSON accepts string names or their numeric iota indices (`"0"` = `finalized`, `"1"` = `unfinalized`, `"2"` = `realtime`, `"3"` = `unknown`). Source: [`common/data.go:L8-76`](https://github.com/erpc/erpc/blob/main/common/data.go#L8-L76) ### Worked examples All patterns below are distilled from real production fleets; comments explain the non-obvious choices. **1. Method whitelist on a narrow upstream: block everything, allow only what it indexes.** Production indexing upstreams (e.g. archive nodes, specialized caches) only serve a small set of methods — blocking everything else avoids wasting a request slot and lets the router immediately fall to a full-service upstream. This is the canonical `ignoreMethods + allowMethods` pattern used on every specialized upstream in the fleet: **Config path:** `upstreams[]` **YAML — `erpc.yaml`:** ```yaml upstreams: - id: archive-node # Block all methods on this upstream by default ignoreMethods: - "*" # Then re-allow only the methods this upstream actually serves — anything # else immediately falls to the next upstream without a wasted RTT allowMethods: - "eth_getLogs" - "eth_getBlockByNumber" - "eth_getBlockByHash" - "eth_getBlockReceipts" - "eth_getTransactionByHash" - "eth_getTransactionReceipt" ``` **TypeScript — `erpc.ts`:** ```typescript upstreams: [{ id: "archive-node", // Block all methods on this upstream by default ignoreMethods: ["*"], // Then re-allow only the methods this upstream actually serves — anything // else immediately falls to the next upstream without a wasted RTT allowMethods: [ "eth_getLogs", "eth_getBlockByNumber", "eth_getBlockByHash", "eth_getBlockReceipts", "eth_getTransactionByHash", "eth_getTransactionReceipt", ], }] ``` **2. Finality-scoped network failsafe: different retry+hedge per finality state.** In production, `realtime` and `unfinalized` requests are subject to block-availability races — a short non-zero `delay` gives the state-propagation poller time to catch up before retrying. Finalized requests have no race, so `delay: 0` is correct there. At upstream scope the 4-tier priority resolves conflicts automatically; at network scope, most-specific entries MUST come first in config order: **Config path:** `projects[].networks[].failsafe[]` **YAML — `erpc.yaml`:** ```yaml failsafe: # Specific method + specific finality wins over all catch-alls (at upstream scope, # this is tier 1 priority; at network scope, place this BEFORE the catch-all) - matchMethod: "eth_call|eth_getLogs" matchFinality: [realtime, unfinalized] retry: maxAttempts: 4 # MUST NOT be 0: a short delay lets block-state propagation catch up before # retrying a "block not available yet" error across another upstream delay: 50ms hedge: delay: quantile: 0.95 min: 500ms max: 10s maxCount: 1 # Same methods, finalized — no block-availability race, so delay: 0 is correct - matchMethod: "eth_call|eth_getLogs" matchFinality: [finalized, unknown] retry: maxAttempts: 4 delay: 0 # Catch-all for everything else — realtime/unfinalized needs the delay too - matchMethod: "*" matchFinality: [realtime, unfinalized] retry: maxAttempts: 6 delay: 50ms - matchMethod: "*" matchFinality: [finalized, unknown] retry: maxAttempts: 6 delay: 0 ``` **TypeScript — `erpc.ts`:** ```typescript failsafe: [ { matchMethod: "eth_call|eth_getLogs", matchFinality: ["realtime", "unfinalized"], retry: { maxAttempts: 4, // MUST NOT be 0: block-state propagation needs a moment to catch up delay: "50ms", }, hedge: { delay: { quantile: 0.95, min: "500ms", max: "10s" }, maxCount: 1 }, }, { matchMethod: "eth_call|eth_getLogs", matchFinality: ["finalized", "unknown"], retry: { maxAttempts: 4, delay: 0 }, }, { matchMethod: "*", matchFinality: ["realtime", "unfinalized"], retry: { maxAttempts: 6, delay: "50ms" }, }, { matchMethod: "*", matchFinality: ["finalized", "unknown"], retry: { maxAttempts: 6, delay: 0 }, }, ] ``` **3. Per-method upstream timeouts using glob priority.** Production upstream failsafe sets tight timeouts per method tier — heavy methods like `eth_getLogs` need a generous ceiling; light point-reads need a tight one to encourage fast hedging on stuck upstreams. Glob patterns (`eth_get*`) serve as a catch-all tier, with more-specific exact entries placed before them. At upstream scope, 4-tier priority handles the ordering automatically, so explicit placement is not required — but it is still good style: **Config path:** `upstreamDefaults.failsafe[]` **YAML — `erpc.yaml`:** ```yaml upstreamDefaults: failsafe: # Exact method entries are tier 2 (method-only) — win over the glob catch-all tier - matchMethod: "eth_getLogs|eth_getBlockReceipts" timeout: # Variable latency tied to range/density — routinely seconds on big queries duration: 15s - matchMethod: "eth_getTransactionReceipt" timeout: duration: 8s - matchMethod: "eth_getBlockByNumber" timeout: # min raised: archive lookups on fast chains (Sei, Monad) can take 350–500ms duration: 3s - matchMethod: "trace_*|debug_*|arbtrace_*" timeout: # Trace methods can be slow — 30s gives them room without blocking others duration: 30s # Glob catch-all for remaining eth_get* light getters — tier 2, wins over "*" - matchMethod: "eth_get*" timeout: duration: 5s # Final catch-all — tier 4, lowest priority - matchMethod: "*" timeout: duration: 60s ``` **TypeScript — `erpc.ts`:** ```typescript upstreamDefaults: { failsafe: [ { matchMethod: "eth_getLogs|eth_getBlockReceipts", // Variable latency tied to range/density — routinely seconds on big queries timeout: { duration: "15s" }, }, { matchMethod: "eth_getTransactionReceipt", timeout: { duration: "8s" } }, { matchMethod: "eth_getBlockByNumber", // min raised: archive lookups on fast chains can take 350–500ms timeout: { duration: "3s" }, }, { matchMethod: "trace_*|debug_*|arbtrace_*", // Trace methods can be slow — 30s gives them room without blocking others timeout: { duration: "30s" }, }, // Glob catch-all for remaining eth_get* light getters { matchMethod: "eth_get*", timeout: { duration: "5s" } }, // Final catch-all — lowest priority { matchMethod: "*", timeout: { duration: "60s" } }, ], } ``` **4. Cache policy with explicit method allowlist and all-finality coverage.** In production, cache policies use a pipe-joined method pattern (`|`) rather than a broad glob because `eth_get*` would flood every connector with requests for methods nothing serves (eth_getBalance, eth_getCode, eth_getStorageAt, …), paying a connector RTT for a guaranteed miss. Three finality tiers cover all concrete-block requests; `realtime` finality is intentionally excluded so head-tracking queries always hit fresh upstreams: ```yaml database: evmJsonRpcCache: policies: # Using | instead of eth_get* avoids cache misses on balance/code/storage methods - network: "evm:1" method: "eth_getBlockByNumber|eth_getBlockByHash|eth_getBlockReceipts|eth_getLogs|eth_getTransactionByHash|eth_getTransactionReceipt" finality: unfinalized connector: my-cache - network: "evm:1" method: "eth_getBlockByNumber|eth_getBlockByHash|eth_getBlockReceipts|eth_getLogs|eth_getTransactionByHash|eth_getTransactionReceipt" finality: unknown connector: my-cache - network: "evm:1" method: "eth_getBlockByNumber|eth_getBlockByHash|eth_getBlockReceipts|eth_getLogs|eth_getTransactionByHash|eth_getTransactionReceipt" finality: finalized connector: my-cache # realtime deliberately omitted — eth_blockNumber / eth_getBlockByNumber("latest") # must always hit upstreams so clients see the current chain head ``` **5. Per-method rate-limit rules: protect heavy methods without stacking a wildcard.** Heavy-method traffic (`eth_getBlockReceipts`, `trace_*`) can exhaust proxy concurrency when a single user sends high-error-rate bursts, holding a connection per failed attempt. A method-specific rule caps only that traffic; a separate wildcard rule caps global volume. Note that ALL matching rules fire simultaneously — if you add a `"*"` rule AND a specific rule, a heavy-method request consumes from both buckets: **Config path:** `rateLimiters.budgets[]` **YAML — `erpc.yaml`:** ```yaml rateLimiters: budgets: - id: chain-admission rules: # Heavy receipt methods empirically drive proxy-connection stack-ups — cap tightly. # Healthy peak is ~5 RPS total; 100 RPS gives 20× headroom for legitimate spikes - method: "eth_getBlockReceipts|eth_getTransactionReceipt" maxCount: 100 period: 1s # Trace methods have low typical volume; 30 RPS matches observed traffic - method: "debug_*|trace_*|arbtrace_*" maxCount: 30 period: 1s # Global wildcard intentionally omitted here — these two rules are # independent; a request matching both rules debits both buckets ``` **TypeScript — `erpc.ts`:** ```typescript rateLimiters: { budgets: [{ id: "chain-admission", rules: [ // Heavy receipt methods empirically drive proxy-connection stack-ups — cap tightly. // Healthy peak is ~5 RPS total; 100 RPS gives 20× headroom for legitimate spikes { method: "eth_getBlockReceipts|eth_getTransactionReceipt", maxCount: 100, period: "1s" }, // Trace methods have low typical volume; 30 RPS matches observed traffic { method: "debug_*|trace_*|arbtrace_*", maxCount: 30, period: "1s" }, // Global wildcard intentionally omitted here — these two rules are // independent; a request matching both rules debits both buckets ], }], } ``` ### Request/response behavior - The `use-upstream` directive is set via the `X-ERPC-Use-Upstream` request header or query param. The value is a `MatchesSelector` pattern — id first, then tags if no `!` in pattern. - `ignoreMethods` and `allowMethods` at project or upstream scope cause eRPC to return `ErrUpstreamMethodIgnored` for blocked methods — surfaced to the client as an upstream unsupported error. - Rate-limit rules whose `method` matches fire simultaneously: a request denied by two matching rules gets a rate-limit error even if only one rule was intended as the effective gate. - A malformed pattern in most config fields silently skips matches at runtime (caller `continue`-on-error pattern) — no error is returned to the client; the config field is simply treated as non-matching. ### Best practices - Prefer **exact method names or simple globs** (`"eth_*"`, `"*"`) on hot paths like failsafe `matchMethod` — complex boolean expressions re-parse on every request with no caching. - At **network scope, order failsafe entries from most-specific to least-specific manually** — unlike upstream scope, there is no 4-tier priority; the first matching entry wins. - For rate-limit budgets, remember that **all matching rules fire simultaneously** — a global `"*"` rule and a per-method rule both debit their buckets. Size them with overlap in mind. - Use **`ignoreMethods: ["*"]` + `allowMethods: [...]`** to build explicit whitelists — it is cleaner and safer than long lists of specific ignore patterns. - For hex numeric comparisons in cache policy params, always **also cover named tags** with `|`: `"(latest | safe | finalized) | >=0x1000000"` — a value like `"latest"` has no `0x` prefix and falls through to glob matching, so numeric-only patterns miss it. - Avoid setting `!` in `use-upstream` selector patterns unless you specifically want to suppress tag matching — any `!` anywhere in the pattern disables tag fallback entirely. - **Validate patterns at startup** where possible: `ValidatePattern` is only automatically called for `evm.servedTip.guaranteedMethods`; other fields accept invalid patterns silently and fail at runtime. ### Edge cases & gotchas 1. **No escaping for meta-characters.** `|`, `&`, `!`, `(`, `)` cannot appear as literal values in a pattern. A method name like `net_!custom` is untestable via `WildcardMatch`. Source: [`common/matcher.go:L226-258`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L226-L258) 2. **Spaces are atom separators.** `"eth_getLogs "` (trailing space) tokenizes as `["eth_getLogs"]`. A pattern intended to match a literal space will silently split into two tokens. Source: [`common/matcher.go:L229-260`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L229-L260) 3. **Numeric operators require `0x` prefix on the runtime value.** `>100` against value `"200"` does NOT compare numerically — it falls through to glob. Use hex-prefixed values (`"0x64"` = 100) to enable numeric comparison. Source: [`common/matcher.go:L373-391`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L373-L391) 4. **Hex overflow at `0x7FFFFFFFFFFFFFFF`.** `parseNumber` uses `strconv.ParseInt(s, 16, 64)` — values above signed 64-bit max parse silently as errors and `compareNumbers` returns `false`. Source: [`common/matcher.go:L399-438`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L399-L438) 5. **`MatchesSelector` negation guard is coarse.** Any `!` anywhere in the pattern — even `"!other-upstream"` — disables tag matching entirely. This is by design: a tag match must not rescue an excluded upstream. Source: [`common/matcher.go:L87-89`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L87-L89) 6. **Network-level vs upstream-level failsafe selection differ.** Upstream uses 4-tier priority; network uses config-order first-match. Same field names, different semantics. Source: [`upstream/upstream.go:L372-408`](https://github.com/erpc/erpc/blob/main/upstream/upstream.go#L372-L408), [`erpc/networks.go:L910-928`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L910-L928) 7. **`matchFinality: []` is catch-all at dispatch, but `MatchFinalities` empty-means-any is only for defaults merging.** Two different functions handle the same field in different contexts. Source: [`common/defaults.go:L17-34`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L17-L34), [`common/match.go:L38-41`](https://github.com/erpc/erpc/blob/main/common/match.go#L38-L41) 8. **Static response `method` is exact equality, not wildcard.** Unlike every other string matcher, `FindStaticResponseMatch` uses byte equality. A glob pattern in `staticResponses[].method` is silently treated as a literal string. Source: [`common/static_response.go:L16`](https://github.com/erpc/erpc/blob/main/common/static_response.go#L16) 9. **`CachePolicyConfig.finality` zero-value is `finalized`.** Omitting `finality` in a cache policy gives a finalized-only policy without any warning. Source: [`common/data.go:L15`](https://github.com/erpc/erpc/blob/main/common/data.go#L15) 10. **Multiple rate-limit rules fire simultaneously.** All rules whose `method` pattern matches are returned and each independently debits a token bucket. A single request may be throttled by a `"*"` rule and a specific-method rule at the same time. Source: [`upstream/ratelimiter_budget.go:L63-78`](https://github.com/erpc/erpc/blob/main/upstream/ratelimiter_budget.go#L63-L78) 11. **`ValidatePattern` is called only for `evm.servedTip.guaranteedMethods` at startup.** Other pattern fields (cache policy method, failsafe matchMethod, etc.) are not validated at config load — an invalid pattern silently skips matches at runtime. Source: [`common/validation.go:L1323-1326`](https://github.com/erpc/erpc/blob/main/common/validation.go#L1323-L1326) 12. **`WildcardMatch` concurrent calls are safe** (no shared mutable state per call) but `RateLimiterBudget.GetRulesByMethod` holds a `sync.RWMutex` during iteration. Re-tokenization cost is paid under the read lock on every matching call. Source: [`upstream/ratelimiter_budget.go:L63-78`](https://github.com/erpc/erpc/blob/main/upstream/ratelimiter_budget.go#L63-L78) 13. **`paramToString` on float64 from JSON.** JSON numbers decode as `float64`; `strconv.FormatFloat(v, 'f', -1, 64)` produces `"1234"` not `"1234.0"`. A string pattern `"123*"` would match numeric param `1234`. Source: [`data/cache_policy.go:L234-249`](https://github.com/erpc/erpc/blob/main/data/cache_policy.go#L234-L249) 14. **Cache `MatchesForGet` returns `true` for `unknown` finality across all policies.** A request with indeterminate finality can be served from a `finalized` policy's cache, potentially returning stale data. Source: [`data/cache_policy.go:L146-149`](https://github.com/erpc/erpc/blob/main/data/cache_policy.go#L146-L149) 15. **Legacy single-object failsafe migration is silent.** An old `failsafe: {…}` (object form) is quietly converted to a 1-element slice. No log or warning is emitted. The migration only forces `matchMethod` to `"*"` when the field is empty — an explicitly-set non-empty `matchMethod` value (e.g., `"eth_call"`) is preserved as-is. The migration applies at YAML parse time across `NetworkConfig`, `NetworkDefaults`, and `UpstreamConfig`. Source: [`common/config.go:L2041-2103`](https://github.com/erpc/erpc/blob/main/common/config.go#L2041-L2103) 16. **Zero-config auto-generated project only when `projects:` is entirely absent.** Even `projects: []` skips the auto-generated 'main' project defaults. Source: [`common/defaults.go:L100-167`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L100-L167) 17. **`DataFinalityStateAll = -1` is an internal sentinel.** It must never appear in config or on requests — it only lives in health-tracker cross-finality rollup keys. Source: [`common/data.go:L29-36`](https://github.com/erpc/erpc/blob/main/common/data.go#L29-L36) 18. **Empty pattern in `MatchesSelector` always returns `false`.** The function guards against empty patterns at entry and immediately returns `false`. Callers must ensure the `use-upstream` directive value is non-empty — an empty `X-ERPC-Use-Upstream` header or query param results in no upstream being selected by that directive. Source: [`common/matcher.go:L73`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L73) ### Observability | Metric | Type | Labels | When it fires | |---|---|---|---| | `erpc_unexpected_panic_total{scope="validate-pattern"}` | counter | scope, extra (pattern), error | `ValidatePattern` catches a tokenizer/parser panic | | `erpc_network_static_response_served_total` | counter | project, network, method | Every static-response cache hit in `tryServeStaticResponse` | Trace span attributes set during network-level failsafe selection: - `failsafe.matched_method` — the matched `matchMethod` value - `failsafe.matched_finalities` — the matched finality list Source: [`erpc/networks.go:L1179-1180`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L1179-L1180) Notable log lines: - `"method support result"` — Debug, `upstream/upstream.go`, fields: `allowed` (bool), `method` (string). Emitted after each upstream method-support check. - `"skipping static response: cannot inspect request"` — Debug, `erpc/networks_static_responses.go`. Emitted when params cannot be read for static-response matching. - `"served static response (no upstream contacted)"` — Debug, `erpc/networks_static_responses.go`. Emitted on a static-response cache hit. - `"error matching ignore method … with method …"` — Error, `auth/authorizer.go`. Emitted when `WildcardMatch` returns an error during auth method-filter evaluation. ### Source code entry points - [`common/matcher.go:L221-L272`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L221-L272) — `tokenize`: byte-by-byte tokenizer producing `tokenOr`, `tokenAnd`, `tokenNot`, `tokenLParen`, `tokenRParen`, `tokenPattern` - [`common/matcher.go:L73-L134`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L73-L134) — `MatchesSelector`, `UpstreamMatchesSelector`, `ValidatePattern` - [`common/match.go:L18-L78`](https://github.com/erpc/erpc/blob/main/common/match.go#L18-L78) — `SelectExecutor[E]`: generic 4-tier executor picker (single pass, four tier buckets) - [`data/cache_policy.go:L62-L249`](https://github.com/erpc/erpc/blob/main/data/cache_policy.go#L62-L249) — `MatchesForSet`, `MatchesForGet`, `matchParams`, `matchParam`, `paramToString` - [`common/static_response.go:L14-L39`](https://github.com/erpc/erpc/blob/main/common/static_response.go#L14-L39) — `FindStaticResponseMatch`: exact-equality path (distinct from `WildcardMatch`) - [`common/defaults.go:L17-L34`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L17-L34) — `MatchFinalities`: set-overlap utility used only in defaults merging - [`upstream/upstream.go:L372-L408`](https://github.com/erpc/erpc/blob/main/upstream/upstream.go#L372-L408) — upstream-level `getFailsafeExecutor`: manual 4-tier loop - [`erpc/networks.go:L910-L928`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L910-L928) — network-level `getFailsafeExecutor`: config-order first-match scan - [`auth/authorizer.go:L80-L115`](https://github.com/erpc/erpc/blob/main/auth/authorizer.go#L80-L115) — `shouldApplyToMethod`: ignore-then-allow semantics - [`consensus/quota.go:L96`](https://github.com/erpc/erpc/blob/main/consensus/quota.go#L96) — `WildcardMatch` on upstream tag strings for consensus quota matching - [`common/matcher_test.go`](https://github.com/erpc/erpc/blob/main/common/matcher_test.go) — comprehensive table-driven tests for `WildcardMatch`, `MatchesSelector`, `ValidatePattern` ### Related pages - [Failsafe](/config/failsafe/retry.llms.txt) — uses `matchMethod` + `matchFinality` for executor dispatch. - [Rate limiters](/config/rate-limiters.llms.txt) — uses `method` pattern; all matching rules fire simultaneously. - [Auth](/config/auth.llms.txt) — uses `ignoreMethods`/`allowMethods` with same ignore-then-allow semantics. - [Database / cache](/config/database.llms.txt) — uses `network`, `method`, and `params` patterns in policies. - [Projects](/config/projects.llms.txt) — project-level `ignoreMethods`/`allowMethods` and `forwardHeaders` patterns. --- ## Navigation (machine-readable surface) - Up: [All pages index](https://docs.erpc.cloud/llms.txt) - Root index of every page: [llms.txt](https://docs.erpc.cloud/llms.txt) · everything in one file: [llms-full.txt](https://docs.erpc.cloud/llms-full.txt) ### Sibling pages - [Authentication](https://docs.erpc.cloud/config/auth.llms.txt) — Lock down every request with a token, JWT, wallet signature, or IP allowlist — and bind each identity to its own rate-limit budget. - [Example config](https://docs.erpc.cloud/config/example.llms.txt) — A production-ready starting point you can copy today, plus a complete annotated reference of every config section — caching, failover, hedging, rate limits, and observability included. - [Failsafe](https://docs.erpc.cloud/config/failsafe.llms.txt) — Six composable failsafe policies that keep every RPC request succeeding — even when upstreams are slow, wrong, or temporarily down. - [Projects](https://docs.erpc.cloud/config/projects.llms.txt) — One eRPC, many tenants — each project gets its own networks, upstreams, auth, and budgets. - [Rate Limiters](https://docs.erpc.cloud/config/rate-limiters.llms.txt) — Stop a runaway caller or a misbehaving provider from affecting everyone else — eRPC applies independent request budgets at four layers and self-tunes outbound limits automatically. - [Server](https://docs.erpc.cloud/config/server.llms.txt) — eRPC's front door — dual-stack listeners, TLS/mTLS, a hard global timeout, gzip, drain-aware shutdown, and domain aliasing so any Host header routes to the right chain without touching a URL path.