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 maps to exactly one validator, one WithdrawalVault, and one beneficiary wallet.

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

The Seat Manager handles vault deployment, allowlist management, and lifecycle tracking — from seat creation through validator activation on the beacon chain.


At a Glance

Capability What It Does
BYO-BLS onboarding External operators bring their own BLS key — foundation deploys vault, operator submits deposit data
Generate keys (internal) Auto-generate BLS validator keypair for foundation-managed validators
Deploy vault + create seat Deploy a per-validator WithdrawalVault, derive withdrawal credentials, create the seat
Approve / Revoke Send addAllowedDeposit / removeAllowedDeposit transactions
Deposit Simulate or send deposit() with full verification of on-chain events
Force-exit (EIP-7002) Treasury can force-exit a validator without BLS key via the vault's triggerValidatorExit()
Watch Monitor EL nodes for DepositEvent logs and CL nodes for validator visibility
Control Toggle the allowlist switch and manage the irreversible freeze
Admin UI Next.js admin frontend for seat/operator/user management
API Fastify REST API with session auth, RBAC, idempotency, and audit logging
Audit Full audit trail with userId, requestId, ipAddress on every action


Architecture

centurion-seat-manager/           (root workspace — CLI + API)
├── apps/
│   └── admin/                    (Next.js 15 admin frontend)
├── packages/
│   └── shared/                   (@centurion/shared — shared TS types + constants)
├── prisma/                       (Prisma schema + migrations)
├── src/                          (Fastify API + CLI source)
├── test/                         (vitest integration tests)
└── scripts/                      (env-check, live test scripts)

Operation Modes

The primary operational mode. The Fastify API serves both the admin frontend and programmatic access.

# Start API backend
DATABASE_URL="postgresql://..." node dist/index.js api-v2 --port 8080

# Start admin frontend (separate terminal)
cd apps/admin && npm run dev

CLI commands for scripting, automation, and direct operations.

node dist/index.js seat approve 1 --send

Without SEAT_MANAGER_OWNER_PRIVATE_KEY, admin commands generate calldata and evidence bundles but never send transactions.


Quick Start

# 1. Install dependencies
npm install

# 2. Set up PostgreSQL
export DATABASE_URL="postgresql://user:password@localhost:5432/seat_manager?schema=public"
npx prisma migrate dev

# 3. Build
npm run build

# 4. Health check (read-only)
node dist/index.js status

# 5. Start API + watchers (two terminals)
node dist/index.js api-v2 --port 8080
node dist/index.js watch

# 6. Start admin frontend (third terminal)
cd apps/admin && npm run dev

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 create-with-vault --operator <id> --pubkey <0x..> --beneficiary <0x..> [--treasury <0x..>] [--principal-ctn 32] Deploy per-validator vault, derive wc, store seat
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 force-exit <seatId> [--send] Trigger EIP-7002 validator exit via vault (no BLS key needed)
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
api-v2 [--port 8080] [--host 127.0.0.1] Start Fastify API server

API Endpoints

Public (no auth)

Method Route Description
GET /health Health check
GET /v1/validators/:address Validators by wallet address
GET /v1/validator/:pubkey Single validator by pubkey

Authenticated (session or admin token)

Method Route Role Required Description
POST /v1/auth/login Login, returns session token
POST /v1/auth/logout Any Revoke session
GET /v1/seats VIEWER+ List all seats
POST /v1/seats OPERATOR+ Create seat (manual)
POST /v1/seats/with-vault ADMIN Deploy vault + create seat (BYO-BLS)
POST /v1/seats/generate ADMIN Generate keys + deploy vault (internal only)
POST /v1/seats/:id/deposit-data OPERATOR+ Submit operator deposit data (BYO-BLS)
POST /v1/seats/:id/approve ADMIN Allowlist on-chain
POST /v1/seats/:id/deposit ADMIN Send 32 CTN deposit
POST /v1/seats/:id/revoke ADMIN Revoke seat
POST /v1/seats/:id/force-exit ADMIN EIP-7002 force-exit (no BLS key needed)
POST /v1/seats/:id/settle ADMIN Start exit settlement on vault
GET/POST /v1/operators VIEWER+ / OPERATOR+ Operator CRUD
GET/POST /v1/users ADMIN User management
GET /v1/audit-logs VIEWER+ Audit log query

Seat Lifecycle

stateDiagram-v2
    [*] --> CREATED : seat create / generate
    CREATED --> ALLOWLISTED : approve (addAllowedDeposit)
    CREATED --> REVOKED : revoke

    ALLOWLISTED --> DEPOSITED : deposit 32 CTN
    ALLOWLISTED --> REVOKED : revoke (removeAllowedDeposit)

    DEPOSITED --> SEEN_BY_CL : CL watcher sees validator
    DEPOSITED --> REVOKED : revoke (operational)

    SEEN_BY_CL --> ACTIVE : CL watcher confirms activation
    SEEN_BY_CL --> REVOKED : revoke (operational)

    ACTIVE --> REVOKED : revoke (operational)

One-way progression

Seats only move forward or to REVOKED from any state. There is no backward transition. State transitions use optimistic concurrency (version column) to prevent races.


Storage

PostgreSQL 18 via Prisma ORM. DATABASE_URL environment variable required.

Table What It Stores
users Admin users with roles (ADMIN, OPERATOR, VIEWER) and scrypt password hashes
sessions Session tokens (HMAC-SHA256 hashed), expiry, status
operators Operator entities (name, description, enabled)
seats Lifecycle state per (pubkey, wc) pair, vault metadata
seat_events Append-only lifecycle transition history
seat_bls_material BLS keystores and deposit data (1:1 with seat)
seat_operations Multi-step workflow journal (keygen → vault → seat)
allowlist_actions On-chain tx records (add/remove)
deposits Observed DepositEvent logs
cl_observations Beacon API visibility checks
audit_log All actions with userId, requestId, ipAddress
idempotency_keys Request deduplication for POST routes

Configuration

seat-manager.config.json in the repo root:

{
  "centurionInfraRoot": "/path/to/centurion-infra",
  "centurionNetworksRoot": "/path/to/centurion-networks",
  "endpoints": [
    {
      "label": "node-1",
      "host": "<node-ip>",
      "rpcPort": 8545,
      "elClient": "geth",
      "clClient": "lighthouse",
      "sshUser": "<ssh-user>"
    }
  ]
}

Environment Variables

Variable Required Purpose
DATABASE_URL Yes PostgreSQL connection string
ADMIN_TOKEN For API Static admin token (legacy auth, alongside session auth)
SEAT_MANAGER_OWNER_PRIVATE_KEY For --send Contract owner private key (or use KMS)
SEAT_MANAGER_KMS_KEY_ID For --send AWS KMS key ID/alias — alternative to raw private key
SEAT_MANAGER_INTENTS_MNEMONIC For key generation BLS mnemonic (never passed as CLI arg)
SEAT_MANAGER_INTENTS_KEYSTORE_PASSWORD For key generation Keystore encryption password (never passed as CLI arg)
SEAT_MANAGER_VAULT_DEPLOYER_PRIVATE_KEY For vault deploy Falls back to owner key
SEAT_MANAGER_DEPOSITOR_PRIVATE_KEY For deposits Falls back to owner key
SESSION_HMAC_SECRET For API Session token HMAC key (auto-generated if unset)
OTEL_EXPORTER_OTLP_ENDPOINT Optional OpenTelemetry collector endpoint

Tests

npm run ci          # typecheck + lint + format check + test
npm test            # 285 tests across 13 suites
Suite Tests Coverage
prismaStore.test.ts 68 Operators, seats, state machine, concurrency, users, sessions, auth, idempotency, audit
security.test.ts 34 RBAC, timing attacks, key material, CSP, error handling
schemas.test.ts 30 Zod schema validation
intent.test.ts 16 Intent hash computation
validator_payload.test.ts 25 Validator API response mapping
gate_status.test.ts 10 Contract gate status logic
validation.test.ts 10 Input validation
deposit_policy.test.ts 3 Deposit policy enforcement
vault.test.ts 3 Vault credential derivation
adversarial.test.ts 53 Money-risk paths, auto-settlement, watcher heartbeat
byoBls.test.ts 30 BYO-BLS deposit data validation, force-exit, RBAC
vault_contract_compile.test.ts 5 WithdrawalVault ABI + constructor verification

Staker Console Integration

For the full user-facing read layer, claim rewards flow, and wallet-scoped validator visibility, see: Staker Console Integration