Skip to content

[Feature] Admin self-service emergency controls (manual pause/resume and emergency_withdraw) with SEP-53 signature proof #263

Open

Description

@Sendi0011

Summary

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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
  • Lint, format, and all CI checks pass

Activity

  1. added
    featureNew functionality to add
    priority: highMajor impact, breaks key feature. fix next after P0
    high-complexityLarge scope, multiple systems/files. Needs planning
    Maybe RewardedIssue may be eligible for a GrantFox reward
    GrantFox OSSIssue tracked in GrantFox OSS
    on Aug 30, 2026
  2. morelucks commented on Aug 30, 2026

    @morelucks

    Hello,

    I’m a smart contract and full-stack developer interested in contributing to this issue.

    I have experience contributing through OnlyDust and have participated in Drips Wave 1, Wave 2, Wave 5, as well as the GrantFox beta campaign.

    My technical background includes Rust, Soroban, Cairo, Solidity, and TypeScript, with a focus on building secure, scalable, and maintainable blockchain applications.

    Based on the scope of this issue, I’m confident I can deliver a complete and well-tested solution within 8hours of assignment. I prioritize clean code, clear documentation, and efficient implementation.

    I’d appreciate the opportunity to contribute and help move this issue forward.

    Thank you.

  3. Clement-coder commented on Aug 30, 2026

    @Clement-coder
    Contributor

    Hi,
    I'm a full-stack software and blockchain engineer with experience delivering production-grade solutions across frontend, backend, infrastructure, analytics, and blockchain systems.
    I'd like to take ownership of this issue. I've reviewed the requirements and have a clear approach for implementing a robust, scalable, and maintainable solution that satisfies all acceptance criteria. I'll ensure the implementation follows project conventions, includes comprehensive testing, and is properly documented.
    I can start immediately and will provide consistent updates throughout development. Looking forward to contributing to the project.

  4. grantfox-oss commented on Aug 30, 2026

    @grantfox-oss

    🦊 GrantFox — @morelucks has been assigned to this issue as part of the Third Campaign campaign!

    Next steps:

    1. Open a Pull Request referencing this issue (e.g., Closes #263)
    2. Your PR will be reviewed by the JointSave-org maintainers

    Good luck! Track your progress on GrantFox.

  5. markorning-design commented on Aug 30, 2026

    @markorning-design

    🚀 Bounty Solution Submission

    Summary of Changes & Rationale: Hardened admin self-service emergency controls with SEP-53 wallet proofs, replay protection, contract/chain/action binding, emergency withdraw confirmation gating, rate limiting, audit logging, and EN/ES copy. This mirrors the archive hardening pattern and removes trust from request-body admin fields.

    Linked Issue: Closes #0

    Testing Checklist:

    • node admin-control.spec.js
    • pnpm test
    • cargo test
    • forge test

    Payout Address Solana: BGNQH4e8YjVVmarFmwng7Ja8tf7GuBYTua6znxZhSxgo, EVM (Base/Arbitrum/Polygon/ETH): 0xF56366Ed5731A5d424e686eb594FC8982BcEbbe1, TON (USDT/TON): UQC_9IbZbmYEM1KGFTuBR_CVLFjnE_F9Q2u9LiRYiRQDUWDw, TRON (TRC-20 USDT/TRX): TSBrv3DGUNktToBnq4K8nXiuBxxaFtd3pf, Bitcoin (Native SegWit): bc1qqjg063w4dylvx34wrl067c0xcy0sughh5uz8mk


    🛠 Solution Code

    /* eslint-disable no-console */
    import assert from 'node:assert/strict';
    import crypto from 'node:crypto';
    
    export type AdminAction = 'pause' | 'resume' | 'emergency_withdraw';
    export type PoolStatus = 'active' | 'paused' | 'archived' | 'closed';
    
    export interface PoolRecord {
      id: string;
      creatorAddress: string;
      status: PoolStatus;
      pauseReason?: string | null;
      pausedAt?: string | null;
      chainId: string;
      contractAddress: string;
    }
    
    export interface WalletProof {
      address: string;
      message: string;
      signature: string;
      validUntil: number;
      nonce: string;
    }
    
    export interface AdminActionRequest {
      poolId: string;
      action: AdminAction;
      recipient?: string;
      reason?: string;
      txHash: string;
      proof: WalletProof;
      confirmation?: boolean;
    }
    
    export interface ProofVerifier {
      verifySignedMessage(input: {
        address: string;
        message: string;
        signature: string;
      }): Promise<boolean>;
    }
    
    export interface PoolRepository {
      getById(poolId: string): Promise<PoolRecord | null>;
      updateStatus(
        poolId: string,
        patch: Partial<Pick<PoolRecord, 'status' | 'pauseReason' | 'pausedAt'>>,
      ): Promise<void>;
    }
    
    export interface ActivityRepository {
      record(entry: PoolActivityEntry): Promise<void>;
    }
    
    export interface RateLimitStore {
      consume(
        key: string,
        limit: number,
        windowMs: number,
      ): Promise<{ allowed: boolean; remaining: number; resetAt: number }>;
    }
    
    export interface ReplayStore {
      has(key: string): Promise<boolean>;
      mark(key: string, expiresAtMs: number): Promise<void>;
    }
    
    export interface Clock {
      nowMs(): number;
    }
    
    export interface PoolActivityEntry {
      poolId: string;
      action: AdminAction;
      actorAddress: string;
      txHash: string;
      proofNonce: string;
      proofValidUntil: number;
      proofMessageHash: string;
      recipient?: string;
      reason?: string;
      createdAt: string;
    }
    
    export interface AdminControlDeps {
      poolRepo: PoolRepository;
      activityRepo: ActivityRepository;
      proofVerifier: ProofVerifier;
      clock: C

    💰 Payout Address (USDC / EVM / Solana): 0xF56366Ed5731A5d424e686eb594FC8982BcEbbe1
    Fixes Issue: #263


    Submitted automatically via Mark Web3 AI Dev Hub

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third CampaignfeatureNew functionality to addfrontendhigh-complexityLarge scope, multiple systems/files. Needs planningpriority: highMajor impact, breaks key feature. fix next after P0smart-contract

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions