From 48fab40a3c871e4cd8b3792b35e440c6c655819b Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Jun 2026 23:34:11 -0300 Subject: [PATCH 01/14] add shielded tab --- src/LEZWalletBackend.cpp | 23 +++++++++++++++++ src/LEZWalletBackend.h | 2 ++ src/LEZWalletBackend.rep | 2 ++ src/qml/ExecutionZoneWalletView.qml | 18 +++++++++++++ src/qml/views/DashboardView.qml | 4 +++ src/qml/views/TransferPanel.qml | 40 ++++++++++++++++++++--------- 6 files changed, 77 insertions(+), 12 deletions(-) diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index e8f8f5b..5eda5f3 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -234,6 +234,29 @@ QString LEZWalletBackend::transferPrivateOwned(QString fromHex, QString toHex, Q return m_logos->logos_execution_zone.transfer_private_owned(fromHex, toHex.trimmed(), amountHex); } +QString LEZWalletBackend::transferShielded(QString fromHex, QString toKeysJson, QString amountStr) +{ + const QString amountHex = amountToLe16Hex(amountStr); + if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount."); + + QString keysPayload = toKeysJson.trimmed(); + if (!keysPayload.startsWith(QLatin1Char('{'))) { + qDebug() << "LEZWalletBackend::transferShielded: resolving keys via get_private_account_keys"; + const QString resolved = getPrivateAccountKeys(keysPayload); + if (!resolved.isEmpty()) + keysPayload = resolved; + } + + return m_logos->logos_execution_zone.transfer_shielded(fromHex, keysPayload, amountHex); +} + +QString LEZWalletBackend::transferShieldedOwned(QString fromHex, QString toHex, QString amountStr) +{ + const QString amountHex = amountToLe16Hex(amountStr); + if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount."); + return m_logos->logos_execution_zone.transfer_shielded_owned(fromHex, toHex.trimmed(), amountHex); +} + bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QString password) { const QString localPath = toLocalPath(configPath); diff --git a/src/LEZWalletBackend.h b/src/LEZWalletBackend.h index 0ebcc3e..259b971 100644 --- a/src/LEZWalletBackend.h +++ b/src/LEZWalletBackend.h @@ -40,6 +40,8 @@ public slots: 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; + QString transferShielded(QString fromHex, QString toKeysJson, QString amountStr) override; + QString transferShieldedOwned(QString fromHex, QString toHex, QString amountStr) override; bool createNew(QString configPath, QString storagePath, QString password) override; void copyToClipboard(QString text) override; diff --git a/src/LEZWalletBackend.rep b/src/LEZWalletBackend.rep index aec5790..695958f 100644 --- a/src/LEZWalletBackend.rep +++ b/src/LEZWalletBackend.rep @@ -19,6 +19,8 @@ class LEZWalletBackend 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(QString transferShielded(QString fromHex, QString toKeysJson, QString amountStr)) + SLOT(QString transferShieldedOwned(QString fromHex, QString toHex, QString amountStr)) SLOT(bool createNew(QString configPath, QString storagePath, QString password)) diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index 9814e46..5b27095 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -180,6 +180,24 @@ Rectangle { dashboardView.transferResultIsError = true }) } + onTransferShieldedRequested: (fromId, toKeysJsonOrAddress, amount) => { + if (!backend) return + logos.watch(backend.transferShielded(fromId, toKeysJsonOrAddress, amount), + function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) }, + function(error) { + dashboardView.transferResult = qsTr("Error: %1").arg(error) + dashboardView.transferResultIsError = true + }) + } + onTransferShieldedOwnedRequested: (fromId, toAccountId, amount) => { + if (!backend) return + logos.watch(backend.transferShieldedOwned(fromId, toAccountId, amount), + function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) }, + function(error) { + dashboardView.transferResult = qsTr("Error: %1").arg(error) + dashboardView.transferResultIsError = true + }) + } onCopyRequested: (copyText) => { clipHelper.text = copyText clipHelper.selectAll() diff --git a/src/qml/views/DashboardView.qml b/src/qml/views/DashboardView.qml index efd4f3d..49565b6 100644 --- a/src/qml/views/DashboardView.qml +++ b/src/qml/views/DashboardView.qml @@ -20,6 +20,8 @@ Rectangle { signal transferPublicRequested(string fromAccountId, string toAddress, string amount) signal transferPrivateRequested(string fromAccountId, string toKeysJsonOrAddress, string amount) signal transferPrivateOwnedRequested(string fromAccountId, string toAccountId, string amount) + signal transferShieldedRequested(string fromAccountId, string toKeysJsonOrAddress, string amount) + signal transferShieldedOwnedRequested(string fromAccountId, string toAccountId, string amount) signal copyRequested(string copyText) color: Theme.palette.background @@ -53,6 +55,8 @@ Rectangle { onTransferPublicRequested: (fromId, toAddress, amount) => root.transferPublicRequested(fromId, toAddress, amount) onTransferPrivateRequested: (fromId, toKeysJsonOrAddress, amount) => root.transferPrivateRequested(fromId, toKeysJsonOrAddress, amount) onTransferPrivateOwnedRequested: (fromId, toAccountId, amount) => root.transferPrivateOwnedRequested(fromId, toAccountId, amount) + onTransferShieldedRequested: (fromId, toKeysJsonOrAddress, amount) => root.transferShieldedRequested(fromId, toKeysJsonOrAddress, amount) + onTransferShieldedOwnedRequested: (fromId, toAccountId, amount) => root.transferShieldedOwnedRequested(fromId, toAccountId, amount) onCopyRequested: (copyText) => root.copyRequested(copyText) } } diff --git a/src/qml/views/TransferPanel.qml b/src/qml/views/TransferPanel.qml index 9240d3f..adcd2d4 100644 --- a/src/qml/views/TransferPanel.qml +++ b/src/qml/views/TransferPanel.qml @@ -15,10 +15,12 @@ Rectangle { property string transferResult: "" property bool transferResultIsError: false - // --- Public API: signals out (match backend: transfer_public, transfer_private, transfer_private_owned) --- + // --- Public API: signals out (match backend: transfer_public, transfer_private, transfer_private_owned, transfer_shielded, transfer_shielded_owned) --- signal transferPublicRequested(string fromAccountId, string toAddress, string amount) signal transferPrivateRequested(string fromAccountId, string toKeysJsonOrAddress, string amount) signal transferPrivateOwnedRequested(string fromAccountId, string toAccountId, string amount) + signal transferShieldedRequested(string fromAccountId, string toKeysJsonOrAddress, string amount) + signal transferShieldedOwnedRequested(string fromAccountId, string toAccountId, string amount) signal copyRequested(string copyText) readonly property int fromFilterCount: fromCombo.count @@ -26,8 +28,11 @@ Rectangle { QtObject { id: d property bool useOwnedAccountForTo: false + readonly property bool isPublicTab: transferTypeBar.currentIndex === 0 readonly property bool isPrivateTab: transferTypeBar.currentIndex === 1 - readonly property bool toAddressValid: isPrivateTab && useOwnedAccountForTo + readonly property bool isShieldedTab: transferTypeBar.currentIndex === 2 + readonly property bool showOwnedOption: isPrivateTab || isShieldedTab + readonly property bool toAddressValid: showOwnedOption && useOwnedAccountForTo ? (fromFilterCount > 0 && toCombo.currentIndex >= 0) : (toField && toField.text.trim().length > 0) readonly property bool sendEnabled: amountField && manualFromField @@ -54,7 +59,7 @@ Rectangle { // Transfer type toggle TabBar { id: transferTypeBar - Layout.preferredWidth: 200 + Layout.preferredWidth: 300 currentIndex: 0 background: Rectangle { @@ -69,6 +74,10 @@ Rectangle { LogosTabButton { text: qsTr("Private") } + + LogosTabButton { + text: qsTr("Shielded") + } } // From: dropdown when accounts exist, or manual entry when list is empty @@ -111,7 +120,7 @@ Rectangle { CheckBox { id: useOwnedToCheck - visible: d.isPrivateTab + visible: d.showOwnedOption checked: d.useOwnedAccountForTo onCheckedChanged: d.useOwnedAccountForTo = checked text: qsTr("Use owned account") @@ -122,15 +131,15 @@ Rectangle { LogosTextField { id: toField Layout.fillWidth: true - placeholderText: qsTr("Recipient public key") - visible: !d.isPrivateTab || !d.useOwnedAccountForTo + placeholderText: d.isPublicTab ? qsTr("Recipient address") : qsTr("Recipient private keys (JSON)") + visible: !d.showOwnedOption || !d.useOwnedAccountForTo } AccountComboBox { id: toCombo Layout.fillWidth: true model: fromAccountModel - visible: d.isPrivateTab && d.useOwnedAccountForTo && fromFilterCount > 0 + visible: d.showOwnedOption && d.useOwnedAccountForTo && fromFilterCount > 0 onCopyRequested: (text) => root.copyRequested(text) } } @@ -168,12 +177,19 @@ Rectangle { : toField.text.trim() var amount = amountField.text.trim() if (fromId.length > 0 && toAddress.length > 0 && amount.length > 0) { - if (transferTypeBar.currentIndex === 0) + if (d.isPublicTab) root.transferPublicRequested(fromId, toAddress, amount) - else if (d.useOwnedAccountForTo) - root.transferPrivateOwnedRequested(fromId, toAddress, amount) - else - root.transferPrivateRequested(fromId, toAddress, amount) + else if (d.isPrivateTab) { + if (d.useOwnedAccountForTo) + root.transferPrivateOwnedRequested(fromId, toAddress, amount) + else + root.transferPrivateRequested(fromId, toAddress, amount) + } else if (d.isShieldedTab) { + if (d.useOwnedAccountForTo) + root.transferShieldedOwnedRequested(fromId, toAddress, amount) + else + root.transferShieldedRequested(fromId, toAddress, amount) + } } } } From 8fa1d2f4400472d2fc06d8e3cd2e2d34a87704c7 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Thu, 4 Jun 2026 23:46:17 -0300 Subject: [PATCH 02/14] filter accounts in dropdown From and To depending on transfer type --- src/LEZWalletBackend.cpp | 3 +++ src/LEZWalletBackend.h | 3 +++ src/qml/ExecutionZoneWalletView.qml | 4 ++++ src/qml/views/DashboardView.qml | 5 ++++- src/qml/views/TransferPanel.qml | 12 +++++++----- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index 5eda5f3..e4b007c 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -46,10 +46,13 @@ LEZWalletBackend::LEZWalletBackend(LogosAPI* logosAPI, QObject* parent) : LEZWalletBackendSimpleSource(parent), m_accountModel(new LEZWalletAccountModel(this)), m_filteredAccountModel(new LEZAccountFilterModel(this)), + m_privateAccountModel(new LEZAccountFilterModel(this)), m_logosAPI(logosAPI ? logosAPI : new LogosAPI("lez_wallet_ui", this)), m_logos(new LogosModules(m_logosAPI)) { m_filteredAccountModel->setSourceModel(m_accountModel); + m_privateAccountModel->setFilterByPublic(false); + m_privateAccountModel->setSourceModel(m_accountModel); // Initialise PROP defaults via the generated setters. setIsWalletOpen(false); diff --git a/src/LEZWalletBackend.h b/src/LEZWalletBackend.h index 259b971..d6658ed 100644 --- a/src/LEZWalletBackend.h +++ b/src/LEZWalletBackend.h @@ -19,6 +19,7 @@ class LEZWalletBackend : public LEZWalletBackendSimpleSource { Q_OBJECT Q_PROPERTY(LEZWalletAccountModel* accountModel READ accountModel CONSTANT) Q_PROPERTY(LEZAccountFilterModel* filteredAccountModel READ filteredAccountModel CONSTANT) + Q_PROPERTY(LEZAccountFilterModel* privateAccountModel READ privateAccountModel CONSTANT) public: explicit LEZWalletBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr); @@ -26,6 +27,7 @@ public: LEZWalletAccountModel* accountModel() const { return m_accountModel; } LEZAccountFilterModel* filteredAccountModel() const { return m_filteredAccountModel; } + LEZAccountFilterModel* privateAccountModel() const { return m_privateAccountModel; } public slots: // Overrides of the pure-virtual slots generated from the .rep. @@ -56,6 +58,7 @@ private: LEZWalletAccountModel* m_accountModel; LEZAccountFilterModel* m_filteredAccountModel; + LEZAccountFilterModel* m_privateAccountModel; LogosAPI* m_logosAPI; LogosModules* m_logos; diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index 5b27095..aa49340 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -11,6 +11,8 @@ Rectangle { readonly property var backend: logos.module("lez_wallet_ui") readonly property var accountModel: logos.model("lez_wallet_ui", "accountModel") + readonly property var publicAccountModel: logos.model("lez_wallet_ui", "filteredAccountModel") + readonly property var privateAccountModel: logos.model("lez_wallet_ui", "privateAccountModel") property bool ready: false Connections { @@ -134,6 +136,8 @@ Rectangle { DashboardView { id: dashboardView accountModel: root.accountModel + publicAccountModel: root.publicAccountModel + privateAccountModel: root.privateAccountModel onCreatePublicAccountRequested: { if (!backend) { console.warn("backend is null"); return } diff --git a/src/qml/views/DashboardView.qml b/src/qml/views/DashboardView.qml index 49565b6..f8361f6 100644 --- a/src/qml/views/DashboardView.qml +++ b/src/qml/views/DashboardView.qml @@ -10,6 +10,8 @@ Rectangle { // --- Public API: input properties (set by parent / MainView) --- property var accountModel: null + property var publicAccountModel: null + property var privateAccountModel: null property string transferResult: "" property bool transferResultIsError: false @@ -48,7 +50,8 @@ Rectangle { id: transferPanel Layout.fillWidth: true Layout.fillHeight: true - fromAccountModel: root.accountModel + publicAccountModel: root.publicAccountModel + privateAccountModel: root.privateAccountModel transferResult: root.transferResult transferResultIsError: root.transferResultIsError diff --git a/src/qml/views/TransferPanel.qml b/src/qml/views/TransferPanel.qml index adcd2d4..a5b4046 100644 --- a/src/qml/views/TransferPanel.qml +++ b/src/qml/views/TransferPanel.qml @@ -11,7 +11,8 @@ Rectangle { id: root // --- Public API: data in --- - property var fromAccountModel: null + property var publicAccountModel: null + property var privateAccountModel: null property string transferResult: "" property bool transferResultIsError: false @@ -24,6 +25,7 @@ Rectangle { signal copyRequested(string copyText) readonly property int fromFilterCount: fromCombo.count + readonly property int toFilterCount: toCombo.count QtObject { id: d @@ -33,7 +35,7 @@ Rectangle { readonly property bool isShieldedTab: transferTypeBar.currentIndex === 2 readonly property bool showOwnedOption: isPrivateTab || isShieldedTab readonly property bool toAddressValid: showOwnedOption && useOwnedAccountForTo - ? (fromFilterCount > 0 && toCombo.currentIndex >= 0) + ? (toFilterCount > 0 && toCombo.currentIndex >= 0) : (toField && toField.text.trim().length > 0) readonly property bool sendEnabled: amountField && manualFromField && amountField.text.length > 0 && d.toAddressValid @@ -101,7 +103,7 @@ Rectangle { AccountComboBox { id: fromCombo Layout.fillWidth: true - model: fromAccountModel + model: d.isPrivateTab ? root.privateAccountModel : root.publicAccountModel visible: fromFilterCount > 0 onCopyRequested: (text) => root.copyRequested(text) } @@ -138,8 +140,8 @@ Rectangle { AccountComboBox { id: toCombo Layout.fillWidth: true - model: fromAccountModel - visible: d.showOwnedOption && d.useOwnedAccountForTo && fromFilterCount > 0 + model: d.isPublicTab ? root.publicAccountModel : root.privateAccountModel + visible: d.showOwnedOption && d.useOwnedAccountForTo && toFilterCount > 0 onCopyRequested: (text) => root.copyRequested(text) } } From a97f8b06dd3ed4a8afda2decfb2377a13661555d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 00:14:43 -0300 Subject: [PATCH 03/14] add deshielded tab --- src/LEZWalletBackend.cpp | 7 +++++++ src/LEZWalletBackend.h | 1 + src/LEZWalletBackend.rep | 1 + src/qml/ExecutionZoneWalletView.qml | 9 +++++++++ src/qml/views/DashboardView.qml | 2 ++ src/qml/views/TransferPanel.qml | 23 ++++++++++++++++------- 6 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index e4b007c..f2f9f46 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -260,6 +260,13 @@ QString LEZWalletBackend::transferShieldedOwned(QString fromHex, QString toHex, return m_logos->logos_execution_zone.transfer_shielded_owned(fromHex, toHex.trimmed(), amountHex); } +QString LEZWalletBackend::transferDeshielded(QString fromHex, QString toHex, QString amountStr) +{ + const QString amountHex = amountToLe16Hex(amountStr); + if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount."); + return m_logos->logos_execution_zone.transfer_deshielded(fromHex.trimmed(), toHex.trimmed(), amountHex); +} + bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QString password) { const QString localPath = toLocalPath(configPath); diff --git a/src/LEZWalletBackend.h b/src/LEZWalletBackend.h index d6658ed..17c2cda 100644 --- a/src/LEZWalletBackend.h +++ b/src/LEZWalletBackend.h @@ -44,6 +44,7 @@ public slots: QString transferPrivateOwned(QString fromHex, QString toHex, QString amountStr) override; QString transferShielded(QString fromHex, QString toKeysJson, QString amountStr) override; QString transferShieldedOwned(QString fromHex, QString toHex, QString amountStr) override; + QString transferDeshielded(QString fromHex, QString toHex, QString amountStr) override; bool createNew(QString configPath, QString storagePath, QString password) override; void copyToClipboard(QString text) override; diff --git a/src/LEZWalletBackend.rep b/src/LEZWalletBackend.rep index 695958f..0bd0a33 100644 --- a/src/LEZWalletBackend.rep +++ b/src/LEZWalletBackend.rep @@ -21,6 +21,7 @@ class LEZWalletBackend SLOT(QString transferPrivateOwned(QString fromHex, QString toHex, QString amountStr)) SLOT(QString transferShielded(QString fromHex, QString toKeysJson, QString amountStr)) SLOT(QString transferShieldedOwned(QString fromHex, QString toHex, QString amountStr)) + SLOT(QString transferDeshielded(QString fromHex, QString toHex, QString amountStr)) SLOT(bool createNew(QString configPath, QString storagePath, QString password)) diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index aa49340..38fb435 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -202,6 +202,15 @@ Rectangle { dashboardView.transferResultIsError = true }) } + onTransferDeshieldedRequested: (fromId, toAccountId, amount) => { + if (!backend) return + logos.watch(backend.transferDeshielded(fromId, toAccountId, amount), + function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) }, + function(error) { + dashboardView.transferResult = qsTr("Error: %1").arg(error) + dashboardView.transferResultIsError = true + }) + } onCopyRequested: (copyText) => { clipHelper.text = copyText clipHelper.selectAll() diff --git a/src/qml/views/DashboardView.qml b/src/qml/views/DashboardView.qml index f8361f6..429ceb9 100644 --- a/src/qml/views/DashboardView.qml +++ b/src/qml/views/DashboardView.qml @@ -24,6 +24,7 @@ Rectangle { signal transferPrivateOwnedRequested(string fromAccountId, string toAccountId, string amount) signal transferShieldedRequested(string fromAccountId, string toKeysJsonOrAddress, string amount) signal transferShieldedOwnedRequested(string fromAccountId, string toAccountId, string amount) + signal transferDeshieldedRequested(string fromAccountId, string toAccountId, string amount) signal copyRequested(string copyText) color: Theme.palette.background @@ -60,6 +61,7 @@ Rectangle { onTransferPrivateOwnedRequested: (fromId, toAccountId, amount) => root.transferPrivateOwnedRequested(fromId, toAccountId, amount) onTransferShieldedRequested: (fromId, toKeysJsonOrAddress, amount) => root.transferShieldedRequested(fromId, toKeysJsonOrAddress, amount) onTransferShieldedOwnedRequested: (fromId, toAccountId, amount) => root.transferShieldedOwnedRequested(fromId, toAccountId, amount) + onTransferDeshieldedRequested: (fromId, toAccountId, amount) => root.transferDeshieldedRequested(fromId, toAccountId, amount) onCopyRequested: (copyText) => root.copyRequested(copyText) } } diff --git a/src/qml/views/TransferPanel.qml b/src/qml/views/TransferPanel.qml index a5b4046..4a9e381 100644 --- a/src/qml/views/TransferPanel.qml +++ b/src/qml/views/TransferPanel.qml @@ -22,6 +22,7 @@ Rectangle { signal transferPrivateOwnedRequested(string fromAccountId, string toAccountId, string amount) signal transferShieldedRequested(string fromAccountId, string toKeysJsonOrAddress, string amount) signal transferShieldedOwnedRequested(string fromAccountId, string toAccountId, string amount) + signal transferDeshieldedRequested(string fromAccountId, string toAccountId, string amount) signal copyRequested(string copyText) readonly property int fromFilterCount: fromCombo.count @@ -33,8 +34,10 @@ Rectangle { readonly property bool isPublicTab: transferTypeBar.currentIndex === 0 readonly property bool isPrivateTab: transferTypeBar.currentIndex === 1 readonly property bool isShieldedTab: transferTypeBar.currentIndex === 2 + readonly property bool isDeshieldedTab: transferTypeBar.currentIndex === 3 readonly property bool showOwnedOption: isPrivateTab || isShieldedTab - readonly property bool toAddressValid: showOwnedOption && useOwnedAccountForTo + readonly property bool toComboPermanent: isDeshieldedTab + readonly property bool toAddressValid: (toComboPermanent || (showOwnedOption && useOwnedAccountForTo)) ? (toFilterCount > 0 && toCombo.currentIndex >= 0) : (toField && toField.text.trim().length > 0) readonly property bool sendEnabled: amountField && manualFromField @@ -61,7 +64,7 @@ Rectangle { // Transfer type toggle TabBar { id: transferTypeBar - Layout.preferredWidth: 300 + Layout.preferredWidth: 400 currentIndex: 0 background: Rectangle { @@ -80,6 +83,10 @@ Rectangle { LogosTabButton { text: qsTr("Shielded") } + + LogosTabButton { + text: qsTr("Deshielded") + } } // From: dropdown when accounts exist, or manual entry when list is empty @@ -103,7 +110,7 @@ Rectangle { AccountComboBox { id: fromCombo Layout.fillWidth: true - model: d.isPrivateTab ? root.privateAccountModel : root.publicAccountModel + model: (d.isPrivateTab || d.isDeshieldedTab) ? root.privateAccountModel : root.publicAccountModel visible: fromFilterCount > 0 onCopyRequested: (text) => root.copyRequested(text) } @@ -134,14 +141,14 @@ Rectangle { id: toField Layout.fillWidth: true placeholderText: d.isPublicTab ? qsTr("Recipient address") : qsTr("Recipient private keys (JSON)") - visible: !d.showOwnedOption || !d.useOwnedAccountForTo + visible: !d.toComboPermanent && (!d.showOwnedOption || !d.useOwnedAccountForTo) } AccountComboBox { id: toCombo Layout.fillWidth: true - model: d.isPublicTab ? root.publicAccountModel : root.privateAccountModel - visible: d.showOwnedOption && d.useOwnedAccountForTo && toFilterCount > 0 + model: (d.isPublicTab || d.isDeshieldedTab) ? root.publicAccountModel : root.privateAccountModel + visible: d.toComboPermanent || (d.showOwnedOption && d.useOwnedAccountForTo && toFilterCount > 0) onCopyRequested: (text) => root.copyRequested(text) } } @@ -174,7 +181,7 @@ Rectangle { var fromId = fromFilterCount > 0 && fromCombo.currentIndex >= 0 ? (fromCombo.currentValue ?? "") : manualFromField.text.trim() - var toAddress = d.useOwnedAccountForTo && toCombo.currentIndex >= 0 + var toAddress = (d.toComboPermanent || (d.useOwnedAccountForTo && toCombo.currentIndex >= 0)) ? (toCombo.currentValue ?? "") : toField.text.trim() var amount = amountField.text.trim() @@ -191,6 +198,8 @@ Rectangle { root.transferShieldedOwnedRequested(fromId, toAddress, amount) else root.transferShieldedRequested(fromId, toAddress, amount) + } else if (d.isDeshieldedTab) { + root.transferDeshieldedRequested(fromId, toAddress, amount) } } } From f4b3554244d5bad98665b2adbe93bf7d1a54a1b8 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 00:17:43 -0300 Subject: [PATCH 04/14] fix checkbox rendering --- src/qml/views/TransferPanel.qml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/qml/views/TransferPanel.qml b/src/qml/views/TransferPanel.qml index 4a9e381..0c67eb9 100644 --- a/src/qml/views/TransferPanel.qml +++ b/src/qml/views/TransferPanel.qml @@ -127,14 +127,12 @@ Rectangle { color: Theme.palette.textSecondary } - CheckBox { + LogosCheckbox { id: useOwnedToCheck visible: d.showOwnedOption checked: d.useOwnedAccountForTo onCheckedChanged: d.useOwnedAccountForTo = checked text: qsTr("Use owned account") - font.pixelSize: Theme.typography.secondaryText - palette.text: Theme.palette.text } LogosTextField { From edf27b82aad5f42570d915e79b3ae6f935ba723e Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 02:06:25 -0300 Subject: [PATCH 05/14] add proog generation message and spinner --- src/LEZWalletBackend.cpp | 32 +++++++++++++++++++++++------ src/qml/ExecutionZoneWalletView.qml | 21 +++++++++++++------ src/qml/views/DashboardView.qml | 2 ++ src/qml/views/TransferPanel.qml | 26 ++++++++++++++++++++++- 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index f2f9f46..12308bc 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -11,6 +11,7 @@ #include #include "logos_api.h" +#include "logos_api_client.h" #include "logos_sdk.h" namespace { @@ -18,7 +19,12 @@ namespace { const char SETTINGS_APP[] = "ExecutionZoneWalletUI"; const char CONFIG_PATH_KEY[] = "configPath"; const char STORAGE_PATH_KEY[] = "storagePath"; + const char LEZ_MODULE[] = "logos_execution_zone"; const int WALLET_FFI_SUCCESS = 0; + // Proof generation time is unbounded on commodity hardware. + // Timeout(-1) means "wait indefinitely", matching Qt's own convention + // for infinite waits (e.g. QRemoteObjectPendingCall::waitForFinished(-1)). + const Timeout NO_TIMEOUT{-1}; // Convert a decimal amount string to 32-char hex (16 bytes little-endian) // for transfer_public/transfer_private/transfer_private_owned. @@ -219,7 +225,6 @@ QString LEZWalletBackend::transferPrivate(QString fromHex, QString toHex, QStrin 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. if (!keysPayload.startsWith(QLatin1Char('{'))) { qDebug() << "LEZWalletBackend::transferPrivate: resolving keys via get_private_account_keys"; const QString resolved = getPrivateAccountKeys(keysPayload); @@ -227,14 +232,20 @@ QString LEZWalletBackend::transferPrivate(QString fromHex, QString toHex, QStrin keysPayload = resolved; } - return m_logos->logos_execution_zone.transfer_private(fromHex, keysPayload, amountHex); + return m_logosAPI->getClient(LEZ_MODULE)->invokeRemoteMethod( + LEZ_MODULE, "transfer_private", + QVariantList{fromHex.trimmed(), keysPayload, amountHex}, + NO_TIMEOUT).toString(); } QString LEZWalletBackend::transferPrivateOwned(QString fromHex, QString toHex, QString amountStr) { const QString amountHex = amountToLe16Hex(amountStr); if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount."); - return m_logos->logos_execution_zone.transfer_private_owned(fromHex, toHex.trimmed(), amountHex); + return m_logosAPI->getClient(LEZ_MODULE)->invokeRemoteMethod( + LEZ_MODULE, "transfer_private_owned", + QVariantList{fromHex.trimmed(), toHex.trimmed(), amountHex}, + NO_TIMEOUT).toString(); } QString LEZWalletBackend::transferShielded(QString fromHex, QString toKeysJson, QString amountStr) @@ -250,21 +261,30 @@ QString LEZWalletBackend::transferShielded(QString fromHex, QString toKeysJson, keysPayload = resolved; } - return m_logos->logos_execution_zone.transfer_shielded(fromHex, keysPayload, amountHex); + return m_logosAPI->getClient(LEZ_MODULE)->invokeRemoteMethod( + LEZ_MODULE, "transfer_shielded", + QVariantList{fromHex.trimmed(), keysPayload, amountHex}, + NO_TIMEOUT).toString(); } QString LEZWalletBackend::transferShieldedOwned(QString fromHex, QString toHex, QString amountStr) { const QString amountHex = amountToLe16Hex(amountStr); if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount."); - return m_logos->logos_execution_zone.transfer_shielded_owned(fromHex, toHex.trimmed(), amountHex); + return m_logosAPI->getClient(LEZ_MODULE)->invokeRemoteMethod( + LEZ_MODULE, "transfer_shielded_owned", + QVariantList{fromHex.trimmed(), toHex.trimmed(), amountHex}, + NO_TIMEOUT).toString(); } QString LEZWalletBackend::transferDeshielded(QString fromHex, QString toHex, QString amountStr) { const QString amountHex = amountToLe16Hex(amountStr); if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount."); - return m_logos->logos_execution_zone.transfer_deshielded(fromHex.trimmed(), toHex.trimmed(), amountHex); + return m_logosAPI->getClient(LEZ_MODULE)->invokeRemoteMethod( + LEZ_MODULE, "transfer_deshielded", + QVariantList{fromHex.trimmed(), toHex.trimmed(), amountHex}, + NO_TIMEOUT).toString(); } bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QString password) diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index 38fb435..de4f165 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -61,7 +61,6 @@ Rectangle { } // 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 @@ -168,45 +167,55 @@ Rectangle { } onTransferPrivateRequested: (fromId, toKeysJsonOrAddress, amount) => { if (!backend) return + dashboardView.transferPending = true logos.watch(backend.transferPrivate(fromId, toKeysJsonOrAddress, amount), - function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) }, + function(raw) { dashboardView.transferPending = false; ffiErrors.applyTransferResult(dashboardView, raw) }, function(error) { + dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true }) } onTransferPrivateOwnedRequested: (fromId, toAccountId, amount) => { if (!backend) return + dashboardView.transferPending = true logos.watch(backend.transferPrivateOwned(fromId, toAccountId, amount), - function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) }, + function(raw) { dashboardView.transferPending = false; ffiErrors.applyTransferResult(dashboardView, raw) }, function(error) { + dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true }) } onTransferShieldedRequested: (fromId, toKeysJsonOrAddress, amount) => { if (!backend) return + dashboardView.transferPending = true logos.watch(backend.transferShielded(fromId, toKeysJsonOrAddress, amount), - function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) }, + function(raw) { dashboardView.transferPending = false; ffiErrors.applyTransferResult(dashboardView, raw) }, function(error) { + dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true }) } onTransferShieldedOwnedRequested: (fromId, toAccountId, amount) => { if (!backend) return + dashboardView.transferPending = true logos.watch(backend.transferShieldedOwned(fromId, toAccountId, amount), - function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) }, + function(raw) { dashboardView.transferPending = false; ffiErrors.applyTransferResult(dashboardView, raw) }, function(error) { + dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true }) } onTransferDeshieldedRequested: (fromId, toAccountId, amount) => { if (!backend) return + dashboardView.transferPending = true logos.watch(backend.transferDeshielded(fromId, toAccountId, amount), - function(raw) { ffiErrors.applyTransferResult(dashboardView, raw) }, + function(raw) { dashboardView.transferPending = false; ffiErrors.applyTransferResult(dashboardView, raw) }, function(error) { + dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true }) diff --git a/src/qml/views/DashboardView.qml b/src/qml/views/DashboardView.qml index 429ceb9..84ebb12 100644 --- a/src/qml/views/DashboardView.qml +++ b/src/qml/views/DashboardView.qml @@ -14,6 +14,7 @@ Rectangle { property var privateAccountModel: null property string transferResult: "" property bool transferResultIsError: false + property bool transferPending: false // --- Public API: output signals (parent connects and calls backend) --- signal createPublicAccountRequested() @@ -55,6 +56,7 @@ Rectangle { privateAccountModel: root.privateAccountModel transferResult: root.transferResult transferResultIsError: root.transferResultIsError + transferPending: root.transferPending onTransferPublicRequested: (fromId, toAddress, amount) => root.transferPublicRequested(fromId, toAddress, amount) onTransferPrivateRequested: (fromId, toKeysJsonOrAddress, amount) => root.transferPrivateRequested(fromId, toKeysJsonOrAddress, amount) diff --git a/src/qml/views/TransferPanel.qml b/src/qml/views/TransferPanel.qml index 0c67eb9..77474da 100644 --- a/src/qml/views/TransferPanel.qml +++ b/src/qml/views/TransferPanel.qml @@ -15,6 +15,7 @@ Rectangle { property var privateAccountModel: null property string transferResult: "" property bool transferResultIsError: false + property bool transferPending: false // --- Public API: signals out (match backend: transfer_public, transfer_private, transfer_private_owned, transfer_shielded, transfer_shielded_owned) --- signal transferPublicRequested(string fromAccountId, string toAddress, string amount) @@ -40,7 +41,9 @@ Rectangle { readonly property bool toAddressValid: (toComboPermanent || (showOwnedOption && useOwnedAccountForTo)) ? (toFilterCount > 0 && toCombo.currentIndex >= 0) : (toField && toField.text.trim().length > 0) - readonly property bool sendEnabled: amountField && manualFromField + readonly property bool needsProof: isPrivateTab || isShieldedTab || isDeshieldedTab + readonly property bool sendEnabled: !root.transferPending + && amountField && manualFromField && amountField.text.length > 0 && d.toAddressValid && ((fromFilterCount > 0 && fromCombo.currentIndex >= 0) || (fromFilterCount === 0 && manualFromField.text.trim().length > 0)) @@ -203,9 +206,30 @@ Rectangle { } } + // Proof pending indicator + RowLayout { + Layout.fillWidth: true + visible: root.transferPending + spacing: Theme.spacing.small + + LogosSpinner { + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + running: root.transferPending + } + + LogosText { + Layout.fillWidth: true + text: qsTr("Generating proof, please wait…") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + } + // Result label RowLayout { Layout.fillWidth: true + visible: !root.transferPending LogosText { id: resultText Layout.fillWidth: true From d4a898809824338cac5be77ff6cb39677b511303 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 02:27:10 -0300 Subject: [PATCH 06/14] use first 4 chars of account_id_hex as account nr to avoid accounts shuffling --- src/LEZWalletAccountModel.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LEZWalletAccountModel.cpp b/src/LEZWalletAccountModel.cpp index 2381024..35b9c1b 100644 --- a/src/LEZWalletAccountModel.cpp +++ b/src/LEZWalletAccountModel.cpp @@ -43,10 +43,8 @@ void LEZWalletAccountModel::replaceFromJsonArray(const QJsonArray& arr) beginResetModel(); int oldCount = m_entries.size(); m_entries.clear(); - int idx = 0; for (const QJsonValue& v : arr) { LEZWalletAccountEntry e; - e.name = QStringLiteral("Account %1").arg(++idx); e.balance = QString(); if (v.isObject()) { const QJsonObject obj = v.toObject(); @@ -56,6 +54,7 @@ void LEZWalletAccountModel::replaceFromJsonArray(const QJsonArray& arr) e.address = v.toString(); e.isPublic = true; } + e.name = QStringLiteral("Account %1").arg(e.address.left(4)); m_entries.append(e); } endResetModel(); From 535481fbb77f6c365e202cdcce016d013b87e702 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 02:38:15 -0300 Subject: [PATCH 07/14] minor fix on wallet storage parsing --- src/LEZWalletBackend.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index 12308bc..ad9111c 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -123,6 +123,9 @@ void LEZWalletBackend::openIfPathsConfigured() refreshAccounts(); refreshBlockHeights(); refreshSequencerAddr(); + } else { + qWarning() << "LEZWalletBackend: failed to open wallet, error" << err + << "config:" << configPath() << "storage:" << storagePath(); } } @@ -289,12 +292,13 @@ QString LEZWalletBackend::transferDeshielded(QString fromHex, QString toHex, QSt bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QString password) { - const QString localPath = toLocalPath(configPath); - int err = m_logos->logos_execution_zone.create_new(localPath, storagePath, password); + const QString localConfigPath = toLocalPath(configPath); + const QString localStoragePath = toLocalPath(storagePath); + int err = m_logos->logos_execution_zone.create_new(localConfigPath, localStoragePath, password); if (err != WALLET_FFI_SUCCESS) return false; - persistConfigPath(localPath); - persistStoragePath(storagePath); + persistConfigPath(localConfigPath); + persistStoragePath(localStoragePath); setIsWalletOpen(true); refreshAccounts(); refreshBlockHeights(); From 5f93ea364870e0d41be16f45ad7d7575c8db995d Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 03:24:03 -0300 Subject: [PATCH 08/14] add Network field on wallet creation --- src/LEZWalletBackend.cpp | 32 ++++++++++++++++++++++- src/LEZWalletBackend.h | 3 ++- src/LEZWalletBackend.rep | 2 +- src/qml/ExecutionZoneWalletView.qml | 4 +-- src/qml/views/OnboardingView.qml | 39 +++++++++++++++++++++++++++-- 5 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index ad9111c..8d0249a 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -4,8 +4,13 @@ #include #include #include +#include +#include +#include #include #include +#include +#include #include #include #include @@ -290,10 +295,35 @@ QString LEZWalletBackend::transferDeshielded(QString fromHex, QString toHex, QSt NO_TIMEOUT).toString(); } -bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QString password) +void LEZWalletBackend::applySequencerAddrToConfig(const QString& configPath, const QString& sequencerAddr) +{ + QJsonObject obj; + QFile file(configPath); + if (file.open(QIODevice::ReadOnly)) { + obj = QJsonDocument::fromJson(file.readAll()).object(); + file.close(); + } else { + // Defaults matching WalletConfig::default() in the wallet crate. + obj[QStringLiteral("seq_poll_timeout")] = QStringLiteral("30s"); + obj[QStringLiteral("seq_tx_poll_max_blocks")] = 15; + obj[QStringLiteral("seq_poll_max_retries")] = 10; + obj[QStringLiteral("seq_block_poll_max_amount")] = 100; + } + obj[QStringLiteral("sequencer_addr")] = sequencerAddr; + + QDir().mkpath(QFileInfo(configPath).absolutePath()); + if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + file.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); +} + +bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QString password, QString sequencerAddr) { const QString localConfigPath = toLocalPath(configPath); const QString localStoragePath = toLocalPath(storagePath); + + if (!sequencerAddr.isEmpty()) + applySequencerAddrToConfig(localConfigPath, sequencerAddr); + int err = m_logos->logos_execution_zone.create_new(localConfigPath, localStoragePath, password); if (err != WALLET_FFI_SUCCESS) return false; diff --git a/src/LEZWalletBackend.h b/src/LEZWalletBackend.h index 17c2cda..2d20cc7 100644 --- a/src/LEZWalletBackend.h +++ b/src/LEZWalletBackend.h @@ -45,12 +45,13 @@ public slots: QString transferShielded(QString fromHex, QString toKeysJson, QString amountStr) override; QString transferShieldedOwned(QString fromHex, QString toHex, QString amountStr) override; QString transferDeshielded(QString fromHex, QString toHex, QString amountStr) override; - bool createNew(QString configPath, QString storagePath, QString password) override; + bool createNew(QString configPath, QString storagePath, QString password, QString sequencerAddr) override; void copyToClipboard(QString text) override; private: void persistConfigPath(const QString& path); void persistStoragePath(const QString& path); + void applySequencerAddrToConfig(const QString& configPath, const QString& sequencerAddr); void refreshBlockHeights(); void refreshSequencerAddr(); void saveWallet(); diff --git a/src/LEZWalletBackend.rep b/src/LEZWalletBackend.rep index 0bd0a33..376f3e4 100644 --- a/src/LEZWalletBackend.rep +++ b/src/LEZWalletBackend.rep @@ -23,7 +23,7 @@ class LEZWalletBackend SLOT(QString transferShieldedOwned(QString fromHex, QString toHex, QString amountStr)) SLOT(QString transferDeshielded(QString fromHex, QString toHex, QString amountStr)) - SLOT(bool createNew(QString configPath, QString storagePath, QString password)) + SLOT(bool createNew(QString configPath, QString storagePath, QString password, QString sequencerAddr)) SLOT(void copyToClipboard(QString text)) } diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index de4f165..ebf6152 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -115,9 +115,9 @@ Rectangle { OnboardingView { storePath: backend ? backend.storagePath : "" configPath: backend ? backend.configPath : "" - onCreateWallet: function(configPath, storagePath, password) { + onCreateWallet: function(configPath, storagePath, password, sequencerUrl) { if (!backend) return - logos.watch(backend.createNew(configPath, storagePath, password), + logos.watch(backend.createNew(configPath, storagePath, password, sequencerUrl), function(ok) { if (!ok) createError = qsTr("Failed to create wallet. Check paths and try again.") diff --git a/src/qml/views/OnboardingView.qml b/src/qml/views/OnboardingView.qml index 30bb903..71cf5fd 100644 --- a/src/qml/views/OnboardingView.qml +++ b/src/qml/views/OnboardingView.qml @@ -13,7 +13,10 @@ Control { property string storePath: "" property string createError: "" - signal createWallet(string configPath, string storagePath, string password) + signal createWallet(string configPath, string storagePath, string password, string sequencerUrl) + + readonly property string testnetUrl: "https://testnet.lez.logos.co" + readonly property string localhostUrl: "http://127.0.0.1:3040" QtObject { @@ -90,6 +93,38 @@ Control { } } + RowLayout { + Layout.fillWidth: true + Layout.topMargin: Theme.spacing.medium + spacing: Theme.spacing.small + LogosText { + text: qsTr("Network") + font.pixelSize: Theme.typography.secondaryText + font.weight: Theme.typography.weightMedium + color: Theme.palette.text + } + LogosTextField { + id: sequencerUrlField + Layout.fillWidth: true + placeholderText: qsTr("Sequencer URL") + text: root.testnetUrl + } + } + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + LogosButton { + text: qsTr("Testnet") + opacity: sequencerUrlField.text === root.testnetUrl ? 1.0 : 0.4 + onClicked: sequencerUrlField.text = root.testnetUrl + } + LogosButton { + text: qsTr("Localhost") + opacity: sequencerUrlField.text === root.localhostUrl ? 1.0 : 0.4 + onClicked: sequencerUrlField.text = root.localhostUrl + } + } + LogosText { text: qsTr("Security") font.pixelSize: Theme.typography.secondaryText @@ -131,7 +166,7 @@ Control { root.createError = qsTr("Passwords do not match.") } else { root.createError = "" - root.createWallet(configPathField.text, storagePathField.text, passwordField.text) + root.createWallet(configPathField.text, storagePathField.text, passwordField.text, sequencerUrlField.text) } } } From cd2e3d8861d387c351265228a625d126a5661ea9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 04:01:21 -0300 Subject: [PATCH 09/14] better updates --- src/LEZWalletBackend.cpp | 72 +++++++++++++++++++++-------- src/LEZWalletBackend.h | 13 +++++- src/qml/ExecutionZoneWalletView.qml | 2 + src/qml/views/AccountsPanel.qml | 30 ++++++++++++ src/qml/views/DashboardView.qml | 4 ++ 5 files changed, 101 insertions(+), 20 deletions(-) diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index 8d0249a..f32ca99 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -125,8 +125,10 @@ void LEZWalletBackend::openIfPathsConfigured() if (err == WALLET_FFI_SUCCESS) { qDebug() << "LEZWalletBackend: wallet opened successfully"; setIsWalletOpen(true); - refreshAccounts(); - refreshBlockHeights(); + QJsonArray arr = m_logos->logos_execution_zone.list_accounts(); + m_accountModel->replaceFromJsonArray(arr); + fetchAndUpdateBlockHeights(); + startChunkedSync(); refreshSequencerAddr(); } else { qWarning() << "LEZWalletBackend: failed to open wallet, error" << err @@ -138,21 +140,66 @@ void LEZWalletBackend::refreshAccounts() { QJsonArray arr = m_logos->logos_execution_zone.list_accounts(); m_accountModel->replaceFromJsonArray(arr); - refreshBalances(); + fetchAndUpdateBlockHeights(); + if (!m_syncing) + startChunkedSync(); } void LEZWalletBackend::refreshBalances() { - refreshBlockHeights(); - syncToBlock(currentBlockHeight()); + fetchAndUpdateBlockHeights(); + if (!m_syncing) + startChunkedSync(); +} + +void LEZWalletBackend::startChunkedSync() +{ + m_syncTarget = static_cast(currentBlockHeight()); + if (m_syncTarget == 0 || static_cast(lastSyncedBlock()) >= m_syncTarget) { + updateBalances(); + return; + } + m_syncing = true; + syncNextChunk(); +} + +void LEZWalletBackend::syncNextChunk() +{ + const quint64 synced = static_cast(lastSyncedBlock()); + if (synced >= m_syncTarget) { + m_syncing = false; + fetchAndUpdateBlockHeights(); + updateBalances(); + return; + } + const quint64 next = qMin(synced + SYNC_CHUNK_SIZE, m_syncTarget); + m_logos->logos_execution_zone.sync_to_block(next); + // Only read lastSyncedBlock between chunks — avoids a sequencer network + // call (get_current_block_height) on every iteration. + const int lastVal = m_logos->logos_execution_zone.get_last_synced_block(); + if (lastSyncedBlock() != lastVal) + setLastSyncedBlock(lastVal); + QTimer::singleShot(0, this, &LEZWalletBackend::syncNextChunk); +} + +void LEZWalletBackend::updateBalances() +{ if (!m_accountModel) return; + bool anyFailed = false; 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(); const bool isPub = m_accountModel->data(idx, LEZWalletAccountModel::IsPublicRole).toBool(); - m_accountModel->setBalanceByAddress(addr, getBalance(addr, isPub)); + const QString bal = getBalance(addr, isPub); + if (!bal.isEmpty()) + m_accountModel->setBalanceByAddress(addr, bal); + else + anyFailed = true; } - saveWallet(); + if (anyFailed) + QTimer::singleShot(3000, this, &LEZWalletBackend::updateBalances); + else + saveWallet(); } void LEZWalletBackend::fetchAndUpdateBlockHeights() @@ -165,16 +212,6 @@ void LEZWalletBackend::fetchAndUpdateBlockHeights() setCurrentBlockHeight(currentVal); } -void LEZWalletBackend::refreshBlockHeights() -{ - fetchAndUpdateBlockHeights(); - if (currentBlockHeight() > 0 - && lastSyncedBlock() < currentBlockHeight() - && syncToBlock(currentBlockHeight())) - { - fetchAndUpdateBlockHeights(); - } -} void LEZWalletBackend::refreshSequencerAddr() { @@ -331,7 +368,6 @@ bool LEZWalletBackend::createNew(QString configPath, QString storagePath, QStrin persistStoragePath(localStoragePath); setIsWalletOpen(true); refreshAccounts(); - refreshBlockHeights(); refreshSequencerAddr(); return true; } diff --git a/src/LEZWalletBackend.h b/src/LEZWalletBackend.h index 2d20cc7..7706d8d 100644 --- a/src/LEZWalletBackend.h +++ b/src/LEZWalletBackend.h @@ -48,16 +48,25 @@ public slots: bool createNew(QString configPath, QString storagePath, QString password, QString sequencerAddr) override; void copyToClipboard(QString text) override; +private slots: + void syncNextChunk(); + private: void persistConfigPath(const QString& path); void persistStoragePath(const QString& path); void applySequencerAddrToConfig(const QString& configPath, const QString& sequencerAddr); - void refreshBlockHeights(); + void fetchAndUpdateBlockHeights(); + void startChunkedSync(); + + void updateBalances(); void refreshSequencerAddr(); void saveWallet(); - void fetchAndUpdateBlockHeights(); void openIfPathsConfigured(); + bool m_syncing = false; + quint64 m_syncTarget = 0; + static constexpr quint64 SYNC_CHUNK_SIZE = 100; + LEZWalletAccountModel* m_accountModel; LEZAccountFilterModel* m_filteredAccountModel; LEZAccountFilterModel* m_privateAccountModel; diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index ebf6152..efb7136 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -137,6 +137,8 @@ Rectangle { accountModel: root.accountModel publicAccountModel: root.publicAccountModel privateAccountModel: root.privateAccountModel + lastSyncedBlock: backend ? backend.lastSyncedBlock : 0 + currentBlockHeight: backend ? backend.currentBlockHeight : 0 onCreatePublicAccountRequested: { if (!backend) { console.warn("backend is null"); return } diff --git a/src/qml/views/AccountsPanel.qml b/src/qml/views/AccountsPanel.qml index f6ff14a..957bccb 100644 --- a/src/qml/views/AccountsPanel.qml +++ b/src/qml/views/AccountsPanel.qml @@ -13,6 +13,8 @@ Rectangle { // --- Public API: data in --- property var accountModel: null + property int lastSyncedBlock: 0 + property int currentBlockHeight: 0 // --- Public API: signals out --- signal createPublicAccountRequested() @@ -57,6 +59,34 @@ Rectangle { } } + // Sync progress + ColumnLayout { + Layout.fillWidth: true + spacing: Theme.spacing.xsmall + visible: root.currentBlockHeight > 0 && root.lastSyncedBlock < root.currentBlockHeight + + RowLayout { + Layout.fillWidth: true + LogosText { + text: qsTr("Syncing") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + Item { Layout.fillWidth: true } + LogosText { + text: root.lastSyncedBlock + " / " + root.currentBlockHeight + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + } + ProgressBar { + Layout.fillWidth: true + from: 0 + to: root.currentBlockHeight + value: root.lastSyncedBlock + } + } + // Empty state (when no real model and we don't show showcase) LogosText { Layout.fillWidth: true diff --git a/src/qml/views/DashboardView.qml b/src/qml/views/DashboardView.qml index 84ebb12..c3d70cf 100644 --- a/src/qml/views/DashboardView.qml +++ b/src/qml/views/DashboardView.qml @@ -15,6 +15,8 @@ Rectangle { property string transferResult: "" property bool transferResultIsError: false property bool transferPending: false + property int lastSyncedBlock: 0 + property int currentBlockHeight: 0 // --- Public API: output signals (parent connects and calls backend) --- signal createPublicAccountRequested() @@ -41,6 +43,8 @@ Rectangle { Layout.fillHeight: true accountModel: root.accountModel + lastSyncedBlock: root.lastSyncedBlock + currentBlockHeight: root.currentBlockHeight onCreatePublicAccountRequested: root.createPublicAccountRequested() onCreatePrivateAccountRequested: root.createPrivateAccountRequested() From 85e4e0e2d7d26f5dea01ed849119a17f40521c7a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 22:39:25 -0300 Subject: [PATCH 10/14] use base58 --- src/qml/Base58.js | 71 ++++++++++++++++++++++++++++ src/qml/controls/AccountComboBox.qml | 3 +- src/qml/controls/AccountDelegate.qml | 5 +- src/qml/views/AccountsPanel.qml | 16 +++++++ src/qml/views/TransferPanel.qml | 6 ++- 5 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 src/qml/Base58.js diff --git a/src/qml/Base58.js b/src/qml/Base58.js new file mode 100644 index 0000000..9477e1e --- /dev/null +++ b/src/qml/Base58.js @@ -0,0 +1,71 @@ +.pragma library + +const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + +// Encode a hex string to base58. +function encode(hexStr) { + if (!hexStr || hexStr.length === 0) return "" + const clean = hexStr.toLowerCase().replace(/^0x/, "") + if (clean.length === 0 || clean.length % 2 !== 0) return hexStr + + const bytes = [] + for (let i = 0; i < clean.length; i += 2) + bytes.push(parseInt(clean.substr(i, 2), 16)) + + let leadingZeros = 0 + while (leadingZeros < bytes.length && bytes[leadingZeros] === 0) + leadingZeros++ + + // digits[i] holds base-58 digits, least-significant first. + // For each input byte, multiply existing digits by 256 and add the byte. + const digits = [] + for (let i = 0; i < bytes.length; i++) { + let carry = bytes[i] + for (let j = 0; j < digits.length; j++) { + carry += digits[j] * 256 + digits[j] = carry % 58 + carry = Math.floor(carry / 58) + } + while (carry > 0) { + digits.push(carry % 58) + carry = Math.floor(carry / 58) + } + } + + let result = "1".repeat(leadingZeros) + for (let i = digits.length - 1; i >= 0; i--) + result += ALPHABET[digits[i]] + return result +} + +// Decode a base58 string to hex. +function decode(b58Str) { + if (!b58Str || b58Str.length === 0) return "" + + let leadingZeros = 0 + while (leadingZeros < b58Str.length && b58Str[leadingZeros] === "1") + leadingZeros++ + + // digits[i] holds base-256 (byte) digits, least-significant first. + // For each input char, multiply existing digits by 58 and add the char value. + const digits = [] + for (let i = 0; i < b58Str.length; i++) { + const idx = ALPHABET.indexOf(b58Str[i]) + if (idx < 0) return "" + let carry = idx + for (let j = 0; j < digits.length; j++) { + carry += digits[j] * 58 + digits[j] = carry % 256 + carry = Math.floor(carry / 256) + } + while (carry > 0) { + digits.push(carry % 256) + carry = Math.floor(carry / 256) + } + } + + let hex = "00".repeat(leadingZeros) + for (let i = digits.length - 1; i >= 0; i--) + hex += (digits[i] < 16 ? "0" : "") + digits[i].toString(16) + return hex +} diff --git a/src/qml/controls/AccountComboBox.qml b/src/qml/controls/AccountComboBox.qml index 0dafffe..ab213df 100644 --- a/src/qml/controls/AccountComboBox.qml +++ b/src/qml/controls/AccountComboBox.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts import Logos.Theme import Logos.Controls +import "../Base58.js" as Base58 ComboBox { id: root @@ -45,7 +46,7 @@ ComboBox { selectByMouse: true font.pixelSize: Theme.typography.secondaryText color: Theme.palette.text - text: root.displayText + text: root.currentValue ? ("Account " + Base58.encode(root.currentValue).substring(0, 6)) : root.displayText verticalAlignment: Text.AlignVCenter clip: true } diff --git a/src/qml/controls/AccountDelegate.qml b/src/qml/controls/AccountDelegate.qml index 676d8b6..f45f739 100644 --- a/src/qml/controls/AccountDelegate.qml +++ b/src/qml/controls/AccountDelegate.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts import Logos.Theme import Logos.Controls +import "../Base58.js" as Base58 ItemDelegate { id: root @@ -68,7 +69,7 @@ ItemDelegate { id: addressLabel Layout.fillWidth: true verticalAlignment: Text.AlignVCenter - text: model.address ?? "" + text: Base58.encode(model.address ?? "") font.pixelSize: Theme.typography.secondaryText color: Theme.palette.textMuted elide: Text.ElideMiddle @@ -76,7 +77,7 @@ ItemDelegate { LogosCopyButton { Layout.preferredHeight: 40 Layout.preferredWidth: 40 - onCopyText: root.copyRequested(model.address) + onCopyText: root.copyRequested(Base58.encode(model.address ?? "")) visible: addressLabel.text icon.color: Theme.palette.textMuted } diff --git a/src/qml/views/AccountsPanel.qml b/src/qml/views/AccountsPanel.qml index 957bccb..797c5e1 100644 --- a/src/qml/views/AccountsPanel.qml +++ b/src/qml/views/AccountsPanel.qml @@ -84,6 +84,22 @@ Rectangle { from: 0 to: root.currentBlockHeight value: root.lastSyncedBlock + + contentItem: Item { + Rectangle { + width: parent.width * (root.currentBlockHeight > 0 + ? root.lastSyncedBlock / root.currentBlockHeight : 0) + height: parent.height + radius: height / 2 + color: Theme.palette.overlayOrange + } + } + + background: Rectangle { + implicitHeight: 6 + radius: height / 2 + color: Theme.palette.backgroundElevated + } } } diff --git a/src/qml/views/TransferPanel.qml b/src/qml/views/TransferPanel.qml index 77474da..0a1b05c 100644 --- a/src/qml/views/TransferPanel.qml +++ b/src/qml/views/TransferPanel.qml @@ -6,6 +6,7 @@ import Logos.Theme import Logos.Controls import "../controls" +import "../Base58.js" as Base58 Rectangle { id: root @@ -181,10 +182,11 @@ Rectangle { onClicked: { var fromId = fromFilterCount > 0 && fromCombo.currentIndex >= 0 ? (fromCombo.currentValue ?? "") - : manualFromField.text.trim() + : Base58.decode(manualFromField.text.trim()) + var rawTo = toField.text.trim() var toAddress = (d.toComboPermanent || (d.useOwnedAccountForTo && toCombo.currentIndex >= 0)) ? (toCombo.currentValue ?? "") - : toField.text.trim() + : (rawTo.startsWith("{") ? rawTo : Base58.decode(rawTo)) var amount = amountField.text.trim() if (fromId.length > 0 && toAddress.length > 0 && amount.length > 0) { if (d.isPublicTab) From c3754703584d78d900c6d82dea26b7e49062e146 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 22:57:19 -0300 Subject: [PATCH 11/14] private account copy button copies public keys json --- src/qml/ExecutionZoneWalletView.qml | 10 ++++++++++ src/qml/controls/AccountComboBox.qml | 2 ++ src/qml/controls/AccountDelegate.qml | 5 ++++- src/qml/views/AccountsPanel.qml | 2 ++ src/qml/views/DashboardView.qml | 2 ++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index efb7136..e7d2b34 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -227,6 +227,16 @@ Rectangle { clipHelper.selectAll() clipHelper.copy() } + onCopyPublicKeysRequested: (accountIdHex) => { + if (!backend) return + logos.watch(backend.getPrivateAccountKeys(accountIdHex), + function(keys) { + clipHelper.text = keys + clipHelper.selectAll() + clipHelper.copy() + }, + function(error) { console.warn("getPrivateAccountKeys failed:", error) }) + } } } } diff --git a/src/qml/controls/AccountComboBox.qml b/src/qml/controls/AccountComboBox.qml index ab213df..8c81196 100644 --- a/src/qml/controls/AccountComboBox.qml +++ b/src/qml/controls/AccountComboBox.qml @@ -12,6 +12,7 @@ ComboBox { // Forwarded from AccountDelegate's copy button — bubble up to the parent // view, which calls backend.copyToClipboard(). signal copyRequested(string text) + signal copyPublicKeysRequested(string accountIdHex) leftPadding: 12 rightPadding: 12 @@ -61,6 +62,7 @@ ComboBox { width: root.popup ? (root.popup.width - root.popup.leftPadding - root.popup.rightPadding) : 368 highlighted: root.highlightedIndex === index onCopyRequested: (text) => root.copyRequested(text) + onCopyPublicKeysRequested: (id) => root.copyPublicKeysRequested(id) } popup: Popup { diff --git a/src/qml/controls/AccountDelegate.qml b/src/qml/controls/AccountDelegate.qml index f45f739..698d346 100644 --- a/src/qml/controls/AccountDelegate.qml +++ b/src/qml/controls/AccountDelegate.qml @@ -14,6 +14,7 @@ ItemDelegate { // the global QML scope for `backend` since it now lives behind the // logos.module() bridge in the parent view. signal copyRequested(string text) + signal copyPublicKeysRequested(string accountIdHex) leftPadding: Theme.spacing.medium rightPadding: Theme.spacing.medium @@ -77,7 +78,9 @@ ItemDelegate { LogosCopyButton { Layout.preferredHeight: 40 Layout.preferredWidth: 40 - onCopyText: root.copyRequested(Base58.encode(model.address ?? "")) + onCopyText: model.isPublic + ? root.copyRequested(Base58.encode(model.address ?? "")) + : root.copyPublicKeysRequested(model.address ?? "") visible: addressLabel.text icon.color: Theme.palette.textMuted } diff --git a/src/qml/views/AccountsPanel.qml b/src/qml/views/AccountsPanel.qml index 797c5e1..aefce03 100644 --- a/src/qml/views/AccountsPanel.qml +++ b/src/qml/views/AccountsPanel.qml @@ -21,6 +21,7 @@ Rectangle { signal createPrivateAccountRequested() signal fetchBalancesRequested() signal copyRequested(string text) + signal copyPublicKeysRequested(string accountIdHex) radius: Theme.spacing.radiusXlarge color: Theme.palette.backgroundSecondary @@ -129,6 +130,7 @@ Rectangle { delegate: AccountDelegate { width: listView.width onCopyRequested: (text) => root.copyRequested(text) + onCopyPublicKeysRequested: (id) => root.copyPublicKeysRequested(id) } } diff --git a/src/qml/views/DashboardView.qml b/src/qml/views/DashboardView.qml index c3d70cf..0172165 100644 --- a/src/qml/views/DashboardView.qml +++ b/src/qml/views/DashboardView.qml @@ -29,6 +29,7 @@ Rectangle { signal transferShieldedOwnedRequested(string fromAccountId, string toAccountId, string amount) signal transferDeshieldedRequested(string fromAccountId, string toAccountId, string amount) signal copyRequested(string copyText) + signal copyPublicKeysRequested(string accountIdHex) color: Theme.palette.background @@ -50,6 +51,7 @@ Rectangle { onCreatePrivateAccountRequested: root.createPrivateAccountRequested() onFetchBalancesRequested: root.fetchBalancesRequested() onCopyRequested: (text) => root.copyRequested(text) + onCopyPublicKeysRequested: (id) => root.copyPublicKeysRequested(id) } TransferPanel { From 3efa884128b4e999e96782cd0bd479d6dfcc80c9 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 5 Jun 2026 23:24:28 -0300 Subject: [PATCH 12/14] add 'use owned account' button on public and deshielded variants --- src/qml/ExecutionZoneWalletView.qml | 7 +++++++ src/qml/views/DashboardView.qml | 2 ++ src/qml/views/TransferPanel.qml | 16 ++++++++-------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index e7d2b34..1b46a66 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -77,6 +77,7 @@ Rectangle { } dashboardView.transferResult = msg dashboardView.transferResultIsError = isError + dashboardView.transferTxHash = (obj && obj.tx_hash) ? obj.tx_hash : "" } } @@ -165,6 +166,7 @@ Rectangle { function(error) { dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true + dashboardView.transferTxHash = "" }) } onTransferPrivateRequested: (fromId, toKeysJsonOrAddress, amount) => { @@ -176,6 +178,7 @@ Rectangle { dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true + dashboardView.transferTxHash = "" }) } onTransferPrivateOwnedRequested: (fromId, toAccountId, amount) => { @@ -187,6 +190,7 @@ Rectangle { dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true + dashboardView.transferTxHash = "" }) } onTransferShieldedRequested: (fromId, toKeysJsonOrAddress, amount) => { @@ -198,6 +202,7 @@ Rectangle { dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true + dashboardView.transferTxHash = "" }) } onTransferShieldedOwnedRequested: (fromId, toAccountId, amount) => { @@ -209,6 +214,7 @@ Rectangle { dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true + dashboardView.transferTxHash = "" }) } onTransferDeshieldedRequested: (fromId, toAccountId, amount) => { @@ -220,6 +226,7 @@ Rectangle { dashboardView.transferPending = false dashboardView.transferResult = qsTr("Error: %1").arg(error) dashboardView.transferResultIsError = true + dashboardView.transferTxHash = "" }) } onCopyRequested: (copyText) => { diff --git a/src/qml/views/DashboardView.qml b/src/qml/views/DashboardView.qml index 0172165..d412515 100644 --- a/src/qml/views/DashboardView.qml +++ b/src/qml/views/DashboardView.qml @@ -13,6 +13,7 @@ Rectangle { property var publicAccountModel: null property var privateAccountModel: null property string transferResult: "" + property string transferTxHash: "" property bool transferResultIsError: false property bool transferPending: false property int lastSyncedBlock: 0 @@ -61,6 +62,7 @@ Rectangle { publicAccountModel: root.publicAccountModel privateAccountModel: root.privateAccountModel transferResult: root.transferResult + transferTxHash: root.transferTxHash transferResultIsError: root.transferResultIsError transferPending: root.transferPending diff --git a/src/qml/views/TransferPanel.qml b/src/qml/views/TransferPanel.qml index 0a1b05c..d212e95 100644 --- a/src/qml/views/TransferPanel.qml +++ b/src/qml/views/TransferPanel.qml @@ -15,6 +15,7 @@ Rectangle { property var publicAccountModel: null property var privateAccountModel: null property string transferResult: "" + property string transferTxHash: "" property bool transferResultIsError: false property bool transferPending: false @@ -37,9 +38,8 @@ Rectangle { readonly property bool isPrivateTab: transferTypeBar.currentIndex === 1 readonly property bool isShieldedTab: transferTypeBar.currentIndex === 2 readonly property bool isDeshieldedTab: transferTypeBar.currentIndex === 3 - readonly property bool showOwnedOption: isPrivateTab || isShieldedTab - readonly property bool toComboPermanent: isDeshieldedTab - readonly property bool toAddressValid: (toComboPermanent || (showOwnedOption && useOwnedAccountForTo)) + readonly property bool showOwnedOption: true + readonly property bool toAddressValid: useOwnedAccountForTo ? (toFilterCount > 0 && toCombo.currentIndex >= 0) : (toField && toField.text.trim().length > 0) readonly property bool needsProof: isPrivateTab || isShieldedTab || isDeshieldedTab @@ -142,15 +142,15 @@ Rectangle { LogosTextField { id: toField Layout.fillWidth: true - placeholderText: d.isPublicTab ? qsTr("Recipient address") : qsTr("Recipient private keys (JSON)") - visible: !d.toComboPermanent && (!d.showOwnedOption || !d.useOwnedAccountForTo) + placeholderText: (d.isPublicTab || d.isDeshieldedTab) ? qsTr("Recipient address") : qsTr("Recipient private keys (JSON)") + visible: !d.useOwnedAccountForTo } AccountComboBox { id: toCombo Layout.fillWidth: true model: (d.isPublicTab || d.isDeshieldedTab) ? root.publicAccountModel : root.privateAccountModel - visible: d.toComboPermanent || (d.showOwnedOption && d.useOwnedAccountForTo && toFilterCount > 0) + visible: d.useOwnedAccountForTo && toFilterCount > 0 onCopyRequested: (text) => root.copyRequested(text) } } @@ -184,7 +184,7 @@ Rectangle { ? (fromCombo.currentValue ?? "") : Base58.decode(manualFromField.text.trim()) var rawTo = toField.text.trim() - var toAddress = (d.toComboPermanent || (d.useOwnedAccountForTo && toCombo.currentIndex >= 0)) + var toAddress = (d.useOwnedAccountForTo && toCombo.currentIndex >= 0) ? (toCombo.currentValue ?? "") : (rawTo.startsWith("{") ? rawTo : Base58.decode(rawTo)) var amount = amountField.text.trim() @@ -246,7 +246,7 @@ Rectangle { Layout.alignment: Qt.AlignRight Layout.preferredHeight: 40 Layout.preferredWidth: 40 - onCopyText: root.copyRequested(root.transferResult) + onCopyText: root.copyRequested(root.transferTxHash || root.transferResult) visible: resultText.text } } From af12a03295072d26892d3069f85e2909091916bf Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 6 Jun 2026 01:24:27 -0300 Subject: [PATCH 13/14] minor refactor --- src/LEZAccountFilterModel.cpp | 2 +- src/LEZWalletAccountModel.cpp | 14 +++++++------- src/LEZWalletAccountModel.h | 6 +++--- src/LEZWalletBackend.cpp | 4 ++-- src/qml/controls/AccountComboBox.qml | 4 ++-- src/qml/controls/AccountDelegate.qml | 8 ++++---- src/qml/popups/CreateAccountDialog.qml | 2 +- src/qml/views/TransferPanel.qml | 4 ++-- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/LEZAccountFilterModel.cpp b/src/LEZAccountFilterModel.cpp index c43046f..4562985 100644 --- a/src/LEZAccountFilterModel.cpp +++ b/src/LEZAccountFilterModel.cpp @@ -31,7 +31,7 @@ bool LEZAccountFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& s int LEZAccountFilterModel::rowForAddress(const QString& address) const { for (int i = 0; i < rowCount(); ++i) { - if (data(index(i, 0), LEZWalletAccountModel::AddressRole).toString() == address) + if (data(index(i, 0), LEZWalletAccountModel::AccountIdRole).toString() == address) return i; } return -1; diff --git a/src/LEZWalletAccountModel.cpp b/src/LEZWalletAccountModel.cpp index 35b9c1b..b9df171 100644 --- a/src/LEZWalletAccountModel.cpp +++ b/src/LEZWalletAccountModel.cpp @@ -21,7 +21,7 @@ QVariant LEZWalletAccountModel::data(const QModelIndex& index, int role) const const LEZWalletAccountEntry& e = m_entries.at(index.row()); switch (role) { case NameRole: return e.name; - case AddressRole: return e.address; + case AccountIdRole: return e.accountId; case BalanceRole: return e.balance; case IsPublicRole: return e.isPublic; default: return QVariant(); @@ -32,7 +32,7 @@ QHash LEZWalletAccountModel::roleNames() const { return { { NameRole, "name" }, - { AddressRole, "address" }, + { AccountIdRole, "accountId" }, { BalanceRole, "balance" }, { IsPublicRole, "isPublic" } }; @@ -48,13 +48,13 @@ void LEZWalletAccountModel::replaceFromJsonArray(const QJsonArray& arr) e.balance = QString(); if (v.isObject()) { const QJsonObject obj = v.toObject(); - e.address = obj.value(QStringLiteral("account_id")).toString(); + e.accountId = obj.value(QStringLiteral("account_id")).toString(); e.isPublic = obj.value(QStringLiteral("is_public")).toBool(true); } else { - e.address = v.toString(); + e.accountId = v.toString(); e.isPublic = true; } - e.name = QStringLiteral("Account %1").arg(e.address.left(4)); + e.name = QString(); m_entries.append(e); } endResetModel(); @@ -62,10 +62,10 @@ void LEZWalletAccountModel::replaceFromJsonArray(const QJsonArray& arr) emit countChanged(); } -void LEZWalletAccountModel::setBalanceByAddress(const QString& address, const QString& balance) +void LEZWalletAccountModel::setBalanceByAccountId(const QString& accountId, const QString& balance) { for (int i = 0; i < m_entries.size(); ++i) { - if (m_entries.at(i).address == address) { + if (m_entries.at(i).accountId == accountId) { if (m_entries.at(i).balance != balance) { m_entries[i].balance = balance; QModelIndex idx = index(i, 0); diff --git a/src/LEZWalletAccountModel.h b/src/LEZWalletAccountModel.h index 6092c8f..8efb086 100644 --- a/src/LEZWalletAccountModel.h +++ b/src/LEZWalletAccountModel.h @@ -6,7 +6,7 @@ struct LEZWalletAccountEntry { QString name; - QString address; + QString accountId; QString balance; bool isPublic = true; }; @@ -17,7 +17,7 @@ class LEZWalletAccountModel : public QAbstractListModel { public: enum Role { NameRole = Qt::UserRole + 1, - AddressRole, + AccountIdRole, BalanceRole, IsPublicRole }; @@ -30,7 +30,7 @@ public: QHash roleNames() const override; void replaceFromJsonArray(const QJsonArray& arr); - void setBalanceByAddress(const QString& address, const QString& balance); + void setBalanceByAccountId(const QString& accountId, const QString& balance); int count() const { return m_entries.size(); } signals: diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index f32ca99..d1789d1 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -188,11 +188,11 @@ void LEZWalletBackend::updateBalances() bool anyFailed = false; 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(); + const QString addr = m_accountModel->data(idx, LEZWalletAccountModel::AccountIdRole).toString(); const bool isPub = m_accountModel->data(idx, LEZWalletAccountModel::IsPublicRole).toBool(); const QString bal = getBalance(addr, isPub); if (!bal.isEmpty()) - m_accountModel->setBalanceByAddress(addr, bal); + m_accountModel->setBalanceByAccountId(addr, bal); else anyFailed = true; } diff --git a/src/qml/controls/AccountComboBox.qml b/src/qml/controls/AccountComboBox.qml index 8c81196..8d42c27 100644 --- a/src/qml/controls/AccountComboBox.qml +++ b/src/qml/controls/AccountComboBox.qml @@ -18,7 +18,7 @@ ComboBox { rightPadding: 12 implicitHeight: 40 textRole: "name" - valueRole: "address" + valueRole: "accountId" background: Rectangle { radius: Theme.spacing.radiusSmall @@ -47,7 +47,7 @@ ComboBox { selectByMouse: true font.pixelSize: Theme.typography.secondaryText color: Theme.palette.text - text: root.currentValue ? ("Account " + Base58.encode(root.currentValue).substring(0, 6)) : root.displayText + text: root.currentValue ? ("Account " + Base58.encode(root.currentValue).substring(0, 4)) : root.displayText verticalAlignment: Text.AlignVCenter clip: true } diff --git a/src/qml/controls/AccountDelegate.qml b/src/qml/controls/AccountDelegate.qml index 698d346..9c4be1f 100644 --- a/src/qml/controls/AccountDelegate.qml +++ b/src/qml/controls/AccountDelegate.qml @@ -35,7 +35,7 @@ ItemDelegate { spacing: Theme.spacing.small LogosText { - text: model.name ?? "" + text: model.name || ("Account " + Base58.encode(model.accountId ?? "").slice(0, 4)) font.pixelSize: Theme.typography.secondaryText font.bold: true } @@ -70,7 +70,7 @@ ItemDelegate { id: addressLabel Layout.fillWidth: true verticalAlignment: Text.AlignVCenter - text: Base58.encode(model.address ?? "") + text: Base58.encode(model.accountId ?? "") font.pixelSize: Theme.typography.secondaryText color: Theme.palette.textMuted elide: Text.ElideMiddle @@ -79,8 +79,8 @@ ItemDelegate { Layout.preferredHeight: 40 Layout.preferredWidth: 40 onCopyText: model.isPublic - ? root.copyRequested(Base58.encode(model.address ?? "")) - : root.copyPublicKeysRequested(model.address ?? "") + ? root.copyRequested(Base58.encode(model.accountId ?? "")) + : root.copyPublicKeysRequested(model.accountId ?? "") visible: addressLabel.text icon.color: Theme.palette.textMuted } diff --git a/src/qml/popups/CreateAccountDialog.qml b/src/qml/popups/CreateAccountDialog.qml index d07c3f2..365a62d 100644 --- a/src/qml/popups/CreateAccountDialog.qml +++ b/src/qml/popups/CreateAccountDialog.qml @@ -64,7 +64,7 @@ Popup { LogosText { text: tabBar.currentIndex === 0 - ? qsTr("Address visible. Balance on-chain.") + ? qsTr("Account ID visible. Balance on-chain.") : qsTr("Private balance and activity.") font.pixelSize: Theme.typography.secondaryText color: Theme.palette.textSecondary diff --git a/src/qml/views/TransferPanel.qml b/src/qml/views/TransferPanel.qml index d212e95..73d2f98 100644 --- a/src/qml/views/TransferPanel.qml +++ b/src/qml/views/TransferPanel.qml @@ -107,7 +107,7 @@ Rectangle { LogosTextField { id: manualFromField Layout.fillWidth: true - placeholderText: qsTr("Paste or type from address") + placeholderText: qsTr("Paste or type from account ID") visible: fromFilterCount === 0 } @@ -142,7 +142,7 @@ Rectangle { LogosTextField { id: toField Layout.fillWidth: true - placeholderText: (d.isPublicTab || d.isDeshieldedTab) ? qsTr("Recipient address") : qsTr("Recipient private keys (JSON)") + placeholderText: (d.isPublicTab || d.isDeshieldedTab) ? qsTr("Recipient account ID") : qsTr("Recipient public keys (JSON)") visible: !d.useOwnedAccountForTo } From 940a9233f0dc5cb6cda0467ac9c00af8a8582e84 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Sat, 6 Jun 2026 03:14:00 -0300 Subject: [PATCH 14/14] allow relative paths for wallet files --- src/LEZWalletBackend.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index d1789d1..5ed2c17 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -47,9 +47,12 @@ namespace { // 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; + QString p = path; + if (p.startsWith(QLatin1Char('~'))) + p = QDir::homePath() + p.mid(1); + if (p.startsWith("file://") || p.contains("/")) + return QUrl::fromUserInput(p).toLocalFile(); + return p; } }