Status: FIXED
Current behavior: Each settlement processed individually. Small settlements may be uneconomical due to network fees.
Expected behavior: Add SettlementBatch model. BullMQ repeatable job runs every BATCH_INTERVAL_SECONDS (default 300). Queries pending settlements, groups by asset. Creates a batch for groups with ≥ BATCH_MIN_COUNT settlements. Updates each settlement's batchId. Marks as completed. Settlements below min count remain pending.
Implementation:
Files modified:
prisma/schema.prisma— added SettlementBatch modelprisma/migrations/20260728110538_add_supported_assets_and_batching/migration.sql— migration for SettlementBatchservices/settlement-engine/src/index.ts— added BullMQ repeatable job for batchingshared/validation/index.ts— added BATCH_INTERVAL_SECONDS and BATCH_MIN_COUNT env varsservices/settlement-engine/src/settlement-batching.test.ts— comprehensive tests
Test coverage: ✅ 3 pending (2 USDC, 1 EURT) — USDC batch created with 2, EURT stays pending ✅ 1 pending — no batch (below min count) ✅ 0 pending — no batches
Acceptance criteria met: ✅ Pending settlements batched by asset at configurable intervals ✅ Batch records accurately reflect totals
Status: FIXED
Current behavior: GET /api/currencies returns codes only (no contract IDs). Settlement creation accepts any asset without validation.
Expected behavior: Add SupportedAsset table (code, contractId, decimals, name, isActive). Seed with initial values. Add GET /api/assets. Validate settlement asset field against this table. Add admin CRUD endpoints. Cache in Redis (1h TTL).
Implementation:
Files modified:
prisma/schema.prisma— added SupportedAsset modelprisma/migrations/20260728110538_add_supported_assets_and_batching/migration.sql— migration with seed data (USDC, EURT, XLMS)services/api-gateway/src/index.ts— added routes and settlement validationshared/validation/schemas.ts— added SupportedAsset schemas
New endpoints:
GET /api/assets— list all active supported assetsPOST /api/admin/assets— admin: add new assetPATCH /api/admin/assets/:code— admin: update assetDELETE /api/admin/assets/:code— admin: delete asset
Validation: ✅ Settlement creation validates asset against SupportedAsset table ✅ Returns 422 for unsupported or inactive assets
Acceptance criteria met: ✅ Supported assets are dynamically configurable ✅ Settlement asset validated against the list ✅ Admin CRUD endpoints functional
Status: FIXED
Current behavior: Fee calculation has no upper bound.
Expected behavior: Add optional maxFeeBps and maxFeeThreshold to merchant settings. In settlement-amounts.ts, if grossAmount > maxFeeThreshold, compute capped fee grossAmount * maxFeeBps / 10000. Use min(uncappedFee, cappedFee). Log when cap is applied.
Implementation:
Files modified:
services/settlement-engine/src/settlement-amounts.ts— added FeeConfig interface with maxFeeBps and maxFeeThreshold, updated computeSettlementAmounts to apply capshared/validation/schemas.ts— added maxFeeBps and maxFeeThreshold to MerchantSettings and UpdateMerchantSettingsBodyservices/settlement-engine/src/index.ts— pass fee config to computeSettlementAmounts, log when cap is applied
Fee logic:
- When grossAmount > maxFeeThreshold, compute cappedFee = grossAmount * maxFeeBps / 10000
- Final fee = min(uncappedFee, cappedFee)
- FeeAuditSnapshot includes capApplied flag and uncappedFee when cap is triggered
Test coverage: ✅ Gross $100K, feeBps 1000, maxFeeBps 200, threshold $10K — fee = $2K (capped) ✅ Gross $5K, below threshold — uncapped fee $500 ✅ No maxFee — uncapped behavior ✅ Fee never exceeds cap when active (property test compatible)
Acceptance criteria met: ✅ Settlements have configurable max fee caps ✅ Merchants without caps use standard fee calculation
Status: FIXED
Current behavior: Failed settlements stay failed permanently.
Expected behavior: Add supersededById to Settlement. Add POST /api/settlements/:id/retry that clones the settlement (same amounts, asset, merchant). Original is marked superseded. Max 3 retries per chain. Listings exclude superseded by default.
Implementation:
Files modified:
prisma/schema.prisma— added supersededById field with self-referencing relationprisma/migrations/20260728111000_add_settlement_retry/migration.sql— migration for retry mechanismservices/settlement-engine/src/index.ts— added POST /api/settlements/:id/retry route, updated listing to exclude superseded by defaultservices/settlement-engine/src/settlement-retry.test.ts— comprehensive test suite
Retry logic:
- Only failed settlements can be retried
- Creates new settlement with same amounts, asset, and merchant
- Original settlement linked via supersededById
- Enforces max 3 retries per chain
- New settlement queued for processing
Test coverage: ✅ Retry failed settlement — new settlement created with same amounts ✅ Retry completed — 422 error ✅ Retry 4 times — 4th attempt returns 422 ✅ Superseded settlements hidden from listing by default
Acceptance criteria met: ✅ Failed settlements can be retried up to 3 times ✅ Original settlement linked via supersededById ✅ Superseded settlements excluded from default listings