P2P Trading 6.1.0
5 March 2026
CRITICAL FIXESFINANCIAL SAFETYRACE CONDITIONSESCROWSTATE MACHINEFRONTEND
P2P v6.1.0
Release Date: March 5, 2026 Tags: CRITICAL FIXES, FINANCIAL SAFETY, RACE CONDITIONS, ESCROW, STATE MACHINE, FRONTEND
Overview
Version 6.1.0 is a comprehensive security and stability release for the P2P trading system.
Key themes: eliminating race conditions and double-spending vectors, hardening the escrow fund flow, fixing the trade state machine, securing WebSocket access, and improving frontend reliability.
Update Instructions
After updating, run the following command in terminal:
pnpm updatorAdded
New operation types
- Added
P2P_OFFER_INCREASE, which records the additional funds locked when a sell offer's total is increased. - Added
P2P_OFFER_DECREASE, which records the funds released when a sell offer's total is decreased.
Fixed
Race Condition: Trade Initiation Double-Spending
- Fixed two concurrent requests being able to double-drain an offer's available amount. Trade initiation now runs at the strictest transaction isolation level rather than the database default.
- Fixed duplicate trade creation across multiple server instances, by taking a distributed lock per offer while a trade is being opened.
Race Condition: Concurrent Cancel and Release
- Fixed cancel, confirm and release being able to run at the same time on one trade. Cancel took no lock while release did, which allowed a double disbursement; all three now share a single lock per trade, so only one trade action can execute at a time.
- Fixed cancellation running at the default isolation level. It now uses the strictest isolation with row-level locking.
Trade Amount Input Validation
- Fixed trade initiation accepting an amount that is not a finite positive number. Such a value passed the bounds checks — nothing is ever below a minimum when it is not a number — and permanently corrupted the offer's remaining total.
State Machine Inconsistency
- Fixed the trade validation rules still permitting
PAYMENT_SENTto move toCANCELLED, contradicting the cancel action's own refusal of exactly that. A trade atPAYMENT_SENTcan now only move toCOMPLETEDorDISPUTED.
Redis Failure on Release Bypasses Idempotency
- Fixed the release action proceeding without duplicate protection when Redis was unavailable. It now refuses immediately with a 503 rather than running a critical financial operation unprotected.
The seller's balance was checked against stale data
- Fixed the seller's wallet being read outside the trade's own transaction, so the locked-funds check ran against a stale snapshot. It is now read inside the transaction with a row lock, against current data.
Trades where the buyer had declared payment were being auto-expired
- Fixed the timeout job expiring
PAYMENT_SENTtrades — trades where the buyer claims to have sent real money. OnlyPENDINGtrades are auto-expired now; a trade stale for 24 hours or more is escalated toDISPUTEDinstead.
WebSocket Admin Privilege Escalation
- Fixed the live trade connection accepting an admin flag from the client, which let any signed-in user watch arbitrary trades. Admin status is now always determined on the server from the account's own role and permissions.
Trade reviews
- Fixed reviews not being checked against an existing submission from the same reviewer on the same trade.
- Fixed reviews being accepted on trades that were not
COMPLETED. - Fixed the review being recorded against the seller rather than against the counterparty.
- Fixed the rating not being saved at all.
Duplicate Dispute Resolution
- Fixed the admin resolve and dispute actions keying their duplicate protection on the current timestamp, which defeated it entirely. They now use a stable key per trade and outcome, so a retry cannot pay out twice.
- Fixed dispute resolution not re-reading and locking the trade row inside its own transaction.
Dispute resolution ignored whether the offer was a buy or a sell
- Fixed dispute resolution not branching on offer type for a seller-wins outcome. Funds on a sell offer now stay locked against the offer; funds on a buy offer are released back to the seller's balance.
A sell offer with insufficient locked funds could still be traded
- Fixed an insufficient locked balance on a sell offer only being logged as a warning, so a trade was created that could never complete. It now fails the trade initiation outright.
User Eligibility Requirements Enforced
- Fixed an offer's user requirements — minimum completed trades, minimum success rate, minimum account age and trusted-only — not being checked when a trade was opened against it. All four now apply.
Ecosystem chain balances could fall out of step on release
- Fixed an ecosystem chain transfer failing silently during release, so the release completed with inconsistent chain balances. The transfer is now part of the same write, and a failure rolls the entire release back.
Fund Leak Prevention
- Fixed the platform fee being deducted from the buyer but never collected when no Super Admin account existed. Collection no longer depends on one.
- Fixed missing ecosystem wallet data silently skipping chain balance synchronisation, which left the buyer unable to withdraw. It now raises an error instead of continuing.
- Fixed changing an active sell offer's total not adjusting the funds held against it, which permanently locked the excess. The hold is now increased or released to match.
Confirm Payment Hardened
- Fixed confirm payment running without the transaction isolation, distributed lock and row-level locking that cancel and release already used.
Reputation
- Fixed reputation scores being computed and then discarded rather than written to the activity log.
- Fixed dispute penalties counting disputes a user had legitimately filed. Only disputes where the user was the defendant and found at fault now count against them.
Admin actions
- Fixed admin cancellation swallowing wallet release errors, so a trade could be marked cancelled with its funds still locked. The transaction now rolls back instead.
- Fixed offer approval not running inside a transaction with row locking.
- Fixed commission records being assigned to the resolving administrator rather than to the Super Admin system account.
WebSocket Stability
- Fixed a malformed live-connection message crashing the handler. It is now caught and logged.
- Fixed disconnected clients not being cleaned up from the subscription map, which leaked memory.
Backend
- Fixed offer updates not checking that attached payment methods belong to the requesting account, which allowed another account's payment method to be attached.
- Fixed the escrow fee calculation silently returning zero on a database error — an invisible revenue leak. It now fails instead.
- Fixed a
SPLITdispute resolution paying out identically to a buyer-wins ruling. It now divides the funds 50/50 between buyer and seller. - Fixed admin audit log entries being left behind when the action they belong to rolled back. They are now written inside the same transaction.
- Fixed admin reject and disable silently skipping a fund release when the locked balance was insufficient. It is now logged as a warning.
- Fixed a dispute filed on an already-completed trade giving no indication that the funds were already released. It now states that the case will be handled manually by an administrator.
- Fixed the offer expiry job depending on MySQL-specific queries. It now filters in a way that works on any supported database.
Frontend
- Fixed trade status comparisons being case-sensitive against the stored values, which is why the rating section never appeared. All comparisons now use the stored uppercase form.
- Fixed the trade action buttons — release, cancel, confirm and dispute — being able to submit twice on a double click.
- Fixed the dispute button being offered during
PENDING, when a trade cannot be disputed. It now appears only atPAYMENT_SENTorCOMPLETED. - Fixed the escrow progress bar showing a fixed 75% regardless of the trade. It now reflects the real status: 25%, then 50%, then 100%.
- Fixed image links in trade chat not being validated. Only
http:,https:anddata:image/links are accepted now, blocking script links and tracking pixels. - Fixed a slow response arriving after a newer one and overwriting it. Stale responses are now discarded.
- Fixed guided matching crashing when no matches came back.
- Fixed three guided-matching requests running one after another. They now run together, cutting load time by roughly three times.
- Fixed hardcoded English text in the escrow panel, which is now translated like the rest of the page.
- Fixed the trade countdown timer not clearing itself on expiry, and not telling the page it had expired.
Trade Expiry Real-Time Updates
- Fixed an expiring trade not being broadcast to either party, so buyer and seller waited for a page refresh to see the status change. Both now see it instantly.
- Fixed the page not re-reading the trade when its own countdown reached zero, so the change was not shown until a manual refresh.
- Changed the trade timeout job from every five minutes to every minute, cutting the maximum expiry delay from five minutes to one.
- Changed the minimum timeout cutoff from ten minutes to five, matching the shortest auto-cancel window a seller is allowed to set on an offer.
Auto-Dispute Notifications
- Fixed a trade auto-escalated to
DISPUTEDafter 24 hours not being broadcast, so neither party saw the status change in real time. - Fixed auto-disputed trades sending no notifications at all. Buyer and seller now receive in-app, email and push notifications.
Auto-Cancel Timer Data Integrity
- Fixed the live trade connection passing an offer's trade settings through as raw text, so the auto-cancel timer received an unusable value.
- Fixed offer updates being able to double-encode the same settings when merging them, which could corrupt the auto-cancel value.
Smaller corrections
- Fixed buyer affiliate rewards being calculated on the gross trade amount rather than on the net amount after the platform fee.
- Fixed offer creation not checking that the minimum and maximum are no larger than the total.
- Fixed offer creation reporting every failure as a 500, discarding the real status code.
- Fixed trade expiry writing an activity log entry for the seller only. Both parties now get one.
- Fixed reputation milestones only firing on an exact trade count, so a milestone was skipped whenever the count jumped past it. They now fire on reaching or passing the threshold.
- Changed offer creation, confirm and dispute to log through the structured logger rather than the console.