The admin console
Every screen under Admin → Ecosystem — the custody overview, chain diagnostics, master wallets, the legacy custodial contracts, the private ledger, unspent outputs, markets, tokens and the custody settings — and the permission each one needs.
Enabling the extension adds an Ecosystem entry to the admin navigation. It
opens at /admin/ecosystem and carries four groups and a settings screen.
Overview /admin/ecosystem
Blockchains
Requirements /admin/ecosystem/blockchain
Custom EVM Chains /admin/ecosystem/custom-chain
Wallets
Master Wallets /admin/ecosystem/wallet/master
Custodial Wallets /admin/ecosystem/wallet/custodial legacy — shown only while draining
UTXO /admin/ecosystem/utxo
Ledgers /admin/ecosystem/ledger
Trading
Markets /admin/ecosystem/market
Tokens /admin/ecosystem/token
Settings /admin/ecosystem/settingsOverview — the custody console
This is not a statistics page. It answers one question in three parts, and every figure on it comes from a single server-side aggregate rather than arrays counted in the browser.
Can we pay people? Per asset, what customers are owed — the sum of balance and in-order across ECO wallets — against what the platform records itself as holding for them, minus the part of that record the withdrawal engine has already spoken for. Each asset is rated covered, partial or unbacked, and the page rolls those up as a count of assets rather than one money figure, because the payload carries no price feed and adding BTC to USDT would be a lie with two decimals on it.
Is anything stuck? The ECO withdrawal pipeline by status, with the rows the automatic recovery deliberately refuses to touch broken out separately. Those are the ones that wait for a human forever, so they get their own count and a link straight into the queue.
Is any chain degraded? Per chain: whether its RPC is configured, whether its extension is enabled, whether it still holds custody, whether its gas payer has a balance, and what its last stored diagnostics run said.
The masthead also carries the vault state. A green Vault Active badge means key material can be decrypted; an Initiate Vault button means it cannot, and clicking it opens the passphrase prompt.
The deduction for committed balance is clamped at zero. A net-negative private ledger says the platform's records understate what an address holds — a bookkeeping discrepancy, not spendable coins — so it is never counted as extra cover. The page may under-state coverage. It will not over-state it.
Blockchains → Requirements
The per-chain configuration and diagnostics console, covered in detail in Supported blockchains. Two things worth repeating here.
It distinguishes required from optional keys, and names the condition
that promotes an optional key to required — for example, BTC_NODE_USER is
optional until BTC_NODE=node. It also flags keys that are dead: read by no
code path, present in shipped .env files, and safe to remove. Cargo-culted
values are a real source of confusion and this page is the authority on which
ones matter.
The live test is stricter than a connectivity check. It reports readiness per platform flow — deposits, withdrawals, transaction history — and a chain whose RPC answers perfectly still fails if the vault is locked or the master wallet is missing, because a customer still cannot get their money out.
Blockchains → Custom EVM Chains
- Add a chain with no code change
- Disable removes the chain from the live registry and keeps everything else
Add, edit, enable and delete operator-defined EVM chains. The create form has a test action that probes the candidate RPC, WebSocket, chain ID and explorer before you commit the row. Use it — a chain saved with a mismatched chain ID will fail at provider construction later, further from the cause.
Wallets → Master Wallets
- One master wallet per chain — a second attempt on the same chain is refused
- Balance is the chain's gas figure, not a customer reserve
One row per chain: chain, currency, address, balance, status. Create, view, enable and disable. Balances are live reads cached for one minute in Redis.
Disabling a wallet is an operational lever with teeth: the diagnostics treat a disabled master wallet the same as a missing one and downgrade the chain's withdrawal readiness to failed. That is the correct way to stop signing on a chain you no longer trust.
Wallets → Custodial Wallets
- Status is ACTIVE, INACTIVE or SUSPENDED
A legacy screen. Custodial contracts were the shared deposit address for
NO_PERMIT tokens; every EVM token now deposits to the customer's own address,
so no contract receives a deposit any more and none can be created — the
create route refuses in every mode. What remains is whatever balance the
existing contracts still hold, and this screen is where you drain it.
Each row shows its master wallet, address, chain, network and status —
ACTIVE, INACTIVE or SUSPENDED. The per-contract sweep moves the
contract's entire balance of one token to the chain's master wallet, which the
withdrawal engine then uses as a source after the customers' own addresses.
currency. Refused while the custodial mode is off.Whether the screen appears at all is the custodial mode on the
Settings screen. drain keeps the contracts as a last-resort
withdrawal source — after the customer's own address, other customers' addresses
and the master wallet — and keeps this entry in the menu. off hides the entry
and makes the custodial routes refuse. To convert immediately, sweep every
contract here, then set the mode to off; to let the balances run down
instead, leave it on drain.
The two older transfer routes — native coin or a token from a contract to any recipient — still exist and are API-only; see the gas runbook.
Wallets → UTXO
Every unspent output the platform tracks on Bitcoin, Litecoin, Dogecoin and
Dash, with amount, script, status (UNSPENT, LOCKED, SPENT) and source
(DEPOSIT, CHANGE, CONSOLIDATION, SYNC).
This is a diagnostic screen, not a routine one. You come here when a UTXO
withdrawal reports that it is uneconomical, to see whether the wallet's inputs
are genuinely too fragmented, or when a payout is stuck to check whether its
inputs are still LOCKED against a transaction that never completed.
Wallets → Ledgers
The private ledger — offchainDifference per wallet, index, currency and
network. A positive value is balance the platform still records at an address
but has already paid away from it.
It matters because the withdrawal engine subtracts it before it will source a payout, and the overview subtracts it before reporting coverage. If an asset shows as under-covered on the overview but the raw record looks fine, this is the screen that explains the gap.
Trading → Markets and Tokens
Covered in Tokens and markets. Both are standard data tables with create, view, edit, status toggle and delete.
The token table is also where the holders view lives, and where fees, limits and
precision are edited after creation — including the optional fee.network, the
flat network fee in token units that a token with no USD price is charged at
withdrawal instead of a gas estimate.
Settings
The custody settings of the addon, at /admin/ecosystem/settings: four tabs,
five writable keys, and two read-only maps the withdrawal engine maintains
itself. Every value is a row in the platform's settings table.
Addresses — ecosystemAddressModel: per_user (one key per customer, the
same address on every EVM chain) or per_currency (the legacy layout, one
address per wallet per chain). Switching only affects addresses issued
afterwards. Deposit wallets has
the reasoning and the defaults.
Withdrawals — ecosystemEvmMover (auto, eip7702, permit, jit_gas):
how a token leaves a customer's gas-less address. ecosystemChargeNetworkFee
(default on) and ecosystemNetworkFeeBufferPercent (default 20, 0–200): whether
the customer pays the estimated gas in the withdrawn token, and the head-room
on the estimate that is refunded after confirmation. See
How a token is moved out
and Network fee.
Custodial wallets — ecosystemCustodialMode: drain or off, beside a
summary of how many legacy contracts exist and how many are still active, with a
link to the Custodial Wallets screen. The default is materialised from the
database the first time the settings are read: drain when the install has
custodial contracts, off when it has none.
Diagnostics — two read-only tables the EIP-7702 mover writes. Delegate
contracts is the EcosystemDelegate address per chain and network, deployed
by the master wallet on first use. Chains refusing EIP-7702 lists each chain
whose node rejected a type-4 transaction and when; the verdict is remembered for
24 hours while the fallback mover is used. The PUT refuses both keys, so an
operator cannot point every withdrawal on a chain at a contract nobody audited.
The screen opens with access.ecosystem.settings; the two routes carry
view.ecosystem.settings and edit.ecosystem.settings. Grant all three
together.
Upgrading an install that already has addresses or custodial contracts? Custody migration walks the four tabs in the order to take them.
Order maintenance
Ecosystem orders are visible from the core's finance section at
/admin/finance/order/ecosystem, and the addon adds a maintenance endpoint of
its own.
The corrupted rows this cleans up are an artefact of ScyllaDB's upsert
behaviour: a partial write creates a ghost row with a valid primary key and null
everything else. Run it with dryRun: true first — it will tell you how many it
found without deleting anything.
Permissions
backend/seeders/20240402234643-permissions.js writes 44 permission rows
whose key contains ecosystem, and 44 is what you count in Admin → Roles. Only
28 of them are enforced on an admin API route — those are the keys that
decide whether a request succeeds, and they are the right-hand column below.
Eight more are page gates and nothing else: an access.ecosystem.* key exported
from a permission.ts beside the screen, which decides whether the page opens
but guards no endpoint. The remaining eight are wired to table buttons the code
switches off, so granting them changes nothing — the order desk
and the private ledger name their share of them.
They all ship ungranted — a newly enabled addon is reachable by Super Admin only until an operator grants its keys. See Permissions for how the key format maps to screens.
| Area | Opens the screen | Enforced on the API |
|---|---|---|
| Console | access.ecosystem |
access.ecosystem |
| Vault | — | manage.ecosystem.kms |
| Blockchains | view.ecosystem.blockchain (list), access.ecosystem.blockchain (one chain) |
view.ecosystem.blockchain, edit.ecosystem.blockchain |
| Master wallets | access.ecosystem.master.wallet |
view…, create…, edit… |
| Custodial wallets (legacy) | access.ecosystem.custodial.wallet |
access…, view…, create… (the route now always refuses), edit… (status changes and the sweep) |
| Settings | access.ecosystem.settings |
view.ecosystem.settings, edit.ecosystem.settings |
| UTXO | access.ecosystem.utxo |
view.ecosystem.utxo, create.ecosystem.utxo, edit.ecosystem.utxo |
| Private ledger | access.ecosystem.private.ledger |
view.ecosystem.private.ledger, edit.ecosystem.private.ledger |
| Tokens | access.ecosystem.token |
view.ecosystem.token, create…, edit…, delete… |
| Markets | access.ecosystem.market |
view.ecosystem.market, create…, edit…, delete… |
| Orders | access.ecosystem.order |
view.ecosystem.order, manage.ecosystem.order |
The two columns are not alternatives, and neither implies the other. A role with
view.ecosystem.master.wallet and no access.ecosystem.master.wallet can read
the wallets through the API but cannot open the screen; grant only the access key
and the page opens onto a table whose first request 403s. Grant them in pairs.
The token and market create and edit pages also carry their matching
create. / edit. key as a page gate, so those four are checked twice.
The gate is compiled into a route manifest at build time and the running process holds it in memory. Granting a key in the UI is not enough on its own — restart the API process for it to take effect.
Note that edit.ecosystem.blockchain covers both enabling a licensed chain and
creating custom EVM chains. It is effectively "may change which chains this
platform custodies", and should be granted narrowly.
POST /api/admin/ecosystem/wallet/custodial/{id}/transfer/native and its /token
sibling carry access.ecosystem.custodial.wallet — not an edit. or manage.
key. That is why access… appears in the API column above, and it means anyone
who can open the Custodial Wallets screen can send coin and tokens out of a
legacy contract to any address. Treat that access key as a spend permission,
because that is what it is. The sweep to the master wallet is the action that
carries edit.ecosystem.custodial.wallet, and it can only ever pay the master
wallet.
Related
- Operations — cron jobs, engine placement, repair scripts
- Blockchains — the diagnostics console in depth
- Troubleshooting — what to do when a screen tells you something is wrong