Skip to main content

04 - Going Live

Settings required to move from dry-run to real operation. Enable auth, wire callbacks, start workers, deploy with Docker, and configure the address allowlist.

1. Enable Workers

The key setting for moving from dry-run to live:
Enabled workers:

2. Configure Exchange Credentials

To use the CEX executor, you need exchange API keys:
The distribution ships Upbit, Binance, Bybit, Coinbase, and OKX adapters.
These credentials are plaintext on disk. That is a deliberate tradeoff, and it is survivable — but only if your exchange-side IP restriction and withdrawal address whitelist are configured. See Security Tradeoffs § Exchange credentials before you deposit real capital.

3. Configure EVM / Signer (CCTP, Gateway)

To use on-chain executors:
Configure the isolated service from Private Signer Service. qtg.main rejects local KMS settings. Explicit one-off preflight tools retain their separately reviewed MG_LOCAL_SIGNER_* inputs; check those inputs with:
Details: Signer protocol, CCTP bridge lane

4. Enable HMAC Auth

4-1. Create API Keys

Generate API keys with the seed CLI:
Store the output key_id and secret safely.

4-2. HMAC Signing on the Client

Canonical string layout:
  • 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.
Required headers:

4-3. curl Example (With Signature)

4-4. Access Scope by Role

Access is governed by a 3-role axis (admin / operator / agent), set per key with --role on seed_auth_client. Each route maps to the set of roles allowed to call it; admin has the widest reach. Capital-transfer surface (operator + admin):
Details: Auth rollout, v3 API endpoints

5. Wire Callbacks

5-1. Callback Configuration

If you set callback: {"url": "..."} when creating a movement, a callback is delivered on every state change.

5-2. Implement the Callback Receiver

The receiving server should validate:
  1. Signature verification - X-QTG-Callback-Signature header
  2. Timestamp freshness - within 5 minutes
  3. Reject duplicate nonces - replay defense
  4. 200 OK response - 4xx is permanent failure, 5xx is retried
Full contract: Callback verification contract Example: Callback receiver example

6. Address Allowlist

The address guard is applied at dispatch time to on-chain executors (CCTP, Gateway, CCIP, Stargate, USDT0, ERC-20 transfer) and to CEX withdrawals. Register the wallet addresses funds move between — not token contracts.

Register Addresses

Allowlist writes are configuration changes with maker/checker. Two supported paths: Dashboard (normal path). With MG_CONFIG_REMOTE_MUTATION_MODE=proposal_only, an admin or operator adds the address under Governance -> Destination addresses (/governance/allowlist). That creates an allowlist.add proposal; a different admin applies it under Governance -> Configuration proposals. The direct POST /dashboard/allowed-addresses route returns 409 CONFIG_PROPOSAL_REQUIRED. Host-maintenance CLI (audited maker/checker bypass).
Avoid raw SQL INSERTs — they bypass the audit log, actor attribution, and the cooling-period stamp.
  • EVM addresses are normalized to lowercase automatically
  • Revoke with python -m qtg.interfaces.tools.revoke_allowed_address --allowed-address-id <id> --reason ... (soft-delete, preferred over deletion)
  • A row is (chain_family, address, chain_id?) — no token scope. An allowlisted address accepts any token, and omitting --chain-id matches every chain.
  • A CEX withdrawal also needs the destination registered in the exchange’s own whitelist; QTG checks both and fails with ADDRESS_NOT_WHITELISTED when the exchange address book does not match.
Details: Runbook — Address Allowlist Management · CEX Stuck Node Recovery
An allowlist row makes a destination eligible; it does not approve a movement. Templates are registered the same way (a route_template.register proposal) — see Registering Templates Outside Dev.

6-1. Onboard an Agent (MCP / qtg-agent)

An agent can read and propose, never approve or execute. It cannot register templates or allowlist entries, and every movement it creates stays in PENDING_APPROVAL even when auto-approve is enabled (agent_auto_approve_skipped). So an agent cannot set itself up; an operator prepares its fence first:
  1. Agent key. seed_auth_client --role agent (above). Note the returned client.id; the authority binds to it.
  2. Allowlist the agent’s destinations (section 6). An agent proposal may only target an EVM address that is allowlisted and bound to its authority, with a matching chain_id; anything else is rejected as destination_outside_authority_scope.
  3. Agent authority. An admin creates an agent_authority.create configuration proposal and a different admin applies it (Governance -> Agent authorities). It binds the agent’s client_id to: allowed protocol families, signer profiles (signers), the allowlisted address IDs (allowed_address_ids), a discovery budget, and a promotion policy.
  4. Give the agent its connection. QTG_API_URL, QTG_KEY_ID, QTG_HMAC_SECRET_FILE, and QTG_AGENT_NAMESPACE=agent/<authority-key> for qtg-agent / qtg-mcp.
What the agent then does on its own, and where a human is still required:
The agent must use values its authority allows (protocol_family, signer_profile, destination). Point it at qtg_authority_describe first instead of letting it guess. CEX routes are not agent-proposable.

7. Docker Deployment

Name the services. The file also defines qtg-testnet and two throwaway test databases; qtg-testnet points at its own qtg_v3_testnet database (JEP-616), so if that database does not exist or has not received the testnet migration bucket, qtg-testnet fails its boot schema check. Service layout: One database holds one network bucket, so run one of the two app services against a shared database — qtg-testnet gets its own database in the compose file.
See operator-network-access.md for production exposure topology.
Docker uses the project-root .env through env_file: ../.env. MG_DATABASE_URL is overridden in docker-compose to use the PostgreSQL hostname.

8. Live Cutover Checklist

Things to confirm before production cutover:
  • MG_WORKERS_ENABLED=true configured
  • Exchange API keys configured and balances checked
  • EVM RPC endpoints configured (if using on-chain paths)
  • Signer configured and preflight passing (if using on-chain paths)
  • MG_AUTH_ENABLED=true plus API keys issued
  • Callback secret configured plus receiving server ready
  • MG_CONFIG_REMOTE_MUTATION_MODE=proposal_only and at least two admin identities (maker ≠ checker)
  • Address allowlist registered (on-chain paths and CEX withdrawal destinations)
  • Route templates registered via applied configuration proposals
  • CEX withdrawal destinations also registered in the exchange’s own whitelist
  • Small-value live test - verify end-to-end with a real small transfer, not dry-run
  • Dashboard access confirmed
  • Callback receiver verification confirmed (signature, nonce, timestamp)
Operational procedures: Secret Rotation · CEX Stuck Node

Replace default RPC endpoints

The quickstart ships PublicNode-based defaults, which are rate-limited and not suitable for live transfers. For production:
  1. Pick a provider per chain (Alchemy, Infura, QuickNode, or self-hosted).
  2. Override per-chain in .env — keep defaults for any chain you do not yet intend to enable:
  3. In air-gapped or regulated environments, also set MG_EVM_RPC_USE_DEFAULTS=false to block any silent fallback to PublicNode.
Live preflight / drill / e2e tools already hard-code defaults off — they require explicit RPC URLs even with MG_EVM_RPC_USE_DEFAULTS=true. Set those explicit URLs before running them. See rpc-defaults.md for the full shipped chain list and the CCIP path caveat.