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.

8 min readUpdated 6 August 2026fees, revenue, adminprofit, maker-taker, withdrawal

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.

Updates a market's metadata, including maker and taker

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 maker or taker on 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.

  1. Finds the Super Admin — the oldest user holding the Super Admin role, cached for five minutes.
  2. Credits their ECO wallet for the quote currency, creating it if it does not exist. Because walletType is ECO and a chain is supplied, the credit goes through ecoCredit, which updates all of wallet.balance, the walletData row, the per-chain address[chain].balance, and the private ledger.
  3. Writes an adminProfit row with type: "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.