Merge pull request #14 from logos-blockchain/feat/portToUsingModuleBuilder

feat: use new module builder and new qml + cpp backend
This commit is contained in:
Khushboo-dev-cpp 2026-04-20 18:43:58 +02:00 committed by GitHub
commit 648bc85722
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 4660 additions and 2457 deletions

View File

@ -1,212 +1,27 @@
cmake_minimum_required(VERSION 3.16)
project(ExecutionZoneWalletUIPlugin VERSION 1.0.0 LANGUAGES CXX)
cmake_minimum_required(VERSION 3.14)
project(LEZWalletPlugin 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_EXECUTION_ZONE_WALLET_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_EXECUTION_ZONE_WALLET_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)
# 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_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces)
add_library(component-interfaces INTERFACE)
target_include_directories(component-interfaces INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/interfaces)
endif()
# Source files
set(SOURCES
src/LEZAccountFilterModel.cpp
src/LEZAccountFilterModel.h
src/LEZWalletAccountModel.cpp
src/LEZWalletAccountModel.h
src/LEZWalletPlugin.cpp
src/LEZWalletPlugin.h
src/LEZWalletBackend.cpp
src/LEZWalletBackend.h
src/wallet_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()
# Create the plugin library
add_library(logos_execution_zone_wallet_ui SHARED ${SOURCES})
# Set output name without lib prefix
set_target_properties(logos_execution_zone_wallet_ui PROPERTIES
PREFIX ""
OUTPUT_NAME "logos_execution_zone_wallet_ui"
)
# Include directories
target_include_directories(logos_execution_zone_wallet_ui PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_BINARY_DIR}
)
# Add include directories based on layout type
if(_liblogos_is_source)
target_include_directories(logos_execution_zone_wallet_ui PRIVATE ${LOGOS_LIBLOGOS_ROOT})
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
else()
target_include_directories(logos_execution_zone_wallet_ui PRIVATE ${LOGOS_LIBLOGOS_ROOT}/include)
message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.")
endif()
if(_cpp_sdk_is_source)
target_include_directories(logos_execution_zone_wallet_ui PRIVATE
${LOGOS_CPP_SDK_ROOT}/cpp
)
else()
target_include_directories(logos_execution_zone_wallet_ui PRIVATE
${LOGOS_CPP_SDK_ROOT}/include
${LOGOS_CPP_SDK_ROOT}/include/cpp
${LOGOS_CPP_SDK_ROOT}/include/core
)
endif()
# Link against libraries
target_link_libraries(logos_execution_zone_wallet_ui PRIVATE
Qt6::Core
Qt6::Widgets
Qt6::RemoteObjects
Qt6::Quick
Qt6::QuickWidgets
component-interfaces
logos_module(
NAME lez_wallet_ui
REP_FILE src/LEZWalletBackend.rep
SOURCES
src/LEZWalletPluginInterface.h
src/LEZWalletPlugin.h
src/LEZWalletPlugin.cpp
src/LEZWalletBackend.h
src/LEZWalletBackend.cpp
src/LEZWalletAccountModel.h
src/LEZWalletAccountModel.cpp
src/LEZAccountFilterModel.h
src/LEZAccountFilterModel.cpp
FIND_PACKAGES
Qt6Gui
LINK_LIBRARIES
Qt6::Gui
)
# When using installed SDK layout (e.g. Nix), link the pre-built SDK library
if(NOT _cpp_sdk_is_source)
find_library(LOGOS_SDK_LIB logos_sdk PATHS ${LOGOS_CPP_SDK_ROOT}/lib NO_DEFAULT_PATH)
if(LOGOS_SDK_LIB)
target_link_libraries(logos_execution_zone_wallet_ui PRIVATE ${LOGOS_SDK_LIB})
else()
message(FATAL_ERROR "logos_sdk library not found in ${LOGOS_CPP_SDK_ROOT}/lib - required when using installed SDK layout")
endif()
endif()
# Link against Abseil libraries if found
find_package(absl QUIET)
if(absl_FOUND)
target_link_libraries(logos_execution_zone_wallet_ui PRIVATE
absl::base
absl::strings
absl::log
absl::check
)
endif()
# Set common properties for both platforms
set_target_properties(logos_execution_zone_wallet_ui PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules"
BUILD_WITH_INSTALL_RPATH TRUE
SKIP_BUILD_RPATH FALSE)
if(APPLE)
set_target_properties(logos_execution_zone_wallet_ui PROPERTIES
INSTALL_RPATH "@loader_path"
INSTALL_NAME_DIR "@rpath"
BUILD_WITH_INSTALL_NAME_DIR TRUE)
add_custom_command(TARGET logos_execution_zone_wallet_ui POST_BUILD
COMMAND install_name_tool -id "@rpath/logos_execution_zone_wallet_ui.dylib" $<TARGET_FILE:logos_execution_zone_wallet_ui>
COMMENT "Updating library paths for macOS")
elseif(UNIX)
set_target_properties(logos_execution_zone_wallet_ui PROPERTIES
INSTALL_RPATH "$ORIGIN"
INSTALL_RPATH_USE_LINK_PATH FALSE)
endif()
# Windows: DLLs are found via PATH or same directory; no RPATH
install(TARGETS logos_execution_zone_wallet_ui
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/metadata.json
DESTINATION ${CMAKE_INSTALL_DATADIR}/logos-execution-zone-wallet-ui
)
message(STATUS "Execution Zone Wallet UI Plugin configured successfully")

124
README.md
View File

@ -1,65 +1,129 @@
# logos-execution-zone-wallet-ui
A Qt UI plugin for the Logos Execution Zone Wallet Module, providing a graphical interface to manage execution zone wallet accounts and transfers.
A QML + C++ backend UI module for the [Logos](https://logos.co) platform that provides a graphical interface to manage execution zone wallet accounts and transfers.
Built with [`logos-module-builder`](https://github.com/logos-co/logos-module-builder) using the `mkLogosQmlModule` pattern (QML frontend + C++ backend with Qt Remote Objects).
## Features
- Create and list public/private accounts
- View account balances
- Sync to block height
- Public and private transfers
- Public and private transfers (shielded, deshielded, private-owned)
- First-time onboarding (config path, storage path, password)
- Account key management
## Supported platforms
## Supported Platforms
- **Linux**: x86_64, aarch64
- **macOS**: aarch64 (Apple Silicon)
- **Windows**: x86_64 (Nix build depends on logos-liblogos and other inputs providing Windows packages)
## How to Build
## How to Run
### Using Nix (Recommended)
```bash
# Build plugin (default)
nix build
# Build standalone app
nix build '.#app'
# Development shell
nix develop
```
### Running the Standalone App
Build and run in one step:
### Standalone (recommended for development)
```bash
# Run directly
nix run
# With local workspace overrides
nix run --override-input lez_wallet_module path:../logos-execution-zone-module \
--override-input logos-module-builder path:../logos-module-builder
```
Or build first, then run:
The standalone app starts Logos Core, loads `capability_module` and the `lez_wallet_module` core plugin (flake input name must match `metadata.json` `dependencies`), then launches the QML UI via an isolated `ui-host` process.
### In Basecamp
```bash
nix build '.#app'
./result/bin/logos-execution-zone-wallet-ui-app
# Build LGX
nix build .#lgx
# Install into Basecamp's plugin directory
lgpm --ui-plugins-dir ~/Library/Application\ Support/Logos/LogosBasecampDev/plugins \
install --file result/*.lgx
```
## Nix Organization
Or from the workspace:
- `nix/default.nix` — Common configuration (dependencies, flags)
- `nix/lib.nix` — UI plugin compilation
- `nix/app.nix` — Standalone Qt application compilation
```bash
ws bundle logos-execution-zone-wallet-ui --auto-local
```
### Build Targets
```bash
nix build # default — combined plugin + QML output
nix build .#lgx # .lgx package for distribution
nix build .#install # lgpm-installed output (modules/ + plugins/)
nix run # standalone app with wallet module
nix develop # enter development shell
```
## Module Structure
```
logos-execution-zone-wallet-ui/
├── flake.nix # mkLogosQmlModule
├── metadata.json # Module config (ui_qml type)
├── CMakeLists.txt # logos_module() macro
└── src/
├── LEZWalletBackend.rep # RemoteObject interface
├── LEZWalletBackend.h/cpp # Business logic (extends LEZWalletBackendSimpleSource)
├── LEZWalletPlugin.h/cpp # Thin plugin entry point
├── LEZWalletPluginInterface.h # Plugin interface marker
├── LEZWalletAccountModel.h/cpp # QAbstractListModel for accounts
├── LEZAccountFilterModel.h/cpp # Proxy model for account filtering
└── qml/
└── ExecutionZoneWalletView.qml # QML frontend (+ sub-views)
```
## Configuration
Config path and storage path are persisted via QSettings (`Logos`, `ExecutionZoneWalletUI`). On first run, if opening the wallet fails, the onboarding screen is shown to create a new wallet.
### QML hot reload
### Resetting saved paths and onboarding
During development, set `DEV_QML_PATH` to your `src/qml` directory to load QML from disk and see changes without recompiling:
Saved config path, storage path, and related UI state come from **QSettings** (organization `Logos`, application `ExecutionZoneWalletUI`), in addition to whatever files live on disk under your chosen storage path.
To fully reset onboarding and drop old paths:
1. **Quit** the app (and any `ui-host` process if you use standalone).
2. **Remove wallet data** on disk if you no longer need it (your storage directory).
3. **Clear persisted settings** for this app so QSettings does not immediately repopulate the old paths. Where that store lives is **platform-specific** (Qt native format per OS).
**macOS** — preferences are under the domain `com.logos.ExecutionZoneWalletUI`. After quitting the app:
```bash
defaults delete com.logos.ExecutionZoneWalletUI 2>/dev/null
rm -f ~/Library/Preferences/com.logos.ExecutionZoneWalletUI.plist
killall cfprefsd
```
Restarting `cfprefsd` (it comes back automatically) avoids stale in-memory preference cache.
On **Linux** and **Windows**, use your platforms usual way to clear app settings (e.g. delete the Qt settings file under `~/.config` / registry / `%AppData%` for `Logos` / `ExecutionZoneWalletUI`, or an equivalent tool), following [QSettings](https://doc.qt.io/qt-6/qsettings.html#locations) locations for the native format on that OS.
### QML Hot Reload
During development, set `DEV_QML_PATH` to load QML from disk without recompiling:
```bash
export DEV_QML_PATH=/path/to/logos-execution-zone-wallet-ui/src/qml
```
## Dependencies
| Dependency | Purpose |
|---|---|
| Qt6 Core, RemoteObjects, Declarative | UI framework + IPC |
| [`logos-module-builder`](https://github.com/logos-co/logos-module-builder) | Build system (mkLogosQmlModule) |
| [`logos-execution-zone-module`](https://github.com/logos-blockchain/logos-execution-zone-module) | Wallet backend module |
## Related Repositories
| Repository | Role |
|---|---|
| [`logos-execution-zone-module`](https://github.com/logos-blockchain/logos-execution-zone-module) | Wallet backend — this UI's required dependency |
| [`logos-module-builder`](https://github.com/logos-co/logos-module-builder) | Module build system |
| [`logos-liblogos`](https://github.com/logos-co/logos-liblogos) | Logos Core platform |

View File

@ -1,56 +0,0 @@
cmake_minimum_required(VERSION 3.16)
project(LogosExecutionZoneWalletUIApp 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()
message(STATUS "Using logos-liblogos at: ${LOGOS_LIBLOGOS_ROOT}")
include_directories(
${LOGOS_LIBLOGOS_ROOT}/include
)
link_directories(
${LOGOS_LIBLOGOS_ROOT}/lib
)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_executable(logos-execution-zone-wallet-ui-app
main.cpp
mainwindow.cpp
mainwindow.h
)
target_link_libraries(logos-execution-zone-wallet-ui-app PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Widgets
logos_core
)
if(APPLE)
set_target_properties(logos-execution-zone-wallet-ui-app PROPERTIES
INSTALL_RPATH "@executable_path/../lib"
BUILD_WITH_INSTALL_RPATH TRUE
)
elseif(UNIX)
set_target_properties(logos-execution-zone-wallet-ui-app PROPERTIES
INSTALL_RPATH "$ORIGIN/../lib"
BUILD_WITH_INSTALL_RPATH TRUE
)
endif()
install(TARGETS logos-execution-zone-wallet-ui-app
RUNTIME DESTINATION bin
)

View File

@ -1,53 +0,0 @@
#include "mainwindow.h"
#include <QApplication>
#include <QDir>
#include <QDebug>
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_execution_zone_wallet_module")) {
qWarning() << "Failed to load execution zone wallet 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;
}

View File

@ -1,62 +0,0 @@
#include <QtWidgets>
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setupUi();
}
MainWindow::~MainWindow()
{
}
void MainWindow::setupUi()
{
QString pluginExtension;
#if defined(Q_OS_WIN)
pluginExtension = ".dll";
#elif defined(Q_OS_MAC)
pluginExtension = ".dylib";
#else
pluginExtension = ".so";
#endif
QString pluginPath = QCoreApplication::applicationDirPath() + "/../logos_execution_zone_wallet_ui" + pluginExtension;
QPluginLoader loader(pluginPath);
QWidget* walletWidget = nullptr;
if (loader.load()) {
QObject* plugin = loader.instance();
if (plugin) {
QMetaObject::invokeMethod(plugin, "createWidget",
Qt::DirectConnection,
Q_RETURN_ARG(QWidget*, walletWidget));
}
}
if (walletWidget) {
setCentralWidget(walletWidget);
} else {
qWarning() << "================================================";
qWarning() << "Failed to load execution zone wallet UI plugin from:" << pluginPath;
qWarning() << "Error:" << loader.errorString();
qWarning() << "================================================";
QWidget* fallbackWidget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(fallbackWidget);
QLabel* messageLabel = new QLabel("Execution Zone Wallet UI module not loaded", fallbackWidget);
QFont font = messageLabel->font();
font.setPointSize(14);
messageLabel->setFont(font);
messageLabel->setAlignment(Qt::AlignCenter);
layout->addWidget(messageLabel);
setCentralWidget(fallbackWidget);
}
setWindowTitle("Logos Execution Zone Wallet UI App");
resize(800, 600);
}

View File

@ -1,18 +0,0 @@
#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

5417
flake.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,90 +1,16 @@
{
description = "Logos Execution Zone Wallet UI - A Qt UI plugin for Logos Execution Zone Wallet Module";
description = "Logos Execution Zone Wallet UI - QML view + C++ backend module";
inputs = {
nixpkgs.follows = "logos-liblogos/nixpkgs";
logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk";
logos-liblogos.url = "github:logos-co/logos-liblogos";
logos-execution-zone-module.url = "github:logos-blockchain/logos-execution-zone-module";
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";
logos-module-builder.url = "github:logos-co/logos-module-builder";
nix-bundle-lgx.url = "github:logos-co/nix-bundle-lgx";
logos-package-manager.url = "github:logos-co/logos-package-manager-module";
lez_wallet_module.url = "github:logos-blockchain/logos-execution-zone-module";
};
outputs = { self, nixpkgs, logos-cpp-sdk, logos-liblogos, logos-execution-zone-module, logos-capability-module, logos-design-system, nix-bundle-lgx, logos-package-manager }:
let
systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" "x86_64-windows" ];
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;
logosExecutionZoneModule = logos-execution-zone-module.packages.${system}.default;
logosCapabilityModule = logos-capability-module.packages.${system}.default;
logosDesignSystem = logos-design-system.packages.${system}.default;
lgxBundler = nix-bundle-lgx.bundlers.${system}.default;
lgpm = logos-package-manager.packages.${system}.cli;
});
in
{
packages = forAllSystems ({ pkgs, logosSdk, logosLiblogos, logosExecutionZoneModule, logosCapabilityModule, logosDesignSystem, lgxBundler, lgpm }:
let
common = import ./nix/default.nix {
inherit pkgs logosSdk logosLiblogos;
};
src = ./.;
lib = import ./nix/lib.nix {
inherit pkgs common src logosExecutionZoneModule;
};
logosCapabilityModuleLgx = lgxBundler logosCapabilityModule;
logosExecutionZoneModuleLgx = lgxBundler logosExecutionZoneModule;
app = import ./nix/app.nix {
inherit pkgs common src logosLiblogos logosExecutionZoneModule logosCapabilityModule logosDesignSystem lgpm logosCapabilityModuleLgx logosExecutionZoneModuleLgx;
logosExecutionZoneWalletUI = lib;
};
in
{
logos-execution-zone-wallet-ui-lib = lib;
app = app;
lib = lib;
default = lib;
}
);
apps = nixpkgs.lib.genAttrs systems (system: {
default = {
type = "app";
program = "${self.packages.${system}.app}/bin/logos-execution-zone-wallet-ui-app";
};
});
devShells = forAllSystems ({ pkgs, logosSdk, logosLiblogos, logosExecutionZoneModule, logosCapabilityModule, logosDesignSystem, lgpm, ... }: {
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_LIBLOGOS_ROOT="${logosLiblogos}"
export LOGOS_DESIGN_SYSTEM_ROOT="${logosDesignSystem}"
echo "Logos Execution Zone Wallet UI development environment"
echo "LOGOS_LIBLOGOS_ROOT: $LOGOS_LIBLOGOS_ROOT"
'';
};
});
outputs = inputs@{ logos-module-builder, ... }:
logos-module-builder.lib.mkLogosQmlModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
};
}

View File

@ -1,17 +0,0 @@
#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)

View File

@ -1,22 +1,26 @@
{
"name": "logos_execution_zone_wallet_ui",
"name": "lez_wallet_ui",
"version": "1.0.0",
"type": "ui_qml",
"category": "wallet",
"description": "Execution Zone Wallet UI module for the Logos application",
"author": "Logos Blockchain Team",
"type": "ui",
"main": "logos_execution_zone_wallet_ui",
"icon": "",
"dependencies": ["liblogos_execution_zone_wallet_module"],
"category": "wallet",
"build": {
"type": "cmake",
"files": [
"src/LEZWalletPlugin.cpp",
"src/LEZWalletPlugin.h",
"src/LEZWalletBackend.cpp",
"src/LEZWalletBackend.h",
"src/wallet_resources.qrc"
]
},
"capabilities": ["ui_components", "wallet"]
"main": "lez_wallet_ui_plugin",
"icon": "src/qml/icons/copy.svg",
"view": "qml/ExecutionZoneWalletView.qml",
"dependencies": ["lez_wallet_module"],
"nix": {
"packages": {
"build": [],
"runtime": ["qt6.qtdeclarative", "zstd", "krb5", "abseil-cpp"]
},
"external_libraries": [],
"cmake": {
"find_packages": [],
"extra_sources": [],
"extra_include_dirs": [],
"extra_link_libraries": []
}
}
}

View File

@ -1,189 +0,0 @@
# Builds the logos-execution-zone-wallet-ui-app standalone application
{ pkgs, common, src, logosLiblogos, logosExecutionZoneModule, logosCapabilityModule, logosExecutionZoneWalletUI, logosDesignSystem, lgpm, logosCapabilityModuleLgx, logosExecutionZoneModuleLgx }:
pkgs.stdenv.mkDerivation rec {
pname = "logos-execution-zone-wallet-ui-app";
version = common.version;
inherit src;
inherit (common) buildInputs meta;
nativeBuildInputs = common.nativeBuildInputs ++ [ pkgs.patchelf pkgs.removeReferencesTo ];
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";
dontWrapQtApps = false;
dontStrip = true;
qtWrapperArgs = [
"--prefix" "LD_LIBRARY_PATH" ":" qtLibPath
"--prefix" "QT_PLUGIN_PATH" ":" qtPluginPath
"--prefix" "QML2_IMPORT_PATH" ":" qmlImportPath
];
preConfigure = ''
runHook prePreConfigure
export MACOSX_DEPLOYMENT_TARGET=12.0
runHook postPreConfigure
'';
preFixup = ''
runHook prePreFixup
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/lib/qt-6/plugins"
export QML_IMPORT_PATH="${pkgs.qt6.qtbase}/lib/qt-6/qml"
find $out -type f -executable -exec sh -c '
if file "$1" | grep -q "ELF.*executable"; then
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
if echo "$1" | grep -q "/logos-execution-zone-wallet-ui-app$"; then
echo "Setting RPATH for $1"
patchelf --set-rpath "$out/lib" "$1" 2>/dev/null || true
fi
fi
' _ {} \;
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-execution-zone-wallet-ui-app..."
test -d "${logosLiblogos}" || (echo "liblogos not found" && exit 1)
test -d "${logosExecutionZoneModule}" || (echo "execution-zone-module not found" && exit 1)
test -d "${logosCapabilityModule}" || (echo "capability-module not found" && exit 1)
test -d "${logosExecutionZoneWalletUI}" || (echo "execution-zone-wallet-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}
runHook postConfigure
'';
buildPhase = ''
runHook preBuild
cmake --build build
echo "logos-execution-zone-wallet-ui-app built successfully!"
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/bin $out/lib $out/modules
if [ -f "build/bin/logos-execution-zone-wallet-ui-app" ]; then
cp build/bin/logos-execution-zone-wallet-ui-app "$out/bin/"
echo "Installed logos-execution-zone-wallet-ui-app binary"
fi
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
if ls "${logosLiblogos}/lib/"liblogos_core.* >/dev/null 2>&1; then
cp -L "${logosLiblogos}/lib/"liblogos_core.* "$out/lib/" || true
fi
OS_EXT="so"
case "$(uname -s)" in
Darwin) OS_EXT="dylib";;
Linux) OS_EXT="so";;
MINGW*|MSYS*|CYGWIN*) OS_EXT="dll";;
esac
for lgxFile in ${logosCapabilityModuleLgx}/*.lgx; do
echo "Installing $lgxFile via lgpm..."
${lgpm}/bin/lgpm --modules-dir "$out/modules" install --file "$lgxFile"
done
for lgxFile in ${logosExecutionZoneModuleLgx}/*.lgx; do
echo "Installing $lgxFile via lgpm..."
${lgpm}/bin/lgpm --modules-dir "$out/modules" install --file "$lgxFile"
done
if [ -f "${logosExecutionZoneWalletUI}/lib/logos_execution_zone_wallet_ui.$OS_EXT" ]; then
cp -L "${logosExecutionZoneWalletUI}/lib/logos_execution_zone_wallet_ui.$OS_EXT" "$out/"
fi
if [ -d "${logosDesignSystem}/lib/Logos/Theme" ]; then
mkdir -p "$out/lib/Logos"
cp -R "${logosDesignSystem}/lib/Logos/Theme" "$out/lib/Logos/"
echo "Copied Logos.Theme to lib/Logos/Theme/"
fi
if [ -d "${logosDesignSystem}/lib/Logos/Controls" ]; then
mkdir -p "$out/lib/Logos"
cp -R "${logosDesignSystem}/lib/Logos/Controls" "$out/lib/Logos/"
echo "Copied Logos.Controls to lib/Logos/Controls/"
fi
cat > $out/README.txt <<EOF
Logos Execution Zone Wallet UI App
==================================
liblogos: ${logosLiblogos}
execution-zone-module: ${logosExecutionZoneModule}
capability-module: ${logosCapabilityModule}
execution-zone-wallet-ui: ${logosExecutionZoneWalletUI}
design-system: ${logosDesignSystem}
Layout:
bin/logos-execution-zone-wallet-ui-app
lib/
modules/
logos_execution_zone_wallet_ui.$OS_EXT
EOF
runHook postInstall
'';
}

View File

@ -1,37 +0,0 @@
# Common build configuration shared across all packages
{ pkgs, logosSdk, logosLiblogos }:
{
pname = "logos-execution-zone-wallet-ui";
version = "1.0.0";
nativeBuildInputs = [
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
pkgs.qt6.wrapQtAppsHook
];
buildInputs = [
pkgs.qt6.qtbase
pkgs.qt6.qtremoteobjects
pkgs.zstd
pkgs.krb5
pkgs.abseil-cpp
];
cmakeFlags = [
"-GNinja"
"-DLOGOS_CPP_SDK_ROOT=${logosSdk}"
"-DLOGOS_LIBLOGOS_ROOT=${logosLiblogos}"
];
env = {
LOGOS_LIBLOGOS_ROOT = "${logosLiblogos}";
};
meta = with pkgs.lib; {
description = "Logos Execution Zone Wallet UI - A Qt UI plugin for Logos Execution Zone Wallet Module";
platforms = platforms.unix ++ platforms.windows;
};
}

View File

@ -1,47 +0,0 @@
# Builds the logos-execution-zone-wallet-ui library
{ pkgs, common, src, logosExecutionZoneModule }:
pkgs.stdenv.mkDerivation {
pname = "${common.pname}-lib";
version = common.version;
inherit src;
inherit (common) buildInputs cmakeFlags meta env;
nativeBuildInputs = common.nativeBuildInputs;
dontWrapQtApps = true;
configurePhase = ''
runHook preConfigure
cmake -S . -B build \
-GNinja \
-DCMAKE_BUILD_TYPE=Release \
''${cmakeFlags}
runHook postConfigure
'';
buildPhase = ''
runHook preBuild
cmake --build build
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/lib
if [ -f build/modules/logos_execution_zone_wallet_ui.dylib ]; then
cp build/modules/logos_execution_zone_wallet_ui.dylib $out/lib/
elif [ -f build/modules/logos_execution_zone_wallet_ui.so ]; then
cp build/modules/logos_execution_zone_wallet_ui.so $out/lib/
elif [ -f build/modules/logos_execution_zone_wallet_ui.dll ]; then
cp build/modules/logos_execution_zone_wallet_ui.dll $out/lib/
else
echo "Error: No library file found in build/modules/"
ls -la build/modules/ 2>/dev/null || true
exit 1
fi
runHook postInstall
'';
}

View File

@ -1,21 +1,27 @@
#include "LEZWalletBackend.h"
#include <QAbstractItemModel>
#include <QClipboard>
#include <QCoreApplication>
#include <QDebug>
#include <QGuiApplication>
#include <QJsonArray>
#include <QSettings>
#include <QTimer>
#include <QUrl>
#include "logos_api.h"
#include "logos_sdk.h"
namespace {
const char SETTINGS_ORG[] = "Logos";
const char SETTINGS_APP[] = "ExecutionZoneWalletUI";
const char CONFIG_PATH_KEY[] = "configPath";
const char STORAGE_PATH_KEY[] = "storagePath";
const QString WALLET_MODULE_NAME = QStringLiteral("liblogos_execution_zone_wallet_module");
const int WALLET_FFI_SUCCESS = 0;
// Convert decimal amount string to 32-char hex (16 bytes little-endian) for transfer_public/transfer_private.
// Convert a decimal amount string to 32-char hex (16 bytes little-endian)
// for transfer_public/transfer_private/transfer_private_owned.
QString amountToLe16Hex(const QString& amountStr) {
const QString trimmed = amountStr.trimmed();
if (trimmed.isEmpty()) return QString();
@ -27,119 +33,102 @@ namespace {
bytes[i] = static_cast<uint8_t>((value >> (i * 8)) & 0xff);
return QByteArray(reinterpret_cast<const char*>(bytes), 16).toHex();
}
// Normalise file:// URLs and OS paths to a plain local path.
QString toLocalPath(const QString& path) {
if (path.startsWith("file://") || path.contains("/"))
return QUrl::fromUserInput(path).toLocalFile();
return path;
}
}
LEZWalletBackend::LEZWalletBackend(LogosAPI* logosAPI, QObject* parent)
: QObject(parent),
m_isWalletOpen(false),
m_lastSyncedBlock(0),
m_currentBlockHeight(0),
: LEZWalletBackendSimpleSource(parent),
m_accountModel(new LEZWalletAccountModel(this)),
m_filteredAccountModel(new LEZAccountFilterModel(this)),
m_logosAPI(nullptr),
m_walletClient(nullptr)
m_logosAPI(logosAPI ? logosAPI : new LogosAPI("lez_wallet_ui", this)),
m_logos(new LogosModules(m_logosAPI))
{
m_filteredAccountModel->setSourceModel(m_accountModel);
// Initialise PROP defaults via the generated setters.
setIsWalletOpen(false);
setLastSyncedBlock(0);
setCurrentBlockHeight(0);
// Load persisted config/storage paths.
QSettings s(SETTINGS_ORG, SETTINGS_APP);
m_configPath = s.value(CONFIG_PATH_KEY).toString();
m_storagePath = s.value(STORAGE_PATH_KEY).toString();
setConfigPath(s.value(CONFIG_PATH_KEY).toString());
setStoragePath(s.value(STORAGE_PATH_KEY).toString());
if (!logosAPI) {
logosAPI = new LogosAPI("logos_execution_zone_wallet_ui", this);
}
m_logosAPI = logosAPI;
m_walletClient = m_logosAPI->getClient(WALLET_MODULE_NAME);
// ui-host runs our constructor inside initLogos(), synchronously, BEFORE
// it enables remoting and emits READY. Any blocking RPC here (open,
// list_accounts, block-height queries, sequencer lookup) would stall
// ui-host startup past ViewModuleHost's 30s ready watchdog and get the
// child SIGTERM'd. Defer the whole open+refresh chain to the first
// event-loop tick so ui-host finishes wiring itself up first.
QTimer::singleShot(0, this, [this]() { openIfPathsConfigured(); });
if (!m_walletClient) {
qWarning() << "LEZWalletBackend: could not get client for" << WALLET_MODULE_NAME;
return;
}
if (!m_configPath.isEmpty() && !m_storagePath.isEmpty()) {
qDebug() << "LEZWalletBackend: opening wallet with config path" << m_configPath << "and storage path" << m_storagePath;
QVariant result = m_walletClient->invokeRemoteMethod(
WALLET_MODULE_NAME, "open", m_configPath, m_storagePath);
int err = result.isValid() ? result.toInt() : -1;
if (err == WALLET_FFI_SUCCESS) {
qWarning() << "LEZWalletBackend: wallet opened successfully";
setWalletOpen(true);
refreshAccounts();
refreshBlockHeights();
refreshSequencerAddr();
}
}
// Save wallet when app quits; host may not call destroyWidget() so destructor might not run.
connect(qApp, &QCoreApplication::aboutToQuit, this, [this]() { saveWallet(); }, Qt::DirectConnection);
// Save wallet when app quits; host may not call destructors so this is best-effort.
connect(qApp, &QCoreApplication::aboutToQuit, this,
[this]() { saveWallet(); }, Qt::DirectConnection);
}
LEZWalletBackend::~LEZWalletBackend()
{
saveWallet();
delete m_logos;
}
void LEZWalletBackend::saveWallet()
{
if (m_walletClient && m_isWalletOpen) {
m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "save");
if (isWalletOpen()) {
m_logos->lez_wallet_module.save();
}
}
void LEZWalletBackend::setWalletOpen(bool open)
void LEZWalletBackend::persistConfigPath(const QString& path)
{
if (m_isWalletOpen != open) {
m_isWalletOpen = open;
emit isWalletOpenChanged();
}
const QString localPath = toLocalPath(path);
setConfigPath(localPath);
QSettings(SETTINGS_ORG, SETTINGS_APP).setValue(CONFIG_PATH_KEY, localPath);
}
void LEZWalletBackend::setConfigPath(const QString& path)
void LEZWalletBackend::persistStoragePath(const QString& path)
{
QString localPath = path;
if (path.startsWith("file://") || path.contains("/")) {
localPath = QUrl::fromUserInput(path).toLocalFile();
}
if (m_configPath != localPath) {
m_configPath = localPath;
QSettings s(SETTINGS_ORG, SETTINGS_APP);
s.setValue(CONFIG_PATH_KEY, m_configPath);
emit configPathChanged();
}
const QString localPath = toLocalPath(path);
setStoragePath(localPath);
QSettings(SETTINGS_ORG, SETTINGS_APP).setValue(STORAGE_PATH_KEY, localPath);
}
void LEZWalletBackend::setStoragePath(const QString& path)
void LEZWalletBackend::openIfPathsConfigured()
{
QString localPath = path;
if (path.startsWith("file://") || path.contains("/")) {
localPath = QUrl::fromUserInput(path).toLocalFile();
}
if (m_storagePath != localPath) {
m_storagePath = localPath;
QSettings s(SETTINGS_ORG, SETTINGS_APP);
s.setValue(STORAGE_PATH_KEY, m_storagePath);
emit storagePathChanged();
if (configPath().isEmpty() || storagePath().isEmpty()) return;
qDebug() << "LEZWalletBackend: opening wallet with config" << configPath()
<< "storage" << storagePath();
int err = m_logos->lez_wallet_module.open(configPath(), storagePath());
if (err == WALLET_FFI_SUCCESS) {
qDebug() << "LEZWalletBackend: wallet opened successfully";
setIsWalletOpen(true);
refreshAccounts();
refreshBlockHeights();
refreshSequencerAddr();
}
}
void LEZWalletBackend::refreshAccounts()
{
if (!m_walletClient) return;
QVariant result = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "list_accounts");
QJsonArray arr;
if (result.isValid() && result.canConvert<QJsonArray>()) {
arr = result.toJsonArray();
}
QJsonArray arr = m_logos->lez_wallet_module.list_accounts();
m_accountModel->replaceFromJsonArray(arr);
emit accountModelChanged();
refreshBalances();
}
void LEZWalletBackend::refreshBalances()
{
refreshBlockHeights();
syncToBlock(m_currentBlockHeight);
if (!m_walletClient || !m_accountModel) return;
syncToBlock(currentBlockHeight());
if (!m_accountModel) return;
for (int i = 0; i < m_accountModel->count(); ++i) {
const QModelIndex idx = m_accountModel->index(i, 0);
const QString addr = m_accountModel->data(idx, LEZWalletAccountModel::AddressRole).toString();
@ -151,178 +140,116 @@ void LEZWalletBackend::refreshBalances()
void LEZWalletBackend::fetchAndUpdateBlockHeights()
{
if (!m_walletClient) return;
const int lastVal = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "get_last_synced_block").toInt();
const int currentVal = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "get_current_block_height").toInt();
if (m_lastSyncedBlock != lastVal) {
m_lastSyncedBlock = lastVal;
emit lastSyncedBlockChanged();
}
if (m_currentBlockHeight != currentVal) {
m_currentBlockHeight = currentVal;
emit currentBlockHeightChanged();
}
const int lastVal = m_logos->lez_wallet_module.get_last_synced_block();
const int currentVal = m_logos->lez_wallet_module.get_current_block_height();
if (lastSyncedBlock() != lastVal)
setLastSyncedBlock(lastVal);
if (currentBlockHeight() != currentVal)
setCurrentBlockHeight(currentVal);
}
void LEZWalletBackend::refreshBlockHeights()
{
fetchAndUpdateBlockHeights();
if (m_currentBlockHeight > 0 && m_lastSyncedBlock < m_currentBlockHeight && syncToBlock(m_currentBlockHeight))
if (currentBlockHeight() > 0
&& lastSyncedBlock() < currentBlockHeight()
&& syncToBlock(currentBlockHeight()))
{
fetchAndUpdateBlockHeights();
}
}
void LEZWalletBackend::refreshSequencerAddr()
{
if (!m_walletClient) return;
QVariant result = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "get_sequencer_addr");
QString addr = result.isValid() ? result.toString() : QString();
if (m_sequencerAddr != addr) {
m_sequencerAddr = std::move(addr);
emit sequencerAddrChanged();
}
const QString addr = m_logos->lez_wallet_module.get_sequencer_addr();
if (sequencerAddr() != addr)
setSequencerAddr(addr);
}
QString LEZWalletBackend::createAccountPublic()
{
if (!m_walletClient) return QString();
QVariant result = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "create_account_public");
if (result.isValid()) {
QString result = m_logos->lez_wallet_module.create_account_public();
if (!result.isEmpty())
refreshAccounts();
return result.toString();
}
return QString();
return result;
}
QString LEZWalletBackend::createAccountPrivate()
{
if (!m_walletClient) return QString();
QVariant result = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "create_account_private");
if (result.isValid()) {
QString result = m_logos->lez_wallet_module.create_account_private();
if (!result.isEmpty())
refreshAccounts();
return result.toString();
}
return QString();
return result;
}
QString LEZWalletBackend::getBalance(const QString& accountIdHex, bool isPublic)
QString LEZWalletBackend::getBalance(QString accountIdHex, bool isPublic)
{
if (!m_walletClient) return QStringLiteral("Error: Module not initialized.");
QVariant result = m_walletClient->invokeRemoteMethod(
WALLET_MODULE_NAME, "get_balance", accountIdHex, isPublic);
return result.isValid() ? result.toString() : QStringLiteral("Error: Call failed.");
return m_logos->lez_wallet_module.get_balance(accountIdHex, isPublic);
}
QString LEZWalletBackend::getPublicAccountKey(const QString& accountIdHex)
QString LEZWalletBackend::getPublicAccountKey(QString accountIdHex)
{
if (!m_walletClient) return QString();
QVariant result = m_walletClient->invokeRemoteMethod(
WALLET_MODULE_NAME, "get_public_account_key", accountIdHex);
return result.isValid() ? result.toString() : QString();
return m_logos->lez_wallet_module.get_public_account_key(accountIdHex);
}
QString LEZWalletBackend::getPrivateAccountKeys(const QString& accountIdHex)
QString LEZWalletBackend::getPrivateAccountKeys(QString accountIdHex)
{
if (!m_walletClient) return QString();
QVariant result = m_walletClient->invokeRemoteMethod(
WALLET_MODULE_NAME, "get_private_account_keys", accountIdHex);
return result.isValid() ? result.toString() : QString();
return m_logos->lez_wallet_module.get_private_account_keys(accountIdHex);
}
bool LEZWalletBackend::syncToBlock(quint64 blockId)
{
if (!m_walletClient) return false;
QVariant result = m_walletClient->invokeRemoteMethod(
WALLET_MODULE_NAME, "sync_to_block", blockId);
int err = result.isValid() ? result.toInt() : -1;
int err = m_logos->lez_wallet_module.sync_to_block(blockId);
return err == WALLET_FFI_SUCCESS;
}
QString LEZWalletBackend::transferPublic(
const QString& fromHex,
const QString& toHex,
const QString& amountLe16Hex)
QString LEZWalletBackend::transferPublic(QString fromHex, QString toHex, QString amountStr)
{
if (!m_walletClient) return QStringLiteral("Error: Module not initialized.");
const QString amountHex = amountToLe16Hex(amountLe16Hex);
const QString amountHex = amountToLe16Hex(amountStr);
if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount.");
QVariant result = m_walletClient->invokeRemoteMethod(
WALLET_MODULE_NAME, "transfer_public", fromHex, toHex, amountHex);
return result.isValid() ? result.toString() : QStringLiteral("Error: Call failed.");
return m_logos->lez_wallet_module.transfer_public(fromHex, toHex, amountHex);
}
QString LEZWalletBackend::transferPrivate(
const QString& fromHex,
const QString& toHex,
const QString& amountLe16Hex)
QString LEZWalletBackend::transferPrivate(QString fromHex, QString toHex, QString amountStr)
{
if (!m_walletClient) return QStringLiteral("Error: Module not initialized.");
const QString amountHex = amountToLe16Hex(amountLe16Hex);
const QString amountHex = amountToLe16Hex(amountStr);
if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount.");
QString keysPayload = toHex.trimmed();
// If "To" is not JSON (e.g. user pasted account id hex), resolve to keys via get_private_account_keys.
// If "To" is not JSON (e.g. user pasted account id hex), resolve to keys.
if (!keysPayload.startsWith(QLatin1Char('{'))) {
qDebug() << "LEZWalletBackend::transferPrivate: keysPayload is not JSON, resolving to keys via get_private_account_keys";
qDebug() << "LEZWalletBackend::transferPrivate: resolving keys via get_private_account_keys";
const QString resolved = getPrivateAccountKeys(keysPayload);
if (!resolved.isEmpty())
keysPayload = resolved;
}
QVariant result = m_walletClient->invokeRemoteMethod(
WALLET_MODULE_NAME, "transfer_private", fromHex, keysPayload, amountHex, Timeout(6*60*1000)); // 6 minutes timeout
return result.isValid() ? result.toString() : QStringLiteral("Error: Call failed.");
return m_logos->lez_wallet_module.transfer_private(fromHex, keysPayload, amountHex);
}
QString LEZWalletBackend::transferPrivateOwned(
const QString& fromHex,
const QString& toHex,
const QString& amountLe16Hex)
QString LEZWalletBackend::transferPrivateOwned(QString fromHex, QString toHex, QString amountStr)
{
if (!m_walletClient) return QStringLiteral("Error: Module not initialized.");
const QString amountHex = amountToLe16Hex(amountLe16Hex);
const QString amountHex = amountToLe16Hex(amountStr);
if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount.");
QVariant result = m_walletClient->invokeRemoteMethod(
WALLET_MODULE_NAME, "transfer_private_owned", fromHex, toHex.trimmed(), amountHex);
return result.isValid() ? result.toString() : QStringLiteral("Error: Call failed.");
return m_logos->lez_wallet_module.transfer_private_owned(fromHex, toHex.trimmed(), amountHex);
}
bool LEZWalletBackend::createNew(
const QString& configPath,
const QString& storagePath,
const QString& password)
bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QString password)
{
const QString localPath = QUrl::fromUserInput(configPath).toLocalFile();
if (!m_walletClient) return false;
QVariant result = m_walletClient->invokeRemoteMethod(
WALLET_MODULE_NAME, "create_new", localPath, storagePath, password);
int err = result.isValid() ? result.toInt() : -1;
const QString localPath = toLocalPath(configPath);
int err = m_logos->lez_wallet_module.create_new(localPath, storagePath, password);
if (err != WALLET_FFI_SUCCESS) return false;
setConfigPath(localPath);
setStoragePath(storagePath);
setWalletOpen(true);
persistConfigPath(localPath);
persistStoragePath(storagePath);
setIsWalletOpen(true);
refreshAccounts();
refreshBlockHeights();
refreshSequencerAddr();
return true;
}
int LEZWalletBackend::indexOfAddressInModel(QObject* model, const QString& address) const
{
auto* m = qobject_cast<QAbstractItemModel*>(model);
if (!m || address.isEmpty())
return -1;
const int role = m->roleNames().key("address", -1);
if (role < 0)
return -1;
for (int i = 0; i < m->rowCount(); ++i) {
if (m->data(m->index(i, 0), role).toString() == address)
return i;
}
return -1;
}
void LEZWalletBackend::copyToClipboard(const QString& text)
void LEZWalletBackend::copyToClipboard(QString text)
{
if (QGuiApplication::clipboard())
QGuiApplication::clipboard()->setText(text);

View File

@ -1,95 +1,62 @@
#pragma once
#ifndef LEZ_WALLET_BACKEND_H
#define LEZ_WALLET_BACKEND_H
#include <QObject>
#include <QString>
#include "rep_LEZWalletBackend_source.h"
#include "LEZAccountFilterModel.h"
#include "LEZWalletAccountModel.h"
#include "logos_api.h"
#include "logos_api_client.h"
class QAbstractItemModel;
class LogosAPI;
struct LogosModules;
class LEZWalletBackend : public QObject {
// Source-side implementation of the LEZWalletBackend .rep interface.
// Inheriting from LEZWalletBackendSimpleSource gives us the generated PROPs
// and SLOTs from LEZWalletBackend.rep — all the simple ones flow over QtRO.
class LEZWalletBackend : public LEZWalletBackendSimpleSource {
Q_OBJECT
Q_PROPERTY(LEZWalletAccountModel* accountModel READ accountModel CONSTANT)
Q_PROPERTY(LEZAccountFilterModel* filteredAccountModel READ filteredAccountModel CONSTANT)
public:
Q_PROPERTY(bool isWalletOpen READ isWalletOpen NOTIFY isWalletOpenChanged)
Q_PROPERTY(QString configPath READ configPath WRITE setConfigPath NOTIFY configPathChanged)
Q_PROPERTY(QString storagePath READ storagePath WRITE setStoragePath NOTIFY storagePathChanged)
Q_PROPERTY(LEZWalletAccountModel* accountModel READ accountModel NOTIFY accountModelChanged)
Q_PROPERTY(LEZAccountFilterModel* filteredAccountModel READ filteredAccountModel NOTIFY filteredAccountModelChanged)
Q_PROPERTY(int lastSyncedBlock READ lastSyncedBlock NOTIFY lastSyncedBlockChanged)
Q_PROPERTY(int currentBlockHeight READ currentBlockHeight NOTIFY currentBlockHeightChanged)
Q_PROPERTY(QString sequencerAddr READ sequencerAddr NOTIFY sequencerAddrChanged)
explicit LEZWalletBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
~LEZWalletBackend();
~LEZWalletBackend() override;
bool isWalletOpen() const { return m_isWalletOpen; }
QString configPath() const { return m_configPath; }
QString storagePath() const { return m_storagePath; }
LEZWalletAccountModel* accountModel() const { return m_accountModel; }
LEZAccountFilterModel* filteredAccountModel() const { return m_filteredAccountModel; }
int lastSyncedBlock() const { return m_lastSyncedBlock; }
int currentBlockHeight() const { return m_currentBlockHeight; }
QString sequencerAddr() const { return m_sequencerAddr; }
void setConfigPath(const QString& path);
void setStoragePath(const QString& path);
Q_INVOKABLE QString createAccountPublic();
Q_INVOKABLE QString createAccountPrivate();
Q_INVOKABLE void refreshAccounts();
Q_INVOKABLE QString getBalance(const QString& accountIdHex, bool isPublic);
Q_INVOKABLE void refreshBalances();
Q_INVOKABLE QString getPublicAccountKey(const QString& accountIdHex);
Q_INVOKABLE QString getPrivateAccountKeys(const QString& accountIdHex);
Q_INVOKABLE bool syncToBlock(quint64 blockId);
Q_INVOKABLE QString transferPublic(
const QString& fromHex,
const QString& toHex,
const QString& amountLe16Hex);
Q_INVOKABLE QString transferPrivate(
const QString& fromHex,
const QString& toHex,
const QString& amountLe16Hex);
Q_INVOKABLE QString transferPrivateOwned(
const QString& fromHex,
const QString& toHex,
const QString& amountLe16Hex);
Q_INVOKABLE bool createNew(
const QString& configPath,
const QString& storagePath,
const QString& password);
Q_INVOKABLE int indexOfAddressInModel(QObject* model, const QString& address) const;
Q_INVOKABLE void copyToClipboard(const QString& text);
signals:
void isWalletOpenChanged();
void configPathChanged();
void storagePathChanged();
void accountModelChanged();
void filteredAccountModelChanged();
void lastSyncedBlockChanged();
void currentBlockHeightChanged();
void sequencerAddrChanged();
public slots:
// Overrides of the pure-virtual slots generated from the .rep.
QString createAccountPublic() override;
QString createAccountPrivate() override;
void refreshAccounts() override;
QString getBalance(QString accountIdHex, bool isPublic) override;
void refreshBalances() override;
QString getPublicAccountKey(QString accountIdHex) override;
QString getPrivateAccountKeys(QString accountIdHex) override;
bool syncToBlock(quint64 blockId) override;
QString transferPublic(QString fromHex, QString toHex, QString amountStr) override;
QString transferPrivate(QString fromHex, QString toHex, QString amountStr) override;
QString transferPrivateOwned(QString fromHex, QString toHex, QString amountStr) override;
bool createNew(QString configPath, QString storagePath, QString password) override;
void copyToClipboard(QString text) override;
private:
void setWalletOpen(bool open);
void persistConfigPath(const QString& path);
void persistStoragePath(const QString& path);
void refreshBlockHeights();
void refreshSequencerAddr();
void saveWallet();
void fetchAndUpdateBlockHeights();
void openIfPathsConfigured();
bool m_isWalletOpen;
QString m_configPath;
QString m_storagePath;
LEZWalletAccountModel* m_accountModel;
LEZAccountFilterModel* m_filteredAccountModel;
int m_lastSyncedBlock;
int m_currentBlockHeight;
QString m_sequencerAddr;
LogosAPI* m_logosAPI;
LogosAPIClient* m_walletClient;
LogosModules* m_logos;
};
#endif // LEZ_WALLET_BACKEND_H

26
src/LEZWalletBackend.rep Normal file
View File

@ -0,0 +1,26 @@
class LEZWalletBackend
{
PROP(bool isWalletOpen READONLY)
PROP(QString configPath READONLY)
PROP(QString storagePath READONLY)
PROP(int lastSyncedBlock READONLY)
PROP(int currentBlockHeight READONLY)
PROP(QString sequencerAddr READONLY)
SLOT(QString createAccountPublic())
SLOT(QString createAccountPrivate())
SLOT(void refreshAccounts())
SLOT(QString getBalance(QString accountIdHex, bool isPublic))
SLOT(void refreshBalances())
SLOT(QString getPublicAccountKey(QString accountIdHex))
SLOT(QString getPrivateAccountKeys(QString accountIdHex))
SLOT(bool syncToBlock(quint64 blockId))
SLOT(QString transferPublic(QString fromHex, QString toHex, QString amountStr))
SLOT(QString transferPrivate(QString fromHex, QString toHex, QString amountStr))
SLOT(QString transferPrivateOwned(QString fromHex, QString toHex, QString amountStr))
SLOT(bool createNew(QString configPath, QString storagePath, QString password))
SLOT(void copyToClipboard(QString text))
}

View File

@ -1,54 +1,19 @@
#include "LEZWalletPlugin.h"
#include "LEZWalletBackend.h"
#include "LEZAccountFilterModel.h"
#include <QQuickWidget>
#include <QQmlContext>
#include <QQmlEngine>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QUrl>
QWidget* LEZWalletPlugin::createWidget(LogosAPI* logosAPI) {
qDebug() << "LEZWalletPlugin::createWidget called";
QQuickWidget* quickWidget = new QQuickWidget();
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
qmlRegisterType<LEZWalletBackend>("LEZWalletBackend", 1, 0, "LEZWalletBackend");
qmlRegisterType<LEZAccountFilterModel>("LEZWalletBackend", 1, 0, "LEZAccountFilterModel");
LEZWalletBackend* backend = new LEZWalletBackend(logosAPI, quickWidget);
quickWidget->rootContext()->setContextProperty("backend", backend);
QString qmlSource = "qrc:/lezwallet/qml/ExecutionZoneWalletView.qml";
QString importPath = "qrc:/lezwallet/qml";
QString envPath = QString::fromUtf8(qgetenv("DEV_QML_PATH")).trimmed();
if (!envPath.isEmpty()) {
QFileInfo info(envPath);
if (info.isDir()) {
QString main = QDir(info.absoluteFilePath()).absoluteFilePath("ExecutionZoneWalletView.qml");
if (QFile::exists(main)) {
importPath = info.absoluteFilePath();
qmlSource = QUrl::fromLocalFile(main).toString();
} else {
qWarning() << "DEV_QML_PATH: ExecutionZoneWalletView.qml not found in" << info.absoluteFilePath();
}
}
}
quickWidget->engine()->addImportPath(importPath);
quickWidget->setSource(QUrl(qmlSource));
if (quickWidget->status() == QQuickWidget::Error) {
qWarning() << "LEZWalletPlugin: Failed to load QML:" << quickWidget->errors();
}
return quickWidget;
LEZWalletPlugin::LEZWalletPlugin(QObject* parent)
: QObject(parent)
{
}
void LEZWalletPlugin::destroyWidget(QWidget* widget) {
delete widget;
LEZWalletPlugin::~LEZWalletPlugin() = default;
void LEZWalletPlugin::initLogos(LogosAPI* api)
{
if (m_backend) return;
m_backend = new LEZWalletBackend(api, this);
setBackend(m_backend);
qDebug() << "LEZWalletPlugin: backend initialized";
}

View File

@ -1,14 +1,38 @@
#pragma once
#ifndef LEZ_WALLET_PLUGIN_H
#define LEZ_WALLET_PLUGIN_H
#include <IComponent.h>
#include <QObject>
#include <QString>
#include <QtPlugin> // for Q_PLUGIN_METADATA, Q_INTERFACES
#include "LEZWalletPluginInterface.h"
#include "LogosViewPluginBase.h"
class LEZWalletPlugin : public QObject, public IComponent {
class LogosAPI;
class LEZWalletBackend;
// Thin plugin entry point. Holds an LEZWalletBackend and lets the
// generated view-plugin base expose it to ui-host.
class LEZWalletPlugin : public QObject,
public LEZWalletPluginInterface,
public LEZWalletBackendViewPluginBase
{
Q_OBJECT
Q_INTERFACES(IComponent)
Q_PLUGIN_METADATA(IID IComponent_iid FILE "metadata.json")
Q_PLUGIN_METADATA(IID LEZWalletPluginInterface_iid FILE "../metadata.json")
Q_INTERFACES(LEZWalletPluginInterface)
public:
Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr) override;
void destroyWidget(QWidget* widget) override;
explicit LEZWalletPlugin(QObject* parent = nullptr);
~LEZWalletPlugin() override;
QString name() const override { return "lez_wallet_ui"; }
QString version() const override { return "1.0.0"; }
// Called by ui-host after plugin load. Creates the backend and wires
// it up with the provided LogosAPI.
Q_INVOKABLE void initLogos(LogosAPI* api);
private:
LEZWalletBackend* m_backend = nullptr;
};
#endif // LEZ_WALLET_PLUGIN_H

View File

@ -0,0 +1,19 @@
#ifndef LEZ_WALLET_PLUGIN_INTERFACE_H
#define LEZ_WALLET_PLUGIN_INTERFACE_H
#include <QtPlugin> // for Q_DECLARE_INTERFACE
#include "interface.h"
// Marker interface used by Qt's plugin loader to identify the LEZ wallet UI
// plugin. The actual API surface (Q_INVOKABLE methods, properties, signals)
// lives in LEZWalletBackend.rep — this header only carries the IID.
class LEZWalletPluginInterface : public PluginInterface
{
public:
virtual ~LEZWalletPluginInterface() = default;
};
#define LEZWalletPluginInterface_iid "org.logos.LEZWalletPluginInterface"
Q_DECLARE_INTERFACE(LEZWalletPluginInterface, LEZWalletPluginInterface_iid)
#endif // LEZ_WALLET_PLUGIN_INTERFACE_H

View File

@ -2,7 +2,6 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import LEZWalletBackend
import Logos.Theme
import Logos.Controls
import "views"
@ -10,6 +9,18 @@ import "views"
Rectangle {
id: root
readonly property var backend: logos.module("lez_wallet_ui")
readonly property var accountModel: logos.model("lez_wallet_ui", "accountModel")
property bool ready: false
Connections {
target: logos
function onViewModuleReadyChanged(moduleName, isReady) {
if (moduleName === "lez_wallet_ui")
root.ready = isReady && root.backend !== null
}
}
// Map wallet FFI error codes to user-facing strings. Matches lssa/wallet-ffi WalletFfiError enum.
QtObject {
id: ffiErrors
@ -46,23 +57,45 @@ Rectangle {
}
return errorMessage
}
// Parse a transfer result JSON string and write to dashboardView.
// Used by all three transfer handlers below.
function applyTransferResult(dashboardView, raw) {
var msg = raw || ""
var isError = false
try {
var obj = JSON.parse(raw)
if (obj.success) {
msg = obj.tx_hash ? qsTr("Success. Tx: %1").arg(obj.tx_hash) : qsTr("Success.")
} else if (obj.error) {
msg = ffiErrors.format(obj.error)
isError = true
}
} catch (e) {
if (msg.length > 0) isError = true
}
dashboardView.transferResult = msg
dashboardView.transferResultIsError = isError
}
}
QtObject {
id: d
readonly property bool isWalletOpen: backend && backend.isWalletOpen
onIsWalletOpenChanged: updateStack(isWalletOpen)
onIsWalletOpenChanged: if (root.ready) updateStack(isWalletOpen)
function updateStack(walletOpen) {
if(walletOpen) {
stackView.push(mainView)
} else {
stackView.push(onboardingView)
}
stackView.replace(walletOpen ? mainView : onboardingView)
}
}
Component.onCompleted: d.updateStack(backend && backend.isWalletOpen)
onReadyChanged: if (ready) d.updateStack(d.isWalletOpen)
Component.onCompleted: {
root.ready = root.backend !== null
&& logos.isViewModuleReady("lez_wallet_ui")
if (root.ready) d.updateStack(d.isWalletOpen)
}
color: Theme.palette.background
@ -73,11 +106,19 @@ Rectangle {
Component {
id: onboardingView
OnboardingView {
storePath: backend.storagePath
configPath: backend.configPath
storePath: backend ? backend.storagePath : ""
configPath: backend ? backend.configPath : ""
onCreateWallet: function(configPath, storagePath, password) {
if (!backend || !backend.createNew(configPath, storagePath, password))
createError = qsTr("Failed to create wallet. Check paths and try again.")
if (!backend) return
logos.watch(backend.createNew(configPath, storagePath, password),
function(ok) {
if (!ok)
createError = qsTr("Failed to create wallet. Check paths and try again.")
},
function(error) {
createError = qsTr("Error creating wallet: %1").arg(error)
}
)
}
}
}
@ -86,87 +127,56 @@ Rectangle {
id: mainView
DashboardView {
id: dashboardView
accountModel: backend ? backend.accountModel : null
accountModel: root.accountModel
onCreatePublicAccountRequested: {
if (!backend) {
console.warning("backend is null")
return
}
backend.createAccountPublic()
if (!backend) { console.warn("backend is null"); return }
// Result not consumed here accountModel updates via NOTIFY when
// the backend's refreshAccounts() runs after creation.
logos.watch(backend.createAccountPublic(),
function(_id) { /* ignored */ },
function(error) { console.warn("createAccountPublic failed:", error) })
}
onCreatePrivateAccountRequested: {
if (!backend) {
console.warning("backend is null")
return
}
backend.createAccountPrivate()
if (!backend) { console.warn("backend is null"); return }
logos.watch(backend.createAccountPrivate(),
function(_id) { /* ignored */ },
function(error) { console.warn("createAccountPrivate failed:", error) })
}
onFetchBalancesRequested: {
if (!backend) {
console.warning("backend is null")
return
}
backend.refreshBalances()
if (!backend) { console.warn("backend is null"); return }
backend.refreshBalances() // void slot, fire-and-forget
}
onTransferPublicRequested: (fromId, toAddress, amount) => {
if (!backend) return
var raw = backend.transferPublic(fromId, toAddress, amount)
var msg = raw || ""
var isError = false
try {
var obj = JSON.parse(raw)
if (obj.success) {
msg = obj.tx_hash ? qsTr("Success. Tx: %1").arg(obj.tx_hash) : qsTr("Success.")
} else if (obj.error) {
msg = ffiErrors.format(obj.error)
isError = true
}
} catch (e) {
if (msg.length > 0) isError = true
}
dashboardView.transferResult = msg
dashboardView.transferResultIsError = isError
logos.watch(backend.transferPublic(fromId, toAddress, amount),
function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) },
function(error) {
dashboardView.transferResult = qsTr("Error: %1").arg(error)
dashboardView.transferResultIsError = true
})
}
onTransferPrivateRequested: (fromId, toKeysJsonOrAddress, amount) => {
if (!backend) return
var raw = backend.transferPrivate(fromId, toKeysJsonOrAddress, amount)
var msg = raw || ""
var isError = false
try {
var obj = JSON.parse(raw)
if (obj.success) {
msg = obj.tx_hash ? qsTr("Success. Tx: %1").arg(obj.tx_hash) : qsTr("Success.")
} else if (obj.error) {
msg = ffiErrors.format(obj.error)
isError = true
}
} catch (e) {
if (msg.length > 0) isError = true
}
dashboardView.transferResult = msg
dashboardView.transferResultIsError = isError
logos.watch(backend.transferPrivate(fromId, toKeysJsonOrAddress, amount),
function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) },
function(error) {
dashboardView.transferResult = qsTr("Error: %1").arg(error)
dashboardView.transferResultIsError = true
})
}
onTransferPrivateOwnedRequested: (fromId, toAccountId, amount) => {
if (!backend) return
var raw = backend.transferPrivateOwned(fromId, toAccountId, amount)
var msg = raw || ""
var isError = false
try {
var obj = JSON.parse(raw)
if (obj.success) {
msg = obj.tx_hash ? qsTr("Success. Tx: %1").arg(obj.tx_hash) : qsTr("Success.")
} else if (obj.error) {
msg = ffiErrors.format(obj.error)
isError = true
}
} catch (e) {
if (msg.length > 0) isError = true
}
dashboardView.transferResult = msg
dashboardView.transferResultIsError = isError
logos.watch(backend.transferPrivateOwned(fromId, toAccountId, amount),
function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) },
function(error) {
dashboardView.transferResult = qsTr("Error: %1").arg(error)
dashboardView.transferResultIsError = true
})
}
onCopyRequested: (copyText) => {
if (backend) backend.copyToClipboard(copyText)
}
onCopyRequested: (copyText) => backend.copyToClipboard(copyText)
}
}
}

