Admin Login

Cloud-agnostic Install Guide

Understand the overall components and flow involved in Instituional Vault installation

This guide describes how to install and configure the Institutional Vault in a cloud-agnostic way. It covers the installation flow, components and how they connect, broker configuration, secrets management, and the configuration options for the wallet service and policy nodes.


Prerequisites

  • A Kubernetes cluster (any supported distribution).
  • Container images for: wallet service, policy-node (×3), NATS, WalletConnect service; optionally cold-wallet and Postgres if not using an existing database.
  • Storage: PersistentVolumes (or equivalent) for Postgres and, if used, cold-wallet nodes.
  • Secrets: An external secrets manager is required for production
  • Confidential Computing is required for Policy Nodes in production to protect MPC key share material while in-use.

Deployment Components

  1. Namespace and RBAC – Create the target namespace and any service accounts/roles used by the components.
  2. Secrets and ConfigMaps – Create Kubernetes Secrets (or provision secrets via your chosen secret store) and ConfigMaps. Do not store secrets in ConfigMaps.
  3. Database (PostgreSQL) – Deploy Postgres and run init scripts to create databases: wallet,orchestrator, approval, mpa0, mpa1, mpa2. Ensure TLS is configured for production.
  4. Message broker (NATS) – Deploy the broker and configure accounts/users for: policy-node0, policy-node1, policy-node2, facade, walletconnect/walletconnector. The wallet service connects as facade; WalletConnect as walletconnector; each policy node has its own user (e.g. node0, node1, node2).
  5. Policy nodes – Deploy the three policy nodes. They require broker and database connectivity.
  6. Wallet service – Deploy the wallet after the broker and (if using the same cluster) policy nodes and database are ready. The wallet connects to the broker as facade and to the MPA routes (route0, route1, route2) via the broker.
  7. WalletConnect – Deploy after the wallet; it uses the same broker with user walletconnector and reads config from a separate env/config file (not covered in this doc).
  8. Cold wallet – Optional. Requires Redis and persistent storage for two cold-wallet nodes and a proxy.
  9. Ingress / Gateway – Expose the wallet API and WalletConnect via your chosen gateway or ingress.

Component Wiring

High-level architecture:

%%{init: {'themeVariables': { 'fontSize': '15px' }}}%%
flowchart TB
  subgraph clients [Clients]
    walletUiClient[Wallet UI<br/>Client]
    walletApiClient[Wallet API<br/>Client]
    approverClient[Approver]
  end

  subgraph workload [Wallet Namespace]
    proxy[Reverse Proxy]
    walletFrontend[Wallet Frontend]
    walletApi[Wallet Service]
    walletconnect[WalletConnect<br/>Connector]
    evmTracker[EVM Tracker]
    nats[NATS Message Broker]
    policyNode0[Policy Node 0]
    policyNode1[Policy Node 1]
    policyNode2[Policy Node 2]
  end

  subgraph data [Data Layer]
    walletDb[(wallet,<br/>approval,<br/>orchestrator)]
    policyNode0Db[(mpa0)]
    policyNode1Db[(mpa1)]
    policyNode2Db[(mpa2)]
  end

  walletUiClient -->|"HTTPS 443"| proxy
  walletApiClient -->|"HTTPS 443"| proxy
  approverClient -->|"HTTPS 443"| proxy
  proxy -->|"HTTP 80"| walletFrontend
  proxy -->|"TCP 8080"| walletApi

  walletApi -->|"TCP 5432"| walletDb
  walletApi -->|"NATS TCP 4222"| nats
  walletconnect -->|"NATS TCP 4222"| nats

  evmTracker -->|"NATS TCP 4222"| nats

  nats -->|"NATS TCP 4222"| policyNode0
  nats -->|"NATS TCP 4222"| policyNode1
  nats -->|"NATS TCP 4222"| policyNode2

  policyNode0 -->|"TCP 5432"| policyNode0Db
  policyNode1 -->|"TCP 5432"| policyNode1Db
  policyNode2 -->|"TCP 5432"| policyNode2Db
  • Wallet service – API and business logic. Talks to the broker as facade, to Postgres (wallet, approval, orchestrator DBs), and to policy nodes only indirectly via the broker (route0, route1, route2). Ports: 8080 (API/health), 9876 (approval), 8085 (webhook), 9090 (metrics).
  • Policy nodes – Three MPC participants. They connect to the broker and the database only. They do not call the wallet; the wallet sends intents over the broker and receives responses over the broker.
  • NATS – Message broker. Client port (e.g. 4222), monitoring (e.g. 8222), optional WebSocket (e.g. 8443). Users: node0, node1, node2, facade, walletconnector.
  • PostgreSQL – Shared database. The wallet uses multiple DBs (wallet, approval, orchestrator); policy nodes use a DB per tenant (e.g. local) or shared DB with schema separation.
  • WalletConnect – Optional; bridges WalletConnect protocol to the wallet; uses broker user walletconnector and a separate env/config file.
  • Cold wallet – Optional; uses Redis and persistent volumes; integrates with the wallet for cold-storage flows.
  • Gateway – Optional; routes external traffic (e.g. Gateway API HTTPRoute or Ingress) to the wallet and WalletConnect.

Secrets Management

Wallet and policy-node services load local config files at startup; any secret values referenced in those files must be interpolated by fetching them from external secret managers at runtime (not stored in plaintext in config files or ConfigMaps). Both wallet and policy nodes support direct SDK-based secret retrieval (for example via AWS Secrets Manager or Azure Key Vault client integrations) so secrets are resolved from the manager instead of being hardcoded in deployment manifests.

Secret interpolation syntax

The config templates in this stack use Go-template style interpolation ({{ ... }}).

Interpolation typeSyntaxBehavior
Environment variable
(for development only)
{{ env `VAR_NAME` }} (example: {{ env `BD_API_KEY` }})Reads VAR_NAME from process environment; errors if missing.
AWS Secrets Manager lookup{{ awsSecret `secret-name` }} or {{ awsSecret `secret-name#FIELD` }}Reads secret by name; when #FIELD is provided, treats the secret value as JSON and returns that key.
Azure Key Vault lookup{{ azureSecret `secret-name` }} or {{ azureSecret `secret-name#FIELD` }}Same pattern as AWS; optional #FIELD extracts a JSON field.
Attested enclave secret lookup (MPA policy-node){{ enclaveSecret `secret-name` }}Retrieves the secret and decrypts through enclave/attestation flow before injecting into config.

In this repository:

  • the policy-node config expansion supports env, awsSecret, azureSecret, and enclaveSecret.
  • the wallet service config expansion supports env, azureSecret and awsSecret.

Security tiers (reference)

  • Tier 0 (dev/example) – Kubernetes Secrets with local config interpolation (including env var expansion); values provided by overlay or manual creation. Not safe against cluster-admin access.
  • Tier 1 (production) – Applications must fetch secrets directly from an external secret manager (for example AWS Secrets Manager, Azure Key Vault, or Vault) at runtime using provider SDKs or approved secret-client libraries. Do not rely on mounted Kubernetes Secrets for sensitive material, because cluster-admin users can observe Kubernetes Secrets once they are materialized in the cluster.
  • Tier 2 (high assurance) – Secret release gated by attestation. Policy nodes may run in TEEs and pull secrets after attestation.

Secret categories and usage

