Skip to content

Add app government#90

Open
crypt0fairy wants to merge 42 commits into
masterfrom
taras/gov
Open

Add app government#90
crypt0fairy wants to merge 42 commits into
masterfrom
taras/gov

Conversation

@crypt0fairy
Copy link
Copy Markdown

@crypt0fairy crypt0fairy commented Mar 6, 2026

Summary

Add multi-identity governance support to the ecloud CLI. Users can now manage apps through Safe multisigs and Timelock contracts alongside the existing EOA flow. The CLI handles identity discovery, on-chain deployment via SafeTimelockFactory, identity-aware transaction routing, and pending-op enumeration.

What's new

Identity management (auth namespace)

  • ecloud auth identity new — deploy a Safe and/or Timelock via SafeTimelockFactory; auto-discovers existing factory-deployed identities for the signing key.
  • ecloud auth identity list — list all stored identities for the current environment.
  • ecloud auth identity select — switch the active identity per environment.
  • ecloud auth sync — rescan the factory for identities owned by the signing key and rebuild the local config.
  • ecloud auth whoami — now shows identities, active identity, signing key source, and pending Timelock operations.
  • ecloud auth generate / login / logout — refactored to separate keyring management from identity management; the signing key is a single master credential from which multiple identities may derive.

App & team governance commands

All commands that perform on-chain writes route through the active identity:

  • compute app upgrade, start, stop, terminate — direct call for EOA, Safe proposal for Safe, schedule → execute for Timelock, Safe-proposed-schedule for Timelock(Safe).
  • compute app ownership transfer (new) — transfer app ownership to another EOA, Safe, or Timelock; transferring to a Timelock enables timelocked mode automatically.
  • compute team grant, revoke, list (new) — ADMIN/PAUSER/DEVELOPER team role management.
  • All governance-routing commands accept shared flags --execute <op-id>, --cancel <op-id>, --delay <duration> for completing or aborting pending Timelock operations.

UserAPI multi-identity header

  • CLI now sends X-eigenx-identity: <address-list> to UserAPI so the platform evaluates permissions against declared identities rather than the recovered EOA.
  • app list switches identity per-iteration; batch calls (profile prefetch) declare all stored non-EOA identities. Fully backward-compatible: absent header → platform falls back to existing EOA-based auth.

SDK additions

  • packages/sdk/src/client/common/contract/identity-router.ts — shared identity-aware routing primitive used by all governance commands.
  • packages/sdk/src/client/common/contract/safe.ts — Safe Transaction Service proposal integration.
  • packages/sdk/src/client/common/contract/caller.ts — helpers for SafeTimelockFactory, TimelockController, pending-op enumeration (getPendingTimelockOps, executeTimelockOp), identity discovery (getSafesByDeployer, getTimelocksByDeployer, discoverTimelock), team role RPCs.
  • UserApiClient — new identities option + setIdentities() method; header wired into all authenticated requests including profile upload.
  • ABIs added: SafeTimelockFactory.json, TimelockController.json; AppController.json regenerated for the new contract surface.

CLI infrastructure

  • utils/identityTransaction.tsexecuteWithIdentity wrapper + printIdentityContext, printTransactionResult.
  • utils/timelockExecute.ts — shared handleTimelockExecute / handleTimelockCancel used by all governance commands.
  • utils/apiIdentity.ts — resolves declared identities for UserAPI header.
  • utils/globalConfig.ts — identity storage schema (type, address, delay, safeAddress, environment) and active-identity-per-environment.
  • flags.tstimelockFlags group shared across governance commands.

Changes to existing behavior

  • ecloud auth whoami output reformatted to surface identities and pending operations.
  • compute app upgrade/start/stop/terminate route through Safe proposal / Timelock schedule when an identity is active. EOA path unchanged.
  • Sensitive app operations on Timelock-owned apps now require going through schedule → execute on-chain; modifiers in the contract enforce this.

Backward compatibility

  • No required migrations for existing users — EOA flows continue to work exactly as before.
  • X-eigenx-identity header is optional; the platform falls back to EOA-derived permissions when absent.
  • Existing identities deployed outside the factory (plain Gnosis Safe Factory) are not accepted by the multi-identity path; users with those should use auth identity new or auth sync to migrate.

Test plan

  • auth generate / login / logout / whoami / sync round-trips cleanly
  • Deploy + upgrade an app with EOA identity (legacy path)
  • Deploy identity: Safe, Timelock(EOA), Timelock(Safe); each appears in whoami
  • Transfer app ownership to Timelock; confirm timelocked is set on-chain
  • Upgrade via Timelock(Safe): schedule proposes to Safe → Safe executes → whoami shows pending op → --execute <op-id> finalizes
  • Cancel a pending op with --cancel <op-id>; op disappears from whoami
  • app list shows apps for every stored identity with correct ownership marker
  • app info / app list display pending Timelock ops when active identity is a Timelock
  • team grant PAUSER → granted address can app stop but not app terminate
  • team grant DEVELOPER → granted address can updateAppMetadataURI only
  • UserAPI requests include X-eigenx-identity when a non-EOA identity is active (verified via server logs)

@crypt0fairy crypt0fairy marked this pull request as ready for review March 24, 2026 19:54
…mmands

- ABI: getAppGoverned→getAppTimelocked, DirectUpgradeNotAllowed→TimelockRequired,
  GovernanceRequired→NotTimelocked
- SDK caller: getAppGoverned→getAppTimelocked, update error messages
- SDK app module: isGoverned→isTimelocked in interface and implementation
- CLI upgrade: block direct upgrade when app is timelocked
- CLI upgrade schedule/execute: new commands for timelocked two-step flow
- CLI ownership transfer: new command, shows timelocked mode note post-transfer
- Docs: add governance-commands.md documenting all new commands and flows
SDK:
- Update AppController ABI from latest contract (adds cancelUpgrade, team role functions, getAppOwner)
- Add cancelAppUpgrade, grantTeamRole, revokeTeamRole, getTeamRoleMembers, getAppOwner to caller.ts
- Add TeamRole enum to SDK
- Add cancelUpgrade, grantTeamRole, revokeTeamRole, getTeamRoleMembers to AppModule interface and implementation
- Export new functions and types from client/index.ts

CLI:
- ecloud compute app upgrade cancel — cancel a pending scheduled upgrade
- ecloud compute team grant <addr> --role=PAUSER|DEVELOPER — grant team role
- ecloud compute team revoke <addr> --role=PAUSER|DEVELOPER — revoke team role
- ecloud compute team list — show ADMIN/PAUSER/DEVELOPER members for an app
…e selection

- CLI now stores multiple identities (EOA, Safe, Timelock) per user in config
- Active identity is tracked per environment
- auth login: shows identity selector; 1 identity auto-prompts, replacing key wipes old identities and re-discovers Timelock for new EOA
- auth new/generate: rewritten with EOA/Safe/Timelock flows; enforces CANONICAL_SALT for deterministic Timelock addresses
- auth whoami: shows all identities with active marker and signing key status
- SDK: add deploySafe, deployTimelock, discoverTimelockForEOA, CANONICAL_SALT
- SDK: add SafeTimelockFactory and TimelockController ABIs
…ore replace

- auth new → Safe: auto-generate signing key if none exists, EOA always pre-filled as owner
- auth new → Timelock: EOA proposer defaults to signing key, Safe prompts for address
- Consistent warning message across all key-replace flows
- Show existing key in pager before warning so user can back it up
- Safe Transaction Service discovery in auth login (find Safes where EOA is owner)
- Remove redundant Safe identity entry when Safe+Timelock deployed together
executeGovernedUpgrade previously re-ran the full Docker build pipeline
to reconstruct the Release for on-chain hash verification. This would
fail with ReleaseMismatch on any non-deterministic build.

The Release is already emitted in full in the AppUpgradeScheduled event.
Add getScheduledRelease() to fetch and decode it from logs by matching
on readyAt, then pass it directly to executeUpgrade — no rebuild needed.

Also simplifies ExecuteGovernedUpgradeOptions and the execute CLI command,
removing all build flags (dockerfile, image-ref, env-file, instance-type,
log-visibility, resource-usage-monitoring) that are no longer required.
…ions

Add schedule/execute CLI commands and SDK methods for the three AppController
operations that require Timelock routing when an app is timelocked:
terminateApp, transferOwnership, and grantTeamRole(ADMIN).

- TimelockController ABI: add schedule, execute, hashOperation, getTimestamp
- caller.ts: add scheduleTimelockOp/executeTimelockOp primitives, per-op
  helpers (terminate, transferOwnership, grantTeamAdmin), and readyAt helpers
- AppModule: expose all new methods including getTimelockTerminateReadyAt,
  getTimelockTransferOwnershipReadyAt, getTimelockGrantAdminReadyAt
