Master wallets and the vault

How Ecosystem encrypts private keys, what the master wallet actually pays for, why it is not a reserve, and the two ways an install loses every wallet it has.

6 min readUpdated 3 August 2026master-wallet, vault, kms, encryption, gas

Every private key this addon creates — master wallets, per-user deposit addresses, custodial secrets — is encrypted with one key. That key is itself encrypted with a passphrase, stored in .env, and held in memory only while the process runs. The vault is the whole security model, so it comes first.

The vault

ENCRYPTED_ENCRYPTION_KEY is a random 32-byte key, AES-256-GCM-encrypted under a passphrase-derived key (PBKDF2, 100,000 iterations, SHA-512) and stored as four colon-separated hex parts: IV, auth tag, ciphertext, salt. Generate it once, before you create anything:

node ./scripts/kms/generate.mjs

At boot, if both ENCRYPTED_ENCRYPTION_KEY and ENCRYPTION_KEY_PASSPHRASE are present, the backend decrypts the key into memory and the vault is unlocked. If the passphrase is absent, the vault starts locked and stays locked until an administrator supplies it.

Unlocks the vault for the running process by supplying the passphrase

The admin console masthead at /admin/ecosystem shows the state directly: a green Vault Active badge, or an Initiate Vault button that opens the passphrase prompt. The unlock is per process and per restart — it is not persisted anywhere.

What a locked vault breaks

Everything that touches key material, and nothing else:

Blocked while locked Still works
Creating master wallets Trading — orders, fills, the order book
Generating user deposit addresses Existing balances and internal transfers
Deploying tokens and custodial contracts Market and token administration
Signing withdrawals Deposit detection (crediting is queued)

That last row is the one that catches people out. Deposits are still noticed; they simply cannot be credited into a wallet that has no address yet, and a new user hitting the deposit page gets an error rather than an address.

The passphrase trade-off

Setting ENCRYPTION_KEY_PASSPHRASE in .env buys unattended restarts — withdrawals resume by themselves after a deploy or a crash. It costs you the property that the passphrase never touches disk. Leaving it out means an operator must be available after every restart, including the ones nobody planned.

Most single-operator installs set it. Installs with a real on-call rotation usually do not. Either is defensible; drifting into the second by accident is not — an install that silently stopped signing withdrawals three days ago looks identical to a quiet week.

Losing the passphrase. The key cannot be decrypted, so nothing encrypted with it can be read. Every deposit address on the platform becomes unusable and the coins at those addresses are unreachable.

Regenerating the key. Running the generator against an .env where ENCRYPTED_ENCRYPTION_KEY has been cleared produces a new key. Existing wallet blobs were encrypted with the old one and will never decrypt again.

There is no recovery, no support workaround, and no partial recovery. pnpm env-manager refuses to edit either variable for this reason. Back up .env somewhere your database backup does not live, and keep the passphrase somewhere else again.

What the master wallet actually is

One master wallet per chain. It is created from the admin panel, its key material is generated on the server, encrypted immediately, and stored in ecosystem_master_wallet.data. That encrypted blob is the only persisted copy — nothing is ever written to a plaintext file on disk, deliberately, because any file-read or backup leak would drain every wallet on the install.

The master wallet does four jobs:

  1. Pays gas for token withdrawals. When a customer withdraws a permit-capable token, the tokens move out of the customer's own address and the master wallet acts only as gas payer.
  2. Deploys token contracts. Token deployment is signed by the master wallet.
  3. Deploys custodial contracts. Each custodial wallet contract is constructed with the master wallet's address as its owner.
  4. Holds the chain's native coin for the above.

It does not hold customer funds. Customer coins live at each customer's own HD-derived deposit address. The admin overview therefore reports master balances as a per-chain gas figure, never as a reserve to compare against customer liability — that comparison would be meaningless on this platform, and shipping it would be a confident wrong number on the one page that must not carry one.

The practical consequence: an empty master wallet does not mean the platform is insolvent. It means token withdrawals on that chain will fail until you top it up with native coin.

Creating one

  1. Unlock the vault first. Creating a wallet encrypts its key material immediately, and a locked vault fails the operation partway.

  2. Go to Admin → Ecosystem → Wallets → Master Wallets and create a wallet, choosing the chain.

    Generates, encrypts and stores a master wallet for a chain
  3. One per chain, enforced. A second attempt on the same chain returns 409. An unknown or disabled chain is rejected with 400 before any key material is generated — enable the chain first, then create its wallet.

  4. Fund it with the chain's native coin. ETH on Ethereum, BNB on BSC, MATIC on Polygon, TRX on Tron, SOL on Solana, and so on.

  5. Back up the platform. The wallet exists in exactly two places now: the database row, and whatever encrypts it in .env. A database backup without the .env is not a backup of this wallet.

How the key is generated depends on the chain family. EVM chains get a random HD wallet — mnemonic, extended keys, chain code and derivation path are all stored in the encrypted blob, because per-user deposit addresses are derived from it. UTXO chains get a UTXO wallet. Solana, Tron, TON and Monero each delegate to their own chain service, which must be installed for the creation to succeed at all.

There is no "reveal seed phrase" screen. The encrypted blob is the record. If your operational model requires an offline copy of the seed, take it from the database and decrypt it deliberately — do not expect the UI to hand it over.

Managing them

The master wallet list shows chain, currency, address, balance and status. Balances are fetched live and cached in Redis for one minute, so a top-up will not appear instantly.

Lists master wallets
Live balance for a master wallet
On-chain transaction history for a master wallet address

Disabling a master wallet is a real operational lever, not a soft delete. The diagnostics treat a disabled wallet the same as a missing one and downgrade the chain's withdrawal readiness to failed. Use it to stop signing on a chain you believe is compromised.

Deleting one is destructive in a way the confirmation dialog cannot fully convey: per-user deposit addresses on EVM chains are derived from the master wallet's HD material. Losing the master wallet row loses the ability to derive — and therefore to spend from — every address under it.

Keeping gas topped up

A per-chain routine worth having:

  • Watch the chain health section of /admin/ecosystem. A chain whose gas payer is empty is flagged there, because token withdrawals on it will fail.
  • Native-coin withdrawals do not need the master wallet — they are signed by the customer's own deposit address and the network fee comes out of the balance being withdrawn. So a chain with an empty master wallet can still process native withdrawals while token withdrawals queue up behind an insufficient-gas error. The symptom is selective, not total.
  • Custodial wallets need their own native balance as well, for the tokens they hold. See Deposit wallets.
  • Deploying a token or a custodial contract costs considerably more gas than a transfer. Fund before you deploy; an out-of-gas deployment returns a 400 and wastes the attempt.