Trading fees and where the revenue lands
How maker and taker fees are charged on ecosystem fills, which wallet the money ends up in, who is excluded, how to reconcile a fee row back to a fill, and the separate on-chain withdrawal fee.
The fee take is the business model, and it is the one number the addon's own screens do not show you. This page says what is charged, where it goes, who is excluded, and how to tie a revenue row back to the fill that produced it.
What is charged
maker and taker are percentages on the market's metadata object,
edited at Admin → Ecosystem → Trading → Markets → edit. A rate of 0 is a valid
configuration and is honoured; the placement path checks for null, not for
truthiness.
The fee is charged per side, and it is always denominated in the quote
currency — the pair half of the symbol. On BTC/USDT both the buyer and the
seller pay in USDT.
Which rate applies is decided at placement, not at match time
A market order is always taker. A limit order is taker if it crosses the current
real book at the moment it is placed — a BUY at or above the best ask, a SELL at
or below the best bid — and maker otherwise. The chosen rate is baked into the
order's fee column right then.
Two consequences worth internalising:
- A resting order that is later taken by someone else still pays the rate decided
when it was placed. Changing
makerortakeron a market does not re-price orders already resting. - The crossing test reads the real order book. AI market-maker display levels carry a TTL and no backing order, so testing against them would charge the taker rate for liquidity that was never taken.
Each fill then charges its proportional share of that stored fee, by fill
ratio. The buyer's share comes out of the quote already held; the seller's is
deducted from the proceeds credited to their quote wallet — a SELL holds only
base at placement, so nothing is held for the seller's fee.
The ratio is amountToFill / order.amount — the fill against the whole
order, never against what has filled so far. The distinction matters because
order.fee is quoted once, at placement, for the order's full size and is never
rewritten as fills land: the per-fill write touches only filled, remaining,
status and trades. Divide it by a running total and every fill of a partly
filled order reads high, and re-reads lower each time the next fill arrives.
Since Ecosystem v6.3.9 the charged figure is also recorded on the fill itself, so nothing downstream has to divide anything up. It is stored on the order's own trade record and deliberately kept off the market's public trade feed, which every subscriber of a symbol receives — a trader's fee reveals their tier. Fills predating that release carry no fee of their own and must be reconstructed with the ratio above.
Where the money goes
Fee revenue is recorded through one shared helper:
await collectPlatformFee({
currency: quoteCurrency,
walletType: "ECO",
chain: quoteToken?.chain ?? undefined,
feeAmount: totalPlatformFee,
type: "TRADE",
referenceId: `${buyOrder.id}_${sellOrder.id}`,
...
});That does three things.
- Finds the Super Admin — the oldest user holding the
Super Adminrole, cached for five minutes. - Credits their ECO wallet for the quote currency, creating it if it does
not exist. Because
walletTypeisECOand achainis supplied, the credit goes throughecoCredit, which updates all ofwallet.balance, thewalletDatarow, the per-chainaddress[chain].balance, and the private ledger. - Writes an
adminProfitrow withtype: "TRADE", the amount, the currency, the chain and a description, linked to the credit's transaction id.
There is no conversion. A BTC/USDT market pays you in USDT, a MYTOKEN/BTC
market pays you in BTC, and each lands in a separate ECO wallet on the Super
Admin account. That balance is a real ECO balance and withdraws on-chain like any
other — through the same vault, master wallet and gas that
Deposit wallets describes.
How the chain is chosen
chain is a blockchain, not a currency, and it decides which per-chain leg
of the fee wallet is credited. It is resolved by looking up the quote token:
the first active ecosystem_token row for that currency, ordered by chain
ascending.
A currency can be issued on more than one chain — USDT on BSC and on POLYGON — and an ECO balance is chain-agnostic, so there is no single right answer. The lowest chain name is picked deterministically so the per-chain ledger accrues consistently instead of scattering run to run. If no token can be resolved, the credit falls back to a plain one with no chain rather than writing a bogus one.
This used to be passed the quote currency, which matched no chain at all — so
every trading fee ever collected credited only wallet.balance and left the
per-chain ledger short by the same amount. If your fee wallet's chain balances
look understated against wallet.balance on an older install, that is the cause.
Who is excluded
Two sides are deliberately excluded from admin profit, and both make reported revenue lower than raw volume implies.
Bot orders. An order carrying a marketMakerId contributes zero to the fee
total. AI market-maker orders are created with fee = 0 anyway, and the pool is
the counterparty rather than a customer, so charging it would be moving money
from one platform pocket to another.
The Super Admin's own side. Charging a fee from the Super Admin and crediting it back to the Super Admin is a circular no-op that inflates the profit report, so that side's fee is dropped.
The consequence is worth stating plainly: a market-maker-heavy pair reports
less fee revenue than its volume suggests, by design. Do not reconcile
adminProfit against traded volume on such a pair and conclude fees are
leaking.
Reconciling a fee row back to a fill
referenceId on the collection is ${buyOrderId}_${sellOrderId} — the two order
ids of the fill, joined by an underscore. It is also the idempotency key
(prefixed platform_fee_TRADE_), which is what stops a replayed settlement from
crediting twice.
The adminProfit row itself has no metadata column — it stores only
transactionId, type, amount, currency, chain and description. The
{ symbol, buyOrderId, sellOrderId } object passed at collection is written onto
the linked transaction row's metadata instead, reachable through the
profit row's transactionId. On the profit row, the description is what names
the symbol, the amount, the price and each side's fee:
ECO trading fee: BTC/USDT 0.25 @ 65000 (buyer fee: 16.25, seller fee: 16.25)So: take the description or referenceId, split on the underscore, and look the
two order ids up on Admin → Finance → Orders → Ecosystem.
collectPlatformFee swallows every error by contract — a fee failure must never
roll back a customer's fill. If there is no Super Admin role, or no user holds
it, every platform fee on the install is dropped on the floor. It is logged,
loudly, and it is the one thing worth grepping for after a role migration:
[CRITICAL] Dropped platform fee — no Super Admin configured. type=TRADE ...The withdrawal fee is a different fee
On-chain withdrawals carry their own charge, unrelated to maker/taker. It comes
from the token's fee object — min and percentage, edited on the token's
screen — and is computed as:
withdrawalFee = max(amount x percentage / 100, min)It is denominated in the withdrawn token's own currency, not in a quote asset, and it is what recovers the network gas the platform pays on the customer's behalf. See Tokens and markets for how to set it.
It is recorded as an adminProfit row of type WITHDRAW, with the chain the
withdrawal actually went out on, and only after the send succeeds — the
recording sits after processWithdrawal returns, so a failed or refunded
withdrawal never books revenue. referenceId is the transaction id, which makes
these rows the easiest of all to reconcile.
Two exceptions:
- XMR is skipped here. Monero withdrawals record their admin profit inside the XMR service, which does its own fee splitting.
- UTXO batches record per-member fees inside the batch processor rather than on the single-transaction path, so a batched BTC payout still books one row per customer.
transaction.fee is a DECIMAL(36,18) column and mysql2 returns every DECIMAL
as a string, because decimalNumbers is not set on the connection. The guard
in front of the collection used to be typeof transaction.fee === "number",
which was therefore always false — and the platform withdrawal fee was never
collected on any ECO chain. If you are auditing revenue on an install that has
not taken that fix, expect zero WITHDRAW rows for ecosystem chains regardless
of what the token's fee object says.
Reading the numbers
Both kinds of row land on Admin → Finance → Revenue Analytics
(/admin/finance/profit). Reading the revenue screen
covers that page in full — what each tile measures, why totals are never summed
across currencies, and the two log lines that mean fees are being dropped. Three
things are specific to this addon.
type: TRADE is not ecosystem-only. Exchange (SPOT) order fills, futures,
copy-trading profit share, e-commerce checkout, trading-bot fills and trading-bot
strategy purchases all book TRADE as well, because the admin_profit.type
column is a fixed ENUM with no member of their own. P2P is not one of them:
it has its own P2P_TRADE member and books under that. To isolate ecosystem spot
fills, match the description prefix ECO trading fee:. Most of the other writers
use walletType: "SPOT" — e-commerce follows the product's own wallet type and
the trading-bot engine uses ECO — but none of them supplies a chain, so a
non-null chain on a TRADE row is a strong secondary signal. It is not
guaranteed in the other direction, though, since an ecosystem row also has a null
chain when the quote token could not be resolved.
type: WITHDRAW is not ecosystem-only either. Every custody path writes it.
The chain column is what narrows it to on-chain ecosystem withdrawals.
Your ecosystem revenue is spread across ECO wallets, one per quote currency.
The Admin wallet balances section of that page shows them under the ECO
wallet type, with available / total where funds are held. That is the
withdrawable side of the same money, and it is the number to reconcile against
the TRADE and WITHDRAW rows above it.
Related
- The order lifecycle — where in a fill the fee is charged
- Tokens and markets — where maker, taker and the withdrawal fee are configured
- Reading the revenue screen — the page these rows appear on
- Admin console — the rest of the Ecosystem screens
- Master wallets and the vault — what pays the gas the withdrawal fee recovers