Edge configuration
Two variables are required. Everything else has a default that is correct for a single container on persistent storage.
Required
| Variable | Notes |
|---|---|
PASS_PHRASE_FILE | Path to a file containing the BIP-39 mnemonic. Use this one. |
PASS_PHRASE | The mnemonic itself, 12 or 24 words, space-separated. The alternative |
API_KEY | flk_live_* or flk_test_*. The prefix determines the environment |
PASS_PHRASE_FILE, not PASS_PHRASE
An environment variable holding a mnemonic leaks through more channels than people expect:
docker inspect, /proc/<pid>/environ, process listings, crash dumps, CI logs that echo
the environment, and most orchestrator UIs — where it is visible to anyone with read access
to the workload definition, which in a bank is a much larger set of people than those
trusted with the keys.
A file mounted at mode 0400 from a Docker secret or a Kubernetes secret does not appear in
any of those. The Edge reads it once at boot, copies the mnemonic into mlocked memory,
zeroes the source buffer, and does not retain the contents.
PASS_PHRASE exists because it is convenient for a local experiment. Use it there and
nowhere else.
Optional
| Variable | Default | Notes |
|---|---|---|
CHAINOS_CLOUD_URL | https://api.chainos.cloud | Staging override. Leave unset in production |
EDGE_BIND | 0.0.0.0:8787 | Local API listen address |
EDGE_DATA_DIR | /var/lib/chainos | Outbox and enrolment cache. Must be persistent |
EDGE_KEYSTORE | mnemonic | mnemonic | pkcs11 | awskms | azurekv |
EDGE_HEARTBEAT_SECONDS | 30 | |
EDGE_POOL_SIZE | 1000 | Pooled addresses per chain per replenish (Solana, Stellar). Leader only. The former EDGE_SOL_POOL_SIZE is still read if this is unset |
EDGE_OUTBOX_MODE | durable | durable | disabled |
EDGE_LEASE_TTL_SECONDS | 45 | Leader lease lifetime — one missed heartbeat of slack |
EDGE_LEASE_RETRY_SECONDS | 5 | Follower acquisition poll, jittered |
EDGE_LEADER_ELIGIBLE | true | false pins this replica as follower-only |
EDGE_POD_HINT | hostname | Display label in the console. Never used for identity |
EDGE_LOG_LEVEL | info | |
EDGE_SPEND_CAP_{CHAIN} | 0 | Autonomous spend cap per period, smallest unit. {CHAIN} is any of the ten slugs, uppercased |
EDGE_SPEND_WINDOW | 24h | Rolling window for the caps above |
EDGE_SWEEP_COLLECTOR | true | Signs the sweeps ChainOS plans on a schedule. false stops sweeping entirely |
EDGE_SWEEP_POLL_SECONDS | 60 | How often the collector looks for work |
EDGE_SWEEP_MAX_PER_PASS | 25 | Builds signed in one pass |
EDGE_SWEEP_TREASURY_ALLOWLIST | (empty) | Comma-separated external sweep destinations. Required for a source: external treasury wallet |
EDGE_GRPC_INSECURE | false | Disables mTLS. Local development only |
EDGE_BIND defaults to all interfaces because the container's network namespace is usually
the boundary; publish it to 127.0.0.1 at the Docker or Service layer rather than exposing
the container port broadly.
The outbox
The Edge relays webhooks to your endpoint. When it cannot — your endpoint is down, or the
Edge lost its link to Cloud — the writes it could not deliver go to a local bbolt file under
EDGE_DATA_DIR and drain in order on reconnect.
| Mode | Behaviour |
|---|---|
durable | Undeliverable writes persist to disk and drain on reconnect, in order. Default |
disabled | Undeliverable writes are dropped. Must be chosen explicitly |
The Edge refuses to start if EDGE_DATA_DIR is on a tmpfs or an emptyDir. An outbox
that loses its contents on restart is worse than no outbox, because it looks durable and is
not — the failure only shows up as missing events after a restart nobody connected to the
gap.
Use a named Docker volume or a volumeClaimTemplate.
disabled is a legitimate choice under an aggressive horizontal autoscaler, where pods are
created and destroyed continuously and per-pod persistent volumes are a nuisance. It costs
you two things beyond dropped writes:
- Enrolment is no longer a one-off. The enrolment cache lives in the same store, so an
Edge in
disabledmode enrols on every boot — seven extended public keys and a thousand derived Solana addresses, every time. - Local API keys are re-fetched. They are cached in the same place, so the Edge cannot authenticate its own callers until Cloud has been reached.
If you disable the outbox, run at least three replicas so a rolling restart never leaves zero leaders. See High availability.
Signing without a request from your application
Almost everything the Edge signs has a caller waiting: your application posts a withdrawal through the Edge and gets a transaction id back. Sweeps are the exception. ChainOS plans them on a schedule, so the Edge collects that work itself, and the two things it may then sign are bounded in two different ways.
Sweeps — bounded by destination
The Edge re-derives your treasury address at m/…/1/1 and signs a sweep only when that is
where the transaction is going. The source cannot be the check here: a sweep spends from a
deposit address, which is the thing the rest of the design exists to protect.
A source: external treasury wallet is an address no mnemonic of yours produces, so there
is nothing to re-derive and it is refused unless you name it:
environment:
EDGE_SWEEP_TREASURY_ALLOWLIST: "0xYourCustodianAddress"
EDGE_SWEEP_COLLECTOR=false stops the Edge collecting altogether. Policies still plan jobs,
and those jobs then wait unsigned until their builds expire — it switches sweeping off
rather than making it manual.
Gas top-ups — bounded by amount
Before a token sweep the Edge may need to send native gas to the deposit address, from your
gas tank. EDGE_SPEND_CAP_{CHAIN} bounds that, and it defaults to zero — so token sweeps
on the account-model chains do nothing until you set one. Native sweeps need no top-up and
are unaffected.
environment:
EDGE_SPEND_CAP_ETH: "500000000000000000" # 0.5 ETH per window, in wei
EDGE_SPEND_WINDOW: "24h"
The caps are enforced Edge-side and persisted, because a cap Cloud enforces is worthless when Cloud is the compromised party, and a cap held in memory is bypassed by restarting the container. The window rolls rather than aligning to a calendar day — an aligned window would allow two full caps in two minutes across midnight. Only a gas tank address may be the source, and the Edge verifies that itself rather than trusting Cloud's assertion. See Non-custodial.
Keystores
EDGE_KEYSTORE=mnemonic is the default and holds the seed in the Edge's own locked memory.
pkcs11, awskms and azurekv move signing to hardware or a managed KMS so the mnemonic
never enters process memory. Those three require the Ultimate tier — see
Keystores.
What is not configurable, and why
- The environment. It comes from the API key prefix and is immutable for the process lifetime. A flag would make it possible to cross sandbox and live data.
- The derivation paths. They are fixed per chain, published in HD derivation, and are what makes an address recoverable with a standard tool.
- The identity key. Derived from the mnemonic at
m/83696968'/0'/0', not enrolled and not settable. A settable identity would let a leaked API key present as your Edge. - Confirmation thresholds. Per chain, set by the chain's reorg characteristics rather than by preference. You can subscribe to fewer webhook events, but not lower a threshold.