- CLI: app terminate schedule/execute, app ownership schedule-transfer/
  execute-transfer, team grant-admin schedule/execute
… consistent style

- Add new commands: ownership schedule/execute-transfer, terminate schedule/execute,
  upgrade cancel, team grant-admin schedule/execute
- Rename sections to command-group style (compute app, compute team, etc.)
- Shorten command names in table rows (remove ecloud compute app prefix)
- Replace emoji cell values with plain text (no permission, direct, yes, visible)
- Abbreviate column headers to TL(EOA) / TL(Safe)
- Update legend to document new cell value vocabulary
crypt0fairy and others added 15 commits April 9, 2026 17:33
Keyring commands (signing key):
- auth generate — generate new key + store in keyring (simplified, no Safe/Timelock)
- auth login — import existing key + store + discover identities on-chain
- auth logout — remove key from keyring + clear all identities

Identity commands (new):
- auth identity new — create Safe or Timelock (moved from auth generate)
- auth identity list — show all stored identities
- auth identity select — switch active identity per environment

Changes:
- auth generate no longer offers Safe/Timelock creation
- auth login no longer has identity selector (use auth identity select)
- auth logout now clears identities (prevents orphaned state)
- Added removeIdentity() to globalConfig.ts
- Added auth-flow-map.md with complete state transition table,
  decision trees for every command, and command tree with key requirements
Rename discoverTimelockForEOA to discoverTimelock — the underlying
calculateTimelockAddress works with any address (EOA or Safe) + salt.

Now when creating a Timelock via auth identity new, the CLI checks
for existing Timelocks for both EOA and Safe proposers before deploying.
This prevents duplicate deployments since addresses are deterministic
via CREATE2 with CANONICAL_SALT.
List now queries apps across all identity addresses from config (EOA,
Safe, Timelock), grouped by owner with active identity marked. Falls
back to signing key address if no identities configured.

No private key needed for reads — uses addresses from config.
List queries apps across all identity addresses (EOA, Safe, Timelock),
grouped by owner with active identity marked. Uses private key for
API authentication — backend resolves Safe/Timelock ownership on-chain.

Update auth-flow-map with backend ownership resolution flow.
New SDK modules:
- safe.ts: Safe Transaction Service proposal (read nonce, sign tx hash,
  post to Safe API)
- identity-router.ts: sendWithIdentity() routes transactions based on
  active identity type (EOA direct / Safe propose / Timelock schedule)

CLI changes:
- identityTransaction.ts: shared utilities for identity context display
  and routing
- start, stop, terminate: show active identity before sending, route
  through identity router when identity is Safe or Timelock

EOA behavior unchanged — existing code path preserved.
Safe/Timelock paths: propose to Safe Transaction Service or schedule
on Timelock via the identity router.
- auth identity new: replace discoverTimelock (single canonical-salt lookup)
  with getTimelocksByDeployer (full factory registry scan), supporting
  multiple timelocks per proposer
- auth whoami: fix misleading hints — 'auth new' → 'auth gen'/'auth login',
  'auth login' to switch → 'auth identity select'
- environment.ts: update sepolia-dev AppController address to latest deploy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After handling existing timelocks, ask whether to deploy another with a
different delay instead of always returning early.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds optional `salt` parameter to `DeployTimelockOptions` in the SDK so
callers can override the default CANONICAL_SALT (bytes32(0)). In the CLI,
when a user chooses to deploy an additional timelock beyond the first,
a cryptographically random 32-byte salt is generated to avoid CREATE2
collision.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Salt is keccak256(abi.encodePacked(minDelay)), so the same delay cannot
be deployed twice for the same proposer (CREATE2 reverts), and different
delays produce distinct Timelock addresses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bare numbers like "22" now error (previously silently treated as seconds).
Both delay prompts validate with the same regex before accepting, so the
user gets inline feedback rather than a thrown error post-confirmation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
crypt0fairy and others added 14 commits April 16, 2026 10:35
Previously the build-time compiled value always won, so BUILD_TYPE=dev
had no effect when running a prod build. Runtime env var now takes
precedence so the dev alias works without rebuilding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When all timelocks are already stored, display the known delay next to
each address so users can distinguish between them without guessing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dedup ABIs

- auth sync: new command to rescan chain and rebuild identities from stored signing key
- auth identity new: proposer selector (EOA + on-chain Safes with threshold/owner info),
  PROPOSER_ROLE filter for existing timelocks, deterministic salt keccak256(proposer, minDelay)
