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
Acceptance criteria
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.
Summary
Clients retry
/api/v1/swapson timeouts and at-least-once delivery, and today a retry creates a duplicate swap. Implement idempotency so a retry with anIdempotency-Keyheader 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
Idempotency-Keyheader on/api/v1/swaps.key -> { fingerprint, statusCode, responseBody }with a 24h TTL.409 idempotency_conflict.409 request_in_progress.Technical guidance
(method, path, tenantId, canonicalized body).IdempotencyStoreinterface (ship the in-memory impl only).Edge cases — each must have a test
Acceptance criteria
npm run lint,npm test, andnpm run buildall pass locallyCloses #<issue>Out of scope
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.