Skip to content

merod CLI Reference

merod is the node daemon. Every invocation takes two global arguments, then a subcommand.

Flag Type Default Notes
--home <PATH> path platform node directory Directory for config and data. Env: CALIMERO_HOME.
-n, --node <NAME> name — (required) Name of the node; its home is <home>/<NAME>.
Terminal window
merod --home data/ --node node1 <subcommand>

Subcommands: init, config, run (alias up), kms, account.

Initialize node configuration: creates the home directory, generates an ed25519 identity keypair, writes config.toml, opens the datastore, and provisions the node’s account root (see account).

Flag Type / values Default Purpose
--boot-nodes <ADDR> multiaddr (repeatable) Explicit bootstrap node multiaddrs.
--no-account-root bool false Start with no account root: the root stays in cold storage and this node’s device is enabled by a certificate signed elsewhere. Such a node cannot certify a device or be the holder half of a pairing.
--boot-network <NETWORK> calimero-dev, ipfs calimero-dev Use a known network’s bootstrap nodes.
--swarm-host <HOST> IP list 0.0.0.0,:: Hosts to listen on for the libp2p swarm.
--swarm-port <PORT> u16 2428 Swarm (peer-to-peer) port.
--server-host <HOST> IP list 127.0.0.1,::1 Hosts to listen on for the RPC server.
--server-port <PORT> u16 2528 RPC / admin / JSON-RPC / WS / SSE port.
--auth-mode <MODE> proxy, embedded proxy Authentication mode for server endpoints.
--auth-storage <KIND> persistent, memory persistent Embedded-auth storage backend (only with --auth-mode embedded).
--auth-storage-path <PATH> path auth Embedded-auth storage path (persistent storage only).
--mdns bool false Enable mDNS local discovery (LAN / offline dev; off by default).
--advertise-address bool false Advertise the node’s observed address.
--external-address <MULTIADDR> multiaddr (repeatable) Static external multiaddrs to advertise. Pair with --advertise-address for them to be advertised.
--rendezvous-registrations-limit <N> usize 3 Max rendezvous registrations.
--relay-registrations-limit <N> usize 3 Max relay registrations.
--autonat-probe-interval <SECS> u64 10 Interval between AutoNAT probes, in seconds.
--autonat-max-candidates <N> usize 5 Max untested address candidates to probe with AutoNAT.
--force bool false Wipe and re-initialize even if the directory already exists.
--mode <MODE> standard, read-only standard Node operation mode (read-only disables JSON-RPC execution).
Terminal window
# Initialize with the default network and ports
merod --node node1 init
# Initialize with custom ports
merod --node node1 init --server-port 2528 --swarm-port 2428

Update keys in the node’s config.toml. Arguments are key=value pairs where the key is a dotted TOML path. The change is validated by reloading the resulting config before it is written.

Terminal window
# Quote the argument so your shell does not glob the brackets
merod --node node1 config "server.listen=['/ip4/127.0.0.2/tcp/3000', '/ip6/::1/tcp/3000']"

There are no flags — only positional key=value arguments (repeatable).

Run the node: loads and validates config.toml, then starts the swarm and RPC server. Foreground process. Alias: up.

Flag Type / values Default Purpose
--auth-mode <MODE> proxy, embedded Override the authentication mode from config.toml for this run.
--mock-tee bool false DEV/TEST ONLY. Produce/accept mock TEE attestation quotes. Env: MEROD_MOCK_TEE.
Terminal window
merod --node node1 run
merod --node node1 up --auth-mode embedded

KMS (key management service) operations for TEE-enabled nodes.

Probe the KMS attestation and storage-key fetch flow against the node’s configured TEE/KMS settings.

Flag Type Default Purpose
--kms-url <URL> URL Override the configured KMS URL for this probe.
--json bool false Emit the probe result as machine-readable JSON.
Terminal window
merod --node node1 kms probe
merod --node node1 kms probe --json

The account-root command family. These are the operations whose whole point is that the signing key does not have to reach a running node — so most of them can run on a machine with no node at all.

Terminal window
merod --node node1 account <export|import|device|sign-cert|revoke-proof|warrant>

Exactly two places, and nothing else mints one:

How Result
merod init Provisions a root. Every ordinary node.
merod account import Restores one from a 24-word phrase.
merod init --no-account-root No root, deliberately. The root stays in cold storage; this node’s device is enabled by a certificate signed elsewhere.

Nothing provisions a root lazily. Any path that needs one and doesn’t find one fails with an error naming these three options, rather than generating a key that certifies nothing anyone recognises.

A root-free node can still name its account — a certified device row answers before the root fallback. What it cannot do is certify a device (its own or anyone’s), so it cannot self-enrol or be the holder half of a pairing.

Certify a device for an account, offline. This closes a specific gap: pair-complete mints certificates but needs a running node holding the root, so a thin client — a phone, a script, anything that runs no application and joins no group — had no way to obtain the credential it needs to present itself.

There are two independent choices, which is why the flag list looks larger than the number of real modes.

Mode Flags Who ever holds the device secret
Certify --device --sign-pk --kem-pk (all three) The client. It minted its own keypairs and sent you only public halves.
Generate --generate (conflicts with the three above) This machine. It mints the device and prints the secret to stdout.

Prefer Certify. --generate exists for clients that cannot mint their own — a provisioning script, a fresh install — and it prints a signing key, so the holder is trusting this machine with it. A client that can mint its own device should, because then the secret never exists anywhere but there.

Mode Flag Requires
Node’s store (default) A stopped node that holds the root.
Recovery phrase --from PATH Nothing. Opens no datastore, so it runs on a machine with no node — no home, no store, no init.

--from is what makes an air-gapped signing machine possible, and it composes with either choice above.

