---
name: lifinance/rotate-pauser-wallet
source: https://app.decimal.ai/s/lifinance-rotate-pauser-wallet@1/SKILL.md
source_sha256: 358c7c1f29bc
---

# Rotate Pauser Wallet (LI.FI Contracts)

Rotates the shared **Pauser** EOA — the wallet allowed to call `pause()` on `EmergencyPauseFacet` to halt a diamond in an incident. The pauser is SC-owned, so it is rotatable (custody guard below).

Why this is a redeploy, not a setter call: `EmergencyPauseFacet` stores the pauser as an **immutable** set in its constructor — there is no on-chain function to repoint it. So "rotate the pauser" means **deploy a fresh `EmergencyPauseFacet` carrying the new pauser** and `diamondCut` it into every diamond, replacing the old facet's selectors. The repoint is delegated to `deploy-contract` (staging / testnets) and `multisig-rollout` (production Safe proposals); the old pauser's residual gas is swept to the new one via `sweep-wallet-funds`.

Kept-simple by design: the coverage gap during a per-chain cut is sub-second (the old facet still pauses until its selectors are replaced), so this skill does **not** atomically flip every chain at once — it repoints chain-by-chain, then rotates the CI secret and re-verifies the pause flow.

## When to use / when NOT

| Situation | Skill |
|---|---|
| Rotate the emergency-pause EOA (`pauserWallet`) | **this skill** |
| Rotate the deployer (`safeOwners[0]` + Timelock CANCELLER) | `rotate-deployer-wallet` |
| Rotate the staging diamond owner (Dev wallet) | `rotate-dev-wallet` |
| Full SC-dev offboarding across all three roles | `offboard-sc-dev` (it calls this skill) |

NOT for changing what `EmergencyPauseFacet` *does* (that is a facet-logic change, deployed via `deploy-contract` on its own) — this skill only changes *who* the pauser is.

## Inputs

- `--new-address 0xNEW` — the new pauser EOA (securely generated by a human; see Guardrails). Required unless the orchestrator passes it.
- `--check` — dry-run: run Phase 5's verification read-only against the current state and report what a rotation *would* change, without deploying, cutting, or moving anything.
- Old pauser address: **derive it, never trust `config/global.json`** — read the pauser currently baked into the live `EmergencyPauseFacet` (its constructor arg / the address the facet reports), because `global.json` can be mid-rotation-inconsistent.

## Guardrails

- **Custody guard.** Rotate only SC-owned wallets (**deployer, dev, pauser**); this skill touches **pauser** only. NEVER rotate the CTO-owned refund / feeCollector / withdraw.
- **Never self-sign.** New-key generation is a human step. Production diamond cuts become Safe proposals a human Ledger-signs via `multisig-rollout` → `script/deploy/safe/confirm-safe-tx.ts`; this skill hands off and WAITS — it never runs the signer.
- **Never bypass Safe/timelock** (rule 002-architecture). Every production repoint is a timelock-wrapped Safe proposal — no direct owner cut, no `SEND_PROPOSALS_DIRECTLY_TO_DIAMOND=true`.
- **Secrets hygiene.** Never print the new pauser's private key or full RPC URL; read keys in a subshell and redact. Derive the old pauser from the on-chain facet, not `global.json`.
- **CI secret is not in this repo.** The CI pauser secret and `verifyEmergencyPauseReadiness.yml` live partly outside `contracts` — coordinate the rotation, don't fake rotating a secret you can't see.
- **Exit-code convention** when shelling to scripts: `0` success; `1` real error (report stderr, stop, no retry/fallback); `2` recoverable misconfig (name the missing env/credential).
- Scripting is TypeScript (`bunx tsx`) or Bash only, never Python. Foundry/bun may need `export PATH="$HOME/.foundry/bin:$HOME/.bun/bin:$PATH"`.

## Workflow

Ordering is deliberate: **repoint → sweep → CI funding check → CI secret + pause-flow re-verify → config**. Repoint first so the new pauser is live before we sweep gas out of the old one and retire it.

### Phase 0 — Preflight