View File

@ -8,6 +8,10 @@ import Logos.Controls
ComboBox {
id: root
// Forwarded from AccountDelegate's copy button bubble up to the parent
// view, which calls backend.copyToClipboard().
signal copyRequested(string text)
leftPadding: 12
rightPadding: 12
implicitHeight: 40
@ -55,6 +59,7 @@ ComboBox {
delegate: AccountDelegate {
width: root.popup ? (root.popup.width - root.popup.leftPadding - root.popup.rightPadding) : 368
highlighted: root.highlightedIndex === index
onCopyRequested: (text) => root.copyRequested(text)
}
popup: Popup {

View File

@ -8,6 +8,12 @@ import Logos.Controls
ItemDelegate {
id: root
// Emitted when the user clicks the copy icon. The parent connects this
// to backend.copyToClipboard(...) AccountDelegate doesn't reach into
// the global QML scope for `backend` since it now lives behind the
// logos.module() bridge in the parent view.
signal copyRequested(string text)
leftPadding: Theme.spacing.medium
rightPadding: Theme.spacing.medium
topPadding: Theme.spacing.medium
@ -70,7 +76,7 @@ ItemDelegate {
LogosCopyButton {
Layout.preferredHeight: 40
Layout.preferredWidth: 40
onCopyText: backend.copyToClipboard(model.address)
onCopyText: root.copyRequested(model.address)
visible: addressLabel.text
icon.color: Theme.palette.textMuted
}

View File

@ -13,7 +13,7 @@ Button {
display: AbstractButton.IconOnly
flat: true
property string iconSource: "qrc:/lezwallet/icons/copy.svg"
property string iconSource: Qt.resolvedUrl("../icons/copy.svg")
icon.source: root.iconSource
icon.width: 24
@ -21,7 +21,7 @@ Button {
icon.color: Theme.palette.textSecondary
function reset() {
iconSource = "qrc:/lezwallet/icons/copy.svg"
iconSource = Qt.resolvedUrl("../icons/copy.svg")
}
Timer {
@ -33,7 +33,7 @@ Button {
onClicked: {
root.copyText()
root.iconSource = "qrc:/lezwallet/icons/checkmark.svg"
root.iconSource = Qt.resolvedUrl("../icons/checkmark.svg")
resetTimer.restart()
}
}

View File

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 462 B

View File

Before

Width:  |  Height:  |  Size: 976 B

After

Width:  |  Height:  |  Size: 976 B

View File

@ -18,6 +18,7 @@ Rectangle {
signal createPublicAccountRequested()
signal createPrivateAccountRequested()
signal fetchBalancesRequested()
signal copyRequested(string text)
radius: Theme.spacing.radiusXlarge
color: Theme.palette.backgroundSecondary
@ -81,6 +82,7 @@ Rectangle {
delegate: AccountDelegate {
width: listView.width
onCopyRequested: (text) => root.copyRequested(text)
}
}

View File

@ -39,6 +39,7 @@ Rectangle {
onCreatePublicAccountRequested: root.createPublicAccountRequested()
onCreatePrivateAccountRequested: root.createPrivateAccountRequested()
onFetchBalancesRequested: root.fetchBalancesRequested()
onCopyRequested: (text) => root.copyRequested(text)
}
TransferPanel {

View File

@ -149,7 +149,7 @@ Control {
id: configFileDialog
modality: Qt.NonModal
nameFilters: ["JSON files (*.json)"]
currentFolder: root.configPath ? d.configParentFolderUrl(oot.configPath) : ""
currentFolder: root.configPath ? d.configParentFolderUrl(root.configPath) : ""
onAccepted: {
if (selectedFile) configPathField.text = selectedFile.toString().replace(/^file:\/\//, "")
}

View File

@ -21,7 +21,7 @@ Rectangle {
signal transferPrivateOwnedRequested(string fromAccountId, string toAccountId, string amount)
signal copyRequested(string copyText)
readonly property int fromFilterCount: fromAccountModel ? fromAccountModel.count : 0
readonly property int fromFilterCount: (fromAccountModel && fromAccountModel.count) ? fromAccountModel.count : 0
QtObject {
id: d
@ -94,6 +94,7 @@ Rectangle {
Layout.fillWidth: true
model: fromAccountModel
visible: fromFilterCount > 0
onCopyRequested: (text) => root.copyRequested(text)
}
}
@ -130,6 +131,7 @@ Rectangle {
Layout.fillWidth: true
model: fromAccountModel
visible: d.isPrivateTab && d.useOwnedAccountForTo && fromFilterCount > 0
onCopyRequested: (text) => root.copyRequested(text)
}
}

View File

@ -1,16 +0,0 @@
<RCC>
<qresource prefix="/lezwallet">
<file>qml/ExecutionZoneWalletView.qml</file>
<file>qml/controls/AccountComboBox.qml</file>
<file>qml/controls/AccountDelegate.qml</file>
<file>qml/popups/CreateAccountDialog.qml</file>
<file>qml/views/qmldir</file>
<file>qml/views/OnboardingView.qml</file>
<file>qml/views/DashboardView.qml</file>
<file>qml/views/AccountsPanel.qml</file>
<file>qml/views/TransferPanel.qml</file>
<file>qml/controls/LogosCopyButton.qml</file>
<file>icons/checkmark.svg</file>
<file>icons/copy.svg</file>
</qresource>
</RCC>