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 in mnemonic mode
PASS_PHRASEThe mnemonic itself, 12 or 24 words, space-separated. The alternative
PASS_PHRASE_SEALED_FILEInstead of the two above, in a sealed mode: the envelope written by seal-mnemonic
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, derives the seed into memory locked against swap on Linux, and never re-reads the file. The file itself stays mounted for the life of the container, so who can read the secret it comes from still matters — see Keystores. A sealed mode mounts ciphertext instead.

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 | awskms | azurekv | nitro. Any other value stops the Edge at boot. See Keystores
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. It reads the phrase from PASS_PHRASE_FILE or PASS_PHRASE and holds the seed in the Edge's own locked memory.

awskms and azurekv are sealed-seed modes: the phrase arrives encrypted and is decrypted once at boot. After that the seed is held exactly as in mnemonic mode. The attested modes and pkcs11 are planned and not implemented; the Edge refuses to start with any of them. See Keystores for what each mode does and does not protect against.

Sealed seed​

VariableDefaultNotes
PASS_PHRASE_SEALED_FILE—Required, or PASS_PHRASE_SEALED. The envelope. Ciphertext; may come from a ConfigMap
PASS_PHRASE_SEALED—The envelope itself, as the value, for platforms that pass no files — Confidential Space takes configuration only from instance metadata. Never together with PASS_PHRASE_SEALED_FILE
EDGE_AWSKMS_KEY_ID—Required for awskms. The key ARN; must equal the envelope's keyRef
AWS_REGION—Required for awskms. Must be the key's region
EDGE_AZUREKV_KEY_ID—Required for azurekv and azureskr. The versioned key identifier; must equal the envelope's keyRef
EDGE_AZURESKR_MAA_ENDPOINT—Required for azureskr. The Microsoft Azure Attestation provider the key's release policy names as its authority, e.g. sharedeus.eus.attest.azure.net
EDGE_AZURESKR_SIDECARhttp://127.0.0.1:8080azureskr: the SKR sidecar in the same container group. Loopback only — its answer is the released key
EDGE_GCPKMS_KEY_ID—Required for gcpcs. The Cloud KMS key name, projects/…/cryptoKeys/<k>, without a version
EDGE_GCPCS_WIP_PROVIDER—Required for gcpcs. The Workload Identity Pool provider that accepts this workload, //iam.googleapis.com/projects/<number>/locations/global/workloadIdentityPools/<pool>/providers/<provider>
EDGE_GCPCS_TOKEN_FILE/run/container_launcher/attestation_verifier_claims_tokengcpcs: the launcher's attestation token
EDGE_NITRO_CID16nitro: the enclave's vsock CID, as given to nitro-cli run-enclave --enclave-cid
EDGE_NITRO_PORT5005nitro: the enclave's vsock port
EDGE_NITRO_SOCKET—nitro: a Unix socket bridged to the enclave by the launcher container. Set it when the Edge runs in a container, which cannot open vsock itself. Takes precedence over the CID
EDGE_NITRO_KMS_PROXY_PORT8000nitro: the host's vsock-proxy port for KMS, which the enclave dials
EDGE_KMS_TIMEOUT_SECONDS10Per call to the KMS
EDGE_KMS_ATTEMPTS5Calls before giving up, with exponential backoff and jitter, 30 seconds at most in all. Access denied, a wrong key and a disabled key are not retried
  • PASS_PHRASE or PASS_PHRASE_FILE set together with a sealed mode is a startup error, not a precedence rule — a plaintext copy beside the sealed one would undo the sealing.
  • The envelope's provider, key and environment must match EDGE_KEYSTORE, the key variable and the API key's prefix. Each mismatch is a startup error, checked before the KMS is called.
  • AWS_ACCESS_KEY_ID in the environment is a startup error in awskms mode, and AZURE_CLIENT_SECRET or AZURE_CLIENT_CERTIFICATE_PATH in azurekv mode. Credentials come from the workload identity only.
  • The KMS is a start-up dependency, not a signing dependency. A running Edge never calls it again; a restart while the KMS is unreachable or the role is revoked does not start.

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.