Like the hundred-headed serpent of Greek myth, Ladon generates as many addresses as you need.
Fast, minimal, multi-chain HD wallet CLI, library, and address-pool daemon — EVM, Bitcoin, Solana.
cargo install ladonladon has three sub-commands:
| Sub-command | Purpose |
|---|---|
derive |
Derive one or more addresses and print to stdout |
decrypt |
Decrypt an encrypted wallet file |
pool |
Run the address-pool daemon (requires [database] in config) |
All settings live in Config.toml (or any path passed with --config/-C).
Per-flag overrides are available on derive for ad-hoc use.
ladon --config /etc/ladon/Config.toml poolSee Config.toml for a fully annotated example.
Output goes to stdout. Redirect it to whatever format you need:
# JSON (default) — pipe or redirect
ladon derive --chain evm --num 5
ladon derive --chain evm --num 20 > wallet.json
# CSV
ladon derive --chain evm --num 20 --format csv > wallet.csv
# Plain text (one address per line)
ladon derive --chain solana --num 10 --format text > addresses.txt
# Encrypted output
ladon derive --chain evm --num 5 --encrypt --password "secret" > wallet.enc
# Bitcoin from existing mnemonic
ladon derive --chain btc --mnemonic "word1 ... word12"
# Specific indexes or ranges
ladon derive --chain evm --indexes "0,5,22-44,55,66-109"
# Watch-only from xpub
ladon derive --chain evm --xpub xpub6C...
# From xpriv
ladon derive --chain evm --xpriv xprv9s...| Flag | Default | Description |
|---|---|---|
--chain / -c |
evm |
evm, btc, solana |
--num / -n |
1 |
Number of addresses |
--index / -i |
— | Single specific index |
--indexes |
— | Comma-separated indexes/ranges |
--account |
0 |
BIP-44 account |
--change |
0 |
BIP-44 change |
--network |
bitcoin |
Bitcoin network |
--passphrase |
"" |
BIP-39 passphrase |
--strength / -s |
12 |
Mnemonic word count |
--solana-mode |
full |
full, cold-export, hsm-sim, pda |
--program-id |
— | Base58 program ID for PDA mode |
--xpub |
— | Watch-only from xpub |
--xpriv |
— | Derive from xpriv |
--format / -f |
json |
json, csv, text |
--encrypt |
false | Encrypt output |
--password |
— | Encryption password |
ladon decrypt wallet.enc --password "secret"
ladon decrypt wallet.enc --password "secret" > wallet.jsonThe pool sub-command runs a long-lived service that:
- Connects to a SQLite or Postgres database.
- Polls the pool table on a configurable interval.
- Derives and inserts new addresses when the count drops below
[pool].threshold. - Keeps the total at
[pool].targetaddresses per chain.
Your application removes rows from the pool table as it assigns addresses to users.
[derive.secret]
kind = "env"
var = "LADON_MNEMONIC"
[[derive.chains]]
name = "evm"
[pool]
target = 1000
threshold = 200
batch = 100
interval_secs = 10
[database.sqlite]
path = "data/addresses.db"
[database.sqlite.table]
name = "derived_addresses"
[database.sqlite.table.columns]
id = "id"
chain = "chain"
address = "address"
path = "path"
index = "index"
created_at = "created_at"Published images are available from GitHub Container Registry. The image does
not include a config file. Mount your environment-specific Config.toml at
runtime and provide secrets through environment variables.
docker run --rm \
-e LADON_MNEMONIC="word1 word2 ... word12" \
-e DATABASE_URL="postgres://user:password@host:5432/ladon" \
-v "$PWD/Config.toml:/app/Config.toml:ro" \
ghcr.io/melonask/ladon:latestFor SQLite-backed pool mode, also mount a writable data directory:
docker run --rm \
-e LADON_MNEMONIC="word1 word2 ... word12" \
-v "$PWD/Config.toml:/app/Config.toml:ro" \
-v "$PWD/data:/app/data" \
ghcr.io/melonask/ladon:latestThe default container command is pool. Override it to run other commands:
docker run --rm ghcr.io/melonask/ladon:latest derive --chain evm --num 5
docker run --rm \
-v "$PWD/prod.toml:/config/Config.toml:ro" \
ghcr.io/melonask/ladon:latest --config /config/Config.toml pool# Start the pool daemon with Postgres using deploy/docker-compose.yml
export LADON_IMAGE="ghcr.io/melonask/ladon:latest"
docker compose -f deploy/docker-compose.yml up -dSet LADON_IMAGE and LADON_MNEMONIC in the environment or in deploy/.env.
The compose file points DATABASE_URL at its bundled Postgres service. Edit it
if you want to use an external database.
The container uses restart: unless-stopped so it recovers automatically.
sudo cp deploy/ladon.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now ladonStore secrets in /etc/ladon/env:
LADON_MNEMONIC=word1 word2 ... word12
DATABASE_URL=postgres://ladon:secret@localhost/ladon
RUST_LOG=ladon=info
use ladon::{derive, decrypt_data, encrypt_data, Params, WalletOutput};
let wallet: WalletOutput = derive(Params {
chain: "evm".into(),
num: 5,
..Default::default()
})?;
for key in &wallet.keys {
println!("{}: {}", key.index, key.address);
}| Chain | Default path |
|---|---|
| EVM | m/44'/60'/0'/0/* |
| Bitcoin | m/44'/0'/0'/0/* |
| Solana | m/44'/501'/0'/0' (hardened, SLIP-0010) |
- Private keys are zeroized on drop.
- Use
--solana-mode cold-exportor--encryptwhen storing derive output. --passwordon the CLI is visible in shell history. Prefer environment-specific secret management (LADON_MNEMONIC, vault agents, etc.) in automation.- Ed25519 derivation follows SLIP-0010 (all path segments hardened).
- In the pool daemon, the mnemonic is never written to disk — it lives only in the process environment.
MIT