---
name: mev-commit-opt-in
description: >
  Opt Ethereum L1 validator pubkeys into or out of mev-commit (vanilla, EigenLayer, Symbiotic).
  Verify Hub status, send and confirm registry transactions on the operator's RPC and wallet,
  and compute real on-chain ETH stake. Use when the user mentions validator opt-in, opt out,
  VanillaRegistry, MevCommitAVS, MevCommitMiddleware, proposer registry, BLS pubkey registration,
  or /mev-commit-opt-in.
---

# mev-commit-opt-in

Opt L1 validator BLS pubkeys into mev-commit by calling the published registry contracts on Ethereum. Replicate the old dashboard wizard without the relay checkbox. Any mev-boost relay set is fine.

Canonical page: https://primev.xyz/ai
Docs: https://docs.primev.xyz/v1.2.x/get-started/validators/agentic-opt-in
Addresses, signatures, errors, and stake math live in `references/`. Do not invent other contracts.

## Safety

- Never print, log, commit, or write a private key. Prefer Foundry keystore, `cast wallet`, or hardware. Use `PRIVATE_KEY` only if the operator insists.
- Only register pubkeys the operator controls. Registering someone else's key can be slashed.
- One pubkey, one method. Do not double-register.
- Simulate every write, then send on **their** `ETH_RPC_URL` (L1, chain id 1 or Hoodi 560048). Wait for the receipt. Then verify on the Hub.
- Batch 40 to 60 keys per transaction.

## Keys

Accept `.txt`, `.csv`, paste, newlines, or commas. Normalize each entry to `0x` + 96 hex (48-byte compressed BLS). Drop empties. Reject anything else.

```text
0xa1b2...   # 98 chars including 0x
```

## Walk

1. Choose method: **vanilla** (simple ETH), **eigenlayer**, or **symbiotic**. Hoodi-only extras: Lido, Rocket Pool (link the docs path pages; do not improvise).
2. Parse keys. Confirm count and first/last pubkey with the operator.
3. Confirm signer + `ETH_RPC_URL`.
4. Simulate, send, wait for receipt, then Hub-verify every key.
5. Report: method, key count, tx hash, Etherscan/Hoodi link, Hub booleans, next step.

No relay configuration step.

## Writes

Read `references/methods.md` for exact signatures and `cast` shapes.

- Vanilla: `stake(bytes[])` payable. Read `minStake()` first. `msg.value` is split evenly. Extra ETH is a slash buffer, not prepaid capacity.
- EigenLayer: `registerValidatorsByPodOwners(bytes[][], address[])`. Caller must be the pod owner or the delegated AVS operator. Plural name only.
- Symbiotic: `registerValidators(bytes[][], address[])`. Vault and operator must already be registered. If setup is missing, stop and name the exact gap (see errors).

## Opt out

- Vanilla: `unstake(bytes[])`, wait `unstakePeriodBlocks()`, then `withdraw(bytes[])`.
- EigenLayer: `requestValidatorsDeregistration(bytes[])`, wait, then `deregisterValidators(bytes[])`.
- Symbiotic: `requestValDeregistrations(bytes[])`, wait, then `deregisterValidators(bytes[])`.

## Verify

Always use **ValidatorOptInHub** (not the legacy Router) on L1:

```
isValidatorOptedIn(bytes)(bool)
areValidatorsOptedIn(bytes[])(bool[])
```

A receipt is not enough. Hub `true` is opted in.

## Status (no performance analytics)

- How many of my keys are opted in: Hub `areValidatorsOptedIn` on the set.
- Operator / withdrawal / pod-owner: signer address, plus AVS `getOperatorRegInfo` / middleware operator views.
- Total ETH stake: follow `references/stake.md`. Sum real balances. **Never** `32 ETH × key count`. If beacon effective balance is needed, use a beacon node or beaconcha.in (`BEACONCHAIN_API_KEY`). If the key is missing, ask for it. Do not invent a placeholder.

Do not query primev-metrics-cron, relaydb, or “how are my keys performing?”

## Errors

On revert, read `references/errors.md`. Tell the operator the contract, function, revert name, the address or pubkey that failed, and the exact missing step (including when they must ping Primev, e.g. vault not registered, vanilla whitelist).

## Addresses
# Registry addresses

Call **proxies**. L1 only. Not the mev-commit chain.

