From 9a8f8875b1fb1050053b8bce413ea882dc9d7327 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 19 Jun 2026 19:49:29 +0200 Subject: [PATCH] Make deposit work --- src/BlockchainBackend.cpp | 53 +++++++++++++++++++++++++++- src/BlockchainBackend.h | 2 +- src/BlockchainBackend.rep | 2 +- src/qml/BlockchainView.qml | 4 +-- src/qml/views/ChannelDepositView.qml | 36 ++++++++++++++++--- 5 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/BlockchainBackend.cpp b/src/BlockchainBackend.cpp index dba6cb5..a7e04c0 100644 --- a/src/BlockchainBackend.cpp +++ b/src/BlockchainBackend.cpp @@ -17,6 +17,8 @@ #include #include +#include + const QString BlockchainBackend::BLOCKCHAIN_MODULE_NAME = QStringLiteral("liblogos_blockchain_module"); @@ -57,6 +59,44 @@ static QString toDisplayMessage(const LogosResult& result) return result.success ? result.value.toString() : toErrorMessage(result); } +// Decode a base58 (Bitcoin alphabet) string to raw bytes. On an invalid +// character *ok is set to false and an empty array is returned. +static QByteArray decodeBase58(const QString& input, bool* ok) +{ + static const QByteArray kAlphabet = + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + + const QByteArray s = input.trimmed().toLatin1(); + QByteArray bytes; // little-endian while building, reversed at the end + bytes.append('\0'); + + for (const char c : s) { + const int value = kAlphabet.indexOf(c); + if (value < 0) { + if (ok) *ok = false; + return {}; + } + int carry = value; + for (int j = 0; j < bytes.size(); ++j) { + carry += static_cast(bytes[j]) * 58; + bytes[j] = static_cast(carry & 0xff); + carry >>= 8; + } + while (carry > 0) { + bytes.append(static_cast(carry & 0xff)); + carry >>= 8; + } + } + + // Each leading '1' maps to a leading zero byte. + for (int i = 0; i < s.size() && s[i] == '1'; ++i) + bytes.append('\0'); + + std::reverse(bytes.begin(), bytes.end()); + if (ok) *ok = true; + return bytes; +} + BlockchainBackend::BlockchainBackend(LogosAPI* logosAPI, QObject* parent) : BlockchainBackendSimpleSource(parent) , m_logosAPI(logosAPI) @@ -341,13 +381,24 @@ QString BlockchainBackend::getNotes(QString walletAddressHex, QString optionalTi } QString BlockchainBackend::channelDepositWithNotes( - QString channelIdHex, QStringList inputNoteIdHexes, QString metadataHex, + QString channelIdHex, QStringList inputNoteIdHexes, QString metadataBase58, QString changePublicKeyHex, QStringList fundingPublicKeyHexes, QString maxTxFee, QString optionalTipHex) { if (!m_blockchainClient) return QStringLiteral("Error: Module not initialized."); + // The metadata arrives base58-encoded; the module expects metadata_hex, so + // decode to bytes and hex-encode. Empty stays empty (metadata is optional). + QString metadataHex; + if (!metadataBase58.trimmed().isEmpty()) { + bool ok = false; + const QByteArray bytes = decodeBase58(metadataBase58, &ok); + if (!ok) + return QStringLiteral("Error: Invalid base58 metadata."); + metadataHex = QString::fromLatin1(bytes.toHex()); + } + // 7 positional args exceed the variadic invokeRemoteMethod overloads // (max 5), so pass them through the QVariantList form. QVariantList args; diff --git a/src/BlockchainBackend.h b/src/BlockchainBackend.h index 7ad66e8..84b26aa 100644 --- a/src/BlockchainBackend.h +++ b/src/BlockchainBackend.h @@ -51,7 +51,7 @@ public slots: QString getNotes(QString walletAddressHex, QString optionalTipHex) override; QString channelDepositWithNotes(QString channelIdHex, QStringList inputNoteIdHexes, - QString metadataHex, + QString metadataBase58, QString changePublicKeyHex, QStringList fundingPublicKeyHexes, QString maxTxFee, diff --git a/src/BlockchainBackend.rep b/src/BlockchainBackend.rep index eb8fd34..ed0e7b2 100644 --- a/src/BlockchainBackend.rep +++ b/src/BlockchainBackend.rep @@ -17,7 +17,7 @@ class BlockchainBackend SLOT(QString claimLeaderRewards()) 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(QString channelDepositWithNotes(QString channelIdHex, QStringList inputNoteIdHexes, QString metadataBase58, 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 695e975..e5c1109 100644 --- a/src/qml/BlockchainView.qml +++ b/src/qml/BlockchainView.qml @@ -298,11 +298,11 @@ Rectangle { function(error) { channelDepositView.setNotesResult("Error: " + error) } ) } - onSubmitRequested: function(channelIdHex, inputNoteIdHexes, metadataHex, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex) { + onSubmitRequested: function(channelIdHex, inputNoteIdHexes, metadataBase58, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex) { if (!root.backend) return logos.watch( root.backend.channelDepositWithNotes( - channelIdHex, inputNoteIdHexes, metadataHex, + channelIdHex, inputNoteIdHexes, metadataBase58, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex), function(result) { channelDepositView.setSubmitResult(result) }, function(error) { channelDepositView.setSubmitResult("Error: " + error) } diff --git a/src/qml/views/ChannelDepositView.qml b/src/qml/views/ChannelDepositView.qml index 9cebc6d..7cf2061 100644 --- a/src/qml/views/ChannelDepositView.qml +++ b/src/qml/views/ChannelDepositView.qml @@ -21,7 +21,7 @@ ColumnLayout { 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 submitRequested(string channelIdHex, var inputNoteIdHexes, string metadataBase58, string changePublicKeyHex, var fundingPublicKeyHexes, string maxTxFee, string optionalTipHex) signal copyToClipboard(string text) // --- Called by the parent after async backend calls return --- @@ -77,6 +77,18 @@ ColumnLayout { .filter(function(s) { return s.length > 0 }) } + // --- Metadata (base58-encoded bytes) --- + + // The actual base58 → bytes decoding happens in the C++ backend; here we + // only check the input uses the base58 (Bitcoin) alphabet. Plain base58 + // has no checksum, so a valid-alphabet string always decodes. + function metadataIsValid() { + var s = metadataField.text.trim() + if (s === "") + return true // optional + return /^[1-9A-HJ-NP-Za-km-z]+$/.test(s) + } + function canAdvance() { switch (step) { case 0: @@ -86,6 +98,7 @@ ColumnLayout { && changeKeyField.text.trim().length > 0 && fundingKeyList().length > 0 && maxFeeField.text.trim().length > 0 + && metadataIsValid() default: return true } @@ -151,7 +164,7 @@ ColumnLayout { { 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("Metadata (base58)"), v: metadataField.text.trim() || qsTr("(none)") }, { k: qsTr("Optional tip hex"), v: tipField.text.trim() || qsTr("(current tip)") } ] } @@ -326,13 +339,28 @@ ColumnLayout { } LogosText { - text: qsTr("Metadata hex (optional)") + text: qsTr("Metadata (base58, optional)") font.pixelSize: Theme.typography.secondaryText } LogosTextField { id: metadataField Layout.fillWidth: true - placeholderText: qsTr("Metadata hex") + placeholderText: qsTr("Base58-encoded metadata bytes") + } + LogosText { + Layout.fillWidth: true + text: qsTr("Input must be base58-encoded; it is decoded to bytes before submission.") + color: Theme.palette.textSecondary + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WordWrap + } + LogosText { + Layout.fillWidth: true + visible: metadataField.text.trim() !== "" && !d.metadataIsValid() + text: qsTr("Invalid base58 input") + color: Theme.palette.error + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WordWrap } LogosText {