Skip to content

idempotency-key support for the swap endpoint with concurrency-safe replay #548

Description

@mikewheeleer

Summary

Clients retry /api/v1/swaps on timeouts and at-least-once delivery, and today a retry creates a duplicate swap. Implement idempotency so a retry with an Idempotency-Key header yields exactly-once semantics.

Why this matters

Duplicate swaps are a correctness and trust failure. Idempotency keys are the industry standard (Stripe/PayPal) and unblock safe client retries.

Requirements

  • Accept an optional Idempotency-Key header on /api/v1/swaps.
  • First request: process normally, persist key -> { fingerprint, statusCode, responseBody } with a 24h TTL.
  • Replay (same key + same body): return the stored status/body without re-executing.
  • Conflict (same key + different body): return 409 idempotency_conflict.
  • Concurrent requests with the same key: exactly one executes; the other returns 409 request_in_progress.
  • Keys are scoped per tenant/API key — no cross-tenant reuse.

Technical guidance

  • Fingerprint = hash of (method, path, tenantId, canonicalized body).
  • Put the store behind an IdempotencyStore interface (ship the in-memory impl only).
  • Resolve the race with an atomic insert-if-absent or a per-key lock — never read-then-write.

Edge cases — each must have a test

  • same key + same body twice -> one swap, identical responses
  • same key + different body -> 409 conflict
  • two concurrent requests, same key -> exactly one executes
  • key reuse after TTL expiry -> treated as new
  • header absent -> behavior unchanged
  • malformed / oversized key -> 400
  • same key across two tenants -> isolated

Acceptance criteria

  • All requirements and every edge case above implemented and covered by tests
  • New unit and integration tests; existing tests still pass and no regressions
  • Structured, typed errors (no leaked internals; stable codes)
  • npm run lint, npm test, and npm run build all pass locally
  • Code follows the repo's existing conventions; no duplication or dead code
  • Short docs/comments explaining the design and any non-obvious decisions
  • PR description explains the approach and includes Closes #<issue>

Out of scope

  • A persistent/Redis-backed store (interface only)
  • Idempotency for other endpoints

Rewards

Part of the GrantFox OSS / Official Campaign (FWC26) — this task may be rewarded. PR quality is assessed by AI: depth, correctness under edge cases, meaningful tests, and clean design are what earn the reward. Shallow changes (typos, formatting, trivial docs) do not qualify.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions