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.

5 min readUpdated 6 August 2026chains, evm, utxo, solana, tron, deposits

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/custody.ts, ecosystem/utils/wallet.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

One list decides whether a customer is given their own address or a shared platform contract. NON_CUSTODIAL_CHAINS holds nine symbols:

SOL   TRON   TON   XMR   MO          own wallet service, per-user address
BTC   LTC    DOGE  DASH               no contracts at all, per-user address

A deposit uses a shared custodial contract only when both are true: the token's contractType is NO_PERMIT, and its chain is not on that list.

A custodial wallet is a deployed contract, and there are none on Solana, Tron, TON, Monero, MO or any UTXO chain. Mis-flagging a token there as NO_PERMIT does not break deposits — the platform still issues the user's own address — because the rule is a conjunction, not a contractType test. On an EVM chain the same mis-flag is a real problem, and it is covered 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 one of the five cases.

Case Address source wallet_data.index
EVM token, PERMIT 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 A freshly generated HD wallet of its own, unrelated to the master wallet 0
EVM token, NO_PERMIT No address is stored; a custodial contract is assigned per deposit session
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.

So the master wallet is load-bearing in a narrower way than it looks: losing it loses every PERMIT deposit address on its chain, and disabling it stops new ones being derived (Skipping chain <CHAIN> - Master wallet not found or not enabled). Native and UTXO deposit keys 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, because Tron has neither EVM permit semantics nor custodial contracts.

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 PERMIT The token contract, transaction sent by the master wallet The master wallet
EVM NO_PERMIT The custodial contract that holds enough of the token, transaction sent by the master wallet The master wallet
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, both token paths spend the master wallet's gas. Custodial contracts hold the tokens; they are not the gas payer. Keep the master wallet funded, and watch it on the chains that see token withdrawals.

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.