Initial commit

This commit is contained in:
Khushboo Mehta 2026-02-11 20:39:32 +01:00
commit da055ad72e
28 changed files with 3397 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.DS_Store
build/
result
*.dylib
*.so
*.dll
*.a
CMakeCache.txt
CMakeFiles/
*.cmake
generated_code/
db/

273
CMakeLists.txt Normal file
View File

@ -0,0 +1,273 @@
cmake_minimum_required(VERSION 3.16)
project(BlockchainUIPlugin VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
# Allow override from environment or command line
if(NOT DEFINED LOGOS_LIBLOGOS_ROOT)
set(_parent_liblogos "${CMAKE_SOURCE_DIR}/../logos-liblogos")
set(_use_vendor ${LOGOS_BLOCKCHAIN_UI_USE_VENDOR})
if(NOT _use_vendor)
if(NOT EXISTS "${_parent_liblogos}/interface.h")
set(_use_vendor ON)
endif()
endif()
if(_use_vendor)
set(LOGOS_LIBLOGOS_ROOT "${CMAKE_SOURCE_DIR}/vendor/logos-liblogos")
else()
set(LOGOS_LIBLOGOS_ROOT "${_parent_liblogos}")
endif()
endif()
if(NOT DEFINED LOGOS_CPP_SDK_ROOT)
set(_parent_cpp_sdk "${CMAKE_SOURCE_DIR}/../logos-cpp-sdk")
set(_use_vendor ${LOGOS_BLOCKCHAIN_UI_USE_VENDOR})
if(NOT _use_vendor)
if(NOT EXISTS "${_parent_cpp_sdk}/cpp/logos_api.h")
set(_use_vendor ON)
endif()
endif()
if(_use_vendor)
set(LOGOS_CPP_SDK_ROOT "${CMAKE_SOURCE_DIR}/vendor/logos-cpp-sdk")
else()
set(LOGOS_CPP_SDK_ROOT "${_parent_cpp_sdk}")
endif()
endif()
# Check if dependencies are available (support both source and installed layouts)
set(_liblogos_found FALSE)
if(EXISTS "${LOGOS_LIBLOGOS_ROOT}/interface.h")
set(_liblogos_found TRUE)
set(_liblogos_is_source TRUE)
elseif(EXISTS "${LOGOS_LIBLOGOS_ROOT}/include/interface.h")
set(_liblogos_found TRUE)
set(_liblogos_is_source FALSE)
endif()
set(_cpp_sdk_found FALSE)
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.h")
set(_cpp_sdk_found TRUE)
set(_cpp_sdk_is_source TRUE)
elseif(EXISTS "${LOGOS_CPP_SDK_ROOT}/include/cpp/logos_api.h")
set(_cpp_sdk_found TRUE)
set(_cpp_sdk_is_source FALSE)
endif()
if(NOT _liblogos_found)
message(FATAL_ERROR "logos-liblogos not found at ${LOGOS_LIBLOGOS_ROOT}. "
"Set LOGOS_LIBLOGOS_ROOT or run git submodule update --init --recursive.")
endif()
if(NOT _cpp_sdk_found)
message(FATAL_ERROR "logos-cpp-sdk not found at ${LOGOS_CPP_SDK_ROOT}. "
"Set LOGOS_CPP_SDK_ROOT or run git submodule update --init --recursive.")
endif()
# Find Qt packages
find_package(Qt6 REQUIRED COMPONENTS Core Widgets RemoteObjects Quick QuickWidgets)
# Get the real path to handle symlinks correctly
get_filename_component(REAL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH)
# Try to find the component-interfaces package first
find_package(component-interfaces QUIET)
# If not found, use the local interfaces folder
if(NOT component-interfaces_FOUND)
# Include the local interfaces directory
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces)
# Create a component-interfaces library
add_library(component-interfaces INTERFACE)
target_include_directories(component-interfaces INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/interfaces)
endif()
# Source files
set(SOURCES
src/BlockchainPlugin.cpp
src/BlockchainPlugin.h
src/BlockchainBackend.cpp
src/BlockchainBackend.h
src/LogModel.cpp
src/LogModel.h
src/blockchain_resources.qrc
)
# Add SDK sources (only if source layout, installed layout uses the library)
if(_cpp_sdk_is_source)
list(APPEND SOURCES
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.h
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_client.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_client.h
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_consumer.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_consumer.h
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.h
${LOGOS_CPP_SDK_ROOT}/cpp/token_manager.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/token_manager.h
${LOGOS_CPP_SDK_ROOT}/cpp/module_proxy.cpp
${LOGOS_CPP_SDK_ROOT}/cpp/module_proxy.h
)
endif()
# Run Logos C++ generator on metadata before compilation (only for source layout)
set(METADATA_JSON "${CMAKE_CURRENT_SOURCE_DIR}/metadata.json")
# Only run generator for source layout - nix builds already have generated files
if(_cpp_sdk_is_source)
# Source layout: build and run the generator
# Generate into build directory
set(PLUGINS_OUTPUT_DIR "${CMAKE_BINARY_DIR}/generated_code")
set(CPP_GENERATOR_BUILD_DIR "${LOGOS_CPP_SDK_ROOT}/../build/cpp-generator")
set(CPP_GENERATOR "${CPP_GENERATOR_BUILD_DIR}/bin/logos-cpp-generator")
if(NOT TARGET cpp_generator_build)
add_custom_target(cpp_generator_build
COMMAND bash "${LOGOS_CPP_SDK_ROOT}/cpp-generator/compile.sh"
WORKING_DIRECTORY "${LOGOS_CPP_SDK_ROOT}/.."
COMMENT "Building logos-cpp-generator via ${LOGOS_CPP_SDK_ROOT}/cpp-generator/compile.sh"
VERBATIM
)
endif()
add_custom_target(run_cpp_generator_blockchain_ui
COMMAND "${CPP_GENERATOR}" --metadata "${METADATA_JSON}" --general-only --output-dir "${PLUGINS_OUTPUT_DIR}"
WORKING_DIRECTORY "${LOGOS_CPP_SDK_ROOT}/.."
COMMENT "Running logos-cpp-generator on ${METADATA_JSON} with output-dir ${PLUGINS_OUTPUT_DIR}"
VERBATIM
)
add_dependencies(run_cpp_generator_blockchain_ui cpp_generator_build)
# Add generated logos_sdk.cpp - will be generated by run_cpp_generator_blockchain_ui
list(APPEND SOURCES ${PLUGINS_OUTPUT_DIR}/logos_sdk.cpp)
# Mark as generated so CMake knows to wait for it
set_source_files_properties(
${PLUGINS_OUTPUT_DIR}/logos_sdk.cpp
PROPERTIES GENERATED TRUE
)
else()
# Installed/nix layout: lib.nix preConfigure populates generated_code before CMake.
# May be overridden by -DPLUGINS_OUTPUT_DIR= when source tree is read-only (e.g. Nix sandbox).
set(PLUGINS_OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated_code" CACHE PATH "Generated code directory (nix layout)")
list(APPEND SOURCES ${PLUGINS_OUTPUT_DIR}/include/logos_sdk.cpp)
endif()
# Create the plugin library
add_library(blockchain_ui SHARED ${SOURCES})
# Set output name without lib prefix and with correct name for generator
set_target_properties(blockchain_ui PROPERTIES
PREFIX ""
OUTPUT_NAME "blockchain_ui"
)
# Ensure generator runs before building the plugin (only for source layout)
if(_cpp_sdk_is_source)
add_dependencies(blockchain_ui run_cpp_generator_blockchain_ui)
endif()
# Include directories
target_include_directories(blockchain_ui PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_BINARY_DIR}
${PLUGINS_OUTPUT_DIR}
)
# Add include directories based on layout type
if(_liblogos_is_source)
target_include_directories(blockchain_ui PRIVATE ${LOGOS_LIBLOGOS_ROOT})
else()
target_include_directories(blockchain_ui PRIVATE ${LOGOS_LIBLOGOS_ROOT}/include)
endif()
if(_cpp_sdk_is_source)
target_include_directories(blockchain_ui PRIVATE
${LOGOS_CPP_SDK_ROOT}/cpp
${LOGOS_CPP_SDK_ROOT}/cpp/generated
)
else()
# For nix builds, also include the include subdirectory
# (lib.nix moves header files to ./generated_code/include/)
target_include_directories(blockchain_ui PRIVATE
${LOGOS_CPP_SDK_ROOT}/include
${LOGOS_CPP_SDK_ROOT}/include/cpp
${LOGOS_CPP_SDK_ROOT}/include/core
${PLUGINS_OUTPUT_DIR}/include
)
endif()
# Link against libraries
target_link_libraries(blockchain_ui PRIVATE
Qt6::Core
Qt6::Widgets
Qt6::RemoteObjects
Qt6::Quick
Qt6::QuickWidgets
component-interfaces
)
# Link SDK library if using installed layout
if(NOT _cpp_sdk_is_source)
find_library(LOGOS_SDK_LIB logos_sdk PATHS ${LOGOS_CPP_SDK_ROOT}/lib NO_DEFAULT_PATH REQUIRED)
target_link_libraries(blockchain_ui PRIVATE ${LOGOS_SDK_LIB})
endif()
# Link against Abseil libraries if found
find_package(absl QUIET)
if(absl_FOUND)
target_link_libraries(blockchain_ui PRIVATE
absl::base
absl::strings
absl::log
absl::check
)
endif()
# Set common properties for both platforms
set_target_properties(blockchain_ui PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules" # For Windows .dll
BUILD_WITH_INSTALL_RPATH TRUE
SKIP_BUILD_RPATH FALSE)
if(APPLE)
# macOS specific settings
set_target_properties(blockchain_ui PROPERTIES
INSTALL_RPATH "@loader_path"
INSTALL_NAME_DIR "@rpath"
BUILD_WITH_INSTALL_NAME_DIR TRUE)
add_custom_command(TARGET blockchain_ui POST_BUILD
COMMAND install_name_tool -id "@rpath/blockchain_ui.dylib" $<TARGET_FILE:blockchain_ui>
COMMENT "Updating library paths for macOS"
)
else()
# Linux specific settings
set_target_properties(blockchain_ui PROPERTIES
INSTALL_RPATH "$ORIGIN"
INSTALL_RPATH_USE_LINK_PATH FALSE)
endif()
install(TARGETS blockchain_ui
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
)
install(FILES ${METADATA_JSON}
DESTINATION ${CMAKE_INSTALL_DATADIR}/logos-blockchain-ui-new
)
install(DIRECTORY "${PLUGINS_OUTPUT_DIR}/"
DESTINATION ${CMAKE_INSTALL_DATADIR}/logos-blockchain-ui-new/generated
OPTIONAL
)
# Print status messages
message(STATUS "Blockchain UI Plugin configured successfully")

