How It Works¶
What this document covers
The actual pipeline implemented in this repo today — from both the operator/foundation perspective and the granted seat user perspective. Everything here reflects current code paths in src/ and the current live operating model.
0. Prerequisites — What Must Be True First¶
Seat Manager works correctly only when all of these conditions are met.
Repository & Network¶
| Condition | Requirement |
|---|---|
| Repo layout | centurion-infra and centurion-networks exist side-by-side under /home/user/centurion/ |
| Canonical files | deploy-node.sh, network.yaml, and deposit_contract_fingerprint.json must be readable |
| Network connectivity | At least 2 endpoint labels must be reachable for cross-check preflight |
| RPC access | Direct or SSH tunnel, depending on SEAT_MANAGER_RPC_MODE |
Chain & Contract State¶
| Condition | Requirement |
|---|---|
| Chain ID | Must match canonical chain_id |
| Contract code | Must exist at canonical address |
| Runtime hash | Must match canonical fingerprint |
On-chain owner() |
Must match canonical deposit_contract_owner |
Key Material & Funding¶
| Operation | Key Required | Funding Required |
|---|---|---|
| Propose-only commands | None | None |
Owner admin send (approve, switch, freeze) |
SEAT_MANAGER_OWNER_PRIVATE_KEY |
Gas |
Deposit send (seat deposit --send) |
SEAT_MANAGER_DEPOSITOR_PRIVATE_KEY (or owner fallback) |
Gas + deposit value |
Contract State for Deposits¶
| Condition | Requirement |
|---|---|
| Seat status | Must not be REVOKED |
| If allowlist is ON | (pubkey, wc) intent must already be allowlisted |
| If owner-only depositor is ON | Deposit sender must be owner |
| Amount | Must map to integer gwei and satisfy contract bounds |
| Deposit data root | Must match (pubkey, wc, signature, amount) used in tx |
Data Requirements¶
| Condition | Requirement |
|---|---|
| Pubkey | 48 bytes |
| Withdrawal credentials | 32 bytes |
Watcher promotion to ACTIVE |
Validator must be visible on at least 2 CL labels |
Deposit Contract Feature Additions¶
These are the 4 key additions versus the baseline deposit contract, and how they must be set for Seat Manager operations:
| # | Feature | Current Stage (Permissioned) | Later Stage (Permissionless) |
|---|---|---|---|
| 1 | Intent allowlist gate (pubkey, wc) |
ENABLED (pubkeyAllowlistEnabled = true) |
DISABLED forever (pubkeyAllowlistDisabledForever = true) |
| 2 | Reversible allowlist switch | AVAILABLE (must not be disabled forever) | NOT AVAILABLE after permanent disable |
| 3 | Timelocked disable-forever path | NOT EXECUTED in normal runs | EXECUTED once, after timelock maturity |
| 4 | Owner-only depositor gate | ENABLED by policy (ownerOnlyDepositorEnabled = true) |
DISABLED (ownerOnlyDepositorEnabled = false) |
Current model requirements
For Seat Manager to work in the current permissioned model:
pubkeyAllowlistEnabledmust betruepubkeyAllowlistDisabledForevermust befalsedisableAllowlistNotBeforeshould be0(unless intentionally running a timelock exercise)- If
ownerOnlyDepositorEnabledistrue, the deposit signer must be the owner - If
ownerOnlyDepositorEnabledisfalse, a non-owner depositor key can be used
1. Roles and Responsibilities¶
| Role | Responsibilities |
|---|---|
| Foundation / Operator (Seat Manager owner) | Maintains Seat Manager DB, runs CLI/watchers, controls allowlist admin txs, sends deposit txs |
| Granted Seat User (candidate operator) | Provides pubkey + wc intent to foundation, runs validator infrastructure, does not submit deposits in current flow |
| DepositContractCTN | Enforces on-chain rules: allowlist gate, owner-only sender gate, deposit root/value checks |
| EL/CL Clients | EL includes tx + event in chain; CL ingests deposit and exposes validator state |
2. Canonical Inputs¶
Seat Manager reads canonical chain data from three sources:
| Source | What It Provides |
|---|---|
centurion-infra/.../deploy-node.sh |
Endpoint labels + client mapping |
centurion-networks/.../network.yaml |
Chain/contract/ports configuration |
centurion-networks/.../deposit_contract_fingerprint.json |
Runtime hash + root expectations |
Networks root resolution order
centurionNetworksRootinseat-manager.config.json(optional)CENTURION_NETWORKS_ROOTenv var (optional)- Sibling default:
/home/user/centurion/centurion-networks - Legacy fallback:
<infraRoot>/networks(compatibility)
3. End-to-End Operator Pipeline¶
graph TD
A["<b>1. seat create</b><br/>Validate pubkey (48B) + wc (32B)<br/>Compute intentHash<br/>Store as CREATED"] --> B
B["<b>2. seat approve</b><br/>Cross-check preflight on 2+ labels<br/>Produce evidence bundle<br/>With --send: addAllowedDeposit<br/>Status → ALLOWLISTED"] --> C
C["<b>3. seat deposit</b><br/>Parse amount, resolve signer<br/>Generate deposit data<br/>Simulate via eth_call<br/>With --send: send + verify event"] --> D
D["<b>4. Watchers</b><br/>EL: DepositEvent → DEPOSITED<br/>CL: 1st label → SEEN_BY_CL<br/>CL: 2+ labels → ACTIVE"]
4. State Machine¶
stateDiagram-v2
[*] --> CREATED : seat create
CREATED --> ALLOWLISTED : seat approve --send
CREATED --> REVOKED : operational revoke
ALLOWLISTED --> DEPOSITED : seat deposit --send\n(successful tx + event)
ALLOWLISTED --> REVOKED : removeAllowedDeposit
DEPOSITED --> SEEN_BY_CL : CL visible on 1 label
DEPOSITED --> REVOKED : operational only
SEEN_BY_CL --> ACTIVE : CL visible on 2+ labels
SEEN_BY_CL --> REVOKED : operational only
Post-deposit revoke
DEPOSITED, SEEN_BY_CL, and ACTIVE seats can only be revoked operationally — there is no on-chain mechanism to eject a validator via the deposit contract.
5. Deposit Send Path¶
What is actually checked when you run seat deposit:
| Step | Check |
|---|---|
| 1 | Seat exists and status is not REVOKED |
| 2 | Parse amount → wei → gwei (must be integer gwei) |
| 3 | Cross-check preflight on 2 labels |
| 4 | Resolve deposit signer (DEPOSITOR_KEY or OWNER_KEY fallback) |
| 5 | Resolve deposit data for seat + amount (deposit-cli generation + local cache) |
| 6 | Simulate with eth_call |
| 7 | If --simulate-only or no --send: stop here |
| 8 | Send tx and assert: receipt success, exactly 1 DepositEvent, field match, depositCount +1 |
Simulation flags
--simulate-only: Always stop after simulation--expect-revert: Pass only if simulation reverts (useful for verifying blocked paths)
6. Operator Controls¶
Allowlist Switch¶
| Command | What It Does |
|---|---|
switch on --send |
setPubkeyAllowlistEnabled(true) |
switch off --send |
setPubkeyAllowlistEnabled(false) |
Both require cross-check preflight on 2+ labels and refuse if disabledForever = true.
Freeze Pipeline¶
| Command | What It Does |
|---|---|
freeze schedule --delay-seconds N |
Sets notBefore = block.timestamp + N |
freeze cancel |
Clears pending schedule (notBefore = 0) |
freeze execute --dangerous |
Irreversible — calls disablePubkeyAllowlistForever() |
Freeze execute gates
Requires: --dangerous flag + CONFIRM_FREEZE=I_UNDERSTAND + matured timelock + cross-check preflight.
7. Owner-Only Depositor Mode¶
Current state
Seat Manager does not expose a CLI command to toggle ownerOnlyDepositorEnabled. This is enforced by the contract itself during deposit().
Effect on Seat Manager deposits:
- If owner-only is ON and signer is not owner → revert (
UnauthorizedDepositor) - If owner-only is ON and signer is owner → proceeds to normal checks
Truth Table (Contract Gate Logic)¶
| Allowlist (A) | Owner-Only (O) | Intent Listed (I) | Sender = Owner (S) | Result |
|---|---|---|---|---|
| ON | ON | Yes | Yes | PASS |
| ON | ON | Yes | No | REVERT UnauthorizedDepositor |
| ON | ON | No | Yes | REVERT IntentNotAllowlisted |
| ON | OFF | Yes | Any | PASS |
| ON | OFF | No | Any | REVERT IntentNotAllowlisted |
| OFF | ON | Any | Yes | PASS |
| OFF | ON | Any | No | REVERT UnauthorizedDepositor |
| OFF | OFF | Any | Any | PASS |
Don't-care fields
When a gate is OFF, its corresponding input doesn't matter — but other checks (value, root, length) can still revert.
8. Granted Seat User View¶
From the user's perspective, the flow is:
| Step | User Action | What Happens |
|---|---|---|
| 1 | Submit pubkey + wc intent to foundation |
Foundation creates the seat |
| 2 | Wait for approval | Foundation sends addAllowedDeposit on-chain |
| 3 | Wait for deposit | Foundation sends the deposit tx |
| 4 | Run validator and monitor | CL visibility → SEEN_BY_CL → ACTIVE |
Current permissioned flow
The granted user is not the transaction sender for deposit. The user cannot bypass contract gates with local actions.
9. Evidence and Audit Artifacts¶
| Artifact | Location | Contents |
|---|---|---|
| Evidence bundles | ./evidence/*.json |
Canonical snapshot, preflight summaries, calldata, tx hash/status/block |
| SQLite database | ./data/seat-manager.sqlite |
Seat lifecycle, deposit events, CL observations, audit log |
10. Watchers Pipeline¶
graph LR
subgraph EL["EL Watcher (12s poll)"]
EL1[Fetch DepositEvent logs] --> EL2[Decode event fields]
EL2 --> EL3[Record deposit row]
EL3 --> EL4["Status → DEPOSITED"]
end
subgraph CL["CL Watcher (24s poll)"]
CL1[Query /validators/pubkey] --> CL2[Record CL observation]
CL2 --> CL3["1st label → SEEN_BY_CL"]
CL3 --> CL4["2+ labels → ACTIVE"]
end
Both watchers use 2+ labels with distinct clients for diversity, bounded exponential backoff on errors, and graceful SIGINT/SIGTERM handling.
11. Failure Modes¶
| Failure | What Happens |
|---|---|
| Cross-check preflight fails | Chain/fingerprint/owner mismatch or endpoint unavailable — operation aborts |
| Simulation reverts | Allowlist missing, owner-only sender mismatch, bad deposit data, or bad amount |
| CL visibility delay | Validator may not appear immediately after EL success due to CL ingestion timing |
| Operational revoke after deposit | Seat marked REVOKED in DB, but on-chain deposit is irreversible |
12. Practical Run Sequence¶
# 1. Health check
seat-manager status
# 2. Create a seat
seat-manager seat create --operator <id> --pubkey <48B> --wc <32B>
# 3. Approve (allowlist on-chain)
seat-manager seat approve <id> --send
# 4. Deposit
seat-manager seat deposit <id> --amount-ctn 32 --send
# 5. Watch for activation
seat-manager watch
# 6. Review
seat-manager seat list
Dry-run options
- Add
--simulate-onlyto test without sending - Add
--expect-revertto verify a blocked path
13. Permissionless Transition¶
To reach truly permissionless behavior at the contract level:
| Setting | Current (Permissioned) | Target (Permissionless) |
|---|---|---|
pubkeyAllowlistDisabledForever |
false |
true |
ownerOnlyDepositorEnabled |
true |
false |
Seat Manager already handles the allowlist switch/freeze pipeline. Owner-only toggling is currently external to this CLI.
14. Staking Launchpad (Current Plan)¶
Current position
The launchpad is not the production deposit path in this permissioned stage. Seat Manager remains the operational system of record for onboarding and deposits.
What we are doing now:
- Keep launchpad tested against mainnet-fork and contract behavior
- Do not use launchpad for live onboarding while owner-only/permissioned controls are active
Cutover Plan¶
graph TD
A["<b>Managed Phase</b><br/>Seat Manager controls approvals + deposits<br/>ownerOnlyDepositorEnabled = true<br/>pubkeyAllowlistEnabled = true"] --> B
B["<b>Permissionless Cutover</b><br/>ownerOnlyDepositorEnabled → false<br/>Schedule disable forever → wait timelock → execute<br/>pubkeyAllowlistDisabledForever = true"] --> C
C["<b>Launchpad Primary Path</b><br/>Public deposits allowed by contract state"]