Terminal window
# Certify a client's own device, on a machine with no node (the recommended shape).
merod account sign-cert \
--device <64-hex DeviceId> \
--sign-pk <64-hex ed25519 public key> \
--kem-pk <64-hex x25519 public key> \
--from ./phrase.txt
# Mint and certify in one step, for a client that cannot mint its own.
merod account sign-cert --generate --from ./phrase.txt
# Read the root from a stopped node's store instead of a phrase.
merod --node node1 account sign-cert --device <HEX> --sign-pk <HEX> --kem-pk <HEX>
# Re-issue after a device re-key. The epoch must strictly advance.
merod account sign-cert --device <HEX> --sign-pk <HEX> --kem-pk <HEX> \
--device-epoch 1 --from ./phrase.txt

Output is the hex credential on the first line, then Account:, Device:, and — under --generate only — Secret:, followed by a hint showing the meroctl context intent invocation that consumes it.

  • It cannot check the device id — for two separate reasons. The signer does know the account: it derives it from the root it holds, and prints it on the Account: line, so an operator can at least confirm the account is the intended one. What it cannot confirm is the --device value.

    DeviceId is H(account ‖ nonce) where the nonce is 16 random bytes chosen by whoever minted the device, and the client sends only deviceId, signPk and kemPk — never the nonce. So the signer cannot recompute the id to compare it. Separately, the id deliberately excludes the keys so that a device survives a re-key, which means the id cannot be checked against --sign-pk / --kem-pk either.

    Consequence: nothing here can tell a mistyped id from a real one. A certificate naming a device nobody holds is inert rather than dangerous — there is no matching secret, so it authorises no one. The failure mode is a wasted certificate, not a forged one.

  • Epoch 0 only. The certificate is signed at key epoch 0 with an empty handoff chain. An account whose root has rotated needs the chain up to the signing epoch, and nothing in this CLI produces one.

  • --device-epoch must strictly advance. The projection refuses a link that does not advance it, so re-issuing at the same epoch is inert rather than a rollback.

Why any of this works offline: the certificate is self-certifying — it carries the genesis and the root-key chain, so a verifier checks it from the account id alone with no folded state. That is why it does not matter who publishes or presents it.

Sign a device revocation offline, to be published by any node. Same shape and same reasoning as sign-cert, and also epoch 0 only. --from means it needs no store at all.

Terminal window
merod account revoke-proof --device <64-hex DeviceId> --from ./phrase.txt

Print this node’s device id and public keys.

--reset drops the stored device row so the next pairing or namespace join mints a fresh one. It drops a row that is revoked in any namespace this node takes part in, or that no namespace ever certified: neither can ever serve again, because revocation is terminal and an uncertified id authorises nobody. It refuses a row that is certified somewhere and that no revocation has reached, because as far as this store can tell that device is live, and its id is the replica lineage the node’s CRDT state is held under - dropping it would strand that state with nothing able to author there again.

An online node normally never needs the command. When its device is revoked the group key rotates, the node strands on the new epoch, and the key-recovery pull releases the spent row and mints a fresh device on its own; pair-init releases a revoked or uncertified row the same way.

--force overrides the one refusal, and exists for a device revoked while its node was offline: the tombstone never reached this store, so the guard sees a live device and the operator has to say otherwise.

Like export and import it opens the datastore directly, so the node must be stopped.

Terminal window
merod --node node1 account device --reset
Terminal window
merod --node node1 account device --reset --force

Sign a warrant authorising one relay to perform one intent, once. Offline by construction: it opens no store and contacts no node, because a warrant is a statement about an intent rather than about any node’s state. The device secret signs it locally and is never sent — only the signature travels.

Terminal window
merod account warrant \
--context <hex ContextId> \
--method set \
--args '{"key":"k","value":"v"}' \
--executor <64-hex AccountId of the relay> \
--nonce 1 \
--device-secret <64-hex> \
--credential <hex from sign-cert>
Terminal window
# Print the 24-word phrase to stdout.
merod --node node1 account export
# Write it to a file instead. Refused without the second flag; created 0600.
merod --node node1 account export --out backup.txt --allow-plaintext-file
# Restore. Reads stdin by default, or --from PATH.
merod --node node1 account import

A backup is one secret. The account id is H(genesis(root_pk)) — a pure function of the root key with no per-namespace nonce — so it is the same account in every namespace, and the phrase alone recovers all of it.

Import refuses to replace a root that has already certified a device, because that is unrecoverable and there is no second copy. Restoring onto a freshly-inited node whose own root has certified nothing needs no --force.

A hosted node that should rejoin the network across restarts is initialized once with a persistent home, has its config.toml adjusted for the deployment, then runs as a foreground service (under your process supervisor).

  1. Initialize under a stable home directory. Keep the RPC server on loopback so it is only reachable through a local reverse proxy:

    Terminal window
    merod --home /var/lib/calimero --node node1 init \
    --server-port 2528 --swarm-port 2428
  2. Edit config.toml for a datacenter deployment — turn off LAN discovery, dial known peers on startup, and advertise a stable public swarm address instead of relying on AutoNAT. These keys live in the file init wrote (<home>/node1/config.toml):

    [bootstrap]
    nodes = [
    "/ip4/198.51.100.7/tcp/2428/p2p/12D3KooW…<peer-id>",
    ]
    [discovery]
    mdns = false
    advertise_address = true
    external_address = ["/ip4/203.0.113.10/tcp/2428"]

    You can apply individual keys without hand-editing using the config subcommand, which validates the result before writing:

    Terminal window
    merod --home /var/lib/calimero --node node1 config "discovery.mdns=false"
  3. Run the node. It reloads and validates config.toml, then stays in the foreground:

    Terminal window
    merod --home /var/lib/calimero --node node1 run