ServiceSecret NamePurpose
walletWALLET_DB_PASSWORDWallet database password for main wallet DB connection (optional when use_iam: true with AWS IAM DB authentication).
walletAPPROVAL_DB_PASSWORDApproval service database password (optional when use_iam: true with AWS IAM DB authentication).
walletORCHESTRATOR_DB_PASSWORDOrchestrator/MPA database password used by wallet adapter (optional when use_iam: true with AWS IAM DB authentication).
walletFACADE_BROKER_PASSWORDNATS password for wallet facade user.
walletBD_API_KEYAPI key used by Blockdaemon service integrations (auth_token/utxos/ubiquity/staking APIs).
walletWEBHOOK_TOKENWebhook shared secret/token for callback verification.
walletBOOTSTRAP_KEYBootstrap key material used for local/bootstrap API auth flow.
walletCHAINALYSIS_TOKENChainalysis credential (optional, only if compliance integration is enabled).
walletBOSS_API_KEYStaking service token (optional, only if staking integration is enabled).
walletWEBHOOK_SECRETAlternate webhook secret key name (optional, only in deployments using this key instead of WEBHOOK_TOKEN).
walletCANTON_DEVNET_CLIENT_SECRETCanton OIDC client secret (optional, only if Canton integration is enabled).
policy-nodeDB_PASSWORDPolicy-node Postgres password for default and tenant DB DSNs.
policy-nodeBROKER_PASSWORDNATS password for per-node broker users (node0/node1/node2).
policy-nodeENCRYPTOR_MASTER_PASSWORDMaster password used to derive encryption keys for sensitive policy-node data.
policy-nodeERS_PUBLIC_KEY_DER_BASE64Tenant ERS RSA public key (base64 DER/SPKI) for tenant configuration (optional, required only when tenant ERS key is configured).
policy-nodeBROKER_INTERMEDIATE_CACA bundle used to validate broker certificate (optional, required when broker TLS validation is enabled).
walletconnectWALLETCONNECTOR_BROKER_PASSWORDNATS password used by WalletConnect bridge user.
walletconnectNATS_PASSWORDAlternate WalletConnect NATS password key (optional, only in deployments/templates that use this key name).

Wallet Service Configuration

The wallet is configured via a single YAML file (e.g. mounted from a ConfigMap). Sensitive values must come from Secrets, not from this file. The following YAML is an annotated example showing the available configuration keys and typical option values.

wallet.yaml

# CORS allow-origin. Typical: "*" for dev, or exact frontend origin in production.
access_control_allow_origin: "http://localhost:3000"

# Bootstrap key source.
# Supported `type` values in wallet config:
# - local (read key from file path)
# - secretsmanager (AWS Secrets Manager)
# - keyvault (Azure Key Vault)
bootstrap_key:
  type: "local"
  key: "/secrets/api-key"

# Compliance provider credentials. Keep secret values in external secret manager.
compliance:
  chainalysis:
    password: "{{ env `CHAINALYSIS_TOKEN` }}"
    # Provider username/account identifier.
    username: new_user_02

# Cold wallet signer set.
# Options: comma-separated signer indexes, e.g. "0,1", "0,2", "1,2".
cold_wallet:
  signer_set: "0,1"

# Wallet DB DSN.
# Options:
# - sslmode=disable (local dev)
# - sslmode=require (production)
database:
  url: postgresql://postgres:{{ env `WALLET_DB_PASSWORD` }}@host.docker.internal:5432/wallet?sslmode=disable

# Environment label / tenant namespace.
# Typical: local, dev, staging, production, or per-tenant subdomain.
environment: "local"

# Feature flags.
# Toggle true/false based on your deployment capabilities.
features:
  block_explorer: true
  cold_storage: true
  dev_tools: true
  recurring_transactions: true
  stake_eth: true
  txn_risk_assessment: true

# Gateway and webhook behavior.
gateway:
  # Webhook listener port.
  webhook_port: 8085
  # Shared secret/token for webhook verification.
  webhook_secret: "{{ env `WEBHOOK_SECRET`}}"
  # Public host URL used in callback generation.
  webhook_url: "http://localhost"
  # Internal buffering/cache knobs.
  buffer_flush_interval: 1s
  cache_flush_interval: 10m
  cache_expiry: 12h

# Logging verbosity.
# Typical: 0 (quiet) to higher values (more verbose), based on runtime implementation.
log_level: 1

# Deployment mode label.
# Typical values: on-prem or cloud-managed flavors depending on environment.
mode: "on-prem"

