Retiring a token, market or chain

What the delete buttons under Admin - Ecosystem actually do, which ones are permanent, which have no guard at all, and the reversible way to take an asset out of service instead.

8 min readUpdated 6 August 2026markets, tokens, delete, retirement, custody

Adding a market is reversible. Removing one is, in this product, the single most destructive action an admin can take from a data table — and the delete button looks exactly like every other delete button in the panel.

Deleting a market is permanent. The route forces a hard delete regardless of what the UI asked for, so the ordinary "Delete" row action removes the row outright. There is no restore, and the market table's "show deleted" toggle will never surface it.

Deleting a token has no guard whatsoever. No balance check, no market check, no address check. Customers can be holding the asset, markets can be quoting it, and the delete still succeeds.

Neither action moves, refunds or reconciles a single unit of customer money.

Prefer status over delete

Almost every retirement an operator actually wants is a status change. It is reversible, it is enforced in the code paths that matter, and it leaves the configuration intact so you can put the asset back.

Action Where What it stops What it leaves
Disable a market Markets table, status toggle The pair disappears from /ecosystem and its market WebSocket refuses new subscriptions Configuration, candles, and every resting order
Disable a token Tokens table, status toggle Deposits and withdrawals 404, the token cannot back a new market, no new addresses are derived for it Balances, existing addresses, existing markets
Disable a custom EVM chain Custom EVM Chains, status toggle Removes the chain from the live registry on reload The chain row, its master wallet, customer addresses and balances
Disable a licensed chain Blockchains, status toggle The chain's flows stop Everything else
Enables or disables a market
Enables or disables one or more tokens

Disabling a token is the strongest reversible lever in the addon. The withdrawal handler resolves an asset with status: true and nothing else, so a disabled token makes every withdrawal on that chain and currency answer Token not found for chain: <CHAIN> and currency: <CURRENCY>. The same lookup gates deposit-address issuance. It is the real off switch.

A disabled market keeps its whole book. Users cannot reach the pair to cancel their own orders, and the funds stay held in inOrder until somebody cancels them. If you are pausing a pair for an hour that is fine. If you are retiring it, cancel the book first — see the ordered procedure at the end of this page.

Deleting a market

Permanently deletes one market and attempts to remove its market data

The handler overrides whatever the caller asked for with force: true, so ecosystem_market is destroyed with force even though the model is paranoid. The row does not come back.

The one guard

Before deleting, the handler counts copyTradingTrade rows on the market's symbol whose status is PENDING, OPEN or PARTIALLY_FILLED. If there are any, the request fails with 400:

{ "message": "Cannot delete market with 3 open copy trading trades. Please close or cancel all copy trades first." }

That is the only thing standing between the click and the deletion. Notably it is a copy-trading guard, not a market guard — ordinary resting orders on the symbol do not block it.

After a successful delete, follower allocations and leader markets for the symbol are set isActive: false. If the Copy Trading addon is not installed, both the check and the cleanup are skipped silently.

What the market-data cleanup is meant to do

The delete calls deleteAllMarketData, which for a given symbol is designed to:

  1. read every order on the symbol and pass each one through cancelAndRefundOrder — which refunds an OPEN order's remaining hold to the wallet type it was funded from (quote currency for a BUY, base for a SELL);
  2. delete those orders from orders and their mirrors from open_orders_by_market, which is the table the matcher actually loads from at boot;
  3. delete every candle for the symbol;
  4. delete every aggregated order-book level for the symbol.

All of that is issued in unlogged chunks of 100 statements, because a batch proportional to a market's whole history gets rejected on size — and a rejected batch deletes nothing and only logs.

The single-market handler builds the full BASE/QUOTE symbol for the copy-trading checks, but passes only the market's base currency to deleteAllMarketData. Every query inside that function matches on the full symbol, so BTC finds no orders, no candles and no book levels for BTC/USDT.

The practical result: the market row is destroyed, and the orders, their held funds, the candles and the book levels are all left behind. Nothing refunds those holds, and open_orders_by_market still carries the book — so the orders are reloaded by the matcher at the next boot, on a market that no longer exists.

Do not rely on the delete to release customers' money. Cancel the book yourself first, and confirm inOrder is back to zero for the affected wallets before you delete anything.

Deleting a token

Deletes one token

There is no guard of any kind on this route. It passes the request straight to the generic delete helper. Specifically, it does not check whether:

  • any customer holds a balance in the currency;
  • any market is built on the token;
  • any wallet carries a deposit address derived for its chain;
  • any withdrawal is in flight against it.

ecosystem_token is paranoid, so the ordinary "Delete" row action is a soft delete (deletedAt is stamped) and "Permanent delete" sends force=true and destroys the row.

