feat(lite): real backend integration — controller, M0-M2a wiring, smoke tool, tests
- LiteWalletController (src/wallet/lite_wallet_controller.*): App-owned; runs real create/open/restore via the linked SDXL bridge with allowBridgeCalls=true; wipes seed/passphrase with sodium_memzero; persists on a ready wallet. M2a: applyLiteRefreshModelToWalletState maps a parsed refresh bundle into WalletState (zatoshi->DRGX, z/t split, tx typing + confirmations, sync progress). - App wiring: liteWallet() accessor + init() construction when supportsLiteBackend(); persist -> settings save. - settings_page: "Validate" reroutes to the controller for real execution (validation- only fallback otherwise); wipes UI secret buffers after submit. - chain name default -> "main" with load-time migration of legacy "DRAGONX" (settings.cpp), preventing the backend "Unknown chain" panic. - M0: build.sh --lite-backend flag; lite_smoke real-backend tool + CMake targets; tests/fake_lite_backend.h deterministic harness. - Tests (test_phase4): injectable-fake bridge, controller lifecycle, chain-name migration, refresh->WalletState mapping; plus the lite test-suite churn-cleanup rewrite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
268
CMakeLists.txt
268
CMakeLists.txt
@@ -14,7 +14,7 @@ if(APPLE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
project(ObsidianDragon
|
||||
project(ObsidianDragon
|
||||
VERSION 1.2.0
|
||||
LANGUAGES C CXX
|
||||
DESCRIPTION "DragonX Cryptocurrency Wallet"
|
||||
@@ -37,6 +37,27 @@ endif()
|
||||
option(DRAGONX_USE_SYSTEM_SDL3 "Use system SDL3 instead of fetching" ON)
|
||||
option(DRAGONX_ENABLE_EMBEDDED_DAEMON "Enable embedded dragonxd support" ON)
|
||||
option(DRAGONX_BUILD_LITE "Build ObsidianDragonLite variant without full-node features" OFF)
|
||||
option(DRAGONX_ENABLE_LITE_BACKEND "Enable real lite wallet backend integration" OFF)
|
||||
option(DRAGONX_ENABLE_CHAT "Enable experimental HushChat protocol/UI integration" OFF)
|
||||
set(DRAGONX_LITE_BACKEND_LIBRARY "" CACHE FILEPATH "Path to a prebuilt SDXL-compatible lite backend library")
|
||||
set(DRAGONX_LITE_BACKEND_INCLUDE_DIR "" CACHE PATH "Optional include directory for SDXL-compatible lite backend headers")
|
||||
set(DRAGONX_LITE_BACKEND_EXTRA_LIBS "" CACHE STRING "Additional libraries needed by the SDXL-compatible lite backend")
|
||||
set(DRAGONX_LITE_BACKEND_LINK_MODE "imported" CACHE STRING "Lite backend link mode; Phase 1 supports imported only")
|
||||
set_property(CACHE DRAGONX_LITE_BACKEND_LINK_MODE PROPERTY STRINGS imported)
|
||||
set(DRAGONX_LITE_BACKEND_ABI "sdxl-c-v1" CACHE STRING "Expected lite backend C ABI version")
|
||||
set(DRAGONX_LITE_BACKEND_SYMBOLS_FILE "" CACHE FILEPATH "Path to generated lite backend exported-symbol inventory")
|
||||
set(DRAGONX_LITE_BACKEND_MANIFEST "" CACHE FILEPATH "Optional path to generated lite backend artifact manifest")
|
||||
option(DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE "Require verified signature metadata in the lite backend artifact manifest" OFF)
|
||||
set(DRAGONX_LITE_BACKEND_REQUIRED_SYMBOLS
|
||||
litelib_wallet_exists
|
||||
litelib_initialize_new
|
||||
litelib_initialize_new_from_phrase
|
||||
litelib_initialize_existing
|
||||
litelib_execute
|
||||
litelib_rust_free_string
|
||||
litelib_check_server_online
|
||||
litelib_shutdown
|
||||
)
|
||||
|
||||
if(DRAGONX_BUILD_LITE)
|
||||
set(DRAGONX_APP_NAME "ObsidianDragonLite")
|
||||
@@ -47,6 +68,83 @@ else()
|
||||
set(DRAGONX_BINARY_NAME "ObsidianDragon")
|
||||
endif()
|
||||
|
||||
set(DRAGONX_LITE_BACKEND_READY OFF)
|
||||
if(DRAGONX_ENABLE_LITE_BACKEND)
|
||||
if(NOT DRAGONX_BUILD_LITE)
|
||||
message(FATAL_ERROR "DRAGONX_ENABLE_LITE_BACKEND is only supported with DRAGONX_BUILD_LITE=ON")
|
||||
endif()
|
||||
if(NOT DRAGONX_LITE_BACKEND_LINK_MODE STREQUAL "imported")
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_LINK_MODE currently supports only 'imported'; runtime dynamic loading is a later bridge-runtime phase")
|
||||
endif()
|
||||
if(NOT DRAGONX_LITE_BACKEND_ABI STREQUAL "sdxl-c-v1")
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_ABI must be sdxl-c-v1")
|
||||
endif()
|
||||
if(NOT DRAGONX_LITE_BACKEND_LIBRARY)
|
||||
message(FATAL_ERROR "DRAGONX_ENABLE_LITE_BACKEND requires DRAGONX_LITE_BACKEND_LIBRARY to point at an SDXL-compatible artifact")
|
||||
endif()
|
||||
if(NOT EXISTS "${DRAGONX_LITE_BACKEND_LIBRARY}")
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_LIBRARY does not exist: ${DRAGONX_LITE_BACKEND_LIBRARY}")
|
||||
endif()
|
||||
if(NOT DRAGONX_LITE_BACKEND_SYMBOLS_FILE)
|
||||
message(FATAL_ERROR "DRAGONX_ENABLE_LITE_BACKEND requires DRAGONX_LITE_BACKEND_SYMBOLS_FILE generated by scripts/build-lite-backend-artifact.sh")
|
||||
endif()
|
||||
if(NOT EXISTS "${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_SYMBOLS_FILE does not exist: ${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||
endif()
|
||||
file(STRINGS "${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}" DRAGONX_LITE_BACKEND_SYMBOL_LINES)
|
||||
if(NOT DRAGONX_LITE_BACKEND_SYMBOL_LINES)
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_SYMBOLS_FILE is empty: ${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||
endif()
|
||||
foreach(DRAGONX_LITE_REQUIRED_SYMBOL IN LISTS DRAGONX_LITE_BACKEND_REQUIRED_SYMBOLS)
|
||||
list(FIND DRAGONX_LITE_BACKEND_SYMBOL_LINES "${DRAGONX_LITE_REQUIRED_SYMBOL}" DRAGONX_LITE_SYMBOL_INDEX)
|
||||
if(DRAGONX_LITE_SYMBOL_INDEX EQUAL -1)
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_SYMBOLS_FILE is missing required symbol: ${DRAGONX_LITE_REQUIRED_SYMBOL}")
|
||||
endif()
|
||||
endforeach()
|
||||
if(DRAGONX_LITE_BACKEND_MANIFEST AND NOT EXISTS "${DRAGONX_LITE_BACKEND_MANIFEST}")
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_MANIFEST does not exist: ${DRAGONX_LITE_BACKEND_MANIFEST}")
|
||||
endif()
|
||||
if(DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE)
|
||||
if(NOT DRAGONX_LITE_BACKEND_MANIFEST)
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE requires DRAGONX_LITE_BACKEND_MANIFEST")
|
||||
endif()
|
||||
file(READ "${DRAGONX_LITE_BACKEND_MANIFEST}" DRAGONX_LITE_BACKEND_MANIFEST_JSON)
|
||||
string(JSON DRAGONX_LITE_SIGNATURE_STATUS ERROR_VARIABLE DRAGONX_LITE_SIGNATURE_STATUS_ERROR GET "${DRAGONX_LITE_BACKEND_MANIFEST_JSON}" signature_verification verification_status)
|
||||
if(DRAGONX_LITE_SIGNATURE_STATUS_ERROR)
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_MANIFEST is missing signature verification status")
|
||||
endif()
|
||||
if(NOT DRAGONX_LITE_SIGNATURE_STATUS STREQUAL "verified")
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE requires verified signature metadata")
|
||||
endif()
|
||||
string(JSON DRAGONX_LITE_SIGNATURE_VERIFIED_SHA ERROR_VARIABLE DRAGONX_LITE_SIGNATURE_VERIFIED_SHA_ERROR GET "${DRAGONX_LITE_BACKEND_MANIFEST_JSON}" signature_verification verified_artifact_sha256)
|
||||
string(JSON DRAGONX_LITE_ARTIFACT_SHA ERROR_VARIABLE DRAGONX_LITE_ARTIFACT_SHA_ERROR GET "${DRAGONX_LITE_BACKEND_MANIFEST_JSON}" artifact sha256)
|
||||
if(DRAGONX_LITE_SIGNATURE_VERIFIED_SHA_ERROR OR DRAGONX_LITE_ARTIFACT_SHA_ERROR)
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_MANIFEST is missing artifact/signature SHA-256 metadata")
|
||||
endif()
|
||||
if(NOT DRAGONX_LITE_SIGNATURE_VERIFIED_SHA STREQUAL DRAGONX_LITE_ARTIFACT_SHA)
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_MANIFEST signature metadata does not verify the artifact SHA-256")
|
||||
endif()
|
||||
string(JSON DRAGONX_LITE_SIGNATURE_PERFORMED ERROR_VARIABLE DRAGONX_LITE_SIGNATURE_PERFORMED_ERROR GET "${DRAGONX_LITE_BACKEND_MANIFEST_JSON}" signature_verification verification_performed)
|
||||
if(DRAGONX_LITE_SIGNATURE_PERFORMED_ERROR OR NOT DRAGONX_LITE_SIGNATURE_PERFORMED)
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE requires verification_performed=true")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(dragonx_lite_backend UNKNOWN IMPORTED)
|
||||
set_target_properties(dragonx_lite_backend PROPERTIES
|
||||
IMPORTED_LOCATION "${DRAGONX_LITE_BACKEND_LIBRARY}"
|
||||
)
|
||||
if(DRAGONX_LITE_BACKEND_INCLUDE_DIR)
|
||||
if(NOT IS_DIRECTORY "${DRAGONX_LITE_BACKEND_INCLUDE_DIR}")
|
||||
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_INCLUDE_DIR does not exist: ${DRAGONX_LITE_BACKEND_INCLUDE_DIR}")
|
||||
endif()
|
||||
set_target_properties(dragonx_lite_backend PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${DRAGONX_LITE_BACKEND_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
set(DRAGONX_LITE_BACKEND_READY ON)
|
||||
endif()
|
||||
|
||||
include(CTest)
|
||||
|
||||
# Output directories
|
||||
@@ -279,6 +377,26 @@ set(QRCODE_SOURCES
|
||||
# Application Sources
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# MinGW writes .obj.d dependency files, so this overlong generated source name
|
||||
# needs a short wrapper to stay under the Windows filename component limit.
|
||||
set(DRAGONX_LONG_LITE_BATCH_SOURCE
|
||||
)
|
||||
set(DRAGONX_LONG_LITE_BATCH_COMPILE_SOURCE ${DRAGONX_LONG_LITE_BATCH_SOURCE})
|
||||
if(WIN32)
|
||||
set(DRAGONX_LONG_LITE_BATCH_COMPILE_SOURCE
|
||||
${CMAKE_BINARY_DIR}/generated/short_sources/lite_batch90_receipt_plan.cpp
|
||||
)
|
||||
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated/short_sources)
|
||||
file(WRITE ${DRAGONX_LONG_LITE_BATCH_COMPILE_SOURCE}
|
||||
"#include \"${CMAKE_SOURCE_DIR}/${DRAGONX_LONG_LITE_BATCH_SOURCE}\"\n"
|
||||
)
|
||||
set_source_files_properties(${DRAGONX_LONG_LITE_BATCH_COMPILE_SOURCE}
|
||||
PROPERTIES
|
||||
GENERATED TRUE
|
||||
OBJECT_DEPENDS ${CMAKE_SOURCE_DIR}/${DRAGONX_LONG_LITE_BATCH_SOURCE}
|
||||
)
|
||||
endif()
|
||||
|
||||
set(APP_SOURCES
|
||||
src/main.cpp
|
||||
src/app.cpp
|
||||
@@ -290,6 +408,30 @@ set(APP_SOURCES
|
||||
src/services/wallet_security_controller.cpp
|
||||
src/services/wallet_security_workflow.cpp
|
||||
src/services/wallet_security_workflow_executor.cpp
|
||||
src/chat/chat_protocol.cpp
|
||||
src/wallet/lite_backend_artifact_contract.cpp
|
||||
src/wallet/lite_backend_artifact_resolver.cpp
|
||||
src/wallet/lite_bridge_runtime.cpp
|
||||
src/wallet/lite_client_bridge.cpp
|
||||
src/wallet/lite_connection_service.cpp
|
||||
src/wallet/lite_wallet_controller.cpp
|
||||
src/wallet/lite_result_parsers.cpp
|
||||
src/wallet/lite_sync_service.cpp
|
||||
src/wallet/lite_wallet_gateway.cpp
|
||||
src/wallet/lite_wallet_refresh_service.cpp
|
||||
src/wallet/lite_wallet_state_mapper.cpp
|
||||
src/wallet/lite_wallet_state_apply_plan.cpp
|
||||
src/wallet/lite_wallet_state_apply_executor.cpp
|
||||
src/wallet/lite_wallet_app_refresh_coordinator.cpp
|
||||
src/wallet/lite_wallet_refresh_readiness_policy.cpp
|
||||
src/wallet/lite_wallet_app_refresh_orchestrator.cpp
|
||||
src/wallet/lite_wallet_sync_app_refresh_integration.cpp
|
||||
src/wallet/lite_wallet_sync_execution_readiness.cpp
|
||||
src/wallet/lite_wallet_lifecycle_ui_adapter.cpp
|
||||
${DRAGONX_LONG_LITE_BATCH_COMPILE_SOURCE}
|
||||
src/wallet/lite_wallet_server_selection_adapter.cpp
|
||||
src/wallet/lite_wallet_server_lifecycle_readiness.cpp
|
||||
src/wallet/lite_wallet_lifecycle_service.cpp
|
||||
src/data/wallet_state.cpp
|
||||
src/data/transaction_history_cache.cpp
|
||||
src/ui/theme.cpp
|
||||
@@ -390,6 +532,30 @@ set(APP_HEADERS
|
||||
src/services/wallet_security_controller.h
|
||||
src/services/wallet_security_workflow.h
|
||||
src/services/wallet_security_workflow_executor.h
|
||||
src/wallet/wallet_capabilities.h
|
||||
src/wallet/wallet_backend.h
|
||||
src/wallet/lite_backend_artifact_contract.h
|
||||
src/wallet/lite_backend_artifact_resolver.h
|
||||
src/wallet/lite_bridge_runtime.h
|
||||
src/wallet/lite_client_bridge.h
|
||||
src/wallet/lite_connection_service.h
|
||||
src/wallet/lite_result_parsers.h
|
||||
src/wallet/lite_sync_service.h
|
||||
src/wallet/lite_wallet_gateway.h
|
||||
src/wallet/lite_wallet_refresh_service.h
|
||||
src/wallet/lite_wallet_state_mapper.h
|
||||
src/wallet/lite_wallet_state_apply_plan.h
|
||||
src/wallet/lite_wallet_state_apply_executor.h
|
||||
src/wallet/lite_wallet_app_refresh_coordinator.h
|
||||
src/wallet/lite_wallet_refresh_readiness_policy.h
|
||||
src/wallet/lite_wallet_app_refresh_orchestrator.h
|
||||
src/wallet/lite_wallet_sync_app_refresh_integration.h
|
||||
src/wallet/lite_wallet_sync_execution_readiness.h
|
||||
src/wallet/lite_wallet_lifecycle_ui_adapter.h
|
||||
src/wallet/lite_wallet_server_selection_adapter.h
|
||||
src/wallet/lite_wallet_server_lifecycle_readiness.h
|
||||
src/wallet/lite_wallet_lifecycle_service.h
|
||||
src/chat/chat_protocol.h
|
||||
src/config/version.h
|
||||
src/data/wallet_state.h
|
||||
src/data/transaction_history_cache.h
|
||||
@@ -489,10 +655,12 @@ if(WIN32)
|
||||
set(WIN_RC_FILE ${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc)
|
||||
endif()
|
||||
|
||||
# Generate version.h from the single project(VERSION ...) declaration
|
||||
# Generate version values from the single project(VERSION ...) declaration.
|
||||
# Keep the build-specific app name in the build tree so full/lite configures do
|
||||
# not rewrite a tracked source header.
|
||||
configure_file(
|
||||
${CMAKE_SOURCE_DIR}/src/config/version.h.in
|
||||
${CMAKE_SOURCE_DIR}/src/config/version.h
|
||||
${CMAKE_BINARY_DIR}/generated/dragonx_generated_version.h
|
||||
@ONLY
|
||||
)
|
||||
|
||||
@@ -554,6 +722,34 @@ target_link_libraries(ObsidianDragon PRIVATE
|
||||
${SODIUM_LIBRARY}
|
||||
)
|
||||
|
||||
if(DRAGONX_LITE_BACKEND_READY)
|
||||
target_link_libraries(ObsidianDragon PRIVATE dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS})
|
||||
|
||||
# Real-backend smoke tool (only built when a real lite backend is linked).
|
||||
add_executable(lite_smoke
|
||||
tools/lite_smoke.cpp
|
||||
src/wallet/lite_client_bridge.cpp
|
||||
src/wallet/lite_bridge_runtime.cpp
|
||||
src/wallet/lite_backend_artifact_contract.cpp
|
||||
src/wallet/lite_backend_artifact_resolver.cpp
|
||||
src/wallet/lite_connection_service.cpp
|
||||
)
|
||||
target_include_directories(lite_smoke PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_BINARY_DIR}/generated
|
||||
${SODIUM_INCLUDE_DIR}
|
||||
)
|
||||
target_compile_definitions(lite_smoke PRIVATE DRAGONX_ENABLE_LITE_BACKEND=1)
|
||||
target_link_libraries(lite_smoke PRIVATE
|
||||
dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS}
|
||||
nlohmann_json::nlohmann_json
|
||||
${SODIUM_LIBRARY}
|
||||
)
|
||||
if(UNIX)
|
||||
target_link_libraries(lite_smoke PRIVATE ${CMAKE_DL_LIBS} pthread)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Platform-specific settings
|
||||
if(WIN32)
|
||||
target_link_libraries(ObsidianDragon PRIVATE ws2_32 winmm imm32 version setupapi dwmapi crypt32 wldap32 psapi iphlpapi d3d11 dxgi d3dcompiler dcomp)
|
||||
@@ -584,6 +780,8 @@ target_compile_definitions(ObsidianDragon PRIVATE
|
||||
DRAGONX_DEBUG
|
||||
DRAGONX_LITE_BUILD=$<BOOL:${DRAGONX_BUILD_LITE}>
|
||||
DRAGONX_ENABLE_EMBEDDED_DAEMON=$<BOOL:${DRAGONX_ENABLE_EMBEDDED_DAEMON}>
|
||||
DRAGONX_ENABLE_LITE_BACKEND=$<BOOL:${DRAGONX_LITE_BACKEND_READY}>
|
||||
DRAGONX_ENABLE_CHAT=$<BOOL:${DRAGONX_ENABLE_CHAT}>
|
||||
)
|
||||
if(WIN32)
|
||||
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_USE_DX11)
|
||||
@@ -591,6 +789,25 @@ else()
|
||||
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_HAS_GLAD)
|
||||
endif()
|
||||
|
||||
add_executable(HushChatFixtureCheck
|
||||
tools/hushchat_fixture_check.cpp
|
||||
src/chat/chat_protocol.cpp
|
||||
)
|
||||
|
||||
target_include_directories(HushChatFixtureCheck PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${SODIUM_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(HushChatFixtureCheck PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
${SODIUM_LIBRARY}
|
||||
)
|
||||
|
||||
target_compile_definitions(HushChatFixtureCheck PRIVATE
|
||||
DRAGONX_ENABLE_CHAT=0
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copy resources
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -757,6 +974,30 @@ if(BUILD_TESTING)
|
||||
src/services/wallet_security_controller.cpp
|
||||
src/services/wallet_security_workflow.cpp
|
||||
src/services/wallet_security_workflow_executor.cpp
|
||||
src/chat/chat_protocol.cpp
|
||||
src/wallet/lite_backend_artifact_contract.cpp
|
||||
src/wallet/lite_backend_artifact_resolver.cpp
|
||||
src/wallet/lite_bridge_runtime.cpp
|
||||
src/wallet/lite_client_bridge.cpp
|
||||
src/wallet/lite_connection_service.cpp
|
||||
src/wallet/lite_wallet_controller.cpp
|
||||
src/wallet/lite_result_parsers.cpp
|
||||
src/wallet/lite_sync_service.cpp
|
||||
src/wallet/lite_wallet_gateway.cpp
|
||||
src/wallet/lite_wallet_refresh_service.cpp
|
||||
src/wallet/lite_wallet_state_mapper.cpp
|
||||
src/wallet/lite_wallet_state_apply_plan.cpp
|
||||
src/wallet/lite_wallet_state_apply_executor.cpp
|
||||
src/wallet/lite_wallet_app_refresh_coordinator.cpp
|
||||
src/wallet/lite_wallet_refresh_readiness_policy.cpp
|
||||
src/wallet/lite_wallet_app_refresh_orchestrator.cpp
|
||||
src/wallet/lite_wallet_sync_app_refresh_integration.cpp
|
||||
src/wallet/lite_wallet_sync_execution_readiness.cpp
|
||||
src/wallet/lite_wallet_lifecycle_ui_adapter.cpp
|
||||
${DRAGONX_LONG_LITE_BATCH_COMPILE_SOURCE}
|
||||
src/wallet/lite_wallet_server_selection_adapter.cpp
|
||||
src/wallet/lite_wallet_server_lifecycle_readiness.cpp
|
||||
src/wallet/lite_wallet_lifecycle_service.cpp
|
||||
src/ui/explorer/explorer_block_cache.cpp
|
||||
src/ui/windows/balance_address_list.cpp
|
||||
src/ui/windows/balance_recent_tx.cpp
|
||||
@@ -772,10 +1013,12 @@ if(BUILD_TESTING)
|
||||
src/data/transaction_history_cache.cpp
|
||||
src/daemon/lifecycle_adapters.cpp
|
||||
src/rpc/connection.cpp
|
||||
src/config/settings.cpp
|
||||
src/resources/embedded_resources.cpp
|
||||
src/util/secure_vault.cpp
|
||||
src/util/platform.cpp
|
||||
src/util/logger.cpp
|
||||
${MINIZ_SOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(ObsidianDragonTests PRIVATE
|
||||
@@ -793,6 +1036,18 @@ if(BUILD_TESTING)
|
||||
${SODIUM_LIBRARY}
|
||||
)
|
||||
|
||||
target_compile_definitions(ObsidianDragonTests PRIVATE
|
||||
DRAGONX_ENABLE_CHAT=$<BOOL:${DRAGONX_ENABLE_CHAT}>
|
||||
DRAGONX_LITE_BUILD=$<BOOL:${DRAGONX_BUILD_LITE}>
|
||||
DRAGONX_ENABLE_EMBEDDED_DAEMON=$<BOOL:${DRAGONX_ENABLE_EMBEDDED_DAEMON}>
|
||||
DRAGONX_ENABLE_LITE_BACKEND=$<BOOL:${DRAGONX_LITE_BACKEND_READY}>
|
||||
DRAGONX_TEST_FIXTURE_DIR="${CMAKE_SOURCE_DIR}/tests/fixtures"
|
||||
)
|
||||
|
||||
if(DRAGONX_LITE_BACKEND_READY)
|
||||
target_link_libraries(ObsidianDragonTests PRIVATE dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS})
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
target_link_libraries(ObsidianDragonTests PRIVATE ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
@@ -812,4 +1067,11 @@ message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
|
||||
message(STATUS " ImGui dir: ${IMGUI_DIR}")
|
||||
message(STATUS " SDL3 found: ${SDL3_FOUND}")
|
||||
message(STATUS " Sodium lib: ${SODIUM_LIBRARY}")
|
||||
message(STATUS " Lite build: ${DRAGONX_BUILD_LITE}")
|
||||
message(STATUS " Lite requested: ${DRAGONX_ENABLE_LITE_BACKEND}")
|
||||
message(STATUS " Lite backend: ${DRAGONX_LITE_BACKEND_READY}")
|
||||
message(STATUS " Lite lib: ${DRAGONX_LITE_BACKEND_LIBRARY}")
|
||||
message(STATUS " Lite symbols: ${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||
message(STATUS " Lite manifest: ${DRAGONX_LITE_BACKEND_MANIFEST}")
|
||||
message(STATUS " Lite signature: ${DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE}")
|
||||
message(STATUS "")
|
||||
|
||||
217
build.sh
217
build.sh
@@ -42,6 +42,7 @@ DO_LINUX=false
|
||||
DO_WIN=false
|
||||
DO_MAC=false
|
||||
DO_LITE=false
|
||||
DO_LITE_BACKEND=false
|
||||
CLEAN=false
|
||||
BUILD_TYPE="Release"
|
||||
|
||||
@@ -56,6 +57,9 @@ Targets (at least one required, or none for dev build):
|
||||
--win-release Windows cross-compile (mingw-w64) -> release/windows/
|
||||
--mac-release macOS .app bundle + DMG -> release/mac/
|
||||
--lite Build ObsidianDragonLite variant (no embedded daemon/full-node features)
|
||||
--lite-backend Like --lite, and link the real SDXL litelib backend artifact
|
||||
(auto-discovers build/lite-backend/<platform>/; build it with
|
||||
scripts/build-lite-backend-artifact.sh, or set DRAGONX_LITE_BACKEND_DIR)
|
||||
|
||||
Build trees are stored under build/{linux,windows,mac}/
|
||||
|
||||
@@ -88,6 +92,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--win-release) DO_WIN=true; shift ;;
|
||||
--mac-release) DO_MAC=true; shift ;;
|
||||
--lite) DO_LITE=true; shift ;;
|
||||
--lite-backend) DO_LITE=true; DO_LITE_BACKEND=true; shift ;;
|
||||
-c|--clean) CLEAN=true; shift ;;
|
||||
-d|--debug) BUILD_TYPE="Debug"; shift ;;
|
||||
-j) JOBS="$2"; shift 2 ;;
|
||||
@@ -109,6 +114,44 @@ if $DO_LITE; then
|
||||
info "Lite mode enabled: building ${APP_BASENAME}"
|
||||
fi
|
||||
|
||||
# ── Lite backend (real SDXL litelib) linking ─────────────────────────────────
|
||||
# Enables DRAGONX_ENABLE_LITE_BACKEND with an imported artifact produced by
|
||||
# scripts/build-lite-backend-artifact.sh. Auto-discovers build/lite-backend/<platform>/;
|
||||
# override the directory with DRAGONX_LITE_BACKEND_DIR.
|
||||
if $DO_LITE_BACKEND; then
|
||||
case "$(uname -s)" in
|
||||
Linux) lb_platform="linux" ;;
|
||||
Darwin) lb_platform="macos" ;;
|
||||
*) lb_platform="linux" ;;
|
||||
esac
|
||||
lb_dir="${DRAGONX_LITE_BACKEND_DIR:-$SCRIPT_DIR/build/lite-backend/$lb_platform}"
|
||||
lb_lib=""
|
||||
for cand in "$lb_dir"/libsilentdragonxlite.a "$lb_dir"/libsilentdragonxlite.so "$lb_dir"/silentdragonxlite.lib; do
|
||||
[[ -f "$cand" ]] && { lb_lib="$cand"; break; }
|
||||
done
|
||||
lb_symbols="$lb_dir/lite-backend-symbols.txt"
|
||||
lb_manifest="$lb_dir/lite-backend-artifact-manifest.json"
|
||||
if [[ -z "$lb_lib" || ! -f "$lb_symbols" ]]; then
|
||||
err "Lite backend artifact not found under: $lb_dir"
|
||||
err "Build it first: ./scripts/build-lite-backend-artifact.sh --platform $lb_platform"
|
||||
err "Or set DRAGONX_LITE_BACKEND_DIR to an existing artifact directory."
|
||||
exit 1
|
||||
fi
|
||||
CMAKE_LITE_ARGS+=(
|
||||
"-DDRAGONX_ENABLE_LITE_BACKEND=ON"
|
||||
"-DDRAGONX_LITE_BACKEND_LIBRARY=$lb_lib"
|
||||
"-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE=$lb_symbols"
|
||||
"-DDRAGONX_LITE_BACKEND_LINK_MODE=imported"
|
||||
"-DDRAGONX_LITE_BACKEND_ABI=sdxl-c-v1"
|
||||
)
|
||||
[[ -f "$lb_manifest" ]] && CMAKE_LITE_ARGS+=("-DDRAGONX_LITE_BACKEND_MANIFEST=$lb_manifest")
|
||||
info "Lite backend enabled: $lb_lib"
|
||||
fi
|
||||
|
||||
should_bundle_full_node_assets() {
|
||||
! $DO_LITE
|
||||
}
|
||||
|
||||
# ── Helper: find resource files ──────────────────────────────────────────────
|
||||
find_sapling_params() {
|
||||
local dirs=(
|
||||
@@ -266,16 +309,20 @@ build_release_linux() {
|
||||
strip "bin/${APP_BASENAME}"
|
||||
info "Binary: $(du -h "bin/${APP_BASENAME}" | cut -f1)"
|
||||
|
||||
# ── Bundle daemon ────────────────────────────────────────────────────────
|
||||
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
||||
if should_bundle_full_node_assets; then
|
||||
# ── Bundle daemon ────────────────────────────────────────────────────
|
||||
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
||||
|
||||
# ── Bundle Sapling params ────────────────────────────────────────────────
|
||||
SAPLING_SPEND="" SAPLING_OUTPUT=""
|
||||
find_sapling_params && {
|
||||
cp -f "$SAPLING_SPEND" "bin/sapling-spend.params"
|
||||
cp -f "$SAPLING_OUTPUT" "bin/sapling-output.params"
|
||||
info "Bundled Sapling params"
|
||||
} || warn "Sapling params not found — not bundled"
|
||||
# ── Bundle Sapling params ────────────────────────────────────────────
|
||||
SAPLING_SPEND="" SAPLING_OUTPUT=""
|
||||
find_sapling_params && {
|
||||
cp -f "$SAPLING_SPEND" "bin/sapling-spend.params"
|
||||
cp -f "$SAPLING_OUTPUT" "bin/sapling-output.params"
|
||||
info "Bundled Sapling params"
|
||||
} || warn "Sapling params not found — not bundled"
|
||||
else
|
||||
info "Lite mode: skipping daemon and Sapling/asmap bundling"
|
||||
fi
|
||||
|
||||
# ── Package: release/linux/ ──────────────────────────────────────────────
|
||||
rm -rf "$out"
|
||||
@@ -286,11 +333,13 @@ build_release_linux() {
|
||||
mkdir -p "$dist_dir"
|
||||
|
||||
cp "bin/${APP_BASENAME}" "$dist_dir/"
|
||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$dist_dir/"
|
||||
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$dist_dir/"
|
||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$dist_dir/"
|
||||
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$dist_dir/"
|
||||
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$dist_dir/"
|
||||
if should_bundle_full_node_assets; then
|
||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$dist_dir/"
|
||||
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$dist_dir/"
|
||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$dist_dir/"
|
||||
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$dist_dir/"
|
||||
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$dist_dir/"
|
||||
fi
|
||||
# Bundle xmrig for mining support
|
||||
local XMRIG_LINUX="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
|
||||
[[ -f "$XMRIG_LINUX" ]] && { cp "$XMRIG_LINUX" "$dist_dir/"; chmod +x "$dist_dir/xmrig"; info "Bundled xmrig"; } || warn "xmrig not found — mining unavailable in zip"
|
||||
@@ -315,13 +364,15 @@ build_release_linux() {
|
||||
cp "bin/${APP_BASENAME}" "$APPDIR/usr/bin/"
|
||||
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
|
||||
|
||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
||||
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
|
||||
# Daemon data files must be alongside the daemon binary (usr/bin/)
|
||||
# because dragonxd searches relative to its own directory.
|
||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/bin/"
|
||||
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$APPDIR/usr/bin/"
|
||||
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$APPDIR/usr/bin/"
|
||||
if should_bundle_full_node_assets; then
|
||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
||||
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
|
||||
# Daemon data files must be alongside the daemon binary (usr/bin/)
|
||||
# because dragonxd searches relative to its own directory.
|
||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/bin/"
|
||||
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$APPDIR/usr/bin/"
|
||||
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$APPDIR/usr/bin/"
|
||||
fi
|
||||
# Bundle xmrig for mining support
|
||||
local XMRIG_LINUX_AI="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
|
||||
[[ -f "$XMRIG_LINUX_AI" ]] && { cp "$XMRIG_LINUX_AI" "$APPDIR/usr/bin/"; chmod +x "$APPDIR/usr/bin/xmrig"; }
|
||||
@@ -510,22 +561,26 @@ HDR
|
||||
|
||||
# ── Daemon binaries ──────────────────────────────────────────────
|
||||
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
||||
if [[ -d "$DD" && -f "$DD/dragonxd.exe" ]]; then
|
||||
info "Embedding daemon binaries ..."
|
||||
echo -e "\n#define HAS_EMBEDDED_DAEMON 1\n" >> "$GEN/embedded_data.h"
|
||||
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||
local sym=$(echo "$f" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||
if [[ -f "$DD/$f" ]]; then
|
||||
cp -f "$DD/$f" "$RES/$f"
|
||||
info " Staged $f ($(du -h "$DD/$f" | cut -f1))"
|
||||
echo "INCBIN(${sym}, \"$RES/$f\");" >> "$GEN/embedded_data.h"
|
||||
else
|
||||
echo "extern \"C\" { static const uint8_t* g_${sym}_data = nullptr; }" >> "$GEN/embedded_data.h"
|
||||
echo "static const unsigned int g_${sym}_size = 0;" >> "$GEN/embedded_data.h"
|
||||
fi
|
||||
done
|
||||
if should_bundle_full_node_assets; then
|
||||
if [[ -d "$DD" && -f "$DD/dragonxd.exe" ]]; then
|
||||
info "Embedding daemon binaries ..."
|
||||
echo -e "\n#define HAS_EMBEDDED_DAEMON 1\n" >> "$GEN/embedded_data.h"
|
||||
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||
local sym=$(echo "$f" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||
if [[ -f "$DD/$f" ]]; then
|
||||
cp -f "$DD/$f" "$RES/$f"
|
||||
info " Staged $f ($(du -h "$DD/$f" | cut -f1))"
|
||||
echo "INCBIN(${sym}, \"$RES/$f\");" >> "$GEN/embedded_data.h"
|
||||
else
|
||||
echo "extern \"C\" { static const uint8_t* g_${sym}_data = nullptr; }" >> "$GEN/embedded_data.h"
|
||||
echo "static const unsigned int g_${sym}_size = 0;" >> "$GEN/embedded_data.h"
|
||||
fi
|
||||
done
|
||||
else
|
||||
warn "prebuilt-binaries/dragonxd-win/ not found — wallet-only build"
|
||||
fi
|
||||
else
|
||||
warn "prebuilt-binaries/dragonxd-win/ not found — wallet-only build"
|
||||
info "Lite mode: skipping embedded daemon binaries"
|
||||
fi
|
||||
|
||||
# ── xmrig binary (from prebuilt-binaries/xmrig-hac/) ────────────────
|
||||
@@ -631,16 +686,20 @@ HDR
|
||||
cp "bin/${APP_BASENAME}.exe" "$dist_dir/"
|
||||
|
||||
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
||||
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
||||
done
|
||||
if should_bundle_full_node_assets; then
|
||||
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
||||
done
|
||||
|
||||
# Bundle Sapling params + asmap for the zip distribution
|
||||
# (The single-file exe has these embedded via INCBIN, but the zip
|
||||
# needs them on disk so the daemon can find them in its work dir.)
|
||||
for f in sapling-spend.params sapling-output.params asmap.dat; do
|
||||
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
||||
done
|
||||
# Bundle Sapling params + asmap for the zip distribution
|
||||
# (The single-file exe has these embedded via INCBIN, but the zip
|
||||
# needs them on disk so the daemon can find them in its work dir.)
|
||||
for f in sapling-spend.params sapling-output.params asmap.dat; do
|
||||
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
||||
done
|
||||
else
|
||||
info "Lite mode: skipping daemon and Sapling/asmap assets in Windows zip"
|
||||
fi
|
||||
|
||||
# Bundle xmrig for mining support
|
||||
local XMRIG_WIN="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig.exe"
|
||||
@@ -907,33 +966,37 @@ TOOLCHAIN
|
||||
# Resources
|
||||
cp -r bin/res/* "$RESOURCES/res/" 2>/dev/null || true
|
||||
|
||||
# Daemon binaries (macOS native, from dragonxd-mac/)
|
||||
local daemon_dir="$SCRIPT_DIR/prebuilt-binaries/dragonxd-mac"
|
||||
if [[ -d "$daemon_dir" ]]; then
|
||||
for f in dragonxd dragonx-cli dragonx-tx; do
|
||||
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; chmod +x "$MACOS/$f"; info " Bundled $f"; }
|
||||
done
|
||||
for f in sapling-spend.params sapling-output.params; do
|
||||
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; info " Bundled $f"; }
|
||||
done
|
||||
elif ! $IS_CROSS; then
|
||||
# Native macOS: try standard paths
|
||||
local daemon_paths=(
|
||||
"$SCRIPT_DIR/../dragonxd"
|
||||
"$HOME/dragonx/src/dragonxd"
|
||||
)
|
||||
for p in "${daemon_paths[@]}"; do
|
||||
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonxd"; chmod +x "$MACOS/dragonxd"; info " Bundled dragonxd"; break; }
|
||||
done
|
||||
local cli_paths=(
|
||||
"$SCRIPT_DIR/../dragonx-cli"
|
||||
"$HOME/dragonx/src/dragonx-cli"
|
||||
)
|
||||
for p in "${cli_paths[@]}"; do
|
||||
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonx-cli"; chmod +x "$MACOS/dragonx-cli"; info " Bundled dragonx-cli"; break; }
|
||||
done
|
||||
if should_bundle_full_node_assets; then
|
||||
# Daemon binaries (macOS native, from dragonxd-mac/)
|
||||
local daemon_dir="$SCRIPT_DIR/prebuilt-binaries/dragonxd-mac"
|
||||
if [[ -d "$daemon_dir" ]]; then
|
||||
for f in dragonxd dragonx-cli dragonx-tx; do
|
||||
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; chmod +x "$MACOS/$f"; info " Bundled $f"; }
|
||||
done
|
||||
for f in sapling-spend.params sapling-output.params; do
|
||||
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; info " Bundled $f"; }
|
||||
done
|
||||
elif ! $IS_CROSS; then
|
||||
# Native macOS: try standard paths
|
||||
local daemon_paths=(
|
||||
"$SCRIPT_DIR/../dragonxd"
|
||||
"$HOME/dragonx/src/dragonxd"
|
||||
)
|
||||
for p in "${daemon_paths[@]}"; do
|
||||
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonxd"; chmod +x "$MACOS/dragonxd"; info " Bundled dragonxd"; break; }
|
||||
done
|
||||
local cli_paths=(
|
||||
"$SCRIPT_DIR/../dragonx-cli"
|
||||
"$HOME/dragonx/src/dragonx-cli"
|
||||
)
|
||||
for p in "${cli_paths[@]}"; do
|
||||
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonx-cli"; chmod +x "$MACOS/dragonx-cli"; info " Bundled dragonx-cli"; break; }
|
||||
done
|
||||
else
|
||||
warn "prebuilt-binaries/dragonxd-mac/ not found — place macOS daemon binaries there for bundling"
|
||||
fi
|
||||
else
|
||||
warn "prebuilt-binaries/dragonxd-mac/ not found — place macOS daemon binaries there for bundling"
|
||||
info "Lite mode: skipping macOS daemon and Sapling/asmap bundling"
|
||||
fi
|
||||
|
||||
# xmrig binary (from prebuilt-binaries/xmrig-hac/)
|
||||
@@ -946,11 +1009,13 @@ TOOLCHAIN
|
||||
warn "xmrig not found — mining unavailable in .app"
|
||||
fi
|
||||
|
||||
# asmap.dat — placed in MacOS/ so the daemon finds it next to its binary
|
||||
find_asmap 2>/dev/null && {
|
||||
cp "$ASMAP_DAT" "$MACOS/asmap.dat"
|
||||
info " Bundled asmap.dat"
|
||||
}
|
||||
if should_bundle_full_node_assets; then
|
||||
# asmap.dat — placed in MacOS/ so the daemon finds it next to its binary
|
||||
find_asmap 2>/dev/null && {
|
||||
cp "$ASMAP_DAT" "$MACOS/asmap.dat"
|
||||
info " Bundled asmap.dat"
|
||||
}
|
||||
fi
|
||||
|
||||
# Bundle SDL3 dylib
|
||||
local sdl_dylib=""
|
||||
|
||||
51
src/app.cpp
51
src/app.cpp
@@ -11,6 +11,8 @@
|
||||
#include "rpc/rpc_worker.h"
|
||||
#include "rpc/connection.h"
|
||||
#include "config/settings.h"
|
||||
#include "wallet/lite_wallet_controller.h"
|
||||
#include "wallet/lite_wallet_server_selection_adapter.h"
|
||||
#include "daemon/daemon_controller.h"
|
||||
#include "daemon/embedded_daemon.h"
|
||||
#include "daemon/lifecycle_adapters.h"
|
||||
@@ -164,6 +166,16 @@ bool App::init()
|
||||
settings_->clearUpgradeSave();
|
||||
}
|
||||
|
||||
// Lite builds with a linked SDXL backend own a lite wallet controller that drives
|
||||
// real create/open/restore through the bridge. Full-node and unlinked-lite builds
|
||||
// leave lite_wallet_ null (the UI falls back to validation-only).
|
||||
if (supportsLiteBackend()) {
|
||||
lite_wallet_ = wallet::LiteWalletController::createLinked(
|
||||
walletCapabilities(),
|
||||
wallet::liteConnectionSettingsFromAppSettings(*settings_));
|
||||
lite_wallet_->setPersistCallback([this]() { settings_->save(); });
|
||||
}
|
||||
|
||||
// Apply verbose logging preference from saved settings
|
||||
util::Logger::instance().setVerbose(settings_->getVerboseLogging());
|
||||
|
||||
@@ -1126,10 +1138,7 @@ void App::render()
|
||||
// Tabs that need balance/transaction data (show overlay):
|
||||
// Overview, Send, Receive, History
|
||||
// ---------------------------------------------------------------
|
||||
bool pageNeedsWalletData = (current_page_ == ui::NavPage::Overview ||
|
||||
current_page_ == ui::NavPage::Send ||
|
||||
current_page_ == ui::NavPage::Receive ||
|
||||
current_page_ == ui::NavPage::History);
|
||||
bool pageNeedsWalletData = wallet::uiSurfaceNeedsWalletData(ui::NavPageSurface(current_page_));
|
||||
bool daemonReady = state_.connected && !state_.warming_up;
|
||||
|
||||
// Don't show lock screen while pool mining — xmrig runs independently
|
||||
@@ -1282,11 +1291,7 @@ void App::render()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Fade content area window draw list (fills, text, borders)
|
||||
fadeVerts(caDL, caVtxStart, caDL->VtxBuffer.Size);
|
||||
|
||||
// Fade ForegroundDrawList panel effects (rainbow border, edge trace,
|
||||
// Apply to foreground panel effects (rainbow borders, edge trace,
|
||||
// shimmer, specular glare) — but NOT viewport-wide effects (embers,
|
||||
// overlay) which were added after fgVtxEnd.
|
||||
fadeVerts(fgDL, fgVtxStart, fgVtxEnd);
|
||||
@@ -2099,7 +2104,12 @@ void App::setCurrentTab(int tab) {
|
||||
|
||||
bool App::startEmbeddedDaemon()
|
||||
{
|
||||
if (!use_embedded_daemon_) {
|
||||
if (!supportsEmbeddedDaemon()) {
|
||||
DEBUG_LOGF("Embedded daemon support unavailable in this build, not starting\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isUsingEmbeddedDaemon()) {
|
||||
DEBUG_LOGF("Embedded daemon disabled, not starting\n");
|
||||
return false;
|
||||
}
|
||||
@@ -2309,6 +2319,11 @@ bool App::isEmbeddedDaemonRunning() const
|
||||
|
||||
void App::rescanBlockchain()
|
||||
{
|
||||
if (!supportsFullNodeLifecycleActions()) {
|
||||
ui::Notifications::instance().warning("Full-node lifecycle actions are unavailable in lite build");
|
||||
return;
|
||||
}
|
||||
|
||||
auto decision = daemon::DaemonController::evaluateLifecycleOperation(
|
||||
daemon::DaemonController::LifecycleOperation::Rescan,
|
||||
isUsingEmbeddedDaemon(), daemon_controller_ != nullptr, isEmbeddedDaemonRunning());
|
||||
@@ -2343,6 +2358,11 @@ void App::rescanBlockchain()
|
||||
|
||||
void App::deleteBlockchainData()
|
||||
{
|
||||
if (!supportsFullNodeLifecycleActions()) {
|
||||
ui::Notifications::instance().warning("Full-node lifecycle actions are unavailable in lite build");
|
||||
return;
|
||||
}
|
||||
|
||||
auto decision = daemon::DaemonController::evaluateLifecycleOperation(
|
||||
daemon::DaemonController::LifecycleOperation::DeleteBlockchainData,
|
||||
isUsingEmbeddedDaemon(), daemon_controller_ != nullptr, isEmbeddedDaemonRunning());
|
||||
@@ -2366,6 +2386,10 @@ void App::deleteBlockchainData()
|
||||
|
||||
bool App::stopDaemonForBootstrap()
|
||||
{
|
||||
if (!supportsFullNodeLifecycleActions()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto decision = daemon::DaemonController::evaluateLifecycleOperation(
|
||||
daemon::DaemonController::LifecycleOperation::BootstrapStop,
|
||||
isUsingEmbeddedDaemon(), daemon_controller_ != nullptr, isEmbeddedDaemonRunning());
|
||||
@@ -3152,9 +3176,14 @@ void App::maybeFinishTransactionSendProgress()
|
||||
}
|
||||
void App::restartDaemon()
|
||||
{
|
||||
if (!supportsFullNodeLifecycleActions()) {
|
||||
ui::Notifications::instance().warning("Full-node lifecycle actions are unavailable in lite build");
|
||||
return;
|
||||
}
|
||||
|
||||
auto decision = daemon::DaemonController::evaluateLifecycleOperation(
|
||||
daemon::DaemonController::LifecycleOperation::ManualRestart,
|
||||
use_embedded_daemon_, daemon_controller_ != nullptr, isEmbeddedDaemonRunning(), daemon_restarting_.load());
|
||||
isUsingEmbeddedDaemon(), daemon_controller_ != nullptr, isEmbeddedDaemonRunning(), daemon_restarting_.load());
|
||||
if (!decision.allowed) return;
|
||||
daemon_restarting_ = true;
|
||||
|
||||
|
||||
20
src/app.h
20
src/app.h
@@ -19,6 +19,7 @@
|
||||
#include "services/wallet_security_controller.h"
|
||||
#include "services/wallet_security_workflow.h"
|
||||
#include "util/async_task_manager.h"
|
||||
#include "wallet/wallet_capabilities.h"
|
||||
#include "ui/sidebar.h"
|
||||
#include "ui/windows/console_tab.h"
|
||||
#include "imgui.h"
|
||||
@@ -32,6 +33,7 @@ namespace dragonx {
|
||||
namespace config { class Settings; }
|
||||
namespace daemon { class DaemonController; class EmbeddedDaemon; class XmrigManager; }
|
||||
namespace util { class Bootstrap; class SecureVault; }
|
||||
namespace wallet { class LiteWalletController; }
|
||||
}
|
||||
|
||||
namespace dragonx {
|
||||
@@ -130,8 +132,13 @@ public:
|
||||
* @brief Whether we are in the shutdown phase
|
||||
*/
|
||||
bool isShuttingDown() const { return shutting_down_; }
|
||||
bool isLiteBuild() const { return DRAGONX_LITE_BUILD != 0; }
|
||||
bool supportsEmbeddedDaemon() const { return DRAGONX_ENABLE_EMBEDDED_DAEMON != 0; }
|
||||
wallet::WalletCapabilities walletCapabilities() const { return wallet::currentWalletCapabilities(); }
|
||||
bool isLiteBuild() const { return wallet::isLiteBuild(walletCapabilities()); }
|
||||
bool supportsEmbeddedDaemon() const { return wallet::supportsEmbeddedDaemon(walletCapabilities()); }
|
||||
bool supportsFullNodeLifecycleActions() const { return wallet::supportsFullNodeLifecycleActions(walletCapabilities()); }
|
||||
bool supportsSoloMining() const { return wallet::supportsSoloMining(walletCapabilities()); }
|
||||
bool supportsPoolMining() const { return wallet::supportsPoolMining(walletCapabilities()); }
|
||||
bool supportsLiteBackend() const { return wallet::supportsLiteBackend(walletCapabilities()); }
|
||||
|
||||
/**
|
||||
* @brief Render the shutdown overlay (called instead of normal UI during shutdown)
|
||||
@@ -148,6 +155,8 @@ public:
|
||||
rpc::RPCClient* rpc() { return rpc_.get(); }
|
||||
rpc::RPCWorker* worker() { return worker_.get(); }
|
||||
config::Settings* settings() { return settings_.get(); }
|
||||
// Lite wallet controller (non-null only in lite builds with a linked backend).
|
||||
wallet::LiteWalletController* liteWallet() { return lite_wallet_.get(); }
|
||||
WalletState& state() { return state_; }
|
||||
const WalletState& state() const { return state_; }
|
||||
const WalletState& getWalletState() const { return state_; }
|
||||
@@ -276,8 +285,8 @@ public:
|
||||
bool startEmbeddedDaemon();
|
||||
void stopEmbeddedDaemon();
|
||||
bool isEmbeddedDaemonRunning() const;
|
||||
bool isUsingEmbeddedDaemon() const { return use_embedded_daemon_; }
|
||||
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use; }
|
||||
bool isUsingEmbeddedDaemon() const { return supportsEmbeddedDaemon() && use_embedded_daemon_; }
|
||||
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use && supportsEmbeddedDaemon(); }
|
||||
void rescanBlockchain(); // restart daemon with -rescan flag
|
||||
void deleteBlockchainData(); // stop daemon, delete chain data, restart fresh
|
||||
bool stopDaemonForBootstrap(); // stop daemon + disconnect for bootstrap, returns true if was running
|
||||
@@ -406,6 +415,7 @@ private:
|
||||
rpc::ConnectionConfig saved_config_;
|
||||
|
||||
std::unique_ptr<config::Settings> settings_;
|
||||
std::unique_ptr<wallet::LiteWalletController> lite_wallet_; // lite builds w/ linked backend
|
||||
std::unique_ptr<daemon::DaemonController> daemon_controller_;
|
||||
std::unique_ptr<daemon::XmrigManager> xmrig_manager_;
|
||||
util::AsyncTaskManager async_tasks_;
|
||||
@@ -441,7 +451,7 @@ private:
|
||||
bool show_address_book_ = false;
|
||||
|
||||
// Embedded daemon state
|
||||
bool use_embedded_daemon_ = (DRAGONX_ENABLE_EMBEDDED_DAEMON != 0);
|
||||
bool use_embedded_daemon_ = wallet::supportsEmbeddedDaemon(wallet::currentWalletCapabilities());
|
||||
std::string daemon_status_;
|
||||
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
|
||||
size_t daemon_output_offset_ = 0; // for incremental output parsing (rescan detection)
|
||||
|
||||
@@ -30,6 +30,33 @@ namespace config {
|
||||
Settings::Settings() = default;
|
||||
Settings::~Settings() = default;
|
||||
|
||||
namespace {
|
||||
|
||||
Settings::LiteServerSelectionPreferenceMode parseLiteServerSelectionPreferenceMode(
|
||||
const json& value)
|
||||
{
|
||||
if (!value.is_string()) return Settings::LiteServerSelectionPreferenceMode::Sticky;
|
||||
const std::string mode = value.get<std::string>();
|
||||
if (mode == "random" || mode == "Random") {
|
||||
return Settings::LiteServerSelectionPreferenceMode::Random;
|
||||
}
|
||||
return Settings::LiteServerSelectionPreferenceMode::Sticky;
|
||||
}
|
||||
|
||||
const char* liteServerSelectionPreferenceModeName(
|
||||
Settings::LiteServerSelectionPreferenceMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case Settings::LiteServerSelectionPreferenceMode::Sticky:
|
||||
return "sticky";
|
||||
case Settings::LiteServerSelectionPreferenceMode::Random:
|
||||
return "random";
|
||||
}
|
||||
return "sticky";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string Settings::getDefaultPath()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@@ -148,6 +175,54 @@ bool Settings::load(const std::string& path)
|
||||
if (j.contains("keep_daemon_running")) keep_daemon_running_ = j["keep_daemon_running"].get<bool>();
|
||||
if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get<bool>();
|
||||
if (j.contains("max_connections")) max_connections_ = j["max_connections"].get<int>();
|
||||
if (j.contains("lite_wallet") && j["lite_wallet"].is_object()) {
|
||||
const auto& lite = j["lite_wallet"];
|
||||
if (lite.contains("server_selection_mode")) {
|
||||
lite_server_selection_mode_ = parseLiteServerSelectionPreferenceMode(
|
||||
lite["server_selection_mode"]);
|
||||
}
|
||||
if (lite.contains("sticky_server_url") && lite["sticky_server_url"].is_string()) {
|
||||
lite_sticky_server_url_ = lite["sticky_server_url"].get<std::string>();
|
||||
}
|
||||
if (lite.contains("chain_name") && lite["chain_name"].is_string()) {
|
||||
lite_chain_name_ = lite["chain_name"].get<std::string>();
|
||||
}
|
||||
// Migration: the SDXL backend only accepts main/test/regtest and hard-panics
|
||||
// (process abort) on any other chain name. Older builds persisted the "DRAGONX"
|
||||
// ticker here, which crashed the lite backend on launch. Rewrite any invalid
|
||||
// value to "main" and flag a re-save so the corrected setting persists.
|
||||
if (lite_chain_name_ != "main" && lite_chain_name_ != "test" &&
|
||||
lite_chain_name_ != "regtest") {
|
||||
lite_chain_name_ = "main";
|
||||
needs_upgrade_save_ = true;
|
||||
}
|
||||
if (lite.contains("random_selection_seed") && lite["random_selection_seed"].is_number_unsigned()) {
|
||||
lite_random_selection_seed_ = lite["random_selection_seed"].get<std::size_t>();
|
||||
} else if (lite.contains("random_selection_seed") && lite["random_selection_seed"].is_number_integer()) {
|
||||
const auto seed = lite["random_selection_seed"].get<long long>();
|
||||
lite_random_selection_seed_ = seed > 0 ? static_cast<std::size_t>(seed) : 0;
|
||||
}
|
||||
if (lite.contains("persist_selected_server") && lite["persist_selected_server"].is_boolean()) {
|
||||
lite_persist_selected_server_ = lite["persist_selected_server"].get<bool>();
|
||||
}
|
||||
if (lite.contains("servers") && lite["servers"].is_array()) {
|
||||
lite_servers_.clear();
|
||||
for (const auto& server : lite["servers"]) {
|
||||
if (!server.is_object()) continue;
|
||||
LiteServerPreference preference;
|
||||
if (server.contains("url") && server["url"].is_string()) {
|
||||
preference.url = server["url"].get<std::string>();
|
||||
}
|
||||
if (server.contains("label") && server["label"].is_string()) {
|
||||
preference.label = server["label"].get<std::string>();
|
||||
}
|
||||
if (server.contains("enabled") && server["enabled"].is_boolean()) {
|
||||
preference.enabled = server["enabled"].get<bool>();
|
||||
}
|
||||
lite_servers_.push_back(preference);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (j.contains("verbose_logging")) verbose_logging_ = j["verbose_logging"].get<bool>();
|
||||
if (j.contains("debug_categories") && j["debug_categories"].is_array()) {
|
||||
debug_categories_.clear();
|
||||
@@ -265,6 +340,23 @@ bool Settings::save(const std::string& path)
|
||||
j["keep_daemon_running"] = keep_daemon_running_;
|
||||
j["stop_external_daemon"] = stop_external_daemon_;
|
||||
j["max_connections"] = max_connections_;
|
||||
{
|
||||
json lite = json::object();
|
||||
lite["server_selection_mode"] = liteServerSelectionPreferenceModeName(lite_server_selection_mode_);
|
||||
lite["sticky_server_url"] = lite_sticky_server_url_;
|
||||
lite["chain_name"] = lite_chain_name_;
|
||||
lite["random_selection_seed"] = lite_random_selection_seed_;
|
||||
lite["persist_selected_server"] = lite_persist_selected_server_;
|
||||
lite["servers"] = json::array();
|
||||
for (const auto& server : lite_servers_) {
|
||||
json entry = json::object();
|
||||
entry["url"] = server.url;
|
||||
entry["label"] = server.label;
|
||||
entry["enabled"] = server.enabled;
|
||||
lite["servers"].push_back(entry);
|
||||
}
|
||||
j["lite_wallet"] = lite;
|
||||
}
|
||||
j["verbose_logging"] = verbose_logging_;
|
||||
j["debug_categories"] = json::array();
|
||||
for (const auto& cat : debug_categories_)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <set>
|
||||
@@ -54,6 +55,17 @@ public:
|
||||
*/
|
||||
static std::string getDefaultPath();
|
||||
|
||||
enum class LiteServerSelectionPreferenceMode {
|
||||
Sticky,
|
||||
Random
|
||||
};
|
||||
|
||||
struct LiteServerPreference {
|
||||
std::string url;
|
||||
std::string label;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
// Theme
|
||||
std::string getTheme() const { return theme_; }
|
||||
void setTheme(const std::string& theme) { theme_ = theme; }
|
||||
@@ -218,6 +230,20 @@ public:
|
||||
int getMaxConnections() const { return max_connections_; }
|
||||
void setMaxConnections(int v) { max_connections_ = std::max(0, v); }
|
||||
|
||||
// Lite wallet server selection
|
||||
LiteServerSelectionPreferenceMode getLiteServerSelectionMode() const { return lite_server_selection_mode_; }
|
||||
void setLiteServerSelectionMode(LiteServerSelectionPreferenceMode mode) { lite_server_selection_mode_ = mode; }
|
||||
std::string getLiteStickyServerUrl() const { return lite_sticky_server_url_; }
|
||||
void setLiteStickyServerUrl(const std::string& url) { lite_sticky_server_url_ = url; }
|
||||
std::string getLiteChainName() const { return lite_chain_name_; }
|
||||
void setLiteChainName(const std::string& chainName) { lite_chain_name_ = chainName; }
|
||||
std::size_t getLiteRandomSelectionSeed() const { return lite_random_selection_seed_; }
|
||||
void setLiteRandomSelectionSeed(std::size_t seed) { lite_random_selection_seed_ = seed; }
|
||||
bool getLitePersistSelectedServer() const { return lite_persist_selected_server_; }
|
||||
void setLitePersistSelectedServer(bool persist) { lite_persist_selected_server_ = persist; }
|
||||
const std::vector<LiteServerPreference>& getLiteServers() const { return lite_servers_; }
|
||||
void setLiteServers(const std::vector<LiteServerPreference>& servers) { lite_servers_ = servers; }
|
||||
|
||||
// Verbose diagnostic logging (connection attempts, daemon state, port owner, etc.)
|
||||
bool getVerboseLogging() const { return verbose_logging_; }
|
||||
void setVerboseLogging(bool v) { verbose_logging_ = v; }
|
||||
@@ -358,6 +384,23 @@ private:
|
||||
bool keep_daemon_running_ = false;
|
||||
bool stop_external_daemon_ = false;
|
||||
int max_connections_ = 0; // 0 = daemon default
|
||||
|
||||
// Lite wallet server preferences. These are user/server settings only;
|
||||
// wallet secrets, wallet files, and lifecycle state are never stored here.
|
||||
LiteServerSelectionPreferenceMode lite_server_selection_mode_ = LiteServerSelectionPreferenceMode::Sticky;
|
||||
std::string lite_sticky_server_url_ = "https://lite.dragonx.is";
|
||||
std::string lite_chain_name_ = "main"; // SDXL backend chain id; must be main/test/regtest
|
||||
std::size_t lite_random_selection_seed_ = 0;
|
||||
bool lite_persist_selected_server_ = true;
|
||||
std::vector<LiteServerPreference> lite_servers_ = {
|
||||
{"https://lite.dragonx.is", "DragonX Lite", true},
|
||||
{"https://lite1.dragonx.is", "DragonX Lite 1", true},
|
||||
{"https://lite2.dragonx.is", "DragonX Lite 2", true},
|
||||
{"https://lite3.dragonx.is", "DragonX Lite 3", true},
|
||||
{"https://lite4.dragonx.is", "DragonX Lite 4", true},
|
||||
{"https://lite5.dragonx.is", "DragonX Lite 5", true}
|
||||
};
|
||||
|
||||
bool verbose_logging_ = false;
|
||||
std::set<std::string> debug_categories_;
|
||||
bool theme_effects_enabled_ = true;
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include "../../app.h"
|
||||
#include "../../config/version.h"
|
||||
#include "../../config/settings.h"
|
||||
#include "../../wallet/lite_wallet_lifecycle_ui_adapter.h"
|
||||
#include "../../wallet/lite_wallet_server_selection_adapter.h"
|
||||
#include "../../wallet/lite_wallet_controller.h"
|
||||
#include <sodium.h>
|
||||
#include "../../util/logger.h"
|
||||
#include "../windows/balance_tab.h"
|
||||
#include "../windows/console_tab.h"
|
||||
@@ -42,6 +46,8 @@
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
@@ -99,6 +105,22 @@ struct SettingsPageState {
|
||||
LowSpecSnapshot low_spec_snapshot;
|
||||
bool keep_daemon_running = false;
|
||||
bool stop_external_daemon = false;
|
||||
int lite_server_mode = 0;
|
||||
char lite_server_url[256] = "https://lite.dragonx.is";
|
||||
int lite_random_seed = 0;
|
||||
bool lite_persist_selected_server = true;
|
||||
std::vector<wallet::LiteServerEndpoint> lite_servers;
|
||||
std::string lite_server_status;
|
||||
bool lite_lifecycle_expanded = false;
|
||||
int lite_lifecycle_operation = 0;
|
||||
char lite_wallet_path[256] = "";
|
||||
char lite_lifecycle_passphrase[128] = "";
|
||||
char lite_restore_seed[512] = "";
|
||||
int lite_restore_birthday = 0;
|
||||
int lite_restore_account = 0;
|
||||
bool lite_restore_overwrite = false;
|
||||
std::string lite_lifecycle_status;
|
||||
std::string lite_lifecycle_summary;
|
||||
bool mine_when_idle = false;
|
||||
int mine_idle_delay = 120;
|
||||
bool idle_thread_scaling = false;
|
||||
@@ -117,6 +139,148 @@ struct SettingsPageState {
|
||||
|
||||
static SettingsPageState s_settingsState;
|
||||
|
||||
static void copyToSettingsBuffer(char* dest, std::size_t destSize, const std::string& value) {
|
||||
if (!dest || destSize == 0) return;
|
||||
std::strncpy(dest, value.c_str(), destSize - 1);
|
||||
dest[destSize - 1] = '\0';
|
||||
}
|
||||
|
||||
static wallet::LiteConnectionSettings liteConnectionSettingsFromPageState(config::Settings* settings) {
|
||||
wallet::LiteConnectionSettings connectionSettings = settings
|
||||
? wallet::liteConnectionSettingsFromAppSettings(*settings)
|
||||
: wallet::defaultLiteConnectionSettings();
|
||||
if (!s_settingsState.lite_servers.empty()) {
|
||||
connectionSettings.servers = s_settingsState.lite_servers;
|
||||
}
|
||||
connectionSettings.selectionMode = s_settingsState.lite_server_mode == 1
|
||||
? wallet::LiteServerSelectionMode::Random
|
||||
: wallet::LiteServerSelectionMode::Sticky;
|
||||
connectionSettings.stickyServerUrl = s_settingsState.lite_server_url;
|
||||
connectionSettings.chainName = wallet::kDragonXLiteChainName;
|
||||
connectionSettings.randomSelectionSeed = static_cast<std::size_t>(std::max(0, s_settingsState.lite_random_seed));
|
||||
return connectionSettings;
|
||||
}
|
||||
|
||||
static wallet::LiteWalletLifecycleOperation liteLifecycleOperationFromPageState() {
|
||||
switch (s_settingsState.lite_lifecycle_operation) {
|
||||
case 1: return wallet::LiteWalletLifecycleOperation::OpenExisting;
|
||||
case 2: return wallet::LiteWalletLifecycleOperation::RestoreFromSeed;
|
||||
default: return wallet::LiteWalletLifecycleOperation::CreateNew;
|
||||
}
|
||||
}
|
||||
|
||||
static void saveLiteServerSelectionFromPageState(App* app) {
|
||||
if (!app || !app->settings()) return;
|
||||
|
||||
const auto connectionSettings = liteConnectionSettingsFromPageState(app->settings());
|
||||
wallet::LiteWalletServerSelectionUiExecutionInput input;
|
||||
input.capabilities = app->walletCapabilities();
|
||||
input.intent.selectedServerIntentProvided = true;
|
||||
input.intent.selectionMode = connectionSettings.selectionMode;
|
||||
input.intent.selectedServerUrl = connectionSettings.stickyServerUrl;
|
||||
input.intent.randomSelectionSeed = connectionSettings.randomSelectionSeed;
|
||||
input.intent.chainName = connectionSettings.chainName;
|
||||
input.intent.replaceServers = true;
|
||||
input.intent.servers = connectionSettings.servers;
|
||||
input.persistence.settingsLoaded = true;
|
||||
input.persistence.havePersistedSelectionIntent = true;
|
||||
input.persistence.persistSelectedServer = s_settingsState.lite_persist_selected_server;
|
||||
input.persistence.persistenceOwnerReady = true;
|
||||
input.persistence.writeSettings = true;
|
||||
input.ui.selectedServerDisplayReady = true;
|
||||
input.ui.lifecycleUiOwnerReady = true;
|
||||
input.ui.operationConfirmed = true;
|
||||
input.ui.privateDataRedactionReady = true;
|
||||
input.ui.syncPlannerFeedReady = true;
|
||||
input.requireLifecycleReadiness = false;
|
||||
|
||||
const auto result = wallet::executeLiteWalletServerSelectionUi(*app->settings(), input);
|
||||
if (result.settingsWritten) {
|
||||
s_settingsState.lite_server_status = "Saved";
|
||||
} else if (!result.error.empty()) {
|
||||
s_settingsState.lite_server_status = result.error;
|
||||
Notifications::instance().warning(result.error);
|
||||
}
|
||||
}
|
||||
|
||||
static void evaluateLiteLifecycleRequestFromPageState(App* app) {
|
||||
if (!app || !app->settings()) return;
|
||||
|
||||
wallet::LiteWalletLifecycleUiExecutionInput input;
|
||||
input.capabilities = app->walletCapabilities();
|
||||
input.settingsLoaded = true;
|
||||
input.requirePersistedServerSelectionIntent = true;
|
||||
input.ui.selectedServerDisplayReady = true;
|
||||
input.ui.lifecycleUiOwnerReady = true;
|
||||
input.ui.operationConfirmed = true;
|
||||
input.ui.privateDataRedactionReady = true;
|
||||
input.ui.syncPlannerFeedReady = true;
|
||||
input.request.requestProvided = true;
|
||||
input.request.operation = liteLifecycleOperationFromPageState();
|
||||
|
||||
switch (input.request.operation) {
|
||||
case wallet::LiteWalletLifecycleOperation::CreateNew:
|
||||
input.request.createRequest.passphrase = s_settingsState.lite_lifecycle_passphrase;
|
||||
break;
|
||||
case wallet::LiteWalletLifecycleOperation::OpenExisting:
|
||||
input.request.openRequest.walletPath = s_settingsState.lite_wallet_path;
|
||||
input.request.openRequest.passphrase = s_settingsState.lite_lifecycle_passphrase;
|
||||
break;
|
||||
case wallet::LiteWalletLifecycleOperation::RestoreFromSeed:
|
||||
input.request.restoreRequest.walletPath = s_settingsState.lite_wallet_path;
|
||||
input.request.restoreRequest.seedPhrase = s_settingsState.lite_restore_seed;
|
||||
input.request.restoreRequest.passphrase = s_settingsState.lite_lifecycle_passphrase;
|
||||
input.request.restoreRequest.birthday = static_cast<unsigned long long>(std::max(0, s_settingsState.lite_restore_birthday));
|
||||
input.request.restoreRequest.account = static_cast<unsigned long long>(std::max(0, s_settingsState.lite_restore_account));
|
||||
input.request.restoreRequest.overwrite = s_settingsState.lite_restore_overwrite;
|
||||
break;
|
||||
}
|
||||
|
||||
// When a linked lite backend is present, execute the operation for real through the
|
||||
// App-owned controller. Otherwise fall back to the validation-only adapter.
|
||||
if (auto* lite = app->liteWallet()) {
|
||||
wallet::LiteWalletLifecycleResult result;
|
||||
switch (input.request.operation) {
|
||||
case wallet::LiteWalletLifecycleOperation::CreateNew:
|
||||
result = lite->createWallet(input.request.createRequest);
|
||||
break;
|
||||
case wallet::LiteWalletLifecycleOperation::OpenExisting:
|
||||
result = lite->openWallet(input.request.openRequest);
|
||||
break;
|
||||
case wallet::LiteWalletLifecycleOperation::RestoreFromSeed:
|
||||
result = lite->restoreWallet(input.request.restoreRequest);
|
||||
break;
|
||||
}
|
||||
|
||||
// Secrets have been consumed; wipe the UI buffers (and the request copies) so they
|
||||
// do not linger in memory after the attempt.
|
||||
sodium_memzero(s_settingsState.lite_lifecycle_passphrase, sizeof(s_settingsState.lite_lifecycle_passphrase));
|
||||
sodium_memzero(s_settingsState.lite_restore_seed, sizeof(s_settingsState.lite_restore_seed));
|
||||
|
||||
s_settingsState.lite_lifecycle_summary = result.bridgeResponseRedacted;
|
||||
if (result.walletReady) {
|
||||
s_settingsState.lite_lifecycle_status = "Wallet ready";
|
||||
} else {
|
||||
s_settingsState.lite_lifecycle_status = result.error.empty()
|
||||
? result.status.message
|
||||
: result.error;
|
||||
Notifications::instance().warning(s_settingsState.lite_lifecycle_status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = wallet::executeLiteWalletLifecycleUiRequest(*app->settings(), input);
|
||||
s_settingsState.lite_lifecycle_summary = result.requestSummaryRedacted;
|
||||
if (result.ok) {
|
||||
s_settingsState.lite_lifecycle_status = "Ready";
|
||||
} else {
|
||||
s_settingsState.lite_lifecycle_status = result.error.empty()
|
||||
? wallet::liteWalletLifecycleUiExecutionStatusName(result.status)
|
||||
: result.error;
|
||||
Notifications::instance().warning(s_settingsState.lite_lifecycle_status);
|
||||
}
|
||||
}
|
||||
|
||||
// (APPEARANCE card now uses ChannelsSplit like all other cards)
|
||||
|
||||
static void loadSettingsPageState(config::Settings* settings) {
|
||||
@@ -166,6 +330,17 @@ static void loadSettingsPageState(config::Settings* settings) {
|
||||
Layout::setUserFontScale(s_settingsState.font_scale); // sync with Layout on load
|
||||
s_settingsState.keep_daemon_running = settings->getKeepDaemonRunning();
|
||||
s_settingsState.stop_external_daemon = settings->getStopExternalDaemon();
|
||||
{
|
||||
const auto liteSettings = wallet::liteConnectionSettingsFromAppSettings(*settings);
|
||||
s_settingsState.lite_server_mode = liteSettings.selectionMode == wallet::LiteServerSelectionMode::Random ? 1 : 0;
|
||||
copyToSettingsBuffer(s_settingsState.lite_server_url,
|
||||
sizeof(s_settingsState.lite_server_url),
|
||||
liteSettings.stickyServerUrl);
|
||||
s_settingsState.lite_random_seed = static_cast<int>(liteSettings.randomSelectionSeed);
|
||||
s_settingsState.lite_persist_selected_server = settings->getLitePersistSelectedServer();
|
||||
s_settingsState.lite_servers = liteSettings.servers;
|
||||
s_settingsState.lite_server_status.clear();
|
||||
}
|
||||
s_settingsState.mine_when_idle = settings->getMineWhenIdle();
|
||||
s_settingsState.mine_idle_delay = settings->getMineIdleDelay();
|
||||
s_settingsState.idle_thread_scaling = settings->getIdleThreadScaling();
|
||||
@@ -220,6 +395,11 @@ static void saveSettingsPageState(config::Settings* settings) {
|
||||
settings->setFontScale(s_settingsState.font_scale);
|
||||
settings->setKeepDaemonRunning(s_settingsState.keep_daemon_running);
|
||||
settings->setStopExternalDaemon(s_settingsState.stop_external_daemon);
|
||||
{
|
||||
auto liteSettings = liteConnectionSettingsFromPageState(settings);
|
||||
wallet::applyLiteConnectionSettingsToAppSettings(*settings, liteSettings);
|
||||
settings->setLitePersistSelectedServer(s_settingsState.lite_persist_selected_server);
|
||||
}
|
||||
settings->setMineWhenIdle(s_settingsState.mine_when_idle);
|
||||
settings->setMineIdleDelay(s_settingsState.mine_idle_delay);
|
||||
settings->setIdleThreadScaling(s_settingsState.idle_thread_scaling);
|
||||
@@ -1073,7 +1253,7 @@ void RenderSettingsPage(App* app) {
|
||||
|
||||
// Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit
|
||||
{
|
||||
const bool showDaemonOptions = !app->isLiteBuild();
|
||||
const bool showDaemonOptions = app->supportsFullNodeLifecycleActions();
|
||||
float cbSpacing = Layout::spacingMd();
|
||||
float fh = ImGui::GetFrameHeight();
|
||||
float inner = ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
@@ -1221,6 +1401,7 @@ void RenderSettingsPage(App* app) {
|
||||
TR("tt_backup"),
|
||||
TR("tt_export_csv")
|
||||
};
|
||||
const bool showFullNodeLifecycleActions = app->supportsFullNodeLifecycleActions();
|
||||
const char* wizLabel = TR("setup_wizard");
|
||||
const char* bsLabel = TR("download_bootstrap");
|
||||
float sp = Layout::spacingSm();
|
||||
@@ -1230,9 +1411,10 @@ void RenderSettingsPage(App* app) {
|
||||
float naturalW = 0;
|
||||
for (int i = 0; i < 5; i++)
|
||||
naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
|
||||
float wizW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
|
||||
float bsW = ImGui::CalcTextSize(bsLabel).x + btnPadX;
|
||||
float totalW = naturalW + wizW + bsW + sp * 7;
|
||||
float wizW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(wizLabel).x + btnPadX : 0.0f;
|
||||
float bsW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(bsLabel).x + btnPadX : 0.0f;
|
||||
float totalW = naturalW + sp * 5;
|
||||
if (showFullNodeLifecycleActions) totalW += wizW + bsW + sp * 2;
|
||||
|
||||
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
|
||||
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
|
||||
@@ -1259,27 +1441,29 @@ void RenderSettingsPage(App* app) {
|
||||
ExportTransactionsDialog::show();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", t1[4]);
|
||||
|
||||
// Right-align Setup Wizard + Download Bootstrap
|
||||
float framePadX2 = ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
float curX = ImGui::GetCursorScreenPos().x;
|
||||
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + framePadX2;
|
||||
float bsBtnW = ImGui::CalcTextSize(bsLabel).x + framePadX2;
|
||||
float rightEdge = cardMin.x + availWidth - pad;
|
||||
float rightGroupW = bsBtnW + scaledSp + wizBtnW;
|
||||
float groupX = rightEdge - rightGroupW;
|
||||
if (groupX > curX) {
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorScreenPos(ImVec2(groupX, ImGui::GetCursorScreenPos().y));
|
||||
} else {
|
||||
if (showFullNodeLifecycleActions) {
|
||||
// Right-align Setup Wizard + Download Bootstrap
|
||||
float framePadX2 = ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
float curX = ImGui::GetCursorScreenPos().x;
|
||||
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + framePadX2;
|
||||
float bsBtnW = ImGui::CalcTextSize(bsLabel).x + framePadX2;
|
||||
float rightEdge = cardMin.x + availWidth - pad;
|
||||
float rightGroupW = bsBtnW + scaledSp + wizBtnW;
|
||||
float groupX = rightEdge - rightGroupW;
|
||||
if (groupX > curX) {
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorScreenPos(ImVec2(groupX, ImGui::GetCursorScreenPos().y));
|
||||
} else {
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
}
|
||||
if (TactileButton(bsLabel, ImVec2(0, 0), btnFont))
|
||||
BootstrapDownloadDialog::show(app);
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_download_bootstrap"));
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(wizLabel, ImVec2(0, 0), btnFont))
|
||||
app->restartWizard();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_wizard"));
|
||||
}
|
||||
if (TactileButton(bsLabel, ImVec2(0, 0), btnFont))
|
||||
BootstrapDownloadDialog::show(app);
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_download_bootstrap"));
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(wizLabel, ImVec2(0, 0), btnFont))
|
||||
app->restartWizard();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_wizard"));
|
||||
|
||||
if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
|
||||
}
|
||||
@@ -1342,6 +1526,189 @@ void RenderSettingsPage(App* app) {
|
||||
if (!body2Info) body2Info = Type().body2();
|
||||
ImGui::PushFont(body2Info);
|
||||
|
||||
if (app->isLiteBuild()) {
|
||||
float liteLabelW = std::min(leftColW * 0.35f, 132.0f);
|
||||
float liteInputW = std::max(80.0f, leftColW - liteLabelW - Layout::spacingSm());
|
||||
const char* modeLabels[] = {"Sticky", "Random"};
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Mode");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
if (ImGui::BeginCombo("##LiteServerMode", modeLabels[s_settingsState.lite_server_mode == 1 ? 1 : 0])) {
|
||||
for (int modeIndex = 0; modeIndex < 2; ++modeIndex) {
|
||||
const bool selected = s_settingsState.lite_server_mode == modeIndex;
|
||||
if (ImGui::Selectable(modeLabels[modeIndex], selected)) {
|
||||
s_settingsState.lite_server_mode = modeIndex;
|
||||
saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Preset");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
std::string presetPreview = s_settingsState.lite_server_url[0] != '\0'
|
||||
? std::string(s_settingsState.lite_server_url)
|
||||
: std::string("Select");
|
||||
for (const auto& server : s_settingsState.lite_servers) {
|
||||
if (server.url == s_settingsState.lite_server_url && !server.label.empty()) {
|
||||
presetPreview = server.label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
if (ImGui::BeginCombo("##LiteServerPreset", presetPreview.c_str())) {
|
||||
for (const auto& server : s_settingsState.lite_servers) {
|
||||
if (!server.enabled) continue;
|
||||
const std::string label = server.label.empty() ? server.url : server.label;
|
||||
const bool selected = server.url == s_settingsState.lite_server_url;
|
||||
if (ImGui::Selectable(label.c_str(), selected)) {
|
||||
copyToSettingsBuffer(s_settingsState.lite_server_url,
|
||||
sizeof(s_settingsState.lite_server_url),
|
||||
server.url);
|
||||
s_settingsState.lite_server_mode = 0;
|
||||
saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Server");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteServerUrl", s_settingsState.lite_server_url,
|
||||
sizeof(s_settingsState.lite_server_url));
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
s_settingsState.lite_server_mode = 0;
|
||||
saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
|
||||
if (s_settingsState.lite_server_mode == 1) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Seed");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
if (ImGui::InputInt("##LiteRandomSeed", &s_settingsState.lite_random_seed)) {
|
||||
if (s_settingsState.lite_random_seed < 0) s_settingsState.lite_random_seed = 0;
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (ImGui::Checkbox("Persist selected server##LitePersistServer",
|
||||
&s_settingsState.lite_persist_selected_server)) {
|
||||
saveLiteServerSelectionFromPageState(app);
|
||||
}
|
||||
|
||||
if (!s_settingsState.lite_server_status.empty()) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
s_settingsState.lite_server_status.c_str());
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
if (ImGui::Button("Lite wallet request##LiteLifecycleToggle", ImVec2(liteInputW, 0))) {
|
||||
s_settingsState.lite_lifecycle_expanded = !s_settingsState.lite_lifecycle_expanded;
|
||||
}
|
||||
|
||||
if (s_settingsState.lite_lifecycle_expanded) {
|
||||
const char* lifecycleLabels[] = {"Create", "Open", "Restore"};
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Action");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
if (ImGui::BeginCombo("##LiteLifecycleOperation",
|
||||
lifecycleLabels[std::max(0, std::min(2, s_settingsState.lite_lifecycle_operation))])) {
|
||||
for (int operationIndex = 0; operationIndex < 3; ++operationIndex) {
|
||||
const bool selected = s_settingsState.lite_lifecycle_operation == operationIndex;
|
||||
if (ImGui::Selectable(lifecycleLabels[operationIndex], selected)) {
|
||||
s_settingsState.lite_lifecycle_operation = operationIndex;
|
||||
s_settingsState.lite_lifecycle_status.clear();
|
||||
s_settingsState.lite_lifecycle_summary.clear();
|
||||
}
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (s_settingsState.lite_lifecycle_operation == 1 ||
|
||||
s_settingsState.lite_lifecycle_operation == 2) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Wallet");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteWalletPath", s_settingsState.lite_wallet_path,
|
||||
sizeof(s_settingsState.lite_wallet_path));
|
||||
}
|
||||
|
||||
if (s_settingsState.lite_lifecycle_operation == 2) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Seed");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteRestoreSeed", s_settingsState.lite_restore_seed,
|
||||
sizeof(s_settingsState.lite_restore_seed),
|
||||
ImGuiInputTextFlags_Password);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Birthday");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
ImGui::InputInt("##LiteRestoreBirthday", &s_settingsState.lite_restore_birthday);
|
||||
if (s_settingsState.lite_restore_birthday < 0) s_settingsState.lite_restore_birthday = 0;
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Account");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
ImGui::InputInt("##LiteRestoreAccount", &s_settingsState.lite_restore_account);
|
||||
if (s_settingsState.lite_restore_account < 0) s_settingsState.lite_restore_account = 0;
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::Checkbox("Overwrite##LiteRestoreOverwrite",
|
||||
&s_settingsState.lite_restore_overwrite);
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Passphrase");
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(liteInputW);
|
||||
ImGui::InputText("##LiteLifecyclePassphrase",
|
||||
s_settingsState.lite_lifecycle_passphrase,
|
||||
sizeof(s_settingsState.lite_lifecycle_passphrase),
|
||||
ImGuiInputTextFlags_Password);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton("Validate##LiteLifecycleValidate", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
evaluateLiteLifecycleRequestFromPageState(app);
|
||||
}
|
||||
|
||||
if (!s_settingsState.lite_lifecycle_status.empty()) {
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
s_settingsState.lite_lifecycle_status.c_str());
|
||||
}
|
||||
if (!s_settingsState.lite_lifecycle_summary.empty()) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
s_settingsState.lite_lifecycle_summary.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
float rpcLblW = std::max(
|
||||
S.drawElement("components.settings-page", "rpc-label-min-width").size,
|
||||
std::min(leftColW * 0.35f, S.drawElement("components.settings-page", "rpc-label-width").size * hs));
|
||||
@@ -1476,7 +1843,7 @@ void RenderSettingsPage(App* app) {
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
// Node maintenance buttons (full-node build only)
|
||||
if (!app->isLiteBuild()) {
|
||||
if (app->supportsFullNodeLifecycleActions()) {
|
||||
ImFont* btnFont = S.resolveFont("button");
|
||||
float nodeBtnW;
|
||||
{
|
||||
@@ -1524,6 +1891,8 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
@@ -1950,7 +2319,7 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// "Restart daemon" button — only active when categories changed
|
||||
if (s_settingsState.debug_cats_dirty) {
|
||||
if (s_settingsState.debug_cats_dirty && app->supportsFullNodeLifecycleActions()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 218, 0, 255));
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
if (iconFont) {
|
||||
@@ -2085,7 +2454,8 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
|
||||
if (ImGui::Button(TrId("delete_blockchain_confirm", "del_bc_btn").c_str(), ImVec2(btnW, 40))) {
|
||||
app->deleteBlockchainData();
|
||||
if (app->supportsFullNodeLifecycleActions())
|
||||
app->deleteBlockchainData();
|
||||
s_settingsState.confirm_delete_blockchain = false;
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
151
src/wallet/lite_wallet_controller.cpp
Normal file
151
src/wallet/lite_wallet_controller.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "lite_wallet_controller.h"
|
||||
|
||||
#include "../data/wallet_state.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <sodium.h>
|
||||
|
||||
namespace dragonx {
|
||||
namespace wallet {
|
||||
|
||||
namespace {
|
||||
constexpr double kZatoshisPerCoin = 100000000.0; // DRGX has 1e8 zatoshis per coin
|
||||
}
|
||||
|
||||
void secureWipeLiteSecret(std::string& secret)
|
||||
{
|
||||
if (!secret.empty()) {
|
||||
sodium_memzero(&secret[0], secret.size());
|
||||
}
|
||||
secret.clear();
|
||||
}
|
||||
|
||||
void applyLiteRefreshModelToWalletState(const LiteWalletAppRefreshModel& model,
|
||||
dragonx::WalletState& state)
|
||||
{
|
||||
if (model.hasBalance) {
|
||||
state.privateBalance = static_cast<double>(model.balance.shieldedZatoshis) / kZatoshisPerCoin;
|
||||
state.transparentBalance = static_cast<double>(model.balance.transparentZatoshis) / kZatoshisPerCoin;
|
||||
state.totalBalance = static_cast<double>(model.balance.totalZatoshis) / kZatoshisPerCoin;
|
||||
state.unconfirmedBalance = static_cast<double>(model.balance.unconfirmedZatoshis) / kZatoshisPerCoin;
|
||||
}
|
||||
|
||||
if (model.hasAddresses) {
|
||||
state.addresses.clear();
|
||||
state.z_addresses.clear();
|
||||
state.t_addresses.clear();
|
||||
for (const auto& addr : model.addresses) {
|
||||
AddressInfo info;
|
||||
info.address = addr.address;
|
||||
info.balance = 0.0; // lite address listing is aggregate-only; per-address balance is M2b (notes correlation)
|
||||
info.type = (addr.kind == LiteWalletAppAddressKind::Shielded) ? "shielded" : "transparent";
|
||||
info.has_spending_key = addr.spendabilityKnown ? addr.spendable : true;
|
||||
if (addr.kind == LiteWalletAppAddressKind::Shielded) {
|
||||
state.z_addresses.push_back(info);
|
||||
} else {
|
||||
state.t_addresses.push_back(info);
|
||||
}
|
||||
state.addresses.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
|
||||
if (model.hasTransactions) {
|
||||
state.transactions.clear();
|
||||
const int64_t chainHeight =
|
||||
model.hasSyncStatus ? static_cast<int64_t>(model.sync.chainHeight) : 0;
|
||||
for (const auto& record : model.transactions) {
|
||||
TransactionInfo tx;
|
||||
tx.txid = record.txid;
|
||||
if (record.kind == LiteWalletAppTransactionKind::Send) {
|
||||
tx.type = "send";
|
||||
} else if (record.kind == LiteWalletAppTransactionKind::Receive) {
|
||||
tx.type = "receive";
|
||||
} else {
|
||||
tx.type = record.signedAmountZatoshis < 0 ? "send" : "receive";
|
||||
}
|
||||
tx.amount = static_cast<double>(record.amountZatoshis) / kZatoshisPerCoin;
|
||||
tx.timestamp = record.timestamp;
|
||||
tx.address = record.address;
|
||||
tx.memo = record.memo;
|
||||
if (record.unconfirmed || !record.blockHeight.has_value() || chainHeight == 0) {
|
||||
tx.confirmations = record.unconfirmed ? 0 : 1;
|
||||
} else {
|
||||
const int64_t confs = chainHeight - *record.blockHeight + 1;
|
||||
tx.confirmations = confs > 0 ? static_cast<int>(confs) : 0;
|
||||
}
|
||||
state.transactions.push_back(std::move(tx));
|
||||
}
|
||||
}
|
||||
|
||||
if (model.hasSyncStatus) {
|
||||
state.sync.blocks = static_cast<int>(model.sync.walletHeight);
|
||||
state.sync.headers = static_cast<int>(model.sync.chainHeight);
|
||||
state.sync.verification_progress = model.sync.progress;
|
||||
state.sync.syncing = !model.sync.complete;
|
||||
}
|
||||
}
|
||||
|
||||
LiteWalletController::LiteWalletController(WalletCapabilities capabilities,
|
||||
LiteConnectionSettings connectionSettings,
|
||||
LiteClientBridge bridge,
|
||||
LiteWalletControllerOptions options)
|
||||
: lifecycle_(capabilities,
|
||||
std::move(connectionSettings),
|
||||
std::move(bridge),
|
||||
LiteWalletLifecycleOptions{options.allowBridgeCalls})
|
||||
{
|
||||
status_ = lifecycle_.status();
|
||||
}
|
||||
|
||||
std::unique_ptr<LiteWalletController> LiteWalletController::createLinked(
|
||||
WalletCapabilities capabilities,
|
||||
LiteConnectionSettings connectionSettings)
|
||||
{
|
||||
return std::make_unique<LiteWalletController>(
|
||||
capabilities,
|
||||
std::move(connectionSettings),
|
||||
LiteClientBridge::linkedSdxl(),
|
||||
LiteWalletControllerOptions{true});
|
||||
}
|
||||
|
||||
void LiteWalletController::onLifecycleResult(const LiteWalletLifecycleResult& result)
|
||||
{
|
||||
status_ = result.status;
|
||||
if (result.walletReady) {
|
||||
walletOpen_ = true;
|
||||
if (persist_) persist_();
|
||||
}
|
||||
}
|
||||
|
||||
LiteWalletLifecycleResult LiteWalletController::createWallet(LiteWalletCreateRequest request)
|
||||
{
|
||||
auto result = lifecycle_.createWallet(request);
|
||||
secureWipeLiteSecret(request.passphrase);
|
||||
onLifecycleResult(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteWalletLifecycleResult LiteWalletController::openWallet(LiteWalletOpenRequest request)
|
||||
{
|
||||
auto result = lifecycle_.openWallet(request);
|
||||
secureWipeLiteSecret(request.passphrase);
|
||||
onLifecycleResult(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
LiteWalletLifecycleResult LiteWalletController::restoreWallet(LiteWalletRestoreRequest request)
|
||||
{
|
||||
auto result = lifecycle_.restoreWallet(request);
|
||||
secureWipeLiteSecret(request.seedPhrase);
|
||||
secureWipeLiteSecret(request.passphrase);
|
||||
onLifecycleResult(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace wallet
|
||||
} // namespace dragonx
|
||||
85
src/wallet/lite_wallet_controller.h
Normal file
85
src/wallet/lite_wallet_controller.h
Normal file
@@ -0,0 +1,85 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// App-owned controller that drives the lite wallet. It constructs and owns the
|
||||
// lite services with bridge calls ENABLED (the services otherwise default to
|
||||
// allowBridgeCalls=false and are never instantiated), executes real create/open/
|
||||
// restore operations through the linked SDXL backend, securely wipes secrets after
|
||||
// use, tracks wallet-open state, and invokes a persistence callback on success.
|
||||
//
|
||||
// Construction:
|
||||
// - Production: LiteWalletController::createLinked(caps, connectionSettings)
|
||||
// (uses LiteClientBridge::linkedSdxl(); requires DRAGONX_ENABLE_LITE_BACKEND).
|
||||
// - Tests: construct directly with an injected bridge
|
||||
// (e.g. LiteClientBridge::fromApi(makeFakeLiteApi())).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lite_client_bridge.h"
|
||||
#include "lite_connection_service.h"
|
||||
#include "lite_wallet_lifecycle_service.h"
|
||||
#include "lite_wallet_state_mapper.h"
|
||||
#include "wallet_backend.h"
|
||||
#include "wallet_capabilities.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace dragonx {
|
||||
struct WalletState; // data/wallet_state.h
|
||||
|
||||
namespace wallet {
|
||||
|
||||
// Securely zero and clear a string holding secret material (seed/passphrase).
|
||||
void secureWipeLiteSecret(std::string& secret);
|
||||
|
||||
// Apply a normalized lite refresh model onto the app's WalletState (the last hop the
|
||||
// existing Balance/Receive/Transactions tabs read from). Converts zatoshis -> DRGX,
|
||||
// splits addresses into shielded/transparent, and maps sync progress. Mutates in place
|
||||
// (WalletState is non-copyable); only sections present in the model are touched.
|
||||
void applyLiteRefreshModelToWalletState(const LiteWalletAppRefreshModel& model,
|
||||
dragonx::WalletState& state);
|
||||
|
||||
struct LiteWalletControllerOptions {
|
||||
bool allowBridgeCalls = true;
|
||||
};
|
||||
|
||||
class LiteWalletController {
|
||||
public:
|
||||
LiteWalletController(WalletCapabilities capabilities,
|
||||
LiteConnectionSettings connectionSettings,
|
||||
LiteClientBridge bridge,
|
||||
LiteWalletControllerOptions options = LiteWalletControllerOptions{});
|
||||
|
||||
// Production factory: links the SDXL backend compiled into this build.
|
||||
static std::unique_ptr<LiteWalletController> createLinked(
|
||||
WalletCapabilities capabilities,
|
||||
LiteConnectionSettings connectionSettings);
|
||||
|
||||
// Invoked after a wallet becomes ready, so the owner can persist settings.
|
||||
void setPersistCallback(std::function<void()> callback) { persist_ = std::move(callback); }
|
||||
|
||||
bool walletOpen() const { return walletOpen_; }
|
||||
const WalletBackendStatus& status() const { return status_; }
|
||||
LiteWalletLifecycleAvailability availability() const { return lifecycle_.availability(); }
|
||||
|
||||
// Execute a real lifecycle operation. The request is taken by value; its secret
|
||||
// fields are securely wiped before returning. On a ready wallet, walletOpen()
|
||||
// becomes true and the persist callback (if any) fires.
|
||||
LiteWalletLifecycleResult createWallet(LiteWalletCreateRequest request);
|
||||
LiteWalletLifecycleResult openWallet(LiteWalletOpenRequest request);
|
||||
LiteWalletLifecycleResult restoreWallet(LiteWalletRestoreRequest request);
|
||||
|
||||
private:
|
||||
void onLifecycleResult(const LiteWalletLifecycleResult& result);
|
||||
|
||||
LiteWalletLifecycleService lifecycle_;
|
||||
std::function<void()> persist_;
|
||||
bool walletOpen_ = false;
|
||||
WalletBackendStatus status_;
|
||||
};
|
||||
|
||||
} // namespace wallet
|
||||
} // namespace dragonx
|
||||
90
tests/fake_lite_backend.h
Normal file
90
tests/fake_lite_backend.h
Normal file
@@ -0,0 +1,90 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// Deterministic in-process fake SDXL backend for lite-wallet tests.
|
||||
//
|
||||
// Provides a LiteClientBridgeApi whose function pointers return canned JSON and
|
||||
// track owned-string allocation/free counts, so tests can drive the lite bridge
|
||||
// (and, from M1 on, the lite services) without a real litelib_* library, network,
|
||||
// or the DRAGONX_ENABLE_LITE_BACKEND build flag. Build a bridge with:
|
||||
//
|
||||
// auto bridge = dragonx::wallet::LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi());
|
||||
//
|
||||
// Invariant for leak/double-free checks: after a test, g_liteFakeAlloc == g_liteFakeFreed.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wallet/lite_client_bridge.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace dragonx {
|
||||
namespace test {
|
||||
|
||||
// Owned-string accounting (C++17 inline vars: single definition across TUs).
|
||||
inline long g_liteFakeAlloc = 0; // owned strings handed to the bridge
|
||||
inline long g_liteFakeFreed = 0; // owned strings released via freeString
|
||||
inline bool g_liteFakeWalletExists = true;
|
||||
inline bool g_liteFakeServerOnline = true;
|
||||
inline bool g_liteFakeShutdownCalled = false;
|
||||
|
||||
inline void resetLiteFakeCounters()
|
||||
{
|
||||
g_liteFakeAlloc = 0;
|
||||
g_liteFakeFreed = 0;
|
||||
g_liteFakeShutdownCalled = false;
|
||||
}
|
||||
|
||||
inline char* liteFakeDup(const char* s)
|
||||
{
|
||||
char* p = static_cast<char*>(std::malloc(std::strlen(s) + 1));
|
||||
std::strcpy(p, s);
|
||||
++g_liteFakeAlloc;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline bool liteFakeWalletExists(const char*) { return g_liteFakeWalletExists; }
|
||||
inline char* liteFakeInitNew(bool, const char*) { return liteFakeDup("{\"result\":\"created\"}"); }
|
||||
inline char* liteFakeInitFromPhrase(bool, const char*, const char*,
|
||||
unsigned long long, unsigned long long, bool)
|
||||
{
|
||||
return liteFakeDup("{\"result\":\"restored\"}");
|
||||
}
|
||||
inline char* liteFakeInitExisting(bool, const char*) { return liteFakeDup("{\"result\":\"opened\"}"); }
|
||||
inline char* liteFakeExecute(const char* command, const char*)
|
||||
{
|
||||
// A command named "boom" yields an "Error:"-prefixed response (the bridge's
|
||||
// looksLikeError contract maps that to ok=false).
|
||||
if (command && std::strcmp(command, "boom") == 0) {
|
||||
return liteFakeDup("Error: simulated lite backend failure");
|
||||
}
|
||||
return liteFakeDup("{\"version\":\"sdxl-fake\"}");
|
||||
}
|
||||
inline void liteFakeFree(char* v)
|
||||
{
|
||||
if (v) {
|
||||
++g_liteFakeFreed;
|
||||
std::free(v);
|
||||
}
|
||||
}
|
||||
inline bool liteFakeServerOnline(const char*) { return g_liteFakeServerOnline; }
|
||||
inline void liteFakeShutdown() { g_liteFakeShutdownCalled = true; }
|
||||
|
||||
inline dragonx::wallet::LiteClientBridgeApi makeFakeLiteApi()
|
||||
{
|
||||
dragonx::wallet::LiteClientBridgeApi api;
|
||||
api.walletExists = &liteFakeWalletExists;
|
||||
api.initializeNew = &liteFakeInitNew;
|
||||
api.initializeNewFromPhrase = &liteFakeInitFromPhrase;
|
||||
api.initializeExisting = &liteFakeInitExisting;
|
||||
api.execute = &liteFakeExecute;
|
||||
api.freeString = &liteFakeFree;
|
||||
api.checkServerOnline = &liteFakeServerOnline;
|
||||
api.shutdown = &liteFakeShutdown;
|
||||
return api;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace dragonx
|
||||
File diff suppressed because it is too large
Load Diff
67
tools/lite_smoke.cpp
Normal file
67
tools/lite_smoke.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// Real-backend smoke harness for the lite wallet bridge. Links the actual SDXL
|
||||
// litelib_* backend (via the same imported CMake target the app uses) and exercises
|
||||
// LiteClientBridge against a real lightwalletd server. This is the "real backend smoke
|
||||
// test" the implementation plan gates behind passing fake-backend tests.
|
||||
//
|
||||
// lite_smoke [server-url] [--create]
|
||||
//
|
||||
// Read-only by default (available / checkServerOnline / walletExists). Pass --create to
|
||||
// also attempt a real litelib_initialize_new (writes wallet state — run with an isolated
|
||||
// HOME, e.g. HOME=/tmp/lite_smoke env, so it cannot clobber a real wallet).
|
||||
|
||||
#include "wallet/lite_client_bridge.h"
|
||||
#include "wallet/lite_connection_service.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
using namespace dragonx::wallet;
|
||||
|
||||
std::string server = "https://lite.dragonx.is";
|
||||
bool doCreate = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg == "--create") doCreate = true;
|
||||
else server = arg;
|
||||
}
|
||||
|
||||
std::printf("[lite-smoke] server = %s\n", server.c_str());
|
||||
|
||||
auto bridge = LiteClientBridge::linkedSdxl();
|
||||
std::printf("[lite-smoke] available() = %s\n", bridge.available() ? "true" : "false");
|
||||
if (!bridge.available()) {
|
||||
std::printf("[lite-smoke] reason = %s\n", bridge.unavailableReason().c_str());
|
||||
std::printf("[lite-smoke] FAIL: backend not linked\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
const bool walletExists = bridge.walletExists(kDragonXLiteChainName);
|
||||
std::printf("[lite-smoke] walletExists(%s) = %s\n", kDragonXLiteChainName, walletExists ? "true" : "false");
|
||||
|
||||
const bool online = bridge.checkServerOnline(server);
|
||||
std::printf("[lite-smoke] checkServerOnline() = %s\n", online ? "true" : "false");
|
||||
|
||||
if (doCreate) {
|
||||
std::printf("[lite-smoke] initializeNew() ... (real network + writes wallet state)\n");
|
||||
auto result = bridge.initializeNew(false, server);
|
||||
std::printf("[lite-smoke] initializeNew ok = %s\n", result.ok ? "true" : "false");
|
||||
if (result.ok) {
|
||||
// The response is the new wallet's SEED PHRASE — never print it. Report only
|
||||
// that a well-formed, non-empty response came back.
|
||||
std::printf("[lite-smoke] wallet created; response len = %zu (seed redacted)\n",
|
||||
result.value.size());
|
||||
} else {
|
||||
std::printf("[lite-smoke] error = %s\n", result.error.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bridge.shutdown();
|
||||
std::printf("[lite-smoke] done (real litelib_* symbols are callable; results above are live)\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user