Split administration from operation from consumption. Put Wardens at the trust edge, on-chain registries at the authority root. Prep the system for MainNet. — Approved April 18, 2026 · See Addendum v1 for open-question resolutions.
/shardkeep/proposals/logs/v1.2.0-role-separation.log.
master.chillxand.me as a Warden is a node operation. Setting reward config, managing treasury, tuning emissions, approving new Wardens — those are protocol governance. They share a server today, but they're two unrelated responsibilities and they should never have been collapsed into one UI. Before MainNet launches, we separate them permanently, and we distribute the auth surface across Wardens anchored by on-chain authority.
admin role in the 3Cs portal can see and modify anything in ShardKeep — nodes they don't own, economic parameters, other operators' stake, global network state. That is fine for a two-person team running the whole network. It is untenable the moment a third-party Bastion operator joins.
Four concrete symptoms, each traceable to the same root cause:
master.chillxand.me is treated as both a network node and the global protocol administrator. Those are distinct responsibilities. A Warden operator should not automatically be a network admin./operator/ tree mixes protocol-governance pages (treasury, reward config, token) with per-node pages (stake, heartbeat). The /user/ tree shares plumbing with operator pages, risking data leaks.Beyond today's inconvenience, this becomes a security and reputation risk at MainNet launch:
Protocol authority. Only 2 wallets for now.
One menu per Warden the wallet owns.
One menu per Bastion the wallet owns.
One menu per Sentry the wallet runs.
The end-user view. Already exists at /shardkeep/user/. Tighten scoping to eliminate shared includes with operator pages.
A single human may have more than one role — the most common case is the ShardKeep team: admin wallet + separate Warden operator wallet + possibly a personal vault wallet. After login, if a wallet qualifies for multiple planes, the user sees a role selector. Selecting a role scopes the session — switching planes requires re-selecting, not the session carrying both sets of capabilities simultaneously. Forces the mental separation.
Three distinct wallet classes, each bound to a single role:
| Class | Purpose | Security posture | Signs |
|---|---|---|---|
| User wallet | Vault encryption key derivation + identity for User plane | Hot (daily use). Phantom, Solflare, or hardware. | Encryption-key derivation, subscription actions, vault reads |
| Operator wallet (cold) | Holds the stake; identity for Warden/Bastion/Sentry plane | Cold. Hardware wallet recommended. Minimally used. | Node registration, stake changes, slashing-sensitive operations, manager-wallet binding |
| Manager wallet (hot) | Day-to-day node operations without touching the cold wallet | Hot. Can be a browser-extension keypair. | Heartbeat acknowledgments, URL updates, non-critical config changes, routine JWT renewals |
Each operator signs a one-time on-chain message with the operator wallet that says "pubkey M is my manager wallet for node N". The Warden registry stores this binding. Rotating a manager wallet is a re-signing by the operator wallet. Compromised manager wallet is revocable.
All on-chain work happens on Solana DevNet first. Program IDs may churn as we iterate. When everything stabilizes, we re-deploy the same programs to mainnet-beta and flip a CLUSTER flag in client config. No database-allowlist stopgap, no "we'll move this on-chain later" debt.
devnet and mainnet-beta.WardenRegistry is the source of truth; clients read it, pick the best Warden for their region, and cache the selection. For users without the extension, GeoDNS provides a convenience bootstrap.
Planned Warden deployment (one per approximate time zone, per prior discussion):
| Region | Fallback 1 | Fallback 2 |
|---|---|---|
| PDT (Pacific) | MDT | CDT |
MDT (Mountain) ← master.chillxand.me | PDT | CDT |
| CDT (Central) | MDT | EDT |
| EDT (Eastern) | CDT | UTC |
| UTC (EU) | EDT | APAC |
| APAC | UTC | PDT |
/ and pinnable to IPFS/Arweave — for first-time web users without the extension. Plain HTML + JS reads the on-chain registry.vault.shardkeep.io — convenience resolution when no extension and no JS. Round-robin or latency-based. Not trust-bearing — the on-chain registry is the trust root. DNS compromise = availability bug only.There are literally two admin humans, and both of you know where your regional Wardens are. Admin UI routing can be hardcoded or an explicit picker. Keeps the admin surface smaller.
A new Solana program (working name: shardkeep_authority) with two top-level PDAs.
AdminRegistry {
admins: Vec<AdminEntry>, // v1: 2 entries
threshold: u8, // v1: 2-of-2 multisig for mutations
created_at: i64,
updated_at: i64,
}
AdminEntry {
wallet: Pubkey,
added_at: i64,
duress_locked: bool, // set by flag_duress
duress_since: Option<i64>,
duress_reason: Option<String>, // recorded on lift_duress
}
Operations:
add_admin(new) — requires threshold existing-admin signaturesremove_admin(target) — requires threshold existing-admin signatureschange_threshold(new_t) — requires threshold existing-admin signaturesflag_duress(wallet) — signed by the Warden that observed the canary match; sets duress_locked = truelift_duress(wallet, reason) — signed by another admin + password re-verification; clears the flag; triggers 24-hour cooldownrecord_duress_ack(wallet, plane, session_id, sig) — on-chain audit when a duress-flagged wallet continues in a non-admin plane (see Addendum §6.1)time_locked_recovery_lift(wallet) — signed by the cold recovery wallet; 7-day on-chain delay; clears all duress flags. Nuclear recovery for the both-admins-coerced scenario.The admin set is thus transparent, verifiable, and un-unilaterally-modifiable. No single Warden operator, no single compromised server, can add an admin. See Addendum §6.1 for the full canary-phrase duress design.
WardenEntry {
node_pubkey: Pubkey, // Warden's node keypair — signs JWTs
operator_wallet: Pubkey, // Cold wallet — signs stake/slashing ops
manager_wallet: Pubkey, // Hot wallet — day-to-day ops
public_url: String, // MUTABLE via update_url
url_history: Vec<UrlChange>, // Last N URL values with timestamps
cluster: enum, // DevNet | MainNet
region_code: String, // ISO 3166-1 + TZ hint, e.g. "US-MDT"
stake_amount: u64, // DevNet-SHRD (DevNet) OR SHRD (MainNet)
status: enum, // Pending | Active | Probation | Slashed | Exited
registered_at: i64,
last_heartbeat: i64, // Coarse, rate-limited to ~1/hour
metadata_uri: Option<String>, // Optional off-chain metadata
}
Operations:
apply_warden(...) — operator signs; stake transfers into PDA escrow; entry status = Pendingapprove_warden(entry) — admin multisig signs; flips Pending → Active. This is the permissioned gate for v1.2 and v2.x.heartbeat_warden(...) — manager_wallet signs; rate-limited on-chainupdate_url(...) — manager_wallet signs; appends old URL to url_history; triggers client cache invalidationexit_warden(...) — operator signs; starts unbonding; stake returns after UNBOND_SLOTS delayslash_warden(...) — admin multisig signs; stake reduced per policyupdate_url op — not a program redeploy. Clients refresh the registry cache every ≤ 5 minutes, so URL changes propagate naturally. The url_history field lets clients display "this Warden was previously at X" during migrations and debugging.
First deployment seeds both PDAs in a single genesis transaction: AdminRegistry with the two admin wallets, WardenRegistry with master.chillxand.me (or its successor URL per Addendum §6.2 domain flexibility) as Warden #1 on DevNet. Subsequent Wardens go through the apply → admin-approve flow — gated upstream by the DevNet admin-airdrop model described in §6a below.
airdrop_devnet_tokens(operator_wallet, amount, reason_uri) — DevNet-SHRD transferred to operator.apply_warden(...) (or equivalent for Bastion/Sentry) using the airdropped tokens as bond.approve_warden(entry) — Pending → Active.| Artifact | Location | Purpose |
|---|---|---|
airdrop_devnet_tokens | On-chain instruction | Admin multisig signs; mints/transfers DevNet-SHRD to operator |
shardkeep_devnet_airdrops | Off-chain (mirrored from on-chain) | Local table for audit + display: admin signer, operator, amount, reason, planned node type, airdrop timestamp |
| DevNet-SHRD mint | On-chain (SPL token) | Separate mint from MainNet SHRD; used only on DevNet; admin-controlled mint authority |
MainNet SHRD is purchased from the market (or awarded via protocol emissions). There is no admin airdrop path on MainNet. The admission gate on MainNet is operator voting (v2.2+) that reads DevNet tenure as one of its inputs. DevNet is thus the onramp to MainNet operation.
A rogue Warden at fake.shardkeep.io cannot successfully phish a ShardKeep user/operator/admin thanks to a three-layer defense:
Extension reads WardenRegistry. Rogue origin has no on-chain entry. Extension refuses to auto-route there and refuses to populate the auth flow.
When any site requests a wallet signature, the extension's sign-hook compares the origin to registered Warden public_urls. Unregistered origin → red warning: "This site is not a registered ShardKeep Warden. Do not sign."
A rogue Warden can sign its own JWTs, but no other Warden (and no other ShardKeep node) will accept them because its node_pubkey isn't in the registry. JWTs are worthless outside the rogue server's own domain.
500 SHRD staked on-chain since 2026-04-15" as part of the login UI. Impersonators with no on-chain presence look obviously wrong to users.flag_duress instruction:
discord-bot service to the other admin and to an operator security channel. Message includes timestamp, source IP, source user-agent, and the plane the attacker attempted to access.alerts-service (port 5006) with the same payload.duress_ack message with their wallet to proceed for the session. Acks are posted on-chain via record_duress_ack and mirrored to every Warden's audit log — so coercer pivots into operator/user planes are publicly witnessed even though they aren't operationally blocked.
/var/www/shared/shardkeep/ admin/ ← NEW. Protocol governance. Gated: admin registry + admin pwd. index.php (global network, aggregate stats) treasury.php (moved from /operator/treasury.php) reward-config.php (moved from /operator/epochs.php + token.php) proposals/ (governance proposals UI) wardens.php (read-only view of all Wardens; approve pending) audit.php (logs) warden/ ← NEW. Gated: wallet owns a Warden node. index.php (MY Warden's dashboard) staking.php (MY stake, earnings, unbond) settings.php (manager wallet, URL change) bastion/ ← NEW. Same pattern, scoped to Bastion nodes. index.php staking.php settings.php sentry/ ← NEW. Same pattern, scoped to Sentry nodes. index.php tasks.php staking.php user/ ← Keeps. Tighten scoping. index.php (vault) settings.php sentry.php (opt-in to running a Sentry — single blessed overlap) operator/ ← DEPRECATED. Split content into admin/warden/bastion/sentry. Its api/ tree becomes the shared backend for all planes. Eventually rename to /api/ + /engine/ to reflect reality.
role + target_node_ids for operators.requireOperatorFor($node_id), requireWardenOwner(), requireShardKeepAdmin(), etc./operator/. Every query scopes by role./operator/ pages in favor of /admin/ + /warden/ + /bastion/ + /sentry/.shardkeep_authority program (Anchor or raw).| Version | Contents | Cluster | Admission model |
|---|---|---|---|
v1.2.0 — The Great Role Split | Tracks 1 + 2. Off-chain role plumbing + UI split. Canary-phrase scaffold. | DevNet (existing) | Admin-approved |
v2.0.0 — On-Chain Authority | Tracks 3 + 4 + 5. AdminRegistry + WardenRegistry deployed. Extension becomes router. DevNet-SHRD mint live. Duress mechanisms fully on-chain. | DevNet stabilization | Admin-approved; admin-airdrop gate active |
v2.1.0 — MainNet Launch | Same codebase, same programs, redeployed to mainnet-beta. Cluster selector in client. DevNet and MainNet both live. | MainNet + DevNet both live | Admin-approved on both clusters |
v2.2+ — Voted Admission (follow-on proposal) | Separate dedicated proposal. Introduces operator-voted permissionless MainNet Warden admission. Reads DevNet tenure + peer vouches. Out of scope for this proposal. | MainNet | Operator-voted |
Decision: Independent keys. The operator wallet signs a one-time on-chain registration message binding the manager pubkey. Cleaner security boundary; compromise of manager wallet cannot compromise operator wallet; rotation is a new binding signature. See Addendum §1.
Decision: Sentry is an extension-level capability, not a wallet-class exception. Any wallet that has signed into the extension can flip the Sentry toggle — user, Warden operator, Bastion operator, or admin. The wallet-class separation rule is not violated because the Sentry capability is wallet-agnostic. See Addendum §1 and §3 of this proposal.
Decision: Yes to 2nd-factor password, and a canary-phrase duress-lockdown mechanism on top of it. Admin plane fully locks under duress; every other plane for that wallet shows a red banner requiring a signed duress_ack posted on-chain. Both-admins-coerced nuclear case mitigated via a time-locked recovery wallet (v2.0). See Addendum §2 and §6.1 for full design.
Decision: Permissionless MainNet admission is a separate follow-on proposal (v2.2+), not part of v1.2 or v2.0 scope. This proposal lays the schema groundwork (WardenEntry has all needed fields). The follow-on will introduce operator-voted admission reading DevNet tenure + peer vouches + on-chain voting. Critically: the DevNet admin-airdrop gate (§6a) means every DevNet operator has already been admin-vetted, so voted-admission doesn't cold-start against Sybil risk. See Addendum §3 and §6.2 for pipeline design.
| Risk | Likelihood | Mitigation |
|---|---|---|
| Track 2 refactor breaks existing flows (ACL migration, node views, etc.) | Medium | Comprehensive audit of every consumer of /operator/ before moves. Integration tests. Staged rollout: ship /admin/ and /warden/ first; keep /operator/ as a redirect shim until /bastion/ + /sentry/ ship. |
| On-chain program has a bug discovered post-deploy | Medium | DevNet-first gives us a free iteration ground. Include a one-time admin_emergency_migrate instruction that lets the admin multisig bulk-migrate state to a new program version. Removed before MainNet launch. |
| Operators running Wardens see "their dashboard suddenly shows less" after the role split | Certain (by design) | Comms + migration doc + in-UI banner explaining the split. Emphasize that the reduction is decentralization, not loss of capability. Admins still have full visibility — they just reach it from a separate menu. |
| Extension v2 rollout creates support burden (users on old extension) | Medium | Extension v1.1.6 already has a version-check mechanism. Force-update cadence: 30-day grace; v1 refuses to authenticate against v2.0.0 Wardens after grace. |
| Geographic Warden deployment slower than expected — only one Warden live at MainNet | High (near-term reality) | Architecture accommodates a single Warden. GeoDNS + registry gracefully degrade to "the only live Warden." Ship with 1, scale as operators come online. |
| Both admins coerced into canary-lockdown simultaneously | Very low (requires coordinated physical attack on both admins) | Time-locked recovery wallet. A third key held in cold physical storage (hardware wallet in a safe) that, after a 7-day on-chain waiting period and three confirmations from the recovery wallet itself, can unilaterally lift all duress flags via time_locked_recovery_lift. Not v1.2-critical; lands in v2.0 with the on-chain programs. See Addendum §2.6. |
| DevNet admin-airdrop becomes a bottleneck as the operator pool grows | Medium (as ShardKeep gains adoption) | Admins are the intentional rate limiter early on — this is a feature. If onboarding volume outpaces admin capacity, introduce a "warden-vouched airdrop" tier where an existing-admin-vetted Warden can airdrop (smaller amount) to a new operator subject to post-hoc admin review. Landed in v2.2+ voted-admission proposal. |
master.chillxand.me registered as Warden #1. Extension v2 routes via registry. JWTs signed by Warden node keys verifiable by any other Warden. Impersonation warnings functional against a test rogue origin.
Approved April 18, 2026. Implementation has started:
/var/www/shared/shardkeep/proposals/logs/v1.2.0-role-separation.log. Log is visible from the proposals index via the existing progress-log viewer convention.shardkeep_authority program will be the first on-chain artifact of the great role split, deployed to Solana DevNet.Items explicitly deferred to dedicated follow-on proposals:
Implements operator-voted permissionless MainNet Warden admission. Reads DevNet tenure (on-chain) + peer vouches (OperatorProfile PDA) + engagement signals. 60%-quorum / 2/3-threshold voting with admin veto retained until ≥20 MainNet Wardens. Design detail in Addendum §6.2. Prerequisite: ≥3 MainNet Wardens live + ≥60 days of operator-community history.
Third cold-storage key used for the nuclear "both admins coerced simultaneously" recovery scenario. 7-day on-chain waiting period + three confirmations from the recovery wallet itself clears all duress flags via time_locked_recovery_lift. Lands with the on-chain programs in v2.0.
On-chain operator identity with self-chosen handle, nodes operated, vouches given/received, last-active timestamp. Reads into the voted-admission pipeline. Also surfaces in operator UIs for community visibility. Scoped with the voted-admission proposal.
Proposed: April 18, 2026 · Approved: April 18, 2026 · Status: Implementation in progress — Track 1 · See: Addendum v1