The tokens screen is rendered with paranoid mode off, so the "show deleted" toggle is not offered on it. A soft-deleted token vanishes from the list with no UI path back. Recovering it means clearing deletedAt in the database by hand.

What breaks the moment the row is gone, soft or hard:

  • Deposits and withdrawals for that chain and currency answer 404 — the lookup only sees live, enabled rows.
  • Address generation skips the chain, so new wallets get no address for it.
  • Balances stay exactly where they are, in MySQL and on-chain, with no way for the customer to move them.
  • A market built on the token is left pointing at an asset that no longer resolves.

If your intent is "stop offering this asset", set its status to false. Delete is for a token that was created in error and never held anything.

The bulk endpoints

Bulk-deletes markets, permanently
Bulk-deletes tokens

The bulk market delete is riskier than the single one in a specific way: it forces the same permanent delete, but it carries no copy-trading guard at all. The one check that exists on the single-row path is simply not there. Selecting rows with a checkbox and using the bulk action bypasses it.

Both bulk routes are reachable from the data table's selection toolbar. Treat the selection checkbox on these two screens as a loaded action, not a filter.

Retiring a chain

A chain is not one record. Retiring one means deciding what happens to the tokens on it, the master wallet that signs for it, and the customer addresses already derived on it.

Disabling the master wallet is not a kill switch

Admin → Ecosystem → Wallets → Master Wallets has a status toggle. Turning it off does two real things:

  • the chain diagnostics treat a disabled master wallet exactly like a missing one and downgrade the chain's withdrawal readiness to failed, with the reason Signing prerequisites missing: master wallet disabled;
  • address derivation for PERMIT tokens on that chain stops — the generator logs Skipping chain <CHAIN> - Master wallet not found or not enabled and issues no address, so new customers get no deposit address there.

What it does not do is stop signing. The gas-payer lookup used by the EVM withdrawal path fetches the master wallet by chain and never inspects its status, so a queued withdrawal on a disabled chain will still be signed and broadcast. If you need signing to stop, disable the chain's tokens — that is the check the withdrawal endpoint actually performs.

There is no delete route for a master wallet, and the screen offers no delete action. That is deliberate: the master wallet's HD material is what every PERMIT deposit address on the chain was derived from, and the encrypted blob in the database is the only copy.

Customer addresses survive everything

Disabling a token, a chain or a master wallet does not touch a single wallet.address entry or wallet_data row. The addresses stay valid on-chain, the keys stay in the database, and coins sent to them after you retire the chain still arrive — they just will not be detected or credited, because nothing is watching any more. Tell customers before you retire a chain, not after.

Deleting a custom EVM chain

Deletes an operator-defined EVM chain

This is the one delete in the addon with real custody guards, and both refuse with 400:

  • A master wallet exists for the chainCannot delete '<CHAIN>': a master wallet exists for this chain. Remove it first. Since there is no way to remove a master wallet from the panel, in practice a chain that ever had one cannot be deleted without a database edit. Disable it instead.
  • Any ECO wallet holds a positive balance in the chain's native currencyCannot delete '<CHAIN>': users still hold <CURRENCY> balances. Disable the chain instead, or have balances withdrawn first.

Past both guards it is a force delete followed by a live registry reload, so the chain disappears from the running process immediately.

Licensed chains (Solana, Tron, TON, Monero) have no delete at all — they are seeded rows, and the status toggle is the whole surface.

A retirement order that works

For a pair you are taking out of service permanently:

  1. Announce it. Customers with resting orders and on-chain balances need notice; nothing in the following steps notifies them.

  2. Disable the market. Admin → Ecosystem → Trading → Markets, status toggle. New orders stop immediately.

  3. Clear the book. Cancel every resting order on the symbol and confirm the holds came back — the affected wallets' inOrder should return to zero. Do not skip this: nothing later in the sequence does it for you.

  4. Let customers withdraw. Leave the tokens enabled and the chain running for as long as you told them they had.

  5. Disable the tokens. Deposits and withdrawals for that chain and currency now refuse. This is the point of no return for customers.

  6. Only then consider deleting, and only if the market and token genuinely hold nothing. Take a MySQL dump and a ScyllaDB snapshot first — the market delete is permanent and Scylla has no backup path in the product.

Permissions

Action Key
Delete a market, single or bulk delete.ecosystem.market
Delete a token, single or bulk delete.ecosystem.token
Enable or disable a market edit.ecosystem.market
Enable or disable a token edit.ecosystem.token
Delete or disable a custom EVM chain edit.ecosystem.blockchain

delete.ecosystem.market and delete.ecosystem.token are the two keys on this addon that no support role should hold. Grant them to the same people who hold your database credentials, and nobody else.