# MPA/orchestrator integration.
mpa_adapter_config:
  # Blockdaemon auth token.
  auth_token: "{{ env `BD_API_KEY` }}"
  # Approval DB DSN.
  approval_db_url: "postgresql://postgres:{{ env `APPROVAL_DB_PASSWORD` }}@host.docker.internal:5432/approval?sslmode=disable"
  approval_service:
    # Mobile approval bundle/application id.
    bundle_id: "com.blockdaemon.Advanced-MPC-Approver"
    # Minimum supported approver app version.
    min_version: "v1.0.8"
    # Groups bypassing manual approval.
    no_approvals_required_for_groups:
      - "group1"
      - "group2"
    # Public host and port for approval callbacks.
    host_url: "http://localhost"
    port: "9876"
  # Supported asset families.
  asset_families:
    - EVM
    - solana
    - bitcoin
    - TVM
    - substrate
  # Supported blockchain definitions.
  # Repeat block entries per chain/network you enable.
  blockchains:
    - protocol_name: "ethereum"
      network_name: "hoodi"
      family_name: "EVM"
      native_symbol: "ETH"
      decimals: 18
      mainnet_coin_type: 60
      test_network: true
      family_properties:
        evm_properties:
          chain_id: 560048
    - protocol_name: "polygon"
      network_name: "amoy"
      family_name: "EVM"
      native_symbol: "POL"
      decimals: 18
      mainnet_coin_type: 966
      test_network: true
      family_properties:
        evm_properties:
          chain_id: 80002
    - protocol_name: "bitcoin"
      network_name: "testnet4"
      native_symbol: "BTC"
      decimals: 8
      mainnet_coin_type: 0
      test_network: true
      family_properties:
        bitcoin_properties:
          # Options depend on chain support, e.g. P2PKH, P2WPKH, P2SH_P2WPKH.
          address_type: P2WPKH
    - protocol_name: "solana"
      # Keep family_name for nonce account support.
      family_name: "solana"
      network_name: "devnet"
      native_symbol: "SOL"
      decimals: 9
      mainnet_coin_type: 501
      test_network: true
      native_driver_url: "https://svc.blockdaemon.com/solana/devnet/native"
      family_properties:
        solana_properties: {}
    - protocol_name: "polkadot"
      network_name: "westend"
      native_symbol: "WND"
      decimals: 12
      mainnet_coin_type: 354
      test_network: true
      family_properties:
        substrate_properties:
          # Substrate network code for the target chain.
          network_code: 42
    - protocol_name: "base"
      network_name: "sepolia"
      family_name: "EVM"
      native_symbol: "BASE-ETH"
      decimals: 18
      mainnet_coin_type: 8453
      test_network: true
      native_driver_url: "https://svc.blockdaemon.com/base/testnet/native/http-rpc"
      family_properties:
        evm_properties:
          chain_id: 84532
  # Orchestrator DB DSN.
  db_url: "postgresql://postgres:{{ env `ORCHESTRATOR_DB_PASSWORD` }}@host.docker.internal:5432/orchestrator?sslmode=disable"
  # NATS connection for wallet facade user.
  nats:
    broker_url: "nats://host.docker.internal:4222"
    username: "facade"
    password: "{{ env `FACADE_BROKER_PASSWORD` }}"
    # Optional TLS CA bundle for NATS server validation.
    ca_bundle_file: ""
    # Optional client cert/key for mTLS to broker.
    client_certificate_file: ""
    client_certificate_key_file: ""
  # Route IDs must match policy-node identities.
  nodes:
    - route_id: "node0"
    - route_id: "node1"
    - route_id: "node2"
  # True for testnets/sandboxes, false for production mainnet-only behavior.
  should_use_test_networks: true
  staking:
    service_name: "BlockdaemonStaking"
    protocol_name: "ethereum"
    native_asset_symbol: "ETH"
    auth_token: "{{ env `BOSS_API_KEY` }}"
    client_id: "3bcf0d33-cb49"
    is_test_network: true
    url: "https://svc.blockdaemon.com"
  # Global MPA timeout.
  timeout: "5m"
  utxos:
    url: "https://svc.blockdaemon.com"
    auth_token: "{{ env `BD_API_KEY` }}"

# OIDC configuration.
oidc_static_config:
  issuer: "https://auth.example-wallet.local/oauth2/default"
  audience: "api://wallet-example"
  # SPA/web frontend OIDC client id.
  client_id: "wallet-spa-client-id-example"
  # Native/mobile Approver app OIDC client id.
  # Must match policy-node TOML OIDCConfig.ClientID for approver JWT validation.
  client_id_native: "wallet-approver-native-client-id-example"

