diff --git a/flake.lock b/flake.lock index 4b3e336..6a380b9 100644 --- a/flake.lock +++ b/flake.lock @@ -528,11 +528,11 @@ ] }, "locked": { - "lastModified": 1771838299, - "narHash": "sha256-Uf45wbh2q5ewoiw4u04YImc2Gij3OXIfbB5NYpUm5dw=", + "lastModified": 1771862755, + "narHash": "sha256-uRQztNMMLhHnQw0P2ZisB1LZ1V7pNqkZRI3pe0wUIts=", "owner": "logos-co", "repo": "logos-design-system", - "rev": "fc6f52d85a008aa1bb513f6b42648df4bcf0713d", + "rev": "4d55203607759c9ef042be4505bbc6efffbbd444", "type": "github" }, "original": { @@ -548,11 +548,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1771420202, - "narHash": "sha256-r3lO12wvYPm6Xe7YFGK40iYe+1OID5YlGlup6RGgvs8=", + "lastModified": 1771604479, + "narHash": "sha256-DxgL4uT8+F0ALuEpENkbJ0OcyNhdFgX8Mr8tezMnZ5E=", "owner": "logos-blockchain", "repo": "lssa", - "rev": "89ce9f322a1fc4214ec818e094e6efc97be02d9c", + "rev": "bc84d5cf31b3f6967060c35a5b98618d99780ade", "type": "github" }, "original": { @@ -574,14 +574,17 @@ ] }, "locked": { - "lastModified": 1771839856, - "narHash": "sha256-MtgK/B6mx9xyojbRc6RY8vHuMkMrS4XMV7BKcO6zK/I=", - "path": "/Users/khushboomehta/Documents/logos/logos-execution-zone-module", - "type": "path" + "lastModified": 1771922567, + "narHash": "sha256-ObbXljdS/hR0PCgBosZgzwMHWRxvSGWmaxgFEy3OvOE=", + "owner": "logos-blockchain", + "repo": "logos-execution-zone-module", + "rev": "8474602359b69f56cfad232b052b52ae2701f74c", + "type": "github" }, "original": { - "path": "/Users/khushboomehta/Documents/logos/logos-execution-zone-module", - "type": "path" + "owner": "logos-blockchain", + "repo": "logos-execution-zone-module", + "type": "github" } }, "logos-liblogos": { @@ -778,11 +781,11 @@ ] }, "locked": { - "lastModified": 1770837874, - "narHash": "sha256-wr75lv1q4U1FS5+l/6ypwzJFJe06l2RyUvx1npoRS88=", + "lastModified": 1771871578, + "narHash": "sha256-6Mu3cmdhd8e7i+n8OWcaIBye+i12gwlwt1fhd9QCbCI=", "owner": "logos-co", "repo": "logos-liblogos", - "rev": "e3741c01fd3abf6b7bd9ff2fa8edf89c41fc0cea", + "rev": "19d29d4ef99292d9285b3a561cb7ea8029be3b74", "type": "github" }, "original": { diff --git a/src/LEZWalletBackend.cpp b/src/LEZWalletBackend.cpp index 7a97567..4e5b7e3 100644 --- a/src/LEZWalletBackend.cpp +++ b/src/LEZWalletBackend.cpp @@ -1,6 +1,8 @@ #include "LEZWalletBackend.h" #include +#include #include +#include #include #include #include @@ -12,6 +14,19 @@ namespace { 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. + QString amountToLe16Hex(const QString& amountStr) { + const QString trimmed = amountStr.trimmed(); + if (trimmed.isEmpty()) return QString(); + bool parseOk = false; + const quint64 value = trimmed.toULongLong(&parseOk); + if (!parseOk) return QString(); + uint8_t bytes[16] = {0}; + for (int i = 0; i < 8; ++i) + bytes[i] = static_cast((value >> (i * 8)) & 0xff); + return QByteArray(reinterpret_cast(bytes), 16).toHex(); + } } LEZWalletBackend::LEZWalletBackend(LogosAPI* logosAPI, QObject* parent) @@ -42,16 +57,21 @@ LEZWalletBackend::LEZWalletBackend(LogosAPI* logosAPI, QObject* parent) } 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); } LEZWalletBackend::~LEZWalletBackend() @@ -112,6 +132,7 @@ void LEZWalletBackend::refreshAccounts() } m_accountModel->replaceFromJsonArray(arr); emit accountModelChanged(); + refreshBalances(); } void LEZWalletBackend::refreshBalances() @@ -125,13 +146,11 @@ void LEZWalletBackend::refreshBalances() } } -void LEZWalletBackend::refreshBlockHeights() +void LEZWalletBackend::fetchAndUpdateBlockHeights() { if (!m_walletClient) return; - QVariant last = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "get_last_synced_block"); - QVariant current = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "get_current_block_height"); - quint64 lastVal = last.isValid() ? last.toULongLong() : 0; - quint64 currentVal = current.isValid() ? current.toULongLong() : 0; + const quint64 lastVal = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "get_last_synced_block").toULongLong(); + const quint64 currentVal = m_walletClient->invokeRemoteMethod(WALLET_MODULE_NAME, "get_current_block_height").toULongLong(); if (m_lastSyncedBlock != lastVal) { m_lastSyncedBlock = lastVal; emit lastSyncedBlockChanged(); @@ -142,6 +161,13 @@ void LEZWalletBackend::refreshBlockHeights() } } +void LEZWalletBackend::refreshBlockHeights() +{ + fetchAndUpdateBlockHeights(); + if (m_currentBlockHeight > 0 && m_lastSyncedBlock < m_currentBlockHeight && syncToBlock(m_currentBlockHeight)) + fetchAndUpdateBlockHeights(); +} + void LEZWalletBackend::refreshSequencerAddr() { if (!m_walletClient) return; @@ -205,11 +231,7 @@ bool LEZWalletBackend::syncToBlock(quint64 blockId) QVariant result = m_walletClient->invokeRemoteMethod( WALLET_MODULE_NAME, "sync_to_block", blockId); int err = result.isValid() ? result.toInt() : -1; - if (err == WALLET_FFI_SUCCESS) { - refreshBlockHeights(); - return true; - } - return false; + return err == WALLET_FFI_SUCCESS; } QString LEZWalletBackend::transferPublic( @@ -218,19 +240,33 @@ QString LEZWalletBackend::transferPublic( const QString& amountLe16Hex) { if (!m_walletClient) return QStringLiteral("Error: Module not initialized."); + const QString amountHex = amountToLe16Hex(amountLe16Hex); + if (amountHex.isEmpty()) return QStringLiteral("Error: Invalid amount."); QVariant result = m_walletClient->invokeRemoteMethod( - WALLET_MODULE_NAME, "transfer_public", fromHex, toHex, amountLe16Hex); + WALLET_MODULE_NAME, "transfer_public", fromHex, toHex, amountHex); return result.isValid() ? result.toString() : QStringLiteral("Error: Call failed."); } QString LEZWalletBackend::transferPrivate( const QString& fromHex, - const QString& toKeysJson, + const QString& toHex, const QString& amountLe16Hex) { if (!m_walletClient) return QStringLiteral("Error: Module not initialized."); + const QString amountHex = amountToLe16Hex(amountLe16Hex); + 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 (!keysPayload.startsWith(QLatin1Char('{'))) { + qDebug() << "LEZWalletBackend::transferPrivate: keysPayload is not JSON, resolving to 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, toKeysJson, amountLe16Hex); + WALLET_MODULE_NAME, "transfer_private", fromHex, keysPayload, amountHex); return result.isValid() ? result.toString() : QStringLiteral("Error: Call failed."); } @@ -269,3 +305,9 @@ int LEZWalletBackend::indexOfAddressInModel(QObject* model, const QString& addre } return -1; } + +void LEZWalletBackend::copyToClipboard(const QString& text) +{ + if (QGuiApplication::clipboard()) + QGuiApplication::clipboard()->setText(text); +} diff --git a/src/LEZWalletBackend.h b/src/LEZWalletBackend.h index bec5a36..16242aa 100644 --- a/src/LEZWalletBackend.h +++ b/src/LEZWalletBackend.h @@ -51,13 +51,14 @@ public: const QString& amountLe16Hex); Q_INVOKABLE QString transferPrivate( const QString& fromHex, - const QString& toKeysJson, + 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(); @@ -74,6 +75,7 @@ private: void refreshBlockHeights(); void refreshSequencerAddr(); void saveWallet(); + void fetchAndUpdateBlockHeights(); bool m_isWalletOpen; QString m_configPath; diff --git a/src/LEZWalletPlugin.cpp b/src/LEZWalletPlugin.cpp index 9688a69..07a1331 100644 --- a/src/LEZWalletPlugin.cpp +++ b/src/LEZWalletPlugin.cpp @@ -22,8 +22,8 @@ QWidget* LEZWalletPlugin::createWidget(LogosAPI* logosAPI) { LEZWalletBackend* backend = new LEZWalletBackend(logosAPI, quickWidget); quickWidget->rootContext()->setContextProperty("backend", backend); - QString qmlSource = "qrc:/qml/ExecutionZoneWalletView.qml"; - QString importPath = "qrc:/qml"; + QString qmlSource = "qrc:/lezwallet/qml/ExecutionZoneWalletView.qml"; + QString importPath = "qrc:/lezwallet/qml"; QString envPath = QString::fromUtf8(qgetenv("DEV_QML_PATH")).trimmed(); if (!envPath.isEmpty()) { diff --git a/src/qml/ExecutionZoneWalletView.qml b/src/qml/ExecutionZoneWalletView.qml index 9d00358..943b2e6 100644 --- a/src/qml/ExecutionZoneWalletView.qml +++ b/src/qml/ExecutionZoneWalletView.qml @@ -10,15 +10,67 @@ import "views" Rectangle { id: root + // Map wallet FFI error codes to user-facing strings. Matches lssa/wallet-ffi WalletFfiError enum. + QtObject { + id: ffiErrors + readonly property var codeToMessage: ({ + 0: qsTr("Success"), + 1: qsTr("Invalid argument (null pointer)"), + 2: qsTr("Invalid UTF-8 string"), + 3: qsTr("Wallet not initialized"), + 4: qsTr("Configuration error"), + 5: qsTr("Storage or persistence error"), + 6: qsTr("Network or RPC error"), + 7: qsTr("Account not found"), + 8: qsTr("Key not found for account"), + 9: qsTr("Insufficient funds"), + 10: qsTr("Invalid account ID format"), + 11: qsTr("Runtime error"), + 12: qsTr("Password required but not provided"), + 13: qsTr("Block synchronization error"), + 14: qsTr("Serialization error"), + 15: qsTr("Invalid type conversion"), + 16: qsTr("Invalid key value"), + 99: qsTr("Internal error") + }) + function format(errorMessage) { + if (!errorMessage || typeof errorMessage !== "string") + return errorMessage || "" + var match = errorMessage.match(/wallet FFI error (\d+)/) + if (match) { + var code = match[1] + var msg = codeToMessage[code] + if (msg) + return msg + return qsTr("Wallet error (code %1)").arg(code) + } + return errorMessage + } + } + + QtObject { + id: d + readonly property bool isWalletOpen: backend && backend.isWalletOpen + onIsWalletOpenChanged: { + if(isWalletOpen) { + stackView.push(mainView) + } else { + stackView.push(onboardingView) + } + } + } + color: Theme.palette.background StackView { + id: stackView anchors.fill: parent - initialItem: backend && backend.isWalletOpen ? mainView: onboardingView Component { id: onboardingView OnboardingView { + storePath: backend.storagePath + configPath: 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.") @@ -31,7 +83,6 @@ Rectangle { DashboardView { id: dashboardView accountModel: backend ? backend.accountModel : null - filteredAccountModel: backend ? backend.filteredAccountModel : null onCreatePublicAccountRequested: { if (!backend) { @@ -59,9 +110,25 @@ Rectangle { console.warning("backend is null") return } - dashboardView.transferResult = isPublic + var raw = isPublic ? backend.transferPublic(fromId, toAddress, amount) : backend.transferPrivate(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 } } } diff --git a/src/qml/controls/AccountDelegate.qml b/src/qml/controls/AccountDelegate.qml index 1d774cb..b90090a 100644 --- a/src/qml/controls/AccountDelegate.qml +++ b/src/qml/controls/AccountDelegate.qml @@ -8,47 +8,69 @@ import Logos.Controls ItemDelegate { id: root - implicitHeight: 80 leftPadding: Theme.spacing.medium rightPadding: Theme.spacing.medium topPadding: Theme.spacing.medium bottomPadding: Theme.spacing.medium background: Rectangle { - color: root.highlighted ? Theme.palette.backgroundMuted : "transparent" - radius: Theme.spacing.radiusSmall + color: root.highlighted || root.hovered ? + Theme.palette.backgroundMuted : + Theme.palette.backgroundTertiary + radius: Theme.spacing.radiusLarge } - contentItem: RowLayout { + contentItem: ColumnLayout { spacing: Theme.spacing.small - - LogosText { - text: model.name - font.pixelSize: Theme.typography.secondaryText - font.bold: true - } - - Rectangle { - Layout.preferredWidth: tagLabel.implicitWidth + Theme.spacing.small * 2 - Layout.preferredHeight: tagLabel.implicitHeight + 4 - radius: 2 - color: model.isPublic ? Theme.palette.backgroundElevated : Theme.palette.backgroundSecondary + RowLayout { + spacing: Theme.spacing.small LogosText { - id: tagLabel - anchors.centerIn: parent - text: model.isPublic ? qsTr("Public") : qsTr("Private") - font.pixelSize: Theme.typography.captionText - color: Theme.palette.textSecondary + text: model.name + font.pixelSize: Theme.typography.secondaryText + font.bold: true + } + + Rectangle { + Layout.preferredWidth: tagLabel.implicitWidth + Theme.spacing.small * 2 + Layout.preferredHeight: tagLabel.implicitHeight + 4 + radius: 4 + color: Theme.palette.backgroundSecondary + + LogosText { + id: tagLabel + anchors.centerIn: parent + text: model.isPublic ? qsTr("Public") : qsTr("Private") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + } + + Item { Layout.fillWidth: true } + + LogosText { + text: model.balance && model.balance.length > 0 ? model.balance : "—" + font.bold: true } } - Item { Layout.fillWidth: true } - LogosText { - text: model.balance && model.balance.length > 0 ? model.balance : "—" + id: addressLabel + verticalAlignment: Text.AlignVCenter + text: model.address && model.address.length > 9 + ? model.address.slice(0, 4) + "…" + model.address.slice(-5) + : (model.address || "") font.pixelSize: Theme.typography.secondaryText - color: Theme.palette.textSecondary + color: Theme.palette.textMuted + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton + onDoubleClicked: { + if (model.address && typeof backend !== "undefined") + backend.copyToClipboard(model.address) + } + } } } } diff --git a/src/qml/popups/CreateAccountDialog.qml b/src/qml/popups/CreateAccountDialog.qml index 363ff94..d07c3f2 100644 --- a/src/qml/popups/CreateAccountDialog.qml +++ b/src/qml/popups/CreateAccountDialog.qml @@ -16,11 +16,7 @@ Popup { padding: Theme.spacing.large closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - // Center in overlay (main window when modal) - parent: Overlay.overlay anchors.centerIn: parent - // width: contentWrapper.width + leftPadding + rightPadding - // height: contentWrapper.height + topPadding + bottomPadding background: Rectangle { color: Theme.palette.backgroundSecondary diff --git a/src/qml/views/DashboardView.qml b/src/qml/views/DashboardView.qml index 001d8b9..6f9425d 100644 --- a/src/qml/views/DashboardView.qml +++ b/src/qml/views/DashboardView.qml @@ -8,12 +8,10 @@ import Logos.Controls Rectangle { id: root - color: Theme.palette.background - // --- Public API: input properties (set by parent / MainView) --- property var accountModel: null - property var filteredAccountModel: null property string transferResult: "" + property bool transferResultIsError: false // --- Public API: output signals (parent connects and calls backend) --- signal createPublicAccountRequested() @@ -21,6 +19,8 @@ Rectangle { signal fetchBalancesRequested() signal transferRequested(bool isPublic, string fromAccountId, string toAddress, string amount) + color: Theme.palette.background + RowLayout { anchors.fill: parent anchors.margins: Theme.spacing.xlarge @@ -42,9 +42,9 @@ Rectangle { id: transferPanel Layout.fillWidth: true Layout.fillHeight: true - - fromAccountModel: root.filteredAccountModel + fromAccountModel: root.accountModel transferResult: root.transferResult + transferResultIsError: root.transferResultIsError onTransferRequested: function(isPublic, fromId, toAddress, amount) { root.transferRequested(isPublic, fromId, toAddress, amount) diff --git a/src/qml/views/OnboardingView.qml b/src/qml/views/OnboardingView.qml index d2c122e..d5b63d7 100644 --- a/src/qml/views/OnboardingView.qml +++ b/src/qml/views/OnboardingView.qml @@ -9,11 +9,25 @@ import Logos.Controls Control { id: root + property string configPath: "" + property string storePath: "" property string createError: "" signal createWallet(string configPath, string storagePath, string password) + QtObject { + id: d + function configParentFolderUrl(path) { + if (!path || path.length === 0) return "" + var p = path + var i = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\")) + if (i <= 0) return "" + var dir = p.substring(0, i) + return dir.indexOf("file://") === 0 ? dir : "file://" + dir + } + } + ColumnLayout { id: cardColumn @@ -46,7 +60,8 @@ Control { LogosTextField { id: storagePathField Layout.fillWidth: true - placeholderText: qsTr("/Users/you/.lez-wallet/") + placeholderText: qsTr("Add store path") + text: root.storePath } LogosButton { text: qsTr("Browse") @@ -66,6 +81,7 @@ Control { id: configPathField Layout.fillWidth: true placeholderText: qsTr("Add path to config") + text: root.configPath } LogosButton { Layout.preferredHeight: configPathField.height @@ -121,16 +137,19 @@ Control { } } - FolderDialog { + FileDialog { id: storageFolderDialog modality: Qt.NonModal - onAccepted: storagePathField.text = selectedFolder.toString().replace(/^file:\/\//, "") + nameFilters: ["JSON files (*.json)"] + currentFolder: root.storePath ? d.configParentFolderUrl(root.storePath) : "" + onAccepted: storagePathField.text = selectedFile.toString().replace(/^file:\/\//, "") } FileDialog { id: configFileDialog modality: Qt.NonModal - nameFilters: ["YAML files (*.yaml)"] + nameFilters: ["JSON files (*.json)"] + currentFolder: root.configPath ? d.configParentFolderUrl(oot.configPath) : "" onAccepted: { if (selectedFile) configPathField.text = selectedFile.toString().replace(/^file:\/\//, "") } diff --git a/src/qml/views/TransferPanel.qml b/src/qml/views/TransferPanel.qml index 8e260ae..c9ac13b 100644 --- a/src/qml/views/TransferPanel.qml +++ b/src/qml/views/TransferPanel.qml @@ -4,13 +4,15 @@ import QtQuick.Layouts import Logos.Theme import Logos.Controls +import "../controls" Rectangle { id: root // --- Public API: data in --- - property var fromAccountModel: null // LEZAccountFilterModel from backend (filtered by public/private) + property var fromAccountModel: null property string transferResult: "" + property bool transferResultIsError: false // --- Public API: signals out --- signal transferRequested(bool isPublic, string fromAccountId, string toAddress, string amount) @@ -25,13 +27,6 @@ Rectangle { || (fromFilterCount === 0 && manualFromField.text.trim().length > 0)) } - Binding { - target: fromAccountModel - property: "filterByPublic" - value: transferTypeBar.currentIndex === 0 - when: fromAccountModel != null - } - radius: Theme.spacing.radiusXlarge color: Theme.palette.backgroundSecondary @@ -113,44 +108,36 @@ Rectangle { visible: fromCombo.count > 0 } - contentItem: TextInput { - readOnly: true - selectByMouse: true - width: fromCombo.width - indicatorText.width - 12 - font.pixelSize: Theme.typography.secondaryText - color: Theme.palette.text - text: fromCombo.currentValue ?? "" - verticalAlignment: Text.AlignVCenter - clip: true - } - - delegate: ItemDelegate { - id: delegate - width: fromCombo.width - leftPadding: 12 - rightPadding: 12 - contentItem: LogosText { - width: parent.width - parent.leftPadding - parent.rightPadding + contentItem: Item { + implicitWidth: fromCombo.width - indicatorText.width - 12 + TextInput { + id: fromComboContentInput + anchors.fill: parent + readOnly: true + selectByMouse: true font.pixelSize: Theme.typography.secondaryText color: Theme.palette.text - text: model.name - elide: Text.ElideRight - horizontalAlignment: Text.AlignLeft + text: fromCombo.displayText verticalAlignment: Text.AlignVCenter + clip: true } - background: Rectangle { - color: delegate.highlighted - ? Theme.palette.backgroundElevated - : Theme.palette.backgroundSecondary + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: fromCombo.popup.visible ? fromCombo.popup.close() : fromCombo.popup.open() } + } + + delegate: AccountDelegate { + width: fromCombo.popup.width - fromCombo.popup.leftPadding - fromCombo.popup.rightPadding highlighted: fromCombo.highlightedIndex === index } popup: Popup { y: fromCombo.height - 1 - width: fromCombo.width + width: 400 height: Math.min(contentItem.implicitHeight + 8, 300) - padding: 0 + padding: Theme.spacing.small contentItem: ListView { clip: true @@ -161,7 +148,7 @@ Rectangle { } background: Rectangle { - color: Theme.palette.backgroundSecondary + color: Theme.palette.backgroundTertiary border.width: 1 border.color: Theme.palette.backgroundElevated radius: Theme.spacing.radiusSmall @@ -226,7 +213,9 @@ Rectangle { Layout.fillWidth: true text: root.transferResult font.pixelSize: Theme.typography.secondaryText - color: root.transferResult.length > 0 ? Theme.palette.textSecondary : "transparent" + color: root.transferResult.length > 0 + ? (root.transferResultIsError ? Theme.palette.error : Theme.palette.textSecondary) + : "transparent" wrapMode: Text.WordWrap } diff --git a/src/wallet_resources.qrc b/src/wallet_resources.qrc index b548e26..38939ea 100644 --- a/src/wallet_resources.qrc +++ b/src/wallet_resources.qrc @@ -1,5 +1,5 @@ - + qml/ExecutionZoneWalletView.qml qml/controls/AccountDelegate.qml qml/popups/CreateAccountDialog.qml