From dc58b1104a48d8a05c07cbc6f6151548edf01316 Mon Sep 17 00:00:00 2001 From: levoncrypto Date: Sun, 3 Nov 2024 13:26:40 +0400 Subject: [PATCH 1/3] Add memo field in UI --- src/qt/forms/sendcoinsentry.ui | 13 +++++-------- src/qt/sendcoinsdialog.cpp | 8 ++++++++ src/qt/sendcoinsentry.cpp | 7 +++++++ src/qt/transactiondesc.cpp | 12 ++++++++---- src/qt/walletmodel.cpp | 4 ++-- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index 361f182f0a..965657edb9 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -221,7 +221,7 @@ - + Message: @@ -231,15 +231,12 @@ - - + + - A message that was attached to the firo: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Firo network. + Optional message for this transaction - - Qt::PlainText - - + diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 3bfbfbc6a7..e9706fd9fc 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -265,6 +265,7 @@ void SendCoinsDialog::on_sendButton_clicked() } ctx = dialog->getUnlockContext(); } + recipient.message = entry->getValue().message; recipients.append(recipient); } else @@ -532,6 +533,13 @@ void SendCoinsDialog::on_sendButton_clicked() QString questionString = tr("Are you sure you want to send?"); questionString.append(warningMessage); questionString.append("

%1"); + questionString.append("\n\nMessage: "); + for (auto rec : recipients) + { + questionString.append(rec.message); + questionString.append(".\t"); + } + double txSize; if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount) && spark::IsSparkAllowed()) { diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index c250db4d21..c762262041 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -55,6 +55,9 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *par connect(ui->deleteButton, &QToolButton::clicked, this, &SendCoinsEntry::deleteClicked); connect(ui->deleteButton_is, &QToolButton::clicked, this, &SendCoinsEntry::deleteClicked); connect(ui->deleteButton_s, &QToolButton::clicked, this, &SendCoinsEntry::deleteClicked); + + ui->messageLabel->setVisible(false); + ui->messageTextLabel->setVisible(false); } SendCoinsEntry::~SendCoinsEntry() @@ -85,6 +88,10 @@ void SendCoinsEntry::on_payTo_textChanged(const QString &address) { updateLabel(address); setWarning(fAnonymousMode); + + bool isSparkAddress = model && model->validateSparkAddress(address); + ui->messageLabel->setVisible(isSparkAddress); + ui->messageTextLabel->setVisible(isSparkAddress); } void SendCoinsEntry::setModel(WalletModel *_model) diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index a13eb7e347..85d04babc4 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -311,10 +311,14 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco strHTML += "" + tr("Transaction total size") + ": " + QString::number(wtx.tx->GetTotalSize()) + " bytes
"; strHTML += "" + tr("Output index") + ": " + QString::number(rec->getOutputIndex()) + "
"; - // Message from normal firo:URI (firo:123...?message=example) - for (const PAIRTYPE(std::string, std::string)& r : wtx.vOrderForm) - if (r.first == "Message") - strHTML += "
" + tr("Message") + ":
" + GUIUtil::HtmlEscape(r.second, true) + "
"; + uint256 selectedTxID = rec->hash; + std::unordered_map coins = wallet->sparkWallet->getMintMap(); + + for (const auto& [id, meta] : coins) { + if (meta.txid == selectedTxID && !meta.memo.empty()) { + strHTML += "" + tr("Message") + ": " + GUIUtil::HtmlEscape(meta.memo, true) + "
\n"; + } + } if (wtx.IsCoinBase()) { diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 66a8538941..c625cf4b77 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1384,7 +1384,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareMintSparkTransaction(std::vecto address.decode(rcp.address.toStdString()); spark::MintedCoinData data; data.address = address; - data.memo = ""; + data.memo = rcp.message.toStdString(); data.v = rcp.amount; outputs.push_back(data); total += rcp.amount; @@ -1481,7 +1481,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareSpendSparkTransaction(WalletMod address.decode(rcp.address.toStdString()); spark::OutputCoinData data; data.address = address; - data.memo = ""; + data.memo = rcp.message.toStdString(); data.v = rcp.amount; privateRecipients.push_back(std::make_pair(data, rcp.fSubtractFeeFromAmount)); } else { From 3e7f4ec18ae88897a9ec2ed5457d41cba823ebce Mon Sep 17 00:00:00 2001 From: levoncrypto Date: Thu, 7 Nov 2024 00:06:42 +0400 Subject: [PATCH 2/3] add memo warrning message and fixes --- src/qt/forms/sendcoinsentry.ui | 42 ++++++++++++++++++++++++++++++++++ src/qt/sendcoinsdialog.cpp | 15 ++++++++---- src/qt/sendcoinsentry.cpp | 26 ++++++++++++++++++++- src/qt/sendcoinsentry.h | 1 + src/qt/transactiondesc.cpp | 40 ++++++++++++++++++++++++++++---- src/spark/primitives.h | 3 +++ src/spark/sparkwallet.cpp | 5 +++- src/wallet/wallet.h | 1 + 8 files changed, 121 insertions(+), 12 deletions(-) diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index 965657edb9..11a74f1155 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -238,6 +238,48 @@
+ + + + 0 + + + + + + + + + + + 5 + + + 5 + + + 10 + + + 10 + + + margin-left:-30px;margin-right:-10px;margin-top:2px; + + + + + + + + + + color: #FFA800; margin-left:-10px; + + + + + diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index e9706fd9fc..fd3d658217 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -533,11 +533,16 @@ void SendCoinsDialog::on_sendButton_clicked() QString questionString = tr("Are you sure you want to send?"); questionString.append(warningMessage); questionString.append("

%1"); - questionString.append("\n\nMessage: "); - for (auto rec : recipients) - { - questionString.append(rec.message); - questionString.append(".\t"); + bool firstMessage = true; + for (const auto& rec : recipients) { + if (!rec.message.isEmpty()) { + if (firstMessage) { + questionString.append("
" + tr("Messages") + ":
"); + firstMessage = false; + } + QString sanitizedMsg = GUIUtil::HtmlEscape(rec.message, true); + questionString.append("• " + sanitizedMsg + "
"); + } } double txSize; diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index c762262041..4697423868 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -29,6 +29,7 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *par QIcon icon_; icon_.addFile(QString::fromUtf8(":/icons/ic_warning"), QSize(), QIcon::Normal, QIcon::On); ui->iconWarning->setPixmap(icon_.pixmap(18, 18)); + ui->iconMessageWarning->setPixmap(icon_.pixmap(18, 18)); ui->addressBookButton->setIcon(platformStyle->SingleColorIcon(":/icons/address-book")); ui->pasteButton->setIcon(platformStyle->SingleColorIcon(":/icons/editpaste")); @@ -55,9 +56,11 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *par connect(ui->deleteButton, &QToolButton::clicked, this, &SendCoinsEntry::deleteClicked); connect(ui->deleteButton_is, &QToolButton::clicked, this, &SendCoinsEntry::deleteClicked); connect(ui->deleteButton_s, &QToolButton::clicked, this, &SendCoinsEntry::deleteClicked); + connect(ui->messageTextLabel, &QLineEdit::textChanged, this, &SendCoinsEntry::on_MemoTextChanged); ui->messageLabel->setVisible(false); ui->messageTextLabel->setVisible(false); + ui->iconMessageWarning->setVisible(false); } SendCoinsEntry::~SendCoinsEntry() @@ -65,6 +68,24 @@ SendCoinsEntry::~SendCoinsEntry() delete ui; } +void SendCoinsEntry::on_MemoTextChanged(const QString &text) +{ + int maxLength = 256; + bool isOverLimit = text.length() > maxLength; + + if (isOverLimit) { + ui->messageWarning->setText("Message exceeds character 256 character limit"); + ui->messageWarning->setVisible(true); + ui->messageTextLabel->setStyleSheet("border: 1px solid red;"); + ui->iconMessageWarning->setVisible(true); + } else { + ui->messageWarning->clear(); + ui->messageWarning->setVisible(false); + ui->messageTextLabel->setStyleSheet(""); + ui->iconMessageWarning->setVisible(false); + } +} + void SendCoinsEntry::on_pasteButton_clicked() { // Paste text from clipboard into recipient field @@ -89,7 +110,10 @@ void SendCoinsEntry::on_payTo_textChanged(const QString &address) updateLabel(address); setWarning(fAnonymousMode); - bool isSparkAddress = model && model->validateSparkAddress(address); + bool isSparkAddress = false; + if (model) { + isSparkAddress = model->validateSparkAddress(address); + } ui->messageLabel->setVisible(isSparkAddress); ui->messageTextLabel->setVisible(isSparkAddress); } diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 2e89f0fd5e..4611bd0027 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -63,6 +63,7 @@ public Q_SLOTS: private Q_SLOTS: void deleteClicked(); void on_payTo_textChanged(const QString &address); + void on_MemoTextChanged(const QString &text); void on_addressBookButton_clicked(); void on_pasteButton_clicked(); void updateDisplayUnit(); diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 85d04babc4..deaabaf85f 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -311,12 +311,42 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco strHTML += "" + tr("Transaction total size") + ": " + QString::number(wtx.tx->GetTotalSize()) + " bytes
"; strHTML += "" + tr("Output index") + ": " + QString::number(rec->getOutputIndex()) + "
"; - uint256 selectedTxID = rec->hash; - std::unordered_map coins = wallet->sparkWallet->getMintMap(); + isminetype fAllFromMe = ISMINE_SPENDABLE; + bool foundSparkOutput = false; - for (const auto& [id, meta] : coins) { - if (meta.txid == selectedTxID && !meta.memo.empty()) { - strHTML += "" + tr("Message") + ": " + GUIUtil::HtmlEscape(meta.memo, true) + "
\n"; + for (const CTxIn& txin : wtx.tx->vin) { + isminetype mine = wallet->IsMine(txin, *wtx.tx); + fAllFromMe = std::min(fAllFromMe, mine); + } + + bool firstMessage = true; + if (fAllFromMe) { + for (const CTxOut& txout : wtx.tx->vout) { + if (wtx.IsChange(txout)) continue; + + CSparkOutputTx sparkOutput; + if (wallet->GetSparkOutputTx(txout.scriptPubKey, sparkOutput)) { + if (!sparkOutput.memo.empty()) { + foundSparkOutput = true; + if (firstMessage) { + strHTML += "
" + tr("Messages") + ":
"; + firstMessage = false; + } + strHTML += "• " + GUIUtil::HtmlEscape(sparkOutput.memo, true) + "
"; + } + } + } + } + + if (!foundSparkOutput && wallet->sparkWallet) { + for (const auto& [id, meta] : wallet->sparkWallet->getMintMap()) { + if (meta.txid == rec->hash && !meta.memo.empty()) { + if (firstMessage) { + strHTML += "
" + tr("Messages") + ":
"; + firstMessage = false; + } + strHTML += "• " + GUIUtil::HtmlEscape(meta.memo, true) + "
"; + } } } diff --git a/src/spark/primitives.h b/src/spark/primitives.h index 7929e73b80..09de4550eb 100644 --- a/src/spark/primitives.h +++ b/src/spark/primitives.h @@ -87,6 +87,7 @@ class CSparkOutputTx { public: std::string address; + std::string memo; int64_t amount; CSparkOutputTx() @@ -97,6 +98,7 @@ class CSparkOutputTx void SetNull() { address = ""; + memo = ""; amount = 0; } @@ -105,6 +107,7 @@ class CSparkOutputTx inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(address); READWRITE(amount); + READWRITE(memo); } }; diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 382cb6c62d..d99a3f4810 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -742,7 +742,8 @@ std::vector CSparkWallet::CreateSparkMintRecipients( script.insert(script.end(), serializedCoins[i].begin(), serializedCoins[i].end()); unsigned char network = spark::GetNetworkType(); std::string addr = outputs[i].address.encode(network); - CRecipient recipient = {script, CAmount(outputs[i].v), false, addr}; + std::string memo = outputs[i].memo; + CRecipient recipient = {script, CAmount(outputs[i].v), false, addr, memo}; results.emplace_back(recipient); } @@ -1093,6 +1094,7 @@ bool CSparkWallet::CreateSparkMintTransactions( CSparkOutputTx output; output.address = recipient.address; output.amount = recipient.nAmount; + output.memo = recipient.memo; walletdb.WriteSparkOutputTx(recipient.scriptPubKey, output); break; } @@ -1530,6 +1532,7 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( CSparkOutputTx output; output.address = privOutputs[i].address.encode(network); output.amount = privOutputs[i].v; + output.memo = privOutputs[i].memo; walletdb.WriteSparkOutputTx(script, output); tx.vout.push_back(CTxOut(0, script)); i++; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f9397f5150..f1f1d0c6a9 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -190,6 +190,7 @@ struct CRecipient CAmount nAmount; bool fSubtractFeeFromAmount; std::string address; + std::string memo; }; typedef std::map mapValue_t; From fd6a779f371b2f0077083f5109b6c2cacf692da8 Mon Sep 17 00:00:00 2001 From: levoncrypto Date: Mon, 11 Nov 2024 09:23:01 +0400 Subject: [PATCH 3/3] Add additional checks for memo --- src/qt/sendcoinsentry.cpp | 12 ++++++++++-- src/spark/primitives.h | 10 +++++++++- src/spark/sparkwallet.cpp | 4 ++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 4697423868..26bd96b33c 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -11,6 +11,7 @@ #include "optionsmodel.h" #include "platformstyle.h" #include "walletmodel.h" +#include "../spark/sparkwallet.h" #include "../wallet/wallet.h" #include @@ -70,15 +71,22 @@ SendCoinsEntry::~SendCoinsEntry() void SendCoinsEntry::on_MemoTextChanged(const QString &text) { - int maxLength = 256; + const spark::Params* params = spark::Params::get_default(); + int maxLength = params->get_memo_bytes(); bool isOverLimit = text.length() > maxLength; if (isOverLimit) { - ui->messageWarning->setText("Message exceeds character 256 character limit"); + ui->messageWarning->setText(QString("Message exceeds %1 bytes limit").arg(maxLength)); ui->messageWarning->setVisible(true); ui->messageTextLabel->setStyleSheet("border: 1px solid red;"); ui->iconMessageWarning->setVisible(true); } else { + QString sanitized = text; + sanitized.remove(QRegExp("[\\x00-\\x1F\\x7F]")); + if (sanitized != text) { + ui->messageTextLabel->setText(sanitized); + return; + } ui->messageWarning->clear(); ui->messageWarning->setVisible(false); ui->messageTextLabel->setStyleSheet(""); diff --git a/src/spark/primitives.h b/src/spark/primitives.h index 09de4550eb..2bfa0c72bb 100644 --- a/src/spark/primitives.h +++ b/src/spark/primitives.h @@ -107,7 +107,15 @@ class CSparkOutputTx inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(address); READWRITE(amount); - READWRITE(memo); + if (ser_action.ForRead()) { + if (!s.empty()) { + READWRITE(memo); + } else { + memo = ""; + } + } else { + READWRITE(memo); + } } }; diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index d99a3f4810..9e5e0a49e8 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -743,6 +743,10 @@ std::vector CSparkWallet::CreateSparkMintRecipients( unsigned char network = spark::GetNetworkType(); std::string addr = outputs[i].address.encode(network); std::string memo = outputs[i].memo; + const std::size_t max_memo_size = outputs[i].address.get_params()->get_memo_bytes(); + if (memo.length() > max_memo_size) { + throw std::runtime_error(strprintf("Memo exceeds maximum length of %d bytes", max_memo_size)); + } CRecipient recipient = {script, CAmount(outputs[i].v), false, addr, memo}; results.emplace_back(recipient); }