Funding the pool

How the liquidity pool works — depositing from the platform's wallet, withdrawing back to it, whose wallet that is and why, the capital basis every P&L figure is measured against, and why the rebalance button reports rather than converts.

12 min readUpdated 5 September 2026pool, liquidity, deposits, withdrawals, rebalance

The pool is real money. Its two balances — base and quote — are the only thing between the platform's ecosystem wallet and the market's inventory. A deposit takes from that wallet and raises the pool. A withdrawal lowers the pool and credits that wallet. Every real user fill settles against those same two balances, in the same transaction that pays the counterparty.

The Pool tab on a market's detail screen is where all of this happens.

Everything below describes an ecosystem pool. A futures pool holds one currency, the money actually lives in a wallet rather than in the columns, and part of it can be locked. See A futures pool at the end of this page.

Whose wallet it is

The wallet on the other side of every pool movement is the platform's: the Super Admin's — the oldest user holding the Super Admin role, resolved the same way the platform resolves where fees go. It is that wallet whoever is signed in. A deposit takes from it, a withdrawal credits it, and deleting a market pays whatever the pool still holds into it.

Three facts about the pool make that the only sensible owner:

  • The pool is house capital. What it holds is your seed plus or minus the profit and loss realised against real customers. It is the platform's inventory, not a personal position belonging to whoever clicked.
  • The same maker's fees already go there. On a futures market every quote the maker places pays a platform fee, and that fee is credited to the Super Admin. Principal and P&L in one account and fee revenue in another is a set of books that reconciles from neither side.
  • A delegated admin must be able to run the pool without owning it. The pool permission is a permission to operate — top up, drain for maintenance, retire a market — not to be paid. Until v6.2.5 the deposit debited, and the withdrawal and the delete credited, the wallet of whichever admin clicked.

The DEPOSIT and WITHDRAW history entries record who acted (adminId) and, separately, whose wallet the money moved through (houseWalletOwnerId). The delete payout writes no history entry — the market's history is removed with it — so its only record is the ledger row on the platform wallet. Every ledger row a pool movement writes carries the acting admin as operatorId and the wallet owner as houseWalletOwnerId.

If no user holds the Super Admin role, all three doors refuse with "The platform has no Super Admin; the pool's money has nowhere to go." Nothing is written: the delete resolves the wallet before it stops the maker or touches the book, so a refused delete leaves the maker running with its quotes in place. The Pool tab's balance card reads that same wallet, so on such an install the refusal appears as soon as the tab opens — the same sentence, from the wallet read, before you have touched deposit, withdraw or delete. It is a read; it writes nothing. The caller's wallet is never used as a fallback — that fallback is the defect the rule replaces. Create the Super Admin and retry. If several users hold the role, the oldest is the wallet, which is the rule fees have always followed; the pool does not invent a second one.

The doors and the card read the Super Admin from the row, not from the five-minute cache the fee path uses. A Super Admin deleted or demoted a moment ago — a delete is a soft delete, and the account's wallets stay live — is not paid a pool's balance in the window before that cache expires.

What a pool holds

Field Meaning
baseCurrencyBalance Units of the market's base asset held now
quoteCurrencyBalance Units of the quote asset held now
initialBaseBalance / initialQuoteBalance The capital basis — everything you have put in, less everything you have taken out
totalValueLocked (base x price) + quote, valued in the quote asset
realizedPnL Profit and loss from fills against real users
unrealizedPnL Mark-to-market on the inventory currently held

totalValueLocked is a plain sum in the pool's quote denomination. Where two of your markets quote in different assets, the dashboard's total has no unit at all and does not print one — a sum across USDT and BTC pools is a number, not an amount of money. Where every market shares a quote asset, the ticker is named once beside the figure.