# Wallet HTTP port.
port: 80

# Optional region label.
region: ""

# Use IAM DB authentication instead of static DB passwords (AWS deployments).
# When true, wallet DB connections use IAM auth token flow (for example RDS IAM auth).
use_iam: false

# Telemetry.
telemetry:
  enable_prom: true
  enable_otlp: true
  service_name: "wallet"
  # OTLP collector endpoint.
  otel_endpoint: "localhost:4318"
  # Sample ratio: 0..1
  otel_sample_ratio: 1

# Ubiquity API integration.
ubiquity:
  url: "https://svc.blockdaemon.com"
  api_key: "{{ env `BD_API_KEY` }}"

# WalletConnect integration settings.
walletconnect:
  project_id: "927b518384169c157828af8b77181ad3"
  nats_user: "walletconnectmgr"
  nats_password: "{{ env `WALLETCONNECTOR_BROKER_PASSWORD` }}"
  # Optional client cert/key/CA for WalletConnect broker mTLS.
  nats_client_cert_file: ""
  nats_client_key_file: ""
  nats_ca_bundle_file: ""

# Staking API settings.
staking_api:
  api_key: "{{ env `BD_API_KEY` }}"
  base_url: "https://svc.blockdaemon.com/boss"

# Staking reporting API settings.
staking_report_api:
  api_key: "{{ env `BD_API_KEY` }}"
  base_url: "https://svc.blockdaemon.com/boss/reporting/staking/v1"
  cache_expiration: "10m"

# Staking reporting API settings.
staking_report_api:
  base_url: "https://svc.blockdaemon.com/boss/reporting/staking/v1"
  cache_expiration: "10m"

# Mailer sender identity.
mailer:
  from_email: "wallet-noreply@localhost"

# Service version label.
version: "2.0"

# Optional Canton integration.
# Uncomment and set values when Canton support is enabled.
#canton_config:
#  token_url: "https://<oidc_issuer>/realms/canton-devnet/protocol/openid-connect/token"
#  client_id: "A54918EC-DD51-4B5A-BED5-C0653C3C48FA"
#  client_secret: "{{ env `CANTON_DEVNET_CLIENT_SECRET` }}"
#  audience: "<audience>>"
#  network: "devnet"
#  ledger_api_url: "https://<canton_grpc_endpoint>:443"
#  validator_api_url: "https://<canton_wallet_endpoint>/api/validator"
#  regenerate_addresses: true

Policy Node Configuration

Each policy node is configured by a single TOML file (e.g. policy-node.conf). The three nodes differ only by identity and broker username; the rest of the structure is the same. Sensitive values must come from secrets (env or files).

Confidential Computing and Attestation

For production deployments, secret protection should include Confidential Computing and remote attestation for policy-node workloads. This ensures secret confidentiality while the workload is running (in-use), not only at rest or in transit.

  • Run policy-node workloads in a confidential execution environment (TEE-backed runtime).
  • Require remote attestation before releasing sensitive material such as ENCRYPTOR_MASTER_PASSWORD, broker credentials, database passwords
  • Keep secrets in an external secrets manager and release them to workloads only after attestation policy checks pass.
  • Use short-lived credentials where possible and rotate static secrets in the service secret table regularly.
⚠️

Note: Confidential Computing and attestation deployments are security-critical and operationally complex. Engage Blockdaemon engineers for design guidance, implementation assistance, and pre-production security/configuration reviews.

Example policy node TOML

Below is an example for policy node 0 (route0). For node 1 and 2, change NodeId, PlayerIndex, and Broker.Username to route1/route2, 1/2, and node1/node2. Sensitive values must come from your secret store (env vars or files); do not commit real passwords or keys.

policy-node.conf

# OIDC config list. You can define one or more OIDC providers.
[[OIDCConfig]]
# OIDC issuer URL.
Issuer = "https://auth.example-wallet.local/oauth2/default"
# OIDC client id expected in JWT audience/client claims.
ClientID = "wallet-approver-native-client-id-example"
# Optional: allow tokens without email_verified=true.
AllowUnverifiedEmail = false
# Optional: static public keys. If omitted, keys are fetched from the IdP JWKS endpoint.
[[OIDCConfig.PublicKeys]]
# Key ID from JWT header.
KID = "kid-1"
# Base64-encoded DER public key bytes (non-secret public key material).
Key = "MIIBojANBgkqhkiG9w0BAQEFAA...u6AfzAgMBAAE="

# Identity: NodeId must match wallet mpa_adapter_config.nodes and broker routing.
[Identity]
NodeId = "route0"
# MPC player index for this node.
PlayerIndex = 0

# Global encryptor password for multi-tenant mode (required unless set per-tenant).
[Secrets]
EncryptorMasterPassword = "{{ env `ENCRYPTOR_MASTER_PASSWORD` }}"

# Global/default database used when tenant-specific database override is not set.
[Database]
# Database driver name.
DriverName = "pgx"
# DSN/connection string.
DataSourceName = "host=postgres sslmode=require user=postgres password={{ env `DB_PASSWORD` }} dbname=local"

# Broker settings.
[Broker]
# Broker endpoint URL.
URL = "nats://nats:4222"
# Username/password auth (optional depending on broker mode).
Username = "node0"
Password = "{{ env `BROKER_PASSWORD` }}"
# Optional token auth (NATS).
Token = ""
# Optional (NATS): allow untrusted TLS certificates (development only).
AllowUntrustedCertificate = false
# Optional broker public key pinning (base64-encoded key bytes).
PublicKey = ""
# Optional client mTLS cert/key file paths.
ClientCertificateFile = ""
ClientCertificateKeyFile = ""
# Optional CA bundle path for broker TLS verification.
CABundleFile = ""

# Service settings.
[Service]
# If > 0, exposes /health on this port.
HealthCheckPort = 8080
# CloudWatch logging toggle.
CloudwatchLogging = false

# Tenant-specific overrides. You can define multiple tenant blocks.
[Tenant.default]
# Optional tenant ERS RSA public key (base64-encoded DER/SPKI).
ERSPublicKey = "MIIBojANBgkqhkiG9w0BAQEFAA...u6AfzAgMBAAE="
# Optional tenant-specific override for EncryptorMasterPassword.
EncryptorMasterPassword = "{{ env `ENCRYPTOR_MASTER_PASSWORD` }}"

# Optional tenant-specific DB override (same keys as [Database]).
[Tenant.default.Database]
DriverName = "pgx"
DataSourceName = "host=postgres sslmode=require user=postgres password={{ env `DB_PASSWORD` }} dbname=local"

Broker Configuration

The message broker (NATS) can be secured with password authentication and/or TLS.

Password authentication

  • The broker is configured with one user per client: e.g. node0, node1, node2 for policy nodes, facade for the wallet, walletconnector for WalletConnect.
  • Each component’s config supplies the same username and the password (from a secret). No certificates are required for this mode.
  • In NATS server config, accounts.default.users[].password should be a bcrypt hash (not raw plaintext). Keep raw passwords only in client-side secret values.

Recommended setup sequence (config-focused):

  1. Generate one raw password per NATS user (node0, node1, node2, facade, walletconnector).
  2. Configure each client service with its username and raw password.
  3. Configure NATS accounts.default.users[].password with bcrypt hashes of those same passwords.
  4. Generate broker server TLS cert/key and CA chain.
  5. If using mTLS, generate per-service client cert/key material.
  6. Mount cert/key/CA files into pods and point service config fields to file paths (ClientCertificateFile, ClientCertificateKeyFile, CABundleFile).

