Architecture: 3-Layer Structure
Why separate into layers?
“Can’t we just put everything in one file?” — that’s faster when vibe coding. But in a financial system like QTG:- What if an exchange API changes? → Only touch infrastructure (no changes to domain/application)
- Want to test without running real withdrawals? → Swap infrastructure with a fake
- Want to add a new bridge (deBridge)? → Just add a new executor to infrastructure
3-Layer Architecture: The company org chart analogy
Mapping QTG to a company structure:Role of each layer
Inbound HMAC authentication runs in the interfaces layer. This layer’s responsibility is to transform requests into trusted internal commands and hand them off to application — not to make business decisions.
Dependency rules: who can import whom?
Concrete dependency rules
- Domain has no external dependencies. It is pure business logic — no DB, no HTTP, no framework imports.
- Infrastructure may reference domain types (states, execution context, protocols) but never reaches “up” into application orchestration.
- Application is the only layer that combines both: it follows domain rules and drives infrastructure tools (executors, persistence) to carry them out.
- Interfaces delegate to application services only. They never reach directly into persistence or other infrastructure internals.
Why does domain know nothing about infrastructure?
Consider this analogy. The constitution (domain) says:“A withdrawal request state can only transition in this order: PENDING_APPROVAL → APPROVED → EXECUTING → COMPLETED.”This rule does not change regardless of whether the Upbit API changes, PostgreSQL gets replaced by MongoDB, or callbacks are sent to Slack. That is exactly why domain should know nothing about such implementation details. Dependencies must always flow in only one direction: things that change often → things that rarely change. If this is reversed, you get situations like “changing one Upbit API call breaks domain code.”
Bootstrap sequence: what happens when the app starts?
When the server starts, initialization proceeds in this order:What runtime bootstrap does
Each layer in detail
Domain Layer — “These are the laws of physics”
Domain holds business rules only. It knows absolutely nothing about DB or HTTP. The domain layer owns four kinds of rules:Application Layer — “I only give directions”
Application follows domain rules and composes infrastructure tools to create business flows.State advance is the central hub. Nearly every service changes state through this one path. This embodies Single Responsibility — “state transitions and event recording happen in one place.” Every time state changes, a movement event is automatically recorded, so the audit trail is always complete.
Infrastructure Layer — “I do the actual work”
Infrastructure handles real I/O. Every point of contact with the outside world — DB queries, HTTP calls, file reads — lives here.Open-Core executor boundary. QTG ships as Open Core. The Free package registers all of the lanes above at startup — CEX (Upbit, Binance, Bybit, Coinbase, OKX), CCTP, CCIP, EVM erc20, Gateway, USDT0/LayerZero, and Hyperliquid topup. Pro lanes (the Bithumb adapter and the Stargate bridge) are part of the separate Pro package and are registered through a single sanctioned boundary. Free code never reaches into the Pro package directly — that one graceful loader is the only door, and the boundary is mechanically enforced.
Interfaces Layer — “I’m the doorman”
Interfaces should be as thin as possible — receive the request, delegate to the application service, format the result, and return it.Worker loop pattern
All workers share the same loop-runner utility: There are 8 always-on workers (started wheneverMG_WORKERS_ENABLED is set) plus 9 conditional sidecars that start only when their feature flag is on:
evm_nonce_reaper and ccip_stranded_reaper, when enabled, join the core fail-fast group alongside the always-on workers; the remaining conditional workers run as independent sidecars. Workers run for the process-selected network class only (MG_NETWORK_MODE), and seed their heartbeats for that one. Serving mainnet and testnet means two separate deployments.Configuration
All runtime configuration comes from environment variables. Every variable uses theMG_ prefix, values can also be supplied via a .env file, and unknown variables are ignored.