120
README.md Normal file
View File

@ -0,0 +1,120 @@
# logos-blockchain-ui-new
A Qt UI plugin for the Logos Blockchain Module, providing a graphical interface to control and monitor the Logos blockchain node.
## Features
- Start/Stop blockchain node
- Configure node parameters (config path, deployment)
- Check wallet balances
- Monitor node status and information
## How to Build
### Using Nix (Recommended)
#### Build Complete UI Plugin
```bash
# Build everything (default)
nix build
# Or explicitly
nix build '.#default'
```
The result will include:
- `/lib/blockchain_ui.dylib` (or `.so` on Linux) - The Blockchain UI plugin
#### Build Individual Components
```bash
# Build only the library (plugin)
nix build '.#lib'
# Build the standalone Qt application
nix build '.#app'
```
#### Development Shell
```bash
# Enter development shell with all dependencies
nix develop
```
**Note:** In zsh, you need to quote the target (e.g., `'.#default'`) to prevent glob expansion.
If you don't have flakes enabled globally, add experimental flags:
```bash
nix build --extra-experimental-features 'nix-command flakes'
```
The compiled artifacts can be found at `result/`
#### Running the Standalone App
After building the app with `nix build '.#app'`, you can run it:
```bash
# Run the standalone Qt application
./result/bin/logos-blockchain-ui-app
```
The app will automatically load the required modules (capability_module, liblogos_blockchain_module) and the blockchain_ui Qt plugin. All dependencies are bundled in the Nix store layout.
#### Nix Organization
The nix build system is organized into modular files in the `/nix` directory:
- `nix/default.nix` - Common configuration (dependencies, flags, metadata)
- `nix/lib.nix` - UI plugin compilation
- `nix/app.nix` - Standalone Qt application compilation
## Output Structure
When built with Nix:
**Library build (`nix build '.#lib'`):**
```
result/
└── lib/
└── blockchain_ui.dylib # Logos Blockchain UI plugin
```
**App build (`nix build '.#app'`):**
```
result/
├── bin/
│ ├── logos-blockchain-ui-app # Standalone Qt application
│ ├── logos_host # Logos host executable (for plugins)
│ └── logoscore # Logos core executable
├── lib/
│ ├── liblogos_core.dylib # Logos core library
│ ├── liblogos_sdk.dylib # Logos SDK library
│ └── Logos/DesignSystem/ # QML design system
├── modules/
│ ├── capability_module_plugin.dylib
│ ├── liblogos_blockchain_module.dylib
│ └── liblogos_blockchain.dylib
└── blockchain_ui.dylib # Qt plugin (loaded by app)
```
## Configuration
### Blockchain Node Configuration
The blockchain node can be configured in two ways:
1. **Via UI**: Enter the config path in the "Config Path" field
2. **Via Environment Variable**: Set `LB_CONFIG_PATH` to your configuration file path
Example configuration file can be found in the logos-blockchain-module repository at `config/node_config.yaml`.
### QML Hot Reload
During development, you can enable QML hot reload by setting an environment variable:
```bash
export BLOCKCHAIN_UI_QML_PATH=/path/to/logos-blockchain-ui/src/qml
```
This allows you to edit the QML file and see changes by reloading the plugin without recompiling.

84
app/CMakeLists.txt Normal file
View File