Certificates

  • Server TLS: Broker can expose a TLS listener (e.g. nats:// with TLS or a separate port). In that case, clients need to trust the broker’s certificate (or the CA that signed it).
  • Client certificates (mTLS): Policy nodes and wallet can present a client certificate to the broker. In the policy-node config this is done via:
    • ClientCertificateFile – path to the client certificate (PEM).
    • ClientCertificateKeyFile – path to the client private key (PEM).
  • CA bundle: To verify the broker’s server certificate, set:
    • CABundleFile – path to a PEM file containing the CA (or CA chain) that signed the broker’s certificate.

In Kubernetes, TLS material is typically mounted from Secrets (e.g. kubernetes.io/tls or opaque Secrets) into the pod; config then references file paths under the mount (e.g. /certs/tls.crt, /certs/tls.key, /certs/ca.crt). On VMs, use equivalent paths to where you deploy certs and keys.

nats-server.conf

{
  // NATS client port used by wallet, policy nodes, and walletconnect.
  "port": 4222,

  // Monitoring/health endpoint (e.g. GET /healthz).
  "http_port": 8222,

  // Optional websocket listener for broker websocket clients.
  "websocket": {
    // Websocket port.
    "port": 8443,
    // Set true for plaintext websocket in local/dev.
    // Set false in production and configure websocket TLS.
    "no_tls": true
  },

  // TLS for NATS client listener (production recommended).
  // Enable this block and provide broker certificate material in production.
  "tls": {
    // Broker/server certificate PEM.
    "cert_file": "/etc/nats/server-cert.pem",
    // Broker/server private key PEM.
    "key_file": "/etc/nats/server-key.pem",
    // CA bundle PEM used to validate client certificates and trust chain.
    "ca_file": "/etc/nats/intermediate-ca.pem",
    // Require and verify client certificates.
    "verify": true
  },

  // Debug tracing. Keep false in production unless troubleshooting.
  "debug": false,
  "trace": false,

  // User accounts and credentials.
  "accounts": {
    "default": {
      "users": [
        // Use bcrypt password hashes in NATS server config.
        { "user": "node0", "password": "{{ env `NODE0_BROKER_PASSWORD_HASH` }}" },
        { "user": "node1", "password": "{{ env `NODE1_BROKER_PASSWORD_HASH` }}" },
        { "user": "node2", "password": "{{ env `NODE2_BROKER_PASSWORD_HASH` }}" },
        { "user": "facade", "password": "{{ env `FACADE_BROKER_PASSWORD_HASH` }}" },
        { "user": "walletconnector", "password": "{{ env `WALLETCONNECTOR_BROKER_PASSWORD_HASH` }}" }
      ]
    },
    // Internal NATS system account.
    "SYS": {}
  },

  // Authorization options.
  // Option A (default): static username/password in accounts.default.users (shown above).
  // Option B (advanced): auth_callout for externalized auth/token validation.
  "authorization": {
    "auth_callout": {
      // Public key / issuer material used by auth callout verifier.
      // Must match the public key derived from the auth-callout signer seed.
      "issuer": "{{ env `BROKER_PUBLIC_KEY` }}",
      // Users that are allowed to use auth callout in the target account.
      // Must use NATS usernames (not service IDs).
      "auth_users": ["node0", "node1", "node2", "facade", "walletconnector"],
      // Account where auth_callout should apply.
      "account": "default"
    }
  },

  "system_account": "SYS"
}

Verification

  • Broker: HTTP health on the monitoring port (e.g. 8222) at /healthz.
  • Wallet: HTTP 200 on /health (e.g. port 8080).
  • Policy nodes: HTTP 200 on /health (e.g. port 8080).
  • Postgres: pg_isready (or equivalent) for the postgres user.

Summary

  • Install in order: namespace/RBAC → secrets/config → database → broker → policy nodes → wallet → WalletConnect (optional) → cold wallet (optional) → ingress/gateway.
  • Policy nodes require broker + database connectivity (+ inter-node MPC).
  • Use an external secrets manager in production (for example AWS Secrets Manager, Azure Key Vault, or Vault); never put secrets in ConfigMaps or in repo.
  • Broker: password auth and/or TLS/mTLS; policy nodes support CA bundle and optional public key pinning.
  • Wallet config: one YAML; MPA section wires route0/route1/route2 via broker only.
  • Policy node config: one TOML per node; identity (NodeId, PlayerIndex), broker URL/user/password, DB, tenant ERS key and encryptor password.