feat: implement idempotency key system for blockchain transaction endpoints#65
Merged
DeFiVC merged 5 commits intoJul 18, 2026
Merged
Conversation
Add new table for tracking idempotency keys on blockchain transaction endpoints. Includes foreign key to users, request hash for body validation, response caching columns, and expiry index for cleanup.
Add checkIdempotency, storeIdempotentResponse, and cleanupIdempotencyKeys functions. Prevents duplicate on-chain transactions by caching responses and detecting reused keys with different request bodies.
Add idempotencyKey field to claim reward and mint credential request schemas. Controllers now check idempotency before processing and store responses after completion, enabling safe retry behavior for clients.
Cleanup runs hourly to purge idempotency keys older than 24 hours. Integrated into server startup and graceful shutdown lifecycle.
Unit tests cover checkIdempotency, storeIdempotentResponse, and cleanupIdempotencyKeys with mocked database. E2E tests verify idempotency key validation on reward claim and credential mint endpoints.
DeFiVC
approved these changes
Jul 18, 2026
DeFiVC
left a comment
Contributor
There was a problem hiding this comment.
Review: Approve
Excellent implementation of the idempotency key system.
Architecture looks solid:
- Clean separation: schema → middleware → controllers → cleanup job
- Client-generated keys (Stripe pattern) with 16-64 char validation
checkIdempotencyhandles all three cases correctly: cached hit, new key, conflict on reuse- Error responses cached alongside successes — prevents re-processing known failures
- Cleanup job with proper lifecycle integration (start/stop)
Testing is thorough:
- 8 unit tests covering all middleware functions (cached response, new key, conflict, response storage, cleanup)
- 5 E2E tests verifying validation on both
/rewards/claimand/credentials/mint - CI green on both Lint & Typecheck and Test
PR quality:
- 5 commits showing clear iterative development (schema → middleware → controllers → cleanup → tests)
- Detailed description with tradeoffs, breaking changes, and self-review checklist
- Clean scope — no unrelated changes
Approving.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #7
Type of Change
Summary
Blockchain transactions are irreversible. The current API has no idempotency mechanism for endpoints that submit on-chain transactions (
POST /api/rewards/claimandPOST /api/credentials/mint). If a client's HTTP request times out after the on-chain transaction succeeds, they have no safe way to retry without risking double-minting tokens or NFTs. This PR adds a complete idempotency key system to prevent duplicate on-chain transactions.Motivation / Context
Fixes #7 — The timeout scenario described in the issue creates a race condition where clients receive a 504 but the on-chain transaction has already succeeded. Retrying causes panics on the Stellar contract. This idempotency system caches responses and detects duplicate requests, returning the original result on retry.
Detailed Changes
A. Database Schema (
src/database/schema.ts)idempotencyKeystable with: key (PK), userId (FK), endpoint, requestHash, responseBody, responseStatus, txHash, createdAt, expiresAtsrc/database/migrations/0003_add_idempotency_keys.sqlB. Idempotency Middleware (
src/middleware/idempotency.ts)checkIdempotency(key, userId, endpoint, requestBody)— checks for existing key, returns cached response if request hash matches, throwsConflictErrorif key reused with different body, inserts new key otherwisestoreIdempotentResponse(key, status, body, txHash?)— updates key with response data after processingcleanupIdempotencyKeys()— deletes expired keys (older than 24h)C. Updated Route Handlers
POST /api/rewards/claim— now requiresidempotencyKey(16-64 chars) in request bodyPOST /api/credentials/mint— now requiresidempotencyKey(16-64 chars) in request bodyD. Cleanup Job (
src/jobs/cleanup-idempotency.ts)Testing
Unit Tests (
tests/unit/services/idempotency.test.ts)E2E Tests
tests/e2e/rewards.test.ts— 3 new tests: missing idempotency key rejection, short key rejection, unauthenticated with keytests/e2e/credentials.test.ts— 2 new tests: missing key rejection, unauthenticated with keyVerification
npm run lint— 0 errors (73 pre-existing warnings)npm run typecheck— cleannpm test— 128/129 pass (1 pre-existing timeout in quizzes.test.ts)Tradeoffs
Out of Scope
Breaking Changes
Yes — The
POST /api/rewards/claimandPOST /api/credentials/mintendpoints now require anidempotencyKeyfield in the request body (16-64 character string). Existing clients must update to include this field.Checklist
Self-Review
Testing
CI / Pipeline
Security