Run from the repo root. Report (don't fix silently):

- `.env` present; `PRODUCTION` matches the intended environment; `SEND_PROPOSALS_DIRECTLY_TO_DIAMOND` not `true`.
- Foundry available (`forge --version`); `gh auth status` OK; VPN up for MongoDB (needed by `multisig-rollout` / `check-rotation-status`).
- New pauser address supplied and confirmed with the user; old pauser read from the live facet.
- Repo version of the facet: `grep -m1 "@custom:version" src/Facets/EmergencyPauseFacet.sol` — the redeploy must ship a new pauser, so bump/confirm the version per repo convention before deploying.

### Phase 1 — Repoint (redeploy EmergencyPauseFacet with the new pauser + diamondCut)

The new pauser is a **constructor argument** of `EmergencyPauseFacet`, so it is set at deploy time (CREATE3). Confirm the deploy script wires the new pauser into the constructor — the address baked in must be the new one, verified before cutting.

- **Staging / testnets** (terminal, no Safe): `/deploy-contract EmergencyPauseFacet <network...>` — deploys, explorer-verifies, and `diamondCut`s the new facet directly.
- **Production** (Safe proposals): `/multisig-rollout EmergencyPauseFacet` — discovers every chain where `EmergencyPauseFacet` is live, deploys via `deploy-contract`, creates one timelock-wrapped `diamondCut` proposal per chain, drafts the deployment-log PR, hands off Ledger signing, verifies `signatureCount >= 2`, and posts `#dev-sc-multisig-proposals`. **Include Tron** — its diamond also hosts the facet and must be cut too.

Do not reimplement the deploy/cut/propose plumbing; `deploy-contract` and `multisig-rollout` own it. Carry forward the per-network new facet addresses for the config update and verification.

### Phase 2 — Sweep the old pauser's gas to the new one

Once the new pauser is live on every chain, drain residual native gas from the old pauser EOA into the new one so nothing is stranded on the retired key:

```text
/sweep-wallet-funds --new-address <NEW_PAUSER> --old-key-env PRIVATE_KEY_PRODUCTION --production
```

`sweep-wallet-funds` previews balances first (dry-run), moves native across all active EVM chains (native swept **last**), and reports per-network moved/skipped. Tron native has no multi-chain script — it flags a single manual `troncast send`.

### Phase 3 — CI funding check

Confirm the new pauser is funded on the chains CI expects (the emergency-pause funding gate). This is the funding slice of the rotation status check:

```text
/check-rotation-status --old-address <OLD_PAUSER> --new-address <NEW_PAUSER> --role pauser --production
```

Read-only. If a chain shows the new pauser underfunded, top it up (`/manage-wallet-funds send` for a single chain, or `/request-dev-funds` for a PR-based top-up) before treating the rotation as complete.

### Phase 4 — Rotate the CI pauser secret + re-verify the pause flow

The pauser's key is also held as a CI secret that drives the automated pause path, and its readiness is asserted by `verifyEmergencyPauseReadiness.yml`. Both live **partly outside** `contracts`:

- Rotate the CI secret so the automation uses the **new** pauser key. Coordinate with whoever owns the CI secret store — do NOT claim to have rotated a secret this skill cannot reach.
- Re-run / confirm `verifyEmergencyPauseReadiness.yml` is green against the new pauser so the automated pause flow actually works end-to-end.

If either is outside your reach in this session, surface it explicitly as a human/operational follow-up rather than marking it done.

### Phase 5 — Update config

Land the pauser role change in the repo:

```text
/update-wallet-config --role pauser --new-address <NEW_PAUSER> --production
```

`update-wallet-config` updates the EVM field and the matching `tronWallets.pauser` (deriving the Tron base58 from the new EVM address and cross-checking the reverse), honors config-structure rule 004, and ends by calling `/create-pr`. The Notion wallet-registry update is a follow-up it flags (needs auth) — don't fake it.

## Verification

Gate the rotation complete only when all hold:

- **On-chain repoint:** every target diamond (incl. Tron) now routes `EmergencyPauseFacet` selectors to the newly deployed facet, and that facet reports the **new** pauser (read it back — do not infer from the proposal).
- **Production proposals:** signed to `signatureCount >= 2` and posted for the remaining signer(s) (`multisig-rollout`'s gate) — timelock ops execute after the delay via the scheduled pipeline.
- **Funding:** `check-rotation-status` shows the new pauser funded on every CI-expected chain.
- **CI:** the pauser CI secret is rotated to the new key and `verifyEmergencyPauseReadiness.yml` is green (or the coordination follow-up is explicitly logged).
- **Config:** the `update-wallet-config` PR (EVM + `tronWallets.pauser`) is open; Notion registry follow-up noted.
- Old pauser swept to ~0 native on every chain.

`--check` runs this section read-only and reports the delta without changing anything.

## Reuse map

| Step | Calls |
|---|---|
| Repoint (staging / testnets) | `/deploy-contract EmergencyPauseFacet <network...>` |
| Repoint (production Safe proposals, incl. Tron) | `/multisig-rollout EmergencyPauseFacet` |
| Fund the new pauser from the old | `/sweep-wallet-funds --new-address <NEW> --old-key-env PRIVATE_KEY_PRODUCTION --production` |
| Single-chain top-up / PR top-up | `/manage-wallet-funds send` / `/request-dev-funds` |
| Funding + role verification gate | `/check-rotation-status --old-address <OLD> --new-address <NEW> --role pauser --production` |
| Land the config change | `/update-wallet-config --role pauser --new-address <NEW> --production` |
| Facet version marker | `grep -m1 "@custom:version" src/Facets/EmergencyPauseFacet.sol` |

CI secret rotation and `verifyEmergencyPauseReadiness.yml` live partly outside this repo — coordinated, not owned here.