Depositing

  1. Open the market's Pool tab. The panel shows both pool balances and the platform wallet's balance for each side — the Super Admin's wallet, not yours, unless you are the Super Admin.

  2. Pick a side — BASE or QUOTE — and an amount. The amount must be greater than zero and cannot exceed the platform wallet's balance; the refusal names both figures.

  3. Submit. The deposit debits the platform's ecosystem wallet, credits the pool, recalculates TVL against the market's target price and writes a DEPOSIT history entry naming you as the admin who acted. It is one database transaction: nothing moves if any part fails.

Funding is an internal transfer between two balances the platform already holds. It needs no deposit address and no configured chain, and it reads the wallet directly. A platform wallet holding a good balance in a token whose chain is not configured on that server can still fund the market.

Deposits and withdrawals are ordinary repeatable operations — you fund the same pool many times over its life — so the wallet ledger key is unique per request. A client that retries a submit and wants exactly-once behaviour must send the same idempotencyKey on both attempts. Without one, two identical requests are two deposits.

An older build derived that key from (pool, currency) alone, which meant a pool could be funded exactly once per currency, ever. Every top-up after the first answered "Duplicate operation detected", permanently. If you are on a build before v6.1.5, that is what you are seeing.

The capital basis accumulates

initialBaseBalance and initialQuoteBalance rise on every deposit and fall on every withdrawal. They are the denominator of the pool's return, so getting them wrong misstates performance rather than losing money: an older build recorded them on the first deposit only, so a pool funded ten times reported its return on a tenth of the money, and a drained pool still reported its original capital.

Withdrawing

POST/api/admin/ai/market-maker/pool/{marketId}/withdrawpermission: edit.ai.market_maker.pool
Withdraws from the pool back to the platform's ecosystem wallet — the Super Admin's, whoever calls

Two guards:

  • The market maker must not be ACTIVE. Pause or stop it first. A withdrawal from a quoting market would race its own settlement.
  • The amount cannot exceed the pool balance on that side. The refusal names the available figure.

Because withdrawal refuses an active market, a market that is stuck ACTIVE locks its pool. If the engine has lost a market and the admin screens cannot stop it, see Troubleshooting.

How much a market needs to start

Starting a market maker checks two things:

  • totalValueLocked must be greater than zero"Cannot start market maker without liquidity. Please deposit funds first."
  • If aiMarketMakerMinLiquidity is set, the quote balance must clear it. The refusal quotes both the requirement and what the pool actually holds.

The same minimum is re-checked by the engine when it loads the market, so a market that passes the admin screen and then fails the engine's check will fail to start with the reason in the log.

Note that neither check applies to trading once real liquidity is off. With Real Liquidity at 0% the pool is not touched by bot-to-bot trades at all — but you still cannot start the market with an empty pool.

Rebalancing

Over time a pool is pushed to one side of the book: absorbing user sells leaves it long the base asset, absorbing buys leaves it long quote. The dashboard reports this as inventory skew — the share of pool value now held in base, against the share it was funded with, both valued at the current price so the market's own price move does not contaminate the reading.

The rebalance control has two modes.

REPORT — the default, and what the button does

Calculate rebalance sizes the trade a target ratio would require and changes nothing. It returns the required movement in each currency. The market maker must be paused or stopped even for this: a report taken while the AI is quoting is stale before it renders.

EXECUTE — a real trade against the real book

mode: "EXECUTE" places a genuine limit order against real order-backed depth in the Ecosystem book and lets the matching engine settle it. Every unit that enters the pool leaves a counterparty's wallet. It refuses outright — HTTP 409 — rather than partially filling, when the real book cannot cover the move or when covering it would push the price further than maxSlippagePercent (default 2).

An older build calculated the balances a requested ratio would need and simply wrote them onto the pool, under a comment describing it as a simulated rebalance. Those balances are not a projection. The exploit was three clicks:

deposit 20 BASE          -> pool holds 20 BASE, 0 QUOTE
rebalance to ratio 0     -> pool "holds" 20 x target price in QUOTE
withdraw QUOTE           -> a real credit on a real ecosystem wallet

