Files
ObsidianDragon/ObsidianDragon-agent/ARCHITECTURE.md
DanS 5ebccceffc refactor: move AI/agent files into ObsidianDragon-agent/
- copilot-instructions.md → ObsidianDragon-agent/copilot-instructions.md
- ARCHITECTURE.md → ObsidianDragon-agent/ARCHITECTURE.md
- Symlinks at original locations preserve Copilot auto-discovery
2026-04-04 11:29:12 -05:00

7.0 KiB

ObsidianDragon — Architecture Guide

A native C++17 desktop wallet for DragonX (DRGX), built with ImGui + SDL3.

Directory Layout

src/
├── main.cpp                   Entry point, SDL3/backend init, render loop
├── app.h / app.cpp            App class: init, shutdown, frame dispatch
├── app_network.cpp            RPC connection, data refresh, mining ops
├── app_security.cpp           Encryption, lock screen, PIN management
├── app_wizard.cpp             First-run setup wizard
├── config/
│   └── settings.h/.cpp        JSON settings persistence (~/.config/ObsidianDragon/)
├── daemon/
│   ├── embedded_daemon.h/.cpp  dragonxd process lifecycle
│   └── xmrig_manager.h/.cpp   xmrig-hac pool mining management
├── rpc/
│   ├── rpc_client.h/.cpp      JSON-RPC over HTTPS (libcurl)
│   ├── rpc_worker.h/.cpp      Background work queue + main-thread drain
│   ├── connection.h/.cpp      DRAGONX.conf auto-detection
│   └── types.h                Transaction, address data types
├── ui/
│   ├── sidebar.h              NavPage enum, sidebar rendering
│   ├── notifications.h/.cpp   Toast notification system
│   ├── schema/
│   │   ├── ui_schema.h/.cpp       UISchema singleton (TOML → typed lookups)
│   │   ├── skin_manager.h/.cpp    Skin enumeration, switching, import
│   │   └── color_var_resolver.h/.cpp  CSS-style color resolution
│   └── windows/               Per-tab render functions (console, settings, etc.)
├── util/
│   ├── i18n.h/.cpp            Internationalization (TR() macro, I18n singleton)
│   ├── platform.h/.cpp        Cross-platform utilities (paths, URLs)
│   └── logger.h               DEBUG_LOGF / VERBOSE_LOGF macros
├── platform/                  Windows-specific (DX11 context, backdrop effects)
├── embedded/                  Build-generated headers (embedded resources)
└── resources/                 Runtime resource loading (fonts, images)

Threading Model

┌─────────────────────────────────────────────────────────────┐
│  Main Thread (UI)                                           │
│  • ImGui render loop (App::update())                        │
│  • drainResults() each frame → executes completed callbacks │
│  • NEVER does blocking I/O                                  │
└────────────┬────────────────────────┬───────────────────────┘
             │ post()                 │ post()
             ▼                        ▼
┌────────────────────┐  ┌────────────────────────────────┐
│  worker_ (RPCWorker)│  │  fast_worker_ (RPCWorker)      │
│  General RPC polling│  │  Console commands, hashrate    │
│  refreshData() 5s   │  │  Avoids head-of-line blocking  │
└────────────────────┘  └────────────────────────────────┘

┌────────────────────────────────────────────┐
│  Monitor Threads (per child process)       │
│  • EmbeddedDaemon: reads dragonxd stdout   │
│  • XmrigManager: reads xmrig-hac stdout    │
└────────────────────────────────────────────┘

Work Submission Pattern

worker_->post([this]() -> rpc::RPCWorker::MainCb {
    json result = rpc_->call("getinfo");   // Runs on worker thread
    return [this, result]() {
        state_.connected = true;            // Runs on main thread
    };
});

RPC Architecture

Dual-client design avoids head-of-line blocking:

Client Worker Purpose
rpc_ worker_ Main polling: balance, transactions, sync status
fast_rpc_ fast_worker_ Console commands, mining stats

Both are RPCClient instances wrapping libcurl for JSON-RPC over HTTPS to dragonxd.

Error flow: RPCClient::call() throws std::runtime_error → caught in worker lambda → error string captured → MainCb calls Notifications::instance().error(msg) on main thread.

Connection Lifecycle

[Disconnected] → tryConnect() every 5s
    → auto-detect DRAGONX.conf (host, port, rpcuser, rpcpassword)
    → if no config: start embedded daemon → retry
    → post async rpc_->connect() to worker_
        → success: onConnected() → state_.connected = true
        → auth 401: try .cookie auth → retry
        → failure: onDisconnected(reason) → may restart daemon

UI System

Page Routing

NavPage enum defines all pages. App::update() dispatches via switch on current_page_ to render functions (e.g., RenderBalanceTab(this)).

Theme System

  • Layout defined in res/themes/ui.toml, accessed via schema::UI()
  • Skins are TOML files in res/themes/ defining color palettes
  • scripts/expand_themes.py merges layout into skins at build time
  • Colors support CSS-style variables resolved by ColorVarResolver
  • Hot-reload: UISchema::pollForChanges() watches file mtime

Internationalization

  • All UI strings use TR("key") macro → I18n singleton lookup
  • Language files: res/lang/{locale}.json with compiled-in fallbacks
  • Supported: en, es, zh, ru, de, fr, pt, ja, ko

Notifications

Notifications::instance().success("Transaction sent");
Notifications::instance().error("Insufficient funds");

Toast-style popups, bottom-right corner, 5-second default duration.

Build System

CMake 3.20+, C++17. FetchContent for SDL3, nlohmann/json, toml++. Bundled: ImGui, GLAD, miniz, libsodium, qrcode.

./build.sh                   # Dev build
./build.sh --mac-release     # macOS universal .app + DMG
./build.sh --linux-release   # Linux AppImage + zip
./build.sh --win-release     # Windows cross-compile (MinGW)

Platform Backends

Platform Renderer Notes
Windows DirectX 11 imgui_impl_dx11, backdrop effects
macOS OpenGL 3 Universal binary (arm64+x86_64), target 11.0+
Linux OpenGL 3 GLAD loader

Key Conventions

  1. Never block the main thread — all I/O goes through worker_->post()
  2. Never hardcode pixel sizes — use schema::UI() lookups from ui.toml
  3. Always use TR() for strings — no hardcoded English in UI code
  4. Use ICON_MD_* for icons — Material Design icon font
  5. std::unique_ptr for ownership — no raw new/delete
  6. Check the right connection statestate_.connected vs rpc_->isConnected()