Skip to content

Add client-side throttling against signature-request and submission spam #249

Description

@james2177

Description: useWalletStore.connect(), useAcceptIntent, and useSolverRegistration have no cooldown after a failed attempt — a user (or a malicious script running in a compromised extension/page context) can trigger rapid repeated Freighter signature popups or repeated backend submission attempts with no client-side rate limiting beyond the in-flight isConnecting/acceptingId guards, which only prevent concurrent calls, not rapid sequential retries.

Problem Statement & Context: Rapid repeated wallet-popup triggering is both a poor UX (popup spam) and a mild security-hygiene concern (training users to reflexively approve repeated prompts, a known vector for social-engineering signature approval). Similarly, unthrottled retries against acceptIntent/registerSolver add unnecessary load and noise to the backend relay.

Scope & Acceptance Criteria:

  • Add a short cooldown (e.g. a few seconds, tunable) after a failed connect(), accept(), or register() attempt before the corresponding action can be retried, with the UI clearly reflecting the cooldown (e.g. a disabled button with a visible countdown) rather than silently swallowing rapid clicks.
  • The cooldown must not degrade the legitimate happy-path retry experience beyond a few seconds' delay.
  • Out of scope: any server-side rate limiting (that's a vortex-backend concern) — this issue is scoped to client-side throttling only.

Implementation Guidelines:

  1. Key files: src/store/wallet.ts, src/hooks/useAcceptIntent.ts, src/hooks/useSolverRegistration.ts, src/components/ConnectWalletButton.tsx, src/app/solve/SolvePageClient.tsx.
  2. A small shared useCooldown(ms)-style hook is a reasonable, reusable shape for all three call sites rather than three bespoke implementations.
  3. Edge cases: cooldown state must reset appropriately after a successful attempt (no cooldown needed there), and must not persist across page reloads (in-memory only is fine, no localStorage needed).
  4. Testing: unit test the cooldown hook in isolation with vi.useFakeTimers(); extend ConnectWalletButton.test.tsx, useAcceptIntent.test.ts, and useSolverRegistration.test.ts to assert a rapid second attempt during the cooldown window is a no-op (and ideally surfaces a "please wait" toast rather than silently doing nothing).

Definition of Done:

  • Code written, tested.
  • Acceptance criteria met.
  • PR passes CI.
  • Reviewed and approved.

Resources: src/store/wallet.ts, src/hooks/useAcceptIntent.ts, src/hooks/useSolverRegistration.ts

Complexity: High (200 points)

Activity

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

Metadata

Metadata

Assignees

Labels

Stellar WaveIssues in the Stellar wave program

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions