From 098a3c8dd5dca221af5d1e4201489fbe1cf292d2 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 19 Jun 2026 13:35:13 +0200 Subject: [PATCH] Implement deposit ui --- src/BlockchainBackend.cpp | 33 ++ src/BlockchainBackend.h | 8 + src/BlockchainBackend.rep | 2 + src/qml/BlockchainView.qml | 55 ++- src/qml/controls/NoteSelector.qml | 162 +++++++++ src/qml/views/ChannelDepositView.qml | 514 +++++++++++++++++++++++++++ src/qml/views/qmldir | 3 +- 7 files changed, 775 insertions(+), 2 deletions(-) create mode 100644 src/qml/controls/NoteSelector.qml create mode 100644 src/qml/views/ChannelDepositView.qml diff --git a/src/BlockchainBackend.cpp b/src/BlockchainBackend.cpp index 832c475..c33a80d 100644 --- a/src/BlockchainBackend.cpp +++ b/src/BlockchainBackend.cpp @@ -277,6 +277,39 @@ int BlockchainBackend::generateConfig( return result.isValid() ? result.toInt() : -1; } +QString BlockchainBackend::getNotes(QString walletAddressHex, QString optionalTipHex) +{ + if (!m_blockchainClient) + return QStringLiteral("Error: Module not initialized."); + + QVariant result = m_blockchainClient->invokeRemoteMethod( + BLOCKCHAIN_MODULE_NAME, "wallet_get_notes", + walletAddressHex, optionalTipHex); + return result.isValid() ? result.toString() + : QStringLiteral("Error: Call failed."); +} + +QString BlockchainBackend::channelDepositWithNotes( + QString channelIdHex, QStringList inputNoteIdHexes, QString metadataHex, + QString changePublicKeyHex, QStringList fundingPublicKeyHexes, + QString maxTxFee, QString optionalTipHex) +{ + if (!m_blockchainClient) + return QStringLiteral("Error: Module not initialized."); + + // 7 positional args exceed the variadic invokeRemoteMethod overloads + // (max 5), so pass them through the QVariantList form. + QVariantList args; + args << channelIdHex << inputNoteIdHexes << metadataHex << changePublicKeyHex + << fundingPublicKeyHexes << maxTxFee << optionalTipHex; + + QVariant result = m_blockchainClient->invokeRemoteMethod( + BLOCKCHAIN_MODULE_NAME, QStringLiteral("channel_deposit_with_notes"), + args); + return result.isValid() ? result.toString() + : QStringLiteral("Error: Call failed."); +} + void BlockchainBackend::clearLogs() { m_logModel->clear(); diff --git a/src/BlockchainBackend.h b/src/BlockchainBackend.h index ee58990..b0082f6 100644 --- a/src/BlockchainBackend.h +++ b/src/BlockchainBackend.h @@ -47,6 +47,14 @@ public slots: int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath) override; + QString getNotes(QString walletAddressHex, QString optionalTipHex) override; + QString channelDepositWithNotes(QString channelIdHex, + QStringList inputNoteIdHexes, + QString metadataHex, + QString changePublicKeyHex, + QStringList fundingPublicKeyHexes, + QString maxTxFee, + QString optionalTipHex) override; void clearLogs() override; void copyToClipboard(QString text) override; diff --git a/src/BlockchainBackend.rep b/src/BlockchainBackend.rep index f518c9e..0e56717 100644 --- a/src/BlockchainBackend.rep +++ b/src/BlockchainBackend.rep @@ -14,6 +14,8 @@ class BlockchainBackend SLOT(QString getBalance(QString addressHex)) SLOT(QString transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr)) SLOT(int generateConfig(QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath)) + SLOT(QString getNotes(QString walletAddressHex, QString optionalTipHex)) + SLOT(QString channelDepositWithNotes(QString channelIdHex, QStringList inputNoteIdHexes, QString metadataHex, QString changePublicKeyHex, QStringList fundingPublicKeyHexes, QString maxTxFee, QString optionalTipHex)) SLOT(void clearLogs()) SLOT(void copyToClipboard(QString text)) } diff --git a/src/qml/BlockchainView.qml b/src/qml/BlockchainView.qml index 9816083..5a5715b 100644 --- a/src/qml/BlockchainView.qml +++ b/src/qml/BlockchainView.qml @@ -3,6 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import Logos.Theme +import Logos.Controls // BlockchainStatus enum (NotStarted/Starting/Running/.../ErrorSubscribeFailed) // declared in BlockchainBackend.rep — registered with QML by the replica // factory plugin. @@ -155,7 +156,27 @@ Rectangle { } } - // Page 2: Node control, wallet, logs + // Page 2: Node control, wallet, logs + Channel Deposit (tabbed) + ColumnLayout { + spacing: Theme.spacing.medium + + LogosTabBar { + id: operationTabBar + Layout.fillWidth: true + LogosTabButton { text: qsTr("Node & Wallet") } + LogosTabButton { + text: qsTr("Channel Deposit") + enabled: root.backend + && root.backend.status === BlockchainBackend.Running + } + } + + StackLayout { + id: operationStack + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: operationTabBar.currentIndex + SplitView { orientation: Qt.Vertical @@ -239,6 +260,38 @@ Rectangle { if (root.backend) root.backend.copyToClipboard(text) } } + } + + ChannelDepositView { + id: channelDepositView + accountsModel: root.accountsModel + nodeRunning: root.backend + ? root.backend.status === BlockchainBackend.Running + : false + + onGetNotesRequested: function(addressHex, optionalTipHex) { + if (!root.backend) return + logos.watch( + root.backend.getNotes(addressHex, optionalTipHex), + function(result) { channelDepositView.setNotesResult(result) }, + function(error) { channelDepositView.setNotesResult("Error: " + error) } + ) + } + onSubmitRequested: function(channelIdHex, inputNoteIdHexes, metadataHex, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex) { + if (!root.backend) return + logos.watch( + root.backend.channelDepositWithNotes( + channelIdHex, inputNoteIdHexes, metadataHex, + changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex), + function(result) { channelDepositView.setSubmitResult(result) }, + function(error) { channelDepositView.setSubmitResult("Error: " + error) } + ) + } + onCopyToClipboard: (text) => { + if (root.backend) root.backend.copyToClipboard(text) + } + } + } } } } diff --git a/src/qml/controls/NoteSelector.qml b/src/qml/controls/NoteSelector.qml new file mode 100644 index 0000000..d7a5af8 --- /dev/null +++ b/src/qml/controls/NoteSelector.qml @@ -0,0 +1,162 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Logos.Theme +import Logos.Controls + +// Widget for selecting spendable notes (UTXOs) to consume in a channel deposit. +// +// The owner sets `notes` (a JS array of { id, value } parsed from +// wallet_get_notes) and reads back the selection via selectedIds() and the +// selectedCount / selectedTotal properties. +ColumnLayout { + id: root + + // Array of { id: "", value: "" }. Assign to (re)populate. + property var notes: [] + property bool loading: false + property string errorText: "" + + // Observable selection summary — bindings can't track ListModel reads, so + // these properties are recomputed explicitly on every selection change. + property int selectedCount: 0 + property double selectedTotal: 0 + + function selectedIds() { + var ids = [] + for (var i = 0; i < notesModel.count; ++i) { + var n = notesModel.get(i) + if (n.selected) + ids.push(n.noteId) + } + return ids + } + + function recompute() { + var c = 0 + var total = 0 + for (var i = 0; i < notesModel.count; ++i) { + var n = notesModel.get(i) + if (n.selected) { + c++ + total += Number(n.value) + } + } + root.selectedCount = c + root.selectedTotal = total + } + + function clearSelection() { + for (var i = 0; i < notesModel.count; ++i) + notesModel.setProperty(i, "selected", false) + recompute() + } + + onNotesChanged: { + notesModel.clear() + var arr = root.notes || [] + for (var i = 0; i < arr.length; ++i) { + notesModel.append({ + noteId: String(arr[i].id), + value: String(arr[i].value), + selected: false + }) + } + recompute() + } + + spacing: Theme.spacing.small + + ListModel { id: notesModel } + + LogosText { + Layout.fillWidth: true + visible: root.loading + text: qsTr("Loading notes…") + color: Theme.palette.textSecondary + font.pixelSize: Theme.typography.secondaryText + } + + LogosText { + Layout.fillWidth: true + visible: !root.loading && root.errorText !== "" + text: root.errorText + color: Theme.palette.error + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WordWrap + } + + LogosText { + Layout.fillWidth: true + visible: !root.loading && root.errorText === "" && notesModel.count === 0 + text: qsTr("No notes found for this address. Load notes for a funded address.") + color: Theme.palette.textSecondary + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WordWrap + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 220 + visible: notesModel.count > 0 + color: Theme.palette.backgroundTertiary + radius: Theme.spacing.radiusLarge + border.color: Theme.palette.border + border.width: 1 + + ListView { + id: notesList + anchors.fill: parent + anchors.margins: Theme.spacing.small + clip: true + model: notesModel + spacing: Theme.spacing.tiny + + delegate: RowLayout { + width: notesList.width + spacing: Theme.spacing.small + + LogosCheckbox { + checked: model.selected + onClicked: { + notesModel.setProperty(index, "selected", checked) + root.recompute() + } + } + + LogosText { + Layout.fillWidth: true + text: model.noteId + elide: Text.ElideMiddle + font.pixelSize: Theme.typography.secondaryText + } + + LogosText { + Layout.alignment: Qt.AlignRight + text: model.value + color: Theme.palette.textSecondary + font.pixelSize: Theme.typography.secondaryText + } + } + } + } + + RowLayout { + Layout.fillWidth: true + visible: notesModel.count > 0 + spacing: Theme.spacing.small + + LogosText { + text: qsTr("%1 selected").arg(root.selectedCount) + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + Item { Layout.fillWidth: true } + LogosText { + text: qsTr("Total: %1").arg(root.selectedTotal) + font.pixelSize: Theme.typography.secondaryText + font.bold: true + } + } +} diff --git a/src/qml/views/ChannelDepositView.qml b/src/qml/views/ChannelDepositView.qml new file mode 100644 index 0000000..9cebc6d --- /dev/null +++ b/src/qml/views/ChannelDepositView.qml @@ -0,0 +1,514 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Logos.Theme +import Logos.Controls + +import "../controls" + +// Multi-step wizard for channel_deposit_with_notes: +// 1. Select notes → wallet_get_notes, pick UTXOs to consume +// 2. Fill fields → channel id, change/funding keys, fee, metadata, tip +// 3. Confirm → review the exact payload +// 4. Result → tx hash (copyable) or error +ColumnLayout { + id: root + + // Known wallet addresses (auto-remoted accounts model). Also used as + // public keys for the change/funding key pickers. + property var accountsModel: null + property bool nodeRunning: false + + signal getNotesRequested(string addressHex, string optionalTipHex) + signal submitRequested(string channelIdHex, var inputNoteIdHexes, string metadataHex, string changePublicKeyHex, var fundingPublicKeyHexes, string maxTxFee, string optionalTipHex) + signal copyToClipboard(string text) + + // --- Called by the parent after async backend calls return --- + + function setNotesLoading() { + noteSelector.loading = true + noteSelector.errorText = "" + } + + function setNotesResult(jsonStr) { + noteSelector.loading = false + var s = jsonStr || "" + if (s.indexOf("Error") === 0) { + noteSelector.errorText = s + noteSelector.notes = [] + return + } + try { + var parsed = JSON.parse(s) + d.notesTip = parsed.tip || "" + noteSelector.errorText = "" + noteSelector.notes = parsed.notes || [] + } catch (e) { + noteSelector.errorText = qsTr("Failed to parse notes: %1").arg(s) + noteSelector.notes = [] + } + } + + function setSubmitResult(resultStr) { + d.resultPending = false + var s = resultStr || "" + d.resultSuccess = (s.indexOf("Error") !== 0) + d.resultText = s + } + + spacing: Theme.spacing.large + + QtObject { + id: d + + property int step: 0 + readonly property int stepCount: 4 + property string notesTip: "" + + // result state + property bool resultPending: false + property bool resultSuccess: false + property string resultText: "" + + function fundingKeyList() { + return fundingKeysArea.text.split("\n") + .map(function(s) { return s.trim() }) + .filter(function(s) { return s.length > 0 }) + } + + function canAdvance() { + switch (step) { + case 0: + return noteSelector.selectedCount > 0 + case 1: + return channelIdField.text.trim().length > 0 + && changeKeyField.text.trim().length > 0 + && fundingKeyList().length > 0 + && maxFeeField.text.trim().length > 0 + default: + return true + } + } + + function goNext() { + if (step === 1) { + // entering confirm — nothing else + } + if (step < stepCount - 1) + step++ + // Prefill key fields from the selected wallet when first reaching step 1. + if (step === 1) { + if (changeKeyField.text.trim() === "") + changeKeyField.text = walletField.text.trim() + if (fundingKeysArea.text.trim() === "" && walletField.text.trim() !== "") + fundingKeysArea.text = walletField.text.trim() + } + } + + function goBack() { + if (step > 0) + step-- + } + + function submit() { + d.resultPending = true + d.resultSuccess = false + d.resultText = "" + step = 3 + root.submitRequested( + channelIdField.text.trim(), + noteSelector.selectedIds(), + metadataField.text.trim(), + changeKeyField.text.trim(), + fundingKeyList(), + maxFeeField.text.trim(), + tipField.text.trim()) + } + + function reset() { + noteSelector.clearSelection() + noteSelector.notes = [] + noteSelector.errorText = "" + channelIdField.text = "" + changeKeyField.text = "" + fundingKeysArea.text = "" + maxFeeField.text = "" + metadataField.text = "" + tipField.text = "" + notesTip = "" + resultText = "" + resultPending = false + step = 0 + } + + function summaryLines() { + var ids = noteSelector.selectedIds() + return [ + { k: qsTr("Channel ID"), v: channelIdField.text.trim() }, + { k: qsTr("Notes to consume (%1)").arg(ids.length), v: ids.join("\n") }, + { k: qsTr("Total amount"), v: String(noteSelector.selectedTotal) }, + { k: qsTr("Change public key"), v: changeKeyField.text.trim() }, + { k: qsTr("Funding public keys"), v: fundingKeyList().join("\n") }, + { k: qsTr("Max tx fee"), v: maxFeeField.text.trim() }, + { k: qsTr("Metadata hex"), v: metadataField.text.trim() || qsTr("(none)") }, + { k: qsTr("Optional tip hex"), v: tipField.text.trim() || qsTr("(current tip)") } + ] + } + } + + // ---- Header / step indicator ---- + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.medium + + LogosText { + text: qsTr("Channel Deposit") + font.pixelSize: Theme.typography.primaryText + font.bold: true + } + Item { Layout.fillWidth: true } + LogosText { + text: qsTr("Step %1 of %2").arg(d.step + 1).arg(d.stepCount) + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + } + + LogosText { + Layout.fillWidth: true + visible: !root.nodeRunning + text: qsTr("Start the node before making a deposit.") + color: Theme.palette.warning + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WordWrap + } + + StackLayout { + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: d.step + + // ---- Step 0: Select notes ---- + ColumnLayout { + spacing: Theme.spacing.medium + + LogosText { + Layout.fillWidth: true + text: qsTr("Select the notes (UTXOs) to deposit into the channel. Their full value is consumed.") + color: Theme.palette.textSecondary + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WordWrap + } + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + + LogosComboBox { + Layout.preferredWidth: 200 + placeholderText: qsTr("Known address…") + model: root.accountsModel + textRole: "address" + currentIndex: -1 + onActivated: function(index) { walletField.text = currentText } + } + LogosTextField { + id: walletField + Layout.fillWidth: true + placeholderText: qsTr("Wallet address hex") + } + LogosButton { + text: qsTr("Load notes") + enabled: root.nodeRunning && walletField.text.trim().length > 0 + onClicked: { + root.setNotesLoading() + root.getNotesRequested(walletField.text.trim(), "") + } + } + } + + NoteSelector { + id: noteSelector + Layout.fillWidth: true + } + + Item { Layout.fillHeight: true } + } + + // ---- Step 1: Fields ---- + ScrollView { + id: fieldsScroll + clip: true + ColumnLayout { + width: fieldsScroll.availableWidth + spacing: Theme.spacing.medium + + LogosText { + text: qsTr("Channel ID hex") + font.pixelSize: Theme.typography.secondaryText + } + LogosTextField { + id: channelIdField + Layout.fillWidth: true + placeholderText: qsTr("Channel ID hex") + } + + LogosText { + text: qsTr("Change public key (receives change)") + font.pixelSize: Theme.typography.secondaryText + } + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + LogosComboBox { + Layout.preferredWidth: 200 + placeholderText: qsTr("Known address…") + model: root.accountsModel + textRole: "address" + currentIndex: -1 + onActivated: function(index) { changeKeyField.text = currentText } + } + LogosTextField { + id: changeKeyField + Layout.fillWidth: true + placeholderText: qsTr("Change public key hex") + } + } + + LogosText { + text: qsTr("Funding public keys (one per line, fund the gas fee)") + font.pixelSize: Theme.typography.secondaryText + } + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + Item { Layout.fillWidth: true } + LogosComboBox { + Layout.preferredWidth: 220 + placeholderText: qsTr("Add known address…") + model: root.accountsModel + textRole: "address" + currentIndex: -1 + onActivated: function(index) { + var t = fundingKeysArea.text.trim() + fundingKeysArea.text = (t.length > 0 ? t + "\n" : "") + currentText + } + } + } + ScrollView { + Layout.fillWidth: true + Layout.preferredHeight: 80 + clip: true + TextArea { + id: fundingKeysArea + background: Rectangle { + radius: Theme.spacing.radiusSmall + color: Theme.palette.backgroundSecondary + border.width: 1 + border.color: Theme.palette.backgroundElevated + } + placeholderText: qsTr("Funding public key hex, one per line") + placeholderTextColor: Theme.palette.textTertiary + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.text + } + } + + LogosText { + text: qsTr("Max tx fee") + font.pixelSize: Theme.typography.secondaryText + } + LogosTextField { + id: maxFeeField + Layout.fillWidth: true + placeholderText: qsTr("Maximum transaction fee") + } + + LogosText { + text: qsTr("Metadata hex (optional)") + font.pixelSize: Theme.typography.secondaryText + } + LogosTextField { + id: metadataField + Layout.fillWidth: true + placeholderText: qsTr("Metadata hex") + } + + LogosText { + text: qsTr("Optional tip hex (leave empty for current tip)") + font.pixelSize: Theme.typography.secondaryText + } + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + LogosTextField { + id: tipField + Layout.fillWidth: true + placeholderText: qsTr("Tip hex") + } + LogosButton { + text: qsTr("Use query tip") + enabled: d.notesTip !== "" + onClicked: tipField.text = d.notesTip + } + } + } + } + + // ---- Step 2: Confirm ---- + ColumnLayout { + spacing: Theme.spacing.medium + + LogosText { + Layout.fillWidth: true + text: qsTr("Review the deposit. This is the exact payload that will be submitted.") + color: Theme.palette.textSecondary + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WordWrap + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + color: Theme.palette.backgroundTertiary + radius: Theme.spacing.radiusLarge + border.color: Theme.palette.border + border.width: 1 + + ScrollView { + id: confirmScroll + anchors.fill: parent + anchors.margins: Theme.spacing.large + clip: true + ColumnLayout { + width: confirmScroll.availableWidth + spacing: Theme.spacing.medium + Repeater { + // Re-evaluated when the confirm step is shown so it + // reflects the latest field values. + model: d.step === 2 ? d.summaryLines() : [] + delegate: ColumnLayout { + Layout.fillWidth: true + spacing: Theme.spacing.tiny + LogosText { + text: modelData.k + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + } + LogosText { + Layout.fillWidth: true + text: modelData.v + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WrapAnywhere + } + } + } + } + } + } + } + + // ---- Step 3: Result ---- + ColumnLayout { + spacing: Theme.spacing.medium + + LogosText { + Layout.alignment: Qt.AlignHCenter + visible: d.resultPending + text: qsTr("Submitting deposit…") + color: Theme.palette.textSecondary + font.pixelSize: Theme.typography.secondaryText + } + BusyIndicator { + Layout.alignment: Qt.AlignHCenter + visible: d.resultPending + running: d.resultPending + } + + LogosText { + Layout.fillWidth: true + visible: !d.resultPending + text: d.resultSuccess ? qsTr("Deposit submitted") : qsTr("Deposit failed") + color: d.resultSuccess ? Theme.palette.success : Theme.palette.error + font.pixelSize: Theme.typography.primaryText + font.bold: true + } + + // Success: tx hash + copy + RowLayout { + Layout.fillWidth: true + visible: !d.resultPending && d.resultSuccess + spacing: Theme.spacing.small + Rectangle { + Layout.fillWidth: true + implicitHeight: txHashText.implicitHeight + Theme.spacing.medium + color: Theme.palette.backgroundTertiary + radius: Theme.spacing.radiusSmall + border.color: Theme.palette.border + border.width: 1 + LogosText { + id: txHashText + anchors.fill: parent + anchors.margins: Theme.spacing.small + text: d.resultText + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WrapAnywhere + verticalAlignment: Text.AlignVCenter + } + } + LogosCopyButton { + Layout.preferredHeight: 40 + Layout.preferredWidth: 40 + onCopyText: root.copyToClipboard(d.resultText) + } + } + + // Error + LogosText { + Layout.fillWidth: true + visible: !d.resultPending && !d.resultSuccess && d.resultText !== "" + text: d.resultText + color: Theme.palette.error + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WordWrap + } + + Item { Layout.fillHeight: true } + } + } + + // ---- Footer navigation ---- + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + + LogosButton { + text: qsTr("Back") + visible: d.step > 0 && d.step < 3 + onClicked: d.goBack() + } + Item { Layout.fillWidth: true } + + // Steps 0 & 1: Next + LogosButton { + text: qsTr("Next") + visible: d.step < 2 + enabled: d.canAdvance() + onClicked: d.goNext() + } + // Step 2: Confirm & submit + LogosButton { + text: qsTr("Confirm & deposit") + visible: d.step === 2 + enabled: root.nodeRunning + onClicked: d.submit() + } + // Step 3: New deposit (after completion) + LogosButton { + text: qsTr("New deposit") + visible: d.step === 3 && !d.resultPending + onClicked: d.reset() + } + } +} diff --git a/src/qml/views/qmldir b/src/qml/views/qmldir index a90a4e1..fbca773 100644 --- a/src/qml/views/qmldir +++ b/src/qml/views/qmldir @@ -4,4 +4,5 @@ LogsView 1.0 LogsView.qml WalletView 1.0 WalletView.qml GenerateConfigView 1.0 GenerateConfigView.qml ConfigChoiceView 1.0 ConfigChoiceView.qml -SetConfigPathView 1.0 SetConfigPathView.qml \ No newline at end of file +SetConfigPathView 1.0 SetConfigPathView.qml +ChannelDepositView 1.0 ChannelDepositView.qml \ No newline at end of file