@ -0,0 +1,84 @@
cmake_minimum_required(VERSION 3.16)
project(LogosBlockchainUIApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Find Qt packages
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets)
# Find logos-liblogos
if(NOT DEFINED LOGOS_LIBLOGOS_ROOT)
message(FATAL_ERROR "LOGOS_LIBLOGOS_ROOT must be defined")
endif()
# Find logos-cpp-sdk
if(NOT DEFINED LOGOS_CPP_SDK_ROOT)
message(FATAL_ERROR "LOGOS_CPP_SDK_ROOT must be defined")
endif()
message(STATUS "Using logos-liblogos at: ${LOGOS_LIBLOGOS_ROOT}")
message(STATUS "Using logos-cpp-sdk at: ${LOGOS_CPP_SDK_ROOT}")
# Check if logos_sdk library exists
if(NOT EXISTS "${LOGOS_CPP_SDK_ROOT}/lib/liblogos_sdk.a" AND NOT EXISTS "${LOGOS_CPP_SDK_ROOT}/lib/liblogos_sdk.dylib" AND NOT EXISTS "${LOGOS_CPP_SDK_ROOT}/lib/liblogos_sdk.so")
message(WARNING "logos_sdk library not found in ${LOGOS_CPP_SDK_ROOT}/lib/")
message(STATUS "Available files in ${LOGOS_CPP_SDK_ROOT}/lib/:")
file(GLOB SDK_LIB_FILES "${LOGOS_CPP_SDK_ROOT}/lib/*")
foreach(file ${SDK_LIB_FILES})
message(STATUS " ${file}")
endforeach()
endif()
# Include directories - the new structure has headers in /include with subdirectories
include_directories(
${LOGOS_LIBLOGOS_ROOT}/include
${LOGOS_CPP_SDK_ROOT}/include
${LOGOS_CPP_SDK_ROOT}/include/cpp
${LOGOS_CPP_SDK_ROOT}/include/core
)
# Link directories
link_directories(
${LOGOS_LIBLOGOS_ROOT}/lib
${LOGOS_CPP_SDK_ROOT}/lib
)
# Set output directories
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# Create the executable
add_executable(logos-blockchain-ui-app
main.cpp
mainwindow.cpp
mainwindow.h
)
# Link libraries
target_link_libraries(logos-blockchain-ui-app PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Widgets
logos_core
logos_sdk
)
# Set RPATH settings for the executable
if(APPLE)
set_target_properties(logos-blockchain-ui-app PROPERTIES
INSTALL_RPATH "@executable_path/../lib"
BUILD_WITH_INSTALL_RPATH TRUE
)
elseif(UNIX)
set_target_properties(logos-blockchain-ui-app PROPERTIES
INSTALL_RPATH "$ORIGIN/../lib"
BUILD_WITH_INSTALL_RPATH TRUE
)
endif()
# Install rules
install(TARGETS logos-blockchain-ui-app
RUNTIME DESTINATION bin
)

56
app/main.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "mainwindow.h"
#include <QApplication>
#include <QDir>
#include <QDebug>
#include <iostream>
#include <memory>
// CoreManager C API functions
extern "C" {
void logos_core_set_plugins_dir(const char* plugins_dir);
void logos_core_start();
void logos_core_cleanup();
char** logos_core_get_loaded_plugins();
int logos_core_load_plugin(const char* plugin_name);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QString pluginsDir = QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../modules");
logos_core_set_plugins_dir(pluginsDir.toUtf8().constData());
logos_core_start();
if (!logos_core_load_plugin("capability_module")) {
qWarning() << "Failed to load capability_module plugin";
}
if (!logos_core_load_plugin("liblogos_blockchain_module")) {
qWarning() << "Failed to load blockchain module plugin";
}
char** loadedPlugins = logos_core_get_loaded_plugins();
int count = 0;
if (loadedPlugins) {
qInfo() << "Currently loaded plugins:";
for (char** p = loadedPlugins; *p != nullptr; ++p) {
qInfo() << " -" << *p;
++count;
}
qInfo() << "Total plugins:" << count;
} else {
qInfo() << "No plugins loaded.";
}
MainWindow window;
window.show();
int result = app.exec();
logos_core_cleanup();
return result;
}

66
app/mainwindow.cpp Normal file
View File

@ -0,0 +1,66 @@
#include <QtWidgets>
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setupUi();
}
MainWindow::~MainWindow()
{
}
void MainWindow::setupUi()
{
// Determine the appropriate plugin extension based on the platform
QString pluginExtension;
#if defined(Q_OS_WIN)
pluginExtension = ".dll";
#elif defined(Q_OS_MAC)
pluginExtension = ".dylib";
#else // Linux and other Unix-like systems
pluginExtension = ".so";
#endif
QString pluginPath = QCoreApplication::applicationDirPath() + "/../blockchain_ui" + pluginExtension;
QPluginLoader loader(pluginPath);
QWidget* blockchainWidget = nullptr;
if (loader.load()) {
QObject* plugin = loader.instance();
if (plugin) {
// Try to create the blockchain widget using the plugin's createWidget method
QMetaObject::invokeMethod(plugin, "createWidget",
Qt::DirectConnection,
Q_RETURN_ARG(QWidget*, blockchainWidget));
}
}
if (blockchainWidget) {
setCentralWidget(blockchainWidget);
} else {
qWarning() << "================================================";
qWarning() << "Failed to load blockchain UI plugin from:" << pluginPath;
qWarning() << "Error:" << loader.errorString();
qWarning() << "================================================";
// Fallback: show a message when plugin is not found
QWidget* fallbackWidget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(fallbackWidget);
QLabel* messageLabel = new QLabel("Blockchain UI module not loaded", fallbackWidget);
QFont font = messageLabel->font();
font.setPointSize(14);
messageLabel->setFont(font);
messageLabel->setAlignment(Qt::AlignCenter);
layout->addWidget(messageLabel);
setCentralWidget(fallbackWidget);
}
// Set window title and size
setWindowTitle("Logos Blockchain UI App");
resize(800, 600);
}

18
app/mainwindow.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
void setupUi();
};
#endif // MAINWINDOW_H

1411
flake.lock generated Normal file

File diff suppressed because it is too large Load Diff

84
flake.nix Normal file
View File

@ -0,0 +1,84 @@
{
description = "Logos Blockchain UI - A Qt UI plugin for Logos Blockchain Module";
inputs = {
# Follow the same nixpkgs as logos-liblogos to ensure compatibility
nixpkgs.follows = "logos-liblogos/nixpkgs";
logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk";
logos-liblogos.url = "github:logos-co/logos-liblogos";
logos-blockchain-module.url = "github:logos-blockchain/logos-blockchain-module/578308270ecfe7463a94ac50cae0584451c135ef";
logos-capability-module.url = "github:logos-co/logos-capability-module";
logos-design-system.url = "github:logos-co/logos-design-system";
logos-design-system.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, logos-cpp-sdk, logos-liblogos, logos-blockchain-module, logos-capability-module, logos-design-system }:
let
systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f {
pkgs = import nixpkgs { inherit system; };
logosSdk = logos-cpp-sdk.packages.${system}.default;
logosLiblogos = logos-liblogos.packages.${system}.default;
logosBlockchainModule = logos-blockchain-module.packages.${system}.default;
logosCapabilityModule = logos-capability-module.packages.${system}.default;
logosDesignSystem = logos-design-system.packages.${system}.default;
});
in
{
packages = forAllSystems ({ pkgs, logosSdk, logosLiblogos, logosBlockchainModule, logosCapabilityModule, logosDesignSystem }:
let
# Common configuration
common = import ./nix/default.nix {
inherit pkgs logosSdk logosLiblogos;
};
src = ./.;
# Library package (default blockchain-module has lib + include via symlinkJoin)
lib = import ./nix/lib.nix {
inherit pkgs common src logosBlockchainModule logosSdk;
};
# App package
app = import ./nix/app.nix {
inherit pkgs common src logosLiblogos logosSdk logosBlockchainModule logosCapabilityModule logosDesignSystem;
logosBlockchainUI = lib;
};
in
{
# Individual outputs
logos-blockchain-ui-lib = lib;
app = app;
lib = lib;
# Default package
default = lib;
}
);
devShells = forAllSystems ({ pkgs, logosSdk, logosLiblogos, logosBlockchainModule, logosCapabilityModule, logosDesignSystem }: {
default = pkgs.mkShell {
nativeBuildInputs = [
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
];
buildInputs = [
pkgs.qt6.qtbase
pkgs.qt6.qtremoteobjects
pkgs.zstd
pkgs.krb5
pkgs.abseil-cpp
];
shellHook = ''
export LOGOS_CPP_SDK_ROOT="${logosSdk}"
export LOGOS_LIBLOGOS_ROOT="${logosLiblogos}"
export LOGOS_DESIGN_SYSTEM_ROOT="${logosDesignSystem}"
echo "Logos Blockchain UI development environment"
echo "LOGOS_CPP_SDK_ROOT: $LOGOS_CPP_SDK_ROOT"
echo "LOGOS_LIBLOGOS_ROOT: $LOGOS_LIBLOGOS_ROOT"
'';
};
});
};
}

