diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f64850..15194bf 100644 --- a/CMakeLists.txt +++ b/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=$ DRAGONX_ENABLE_EMBEDDED_DAEMON=$ + DRAGONX_ENABLE_LITE_BACKEND=$ + 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=$ + DRAGONX_LITE_BUILD=$ + DRAGONX_ENABLE_EMBEDDED_DAEMON=$ + DRAGONX_ENABLE_LITE_BACKEND=$ + 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 "") diff --git a/build.sh b/build.sh index c695613..5fe8ca6 100755 --- a/build.sh +++ b/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//; 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//; +# 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="" diff --git a/src/app.cpp b/src/app.cpp index 90b00f2..a979e44 100644 --- a/src/app.cpp +++ b/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; diff --git a/src/app.h b/src/app.h index 673d1c4..4629112 100644 --- a/src/app.h +++ b/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 settings_; + std::unique_ptr lite_wallet_; // lite builds w/ linked backend std::unique_ptr daemon_controller_; std::unique_ptr 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) diff --git a/src/config/settings.cpp b/src/config/settings.cpp index f030bcd..c12440c 100644 --- a/src/config/settings.cpp +++ b/src/config/settings.cpp @@ -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(); + 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(); if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get(); if (j.contains("max_connections")) max_connections_ = j["max_connections"].get(); + 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(); + } + if (lite.contains("chain_name") && lite["chain_name"].is_string()) { + lite_chain_name_ = lite["chain_name"].get(); + } + // 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(); + } else if (lite.contains("random_selection_seed") && lite["random_selection_seed"].is_number_integer()) { + const auto seed = lite["random_selection_seed"].get(); + lite_random_selection_seed_ = seed > 0 ? static_cast(seed) : 0; + } + if (lite.contains("persist_selected_server") && lite["persist_selected_server"].is_boolean()) { + lite_persist_selected_server_ = lite["persist_selected_server"].get(); + } + 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(); + } + if (server.contains("label") && server["label"].is_string()) { + preference.label = server["label"].get(); + } + if (server.contains("enabled") && server["enabled"].is_boolean()) { + preference.enabled = server["enabled"].get(); + } + lite_servers_.push_back(preference); + } + } + } if (j.contains("verbose_logging")) verbose_logging_ = j["verbose_logging"].get(); 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_) diff --git a/src/config/settings.h b/src/config/settings.h index a2132aa..b7f5b87 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -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& getLiteServers() const { return lite_servers_; } + void setLiteServers(const std::vector& 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 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 debug_categories_; bool theme_effects_enabled_ = true; diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index f0e66b7..cae6222 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -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 #include "../../util/logger.h" #include "../windows/balance_tab.h" #include "../windows/console_tab.h" @@ -42,6 +46,8 @@ #include #include #include +#include +#include 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 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::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(std::max(0, s_settingsState.lite_restore_birthday)); + input.request.restoreRequest.account = static_cast(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(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); diff --git a/src/wallet/lite_wallet_controller.cpp b/src/wallet/lite_wallet_controller.cpp new file mode 100644 index 0000000..f9a4d41 --- /dev/null +++ b/src/wallet/lite_wallet_controller.cpp @@ -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 + +#include + +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(model.balance.shieldedZatoshis) / kZatoshisPerCoin; + state.transparentBalance = static_cast(model.balance.transparentZatoshis) / kZatoshisPerCoin; + state.totalBalance = static_cast(model.balance.totalZatoshis) / kZatoshisPerCoin; + state.unconfirmedBalance = static_cast(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(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(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(confs) : 0; + } + state.transactions.push_back(std::move(tx)); + } + } + + if (model.hasSyncStatus) { + state.sync.blocks = static_cast(model.sync.walletHeight); + state.sync.headers = static_cast(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::createLinked( + WalletCapabilities capabilities, + LiteConnectionSettings connectionSettings) +{ + return std::make_unique( + 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 diff --git a/src/wallet/lite_wallet_controller.h b/src/wallet/lite_wallet_controller.h new file mode 100644 index 0000000..7137770 --- /dev/null +++ b/src/wallet/lite_wallet_controller.h @@ -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 +#include +#include + +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 createLinked( + WalletCapabilities capabilities, + LiteConnectionSettings connectionSettings); + + // Invoked after a wallet becomes ready, so the owner can persist settings. + void setPersistCallback(std::function 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 persist_; + bool walletOpen_ = false; + WalletBackendStatus status_; +}; + +} // namespace wallet +} // namespace dragonx diff --git a/tests/fake_lite_backend.h b/tests/fake_lite_backend.h new file mode 100644 index 0000000..c03c870 --- /dev/null +++ b/tests/fake_lite_backend.h @@ -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 +#include + +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(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 diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index fd13bf6..1487a65 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -20,6 +20,14 @@ #include "ui/windows/mining_tab_helpers.h" #include "util/amount_format.h" #include "util/payment_uri.h" +#include "wallet/lite_backend_artifact_contract.h" +#include "wallet/lite_bridge_runtime.h" +#include "wallet/lite_wallet_controller.h" +#include "wallet/lite_wallet_gateway.h" +#include "wallet/lite_wallet_state_mapper.h" +#include "config/settings.h" +#include "data/wallet_state.h" +#include "fake_lite_backend.h" #include #include @@ -29,9 +37,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -81,6 +91,637 @@ fs::path makeTempDir() return dir; } +void writeTestFile(const fs::path& path, const std::string& content) +{ + std::ofstream output(path, std::ios::binary); + output << content; +} + +using LiteBackendArtifactContractInput = dragonx::wallet::LiteBackendArtifactContractInput; +using LiteBackendArtifactContractIssue = dragonx::wallet::LiteBackendArtifactContractIssue; +using LiteBackendArtifactContractLinkMode = dragonx::wallet::LiteBackendArtifactContractLinkMode; +using LiteBackendArtifactContractResult = dragonx::wallet::LiteBackendArtifactContractResult; +using LiteBackendArtifactContractStatus = dragonx::wallet::LiteBackendArtifactContractStatus; +using LiteBridgeRuntime = dragonx::wallet::LiteBridgeRuntime; +using LiteBridgeRuntimeBindingInput = dragonx::wallet::LiteBridgeRuntimeBindingInput; +using LiteBridgeRuntimeBindingResult = dragonx::wallet::LiteBridgeRuntimeBindingResult; +using LiteBridgeRuntimeFakeDynamicLoaderInput = dragonx::wallet::LiteBridgeRuntimeFakeDynamicLoaderInput; +using LiteBridgeRuntimeFakeDynamicLoaderResult = dragonx::wallet::LiteBridgeRuntimeFakeDynamicLoaderResult; +using LiteBridgeRuntimeIssue = dragonx::wallet::LiteBridgeRuntimeIssue; +using LiteBridgeRuntimeLinkMode = dragonx::wallet::LiteBridgeRuntimeLinkMode; +using LiteBridgeRuntimeDynamicLoaderSmokeGateInput = dragonx::wallet::LiteBridgeRuntimeDynamicLoaderSmokeGateInput; +using LiteBridgeRuntimeDynamicLoaderSmokeGateResult = dragonx::wallet::LiteBridgeRuntimeDynamicLoaderSmokeGateResult; +using LiteBridgeRuntimePlatformLoaderReviewInput = dragonx::wallet::LiteBridgeRuntimePlatformLoaderReviewInput; +using LiteBridgeRuntimePlatformLoaderReviewResult = dragonx::wallet::LiteBridgeRuntimePlatformLoaderReviewResult; +using LiteBridgeRuntimePlatformDynamicLoaderAdapterContractInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderAdapterContractInput; +using LiteBridgeRuntimePlatformDynamicLoaderAdapterContractResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderAdapterContractResult; +using LiteBridgeRuntimePlatformDynamicLoaderAdapter = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderAdapter; +using LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult; +using LiteBridgeRuntimePlatformDynamicLoaderNoOpAdapterSeamInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderNoOpAdapterSeamInput; +using LiteBridgeRuntimePlatformDynamicLoaderNoOpAdapterSeamResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderNoOpAdapterSeamResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterImplementationReviewInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterImplementationReviewInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterImplementationReviewResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterImplementationReviewResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterDisabledScaffoldInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterDisabledScaffoldInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterDisabledScaffoldResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterDisabledScaffoldResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionPreflightInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionPreflightInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionPreflightResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionPreflightResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionApprovalGateInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionApprovalGateInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionApprovalGateResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionApprovalGateResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledOwnerHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledOwnerHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledOwnerHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledOwnerHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackBindingInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackBindingInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackBindingResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackBindingResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackPreInvocationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackPreInvocationGuardInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackPreInvocationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackPreInvocationGuardResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultPropagationInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultPropagationInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultPropagationResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalResultPropagationResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; +using LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; +using LiteBridgeRuntimeBatch46ResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; +using LiteBridgeRuntimeBatch46ResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; +using LiteBridgeRuntimeBatch47ReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; +using LiteBridgeRuntimeBatch47ReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; +using LiteBridgeRuntimeBatch48StatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; +using LiteBridgeRuntimeBatch48StatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; +using LiteBridgeRuntimeBatch49PublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; +using LiteBridgeRuntimeBatch49PublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; +using LiteBridgeRuntimeBatch50ResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; +using LiteBridgeRuntimeBatch50ResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; +using LiteBridgeRuntimeBatch51ReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; +using LiteBridgeRuntimeBatch51ReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; +using LiteBridgeRuntimeBatch52StatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; +using LiteBridgeRuntimeBatch52StatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; +using LiteBridgeRuntimeBatch53PublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; +using LiteBridgeRuntimeBatch53PublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; +using LiteBridgeRuntimeBatch54ResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; +using LiteBridgeRuntimeBatch54ResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; +using LiteBridgeRuntimeBatch55ReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; +using LiteBridgeRuntimeBatch55ReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; +using LiteBridgeRuntimeBatch56StatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; +using LiteBridgeRuntimeBatch56StatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; +using LiteBridgeRuntimeBatch57PublicationGuardInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardInput; +using LiteBridgeRuntimeBatch57PublicationGuardResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResult; +using LiteBridgeRuntimeBatch58ResultHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffInput; +using LiteBridgeRuntimeBatch58ResultHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffResult; +using LiteBridgeRuntimeBatch59ReadinessProjectionInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionInput; +using LiteBridgeRuntimeBatch59ReadinessProjectionResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionResult; +using LiteBridgeRuntimeBatch60StatusHandoffInput = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffInput; +using LiteBridgeRuntimeBatch60StatusHandoffResult = dragonx::wallet::LiteBridgeRuntimePlatformDynamicLoaderRealAdapterLoadOnlyExecutionDisabledCallbackRefusalPropagationConsumerReadinessDispatchConsumptionGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffPublicationGuardResultHandoffReadinessProjectionStatusHandoffResult; +static_assert(!std::is_same_v); +static_assert(!std::is_same_v); +static_assert(!std::is_same_v); +static_assert(!std::is_same_v); +static_assert(!std::is_same_v); +static_assert(!std::is_same_v); +using LiteBridgeRuntimeSharedArtifactSmokeEvidenceAcquisitionInput = dragonx::wallet::LiteBridgeRuntimeSharedArtifactSmokeEvidenceAcquisitionInput; +using LiteBridgeRuntimeSharedArtifactSmokeEvidenceAcquisitionResult = dragonx::wallet::LiteBridgeRuntimeSharedArtifactSmokeEvidenceAcquisitionResult; +using LiteBridgeRuntimeStatus = dragonx::wallet::LiteBridgeRuntimeStatus; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +template +std::unique_ptr heapConstructPlanResult(Factory&& factory) +{ + void* storage = ::operator new(sizeof(T)); + try { + return std::unique_ptr(::new (storage) T(factory())); + } catch (...) { + ::operator delete(storage); + throw; + } +} + + +dragonx::wallet::LiteBackendArtifactProvenanceMetadata makeReadyLiteBackendArtifactProvenance() +{ + dragonx::wallet::LiteBackendArtifactProvenanceMetadata provenance; + provenance.ownerReady = true; + provenance.metadataProvided = true; + provenance.source = "test-fixture"; + provenance.builder = "phase4-tests"; + provenance.sourceRevision = "test-revision"; + provenance.artifactSetId = "phase1-contract"; + provenance.redacted = true; + return provenance; +} + + +bool liteBackendArtifactContractHasIssue( + const LiteBackendArtifactContractResult& result, + LiteBackendArtifactContractIssue issue) +{ + for (const auto& issueInfo : result.issues) { + if (issueInfo.issue == issue) return true; + } + return false; +} + +int g_liteBridgeRuntimeFakeCallCount = 0; +int g_liteBridgeRuntimeTrackedFreeCount = 0; +int g_liteBridgeRuntimeTrackedShutdownCount = 0; +int g_liteBridgeRuntimeTrackedUnloadCount = 0; +std::vector g_liteBridgeRuntimeTrackedFreedValues; +std::vector g_liteBridgeRuntimeTrackedUnloadedHandles; +std::vector g_liteBridgeRuntimeTeardownEvents; + +char* makeLiteBridgeRuntimeOwnedCString(const std::string& value) +{ + char* rawValue = new char[value.size() + 1]; + for (std::size_t index = 0; index < value.size(); ++index) { + rawValue[index] = value[index]; + } + rawValue[value.size()] = '\0'; + return rawValue; +} + +void resetLiteBridgeRuntimeTrackedFree() +{ + g_liteBridgeRuntimeTrackedFreeCount = 0; + g_liteBridgeRuntimeTrackedShutdownCount = 0; + g_liteBridgeRuntimeTrackedUnloadCount = 0; + g_liteBridgeRuntimeTrackedFreedValues.clear(); + g_liteBridgeRuntimeTrackedUnloadedHandles.clear(); + g_liteBridgeRuntimeTeardownEvents.clear(); +} + +void fakeLiteBridgeRuntimeTrackedFreeString(char* value) +{ + ++g_liteBridgeRuntimeTrackedFreeCount; + if (value) { + g_liteBridgeRuntimeTrackedFreedValues.push_back(value); + g_liteBridgeRuntimeTeardownEvents.push_back("free:" + std::string(value)); + } + delete[] value; +} + +void fakeLiteBridgeRuntimeTrackedShutdown() +{ + ++g_liteBridgeRuntimeTrackedShutdownCount; + g_liteBridgeRuntimeTeardownEvents.push_back("shutdown"); +} + +void fakeLiteBridgeRuntimeTrackedUnload(const char* handleLabel) +{ + ++g_liteBridgeRuntimeTrackedUnloadCount; + const std::string label = handleLabel ? handleLabel : ""; + g_liteBridgeRuntimeTrackedUnloadedHandles.push_back(label); + g_liteBridgeRuntimeTeardownEvents.push_back("unload:" + label); +} + +bool fakeLiteBridgeRuntimeWalletExists(const char*) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return true; +} + +char* fakeLiteBridgeRuntimeInitializeNew(bool, const char*) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return nullptr; +} + +char* fakeLiteBridgeRuntimeOwnedInitializeNew(bool, const char*) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return makeLiteBridgeRuntimeOwnedCString("initialize-new-ok"); +} + +char* fakeLiteBridgeRuntimeInitializeNewFromPhrase(bool, + const char*, + const char*, + unsigned long long, + unsigned long long, + bool) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return nullptr; +} + +char* fakeLiteBridgeRuntimeOwnedInitializeNewFromPhrase(bool, + const char*, + const char*, + unsigned long long, + unsigned long long, + bool) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return makeLiteBridgeRuntimeOwnedCString("initialize-phrase-ok"); +} + +char* fakeLiteBridgeRuntimeInitializeExisting(bool, const char*) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return nullptr; +} + +char* fakeLiteBridgeRuntimeOwnedInitializeExisting(bool, const char*) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return makeLiteBridgeRuntimeOwnedCString("initialize-existing-ok"); +} + +char* fakeLiteBridgeRuntimeExecute(const char*, const char*) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return nullptr; +} + +char* fakeLiteBridgeRuntimeOwnedExecute(const char*, const char*) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return makeLiteBridgeRuntimeOwnedCString("bridge-runtime-ok"); +} + +void fakeLiteBridgeRuntimeFreeString(char*) +{ + ++g_liteBridgeRuntimeFakeCallCount; +} + +bool fakeLiteBridgeRuntimeCheckServerOnline(const char*) +{ + ++g_liteBridgeRuntimeFakeCallCount; + return true; +} + +void fakeLiteBridgeRuntimeShutdown() +{ + ++g_liteBridgeRuntimeFakeCallCount; +} + +dragonx::wallet::LiteClientBridgeApi makeCompleteFakeLiteBridgeRuntimeApi() +{ + return dragonx::wallet::LiteClientBridgeApi{ + &fakeLiteBridgeRuntimeWalletExists, + &fakeLiteBridgeRuntimeInitializeNew, + &fakeLiteBridgeRuntimeInitializeNewFromPhrase, + &fakeLiteBridgeRuntimeInitializeExisting, + &fakeLiteBridgeRuntimeExecute, + &fakeLiteBridgeRuntimeFreeString, + &fakeLiteBridgeRuntimeCheckServerOnline, + &fakeLiteBridgeRuntimeShutdown, + }; +} + +void clearLiteBridgeRuntimeSymbol(dragonx::wallet::LiteClientBridgeApi& bridgeApi, + const std::string& logicalName) +{ + if (logicalName == "walletExists") bridgeApi.walletExists = nullptr; + if (logicalName == "initializeNew") bridgeApi.initializeNew = nullptr; + if (logicalName == "initializeNewFromPhrase") bridgeApi.initializeNewFromPhrase = nullptr; + if (logicalName == "initializeExisting") bridgeApi.initializeExisting = nullptr; + if (logicalName == "execute") bridgeApi.execute = nullptr; + if (logicalName == "freeString") bridgeApi.freeString = nullptr; + if (logicalName == "checkServerOnline") bridgeApi.checkServerOnline = nullptr; + if (logicalName == "shutdown") bridgeApi.shutdown = nullptr; +} + + + + +LiteBridgeRuntimeFakeDynamicLoaderInput makeReadyFakeLiteBridgeRuntimeDynamicLoaderInput( + const std::string& source = "fake-dynamic-library", + const std::string& handleLabel = "phase6-handle") +{ + LiteBridgeRuntimeFakeDynamicLoaderInput input; + input.artifactPathReviewed = true; + input.platformLoaderStrategyReady = true; + input.loadSequenceReady = true; + input.unloadSequenceReady = true; + input.handleStoreReady = true; + input.symbolLookupReady = true; + input.fakeHandleProvided = true; + input.artifactPath = "/tmp/obsidian-dragon-phase6/" + handleLabel + ".so"; + input.displayPath = handleLabel + ".so"; + input.platform = "fake-linux"; + input.handleLabel = handleLabel; + input.source = source; + input.api = makeCompleteFakeLiteBridgeRuntimeApi(); + input.api.initializeNew = &fakeLiteBridgeRuntimeOwnedInitializeNew; + input.api.initializeNewFromPhrase = &fakeLiteBridgeRuntimeOwnedInitializeNewFromPhrase; + input.api.initializeExisting = &fakeLiteBridgeRuntimeOwnedInitializeExisting; + input.api.execute = &fakeLiteBridgeRuntimeOwnedExecute; + input.api.freeString = &fakeLiteBridgeRuntimeTrackedFreeString; + input.api.shutdown = &fakeLiteBridgeRuntimeTrackedShutdown; + input.unload = &fakeLiteBridgeRuntimeTrackedUnload; + return input; +} + + + + + + + + + +LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult +fakeLiteBridgeRuntimeNoOpAdapterBadSymbolLookup(const char*, const char*) +{ + LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult result; + result.ok = true; + result.disabled = false; + result.noPlatformSymbolResolution = false; + result.symbolAddressProduced = true; + result.summary = "bad symbol lookup produced an address"; + return result; +} + +LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult +fakeLiteBridgeRuntimeRealAdapterBadLoad(const char*, const char*) +{ + LiteBridgeRuntimePlatformDynamicLoaderAdapterCallResult result; + result.ok = true; + result.disabled = false; + result.noPlatformDynamicLibraryLoaded = false; + result.handleProduced = true; + result.summary = "bad real adapter scaffold load produced a handle"; + return result; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +bool liteBridgeRuntimeHasIssue( + const LiteBridgeRuntimeBindingResult& result, + LiteBridgeRuntimeIssue issue) +{ + for (const auto& issueInfo : result.issues) { + if (issueInfo.issue == issue) return true; + } + return false; +} + class MockWalletSecurityRpc : public dragonx::services::WalletSecurityController::RpcGateway { public: bool encryptResult = true; @@ -2421,6 +3062,902 @@ void testTransactionHistoryCacheRefreshApply() fs::remove_all(dir); } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +void testLiteBridgeOwnedStringCopiesBeforeFreeOnSuccess() +{ + resetLiteBridgeRuntimeTrackedFree(); + dragonx::wallet::LiteBridgeOwnedString ownedString( + makeLiteBridgeRuntimeOwnedCString("phase2-ok"), + &fakeLiteBridgeRuntimeTrackedFreeString); + + auto result = ownedString.intoResult(); + EXPECT_TRUE(result.ok); + EXPECT_EQ(result.value, std::string("phase2-ok")); + EXPECT_TRUE(result.error.empty()); + EXPECT_TRUE(ownedString.rawPointerReceived()); + EXPECT_TRUE(ownedString.copiedBeforeFree()); + EXPECT_TRUE(ownedString.freed()); + EXPECT_FALSE(ownedString.rawPointerEscaped()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues.size(), static_cast(1)); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("phase2-ok")); + + auto secondResult = ownedString.intoResult(); + EXPECT_FALSE(secondResult.ok); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); +} + +void testLiteBridgeOwnedStringClassifiesNullWithoutFree() +{ + resetLiteBridgeRuntimeTrackedFree(); + dragonx::wallet::LiteBridgeOwnedString ownedString( + nullptr, + &fakeLiteBridgeRuntimeTrackedFreeString); + + auto result = ownedString.intoResult(); + EXPECT_FALSE(result.ok); + EXPECT_TRUE(result.value.empty()); + EXPECT_TRUE(result.error.find("null string") != std::string::npos); + EXPECT_FALSE(ownedString.rawPointerReceived()); + EXPECT_FALSE(ownedString.copiedBeforeFree()); + EXPECT_FALSE(ownedString.freed()); + EXPECT_FALSE(ownedString.rawPointerEscaped()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 0); +} + +void testLiteBridgeOwnedStringClassifiesErrorAndFreesOnce() +{ + resetLiteBridgeRuntimeTrackedFree(); + dragonx::wallet::LiteBridgeOwnedString ownedString( + makeLiteBridgeRuntimeOwnedCString("Error: phase2 denied"), + &fakeLiteBridgeRuntimeTrackedFreeString); + + auto result = ownedString.intoResult(); + EXPECT_FALSE(result.ok); + EXPECT_TRUE(result.value.empty()); + EXPECT_EQ(result.error, std::string("Error: phase2 denied")); + EXPECT_TRUE(ownedString.copiedBeforeFree()); + EXPECT_TRUE(ownedString.freed()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues.size(), static_cast(1)); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("Error: phase2 denied")); +} + +void testLiteBridgeOwnedStringMovePreventsDoubleFree() +{ + resetLiteBridgeRuntimeTrackedFree(); + { + dragonx::wallet::LiteBridgeOwnedString original( + makeLiteBridgeRuntimeOwnedCString("phase2-move"), + &fakeLiteBridgeRuntimeTrackedFreeString); + dragonx::wallet::LiteBridgeOwnedString moved(std::move(original)); + + EXPECT_FALSE(original.rawPointerReceived()); + EXPECT_TRUE(moved.rawPointerReceived()); + EXPECT_FALSE(moved.rawPointerEscaped()); + } + + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues.size(), static_cast(1)); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("phase2-move")); +} + +void testLiteClientBridgeUsesRuntimeOwnedStringCleanup() +{ + resetLiteBridgeRuntimeTrackedFree(); + g_liteBridgeRuntimeFakeCallCount = 0; + auto bridgeApi = makeCompleteFakeLiteBridgeRuntimeApi(); + bridgeApi.execute = &fakeLiteBridgeRuntimeOwnedExecute; + bridgeApi.freeString = &fakeLiteBridgeRuntimeTrackedFreeString; + + auto bridge = dragonx::wallet::LiteClientBridge::fromApi(bridgeApi); + auto result = bridge.execute("phase2", "{}"); + + EXPECT_TRUE(result.ok); + EXPECT_EQ(result.value, std::string("bridge-runtime-ok")); + EXPECT_TRUE(result.error.empty()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues.size(), static_cast(1)); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("bridge-runtime-ok")); + EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 1); +} + + + + + + +void testLiteBridgeRuntimeUnavailableDoesNotCallShutdown() +{ + resetLiteBridgeRuntimeTrackedFree(); + auto runtime = LiteBridgeRuntime::fromBindingResult({}); + + EXPECT_FALSE(runtime.available()); + EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Unavailable); + runtime.shutdown(); + EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Shutdown); + EXPECT_TRUE(runtime.shutdownCalled()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); + EXPECT_TRUE(runtime.dynamicLibraryUnloadDeferred()); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + void testGeneratedResourceBehavior() { const auto* themes = dragonx::resources::getEmbeddedThemes(); @@ -2432,6 +3969,657 @@ void testGeneratedResourceBehavior() } } + +// ---- restored live lite_bridge_runtime tests ---- +std::vector requiredLiteBackendAbiSymbolNames() +{ + std::vector names; + for (const auto& symbol : dragonx::wallet::liteBackendArtifactContractRequiredSymbols()) { + names.push_back(symbol.abiName); + } + return names; +} + +LiteBackendArtifactContractInput makeReadyLiteBackendArtifactContractInput(const fs::path& artifactPath) +{ + LiteBackendArtifactContractInput input; + input.contractOwnerReady = true; + input.readOnlyGateReady = true; + input.projectRoot = artifactPath.parent_path().string(); + input.artifactPath = artifactPath.string(); + input.platform = dragonx::wallet::currentLiteBackendArtifactPlatform(); + input.artifactKind = dragonx::wallet::LiteBackendArtifactKind::StaticLibrary; + input.linkMode = LiteBackendArtifactContractLinkMode::ImportedLibrary; + input.abiVersion = dragonx::wallet::liteBackendArtifactContractSupportedAbiVersion(); + input.artifactSha256 = "phase1-artifact-sha256"; + input.provenance = makeReadyLiteBackendArtifactProvenance(); + input.sdxlCompatible = true; + input.symbolInventoryOwnerReady = true; + input.exportedSymbols = requiredLiteBackendAbiSymbolNames(); + return input; +} + +LiteBackendArtifactContractResult makeReadyLiteBridgeRuntimeArtifactContractResult() +{ + const auto tempDir = makeTempDir(); + const auto artifactPath = tempDir / "libsilentdragonxlite.a"; + writeTestFile(artifactPath, "phase2 fake archive"); + + auto contractInput = makeReadyLiteBackendArtifactContractInput(artifactPath); + auto contractResult = dragonx::wallet::evaluateLiteBackendArtifactContract(contractInput); + fs::remove_all(tempDir); + return contractResult; +} + +LiteBridgeRuntimeBindingInput makeReadyFakeLiteBridgeRuntimeBindingInput(bool activationRequested = true) +{ + LiteBridgeRuntimeBindingInput input; + input.artifactContract = makeReadyLiteBridgeRuntimeArtifactContractResult(); + input.config = dragonx::wallet::liteBridgeRuntimeConfigFromContractResult( + input.artifactContract, activationRequested); + input.useProvidedSymbolTable = true; + input.symbolTable = dragonx::wallet::liteBridgeRuntimeSymbolTableFromApi( + makeCompleteFakeLiteBridgeRuntimeApi(), "fake-imported-linked"); + return input; +} + +LiteBridgeRuntime makeReadyTrackedLiteBridgeRuntime() +{ + auto input = makeReadyFakeLiteBridgeRuntimeBindingInput(); + auto bridgeApi = makeCompleteFakeLiteBridgeRuntimeApi(); + bridgeApi.initializeNew = &fakeLiteBridgeRuntimeOwnedInitializeNew; + bridgeApi.initializeNewFromPhrase = &fakeLiteBridgeRuntimeOwnedInitializeNewFromPhrase; + bridgeApi.initializeExisting = &fakeLiteBridgeRuntimeOwnedInitializeExisting; + bridgeApi.execute = &fakeLiteBridgeRuntimeOwnedExecute; + bridgeApi.freeString = &fakeLiteBridgeRuntimeTrackedFreeString; + bridgeApi.shutdown = &fakeLiteBridgeRuntimeTrackedShutdown; + input.symbolTable = dragonx::wallet::liteBridgeRuntimeSymbolTableFromApi( + bridgeApi, "fake-imported-linked"); + auto bindingResult = dragonx::wallet::evaluateLiteBridgeRuntimeBinding(input); + return LiteBridgeRuntime::fromBindingResult(bindingResult); +} + +LiteBridgeRuntime makeReadyFakeDynamicLoaderLiteBridgeRuntime( + const std::string& source = "fake-dynamic-library", + const std::string& handleLabel = "phase6-handle") +{ + auto result = dragonx::wallet::evaluateLiteBridgeRuntimeFakeDynamicLoader( + makeReadyFakeLiteBridgeRuntimeDynamicLoaderInput(source, handleLabel)); + return LiteBridgeRuntime::fromFakeDynamicLoaderResult(result); +} + +void testLiteBridgeRuntimeShutdownIsIdempotent() +{ + resetLiteBridgeRuntimeTrackedFree(); + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + + EXPECT_TRUE(runtime.available()); + EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Ready); + EXPECT_TRUE(runtime.dynamicLibraryUnloadDeferred()); + + runtime.shutdown(); + EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Shutdown); + EXPECT_TRUE(runtime.shutdownCalled()); + EXPECT_FALSE(runtime.shutdownPending()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + + runtime.shutdown(); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(1)); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("shutdown")); +} + +void testLiteBridgeRuntimeDestructorCallsShutdownOnce() +{ + resetLiteBridgeRuntimeTrackedFree(); + { + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + EXPECT_TRUE(runtime.available()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); + } + + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(1)); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("shutdown")); +} + +void testLiteBridgeRuntimeShutdownWaitsForOwnedStringRelease() +{ + resetLiteBridgeRuntimeTrackedFree(); + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + auto ownedString = runtime.takeOwnedString(makeLiteBridgeRuntimeOwnedCString("phase3-owned")); + + EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(1)); + runtime.shutdown(); + EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::ShuttingDown); + EXPECT_TRUE(runtime.shutdownPending()); + EXPECT_FALSE(runtime.shutdownCalled()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 0); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); + + auto result = ownedString.intoResult(); + EXPECT_TRUE(result.ok); + EXPECT_EQ(result.value, std::string("phase3-owned")); + EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(0)); + EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Shutdown); + EXPECT_TRUE(runtime.shutdownCalled()); + EXPECT_FALSE(runtime.shutdownPending()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("free:phase3-owned")); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); +} + +void testLiteBridgeRuntimeDestructorShutdownWaitsForExternalOwnedString() +{ + resetLiteBridgeRuntimeTrackedFree(); + dragonx::wallet::LiteBridgeOwnedString heldString; + { + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + heldString = runtime.takeOwnedString(makeLiteBridgeRuntimeOwnedCString("phase3-destructor")); + EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(1)); + } + + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 0); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); + EXPECT_TRUE(g_liteBridgeRuntimeTeardownEvents.empty()); + + auto result = heldString.intoResult(); + EXPECT_TRUE(result.ok); + EXPECT_EQ(result.value, std::string("phase3-destructor")); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("free:phase3-destructor")); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); +} + +void testLiteBridgeRuntimeMoveAssignmentShutsDownReplacedRuntime() +{ + resetLiteBridgeRuntimeTrackedFree(); + { + auto firstRuntime = makeReadyTrackedLiteBridgeRuntime(); + auto secondRuntime = makeReadyTrackedLiteBridgeRuntime(); + + secondRuntime = std::move(firstRuntime); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + EXPECT_EQ(secondRuntime.status(), LiteBridgeRuntimeStatus::Ready); + EXPECT_TRUE(secondRuntime.available()); + } + + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 2); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("shutdown")); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); +} + +void testLiteBridgeRuntimeDryDispatchBoolWrappersAreFakeOnly() +{ + resetLiteBridgeRuntimeTrackedFree(); + g_liteBridgeRuntimeFakeCallCount = 0; + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + + auto walletExists = runtime.dryDispatchWalletExists("DRGX"); + EXPECT_TRUE(walletExists.ok); + EXPECT_TRUE(walletExists.boolValue); + EXPECT_TRUE(walletExists.fakeOnlyDispatch); + EXPECT_TRUE(walletExists.fakeCallAttempted); + EXPECT_TRUE(walletExists.noRealBridgeCalls); + EXPECT_TRUE(walletExists.noNetwork); + EXPECT_TRUE(walletExists.noWalletLifecycle); + EXPECT_TRUE(walletExists.noWalletStateMutation); + EXPECT_EQ(walletExists.operation, dragonx::wallet::LiteBridgeRuntimeDryDispatchOperation::WalletExists); + + auto serverOnline = runtime.dryDispatchCheckServerOnline("https://lite.example.invalid"); + EXPECT_TRUE(serverOnline.ok); + EXPECT_TRUE(serverOnline.boolValue); + EXPECT_TRUE(serverOnline.fakeOnlyDispatch); + EXPECT_TRUE(serverOnline.fakeCallAttempted); + EXPECT_TRUE(serverOnline.noRealBridgeCalls); + EXPECT_TRUE(serverOnline.noNetwork); + EXPECT_TRUE(serverOnline.noWalletLifecycle); + EXPECT_EQ(serverOnline.operation, dragonx::wallet::LiteBridgeRuntimeDryDispatchOperation::CheckServerOnline); + EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 2); +} + +void testLiteBridgeRuntimeDryDispatchExecuteOwnsReturnedString() +{ + resetLiteBridgeRuntimeTrackedFree(); + g_liteBridgeRuntimeFakeCallCount = 0; + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + + auto result = runtime.dryDispatchExecute("balance", "{}"); + EXPECT_TRUE(result.ok); + EXPECT_TRUE(result.fakeCallAttempted); + EXPECT_TRUE(result.noRealBridgeCalls); + EXPECT_TRUE(result.noWalletLifecycle); + EXPECT_TRUE(result.noSendImportExportExecution); + EXPECT_EQ(result.stringResult.value, std::string("bridge-runtime-ok")); + EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(0)); + EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreedValues[0], std::string("bridge-runtime-ok")); + + result = runtime.dryDispatchExecute("", "{}"); + EXPECT_FALSE(result.ok); + EXPECT_FALSE(result.fakeCallAttempted); + EXPECT_TRUE(result.error.find("empty") != std::string::npos); + EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 1); +} + +void testLiteBridgeRuntimeDryDispatchLifecycleWrappersRemainFakeOnly() +{ + resetLiteBridgeRuntimeTrackedFree(); + g_liteBridgeRuntimeFakeCallCount = 0; + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + + auto newResult = runtime.dryDispatchInitializeNew(false, "https://lite.example.invalid"); + auto existingResult = runtime.dryDispatchInitializeExisting(false, "https://lite.example.invalid"); + auto phraseResult = runtime.dryDispatchInitializeNewFromPhrase( + false, "https://lite.example.invalid", "seed words redacted", 42, 0, false); + + EXPECT_TRUE(newResult.ok); + EXPECT_TRUE(existingResult.ok); + EXPECT_TRUE(phraseResult.ok); + EXPECT_EQ(newResult.stringResult.value, std::string("initialize-new-ok")); + EXPECT_EQ(existingResult.stringResult.value, std::string("initialize-existing-ok")); + EXPECT_EQ(phraseResult.stringResult.value, std::string("initialize-phrase-ok")); + EXPECT_TRUE(newResult.noWalletLifecycle); + EXPECT_TRUE(existingResult.noWalletLifecycle); + EXPECT_TRUE(phraseResult.noWalletLifecycle); + EXPECT_TRUE(newResult.noWalletPersistence); + EXPECT_TRUE(existingResult.noWalletPersistence); + EXPECT_TRUE(phraseResult.noWalletPersistence); + EXPECT_TRUE(newResult.noWalletStateMutation); + EXPECT_TRUE(existingResult.noWalletStateMutation); + EXPECT_TRUE(phraseResult.noWalletStateMutation); + EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 3); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 3); +} + +void testLiteBridgeRuntimeDryDispatchRejectsShutdownRuntime() +{ + resetLiteBridgeRuntimeTrackedFree(); + g_liteBridgeRuntimeFakeCallCount = 0; + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + runtime.shutdown(); + + auto result = runtime.dryDispatchExecute("balance", "{}"); + EXPECT_FALSE(result.ok); + EXPECT_FALSE(result.fakeCallAttempted); + EXPECT_EQ(result.status, LiteBridgeRuntimeStatus::Shutdown); + EXPECT_TRUE(result.error.find("not ready") != std::string::npos); + EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 0); + EXPECT_EQ(g_liteBridgeRuntimeTrackedFreeCount, 0); +} + +void testLiteBridgeRuntimeDryDispatchShutdownUsesTeardownOwner() +{ + resetLiteBridgeRuntimeTrackedFree(); + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + + auto result = runtime.dryDispatchShutdown(); + EXPECT_TRUE(result.ok); + EXPECT_TRUE(result.fakeCallAttempted); + EXPECT_EQ(result.status, LiteBridgeRuntimeStatus::Shutdown); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + + result = runtime.dryDispatchShutdown(); + EXPECT_TRUE(result.ok); + EXPECT_FALSE(result.fakeCallAttempted); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); +} + +void testLiteBridgeRuntimeDryDispatchShutdownWaitsForOwnedString() +{ + resetLiteBridgeRuntimeTrackedFree(); + auto runtime = makeReadyTrackedLiteBridgeRuntime(); + auto ownedString = runtime.takeOwnedString(makeLiteBridgeRuntimeOwnedCString("phase4-dry")); + + auto result = runtime.dryDispatchShutdown(); + EXPECT_TRUE(result.ok); + EXPECT_FALSE(result.fakeCallAttempted); + EXPECT_EQ(result.status, LiteBridgeRuntimeStatus::ShuttingDown); + EXPECT_TRUE(runtime.shutdownPending()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); + + auto stringResult = ownedString.intoResult(); + EXPECT_TRUE(stringResult.ok); + EXPECT_EQ(stringResult.value, std::string("phase4-dry")); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("free:phase4-dry")); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); +} + +void testLiteBridgeRuntimeFakeDynamicLoaderUnloadWaitsForOwnedStringRelease() +{ + resetLiteBridgeRuntimeTrackedFree(); + auto runtime = makeReadyFakeDynamicLoaderLiteBridgeRuntime( + "fake-dynamic-library", "phase6-deferred"); + auto ownedString = runtime.takeOwnedString(makeLiteBridgeRuntimeOwnedCString("phase6-owned")); + + EXPECT_TRUE(runtime.dynamicLibraryHandlePresent()); + EXPECT_EQ(runtime.outstandingOwnedStringCount(), static_cast(1)); + runtime.shutdown(); + EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::ShuttingDown); + EXPECT_TRUE(runtime.shutdownPending()); + EXPECT_FALSE(runtime.shutdownCalled()); + EXPECT_TRUE(runtime.dynamicLibraryHandlePresent()); + EXPECT_TRUE(runtime.dynamicLibraryUnloadDeferred()); + EXPECT_FALSE(runtime.dynamicLibraryUnloadCalled()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 0); + EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 0); + + auto result = ownedString.intoResult(); + EXPECT_TRUE(result.ok); + EXPECT_EQ(result.value, std::string("phase6-owned")); + EXPECT_EQ(runtime.status(), LiteBridgeRuntimeStatus::Shutdown); + EXPECT_FALSE(runtime.dynamicLibraryHandlePresent()); + EXPECT_TRUE(runtime.dynamicLibraryUnloadCalled()); + EXPECT_FALSE(runtime.dynamicLibraryUnloadDeferred()); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(3)); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("free:phase6-owned")); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("shutdown")); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[2], std::string("unload:phase6-deferred")); +} + +void testLiteBridgeRuntimeFakeDynamicLoaderMoveAssignmentUnloadsReplacedHandle() +{ + resetLiteBridgeRuntimeTrackedFree(); + { + auto firstRuntime = makeReadyFakeDynamicLoaderLiteBridgeRuntime( + "fake-dynamic-library", "phase6-first"); + auto secondRuntime = makeReadyFakeDynamicLoaderLiteBridgeRuntime( + "fake-dynamic-library", "phase6-second"); + + secondRuntime = std::move(firstRuntime); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(2)); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[0], std::string("shutdown")); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[1], std::string("unload:phase6-second")); + EXPECT_TRUE(secondRuntime.available()); + EXPECT_TRUE(secondRuntime.dynamicLibraryHandlePresent()); + } + + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 2); + EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 2); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents.size(), static_cast(4)); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[2], std::string("shutdown")); + EXPECT_EQ(g_liteBridgeRuntimeTeardownEvents[3], std::string("unload:phase6-first")); +} + +void testLiteBridgeRuntimeFakeDynamicLoaderNonFakeSourceBlocksDryDispatch() +{ + resetLiteBridgeRuntimeTrackedFree(); + g_liteBridgeRuntimeFakeCallCount = 0; + auto runtime = makeReadyFakeDynamicLoaderLiteBridgeRuntime( + "dynamic-library", "phase6-nonfake"); + + EXPECT_TRUE(runtime.available()); + EXPECT_FALSE(runtime.fakeDispatchAllowed()); + auto result = runtime.dryDispatchWalletExists("DRGX"); + EXPECT_FALSE(result.ok); + EXPECT_FALSE(result.fakeCallAttempted); + EXPECT_TRUE(result.error.find("fake") != std::string::npos); + EXPECT_EQ(g_liteBridgeRuntimeFakeCallCount, 0); + EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 0); + + runtime.shutdown(); + EXPECT_EQ(g_liteBridgeRuntimeTrackedShutdownCount, 1); + EXPECT_EQ(g_liteBridgeRuntimeTrackedUnloadCount, 1); +} + +// M0: deterministic injectable fake backend driving the real LiteClientBridge +// (ungated fromApi path). This is the harness M1 service tests reuse. +void testLiteBackendInjectableFakeBridge() +{ + using dragonx::wallet::LiteClientBridge; + + // available() + bool round-trips + { + dragonx::test::resetLiteFakeCounters(); + auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); + EXPECT_TRUE(bridge.available()); + EXPECT_TRUE(bridge.walletExists("DRGX")); + EXPECT_TRUE(bridge.checkServerOnline("https://lite.example")); + } + + // execute() round-trips the canned value and frees the owned string exactly once + { + dragonx::test::resetLiteFakeCounters(); + auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); + const auto result = bridge.execute("info", ""); + EXPECT_TRUE(result.ok); + EXPECT_EQ(result.value, std::string("{\"version\":\"sdxl-fake\"}")); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 1L); + EXPECT_EQ(dragonx::test::g_liteFakeFreed, 1L); + } + + // lifecycle calls round-trip + { + dragonx::test::resetLiteFakeCounters(); + auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); + EXPECT_TRUE(bridge.initializeNew(false, "https://lite.example").ok); + EXPECT_TRUE(bridge.initializeExisting(false, "https://lite.example").ok); + EXPECT_TRUE(bridge.initializeNewFromPhrase(false, "https://lite.example", "seed words", 0, 0, false).ok); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, dragonx::test::g_liteFakeFreed); + } + + // an "Error:"-prefixed backend response maps to ok=false with the message propagated + { + dragonx::test::resetLiteFakeCounters(); + auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); + const auto result = bridge.execute("boom", ""); + EXPECT_FALSE(result.ok); + EXPECT_TRUE(result.error.rfind("Error:", 0) == 0); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, dragonx::test::g_liteFakeFreed); + } + + // empty command is rejected before reaching the backend (no allocation) + { + dragonx::test::resetLiteFakeCounters(); + auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); + EXPECT_FALSE(bridge.execute("", "").ok); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); + } + + // an unavailable bridge fails closed (never fakes success) + { + dragonx::test::resetLiteFakeCounters(); + auto bridge = LiteClientBridge::unavailable("lite backend is not linked"); + EXPECT_FALSE(bridge.available()); + EXPECT_FALSE(bridge.execute("info", "").ok); + EXPECT_FALSE(bridge.walletExists("DRGX")); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); + } + + // shutdown is invoked (via destructor) and no owned string leaks or double-frees + { + dragonx::test::resetLiteFakeCounters(); + { + auto bridge = LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()); + (void)bridge.execute("info", ""); + } + EXPECT_TRUE(dragonx::test::g_liteFakeShutdownCalled); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, dragonx::test::g_liteFakeFreed); + } +} + +// M1: the App-owned LiteWalletController drives real create/open/restore through an +// (injected fake) bridge, reports wallet-ready, persists, wipes secrets, and fails closed. +void testLiteWalletControllerLifecycle() +{ + using namespace dragonx::wallet; + const auto liteCaps = makeWalletCapabilities(WalletBuildKind::Lite, /*embeddedDaemon*/ false, /*liteBackendLinked*/ true); + const LiteConnectionSettings conn = defaultLiteConnectionSettings(); + + // create -> wallet ready, walletOpen() true, persist callback fires once, no leak + { + dragonx::test::resetLiteFakeCounters(); + int persistCount = 0; + LiteWalletController controller(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); + controller.setPersistCallback([&persistCount]() { ++persistCount; }); + EXPECT_FALSE(controller.walletOpen()); + + LiteWalletCreateRequest req; + req.passphrase = "hunter2"; + const auto result = controller.createWallet(req); + EXPECT_TRUE(result.ok); + EXPECT_TRUE(result.walletReady); + EXPECT_TRUE(result.operation == LiteWalletLifecycleOperation::CreateNew); + EXPECT_TRUE(controller.walletOpen()); + EXPECT_EQ(persistCount, 1); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, dragonx::test::g_liteFakeFreed); + } + + // restore-from-seed round-trips to ready + { + dragonx::test::resetLiteFakeCounters(); + LiteWalletController controller(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); + LiteWalletRestoreRequest req; + req.seedPhrase = "abandon abandon abandon ... art"; + const auto result = controller.restoreWallet(req); + EXPECT_TRUE(result.walletReady); + EXPECT_TRUE(result.operation == LiteWalletLifecycleOperation::RestoreFromSeed); + EXPECT_TRUE(controller.walletOpen()); + } + + // empty restore seed is rejected before the backend is touched + { + dragonx::test::resetLiteFakeCounters(); + LiteWalletController controller(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); + const auto result = controller.restoreWallet(LiteWalletRestoreRequest{}); + EXPECT_FALSE(result.ok); + EXPECT_FALSE(result.walletReady); + EXPECT_FALSE(controller.walletOpen()); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); + } + + // bridge calls disabled -> blocked, backend never reached + { + dragonx::test::resetLiteFakeCounters(); + LiteWalletController controller(liteCaps, conn, + LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()), + LiteWalletControllerOptions{/*allowBridgeCalls*/ false}); + const auto result = controller.createWallet(LiteWalletCreateRequest{}); + EXPECT_FALSE(result.walletReady); + EXPECT_FALSE(controller.walletOpen()); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); + } + + // full-node capabilities -> unsupported, fails closed + { + dragonx::test::resetLiteFakeCounters(); + const auto fullNodeCaps = makeWalletCapabilities(WalletBuildKind::FullNode, true, false); + LiteWalletController controller(fullNodeCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); + const auto result = controller.createWallet(LiteWalletCreateRequest{}); + EXPECT_FALSE(result.walletReady); + EXPECT_EQ(dragonx::test::g_liteFakeAlloc, 0L); + } + + // secret-wipe helper zeroes and clears + { + std::string secret = "super-secret-seed-phrase"; + secureWipeLiteSecret(secret); + EXPECT_TRUE(secret.empty()); + } +} + +// Migration: a saved lite chain_name outside {main,test,regtest} (e.g. the legacy +// "DRAGONX" ticker) is rewritten to "main" on load, since the backend hard-panics +// on unknown chains. Valid values are preserved. +void testLiteChainNameMigration() +{ + const fs::path dir = fs::temp_directory_path() / "dragonx_lite_chain_migration_test"; + fs::create_directories(dir); + + { + const fs::path p = dir / "legacy.json"; + writeTestFile(p, "{\"lite_wallet\":{\"chain_name\":\"DRAGONX\"}}"); + dragonx::config::Settings settings; + EXPECT_TRUE(settings.load(p.string())); + EXPECT_EQ(settings.getLiteChainName(), std::string("main")); + } + { + const fs::path p = dir / "valid.json"; + writeTestFile(p, "{\"lite_wallet\":{\"chain_name\":\"test\"}}"); + dragonx::config::Settings settings; + EXPECT_TRUE(settings.load(p.string())); + EXPECT_EQ(settings.getLiteChainName(), std::string("test")); + } + + std::error_code ec; + fs::remove_all(dir, ec); +} + +// M2: a parsed lite refresh bundle maps through to the app's WalletState (the last hop +// the Balance/Receive/Transactions tabs read), with zatoshi->DRGX conversion, z/t address +// split, transaction typing, confirmations, and sync progress. +void testLiteRefreshModelAppliesToWalletState() +{ + using namespace dragonx::wallet; + + LiteWalletRefreshBundle bundle; + bundle.complete = true; + bundle.successfulCommandCount = 4; + + bundle.hasBalance = true; + bundle.balance.shieldedBalance = 200000000; // 2 DRGX + bundle.balance.transparentBalance = 100000000; // 1 DRGX + bundle.balance.unconfirmedBalance = 50000000; // 0.5 DRGX + + bundle.hasAddresses = true; + bundle.addresses.zAddresses = {"zs1shielded"}; + bundle.addresses.tAddresses = {"t1transparent"}; + + bundle.hasTransactions = true; + LiteTransactionRecord rec; + rec.txid = "deadbeef"; + rec.datetime = 1700000000; + rec.blockHeight = 990; + rec.unconfirmed = false; + rec.direction = LiteTransactionDirection::Receive; + rec.address = "zs1shielded"; + rec.amount = 150000000; // 1.5 DRGX + bundle.transactions.transactions.push_back(rec); + + bundle.hasSyncStatus = true; + bundle.syncStatus.syncedBlocks = 1000; + bundle.syncStatus.totalBlocks = 1000; + bundle.syncStatus.progress = 1.0; + bundle.syncStatus.complete = true; + + const auto mapped = mapLiteWalletRefreshBundle(bundle); + EXPECT_TRUE(mapped.ok); + + dragonx::WalletState state; + applyLiteRefreshModelToWalletState(mapped.model, state); + + EXPECT_NEAR(state.privateBalance, 2.0, 1e-9); + EXPECT_NEAR(state.transparentBalance, 1.0, 1e-9); + EXPECT_NEAR(state.totalBalance, 3.0, 1e-9); + EXPECT_NEAR(state.unconfirmedBalance, 0.5, 1e-9); + + EXPECT_EQ(static_cast(state.addresses.size()), 2); + EXPECT_EQ(static_cast(state.z_addresses.size()), 1); + EXPECT_EQ(static_cast(state.t_addresses.size()), 1); + EXPECT_EQ(state.z_addresses[0].type, std::string("shielded")); + EXPECT_EQ(state.t_addresses[0].type, std::string("transparent")); + + EXPECT_EQ(static_cast(state.transactions.size()), 1); + EXPECT_EQ(state.transactions[0].type, std::string("receive")); + EXPECT_NEAR(state.transactions[0].amount, 1.5, 1e-9); + EXPECT_EQ(state.transactions[0].confirmations, 11); // chain 1000 - block 990 + 1 + + EXPECT_EQ(state.sync.blocks, 1000); + EXPECT_EQ(state.sync.headers, 1000); + EXPECT_FALSE(state.sync.syncing); +} + } // namespace int main() @@ -2459,6 +4647,30 @@ int main() testExplorerBlockCache(); testTransactionHistoryCache(); testTransactionHistoryCacheRefreshApply(); + testLiteBridgeOwnedStringCopiesBeforeFreeOnSuccess(); + testLiteBridgeOwnedStringClassifiesNullWithoutFree(); + testLiteBridgeOwnedStringClassifiesErrorAndFreesOnce(); + testLiteBridgeOwnedStringMovePreventsDoubleFree(); + testLiteClientBridgeUsesRuntimeOwnedStringCleanup(); + testLiteBackendInjectableFakeBridge(); + testLiteWalletControllerLifecycle(); + testLiteChainNameMigration(); + testLiteRefreshModelAppliesToWalletState(); + testLiteBridgeRuntimeShutdownIsIdempotent(); + testLiteBridgeRuntimeDestructorCallsShutdownOnce(); + testLiteBridgeRuntimeShutdownWaitsForOwnedStringRelease(); + testLiteBridgeRuntimeDestructorShutdownWaitsForExternalOwnedString(); + testLiteBridgeRuntimeMoveAssignmentShutsDownReplacedRuntime(); + testLiteBridgeRuntimeDryDispatchBoolWrappersAreFakeOnly(); + testLiteBridgeRuntimeDryDispatchExecuteOwnsReturnedString(); + testLiteBridgeRuntimeDryDispatchLifecycleWrappersRemainFakeOnly(); + testLiteBridgeRuntimeDryDispatchRejectsShutdownRuntime(); + testLiteBridgeRuntimeDryDispatchShutdownUsesTeardownOwner(); + testLiteBridgeRuntimeDryDispatchShutdownWaitsForOwnedString(); + testLiteBridgeRuntimeFakeDynamicLoaderUnloadWaitsForOwnedStringRelease(); + testLiteBridgeRuntimeFakeDynamicLoaderMoveAssignmentUnloadsReplacedHandle(); + testLiteBridgeRuntimeFakeDynamicLoaderNonFakeSourceBlocksDryDispatch(); + testLiteBridgeRuntimeUnavailableDoesNotCallShutdown(); testGeneratedResourceBehavior(); if (g_failures != 0) { diff --git a/tools/lite_smoke.cpp b/tools/lite_smoke.cpp new file mode 100644 index 0000000..1b5394e --- /dev/null +++ b/tools/lite_smoke.cpp @@ -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 +#include + +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; +}