Chain families: what actually differs per chain

Per-chain behaviour rather than per-chain configuration — which deposit monitor runs, where the address comes from, how deep a confirmation is, who signs a withdrawal and who pays the fee.

6 min readUpdated 3 September 2026chains, evm, utxo, solana, tron, deposits, eip-7702

Supported blockchains covers how a chain is configured. This page covers how a chain behaves — the differences that decide what a deposit problem looks like, why a withdrawal needs gas from one place on one chain and another place on the next, and which chains can do things the others cannot.

Everything here comes from ecosystem/utils/chains.ts, ecosystem/utils/wallet.ts, ecosystem/utils/token-move.ts, ecosystem/utils/evm-mover.ts and the deposit-monitor factory.

Which deposit monitor runs

Seven monitor implementations ship. createMonitor picks one from the chain symbol alone, in this order — the first match wins:

Order Monitor Chains
1 UTXODeposits BTC, LTC, DOGE, DASH
2 SolanaDeposits SOL
3 TronDeposits TRON
4 MoneroDeposits XMR
5 TonDeposits TON
6 MODeposits MO, only for a non-NATIVE token
7 EVMDeposits everything else

Two consequences of "everything else". A native MO deposit is watched by EVMDeposits, not MODeposits — the MO-specific monitor exists for its tokens only. And every custom EVM chain you add from the admin panel falls into the same default branch with no per-chain code, which is exactly why adding one needs no code change.

The same factory is used by the live deposit-page session and by the background scanner, so the two can never disagree about what watches an address.

Where a deposit lands

At the customer's own address, on every chain and for every token type. There is no list to consult and no shared platform contract: a NO_PERMIT token on an EVM chain deposits to exactly the same kind of address as a PERMIT one or the chain's native coin. The custodial contracts an older install deployed for NO_PERMIT deposits receive nothing now; they are only a withdrawal source while they drain.

contractType still matters, but only at withdrawal, where it decides which movers may be used to get the token out of a gas-less address.

Flag a token PERMIT that does not implement EIP-2612, and the engine offers it a permit mover it cannot sign; under the default auto order EIP-7702 is tried before it and just-in-time gas after it, so the withdrawal still goes out. Flag a permit-capable token NO_PERMIT, and it simply never gets the permit option. On Solana, Tron, TON, Monero, MO and the UTXO chains the flag is ignored entirely — those chains have no movers to choose between. The mechanism is in Deposit wallets.

Confirmation depth

The crediting watchdog compares an on-chain transaction's depth against the chain's configured confirmations, and credits nothing below it.

Chain Required confirmations
BTC 3
LTC 6
DOGE 6
DASH 6
Everything else, including every EVM chain 12 (the fallback)
Custom EVM chains whatever the chain row says, default 12

Only the four UTXO chains name a depth of their own. Every other built-in chain leaves it unset and inherits 12 from the verification job's default. Custom EVM chains are the exception that can be tuned: the create form at Admin → Ecosystem → Blockchains → Custom EVM Chains has a Confirmations field, defaulting to 12 and accepting 1 to 1000.

"The deposit shows as pending and never credits" on a fast chain is usually this number: twelve blocks is a long wait on some networks and a short one on others, and there is no per-chain override for the built-ins.

Where the address comes from

This is the difference operators get wrong most often, because "HD-derived from the master wallet" is only true for some of the cases, and on EVM chains it depends on the address model (ecosystemAddressModel, Admin → Ecosystem → Settings → Addresses).

Case Address source wallet_data.index
EVM token, any contractType, model per_currency The master wallet's mnemonic, child at lastIndex + 1; the master wallet's lastIndex is incremented in the same transaction the derived index
EVM coin, NATIVE, model per_currency A freshly generated HD wallet of its own, unrelated to the master wallet 0
Any EVM token or coin, model per_user The customer's one canonical EVM key — derived from the master wallet the first time any EVM chain asks, copied for every (wallet, chain) pair after that, so the address is identical on every EVM chain the canonical key's index
UTXO chain A freshly generated P2PKH key per wallet and chain 0
SOL, TRON, TON, XMR The chain service's own createWallet() 0

Every one of these private keys is encrypted with the vault key and written to wallet_data. The encrypted blob is the only persisted copy. An entry that was written under the old shared-deposit model — a NO_PERMIT token's { balance } with no address — is issued an address the next time the wallet is opened and keeps its balance.

So the master wallet is load-bearing in a narrower way than it looks: losing it loses every deposit address that was derived from it, and disabling it stops new ones being derived (Skipping chain <CHAIN> - Master wallet not found or not enabled) — though under per_user a customer who already holds an EVM key is given that key without the master wallet being consulted. UTXO deposit keys, and native-coin keys issued under per_currency, are independent of it.

Tron is the special case worth naming: PERMIT and NO_PERMIT TRC20 tokens both route to the user's own TRON address, and the flag plays no part there.

Who signs, and who pays the network fee

Chain and type Signer Network fee paid by
EVM NATIVE The user's own address The user — actual gas is deducted from their balance after the receipt
EVM token, PERMIT or NO_PERMIT The source is chosen in order — the customer's own address, another customer's address, the master wallet's own token balance, a draining legacy custodial contract — and each is checked on-chain. From a customer address the token leaves by EIP-7702 (one transaction sent by the master wallet), by permit (PERMIT tokens), or by just-in-time gas (the address's own key, after the master tops it up) The master wallet on-chain, in native coin; recovered from the customer in the withdrawn token as the network fee when ecosystemChargeNetworkFee is on (the default)
UTXO Each input signed with its own wallet's key, in one PSBT Deducted from the inputs; one fee per batched transaction
SOL native The user's own SOL address The user — the amount is reduced to reserve the fee if needed
SOL SPL token The user's own SOL keypair, decrypted from wallet_data — it is the transfer authority; the master wallet only co-signs The master wallet, as the transaction's fee payer
TRON The address holding the coins — the user's own, or another pooled address when theirs cannot cover it That address, in TRX, topped up from the master wallet when short
TON, XMR The user's own wallet from wallet_data That wallet

Three operational notes fall out of this table.

A native withdrawal does not need a master wallet. The EVM path resolves only token metadata for NATIVE, deliberately, so a chain with no master wallet can still process native withdrawals. Everything else on that chain cannot.

On EVM, every token path spends the master wallet's gas. A customer's address holds no native coin, and a draining legacy custodial contract holds tokens but is not the gas payer either. Keep the master wallet funded and watch it on the chains that see token withdrawals; with the network fee charged to the customer the gas comes back in the token, but only after the coin was there to spend.

Tron requeues rather than fails when the master wallet is short. If the signing address needs TRX for fees and the master wallet cannot cover the top-up, the withdrawal is left PENDING with WITHDRAWAL_REQUEUED and retried later rather than failed and refunded. A queue of Tron withdrawals that never moves is a master wallet with no TRX.

Which chains can deploy a token

Deployment reads a bundled contract for the chain. A chain with no contract entry answers Smart contract file not found for chain <CHAIN> and can only import an existing asset.

Can deploy Contract name recorded
ETH, POLYGON, FTM, OPTIMISM, ARBITRUM, BASE, CELO, MO, and every custom EVM chain ERC20
BSC BEP20
HECO HRC20
CRONOS CRC20
SOL SPL

Import-only: TRON, RSK, BTC, LTC, DOGE, DASH, XMR, TON.

Deployed EVM tokens use the bundled contract, which supports permit, so they are always recorded as contractType: PERMIT.

Rate limits and self-termination

The non-EVM chains defend themselves against provider limits in ways that look like outages if you do not know about them.

Tron stops its own monitor. Both the TRX and the TRC20 deposit monitors count consecutive errors and stop themselves at ten, logging Max consecutive errors (10) reached for <address>, stopping monitor. On mainnet without TRON_API_KEY, anonymous TronGrid quotas will reach ten errors quickly, and once stopped the monitor does not restart on its own — the address is picked up again on the next deposit-page session or background sweep.

TON serialises everything to roughly one request per second. Wallet operations go through a static queue with a one-second delay between calls, which throttles deposit polling and the withdrawal confirmation loop alike. That loop polls ten times at ten-second intervals and then gives up with Transaction hash could not be retrieved after 10 retries. TON deposit monitoring polls on a 60-second base interval, doubles that interval on each consecutive failure up to sixteen times, and stops itself at ten consecutive errors with Too many consecutive errors for <address>, stopping deposit monitoring. Set the Toncenter API key for the active network.

Monero's wallet RPC is a startup gate. If get_version fails the whole chain is disabled rather than degraded.

BlockCypher's anonymous quota cannot sustain polling. Roughly 3 requests per second and 100 per hour, which is why BLOCKCYPHER_TOKEN is effectively required on DOGE and DASH — they have no other working provider.

Licensing and the chain service module

Solana, Tron, TON and Monero are separate products, and each is gated twice.

  1. The licence. Activation writes lic/<productId>.lic; without it the status toggle refuses with 403. Product IDs: Solana 54514052, Tron 54577641, Monero 54578959, TON 55715370. All four ship with status: false.
  2. The code. Each chain's service module has to be present in the install.

The second gate is the one that surprises people, because a valid licence does not put the code on disk. The diagnostics check for it explicitly and report:

Chain service installed: no
The SOL blockchain addon code is not installed (@b/blockchains/sol)

Every flow on that chain is dead in that state — address generation, deposits, withdrawals and balances alike — regardless of what the licence says. Run Admin → Ecosystem → Blockchains → Requirements for the chain and read this row before debugging anything else on it.