Skip to main content

Edge configuration

Two variables are required. Everything else has a default that is correct for a single container on persistent storage.

Required

VariableNotes
PASS_PHRASE_FILEPath to a file containing the BIP-39 mnemonic. Use this one.
PASS_PHRASEThe mnemonic itself, 12 or 24 words, space-separated. The alternative
API_KEYflk_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

VariableDefaultNotes
CHAINOS_CLOUD_URLhttps://api.chainos.cloudStaging override. Leave unset in production
EDGE_BIND0.0.0.0:8787Local API listen address
EDGE_DATA_DIR/var/lib/chainosOutbox and enrolment cache. Must be persistent
EDGE_KEYSTOREmnemonicmnemonic | pkcs11 | awskms | azurekv
EDGE_HEARTBEAT_SECONDS30
EDGE_POOL_SIZE1000Pooled addresses per chain per replenish (Solana, Stellar). Leader only. The former EDGE_SOL_POOL_SIZE is still read if this is unset
EDGE_OUTBOX_MODEdurabledurable | disabled
EDGE_LEASE_TTL_SECONDS45Leader lease lifetime — one missed heartbeat of slack
EDGE_LEASE_RETRY_SECONDS5Follower acquisition poll, jittered
EDGE_LEADER_ELIGIBLEtruefalse pins this replica as follower-only
EDGE_POD_HINThostnameDisplay label in the console. Never used for identity
EDGE_LOG_LEVELinfo
EDGE_SPEND_CAP_{CHAIN}0Autonomous spend cap per period, smallest unit. {CHAIN} is any of the ten slugs, uppercased
EDGE_SPEND_WINDOW24hRolling window for the caps above
EDGE_SWEEP_COLLECTORtrueSigns the sweeps ChainOS plans on a schedule. false stops sweeping entirely
EDGE_SWEEP_POLL_SECONDS60How often the collector looks for work
EDGE_SWEEP_MAX_PER_PASS25Builds signed in one pass
EDGE_SWEEP_TREASURY_ALLOWLIST(empty)Comma-separated external sweep destinations. Required for a source: external treasury wallet
EDGE_GRPC_INSECUREfalseDisables 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.

ModeBehaviour
durableUndeliverable writes persist to disk and drain on reconnect, in order. Default
disabledUndeliverable writes are dropped. Must be chosen explicitly
The data directory must not be ephemeral

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 disabled mode 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.