- auth login/sync: read getMinDelay() from chain, store human-readable delay (e.g. "24h")
- StoredIdentity: add threshold/owners fields for Safes; formatIdentity shows "Safe 1/1 · 0xabc…xyz"
- contractAbis.ts: shared SAFE_ABI, TIMELOCK_ABI, fetchSafeInfo(), fetchTimelockDelay() — eliminates
  inline ABI duplicates across new.ts, login.ts, sync.ts
- format.ts: add formatDelay(seconds: bigint) → human-readable string
- team grant/revoke: route all role operations through identity (Safe/Timelock)
  via encodeGrantTeamRoleData/encodeRevokeTeamRoleData; previously only ADMIN
  went through identity routing, PAUSER/DEVELOPER were sent directly as EOA
- add encodeRevokeTeamRoleData to SDK (ABI, encoder fn, barrel exports)
- fix gas estimation crash for Safe/Timelock identities in start/stop/terminate/
  transfer: skip estimation when identity is not EOA (msg.sender mismatch causes
  revert); estimateGas still runs for EOA path
- fix getPrivateKeyInteractive(environment) bug in all compute commands — was
  passing environment string "sepolia-dev" as the private key parameter
- remove acceptAdmin from deploy batch — eigenx-contracts emptied App.initialize,
  2-step admin handshake no longer needed
- whoami: add verbose flag to show full addresses in identity display
- whoami fetches pending ops for all Timelock identities via getPendingOperations()
  (single view call per Timelock, no log scanning, no RPC range limits)
- pending ops show description (decoded function name), countdown to executable, and op ID
- TimelockController ABI: add getPendingOperations, getPendingOperationIds, events
- SDK: getPendingTimelockOps() + PendingTimelockOp type exported
- whoami: add --rpc-url flag (reads ECLOUD_RPC_URL env var)
- environment.ts: update sepolia-dev AppController to 0x6A56214b79d24469f066AdfD1F28bB929824daCE
sync: replace factory deployer lookup with hasRole(PROPOSER_ROLE, safe) check — TimelockControllerImpl uses AccessControl not AccessControlEnumerable so getRoleMember doesn't exist

deploySafe: predict address before deploy and skip if already exists to handle re-runs gracefully

identity new: surface existing predicted Safe in proposer selector
All governance commands (upgrade, stop, start, terminate, transfer,
team grant, team revoke) now support --execute <op-id> and --cancel <op-id>
to complete or abort pending Timelock operations. Pending ops are also
shown in app list and app info output.

Shared utilities: timelockExecute.ts (execute/cancel handlers),
timelockFlags in flags.ts, formatCountdown in format.ts.
Also includes describeCalldata improvement to show app address in
pending op descriptions.
- Add --delay flag to all governance commands (override Timelock delay,
  must be >= minDelay, defaults to minDelay)
- Improve describeCalldata to show all address args in pending op
  descriptions (e.g. grantTeamRole(team, account))
- Make address arg optional in team grant/revoke when using
  --execute or --cancel
When an identity (Safe, Timelock, Timelock(Safe)) is active, attach the
X-eigenx-identity header to UserAPI requests so the platform evaluates
permissions against the declared identity instead of the recovered EOA.
The platform falls back to the EOA when the header is absent, so older
CLIs continue to work unchanged.

SDK changes (userapi.ts):
- UserApiClientOptions.identities: addresses to declare in the header.
- UserApiClient.setIdentities(): switch identities for subsequent requests
  (used by app list to iterate per-identity).
- addIdentityHeader(): wires X-eigenx-identity into both the standard
  authenticated path and the multipart profile upload path.

CLI helper (apiIdentity.ts):
- identityForActiveContext(env): declare the active identity on commands
  that act on a single app (info, releases, upgrade current-info fetch).
- identityForAllContexts(env): declare every non-EOA identity for batch
  endpoints (appResolver, prompts) so one call covers apps across all
  identities the caller holds.

app list switches identities per loop iteration, since each iteration
queries apps for a different identity.
# Conflicts:
#	package.json
#	packages/cli/src/commands/compute/app/upgrade.ts
#	packages/cli/src/utils/prompts.ts
These three docs were written as iteration notes during feature
development. They drifted from the final implementation (flag names,
command behavior) and overlap with commit messages and 'ecloud --help'
output. Dropping them rather than attempting a full refresh.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant