The ecosystem order desk
The admin orders screen at /admin/finance/order/ecosystem — where it hides in the nav, what its ScyllaDB-backed tiles and filters can and cannot do, and how to run the corrupted-order cleanup safely.
This is the one screen in the product where an operator can look across customer
orders on ecosystem markets. It is not under Admin → Ecosystem, and that single
fact accounts for most of the support traffic about it. (The same rows also
appear one customer at a time on the Ecosystem tab of a CRM record at
/admin/crm/user/{id} — the same endpoint, declaring the same
access.ecosystem.order and view.ecosystem.order pair, filtered to that
customer and equally read-only. It answers "what has this customer traded", not
"what is happening on the book".)
Where it actually is
Spot Orders /admin/finance/order/exchange
Ecosystem Orders /admin/finance/order/ecosystem <- this page
Futures Orders /admin/finance/order/futures
Binary Orders /admin/finance/order/binaryThe addon's own navigation — Overview, Blockchains, Wallets, Trading — carries no Orders entry at all. Orders live in the core finance section beside every other venue's order table, because that is where an operator looks for "orders" regardless of which engine filled them.
The entry is tagged with the ecosystem extension, but the admin menu
deliberately keeps extension-tagged items visible even when the extension is off
— they are shown flagged as not installed, so an operator can see what is
available. Seeing Ecosystem Orders in the nav is therefore not proof the
addon is enabled. The user-facing menu is the one that hides them.
Two permissions gate it, and they are not the same key:
| Key | Grants |
|---|---|
access.ecosystem.order |
Opening the screen. Enforced on the route path itself. |
view.ecosystem.order |
The data. Enforced on GET /api/admin/ecosystem/order. |
manage.ecosystem.order |
The cleanup action only. |
create.ecosystem.order, edit.ecosystem.order and delete.ecosystem.order are
seeded and grantable, but the screen disables create, edit and delete outright —
granting them changes nothing here.
The tiles, and the one thing they cannot count
The analytics header comes in three bands.
| Band | Cards |
|---|---|
| Book state | Total orders · Open orders · Matched orders · Canceled orders, plus a status pie |
| Flow over time | A stacked bar of OPEN / CLOSED / CANCELED per bucket, over 24h, 7d, 30d, 3m, 6m and 1y |
| Liquidity composition | Limit orders · Market orders, plus a stacked bar of resting versus taking flow |
The analytics run against ScyllaDB, whose aggregator here understands exactly one question: count the rows where this column equals this literal. No sums, no averages, no distinct counts, no derived ratios. There is no traded-volume tile, no fee-revenue tile and no average-fill-size tile on this screen, and there cannot be one without a different data store. Fee revenue is a MySQL question — see Trading fees.
The liquidity band is the one worth reading regularly. Limit orders add depth and market orders consume it; a limit band that collapses on one market is that market losing its own liquidity providers.
The columns
Six columns are visible in the table: Status, Symbol, Order type, Side, Price and Amount. Everything else is carried on the row's expanded view and in the view dialog — ID, User ID, Time in force, Fee, Fee currency, Average price, Filled, Remaining, Cost and the raw trades payload.
Opening a row gives a four-figure strip — filled against amount with a
percentage, remaining, total cost, and fee with its currency — then sections for
pricing, the individual fills, routing (walletType, marketMakerId, botId),
the order and account ids, and the timeline. An order placed by the AI market
maker or a trading bot carries a Bot order badge, which is the fastest way to
tell an operator's own liquidity from a customer's.
What each of those figures means over an order's life — why a partly filled order
still reads OPEN, what the hold is, why a market BUY stores a price that looks
wrong — is The order lifecycle.
Reading from ScyllaDB changes how the table behaves
This is one of three admin data tables declared against Scylla rather than MySQL
— the other two are Futures Orders (/admin/finance/order/futures) and
futures positions (/admin/futures/position), both on the futures keyspace.
This one uses the ecosystem keyspace, which the API resolves to
SCYLLA_KEYSPACE (default trading). Four consequences you will meet in
practice — they come from the shared Scylla query helper, so they hold on all
three screens, not just this one:
A text filter is a prefix, never a substring. A "contains" or "starts with"
filter is compiled into CQL as column LIKE 'value%'. Searching Symbol for
USDT is not the same question as searching for BTC on an install full of
BTC/USDT orders, and the two do not return the same rows. No secondary or SASI
index is created on any column of these tables, so a LIKE restriction the
cluster cannot serve comes back as a query error on the screen rather than as an
empty table — treat a red banner on a text filter as "that filter is not
supported here", not as "the database is down". Equality filters work because
every query carries ALLOW FILTERING.
A partial User ID is turned into a range query. Anything shorter than a full 36-character uuid is padded into a lower and upper bound, so a prefix works. A full uuid becomes an equality match. A value that is neither is rejected with a 400 rather than quietly widening the result set.
Sorting is only done by the database when you have filtered on User ID.
userId is the partition key. With an equality filter on it the query can carry
ORDER BY; without one, the rows are fetched and sorted in the API process
before the page is sliced out.
Every page load is a full scan. Both the count and the data query are issued
with ALLOW FILTERING and no partition key restriction. On a large install this
screen is genuinely expensive to open, which is a reason to filter it rather than
to page through it.
One quiet behaviour worth knowing: the list endpoint drops rows whose symbol,
amount, price or side is null before it answers, and adjusts the reported
total to match. Those rows exist; you simply never see them here. The cleanup
dialog below is the only place the platform will tell you how many there are.
The status filter offers Cancelled, spelled with two Ls. The matching engine
writes CANCELED with one. The filter value is passed straight into the CQL
WHERE clause, so selecting it returns an empty table on an install with
thousands of cancelled orders. Filter on OPEN or CLOSED and read the rest.
The cleanup dialog
Cleanup Corrupted Orders sits in the table's top bar. It exists for one specific artefact of how Cassandra-family databases write.
A corrupted order is a Scylla upsert ghost. In CQL an UPDATE against a
primary key with no row behind it does not fail — it creates the row, with the
key populated and every other column null. Anything that once updated an order by
a (userId, createdAt, id) triple that never existed left behind a row with a
perfectly valid primary key, a null symbol, a null amount, a null price, a
null cost and a null side. They are not orders. They were never placed by
anyone, they hold no funds, and no customer is waiting on them.
DELETE in ScyllaDB is not a soft delete, this table has no deletedAt, and the
platform takes no backup of ScyllaDB at all — the built-in database backup
and mysqldump both cover MySQL only. There is no undo. Always run the scan
first.
-
Open the dialog — the red Cleanup Corrupted Orders button above the table.
-
Scan first. The Scan First action sends
{ dryRun: true, limit: 10000 }and deletes nothing. It reports Total scanned and Corrupted found. -
Read the two numbers. If Corrupted found is 0, close the dialog — there is nothing to do, and the destructive action is not even offered.
-
Clean up orders. The confirm action re-runs with
dryRun: falseand the same limit, deletes each ghost row by its primary key, and reports how many were removed. The page reloads afterwards. -
Re-run if the scan was saturated.
limitcaps how many rows are scanned, not how many are deleted. If Total scanned came back at exactly 10,000 you have looked at the first 10,000 rows and no further; run it again until a scan comes back with Corrupted found at 0.
The endpoint accepts a limit between 1 and 100,000 and refuses anything outside
that range with a 400. Its response carries totalScanned, corruptedFound,
deleted, errors and dryRun; the dialog shows the first three.
The scan reads the orders table with ALLOW FILTERING and no partition key,
which means every node walks every partition it owns. On a busy install that is a
real load event, and it is issued from the same process that serves trading. Do
it during a quiet window, not while a market is active.
A role that can open this screen sees the cleanup button whether or not it holds
manage.ecosystem.order — the gate is on the endpoint, not the button. A grantee
without it gets a permission error at the moment of the scan.
What this screen cannot do
It is read-only by construction: create, edit and delete are all disabled, and there is no cancel action anywhere on it. You cannot cancel a customer's order from the admin panel.
The two paths that do cancel an ecosystem order are:
- the customer's own cancel,
DELETE /api/ecosystem/order/{id}with the order'screatedAtas the requiredtimestampquery parameter, or their cancel-all; - deleting the market, from Admin → Ecosystem → Trading → Markets, which
needs
delete.ecosystem.marketand cancels and refunds the orders it removes as part of tearing the market's data down.
An order that is stuck — resting on a customer's money with nothing matching it —
is a repair-script problem rather than a screen problem. pnpm eco:index:check
answers "is the open-orders index short of the ledger?" with an exit code, and
pnpm reconcile:eco-inorder releases funds held with no open order behind them.
Both, and the rule about restarting the backend afterwards, are in
Operations.
Related
- The order lifecycle — what the statuses, the hold and the fills mean
- ScyllaDB schema and engine tuning — the tables behind this screen
- Trading fees and where the revenue lands — the numbers this screen cannot show you
- Operations — repair scripts, backups, and where the engine runs
- The admin console — every screen that is under Admin → Ecosystem