Issues found in the Node/Express API, services, and jobs. Add new items below as numbered list entries.
- API key validation broken – src/middleware/auth.ts:
validateApiKeyuseskeyHash: await hashApiKey(apiKey)for lookup.hashApiKeycallsbcrypt.hash(), which produces a different hash each time (random salt). Stored hashes will never match. Usebcrypt.compare(plaintextApiKey, storedKeyHash)instead of hashing the input for lookup. - Wrong import in usdcConvertAndMintJob – src/jobs/usdcConvertAndMintJob.ts: Imports
dbfrom../config/database, but that module exportsprisma. The code usesprisma, which is never imported, causing aReferenceErrorat runtime. - IDOR / auth bypass in savings controller – src/controllers/savingsController.ts:
useris taken fromreq.bodyorreq.querywithout checking it against the authenticated user. Any authenticated user can operate on another user's savings by supplying a differentuserID. - Env validation too late – src/config/env.ts:
requiredEnvVarsis checked afterconfigis built. IfDATABASE_URLis missing, the app may still start and fail later. Validation should run before any use ofconfig.
- Infinite retries for failed webhooks – src/services/webhook/webhookService.ts: When
status === 'failed',deliverWebhookstill attempts delivery and returnsfalse, so the consumer nacks and requeues indefinitely. Add early return for failed status. - Organization users never notified for investment withdrawal – src/jobs/investmentWithdrawalJob.ts:
publishInvestmentWithdrawalReadyonly fires whenr.userIdis set. For organization requests (r.organizationIdset,r.userIdnull), no notification is sent. - Burn limits bypass when
audienceis unset – src/controllers/burnController.ts:checkWithdrawalLimitsandisCurrencyWithdrawalPausedrun only whenreq.audienceis set./burn/acbuviaburnRoutesdoes not setaudience, so limits and circuit breakers are skipped. - Mint limits bypass when
audienceis unset – src/controllers/mintController.ts:isMintingPausedandcheckDepositLimitsrun only whenreq.audienceis set./mint/usdcand/mint/depositviamintRoutesdo not setaudience, so limits are bypassed. - Fee thresholds likely wrong – src/services/feePolicy/feePolicyService.ts:
getBurnFeeBpsusespctOfTarget < 15andpctOfTarget > 21. WithpctOfTarget = (actualWeight / targetWeight) * 100, these look like typos (should be 85/115). Logic is unlikely to behave as intended. - Decimal vs string for
acbuAmountin transfer – src/services/transfer/transferService.ts:acbuAmountis passed as a string toprisma.transaction.create. Prisma expectsDecimalfor that field; this can cause type or precision issues. - No dead-letter queues – src/config/rabbitmq.ts: Queues are declared without
deadLetterExchangeordeadLetterRoutingKey. Messages that repeatedly fail are requeued forever instead of being moved to a DLQ. - No max retry limit for webhook consumer – src/jobs/webhookConsumer.ts: Failed webhooks are nacked with requeue indefinitely. There is no per-message retry cap.
- Wallet activation uses XLM, not Pi – src/services/wallet/walletActivationService.ts: Sends XLM to activate Stellar accounts; comment says "using pi instead of xlm" is desired. Switch to Pi when the Pi bridge/chain is in use.
- Remove placeholder Stellar address in auth – src/services/auth/authService.ts: Uses
getPlaceholderStellarAddress()on signup to satisfy schema before the real wallet exists. Replace with a proper flow. - Transfer service: verify alias-based flow – Transfer supports sending by username/phone (via recipientResolver); confirm the current resolve-to-Stellar flow is correct, secure, and documented.
- Contract client and Stellar fees hardcoded – src/services/stellar/contractClient.ts defaults fee to
'100'; walletActivationService.ts and transferService.ts also use hardcoded'100'. Make fees configurable or network-derived. - Multiple fintech providers per nation – Only Flutterwave is wired in webhookRoutes; add support for multiple providers per country (e.g. Nigeria: Paystack and Flutterwave) to avoid single point of failure.
- KYC extraction placeholder – src/services/kyc/machineLayer.ts: Uses a placeholder for "real impl would call Vision API and redact"; replace with actual implementation.
- Basket weight script missing – Create a backend/tooling script or job that computes and reports current basket weights based on market conditions and factors considered.
- Balance endpoint for frontend missing – Frontend shows balance as placeholder "—" everywhere; add or expose GET /users/me/balance so the UI can display real balances.
- No dependency health checks – /health/metrics does not verify PostgreSQL, MongoDB, or RabbitMQ. A "healthy" response can be returned even when core dependencies are down.
- Race condition in API key rate limiting – src/middleware/rateLimiter.ts: The get-then-set flow for rate limit counters is not atomic. Concurrent requests can both pass the limit before either updates the cache.
- ReDoS risk in
deletePattern– src/utils/cache.ts:new RegExp(pattern)with user-controlled or unsanitized input can lead to ReDoS. Patterns should be validated or escaped. - Webhook verification skipped when secret missing – src/controllers/webhookController.ts: If
FLUTTERWAVE_WEBHOOK_SECRETis unset,verifyFlutterwaveSignaturecallsnext()and skips verification, allowing forged webhooks. - Placeholder Stellar address format invalid – src/services/auth/authService.ts:
getPlaceholderStellarAddress()returns'P' + ...(56 chars). Stellar addresses start withG. This may violate schema or downstream validation. - ACBU supply from DB, not chain – src/services/reserve/ReserveTracker.ts:
getTotalAcbuSupplyuses transaction aggregates instead of on-chain supply. This can diverge from the real supply if there are off-chain mints/burns. - Org-scoped limits may not apply – src/services/limits/limitsService.ts: For org-level API keys (
userIdnull), transactions created withuserId: nullhave no user relation, so they are excluded from limit aggregates. - USDC to XLM conversion is a no-op – src/jobs/usdcConvertAndMintJob.ts:
convertUsdcToXlmis empty. USDC is never converted; the job still mints ACBU as if conversion succeeded. - Hardcoded XLM rate – src/jobs/xlmToAcbuJob.ts:
XLM_USD_RATEdefaults to0.2from env. A wrong or stale rate can cause incorrect ACBU mint amounts. - Deposit limits use wrong USD equivalent – src/controllers/mintController.ts:
amountUsdPlaceholder = amountNumtreats local currency amount as USD for limit checks. Limits should use a proper USD conversion. - Incomplete treasury aggregation – src/controllers/governmentController.ts: TODO: aggregate from transactions for org/user and reserve exposure.
byCurrencyis always[]. - Audit failures are silent – src/services/audit/auditService.ts: Audit write failures are only logged. There is no retry, alerting, or fallback, so audit data can be lost without visibility.
anycast for permissions – src/middleware/auth.ts:permissions: permissions as anybypasses type safety. Use a proper type.anyusage in segmentGuard – src/middleware/segmentGuard.ts:(req as any).userTierandtier as anyweaken type safety.anyusage in Stellar client – src/services/stellar/client.ts:submitTransaction(transaction: any)and(b: any)in balance lookups.anycast in burning service – src/services/contracts/acbuBurning.service.ts:localAmounts as any[]bypasses type checking.- No pagination on transfers – src/controllers/transferController.ts:
getTransfersusestake: 50with noskipor cursor. No way to page through older transfers. - No pagination on investment withdrawals – src/controllers/investmentController.ts:
getInvestmentWithdrawRequestsusestake: 50with no pagination parameters. limitquery param not validated – src/controllers/governmentController.ts:Number(req.query.limit)can beNaN;Math.max(1, NaN)isNaN.- No input validation in savings controller – src/controllers/savingsController.ts:
amount,term_seconds, anduserare not validated with Zod or similar. Invalid values can reach the service layer. - Missing composite index – prisma/schema.prisma:
OnRampSwapis often queried by(status, createdAt). A composite index would help those queries. - Webhook JSON parse failure returns empty body – src/index.ts: If
raw.toString()is invalid JSON,bodyis set to{}. The webhook handler may run with incomplete data instead of returning 400. - Stack traces in error logs – src/errorHandler.ts:
logger.error('Unexpected error', { error: err, ... })can log full error objects. Ensure logs are not exposed to clients and that PII is not included.
(prisma as any).onRampSwapcasts – src/jobs/usdcConvertAndMintJob.ts, xlmToAcbuJob.ts, mintController.ts, onrampController.ts: Prisma client is cast toanyto accessonRampSwap. Runnpx prisma generateand fix the schema/client soonRampSwapis properly typed.- Hardcoded limit values – src/config/limits.ts: Deposit/withdrawal limits are hardcoded. Consider loading from env or config for easier tuning.
- Fallback email placeholder – src/services/notification/notificationService.ts:
noreply@acbu.example.comis a placeholder and may not be valid in production. - Yield accounting stub – src/services/investment/yieldAccountingService.ts: Placeholder only; yield accounting is not implemented.
- No automated tests – No
*.test.tsor*.spec.tsfiles found. Critical paths (auth, transfers, limits, webhooks) lack automated tests.