Skip to content

[BUG] Race condition in crypto buy/sell allows double-spend #7

Description

@derbydaniels9-cpu

Summary

In src/controllers/crypto.controller.ts, the balance check and wallet update are two separate DB operations with no atomicity:

const wallet = await prisma.wallet.findUnique(...);    // read
if (wallet.ngnBalance < totalDebit) { return 400; }    // check
await prisma.wallet.update({ data: { ngnBalance: wallet.ngnBalance - totalDebit } }); // write

Impact

Two concurrent POST /crypto/buy requests with the same token both pass the balance check before either write commits — effectively double-spending the same funds.

Steps to reproduce

  1. Fund a wallet with N5,000.
  2. Fire two simultaneous buy requests each for N4,000.
  3. Both pass the ngnBalance < 4000 check and both writes succeed — balance goes negative.

Proposed fix

Use a conditional updateMany that asserts the balance at write time:

const updated = await prisma.wallet.updateMany({
  where: { userId: req.userId!, ngnBalance: { gte: totalDebit } },
  data: { ngnBalance: { decrement: totalDebit } },
});
if (updated.count === 0) {
  return res.status(400).json({ success: false, message: 'Insufficient balance' });
}

Same fix needed in:

  • src/controllers/wallet.controller.tssendPayment
  • src/controllers/utility.controller.tsbuyAirtime, buyData, payElectricity, payCableTv
  • src/controllers/savings.controller.tsdepositToBox

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions