Skip to content

Centurion Seat Manager

Validator seat lifecycle manager for the Centurion DepositContractCTN allowlist model.


What Is a Seat?

A seat is a tracked (pubkey, withdrawal_credentials) pair bound to an on-chain allowlist intent on the deposit contract.

Each seat has an intent hashkeccak256(pubkey || wc) — that the contract uses to allow or deny deposits.

The Seat Manager handles everything from creating seats to watching them go live on the beacon chain.


At a Glance

Capability What It Does
Create seats Store a (pubkey, wc) pair locally, compute the intent hash
Approve / Revoke Propose or send addAllowedDeposit / removeAllowedDeposit transactions
Deposit Simulate or send deposit() with full verification of on-chain events
Watch Monitor EL nodes for DepositEvent logs and CL nodes for validator visibility
Control Toggle the allowlist switch and manage the irreversible freeze (with strict safety gates)
Audit Write tamper-evident evidence bundles for every admin action

Operation Modes

No private key required. Every command generates calldata and writes an evidence bundle — but never sends a transaction.

This is the recommended mode for planning, review, and governance workflows.

node dist/index.js seat approve 1
# Output: calldata + evidence written, no tx sent

Requires SEAT_MANAGER_OWNER_PRIVATE_KEY env var. When you add the --send flag, the tool actually sends the transaction on-chain.

Evidence is always written first, then the tx is sent. Receipts are recorded in the database and evidence bundle.

export SEAT_MANAGER_OWNER_PRIVATE_KEY=0x...
node dist/index.js seat approve 1 --send
unset SEAT_MANAGER_OWNER_PRIVATE_KEY

Both modes require --send per command

Having the key set alone does not auto-send anything. You must explicitly pass --send on every command.


Quick Start

# 1. Build
npm install && npm run build

# 2. Health check (read-only, no key needed)
node dist/index.js status

# 3. Start watchers (Ctrl+C to stop)
node dist/index.js watch

CLI Commands

Command What It Does
status Run preflight checks, show allowlist state
seat create --operator <id> --pubkey <0x..> --wc <0x..> Store a new seat locally
seat approve <seatId> [--send] Propose or send addAllowedDeposit
seat deposit <seatId> --amount-ctn <N> [--simulate-only] [--send] Simulate or send a deposit
seat revoke <seatId> [--send] Propose or execute revocation
seat list [--status CREATED,ALLOWLISTED,...] List seats with optional status filter
switch on/off [--send] Toggle the allowlist
freeze schedule --delay-seconds <N> [--send] Schedule irreversible allowlist disable
freeze cancel [--send] Cancel a pending freeze schedule
freeze execute --dangerous [--send] Permanently disable the allowlist
watch Run EL + CL watchers continuously

Seat Lifecycle

stateDiagram-v2
    [*] --> CREATED : seat create
    CREATED --> ALLOWLISTED : seat approve --send
    CREATED --> REVOKED : seat revoke

    ALLOWLISTED --> DEPOSITED : EL watcher sees DepositEvent
    ALLOWLISTED --> REVOKED : seat revoke --send\n(removeAllowedDeposit)

    DEPOSITED --> SEEN_BY_CL : CL watcher — 1st label sees validator
    DEPOSITED --> REVOKED : seat revoke\n(operational only)

    SEEN_BY_CL --> ACTIVE : CL watcher — 2+ labels confirm
    SEEN_BY_CL --> REVOKED : seat revoke\n(operational only)

One-way progression

Seats only move forward (CREATED → ALLOWLISTED → DEPOSITED → SEEN_BY_CL → ACTIVE) or to REVOKED from any state. There is no backward transition.


Safety Gates

Gate What It Protects
Preflight checks Chain ID, contract code, runtime bytecode fingerprint, and owner are verified before any admin action
Cross-check (2+ endpoints) Dangerous operations require confirmation from at least 2 distinct EL clients
Freeze execute: 4 gates --dangerous flag + CONFIRM_FREEZE=I_UNDERSTAND env + cross-check + on-chain timelock maturity
Evidence bundles Written to ./evidence/ for every admin proposal or action — before any tx is sent
Labels-only output Endpoint URLs are never printed — all output uses labels like [aws-a-1]

Storage

SQLite database at ./data/seat-manager.sqlite (gitignored).

Table What It Stores
seats Lifecycle state per (pubkey, wc) pair
allowlist_actions On-chain tx records (add/remove)
deposits Observed DepositEvent logs
cl_observations Beacon API visibility checks
audit_log All actions with timestamps

Configuration

seat-manager.config.json points to the centurion-infra root:

{ "centurionInfraRoot": "/home/user/centurion/centurion-infra" }

Override with CENTURION_INFRA_ROOT env var. All canonical values (chainId, contract address, owner, endpoints) are read from infra files at runtime.

Full configuration details

See the Operator Guide for complete configuration reference including endpoint discovery, RPC modes, and all environment variables.


Tests

npm test        # 30 unit tests (intent hash, config parsing, validation)
npm run lint    # ESLint