> ## Documentation Index
> Fetch the complete documentation index at: https://jephalabs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Bootstrap

> Runtime bootstrap sequence — executor and signer registry initialization

# Bootstrap & Configuration Reference

> Source code: `src/qtg/infrastructure/bootstrap.py`, `src/qtg/pro_loader.py`, `config.py`, `main.py`

Defines the qtg v3 runtime initialization sequence, configuration model, and FastAPI app wiring.

***

## Configuration (Settings)

`qtg.config.Settings` is a `pydantic-settings` based settings class. Every setting can be overridden by an environment variable with the `MG_` prefix.

### Environment variable catalog

| Environment variable                     | Python field                          | Type         | Default                                                | Description                                                                                                                                                                       |
| ---------------------------------------- | ------------------------------------- | ------------ | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MG_APP_NAME`                            | `app_name`                            | str          | `"movement-guard"`                                     | Service name shown by the `healthz` response                                                                                                                                      |
| `MG_ENVIRONMENT`                         | `environment`                         | str          | `"local"`                                              | Runtime environment                                                                                                                                                               |
| `MG_API_PREFIX`                          | `api_prefix`                          | str          | `"/v3"`                                                | API router prefix                                                                                                                                                                 |
| `MG_DATABASE_URL`                        | `database_url`                        | str          | `"postgresql+asyncpg://qtg:qtg@localhost:5432/qtg_v3"` | Async SQLAlchemy DB URL (PostgreSQL only since v0.1.0)                                                                                                                            |
| `MG_NETWORK_MODE`                        | `network_mode`                        | NetworkClass | **required, no default**                               | Process network class (`mainnet` / `testnet`). The server refuses to boot if unset                                                                                                |
| `MG_AUTH_ENABLED`                        | `auth_enabled`                        | bool         | `True`                                                 | Whether inbound HMAC auth is enforced for `/v3/**` (`/healthz` excluded)                                                                                                          |
| `MG_HMAC_TIMESTAMP_TOLERANCE_SECONDS`    | `hmac_timestamp_tolerance_seconds`    | int          | `300`                                                  | Allowed freshness window for inbound auth timestamps                                                                                                                              |
| `MG_AUTH_NONCE_MAX_AGE_SECONDS`          | `auth_nonce_max_age_seconds`          | int          | `1800`                                                 | Nonce ledger retention for cleanup                                                                                                                                                |
| `MG_AUTH_NONCE_CLEANUP_INTERVAL_SECONDS` | `auth_nonce_cleanup_interval_seconds` | int          | `300`                                                  | Minimum interval between nonce cleanup runs                                                                                                                                       |
| `MG_RATE_LIMIT_ENABLED`                  | `rate_limit_enabled`                  | bool         | `True`                                                 | Whether inbound per-key rate limiting is enabled (`MG_AUTH_ENABLED=true` required)                                                                                                |
| `MG_RATE_LIMIT_READ_RPM`                 | `rate_limit_read_rpm`                 | int          | `300`                                                  | Requests per minute for read-purpose keys                                                                                                                                         |
| `MG_RATE_LIMIT_WRITE_RPM`                | `rate_limit_write_rpm`                | int          | `60`                                                   | Requests per minute for write / approval / operate / admin / all-purpose keys                                                                                                     |
| `MG_AUTO_APPROVE_ENABLED`                | `auto_approve_enabled`                | bool         | `False`                                                | Whether the auto-approve policy engine is globally enabled                                                                                                                        |
| `MG_AUTO_APPROVE_DRY_RUN`                | `auto_approve_dry_run`                | bool         | `False`                                                | Compute auto-approve verdicts without applying the actual approval transition                                                                                                     |
| `MG_WORKERS_ENABLED`                     | `workers_enabled`                     | bool         | `False`                                                | Whether background workers are enabled                                                                                                                                            |
| `MG_APPROVAL_TTL_SECONDS`                | `approval_ttl_seconds`                | int          | `300`                                                  | Approval wait timeout in seconds                                                                                                                                                  |
| `MG_NODE_DISPATCH_INTERVAL_SECONDS`      | `node_dispatch_interval_seconds`      | int          | `2`                                                    | `node_dispatcher` worker interval                                                                                                                                                 |
| `MG_NODE_OBSERVE_INTERVAL_SECONDS`       | `node_observe_interval_seconds`       | int          | `5`                                                    | `node_observer` worker interval                                                                                                                                                   |
| `MG_NODE_RECOVERY_INTERVAL_SECONDS`      | `node_recovery_interval_seconds`      | int          | `60`                                                   | `node_recovery` worker interval                                                                                                                                                   |
| `MG_CALLBACK_DISPATCH_INTERVAL_SECONDS`  | `callback_dispatch_interval_seconds`  | int          | `3`                                                    | `callback_dispatcher` worker interval                                                                                                                                             |
| `MG_EXPIRATION_CHECK_INTERVAL_SECONDS`   | `expiration_check_interval_seconds`   | int          | `30`                                                   | `expiration_checker` worker interval                                                                                                                                              |
| `MG_EXECUTOR_HEALTH_INTERVAL_SECONDS`    | `executor_health_interval_seconds`    | int          | `60`                                                   | `executor_health` worker interval                                                                                                                                                 |
| `MG_BALANCE_SNAPSHOT_ENABLED`            | `balance_snapshot_enabled`            | bool         | `False`                                                | Whether the balance snapshot sidecar worker is enabled                                                                                                                            |
| `MG_BALANCE_SNAPSHOT_INTERVAL_SECONDS`   | `balance_snapshot_interval_seconds`   | int          | `30`                                                   | Balance snapshot worker interval                                                                                                                                                  |
| `MG_BALANCE_SNAPSHOT_RETENTION_DAYS`     | `balance_snapshot_retention_days`     | int          | `7`                                                    | Balance snapshot retention period                                                                                                                                                 |
| `MG_BALANCE_STALE_THRESHOLD_SECONDS`     | `balance_stale_threshold_seconds`     | int          | `120`                                                  | Staleness threshold for balance snapshots                                                                                                                                         |
| `MG_CALLBACK_TIMEOUT_SECONDS`            | `callback_timeout_seconds`            | int          | `10`                                                   | Callback HTTP request timeout                                                                                                                                                     |
| `MG_CALLBACK_MAX_ATTEMPTS`               | `callback_max_attempts`               | int          | `5`                                                    | Maximum callback retry count                                                                                                                                                      |
| `MG_CALLBACK_HMAC_SECRET`                | `callback_hmac_secret`                | str \| None  | `None`                                                 | Callback HMAC signing secret                                                                                                                                                      |
| `MG_CALLBACK_RETENTION_DAYS`             | `callback_retention_days`             | int          | `365`                                                  | Retention for callback outbox and nonce ledger                                                                                                                                    |
| `MG_CALLBACK_ALLOWED_HOSTS_CSV`          | `callback_allowed_hosts_csv`          | str          | `""`                                                   | Callback URL allowlist                                                                                                                                                            |
| `MG_EVM_RPC_ENDPOINTS_JSON`              | `evm_rpc_endpoints_json`              | str          | `"{}"`                                                 | EVM RPC endpoints JSON                                                                                                                                                            |
| `MG_EVM_BALANCE_TARGETS_JSON`            | `evm_balance_targets_json`            | str          | `"[]"`                                                 | EVM balance fetcher bootstrap targets JSON                                                                                                                                        |
| `MG_EVM_RPC_TIMEOUT_SECONDS`             | `evm_rpc_timeout_seconds`             | int          | `10`                                                   | EVM RPC timeout                                                                                                                                                                   |
| `MG_CCTP_IRIS_BASE_URL`                  | `cctp_iris_base_url`                  | str          | `"https://iris-api.circle.com"`                        | Circle CCTP Iris API base URL                                                                                                                                                     |
| `MG_CCTP_IRIS_TIMEOUT_SECONDS`           | `cctp_iris_timeout_seconds`           | int          | `10`                                                   | CCTP Iris API timeout                                                                                                                                                             |
| `MG_LOCAL_SIGNER_BACKEND`                | `local_signer_backend`                | str \| None  | `None`                                                 | Local signer backend name (`aws_kms_evm`)                                                                                                                                         |
| `MG_LOCAL_SIGNER_KEY`                    | `local_signer_key`                    | str \| None  | `None`                                                 | Local signer registry key                                                                                                                                                         |
| `MG_LOCAL_SIGNER_KMS_KEY_ID`             | `local_signer_kms_key_id`             | str \| None  | `None`                                                 | AWS KMS key ID or alias                                                                                                                                                           |
| `MG_LOCAL_SIGNER_AWS_REGION`             | `local_signer_aws_region`             | str \| None  | `None`                                                 | Local signer AWS region                                                                                                                                                           |
| `MG_LOCAL_SIGNER_AWS_PROFILE`            | `local_signer_aws_profile`            | str \| None  | `None`                                                 | AWS profile for the local signer (optional)                                                                                                                                       |
| `MG_LOCAL_SIGNER_AWS_ENDPOINT_URL`       | `local_signer_aws_endpoint_url`       | str \| None  | `None`                                                 | AWS endpoint override for the local signer (optional)                                                                                                                             |
| `MG_LOCAL_SIGNER_TIMEOUT_SECONDS`        | `local_signer_timeout_seconds`        | int          | `10`                                                   | Local signer health and sign timeout                                                                                                                                              |
| `MG_REMOTE_SIGNER_KEY`                   | `remote_signer_key`                   | str \| None  | `None`                                                 | Remote signer registry key                                                                                                                                                        |
| `MG_REMOTE_SIGNER_BASE_URL`              | `remote_signer_base_url`              | str \| None  | `None`                                                 | Remote signer base URL                                                                                                                                                            |
| `MG_REMOTE_SIGNER_AUTH_TOKEN`            | `remote_signer_auth_token`            | str \| None  | `None`                                                 | Remote signer Bearer token                                                                                                                                                        |
| `MG_REMOTE_SIGNER_TIMEOUT_SECONDS`       | `remote_signer_timeout_seconds`       | int          | `30`                                                   | Remote signer HTTP timeout                                                                                                                                                        |
| `MG_DASHBOARD_ALLOWED_ORIGINS`           | `dashboard_allowed_origins`           | str          | `"http://localhost:5173,http://localhost:3000"`        | Dashboard CORS allowlist                                                                                                                                                          |
| `MG_GATEWAY_API_BASE_URL`                | `gateway_api_base_url`                | str          | `"https://gateway-api.circle.com"`                     | Circle Gateway API base URL                                                                                                                                                       |
| `MG_GATEWAY_DEPOSITOR_ADDRESS`           | `gateway_depositor_address`           | str          | `""`                                                   | Depositor for Gateway balance fetch and dashboard use. When a local AWS KMS signer exists, it must match the signer address; if left empty, it is derived from the signer address |
| `MG_GATEWAY_TIMEOUT_SECONDS`             | `gateway_timeout_seconds`             | int          | `15`                                                   | Gateway API timeout                                                                                                                                                               |
| `MG_UPBIT_ACCESS_KEY`                    | `upbit_access_key`                    | str \| None  | `None`                                                 | Upbit API access key                                                                                                                                                              |
| `MG_UPBIT_SECRET_KEY`                    | `upbit_secret_key`                    | str \| None  | `None`                                                 | Upbit API secret key                                                                                                                                                              |
| `MG_CEX_BINDINGS_JSON`                   | `cex_bindings_json`                   | str          | `"{}"`                                                 | Logical venue alias binding JSON (`exchange_key -> provider + credentials + options`)                                                                                             |
| `MG_BINANCE_ACCESS_KEY`                  | `binance_access_key`                  | str \| None  | `None`                                                 | Binance API access key                                                                                                                                                            |
| `MG_BINANCE_SECRET_KEY`                  | `binance_secret_key`                  | str \| None  | `None`                                                 | Binance API secret key                                                                                                                                                            |
| `MG_BINANCE_BASE_URL`                    | `binance_base_url`                    | str          | `"https://api.binance.com"`                            | Binance API base URL                                                                                                                                                              |
| `MG_BINANCE_WALLET_TYPE_DEFAULT`         | `binance_wallet_type_default`         | int          | `1`                                                    | Default Binance wallet type                                                                                                                                                       |
| `MG_BINANCE_RECV_WINDOW_MS`              | `binance_recv_window_ms`              | int          | `5000`                                                 | Binance `recvWindow` parameter                                                                                                                                                    |
| `MG_OKX_ACCESS_KEY`                      | `okx_access_key`                      | str \| None  | `None`                                                 | OKX API access key                                                                                                                                                                |
| `MG_OKX_SECRET_KEY`                      | `okx_secret_key`                      | str \| None  | `None`                                                 | OKX API secret key                                                                                                                                                                |
| `MG_OKX_PASSPHRASE`                      | `okx_passphrase`                      | str \| None  | `None`                                                 | OKX API passphrase                                                                                                                                                                |
| `MG_OKX_BASE_URL`                        | `okx_base_url`                        | str          | `"https://www.okx.com"`                                | OKX API base URL                                                                                                                                                                  |
| `MG_BYBIT_ACCESS_KEY`                    | `bybit_access_key`                    | str \| None  | `None`                                                 | Bybit API access key                                                                                                                                                              |
| `MG_BYBIT_SECRET_KEY`                    | `bybit_secret_key`                    | str \| None  | `None`                                                 | Bybit API secret key                                                                                                                                                              |
| `MG_BYBIT_BASE_URL`                      | `bybit_base_url`                      | str          | `"https://api.bybit.com"`                              | Bybit API base URL                                                                                                                                                                |
| `MG_BYBIT_RECV_WINDOW_MS`                | `bybit_recv_window_ms`                | int          | `5000`                                                 | Bybit `recvWindow` parameter                                                                                                                                                      |
| `MG_EVM_RPC_USE_DEFAULTS`                | `evm_rpc_use_defaults`                | bool         | `True`                                                 | Whether built-in default RPC endpoints are merged in when not explicitly configured                                                                                               |
| `MG_CCIP_ENABLED`                        | `ccip_enabled`                        | bool         | `False`                                                | Whether CCIP executors and the CCIP sidecar are registered                                                                                                                        |
| `MG_USDT0_ENABLED`                       | `usdt0_enabled`                       | bool         | `False`                                                | Whether the USDT0 (LayerZero OFT) executor lane is registered                                                                                                                     |
| `MG_VERIFY_DRIFT_ENABLED`                | `verify_drift_enabled`                | bool         | `False`                                                | Whether the `verify_drift` sidecar worker runs                                                                                                                                    |
| `MG_EVM_NONCE_REAPER_ENABLED`            | `evm_nonce_reaper_enabled`            | bool         | `True`                                                 | Whether the `evm_nonce_reaper` worker runs (joins the core fail-fast group when enabled)                                                                                          |
| `MG_CCIP_STRANDED_REAPER_ENABLED`        | `ccip_stranded_reaper_enabled`        | bool         | `True`                                                 | Whether the `ccip_stranded_reaper` worker runs (joins the core fail-fast group when enabled)                                                                                      |
| `MG_CAPITAL_TRANSFER_FOLLOWUP_ENABLED`   | `capital_transfer_followup_enabled`   | bool         | `False`                                                | Whether the `capital_transfer_followup` sidecar worker runs                                                                                                                       |

### Settings load order

Per `SettingsConfigDict` in `pydantic-settings`:

1. `.env` file (project root)
2. Environment variables (`MG_` prefix)
3. Environment variables win over `.env`

Unknown environment variables are ignored because `extra="ignore"` is set.

***

## EVM RPC endpoint configuration

`MG_EVM_RPC_ENDPOINTS_JSON` is a JSON object string whose keys are chain IDs and whose values are RPC URLs.

```bash theme={null}
export MG_EVM_RPC_ENDPOINTS_JSON='{"1":"https://eth.llamarpc.com","43114":"https://api.avax.network/ext/bc/C/rpc"}'
```

`parse_evm_rpc_endpoints_json()` parses it and:

* raises `ValueError` if the value is not a valid JSON object
* skips keys whose value is `None` or an empty string
* converts every key and value to `str`

An empty object (`{}`) skips CCTP executor and EVM probe registration.

## CEX binding configuration

`MG_CEX_BINDINGS_JSON` is a JSON object string that resolves a logical venue alias into a concrete provider and credential bundle.

Example:

```bash theme={null}
export MG_CEX_BINDINGS_JSON='{
  "binance_master": {
    "provider": "binance",
    "account_role": "master",
    "access_key": "main-ak",
    "secret_key": "main-sk",
    "base_url": "https://api.binance.com",
    "recv_window_ms": 5000
  },
  "binance_lab": {
    "provider": "binance",
    "account_role": "sub",
    "sub_account_uid": "lab-sub@example.com",
    "master_alias": "binance_master"
  }
}'
```

Meaning:

* key = internal QTG exchange key (`binance_lab`)
* `provider` = concrete adapter factory name (`binance`)
* master credentials and options = values injected when building adapters and fetchers
* a Binance sub binding stores only `sub_account_uid`, not credentials
* because of current Binance API constraints, that value must be the sub-account email, not a numeric `subUserId` / UID

Lookup order:

1. `MG_CEX_BINDINGS_JSON` binding-first
2. legacy per-adapter credentials and options from `MG_UPBIT_*`, `MG_BINANCE_*`, `MG_OKX_*`, and `MG_BYBIT_*`

***

## CCTP Iris configuration

Settings for checking Circle CCTP (Cross-Chain Transfer Protocol) attestations through the Iris API.

| Setting                        | Purpose                                                      |
| ------------------------------ | ------------------------------------------------------------ |
| `MG_CCTP_IRIS_BASE_URL`        | Iris API server URL (default: `https://iris-api.circle.com`) |
| `MG_CCTP_IRIS_TIMEOUT_SECONDS` | HTTP request timeout                                         |

`validate_http_base_url()` validates the URL format. It must start with `http://` or `https://`.

***

## Pro-side graceful loading

`main.py` separates Pro registration into three guarded phases:

1. `collect_pro_dashboard_routers(settings)` during `create_app()`
2. `register_pro_factories(settings)` at the start of `lifespan()`, before `bootstrap_runtime(settings=settings)`
3. `register_pro_routes(app, settings)` after `bootstrap_runtime()` completes

The graceful loader lives in `src/qtg/pro_loader.py`. When a Pro distribution is present, the loader reaches the Pro package's registration hook through this single sanctioned boundary. OSS-only deployments skip these hooks transparently when no Pro package is installed.

***

## Inbound HMAC auth

`main.create_app()` always installs `HMACAuthMiddleware`, and actual enforcement is controlled by `MG_AUTH_ENABLED`.

* if `MG_AUTH_ENABLED=false`, the middleware bypasses requests
* if `MG_AUTH_ENABLED=true`, every `/v3/**` request except `/healthz` must include:
  * `X-QTG-Key-Id`
  * `X-QTG-Timestamp`
  * `X-QTG-Nonce`
  * `X-QTG-Signature`

Canonical string:

```text theme={null}
METHOD
PATH
QUERY
SHA256(raw_body_bytes)
TIMESTAMP
NONCE
```

* `PATH` is the routed, percent-decoded path the server dispatches on — no origin, no query string.
* `QUERY` is the query **exactly as sent**: no leading `?`, **no sorting**, empty string if absent.
  Sorting before signing transmits bytes you did not sign, and every such request 401s.

On success, `request.state.auth = {key_id, client_id, purpose}` is injected.

The key and nonce store is DB-backed. On app startup, `app.state.auth_session_factory = AsyncSessionLocal` is injected.

### Auth seed CLI

Before enabling `MG_AUTH_ENABLED=true` in an operational environment, seed at least one caller key first.

```bash theme={null}
uv run python -m qtg.interfaces.tools.seed_auth_client \
  --client-name dashboard \
  --key-id dashboard-staging \
  --role operator
```

Behavior:

* creates `ApiClient`, or reuses the active client with the same name
* creates a new `ApiClientKey`
* generates a random secret when `--hmac-secret` is omitted
* prints the result once as JSON on stdout

The first version is create-only. Revoke, rotate, and batch seeding are follow-up work.

***

## bootstrap\_runtime() initialization sequence

`bootstrap_runtime(settings=settings)` registers runtime dependencies. The actual sequence in `src/qtg/infrastructure/bootstrap.py` is:

```mermaid theme={null}
flowchart TD
    A[bootstrap_runtime] --> B["1. parse_evm_rpc_endpoints_json()\nParse MG_EVM_RPC_ENDPOINTS_JSON"]
    B --> C["2. validate_http_base_url()\nValidate MG_CCTP_IRIS_BASE_URL"]
    C --> D["3. Signer bootstrap validation\n+ local/remote signer registration"]
    D --> E["4. register_builtin_cex_executors()"]
    E --> F["5. register_capital_transfer_executor\n(BinanceCapitalTransferExecutor)"]
    F --> G["6. register_builtin_observe_executors()"]
    G --> H["7. Create CctpIrisClient\n+ register CctpAttestationProbe"]
    H --> I["8. Canonicalize Gateway depositor address\n(fail closed on mismatch)"]
    I --> J{"9. gateway_api_base_url\n+ depositor address?"}
    J -->|yes| K["Register GatewayBalanceFetcher"]
    J -->|no| L["10. Register CEX balance fetchers"]
    K --> L
    L --> M["11. Register EVM balance fetchers"]
    M --> N{"12. ccip_enabled?"}
    N -->|yes| O["register_builtin_ccip_executors()\n+ cache sidecar client"]
    N -->|no| P{"13. EVM endpoints\nconfigured?"}
    O --> P
    P -->|no| Z[return]
    P -->|yes| Q["14. Create EvmJsonRpcClient\n+ set runtime EVM endpoints"]
    Q --> R["15. register_builtin_cctp_executors()"]
    R --> S["16. Register chain_receive_probe\n+ chain_finality_probe for EVM"]
    S --> T["17. Register Hyperliquid executors"]
    T --> PRO["18. pro_loader.register_pro_executors()\nPro executors (boundary-gated)"]
    PRO --> U{"19. gateway_api_base_url?"}
    U -->|yes| V["register_builtin_gateway_executors()"]
    U -->|no| W{"20. usdt0_enabled?"}
    V --> W
    W -->|yes| X["register_builtin_usdt0_executors()"]
    W -->|no| Y[done]
    X --> Y
```

> **Stargate is Pro.** There is no `stargate_enabled` branch in the Free `bootstrap.py`. Stargate executors register from the Pro package, gated on a Pro-side setting, and are reached only through the `pro_loader.register_pro_executors()` boundary call shown above.

### Representative executors and probes

This document focuses on the core executor families referenced elsewhere in `docs/reference/`:

| Registry item                             | Handler / probe                           | Registration condition   |
| ----------------------------------------- | ----------------------------------------- | ------------------------ |
| `exec.cex.withdrawal_action`              | `CexWithdrawalActionExecutor`             | Always                   |
| `exec.cex.withdrawal_observe`             | `CexWithdrawalObserveExecutor`            | Always                   |
| `exec.cex.deposit_observe`                | `CexDepositObserveExecutor`               | Always                   |
| `exec.observe.destination_chain_receive`  | `DestinationChainReceiveObserveExecutor`  | Always                   |
| `exec.observe.destination_chain_finality` | `DestinationChainFinalityObserveExecutor` | Always                   |
| `exec.observe.protocol`                   | `ProtocolObserveExecutor`                 | Always                   |
| `exec.cctp.burn`                          | `CctpBurnExecutor`                        | EVM endpoints configured |
| `exec.cctp.mint`                          | `CctpMintExecutor`                        | EVM endpoints configured |
| `protocol_probe["cctp_iris"]`             | `CctpAttestationProbe`                    | Always                   |
| `chain_receive_probe["evm"]`              | `EvmChainReceiveProbe`                    | EVM endpoints configured |
| `chain_finality_probe["evm"]`             | `EvmChainFinalityProbe`                   | EVM endpoints configured |

All local executors are registered through `LocalExecutorAdapter`.

***

## FastAPI app startup sequence

### create\_app()

```python theme={null}
def create_app() -> FastAPI:
    app = FastAPI(title=settings.app_name, lifespan=lifespan)
    register_exception_handlers(app)
    app.state.auth_session_factory = AsyncSessionLocal
    app.add_middleware(HMACAuthMiddleware)
    app.include_router(health_router)
    app.include_router(
        build_dashboard_router(pro_dashboard_routers=_collect_pro_dashboard_routers(settings)),
    )
    app.include_router(whoami_router, prefix=settings.api_prefix)
    app.include_router(audit_router, prefix=settings.api_prefix)
    app.include_router(catalog_router, prefix=settings.api_prefix)
    app.include_router(templates_router, prefix=settings.api_prefix)
    app.include_router(agent_authorities_router, prefix=settings.api_prefix)
    app.include_router(agent_wallet_topups_router, prefix=settings.api_prefix)
    app.include_router(movements_router, prefix=settings.api_prefix)
    app.include_router(registry_router, prefix=settings.api_prefix)
    app.include_router(registry_verify_drift_router, prefix=settings.api_prefix)
    app.include_router(balances_router, prefix=settings.api_prefix)
    app.include_router(capital_transfers_router, prefix=settings.api_prefix)
    app.include_router(recommend_router, prefix=settings.api_prefix)
    app.include_router(hyperliquid_router, prefix=settings.api_prefix)
    app.include_router(ccip_registry.router)
    app.include_router(admin_cutover_router)
    app.include_router(admin_signers_router)
    return app
```

The `whoami`, `audit`, `agent_wallet_topups`, `registry_verify_drift`, `hyperliquid`, `ccip_registry`, `admin_cutover`, and `admin_signers` routers are mounted alongside the core movement/registry/balance routers. Pro-side routers are mounted separately after `bootstrap_runtime()` via `register_pro_routes(app, settings)`.

### `lifespan` context manager

FastAPI lifespan handles server startup and shutdown.

**On startup:**

```mermaid theme={null}
flowchart TD
    A["lifespan() start"] --> B["1. register_pro_factories(settings)\nPro-side adapter and factory hooks"]
    B --> C["2. bootstrap_runtime(settings)\nRegister executors, probes, signers, fetchers"]
    C --> D{"3. CCIP enabled\n+ sidecar available?"}
    D -->|yes| E["bootstrap_ccip_sidecar_health(...)"]
    D -->|no| F["4. boot_validate_schema_invariants(network_mode)\nPG-only guard + selected-network schema/head check"]
    E --> F
    F --> G["5. iterate_per_network(sync_registry_to_db)\nPer-network registry sync via session_for(network_class)"]
    G --> H["6. iterate_per_network(seed_worker_heartbeats)\nPer-network heartbeat seeding"]
    H --> I["7. register_pro_routes(app, settings)\nMount guarded Pro routes"]
    I --> J["8. Audit-descriptor boot_check\nregister_free_audit_descriptors()\n+ verify_all_mutating_routes_have_descriptors()"]
    J --> K{"9. workers_enabled?"}
    K -->|yes| L["Start worker supervision\nwith enabled worker groups"]
    K -->|no| M["yield (server running)"]
    L --> M
```

**On shutdown:**

```mermaid theme={null}
flowchart TD
    A["lifespan() shutdown"] --> B["runtime_supervision_task.cancel()"]
    B --> C["await asyncio.gather(runtime_supervision_task,\nreturn_exceptions=True)"]
```

### Important runtime characteristics

1. **`bootstrap_runtime()` runs once from `lifespan`**: runtime registries are populated at the startup boundary, not when the app object is created.

2. **Schema is validated, not auto-created**: `Base.metadata.create_all()` is **no longer used**. Instead `boot_validate_schema_invariants(settings.network_mode)` runs at startup and (a) rejects any non-PostgreSQL dialect (PG-only since v0.1.0), (b) asserts `public` and the process-selected network schema exist, and (c) checks the global and selected-network Alembic heads. The unselected network schema and version table are not required or queried. Fresh databases are bootstrapped through the global chain plus one selected network chain, not by the app:

   ```bash theme={null}
   NETWORK=mainnet  # or testnet
   alembic -c alembic_v3.ini -x network=global upgrade global_chain@head
   alembic -c alembic_v3.ini -x network="$NETWORK" upgrade network_chain@head
   ```

3. **Runtime is network-class split and process-scoped**: sessions are obtained via `session_for(network_class)`, while `iterate_per_network(...)` defaults to the single process-selected network. Registry sync, worker-heartbeat seeding, and worker loops therefore use `settings.network_mode` unless a test or explicit administrative caller supplies another target. The in-memory engine/sessionmaker registry retains entries for both `NetworkClass` values; that capability does not require both physical schemas.

4. **Audit-descriptor boot check**: after Pro routes are mounted, `register_free_audit_descriptors()` runs and then `verify_all_mutating_routes_have_descriptors(app, ...)` fails fast at boot if any mutating route lacks an audit descriptor (or an `@audit_exempt` marker).

5. **Gateway and balance fetcher registration are independent of EVM executor registration**: Gateway and CEX balance fetchers can register before any EVM endpoints are available.

6. **Worker activation is conditional**: background workers run only when `MG_WORKERS_ENABLED=true`. Beyond the 8 always-on workers, the conditional workers (`evm_nonce_reaper`, `ccip_stranded_reaper`, `capital_transfer_followup`, `balance_snapshot`, `verify_drift`, `ccip_drift`) are added only when their respective flags are set. See [Runtime Workers](/reference/workers/runtime-workers).

7. **Logging initialization**: `logging.basicConfig(level=logging.INFO)` is configured when `main.py` is imported.

8. **Toolchain baseline**: both local development and the backend Docker image use the `uv` lockfile install path. The current minimum supported Python is `3.14`.

***

## Minimal configuration examples

### API-only mode (development / test)

```bash theme={null}
export MG_DATABASE_URL="postgresql+asyncpg://qtg:qtg@localhost:5432/qtg"
export MG_NETWORK_MODE=testnet  # required, no default
export MG_WORKERS_ENABLED=false
uv run uvicorn qtg.main:app --reload --port 8200
```

### Full operational mode

```bash theme={null}
export MG_DATABASE_URL="postgresql+asyncpg://qtg:password@db:5432/qtg_v3"
export MG_NETWORK_MODE=mainnet  # required, no default
export MG_WORKERS_ENABLED=true
export MG_CALLBACK_HMAC_SECRET="your-hmac-secret"
export MG_CALLBACK_ALLOWED_HOSTS_CSV="dashboard.internal,api.dashboard.internal"
export MG_UPBIT_ACCESS_KEY="..."
export MG_UPBIT_SECRET_KEY="..."
export MG_BINANCE_ACCESS_KEY="..."
export MG_BINANCE_SECRET_KEY="..."
uv run uvicorn qtg.main:app --port 8200
```

### CCTP-enabled mode

```bash theme={null}
export MG_EVM_RPC_ENDPOINTS_JSON='{"1":"https://eth.llamarpc.com","43114":"https://api.avax.network/ext/bc/C/rpc"}'
export MG_CCTP_IRIS_BASE_URL="https://iris-api.circle.com"
export MG_CCTP_IRIS_TIMEOUT_SECONDS=15
```

***

## Related documents

* [Data Model Reference](/reference/infrastructure/data-model) - table-level detail
* [V3 API Endpoints](/reference/api/v3-endpoints) - endpoints exposed by the routers
* [Runtime Workers](/reference/workers/runtime-workers) - worker loop details
