P2P Trading 6.3.3
1 September 2026
This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.
P2P Trading v6.3.3
Release Date: September 1, 2026 Tags: OFFERS, PRICING, LIMITS, TRADE ROOM, COUNTDOWN, DISPUTES
Overview
A pricing release: an offer can now be priced in any currency you have enabled — a national currency, a spot exchange currency, or an ecosystem token — where the composer's Priced in list offered national currencies and nothing else. An install with no enabled national currency, which is the normal state of a platform running no FX-rate provider, had an empty list and could not publish an offer at all.
The decision to make before updating is your per-trade bounds. Minimum and Maximum Trade Amount are still typed in US dollars, and they now convert into a crypto price currency too — through the exchange listing for a spot currency, through the ecosystem market for a token. While either bound was set, a crypto-priced offer was refused outright; from this release it publishes and your dollar bound is enforced on it. Money in the trade room also changes shape, including on trades already open. Read Upgrade Notes first.
It also corrects what the platform says about the payment window: the deadline stopped ending trades several releases ago — a late buyer can still pay, and zero unlocks the seller's cancel — but Learn, the composer and the offer page never caught up.
Nothing new is needed from the core platform. Requires Core v6.7.2.
Update Instructions
pnpm updatorRestart the backend and ship the frontend build. There is no database change, no migration and no new setting.
Upgrade Notes
Your dollar bounds now govern crypto-priced offers instead of refusing them
Minimum Trade Amount and Maximum Trade Amount are dollars, and the platform converts them into each offer's own currency before comparing. The conversion could only read the national-currency table, so while either bound was set an offer priced in a coin was refused — and the refusal told the maker to have an administrator set "the USDT rate under Finance → Currencies", a page on which USDT cannot appear. The conversion now reads the exchange listing's price for a spot currency and the ecosystem market for a token, so those offers publish and your bound applies to them.
- Changed the per-trade bounds to convert through crypto as well as national currencies. A 10 / 100,000 range sizes a BTC-priced offer at 0.00016667 to 1.6666 BTC with BTC at $60,000 — floor rounded up, ceiling rounded down, so the figure the platform quotes is always one it accepts.
- Check the two figures before you update. The settings page fills in 10 and 100000, and an install carrying those accepted no crypto-priced offer at all until now. From this release it accepts them from about ten to about a hundred thousand dollars' worth, converted at the coin's price. Clearing both bounds removes the conversion, and the refusal, entirely.
- USDT counts as one dollar while the exchange listing carries no price for it. The price job writes that 1 itself, but only inside a tick it skips whenever no exchange market is enabled, so without the assumption a fresh install would refuse a stablecoin for having no price. Every other unpriced coin is refused until the job's next run, roughly two minutes.
- The price behind a bound on a spot currency is the exchange listing's own price column — up to two minutes old, and quoted in USDT rather than dollars. That is deliberate: it sizes a floor and a ceiling on trade size, and it is never what a trade settles at. No settlement figure moved.
A code you list as a coin and as a currency is priced as whichever one you have switched on
Ten of the codes a typical exchange listing carries are also national currency codes — BOB, BTN, CAD, EGP, ERN, GEL, MNT, RON, SCR and SOS. RON is the Romanian leu and it is also Ronin. Now that a price currency can be a coin, each of those is a code two tables answer for, and the platform has to pick one. It picks by your switch. A national currency you have enabled wins the code outright and ends the lookup there, whether or not its row carries a rate. A row that is switched off is not a currency this platform runs, so the code is priced as the coin instead.
That second half carries more weight than it looks. The currency table arrives seeded with a row for every ISO code and almost all of them arrive switched off — on a stock install, 139 rows out of 161. Treating an untouched seed row as a national currency would make all ten codes above unpublishable as coins on every install whose exchange lists them, and would answer a maker who picked RON - Ronin out of the crypto group by telling them to switch on a national currency they had never chosen.
- Changed the refusal to name what is actually missing, because the fix is a different person's job in each case: a rate for an administrator to type in, a market with no price yet, a code the platform does not know at all, or — only once nothing else has managed to price the code — a currency switched off that has to be switched back on.
- Check for live offers before you switch off one of those ten codes. A currency you disable stops being that currency for the per-trade bounds, so offers your makers published in leu are sized against Ronin's market price from then on, and nothing announces it. The offer row stores the code and not which table the maker meant, so the two cannot be told apart after the fact; recording the wallet type on the offer at publish time is a follow-up, not something this release does.
- A maker cannot quote the coin behind such a code while you have the national currency enabled — Priced in offers that code once, as the currency.
Money in the trade room is printed the way the rest of P2P prints it
The trade room's headline amount and the payment instructions formatted the figure with the browser's own currency formatter at two decimals. That formatter does not refuse a code it has never heard of, it formats it, which is how a crypto-priced trade came to headline Send BTC 0.00. Repairing that means every amount is now printed by the same rule as every other figure in P2P — including on trades already open in a national currency. The amount is unchanged; the symbol in front becomes the code behind, so Send €1,234.00 now reads Send 1,234.00 EUR, and the digit grouping no longer follows each reader's own locale.
- Tell your support desk before a trader asks. Nothing about an open trade changed except how its amount is written.
What your desk has been telling traders about the clock
The payment window has not ended a trade for several releases. Past zero the buyer keeps the confirm button and can still pay; what the deadline does is let the seller cancel on demand, and a trade neither side touches is swept about a day later. Every screen that explained the clock still described the old behaviour, so anyone who learned it from the product learned it wrong — your support desk included, and any help material you wrote from Learn.
- Fixed the countdown section of Learn, the payment-window field in the offer composer and edit screen, and the note under Time to pay on an offer page. All three said the trade cancels itself when the window closes.
- Re-read any help material you wrote from Learn, and give your desk the two answers that changed: a buyer whose transfer is late should confirm rather than cancel, and a seller who wants out after the window cancels it themselves rather than waiting for the platform to.
- Six languages lost a translation here. Arabic, Assamese, Bulgarian, Bengali, Greek and Persian carried real translations of sentences whose meaning has since inverted, and they now hold the new English instead. That is deliberate — a fluent statement of the old behaviour is a worse answer than an untranslated statement of the true one — but retranslate them if you maintain your own catalogues.
The admin rate limits on P2P had never applied, and now do
Ten admin P2P routes declare a per-admin rate limit — 50 offer actions an hour across approve, reject, pause, activate, disable, flag and edit; 100 trade actions across cancel and resolve; 100 dispute actions — and not one of them ran. The router resolved a route's middleware by looking each entry up in the rate-limiter table by name. These ten do not pass a name: they import the limiter and pass it directly, which is how each one reads as protected. The lookup found nothing, the request was logged as unknown middleware, and it was served with no limit at all — for as long as those routes have existed.
The boot-time check that shouts when a route names a limiter that does not exist could not see it either. It skipped every non-string on the reasoning that a direct reference was already proved right by the compiler, which was exactly backwards, and the same skip hid the worse case of an import that resolves to nothing.
- Fixed the router ignoring a limiter passed directly rather than by name. It now calls either spelling, so all ten limits apply from this release.
- Expect throttling on bulk moderation for the first time. If you clear stale boards in large batches, or drive these screens from a script, the work will now stop at fifty offer actions in an hour where it used to run straight through. Spread it, or do it against the database with the escrow consequences in mind. Nothing else changes: the buckets are per admin, and the figures above are the ones that were always declared.
- The three
noteendpoints declare no limiter at all and are unaffected, as is reading a case.
Added
An offer can be priced in any currency the platform has enabled
The Priced in list was the fiat deposit list, which is why an install with no enabled national currency could not publish anything: the box read "No currencies available." and the form could not be completed, on a platform whose makers would have priced everything in a stablecoin anyway. The list is now built from what the platform can actually price — national currencies first and alphabetically, then cryptocurrencies with USDT and USDC at the head — under two group headings in the box. Each group appears only where the platform can hold that kind of money: national currencies while fiat wallets are on, the exchange's currencies while spot wallets are on and an exchange is connected, ecosystem tokens where the Ecosystem addon is installed and switched on.
- Added spot exchange currencies and ecosystem tokens to the currencies an offer can be priced in, in the composer and on the server.
- Added the market rate for a crypto price currency, in the composer and on the edit screen. Both asked for every price currency as a national one, so a MARGIN price on a coin-priced offer was a percentage of nothing.
- Added a publish-time refusal for a price currency the platform cannot
price: 400, "
<code>is not a currency this platform can price an offer in." It runs on publish and on an edit that changes the currency — never on a draft save, and never on an edit that leaves the currency alone, so disabling a currency does not brick edits of the offers already quoted in it. - Added
GET /api/p2p/offer/price-currencies, which answers that list with the wallet type each code lives in, andpriceCurrencyWalletTypeonGET /api/p2p/offer/{id}for the same reason: a client cannot ask for a rate without knowing which listing the code belongs to.
Changed
What a refused offer is told to do about it
One refusal covered every way a rate could be missing, and it always gave the same advice — set the rate under Finance → Currencies. That advice is right for exactly one of the four.
- Changed the refusal to name the fix: a rate to set for an enabled national currency that has none, "no market price right now" for a coin the price job has not reached or a token with no ecosystem market, and "not a currency this platform knows" for a code in none of the three listings — except where a switched-off national currency row exists for that code, which is named as a switch to turn back on rather than as an unknown code. That last one is a message and never a decision: it is reached only after every listing has failed to price the code, so it can never take a listed coin away from its own market.
- Changed the warning the backend logs beside it to say which of the four it was, so a log line tells you whether a missing rate is an administrator's job or a market's.
The handbook and the help pages said the old thing too
Correcting the product's own guidance is only half of it: these pages are what your support desk answers from, and a stale page is not slightly out of date — it is a wrong answer waiting to be quoted with a citation. So all 32 P2P documentation pages were read against the code in the same pass.
- Changed seventy-one statements across 22 pages. Most were the payment window described as ending, cancelling or expiring a trade at the deadline, and a seller described as unable to cancel or as having to wait for the platform. The rest were older drift: escrow described as locking when a trade opens rather than when a sell offer is published, a post-completion dispute window that cannot be reached, "nobody can cancel a paid trade" on pages written for operators who can, a completion rate described as counting every trade that ended regardless of who caused it, and release time called a median when it is the mean of the last ten.
- Added the two routes that were documented nowhere: a trader withdrawing their own dispute, and the trade chat closing when a trade reaches a terminal status.
- Nine further candidates were checked against the code and thrown out as already correct. Seven of the 22 pages are operator pages rather than customer help, and some of what they picked up has nothing to do with this release — it is here because the same read found it, not because the code moved.
What the platform says about the payment window
The window is set per offer, and the composer called it an auto-cancel because that is what it used to be. It is not one now: past zero the buyer may still pay, the seller may cancel on demand, and a trade neither side touches is swept about a day later. The wording is brought into line everywhere it is stated.
- Changed the countdown section of Learn — the three state tiles, the timeline figure and the sentence a screen reader is given for it, the escrow section's middle exit, and the warning aimed at a buyer who paid late. The tile at 00:00 read "The window expired. The buyer can no longer confirm payment on this trade." It now says the window has passed, that you can still pay and still confirm, and that the seller may cancel from here.
- Changed the offer composer and edit screen's field from Cancel unpaid trades after to Payment window, with a hint saying what the maker actually gets: after it they can cancel whenever they like, the taker can still pay until they do, and the funds come back to the offer.
- Changed the note under Time to pay on an offer page, which told a taker "Miss it and the trade cancels itself."
- Added to Learn what the change created rather than what it removed: that zero is the seller's door and not the buyer's wall, that an untouched trade is swept about a day later and cannot be disputed once it has been, that a cancellation counts against whoever pressed it rather than against both records, and that a trade left sitting on a confirmed payment is disputed for the buyer automatically after a day.
Fixed
Amounts in a cryptocurrency printed as 0.00
Three surfaces printed money with the browser's currency formatter at two decimals, and it does not refuse a three-letter code it has never heard of — it formats it. Underneath, P2P decided whether a code was a national currency by checking it against a list of the coins somebody had thought of, which meant every coin nobody had thought of counted as a national currency: ZRX, ZIL, FTT, HOT and REP among them, printed at two decimals everywhere P2P prints money, which renders a coin worth a tenth of a cent as 0.00.
- Fixed the trade room headline and the payment instructions printing a crypto-priced trade's amount as BTC 0.00 — the one row a buyer reads to know what to send.
- Fixed the offer page quoting a 0.001 BTC minimum as 0 BTC: a refusal naming a bound that does not exist.
- Fixed the classification under all of them. The question asked is now "is this code on the frozen list of 167 national currencies?" rather than "is this one of the coins we remembered to write down" — a closed list that changes a few times a decade instead of an open one that changes every time you enable a token. The 167 are the currency codes the browser's own internationalisation data carries, plus five this platform seeds that the data leaves out: CLF and XTS, which ISO 4217 does list, and GGP, IMP and JEP, which it does not — the Guernsey, Manx and Jersey pounds are sterling issues ISO files under GBP rather than coding separately. All five are rows an operator can switch on here, and every one of them was printing as a ticker.
A restored draft kept its old figures under a new currency
The composer brings back an unfinished offer when a maker returns to it. If the currency it was priced in was no longer offered — you disabled it in the meantime — the form moved the draft onto another currency and kept every number typed in the old one, so a price of 150,000,000 naira came back as 150,000,000 USDT under nothing but the usual "draft restored" banner.
- Fixed the silent re-pricing. The draft is still moved, because there is nothing else to price it in, but the price and the per-trade limits are cleared rather than relabelled, and the Priced in field names the currency the draft was moved from and says the figures were cleared.
Three things Learn stated that were not true
Nothing to do with the clock. Each was checked against the code rather than against the comment describing it, which is where two of them came from.
- Fixed the claim that a trade which already completed can be disputed. It cannot — completion is terminal and the request is refused. The page offered that route to a seller who released and then found the payment reversed, which is the worst possible moment to discover it does not exist.
- Fixed release time described as a median. It is the mean of that person's last ten completed trades on that side, and the page now says both parts: "the middle of what they have done" invites a reader to discount an outlier that the figure has in fact absorbed, and neither the averaging nor the ten-trade window was stated at all.
- Fixed the trade count described as "trades that reached an end, whichever end." Ends are attributed to the party answerable for them: a cancellation counts only against whoever cancelled it, an expiry only against the buyer. The old wording described a figure a stranger could walk down by opening trades against you and dropping them — which is the exact thing the attribution rule exists to prevent.
The trade room offered a dispute on a settled trade, then refused it
Writing that correction into Learn turned up the button behind it. On a completed trade the room drew Something's wrong, took the customer through picking a reason and writing a description, and answered with an error. Completion is terminal and always has been — the platform has never accepted a dispute on a settled trade, and the seven-day post-completion window sitting in the code behind that check is unreachable, which is why nobody noticed the door was shut. The button was the only thing saying otherwise, and it said it to somebody who had just decided they had been defrauded.
- Fixed the trade room offering a dispute on a completed trade. It is offered once payment has been confirmed and nowhere else, which is what the platform has enforced all along.
- A seller who releases and then finds the payment reversed has no dispute route. That is the existing rule and not a new restriction; it is simply now what the button and the guidance both say. Support is the route.
The seller's cancel refusal sent them somewhere that refuses them too
A seller who tries to cancel a trade while its payment window is still running is
told they cannot — correctly, because they advertised that window — and was then
told to pause their offer instead. Pausing does not work. It is a status change
through the same route that edits an offer, and that route refuses any edit
with a 422 while a trade against the offer is PENDING, PAYMENT_SENT or
DISPUTED. So the remedy produced a second refusal, naming a field the seller
had not touched, and left them with no way out and no explanation.
- Fixed both refusals — the one for a window still running, and the one for an offer whose window never expires — to say what actually helps. Inside the window the buyer is the only party who can end the trade, so the message points at the trade chat, and says plainly that pausing will not release it either.
Four more, in the escrow and dispute sections
Found the same way, and none of them to do with the clock either.
- Fixed the page saying a seller's coins move into escrow when a trade opens. On a sell offer they move when the offer is published — the whole advertised amount at once — and each trade takes its share from that pool. Only a buy offer works the way the page described. The countdown figure's opening label repeated it and now marks the start of the buyer's window instead, which is what that point on the line actually is.
- Fixed "There is no fourth exit." There is one: an administrator can resolve or cancel a trade from the admin side and settle the escrow with it, including on a trade nobody disputed. The page went further and promised that "no one at this platform can quietly move escrow somewhere else." It now names the exit and says it is recorded against the administrator who used it.
- Fixed the dispute tile saying escrow stays frozen "until a person on our team has read the whole trade." Whoever opened a dispute can withdraw it and unfreeze the trade themselves, right up until a handler picks the case up — the fast way out when the payment lands mid-argument, and the page did not mention it existed. Learn now carries it as its own entry.
- Removed two claims with nothing behind them: that a dispute is answered in 24 to 48 hours, which is not a service level this platform sets anywhere, and that a groundless dispute "goes on your record", which nothing counts. The second was a deterrent rather than a fact, and it is replaced by what a groundless dispute really costs — a handler's time and a counterparty's escrow, frozen while it is read.