## Mainnet (chain id 1)

| Contract | Proxy |
| --- | --- |
| VanillaRegistry | `0x47afdcB2B089C16CEe354811EA1Bbe0DB7c335E9` |
| MevCommitAVS | `0xBc77233855e3274E1903771675Eb71E602D9DC2e` |
| MevCommitMiddleware | `0x21fD239311B050bbeE7F32850d99ADc224761382` |
| ValidatorOptInHub | `0x1059155bD5854224bF58e43CD3EEC6B07b4F30Ad` |
| ValidatorOptInRouter (legacy, three paths only) | `0x821798d7b9d57dF7Ed7616ef9111A616aB19ed64` |
| Symbiotic network | `0x9101eda106A443A0fA82375936D0D1680D5a64F5` |

## Hoodi (chain id 560048)

| Contract | Address |
| --- | --- |
| VanillaRegistry | `0x536f0792c5d5ed592e67a9260606c85f59c312f0` |
| MevCommitAVS | `0xdF8649d298ad05f019eE4AdBD6210867B8AB225F` |
| MevCommitMiddleware | `0x8E847EC4a36c8332652aB3b2B7D5c54dE29c7fde` |
| ValidatorOptInHub | `0x953c2a669493A126fd50E9f56306f254B4e49709` |
| ValidatorOptInRouter | `0xa380ba6d6083a4Cb2a3B62b0a81Ea8727861c13e` |
| Lido registry | `0xefd6333907fc73c1ac3167d843488b8899bac91b` |
| RocketMinipoolRegistry | `0xbe5a803a7b68f442eff1953c672a3499779680b0` |
| Symbiotic network / vault | `0x1623fE21185c92BB43bD83741E226288B516134a` / `0x6b23e5a4f3eb2ffc408346df0a41e9de83b4da8a` |

Source: https://docs.primev.xyz/v1.2.x/developers/networks/mainnet and testnet snippets.

## Methods
# Contract calls

`$RPC` is the operator's L1 RPC. `$KEY` is keystore / wallet flags, never a pasted key in the prompt.

BLS keys are `bytes`: `0x` + 96 hex.

## Vanilla

```bash
cast call $VANILLA "minStake()(uint256)" --rpc-url $RPC
cast send $VANILLA "stake(bytes[])" "[$KEY1,$KEY2]" --value $WEI --rpc-url $RPC
cast send $VANILLA "unstake(bytes[])" "[$KEY1]" --rpc-url $RPC
cast call $VANILLA "unstakePeriodBlocks()(uint256)" --rpc-url $RPC
cast send $VANILLA "withdraw(bytes[])" "[$KEY1]" --rpc-url $RPC
cast call $VANILLA "getStakedAmount(bytes)(uint256)" $KEY1 --rpc-url $RPC
cast call $VANILLA "isValidatorOptedIn(bytes)(bool)" $KEY1 --rpc-url $RPC
```

`msg.value` is split evenly across keys in that tx. Floor is `minStake * n`. Documented `minStake` is 0.0001 ETH; always read it on-chain. Hoodi `stake` is `onlyWhitelistedStaker` (mainnet whitelist coming).

## EigenLayer

```bash
cast send $AVS "registerValidatorsByPodOwners(bytes[][],address[])" "[[$KEY1,$KEY2]]" "[$POD_OWNER]" --rpc-url $RPC
cast send $AVS "requestValidatorsDeregistration(bytes[])" "[$KEY1]" --rpc-url $RPC
cast send $AVS "deregisterValidators(bytes[])" "[$KEY1]" --rpc-url $RPC
cast call $AVS "isValidatorOptedIn(bytes)(bool)" $KEY1 --rpc-url $RPC
cast call $AVS "getOperatorRegInfo(address)(...)" $OPERATOR --rpc-url $RPC
```

Do not use the obsolete singular `registerValidatorsByPodOwner`. Operator registration is `primev/eigen-operator-cli`, not this skill.

## Symbiotic