Both the target price and the price range are operator-settable, so that was a conversion between any two ecosystem tokens at any rate the operator chose, with no counterparty. The automatic rebalancer had always refused to do it; the admin action now matches.

The supported way to shift a pool's composition without EXECUTE is to move real assets: withdraw the surplus side, trade it wherever you normally would, and deposit the proceeds back.

The automatic rebalancer

processAiPoolRebalancer runs hourly. It reports drift; it does not convert balances. Like every other scheduled job in the addon it treats an unset global trading switch as on — two of them used to require the switch to have been explicitly saved once, and reported completed on every pass while doing nothing at all on a fresh install.

Reading P&L honestly

A trade between two of the platform's own bots has no profit or loss — both sides are the house. Only fills against real users produce a number, which means:

  • On a market running at 0% real liquidity, realised P&L is legitimately zero however busy the tape looks.
  • Bot statistics show the real count of fills against real users and the real profitable count, and show nothing where there is nothing to measure. A confident win rate on a bot that has never met a counterparty is a symptom of a build before v6.1.5, not a measurement.

The P&L report separates its sources: the all-time figure comes from each bot's lifetime accumulator, while daily, weekly and monthly come from a per-fill ledger that only began recording in v6.1.0. The report states when that ledger begins, so a large all-time figure beside three small ones reads as a short ledger rather than as a collapse in performance.

Unrealised P&L values the pool's inventory at the current price. An older build valued the opening inventory at a price of 1 — a pool funded with 40 base on a market at 100 had its opening capital valued at 40 instead of 4,000, and the engine wrote a roughly +3,960 "unrealised profit" to it every minute for a position that had never traded.

A futures pool

A futures market maker posts margin, and margin is a wallet balance rather than a number in a column. That changes four things about this page.

One currency, not two

Futures margin is posted in the quote currency whichever way the maker is facing, and a fill opens a position rather than moving base inventory. There is no base balance for a base deposit to become, so a BASE deposit is refused with the reason rather than accepted into a column nothing reads. Deposit QUOTE.

The money is in a wallet, and the column is an allocation

A deposit debits the platform's futures wallet — the Super Admin's, not the ecosystem one and not yours — and credits the market maker's own futures wallet in the same transaction. A withdrawal pays back into that same futures wallet. The quoteCurrencyBalance column records what you allocated to this market; the engine settles against the wallet.

That distinction matters when you run more than one futures market on the same quote asset. Margin is held per account and currency, so two markets quoting USDT draw on the same wallet balance. The per-market column is the budget the maker checks before it opens more exposure — it is not a separate pot, and pretending otherwise would let two markets each believe they had their own funds and jointly overdraw.

Part of it can be locked

An open position and a resting order both hold margin, and margin cannot be withdrawn. A withdrawal is checked against what is free, and refused with the figure when it is not:

Only 412.50 USDT is free to withdraw. The rest is posted as margin behind an open position or a resting order — close them to release it.

That number is a floor rather than a promise: cancelling an order or closing a position credits the wallet straight back, so a refusal can become a success a moment later. Pausing the market — which the withdrawal gate already requires — cancels the maker's resting orders and releases their margin. What remains after that is behind the position itself, and the maker unwinds that through its own reduce-only exits.

Rebalance does not apply

The rebalance control moves a pool between base and quote to reach a target ratio. A futures pool has one currency and its directional exposure is a position, so there is no ratio to correct. The endpoint refuses it and says so.

The maker's account

Every futures order the maker places belongs to one system account — visible in the admin user list as AI Market Maker, with no password, no wallet address and an address in the reserved .invalid domain. Nobody can sign into it. It exists so the futures engine sees an ordinary funded trader, which is what makes margin, funding, ADL and liquidation work on the maker's positions without a second implementation of any of them.

The consequence is stated plainly on the create page and is worth repeating here: the maker's position is liquidatable. Fund the pool for a move against you, not for the spread you expect to earn.