Imported from ethereum/forkcast (
.agents/skills/infer-eip-layer/SKILL.md). Install upstream withnpx skills add ethereum/forkcast --skill infer-eip-layer. Copyright stays with the author.
Infer EIP layer (EL / CL)
Set the layer field ("EL" or "CL") on EIPs that are missing it. layer records which client primarily implements the change: EL = execution-layer clients (geth, reth, besu, nethermind, erigon); CL = consensus-layer clients (prysm, lighthouse, teku, nimbus, lodestar, grandine).
There is no authoritative layer field in upstream EIP frontmatter (both EL and CL Core EIPs are category: "Core"), so this is inferred from the EIP's substance.
Do NOT use the call venue as a signal. An EIP being staged on ACDE vs ACDC only reflects which working group shepherded the discussion, not which client implements it. Cross-layer EIPs are routinely discussed on either call (e.g. EIP-8282 "Builder Execution Requests" is CL but was PFI'd on an ACDE call). Ignore the
callprefix entirely.
Step 1: Identify the target set
Find active fork EIPs missing a layer. The audit already reports these:
npm run audit-eips -- --fork Hegota
Look for - missing layer (EL or CL) under the fork. Alternatively, list them directly (source files are the source of truth):
node scripts/audit-eips.mjs --fork Hegota # same thing
Confirm the list with the user (or take the ids they gave you). Only touch EIPs that are missing layer — never overwrite an existing one as part of a bulk pass.
The exception is a reported mislabel: if the user points at a specific EIP as wrongly classified, run Steps 3–4 on it and correct it. Existing labels are anchors for Signal A, so a wrong one propagates — fixing it is worth more than the one row.
Step 2: Build the labeled reference map (for Signal A)
Load every EIP that already has a layer set into an id → layer map. These are your ground-truth anchors for dependency propagation:
node -e 'const e=require("./src/data/eips.json"); const m={}; for(const x of e){ if(x.layer) m[x.id]=x.layer } process.stdout.write(JSON.stringify(m))'
Step 3: Signal A — requires dependency propagation (deterministic)
For each target EIP, look at its requires array and map each dependency to its layer via the map from Step 2.
- All resolvable dependencies are EL → Signal A =
EL - All resolvable dependencies are CL → Signal A =
CL - Mixed, or no dependency has a known layer → Signal A =
inconclusive
Notes:
- Only count dependencies whose layer is known. Don't recursively guess through unlabeled EIPs.
- Some dependencies are cross-layer bridges and weaken the signal — treat these as not decisive on their own:
4788(beacon root in EVM),6110(validator deposits),4844/7594(blobs). If the only signal is one of these, mark Signal Ainconclusive.
Step 4: Signal B — semantic read of the EIP substance
Read the EIP's title, description, and (if present) laymanDescription/benefits/stakeholderImpacts in src/data/eips/{id}.json. If those are thin, fetch the abstract from the source:
gh api '/repos/ethereum/EIPs/contents/EIPS/eip-{id}.md' --jq '.content' | base64 -d
Read past the Abstract — the Specification section is what decides the layer (see the caveat below). For an EIP still in an unmerged PR, the file isn't on master yet; get it from the PR instead:
gh api /repos/ethereum/EIPs/pulls/{pr}/files --jq '.[0].patch'
Classify by what state/structures the change modifies, not by keywords alone:
| Signal B = EL | Signal B = CL |
|---|---|
| EVM opcodes, precompiles, bytecode, stack/memory | validators, attestations, committees |
| gas schedule, calldata, access lists, tx types | beacon state, eth1data, deposits, withdrawals |
| account/storage state, state trie, nonces | slots, epochs, RANDAO, slashing, fork choice |
ecrecover, EOA delegation, CREATE/CALL |
sync committee, proposer duties, finality |
execution-specs (.py under eth1/EELS) |
consensus-specs, presets/configs, gossip topics |
Watch for execution payload framing: the payload container is defined in the CL, but changes to payload contents/metering (BAL, gas, tx floor) are EL. A "Partial Execution Payload Commitment" (block-production/ePBS plumbing) is CL; a "Block Access List Byte Floor" (gas metering) is EL.
Motivation is not the implementation layer. Read the Specification, not the Abstract/Motivation. An EIP is routinely motivated by CL problems (ePBS builder timing, attestation deadlines, validator revenue) while only modifying EL structures — and vice versa. Abstracts written for a CL audience are full of "validators", "attest", and "slot", which will drag a keyword-driven read the wrong way.
- EIP-7862 (Delayed State Root) was mislabeled CL: its motivation is entirely ePBS/builder timing and its abstract says "validators can attest without waiting for state root computation" — but the spec only changes the EL header's
state_rootsemantics and EELSstate_transition. Its own Rationale states "The CL state root verification is unaffected." → EL. - Contrast EIP-8375 (eMBER), which really is cross-layer: its spec says outright "This specification changes both the execution layer and the consensus-layer ePBS mechanism."
When an EIP explicitly says it changes both layers, don't force a single decisive read — set Signal B to unclear and let Signal A (or the user) break the tie.
The most reliable tell is which spec repo the pseudocode belongs to: Header/BlockChain/state_transition/apply_body/vm.* are execution-specs (EL); BeaconState/process_*/get_* over epochs and committees are consensus-specs (CL). Prefer this over any prose signal.
Output Signal B as EL, CL, or unclear, plus a one-line justification.
Step 5: Combine and decide
| Signal A | Signal B | Action |
|---|---|---|
| EL/CL, and agrees with B | same | Auto-apply (high confidence) |
| one is EL/CL, other inconclusive/unclear | — | Apply the decisive one, but list it in the review queue (medium confidence) |
| A and B disagree | conflict | Do not apply. Flag for human review with both rationales. |
| both inconclusive/unclear | — | Do not apply. Flag for human review. |
Never guess when signals conflict or are both weak — a wrong layer is worse than a missing one.
Step 6: Apply
For each auto-apply (and any medium-confidence ones the user approves), set layer in src/data/eips/{id}.json. Placement convention: put layer immediately after reviewer if present, otherwise immediately before forkRelationships. Only add the field; don't reorder or reformat anything else.
Step 7: Validate
npm run compile-eips
npm run lint
Fix any errors before continuing.
Step 8: Summary + PR
Show a table: EIP | Signal A | Signal B | decision | confidence. Call out every EIP left in the review queue and ask the user to resolve those before committing.
For the PR (only if the user wants one):
- Branch:
infer-layer-{fork}(e.g.infer-layer-hegota). - Commit:
data: set layer on {fork} EIPs. - PR body: list each EIP with its assigned layer and the deciding signal; note any the user resolved manually.
Step 9: Retrospective
Note any friction — dependencies that should be added to the cross-layer bridge list, EIPs where the semantic read was ambiguous, mislabeled anchors in the reference map — and offer to patch this skill.