```bash
cast send $MIDDLEWARE "registerValidators(bytes[][],address[])" "[[$KEY1,$KEY2]]" "[$VAULT]" --rpc-url $RPC
cast send $MIDDLEWARE "requestValDeregistrations(bytes[])" "[$KEY1]" --rpc-url $RPC
cast send $MIDDLEWARE "deregisterValidators(bytes[])" "[$KEY1]" --rpc-url $RPC
cast call $MIDDLEWARE "isValidatorOptedIn(bytes)(bool)" $KEY1 --rpc-url $RPC
cast call $MIDDLEWARE "isValidatorSlashable(bytes)(bool)" $KEY1 --rpc-url $RPC
cast call $MIDDLEWARE "getNumSlashableVals(address,address)(uint256)" $VAULT $OPERATOR --rpc-url $RPC
```

Vault / operator / network setup: docs Symbiotic page and `ExampleSetup.s.sol`. This skill only registers keys after that setup exists.

## Hub (preferred verify)

```bash
cast call $HUB "isValidatorOptedIn(bytes)(bool)" $KEY1 --rpc-url $RPC
cast call $HUB "areValidatorsOptedIn(bytes[])(bool[])" "[$KEY1,$KEY2]" --rpc-url $RPC
```

## Simulate then send

```bash
cast call $CONTRACT "$SIG" $ARGS --from $SENDER --rpc-url $RPC
cast send $CONTRACT "$SIG" $ARGS --rpc-url $RPC
cast receipt $TX --rpc-url $RPC
```

## Errors
# Reverts

Always report: contract, function, revert name, the address or pubkey that failed, and the missing step.

| Revert | Meaning | Tell the operator |
| --- | --- | --- |
| `VaultNotRegistered` | Vault is not on MevCommitMiddleware | Primev must `registerVaults` first. Give them the vault address. |
| `OperatorNotEntity` / `OperatorNotRegistered` | Signer is not a registered operator | Run the Symbiotic / AVS setup in docs, or ask Primev to `registerOperators` for this address. |
| `ValidatorsNotSlashable` | Not enough slashable collateral for this many keys | Increase vault allocation or register fewer keys. `getNumSlashableVals(vault, operator)` is the cap. |
| `SenderIsNotWhitelistedStaker` | Vanilla whitelist | Ask Primev to whitelist this operator address. Hoodi is live; mainnet whitelist is coming. |
| `SenderNotPodOwnerOrOperator` | Signer is neither pod owner nor delegated AVS operator | Switch to the pod-owner or operator key. |
| `ValidatorNotActiveWithEigenCore` | Pubkey is not an active EigenPod validator | Confirm the key is natively restaked and active on EigenLayer. |
| `NoPodExists` | No pods for this account | The signer has no EigenPod / delegation to use. |
| `ValidatorRecordMustNotExist` | Key already registered | Skip already-opted-in keys. Verify on the Hub first. |

If the revert is unknown, print the raw error data and still include contract + function + pubkey.

## Stake
# On-chain ETH stake

Never report `32 ETH × opted-in key count`. Sum real balances.

## Beacon effective balance (required for restaked / beacon-backed keys)

Each opted-in BLS key has an effective balance on the beacon chain. Resolve it:

1. Operator's beacon RPC: `/eth/v1/beacon/states/head/validators/0xPUBKEY` → `data.validator.effective_balance` (gwei).
2. Or beaconcha.in: `GET https://beaconcha.in/api/v1/validator/{pubkey}` with `BEACONCHAIN_API_KEY` if required.

If neither is available, ask for a beacon RPC or a beaconcha.in API key. Do not substitute 32 ETH.

## Vanilla extra stake

```bash
cast call $VANILLA "getStakedAmount(bytes)(uint256)" $KEY --rpc-url $RPC
```

This is ETH locked in VanillaRegistry, separate from beacon stake. Add it. Do not treat it as the validator's 32 ETH.

## EigenLayer restake

Read actual shares / underlying from EigenLayer core for the pod owner or operator (`StrategyManager`, `DelegationManager`, EigenPod). Do not assume 32 ETH per key. Beacon effective balance still applies to each pubkey.

## Symbiotic

Report slashable / allocated collateral from middleware + vault views (`getNumSlashableVals`, vault stake toward the operator). Token may be ETH or an ERC20. Name the asset. Do not convert unknown tokens into “32 ETH”.

## Output

For a set of keys, print a table: pubkey, Hub opted-in, beacon effective ETH, vanilla extra ETH, restake/collateral notes, row total. Then a sum. If a row cannot be resolved, say why and omit it from the sum rather than filling 32.
