Moderating offers and trades

The P2P admin screens — the offer approval queue, flagging, pausing and disabling, the trade list, cancelling and force-resolving a trade, and the activity log that records all of it.

6 min readUpdated 6 August 2026admin, offers, trades, moderation

Six screens live under /admin/p2p. This page covers four of them — Overview, Offers, Trades and Activity Log. Disputes has its own page, and so do Settings.

Every screen is gated on its own permission key, so a role that can reach the admin panel still sees nothing here until you grant them.

Screen Path Read Write
Overview /admin/p2p access.p2p
Trades /admin/p2p/trade view.p2p.trade edit.p2p.trade
Offers /admin/p2p/offer view.p2p.offer edit.p2p.offer
Disputes /admin/p2p/dispute view.p2p.dispute edit.p2p.dispute
Activity Log /admin/p2p/activity view.p2p.activity
Payment Methods /admin/p2p/payment-method view.p2p.payment_method create / edit / delete.p2p.payment_method
Payment Rails /admin/p2p/payment-rail view.p2p.payment_method create / edit / delete.p2p.payment_method
Method setup /admin/p2p/payment-method/setup view.p2p.payment_method create.p2p.payment_method

The last two are new in 6.2.5 and carry the shared half of a payment method — see Payment methods. No role holds their permissions at install, so the setup console refuses everyone except a Super Admin until you grant them in Admin → Roles.

It is the permission behind force-resolving a trade, which credits a buyer or refunds a seller directly. Treat it like a withdrawal-approval permission, not like a moderation one.

Overview

Totals, growth, distributions and a recent-activity panel drawn from the P2P activity log. It is a health read, not a work queue — the two queues that need daily attention are the offer approval list and the dispute list.

Offers

/admin/p2p/offer lists every offer with its owner, type, currency, price model, amounts, status and flag state.

The approval queue

When p2pAutoApproveOffers is off — the default, and the right default — every new offer and every edit to a live offer lands in PENDING_APPROVAL and waits here.

PENDING_APPROVAL is a collateralized status. A SELL offer took its full advertised total out of the maker's spendable balance the moment it was submitted, and it stays there while the offer sits in your queue.

This is deliberate — approval must never have to find the funds a second time, because an approval that could fail on insufficient balance is a queue that randomly rejects. But it means a slow queue is a queue that freezes customers' money. Work it daily, or turn auto-approve on and moderate reactively instead.

Approve validates that the offer has an amount and at least one payment method, sets it ACTIVE, and emails the owner.

Reject sets it REJECTED and releases any locked funds back to the owner, with your reason attached to the notification. REJECTED is terminal — the maker posts a new offer rather than fixing this one.

The other actions

Action Status after Escrow effect Owner notified
Approve ACTIVE unchanged (already held) yes
Reject REJECTED released yes, with reason
Pause PAUSED released yes
Activate ACTIVE topped up to the advertised total yes
Disable CANCELLED released yes, with reason
Flag unchanged none yes
Note unchanged none no — internal
Edit per the change recomputed per the change

Three of these need explaining.

Activate is the counterpart to pause and reject: it can bring a paused, disabled, rejected or cancelled offer back. Because those statuses hold no escrow, activating a SELL offer tops the escrow back up to the advertised total, drawing on the owner's current balance. If they have spent that money in the meantime, activation fails — that is correct, since a published SELL offer must always be fully collateralized.

Flag does not change the offer's status. It writes a flag row with your reason and notifies the owner. Use it for "this needs a second look" and for things you want visible on the offer without taking a live offer off the board in front of its counterparties. Unflagging revives the same row rather than creating a second one.

Note appends a timestamped internal note to adminNotes and to the offer's activity log. Users never see it. This is where the reasoning behind a decision should go, because the notification the user gets is short and the audit log entry is structured rather than narrative.

Editing an offer as an admin

The admin edit route can do things the owner's route cannot, including status transitions the owner is not allowed to make. It manages locked funds for SELL offers the same way, and the platform minimum and maximum trade amounts are still enforced — as dollar figures converted into the offer's pricing currency, the same as at creation.

The same blocker applies as for owners: an offer with a trade in PENDING, PAYMENT_SENT or DISPUTED cannot have its terms changed underneath a live counterparty.

Trades

/admin/p2p/trade lists every trade with both parties, amount, price, total, status, escrow status and timestamps. Opening one shows the full timeline — every status change, every chat message, every admin action — plus the payment details snapshot and the dispute if there is one.

Adding a note or a message

One endpoint, two behaviours, decided by a flag:

  • Note (default) — internal. Recorded against the trade for other admins.
  • Message — posted into the trade chat as an admin message, broadcast over the WebSocket, and both parties are notified.

Use messages sparingly and use them to state facts: what you have decided and what you need from each side. Everything else belongs in a note.

Both are sanitised on the same terms as a trader's message. A broadcast is capped at 1,000 characters, the same limit the trade chat enforces — it lands in both traders' chat panels and is rendered by the same component. An internal note is escaped the same way but allows 4,000, because it is staff prose recording why a decision was made rather than a chat message.

Cancelling a trade

Cancels the trade with your reason, settles the escrow back and restores the offer's advertised capacity. It exists for trades that are stuck for reasons that are not a dispute — a payment method that no longer exists, a counterparty whose account has been blocked, a test trade left open on a live board.

If the two parties disagree about money, this is the wrong tool. Use resolve.

Cancelling a disputed trade closes its dispute. Cancelling settles the escrow, so there is nothing left to rule on — the case leaves your queue with the outcome recorded as CANCELLED, and stops ageing against the response target on the dashboard.

It is recorded as CANCELLED rather than as either side winning because nobody won: the trade was unwound and the money went back where it came from. Anyone auditing the case later sees a cancellation, not a ruling that was never made.

Force-resolving a trade

/admin/p2p/trade/<id> offers the same four outcomes as dispute resolution, and accepts a trade in DISPUTED, PAYMENT_SENT or PENDING. That is wider than the dispute door on purpose: it lets you settle a trade that never had a dispute filed.

Outcome Trade ends Money
BUYER_WINS COMPLETED escrow to the buyer, less the platform fee
SPLIT COMPLETED divided by buyerPercentage (0–100) or buyerShare; 50 % if neither is given
SELLER_WINS CANCELLED escrow back to the seller, no fee, offer capacity restored
CANCELLED CANCELLED identical to SELLER_WINS

Both doors route through the same escrow authority, so SPLIT means the same thing in each. If a dispute exists on the trade, resolving here marks it RESOLVED with the same outcome.

fundsReleased is true only if a credit, a refund or a fee was genuinely booked — not merely that a branch ran. A trade whose escrow was already settled by another door returns "Trade resolved, but no escrow was moved", and the warning is worth reading rather than dismissing: it usually means the seller already released, or the expiry cron got there first.

Running the timeout handler by hand

There is a manual trigger for the expiry job on the trades screen. It runs the same handler the cron runs every minute: expire overdue PENDING trades, auto-dispute PAYMENT_SENT trades older than 24 hours, expire dead offers.

Use it when a payment window has clearly lapsed and nothing has happened. If it fixes things, your cron process is not running — fix that, because the manual button is not a substitute for it.

Activity log

/admin/p2p/activity is the P2P event stream: trades initiated, payments confirmed, trades completed, cancelled and expired, disputes created and updated, offers expired, payment methods created, reputation milestones, and every admin action.

Admin actions are additionally written to a dedicated admin-activity table inside the same transaction as the change they describe, so a rolled-back resolution does not leave a log entry claiming it happened.

The hourly reputation job writes a log row per user. It now writes one only when a score actually moves — previously it wrote one per active user per hour forever, and because the dashboard's recent panel takes the five newest rows with no type filter, every hour those five were reputation noise hiding the trades and disputes the panel exists to show.

Rate limits on admin actions

Fixed in code, per admin:

Door Limit
Offer actions (approve, reject, pause, activate, disable, flag, edit) 50 per hour
Trade actions (resolve, cancel, note) 100 per hour
Dispute actions 100 per hour

A bulk cleanup of a stale board will hit the offer limit. Spread it, or do it against the database with the escrow consequences in mind.