You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Soroban contracts expose high-stakes admin primitives — rotational::pause, unpause, and emergency_withdraw — but there is no admin-facing UI to invoke any of them directly. Today the only path that ever pauses a pool is the automatic security circuit breaker (PR #259); a pool admin has no self-service way to manually pause a pool, resume it on-chain, or trigger an emergency_withdraw if a contract malfunctions.
Compare this to the archive flow, which was hardened in PR #259: it got a proper wallet-signature proof (lib/archive-proof.ts + lib/server/wallet-proof.ts) and a UI banner. Pool pause/resume/emergency withdraw need the same treatment — these actions move or halt money and must not be callable by an address stuck in a request body.
Requirements
Manual pause / resume UI in the pool group page for the pool admin: pause with a reason and resume, mirroring archived-pool-banner.tsx styling. The platform pause flips pools.status (reuse pause_reason/paused_at columns added in PR feat(security): automated incident response and on-chain circuit breaker for critical pool alerts #259); the on-chain pause/unpause is a contract call signed by the admin's own wallet.
Emergency withdrawal UI (admin-only): a deliberate, clearly-labelled flow to call rotational::emergency_withdraw(admin, recipient) that (a) shows exactly what it will do and the recipient address, (b) requires a confirmation step, and (c) is signed by the admin's wallet.
SEP-53 signature proof (mirror the archive hardening): do NOT trust callerAddress/admin_address in the request body. The admin wallet signs a short timestamped message (via wallet-proof/signMessage) naming the pool and the exact action, and the server verifies it against the pool's recorded creator_address before committing. Reuse checkWalletProof / verifySignedMessage in frontend/lib/server/wallet-proof.ts.
Server endpoints (or extend the existing admin route) that verify ownership, the signature proof, the 64-char tx hash, and then record the action in pool_activity so it shows in the admin audit log — mirroring the /api/pools/[id]/archive and /api/admin/incidents/[id] (record_onchain) patterns.
Safeguards: emergency withdraw should refuse when the pool is not active/paused-eligible, should be rate-limited, and should clearly warn that it is irreversible. Add EN + ES strings.
Notes / context
The contract functions already exist — rotational/src/lib.rs has pause, unpause, emergency_withdraw. This issue is about the safe, provable UI + API layer, not changing the contracts.
Follow the archive hardening exactly: frontend/lib/archive-proof.ts (client signing), frontend/lib/server/wallet-proof.ts (verification), and the archived-pool-banner.tsx component conventions.
Emergency withdraw is a funds-moving admin action and must remain strictly manual and admin-only — never reachable from any automated path (the incident-response breaker already enforces this; keep it that way).
Component tests using Vitest + RTL, following frontend/__tests__/deposit-calendar.test.tsx conventions, would strengthen this.
Acceptance criteria
Admin can manually pause/resume a pool from the UI with a reason, and call emergency_withdraw through a confirmed, signed flow
Every action requires a fresh SEP-53 wallet signature verified against the pool's creator_address (spoofing admin_address gets nowhere)
On-chain submissions record the 64-char tx hash and appear in the admin audit log
Guardrails: eligibility checks, rate limiting, irreversible-action warning, EN + ES strings
Component + unit tests cover the signature proof, ownership checks, and the action flow
Summary
The Soroban contracts expose high-stakes admin primitives —
rotational::pause,unpause, andemergency_withdraw— but there is no admin-facing UI to invoke any of them directly. Today the only path that ever pauses a pool is the automatic security circuit breaker (PR #259); a pool admin has no self-service way to manually pause a pool, resume it on-chain, or trigger anemergency_withdrawif a contract malfunctions.Compare this to the archive flow, which was hardened in PR #259: it got a proper wallet-signature proof (
lib/archive-proof.ts+lib/server/wallet-proof.ts) and a UI banner. Pool pause/resume/emergency withdraw need the same treatment — these actions move or halt money and must not be callable by an address stuck in a request body.Requirements
archived-pool-banner.tsxstyling. The platform pause flipspools.status(reusepause_reason/paused_atcolumns added in PR feat(security): automated incident response and on-chain circuit breaker for critical pool alerts #259); the on-chainpause/unpauseis a contract call signed by the admin's own wallet.rotational::emergency_withdraw(admin, recipient)that (a) shows exactly what it will do and the recipient address, (b) requires a confirmation step, and (c) is signed by the admin's wallet.callerAddress/admin_addressin the request body. The admin wallet signs a short timestamped message (viawallet-proof/signMessage) naming the pool and the exact action, and the server verifies it against the pool's recordedcreator_addressbefore committing. ReusecheckWalletProof/verifySignedMessageinfrontend/lib/server/wallet-proof.ts.pool_activityso it shows in the admin audit log — mirroring the/api/pools/[id]/archiveand/api/admin/incidents/[id](record_onchain) patterns.active/paused-eligible, should be rate-limited, and should clearly warn that it is irreversible. Add EN + ES strings.Notes / context
rotational/src/lib.rshaspause,unpause,emergency_withdraw. This issue is about the safe, provable UI + API layer, not changing the contracts.frontend/lib/archive-proof.ts(client signing),frontend/lib/server/wallet-proof.ts(verification), and thearchived-pool-banner.tsxcomponent conventions.frontend/__tests__/deposit-calendar.test.tsxconventions, would strengthen this.Acceptance criteria
emergency_withdrawthrough a confirmed, signed flowcreator_address(spoofingadmin_addressgets nowhere)