17
interfaces/IComponent.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <QObject>
#include <QWidget>
#include <QtPlugin>
class LogosAPI;
class IComponent {
public:
virtual ~IComponent() = default;
virtual QWidget* createWidget(LogosAPI* logosAPI = nullptr) = 0;
virtual void destroyWidget(QWidget* widget) = 0;
};
#define IComponent_iid "com.logos.component.IComponent"
Q_DECLARE_INTERFACE(IComponent, IComponent_iid)

26
metadata.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "blockchain_ui",
"version": "1.0.0",
"description": "Blockchain UI module for the Logos application",
"author": "Logos Blockchain Team",
"type": "ui",
"main": "blockchain_ui",
"dependencies": ["liblogos_blockchain_module"],
"category": "blockchain",
"build": {
"type": "cmake",
"files": [
"src/BlockchainPlugin.cpp",
"src/BlockchainPlugin.h",
"src/BlockchainBackend.cpp",
"src/BlockchainBackend.h",
"src/LogModel.cpp",
"src/LogModel.h",
"src/blockchain_resources.qrc"
]
},
"capabilities": [
"ui_components",
"blockchain"
]
}

250
nix/app.nix Normal file
View File

@ -0,0 +1,250 @@
# Builds the logos-blockchain-ui-app standalone application
{ pkgs, common, src, logosLiblogos, logosSdk, logosBlockchainModule, logosCapabilityModule, logosBlockchainUI, logosDesignSystem }:
pkgs.stdenv.mkDerivation rec {
pname = "logos-blockchain-ui-app";
version = common.version;
inherit src;
inherit (common) buildInputs cmakeFlags meta;
# Add logosSdk to nativeBuildInputs for logos-cpp-generator
nativeBuildInputs = common.nativeBuildInputs ++ [ logosSdk pkgs.patchelf pkgs.removeReferencesTo ];
# Provide Qt/GL runtime paths so the wrapper can inject them
qtLibPath = pkgs.lib.makeLibraryPath (
[
pkgs.qt6.qtbase
pkgs.qt6.qtremoteobjects
pkgs.zstd
pkgs.krb5
pkgs.zlib
pkgs.glib
pkgs.stdenv.cc.cc
pkgs.freetype
pkgs.fontconfig
]
++ pkgs.lib.optionals pkgs.stdenv.isLinux [
pkgs.libglvnd
pkgs.mesa.drivers
pkgs.xorg.libX11
pkgs.xorg.libXext
pkgs.xorg.libXrender
pkgs.xorg.libXrandr
pkgs.xorg.libXcursor
pkgs.xorg.libXi
pkgs.xorg.libXfixes
pkgs.xorg.libxcb
]
);
qtPluginPath = "${pkgs.qt6.qtbase}/lib/qt-6/plugins";
qmlImportPath = "${placeholder "out"}/lib:${pkgs.qt6.qtbase}/lib/qt-6/qml";
# This is a GUI application, enable Qt wrapping
dontWrapQtApps = false;
# This is an aggregate runtime layout; avoid stripping to prevent hook errors
dontStrip = true;
# Ensure proper Qt environment setup via wrapper
qtWrapperArgs = [
"--prefix" "LD_LIBRARY_PATH" ":" qtLibPath
"--prefix" "QT_PLUGIN_PATH" ":" qtPluginPath
"--prefix" "QML2_IMPORT_PATH" ":" qmlImportPath
];
preConfigure = ''
runHook prePreConfigure
# Set macOS deployment target to match Qt frameworks
export MACOSX_DEPLOYMENT_TARGET=12.0
# Copy logos-cpp-sdk headers to expected location
echo "Copying logos-cpp-sdk headers for app..."
mkdir -p ./logos-cpp-sdk/include/cpp
cp -r ${logosSdk}/include/cpp/* ./logos-cpp-sdk/include/cpp/
# Also copy core headers
echo "Copying core headers..."
mkdir -p ./logos-cpp-sdk/include/core
cp -r ${logosSdk}/include/core/* ./logos-cpp-sdk/include/core/
# Copy SDK library files to lib directory
echo "Copying SDK library files..."
mkdir -p ./logos-cpp-sdk/lib
if [ -f "${logosSdk}/lib/liblogos_sdk.dylib" ]; then
cp "${logosSdk}/lib/liblogos_sdk.dylib" ./logos-cpp-sdk/lib/
elif [ -f "${logosSdk}/lib/liblogos_sdk.so" ]; then
cp "${logosSdk}/lib/liblogos_sdk.so" ./logos-cpp-sdk/lib/
elif [ -f "${logosSdk}/lib/liblogos_sdk.a" ]; then
cp "${logosSdk}/lib/liblogos_sdk.a" ./logos-cpp-sdk/lib/
fi
runHook postPreConfigure
'';
# Additional environment variables for Qt and RPATH cleanup
preFixup = ''
runHook prePreFixup
# Set up Qt environment variables
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/lib/qt-6/plugins"
export QML_IMPORT_PATH="${pkgs.qt6.qtbase}/lib/qt-6/qml"
# Remove any remaining references to /build/ in binaries and set proper RPATH
find $out -type f -executable -exec sh -c '
if file "$1" | grep -q "ELF.*executable"; then
# Use patchelf to clean up RPATH if it contains /build/
if patchelf --print-rpath "$1" 2>/dev/null | grep -q "/build/"; then
echo "Cleaning RPATH for $1"
patchelf --remove-rpath "$1" 2>/dev/null || true
fi
# Set proper RPATH for the main binary
if echo "$1" | grep -q "/logos-blockchain-ui-app$"; then
echo "Setting RPATH for $1"
patchelf --set-rpath "$out/lib" "$1" 2>/dev/null || true
fi
fi
' _ {} \;
# Also clean up shared libraries
find $out -name "*.so" -exec sh -c '
if patchelf --print-rpath "$1" 2>/dev/null | grep -q "/build/"; then
echo "Cleaning RPATH for $1"
patchelf --remove-rpath "$1" 2>/dev/null || true
fi
' _ {} \;
runHook prePostFixup
'';
configurePhase = ''
runHook preConfigure
echo "Configuring logos-blockchain-ui-app..."
echo "liblogos: ${logosLiblogos}"
echo "cpp-sdk: ${logosSdk}"
echo "blockchain-module: ${logosBlockchainModule}"
echo "capability-module: ${logosCapabilityModule}"
echo "blockchain-ui: ${logosBlockchainUI}"
echo "logos-design-system: ${logosDesignSystem}"
# Verify that the built components exist
test -d "${logosLiblogos}" || (echo "liblogos not found" && exit 1)
test -d "${logosSdk}" || (echo "cpp-sdk not found" && exit 1)
test -d "${logosBlockchainModule}" || (echo "blockchain-module not found" && exit 1)
test -d "${logosCapabilityModule}" || (echo "capability-module not found" && exit 1)
test -d "${logosBlockchainUI}" || (echo "blockchain-ui not found" && exit 1)
test -d "${logosDesignSystem}" || (echo "logos-design-system not found" && exit 1)
cmake -S app -B build \
-GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 \
-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=FALSE \
-DCMAKE_INSTALL_RPATH="" \
-DCMAKE_SKIP_BUILD_RPATH=TRUE \
-DLOGOS_LIBLOGOS_ROOT=${logosLiblogos} \
-DLOGOS_CPP_SDK_ROOT=$(pwd)/logos-cpp-sdk
runHook postConfigure
'';
buildPhase = ''
runHook preBuild
cmake --build build
echo "logos-blockchain-ui-app built successfully!"
runHook postBuild
'';
installPhase = ''
runHook preInstall
# Create output directories
mkdir -p $out/bin $out/lib $out/modules
# Install our app binary
if [ -f "build/bin/logos-blockchain-ui-app" ]; then
cp build/bin/logos-blockchain-ui-app "$out/bin/"
echo "Installed logos-blockchain-ui-app binary"
fi
# Copy the core binaries from liblogos
if [ -f "${logosLiblogos}/bin/logoscore" ]; then
cp -L "${logosLiblogos}/bin/logoscore" "$out/bin/"
echo "Installed logoscore binary"
fi
if [ -f "${logosLiblogos}/bin/logos_host" ]; then
cp -L "${logosLiblogos}/bin/logos_host" "$out/bin/"
echo "Installed logos_host binary"
fi
# Copy required shared libraries from liblogos
if ls "${logosLiblogos}/lib/"liblogos_core.* >/dev/null 2>&1; then
cp -L "${logosLiblogos}/lib/"liblogos_core.* "$out/lib/" || true
fi
# Copy SDK library if it exists
if ls "${logosSdk}/lib/"liblogos_sdk.* >/dev/null 2>&1; then
cp -L "${logosSdk}/lib/"liblogos_sdk.* "$out/lib/" || true
fi
# Determine platform-specific plugin extension
OS_EXT="so"
case "$(uname -s)" in
Darwin) OS_EXT="dylib";;
Linux) OS_EXT="so";;
MINGW*|MSYS*|CYGWIN*) OS_EXT="dll";;
esac
# Copy module plugins into the modules directory
if [ -f "${logosCapabilityModule}/lib/capability_module_plugin.$OS_EXT" ]; then
cp -L "${logosCapabilityModule}/lib/capability_module_plugin.$OS_EXT" "$out/modules/"
fi
if [ -f "${logosBlockchainModule}/lib/liblogos_blockchain_module.$OS_EXT" ]; then
cp -L "${logosBlockchainModule}/lib/liblogos_blockchain_module.$OS_EXT" "$out/modules/"
fi
# Copy liblogos_blockchain library to modules directory (needed by blockchain module)
if [ -f "${logosBlockchainModule}/lib/liblogos_blockchain.$OS_EXT" ]; then
cp -L "${logosBlockchainModule}/lib/liblogos_blockchain.$OS_EXT" "$out/modules/"
fi
# Copy blockchain_ui Qt plugin to root directory (not modules, as it's loaded differently)
if [ -f "${logosBlockchainUI}/lib/blockchain_ui.$OS_EXT" ]; then
cp -L "${logosBlockchainUI}/lib/blockchain_ui.$OS_EXT" "$out/"
fi
# Copy design system QML module (Logos/DesignSystem) for runtime
if [ -d "${logosDesignSystem}/lib/Logos/DesignSystem" ]; then
mkdir -p "$out/lib/Logos"
cp -R "${logosDesignSystem}/lib/Logos/DesignSystem" "$out/lib/Logos/"
echo "Copied Logos Design System to lib/Logos/DesignSystem/"
fi
# Create a README for reference
cat > $out/README.txt <<EOF
Logos Blockchain UI App - Build Information
============================================
liblogos: ${logosLiblogos}
cpp-sdk: ${logosSdk}
blockchain-module: ${logosBlockchainModule}
capability-module: ${logosCapabilityModule}
blockchain-ui: ${logosBlockchainUI}
logos-design-system: ${logosDesignSystem}
Runtime Layout:
- Binary: $out/bin/logos-blockchain-ui-app
- Libraries: $out/lib
- Modules: $out/modules
- Qt Plugin: $out/blockchain_ui.$OS_EXT
Usage:
$out/bin/logos-blockchain-ui-app
EOF
runHook postInstall
'';
}

43
nix/default.nix Normal file
View File

@ -0,0 +1,43 @@
# Common build configuration shared across all packages
{ pkgs, logosSdk, logosLiblogos }:
{
pname = "logos-blockchain-ui";
version = "1.0.0";
# Common native build inputs
nativeBuildInputs = [
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
pkgs.qt6.wrapQtAppsHook
];
# Common runtime dependencies
buildInputs = [
pkgs.qt6.qtbase
pkgs.qt6.qtremoteobjects
pkgs.zstd
pkgs.krb5
pkgs.abseil-cpp
];
# Common CMake flags
cmakeFlags = [
"-GNinja"
"-DLOGOS_CPP_SDK_ROOT=${logosSdk}"
"-DLOGOS_LIBLOGOS_ROOT=${logosLiblogos}"
];
# Environment variables
env = {
LOGOS_CPP_SDK_ROOT = "${logosSdk}";
LOGOS_LIBLOGOS_ROOT = "${logosLiblogos}";
};
# Metadata
meta = with pkgs.lib; {
description = "Logos Blockchain UI - A Qt UI plugin for Logos Blockchain Module";
platforms = platforms.unix;
};
}

88
nix/lib.nix Normal file
View File

@ -0,0 +1,88 @@
# Builds the logos-blockchain-ui library
{ pkgs, common, src, logosBlockchainModule, logosSdk }:
pkgs.stdenv.mkDerivation {
pname = "${common.pname}-lib";
version = common.version;
inherit src;
inherit (common) buildInputs cmakeFlags meta env;
# Add logosSdk to nativeBuildInputs for logos-cpp-generator
nativeBuildInputs = common.nativeBuildInputs ++ [ logosSdk ];
preConfigure = ''
runHook prePreConfigure
# Create generated_code directory for generated files
mkdir -p ./generated_code
# Copy include files from logos-blockchain-module result
echo "Copying include files from logos-blockchain-module..."
if [ -d "${logosBlockchainModule}/include" ]; then
echo "Found include directory in logos-blockchain-module"
cp -r "${logosBlockchainModule}/include"/* ./generated_code/
echo "Copied include files:"
ls -la ./generated_code/
else
echo "Warning: No include directory found in logos-blockchain-module"
fi
# Run logos-cpp-generator with metadata.json and --general-only flag
echo "Running logos-cpp-generator..."
logos-cpp-generator --metadata ${src}/metadata.json --general-only --output-dir ./generated_code
# Check what was generated by logos-cpp-generator
echo "Checking generated files in generated_code:"
ls -la ./generated_code/
# Create include directory and move generated files there if they exist
if [ -f "./generated_code/core_manager_api.h" ] || [ -f "./generated_code/logos_sdk.h" ]; then
echo "Creating include directory and moving generated files..."
mkdir -p ./generated_code/include
# Move generated header files to include directory
for file in ./generated_code/*.h; do
if [ -f "$file" ]; then
mv "$file" ./generated_code/include/
fi
done
# Also copy generated .cpp files to include directory
for file in ./generated_code/*.cpp; do
if [ -f "$file" ]; then
cp "$file" ./generated_code/include/
fi
done
echo "Generated include directory:"
ls -la ./generated_code/include/
else
echo "Warning: No header files generated by logos-cpp-generator"
fi
runHook postPreConfigure
'';
installPhase = ''
runHook preInstall
mkdir -p $out/lib
# We are in build/; library is in build/modules/
if [ -f modules/blockchain_ui.dylib ]; then
cp modules/blockchain_ui.dylib $out/lib/
elif [ -f modules/blockchain_ui.so ]; then
cp modules/blockchain_ui.so $out/lib/
else
echo "Error: No library file found"
exit 1
fi
# Also install the generated include files
if [ -d "./generated_code/include" ]; then
mkdir -p $out/include
cp -r ./generated_code/include/* $out/include/
echo "Installed generated include files:"
ls -la $out/include/
fi
runHook postInstall
'';
}

148
src/BlockchainBackend.cpp Normal file
View File

@ -0,0 +1,148 @@
#include "BlockchainBackend.h"
#include <QDebug>
#include <QDateTime>
#include <QUrl>
BlockchainBackend::BlockchainBackend(LogosAPI* logosAPI, QObject* parent)
: QObject(parent),
m_status(NotStarted),
m_configPath(""),
m_logModel(new LogModel(this)),
m_logos(nullptr),
m_blockchainModule(nullptr)
{
m_configPath = QString::fromUtf8(qgetenv("LB_CONFIG_PATH"));
if (!logosAPI) {
logosAPI = new LogosAPI("core", this);
}
m_logos = new LogosModules(logosAPI);
if (!m_logos) {
setStatus(ErrorNotInitialized);
return;
}
m_blockchainModule = &m_logos->liblogos_blockchain_module;
if (m_blockchainModule && !m_blockchainModule->on("newBlock", [this](const QVariantList& data) {
onNewBlock(data);
})) {
setStatus(ErrorSubscribeFailed);
}
}
BlockchainBackend::~BlockchainBackend()
{
stopBlockchain();
}
void BlockchainBackend::setStatus(BlockchainStatus newStatus)
{
if (m_status != newStatus) {
m_status = newStatus;
emit statusChanged();
}
}
void BlockchainBackend::setConfigPath(const QString& path)
{
const QString localPath = QUrl::fromUserInput(path).toLocalFile();
if (m_configPath != localPath) {
m_configPath = localPath;
emit configPathChanged();
}
}
void BlockchainBackend::clearLogs()
{
m_logModel->clear();
}
QString BlockchainBackend::getBalance(const QString& addressHex)
{
if (!m_blockchainModule) {
return QStringLiteral("Error: Module not initialized.");
}
// The generated proxy converts C pointer params (uint8_t*, BalanceResult*) to QVariant,
// which cannot carry raw C pointers. The module needs to expose a QString-based
// wrapper (e.g. getWalletBalanceQ) for this to work through the proxy.
Q_UNUSED(addressHex)
return QStringLiteral("Not yet available: module needs Qt-friendly wallet API.");
}
QString BlockchainBackend::transferFunds(const QString& fromKeyHex, const QString& toKeyHex, const QString& amountStr)
{
if (!m_blockchainModule) {
return QStringLiteral("Error: Module not initialized.");
}
// Same limitation: TransferFundsArguments and Hash are C types that cannot
// pass through the QVariant-based generated proxy.
Q_UNUSED(fromKeyHex)
Q_UNUSED(toKeyHex)
Q_UNUSED(amountStr)
return QStringLiteral("Not yet available: module needs Qt-friendly wallet API.");
}
void BlockchainBackend::startBlockchain()
{
if (!m_blockchainModule) {
setStatus(ErrorNotInitialized);
return;
}
setStatus(Starting);
int result = m_blockchainModule->start(m_configPath, QString());
if (result == 0 || result == 1) {
setStatus(Running);
} else if (result == 2) {
setStatus(ErrorConfigMissing);
} else if (result == 3) {
setStatus(ErrorStartFailed);
} else {
setStatus(ErrorStartFailed);
}
}
void BlockchainBackend::stopBlockchain()
{
if (m_status != Running && m_status != Starting) {
return;
}
if (!m_blockchainModule) {
setStatus(ErrorNotInitialized);
return;
}
setStatus(Stopping);
int result = m_blockchainModule->stop();
if (result == 0 || result == 1) {
setStatus(Stopped);
} else {
setStatus(ErrorStopFailed);
}
}
void BlockchainBackend::onNewBlock(const QVariantList& data)
{
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss");
QString line;
if (!data.isEmpty()) {
QString blockInfo = data.first().toString();
QString shortInfo = blockInfo.left(80);
if (blockInfo.length() > 80) {
shortInfo += "...";
}
line = QString("[%1] 📦 New block: %2").arg(timestamp, shortInfo);
} else {
line = QString("[%1] 📦 New block (no data)").arg(timestamp);
}
m_logModel->append(line);
}

67
src/BlockchainBackend.h Normal file
View File

@ -0,0 +1,67 @@
#pragma once
#include <QObject>
#include <QString>
#include <utility>
#include "logos_api.h"
#include "logos_api_client.h"
#include "logos_sdk.h"
#include "LogModel.h"
// Type of the blockchain module proxy (has start(), stop(), on() etc.)
using BlockchainModuleProxy = std::remove_reference_t<decltype(std::declval<LogosModules>().liblogos_blockchain_module)>;
class BlockchainBackend : public QObject {
Q_OBJECT
public:
enum BlockchainStatus {
NotStarted = 0,
Starting,
Running,
Stopping,
Stopped,
Error,
ErrorNotInitialized,
ErrorConfigMissing,
ErrorStartFailed,
ErrorStopFailed,
ErrorSubscribeFailed
};
Q_ENUM(BlockchainStatus)
Q_PROPERTY(BlockchainStatus status READ status NOTIFY statusChanged)
Q_PROPERTY(QString configPath READ configPath WRITE setConfigPath NOTIFY configPathChanged)
Q_PROPERTY(LogModel* logModel READ logModel CONSTANT)
explicit BlockchainBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
~BlockchainBackend();
BlockchainStatus status() const { return m_status; }
QString configPath() const { return m_configPath; }
LogModel* logModel() const { return m_logModel; }
void setConfigPath(const QString& path);
Q_INVOKABLE void clearLogs();
Q_INVOKABLE QString getBalance(const QString& addressHex);
Q_INVOKABLE QString transferFunds(const QString& fromKeyHex, const QString& toKeyHex, const QString& amountStr);
Q_INVOKABLE void startBlockchain();
Q_INVOKABLE void stopBlockchain();
public slots:
void onNewBlock(const QVariantList& data);
signals:
void statusChanged();
void configPathChanged();
private:
void setStatus(BlockchainStatus newStatus);
BlockchainStatus m_status;
QString m_configPath;
LogModel* m_logModel;
LogosModules* m_logos;
BlockchainModuleProxy* m_blockchainModule;
};

54
src/BlockchainPlugin.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "BlockchainPlugin.h"
#include "BlockchainBackend.h"
#include "LogModel.h"
#include <QQuickWidget>
#include <QQmlContext>
#include <QQmlEngine>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QUrl>
QWidget* BlockchainPlugin::createWidget(LogosAPI* logosAPI) {
qDebug() << "BlockchainPlugin::createWidget called";
QQuickWidget* quickWidget = new QQuickWidget();
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
qmlRegisterType<BlockchainBackend>("BlockchainBackend", 1, 0, "BlockchainBackend");
qmlRegisterType<LogModel>("BlockchainBackend", 1, 0, "LogModel");
BlockchainBackend* backend = new BlockchainBackend(logosAPI, quickWidget);
quickWidget->rootContext()->setContextProperty("backend", backend);
QString qmlSource = "qrc:/qml/BlockchainView.qml";
QString importPath = "qrc:/qml";
QString envPath = QString::fromUtf8(qgetenv("BLOCKCHAIN_UI_QML_PATH")).trimmed();
if (!envPath.isEmpty()) {
QFileInfo info(envPath);
if (info.isDir()) {
QString main = QDir(info.absoluteFilePath()).absoluteFilePath("BlockchainView.qml");
if (QFile::exists(main)) {
importPath = info.absoluteFilePath();
qmlSource = QUrl::fromLocalFile(main).toString();
} else {
qWarning() << "BLOCKCHAIN_UI_QML_PATH: BlockchainView.qml not found in" << info.absoluteFilePath();
}
}
}
quickWidget->engine()->addImportPath(importPath);
quickWidget->setSource(QUrl(qmlSource));
if (quickWidget->status() == QQuickWidget::Error) {
qWarning() << "BlockchainPlugin: Failed to load QML:" << quickWidget->errors();
}
return quickWidget;
}
void BlockchainPlugin::destroyWidget(QWidget* widget) {
delete widget;
}

14
src/BlockchainPlugin.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <IComponent.h>
#include <QObject>
class BlockchainPlugin : public QObject, public IComponent {
Q_OBJECT
Q_INTERFACES(IComponent)
Q_PLUGIN_METADATA(IID IComponent_iid FILE "metadata.json")
public:
Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr) override;
void destroyWidget(QWidget* widget) override;
};

43
src/LogModel.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "LogModel.h"
int LogModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return m_lines.size();
}
QVariant LogModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_lines.size())
return QVariant();
if (role == TextRole || role == Qt::DisplayRole)
return m_lines.at(index.row());
return QVariant();
}
QHash<int, QByteArray> LogModel::roleNames() const
{
QHash<int, QByteArray> names;
names[TextRole] = "text";
return names;
}
void LogModel::append(const QString& line)
{
const int row = m_lines.size();
beginInsertRows(QModelIndex(), row, row);
m_lines.append(line);
endInsertRows();
emit countChanged();
}
void LogModel::clear()
{
if (m_lines.isEmpty())
return;
beginResetModel();
m_lines.clear();
endResetModel();
emit countChanged();
}

26
src/LogModel.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <QAbstractListModel>
#include <QStringList>
class LogModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles { TextRole = Qt::UserRole + 1 };
explicit LogModel(QObject* parent = nullptr) : QAbstractListModel(parent) {}
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void append(const QString& line);
Q_INVOKABLE void clear();
signals:
void countChanged();
private:
QStringList m_lines;
};

View File

@ -0,0 +1,11 @@
<RCC>
<qresource prefix="/">
<file>qml/BlockchainView.qml</file>
<file>qml/controls/qmldir</file>
<file>qml/controls/LogosButton.qml</file>
<file>qml/views/qmldir</file>
<file>qml/views/StatusConfigView.qml</file>
<file>qml/views/LogsView.qml</file>
<file>qml/views/WalletView.qml</file>
</qresource>
</RCC>

110
src/qml/BlockchainView.qml Normal file
View File

@ -0,0 +1,110 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import QtCore
import BlockchainBackend
import Logos.DesignSystem
import views
Rectangle {
id: root
QtObject {
id: _d
function getStatusString(status) {
switch(status) {
case BlockchainBackend.NotStarted: return qsTr("Not Started");
case BlockchainBackend.Starting: return qsTr("Starting...");
case BlockchainBackend.Running: return qsTr("Running");
case BlockchainBackend.Stopping: return qsTr("Stopping...");
case BlockchainBackend.Stopped: return qsTr("Stopped");
case BlockchainBackend.Error: return qsTr("Error");
case BlockchainBackend.ErrorNotInitialized: return qsTr("Error: Module not initialized");
case BlockchainBackend.ErrorConfigMissing: return qsTr("Error: Config path missing");
case BlockchainBackend.ErrorStartFailed: return qsTr("Error: Failed to start node");
case BlockchainBackend.ErrorStopFailed: return qsTr("Error: Failed to stop node");
case BlockchainBackend.ErrorSubscribeFailed: return qsTr("Error: Failed to subscribe to events");
default: return qsTr("Unknown");
}
}
function getStatusColor(status) {
switch(status) {
case BlockchainBackend.Running: return Theme.palette.success;
case BlockchainBackend.Starting: return Theme.palette.warning;
case BlockchainBackend.Stopping: return Theme.palette.warning;
case BlockchainBackend.NotStarted: return Theme.palette.error;
case BlockchainBackend.Stopped: return Theme.palette.error;
case BlockchainBackend.Error:
case BlockchainBackend.ErrorNotInitialized:
case BlockchainBackend.ErrorConfigMissing:
case BlockchainBackend.ErrorStartFailed:
case BlockchainBackend.ErrorStopFailed:
case BlockchainBackend.ErrorSubscribeFailed: return Theme.palette.error;
default: return Theme.palette.textSecondary;
}
}
}
color: Theme.palette.background
SplitView {
anchors.fill: parent
anchors.margins: Theme.spacing.large
orientation: Qt.Vertical
// Top: Status/Config + Wallet side-by-side
RowLayout {
SplitView.fillWidth: true
SplitView.minimumHeight: 200
StatusConfigView {
Layout.preferredWidth: parent.width / 2
statusText: _d.getStatusString(backend.status)
statusColor: _d.getStatusColor(backend.status)
configPath: backend.configPath
canStart: !!backend.configPath
&& backend.status !== BlockchainBackend.Starting
&& backend.status !== BlockchainBackend.Stopping
isRunning: backend.status === BlockchainBackend.Running
onStartRequested: backend.startBlockchain()
onStopRequested: backend.stopBlockchain()
onChangeConfigRequested: fileDialog.open()
}
WalletView {
id: walletView
Layout.preferredWidth: parent.width / 2
onGetBalanceRequested: function(addressHex) {
walletView.setBalanceResult(backend.getBalance(addressHex))
}
onTransferRequested: function(fromKeyHex, toKeyHex, amount) {
walletView.setTransferResult(backend.transferFunds(fromKeyHex, toKeyHex, amount))
}
}
}
// Bottom: Logs
LogsView {
SplitView.fillWidth: true
SplitView.minimumHeight: 150
logModel: backend.logModel
onClearRequested: backend.clearLogs()
}
}
FileDialog {
id: fileDialog
modality: Qt.NonModal
nameFilters: ["YAML files (*.yaml)"]
currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]
onAccepted: {
backend.configPath = selectedFile
}
}
}

View File

@ -0,0 +1,20 @@
import QtQuick
import QtQuick.Controls
import Logos.DesignSystem
Button {
implicitWidth: 200
implicitHeight: 50
background: Rectangle {
color: parent.pressed || parent.hovered ?
Theme.palette.backgroundMuted :
Theme.palette.backgroundSecondary
radius: Theme.spacing.radiusXlarge
border.color: parent.pressed || parent.hovered ?
Theme.palette.overlayOrange :
Theme.palette.border
border.width: 1
}
}

2
src/qml/controls/qmldir Normal file
View File

@ -0,0 +1,2 @@
module controls
LogosButton 1.0 LogosButton.qml

View File

@ -0,0 +1,92 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Logos.DesignSystem
import controls
Control {
id: root
// --- Public API ---
required property var logModel // LogModel (QAbstractListModel with "text" role)
signal clearRequested()
background: Rectangle {
color: Theme.palette.background
}
ColumnLayout {
anchors.fill: parent
anchors.topMargin: Theme.spacing.large
spacing: Theme.spacing.medium
// Header
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
spacing: Theme.spacing.medium
Text {
text: qsTr("Logs")
font.pixelSize: Theme.typography.secondaryText
font.bold: true
color: Theme.palette.text
}
Item { Layout.fillWidth: true }
LogosButton {
text: qsTr("Clear")
Layout.preferredWidth: 80
Layout.preferredHeight: 32
onClicked: root.clearRequested()
}
}
// Log list
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.palette.backgroundSecondary
radius: Theme.spacing.radiusLarge
border.color: Theme.palette.border
border.width: 1
ListView {
id: logsListView
anchors.fill: parent
clip: true
model: root.logModel
spacing: 2
delegate: Text {
text: model.text
font.pixelSize: Theme.typography.secondaryText
font.family: Theme.typography.publicSans
color: Theme.palette.text
width: logsListView.width
wrapMode: Text.Wrap
}
Text {
visible: !root.logModel || root.logModel.count === 0
anchors.centerIn: parent
text: qsTr("No logs yet...")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
}
Connections {
target: root.logModel
function onCountChanged() {
if (root.logModel.count > 0)
logsListView.positionViewAtEnd()
}
}
}
}
}
}

View File

@ -0,0 +1,117 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Logos.DesignSystem
import controls
ColumnLayout {
id: root
// --- Public API ---
required property string statusText
required property color statusColor
required property string configPath
required property bool canStart
required property bool isRunning
signal startRequested()
signal stopRequested()
signal changeConfigRequested()
spacing: Theme.spacing.large
// Status Card
Rectangle {
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: parent.width * 0.9
Layout.preferredHeight: implicitHeight
implicitHeight: statusContent.implicitHeight + 2 * Theme.spacing.large
color: Theme.palette.backgroundTertiary
radius: Theme.spacing.radiusLarge
border.color: Theme.palette.border
border.width: 1
ColumnLayout {
id: statusContent
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacing.large
spacing: Theme.spacing.medium
Text {
Layout.alignment: Qt.AlignLeft
font.pixelSize: Theme.typography.primaryText
font.bold: true
text: root.statusText
color: root.statusColor
}
Text {
Layout.alignment: Qt.AlignLeft
Layout.topMargin: -Theme.spacing.medium
text: qsTr("Mainnet - chain ID 1")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
}
LogosButton {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: parent.width
Layout.preferredHeight: 50
enabled: root.canStart
text: root.isRunning ? qsTr("Stop Node") : qsTr("Start Node")
onClicked: root.isRunning ? root.stopRequested() : root.startRequested()
}
}
}
// Config Card
Rectangle {
Layout.preferredWidth: parent.width * 0.9
Layout.preferredHeight: implicitHeight
implicitHeight: configContent.implicitHeight + 2 * Theme.spacing.large
color: Theme.palette.backgroundTertiary
radius: Theme.spacing.radiusLarge
border.color: Theme.palette.border
border.width: 1
ColumnLayout {
id: configContent
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacing.large
spacing: Theme.spacing.medium
Text {
text: qsTr("Current Config: ")
font.pixelSize: Theme.typography.primaryText
font.bold: true
color: Theme.palette.text
}
Text {
Layout.fillWidth: true
Layout.topMargin: -Theme.spacing.medium
text: root.configPath || qsTr("No file selected")
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
wrapMode: Text.WordWrap
}
LogosButton {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: parent.width
Layout.preferredHeight: 50
text: qsTr("Change")
onClicked: root.changeConfigRequested()
}
}
}
Item { Layout.fillHeight: true }
}

View File

@ -0,0 +1,141 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Logos.DesignSystem
import controls
ColumnLayout {
id: root
// --- Public API ---
signal getBalanceRequested(string addressHex)
signal transferRequested(string fromKeyHex, string toKeyHex, string amount)
// Call these from the parent to display results
function setBalanceResult(text) {
balanceResultText.text = text
}
function setTransferResult(text) {
transferResultText.text = text
}
spacing: Theme.spacing.medium
// Get balance card
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: balanceCol.implicitHeight
color: Theme.palette.backgroundTertiary
radius: Theme.spacing.radiusLarge
border.color: Theme.palette.border
border.width: 1
ColumnLayout {
id: balanceCol
anchors.fill: parent
anchors.margins: Theme.spacing.large
spacing: Theme.spacing.large
Text {
text: qsTr("Get balance")
font.pixelSize: Theme.typography.secondaryText
font.bold: true
color: Theme.palette.text
}
CustomTextFeild {
id: balanceAddressField
placeholderText: qsTr("Wallet address (64 hex chars)")
}
LogosButton {
text: qsTr("Get balance")
Layout.alignment: Qt.AlignRight
onClicked: root.getBalanceRequested(balanceAddressField.text)
}
Text {
id: balanceResultText
Layout.fillWidth: true
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
wrapMode: Text.WordWrap
}
}
}
// Transfer funds card
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: transferCol.height
color: Theme.palette.backgroundTertiary
radius: Theme.spacing.radiusLarge
border.color: Theme.palette.border
border.width: 1
ColumnLayout {
id: transferCol
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Theme.spacing.large
spacing: Theme.spacing.large
Text {
text: qsTr("Transfer funds")
font.pixelSize: Theme.typography.secondaryText
font.bold: true
color: Theme.palette.text
}
CustomTextFeild {
placeholderText: qsTr("From key (64 hex chars)")
}
CustomTextFeild {
id: transferToField
placeholderText: qsTr("To key (64 hex chars)")
}
CustomTextFeild {
placeholderText: qsTr("Amount")
}
LogosButton {
text: qsTr("Transfer")
Layout.alignment: Qt.AlignRight
onClicked: root.transferRequested(transferFromField.text, transferToField.text, transferAmountField.text)
}
Text {
id: transferResultText
Layout.fillWidth: true
font.pixelSize: Theme.typography.secondaryText
color: Theme.palette.textSecondary
wrapMode: Text.WordWrap
}
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: Theme.spacing.small
}
component CustomTextFeild: TextField {
id: textField
Layout.fillWidth: true
placeholderText: qsTr("From key (64 hex chars)")
font.pixelSize: Theme.typography.secondaryText
background: Rectangle {
radius: Theme.spacing.radiusSmall
color: Theme.palette.backgroundSecondary
border.color: textField.activeFocus ?
Theme.palette.overlayOrange :
Theme.palette.backgroundElevated
}
}
}

4
src/qml/views/qmldir Normal file
View File

@ -0,0 +1,4 @@
module views
StatusConfigView 1.0 StatusConfigView.qml
LogsView 1.0 LogsView.qml
WalletView 1.0 WalletView.qml