diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 57b183151d..30cbde15af 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -98,6 +98,7 @@ QT_TS = \ QT_FORMS_UI = \ qt/forms/addressbookpage.ui \ qt/forms/askpassphrasedialog.ui \ + qt/forms/assetcontroldialog.ui \ qt/forms/coincontroldialog.ui \ qt/forms/editaddressdialog.ui \ qt/forms/helpmessagedialog.ui \ @@ -122,6 +123,8 @@ QT_MOC_CPP = \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ qt/moc_askpassphrasedialog.cpp \ + qt/moc_assetcontroldialog.cpp \ + qt/moc_assetcontroltreewidget.cpp \ qt/moc_bantablemodel.cpp \ qt/moc_ravenaddressvalidator.cpp \ qt/moc_ravenamountfield.cpp \ @@ -196,6 +199,8 @@ RAVEN_QT_H = \ qt/addressbookpage.h \ qt/addresstablemodel.h \ qt/askpassphrasedialog.h \ + qt/assetcontroldialog.h \ + qt/assetcontroltreewidget.h \ qt/assetsdialog.h \ qt/createassetdialog.h \ qt/bantablemodel.h \ @@ -343,6 +348,8 @@ RAVEN_QT_WALLET_CPP = \ qt/addressbookpage.cpp \ qt/addresstablemodel.cpp \ qt/askpassphrasedialog.cpp \ + qt/assetcontroldialog.cpp \ + qt/assetcontroltreewidget.cpp \ qt/assetsdialog.cpp \ qt/createassetdialog.cpp \ qt/coincontroldialog.cpp \ diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index b37f7f218b..6e6c812810 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -255,7 +255,7 @@ bool CNewAsset::IsValid(std::string& strError, CAssetsCache& assetCache, bool fC } if (nAmount > MAX_MONEY) { - strError = "Invalid parameter: asset amount greater than max money: " + MAX_MONEY / COIN; + strError = "Invalid parameter: asset amount greater than max money: " + std::to_string(MAX_MONEY / COIN); return false; } @@ -1976,7 +1976,15 @@ void GetAssetData(const CScript& script, CAssetOutputEntry& data) } } -void GetAllOwnedAssets(CWallet* pwallet, std::vector& names) +void GetAllAdministrativeAssets(CWallet *pwallet, std::vector &names) +{ + if(!pwallet) + return; + + GetAllMyAssets(pwallet, names, true, true); +} + +void GetAllMyAssets(CWallet* pwallet, std::vector& names, bool fIncludeAdministrator, bool fOnlyAdministrator) { if(!pwallet) return; @@ -1985,16 +1993,27 @@ void GetAllOwnedAssets(CWallet* pwallet, std::vector& names) pwallet->AvailableAssets(mapAssets); for (auto item : mapAssets) { - if (IsAssetNameAnOwner(item.first)) + bool isOwner = IsAssetNameAnOwner(item.first); + + if (isOwner) { + if (fOnlyAdministrator || fIncludeAdministrator) + names.emplace_back(item.first); + } else { + if (fOnlyAdministrator) + continue; names.emplace_back(item.first); + } } } -void GetAllMyAssets(std::vector& names) +void GetAllMyAssetsFromCache(std::vector& names) { - for (auto owned : passets->mapMyUnspentAssets) { + if (!passets) + return; + + for (auto owned : passets->mapMyUnspentAssets) names.emplace_back(owned.first); - } + } CAmount GetIssueAssetBurnAmount() @@ -2391,7 +2410,7 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu return true; } -bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) +bool CreateTransferAssetTransaction(CWallet* pwallet, const CCoinControl& coinControl, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) { // Initialize Values for transaction std::string strTxError; @@ -2451,10 +2470,8 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa vecSend.push_back(recipient); } - CCoinControl coin_control; - // Create and send the transaction - if (!pwallet->CreateTransactionWithTransferAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control)) { + if (!pwallet->CreateTransactionWithTransferAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coinControl)) { if (!fSubtractFeeFromAmount && nFeeRequired > curBalance) { error = std::make_pair(RPC_WALLET_ERROR, strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired))); return false; diff --git a/src/assets/assets.h b/src/assets/assets.h index ad18358717..82733fa8db 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -45,6 +45,7 @@ class CWallet; class CReserveKey; class CWalletTx; struct CAssetOutputEntry; +class CCoinControl; // 50000 * 82 Bytes == 4.1 Mb #define MAX_CACHE_ASSETS_SIZE 50000 @@ -345,8 +346,10 @@ bool IsScriptTransferAsset(const CScript& scriptPubKey); bool IsNewOwnerTxValid(const CTransaction& tx, const std::string& assetName, const std::string& address, std::string& errorMsg); -void GetAllOwnedAssets(CWallet* pwallet, std::vector& names); -void GetAllMyAssets(std::vector& names); +void GetAllAdministrativeAssets(CWallet *pwallet, std::vector &names); +void GetAllMyAssets(CWallet* pwallet, std::vector& names, bool fIncludeAdministrator = false, bool fOnlyAdministrator = false); +/** TO BE USED ONLY ON STARTUP */ +void GetAllMyAssetsFromCache(std::vector& names); void UpdatePossibleAssets(); @@ -371,6 +374,6 @@ std::string EncodeIPFS(std::string decoded); bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std::string& address, std::pair& error, std::string& rvnChangeAddress, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& asset, const std::string& address, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); -bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); +bool CreateTransferAssetTransaction(CWallet* pwallet, const CCoinControl& coinControl, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); bool SendAssetTransaction(CWallet* pwallet, CWalletTx& transaction, CReserveKey& reserveKey, std::pair& error, std::string& txid); #endif //RAVENCOIN_ASSET_PROTOCOL_H diff --git a/src/qt/assetcontroldialog.cpp b/src/qt/assetcontroldialog.cpp new file mode 100644 index 0000000000..14ea897f23 --- /dev/null +++ b/src/qt/assetcontroldialog.cpp @@ -0,0 +1,809 @@ +// Copyright (c) 2011-2016 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "assetcontroldialog.h" +#include "ui_assetcontroldialog.h" + +#include "addresstablemodel.h" +#include "ravenunits.h" +#include "guiutil.h" +#include "optionsmodel.h" +#include "platformstyle.h" +#include "txmempool.h" +#include "walletmodel.h" + +#include "wallet/coincontrol.h" +#include "init.h" +#include "policy/fees.h" +#include "policy/policy.h" +#include "validation.h" // For mempool +#include "wallet/fees.h" +#include "wallet/wallet.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QList AssetControlDialog::payAmounts; +CCoinControl* AssetControlDialog::assetControl = new CCoinControl(); +bool AssetControlDialog::fSubtractFeeFromAmount = false; + +bool CAssetControlWidgetItem::operator<(const QTreeWidgetItem &other) const { + int column = treeWidget()->sortColumn(); + if (column == AssetControlDialog::COLUMN_AMOUNT || column == AssetControlDialog::COLUMN_DATE || column == AssetControlDialog::COLUMN_CONFIRMATIONS) + return data(column, Qt::UserRole).toLongLong() < other.data(column, Qt::UserRole).toLongLong(); + return QTreeWidgetItem::operator<(other); +} + +AssetControlDialog::AssetControlDialog(const PlatformStyle *_platformStyle, QWidget *parent) : + QDialog(parent), + ui(new Ui::AssetControlDialog), + model(0), + platformStyle(_platformStyle) +{ + ui->setupUi(this); + + // context menu actions + QAction *copyAddressAction = new QAction(tr("Copy address"), this); + QAction *copyLabelAction = new QAction(tr("Copy label"), this); + QAction *copyAmountAction = new QAction(tr("Copy amount"), this); + copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this + lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this + unlockAction = new QAction(tr("Unlock unspent"), this); // we need to enable/disable this + + // context menu + contextMenu = new QMenu(this); + contextMenu->addAction(copyAddressAction); + contextMenu->addAction(copyLabelAction); + contextMenu->addAction(copyAmountAction); + contextMenu->addAction(copyTransactionHashAction); + contextMenu->addSeparator(); + contextMenu->addAction(lockAction); + contextMenu->addAction(unlockAction); + + // context menu signals + connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); + connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); + connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); + connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); + connect(copyTransactionHashAction, SIGNAL(triggered()), this, SLOT(copyTransactionHash())); + connect(lockAction, SIGNAL(triggered()), this, SLOT(lockCoin())); + connect(unlockAction, SIGNAL(triggered()), this, SLOT(unlockCoin())); + + // clipboard actions + QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); + QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); + QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); + QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); + QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); + QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); + QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); + + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(clipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(clipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(clipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(clipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(clipboardBytes())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(clipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(clipboardChange())); + + ui->labelAssetControlQuantity->addAction(clipboardQuantityAction); + ui->labelAssetControlAmount->addAction(clipboardAmountAction); + ui->labelAssetControlFee->addAction(clipboardFeeAction); + ui->labelAssetControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelAssetControlBytes->addAction(clipboardBytesAction); + ui->labelAssetControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelAssetControlChange->addAction(clipboardChangeAction); + + // toggle tree/list mode + connect(ui->radioTreeMode, SIGNAL(toggled(bool)), this, SLOT(radioTreeMode(bool))); + connect(ui->radioListMode, SIGNAL(toggled(bool)), this, SLOT(radioListMode(bool))); + + // click on checkbox + connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(viewItemChanged(QTreeWidgetItem*, int))); + + // click on header +#if QT_VERSION < 0x050000 + ui->treeWidget->header()->setClickable(true); +#else + ui->treeWidget->header()->setSectionsClickable(true); +#endif + connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); + + // ok button + connect(ui->buttonBox, SIGNAL(clicked( QAbstractButton*)), this, SLOT(buttonBoxClicked(QAbstractButton*))); + + // (un)select all + connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); + + // change coin control first column label due Qt4 bug. + // see https://github.com/RavenProject/Ravencoin/issues/5716 + ui->treeWidget->headerItem()->setText(COLUMN_CHECKBOX, QString()); + + ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); + ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110); + ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190); + ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320); + ui->treeWidget->setColumnWidth(COLUMN_DATE, 130); + ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110); + ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transaction hash in this column, but don't show it + ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but don't show it + + // default view is sorted by amount desc + sortView(COLUMN_AMOUNT, Qt::DescendingOrder); + + // restore list mode and sortorder as a convenience feature + QSettings settings; + if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool()) + ui->radioTreeMode->click(); + if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) + sortView(settings.value("nCoinControlSortColumn").toInt(), ((Qt::SortOrder)settings.value("nCoinControlSortOrder").toInt())); + + // Add the assets into the dropdown menu + connect(ui->viewAdministrator, SIGNAL(clicked()), this, SLOT(viewAdministratorClicked())); + connect(ui->assetList, SIGNAL(currentIndexChanged(QString)), this, SLOT(onAssetSelected(QString))); +} + +AssetControlDialog::~AssetControlDialog() +{ + QSettings settings; + settings.setValue("nCoinControlMode", ui->radioListMode->isChecked()); + settings.setValue("nCoinControlSortColumn", sortColumn); + settings.setValue("nCoinControlSortOrder", (int)sortOrder); + + delete ui; +} + +void AssetControlDialog::setModel(WalletModel *_model) +{ + this->model = _model; + + if(_model && _model->getOptionsModel() && _model->getAddressTableModel()) + { + updateView(); + updateAssetList(true); + updateLabelLocked(); + AssetControlDialog::updateLabels(_model, this); + } +} + +// ok button +void AssetControlDialog::buttonBoxClicked(QAbstractButton* button) +{ + if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + if (AssetControlDialog::assetControl->HasSelected()) + AssetControlDialog::assetControl->strAssetSelected = ui->assetList->currentText().toStdString(); + done(QDialog::Accepted); // closes the dialog + } +} + +// (un)select all +void AssetControlDialog::buttonSelectAllClicked() +{ + Qt::CheckState state = Qt::Checked; + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + { + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) + { + state = Qt::Unchecked; + break; + } + } + ui->treeWidget->setEnabled(false); + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) + ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); + ui->treeWidget->setEnabled(true); + if (state == Qt::Unchecked) + assetControl->UnSelectAll(); // just to be sure + AssetControlDialog::updateLabels(model, this); +} + +// context menu +void AssetControlDialog::showMenu(const QPoint &point) +{ + QTreeWidgetItem *item = ui->treeWidget->itemAt(point); + if(item) + { + contextMenuItem = item; + + // disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu + if (item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + copyTransactionHashAction->setEnabled(true); + if (model->isLockedCoin(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) + { + lockAction->setEnabled(false); + unlockAction->setEnabled(true); + } + else + { + lockAction->setEnabled(true); + unlockAction->setEnabled(false); + } + } + else // this means click on parent node in tree mode -> disable all + { + copyTransactionHashAction->setEnabled(false); + lockAction->setEnabled(false); + unlockAction->setEnabled(false); + } + + // show context menu + contextMenu->exec(QCursor::pos()); + } +} + +// context menu action: copy amount +void AssetControlDialog::copyAmount() +{ + GUIUtil::setClipboard(RavenUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT))); +} + +// context menu action: copy label +void AssetControlDialog::copyLabel() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) + GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL)); + else + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL)); +} + +// context menu action: copy address +void AssetControlDialog::copyAddress() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) + GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS)); + else + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS)); +} + +// context menu action: copy transaction id +void AssetControlDialog::copyTransactionHash() +{ + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_TXHASH)); +} + +// context menu action: lock coin +void AssetControlDialog::lockCoin() +{ + if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) + contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->lockCoin(outpt); + contextMenuItem->setDisabled(true); + contextMenuItem->setIcon(COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); + updateLabelLocked(); +} + +// context menu action: unlock coin +void AssetControlDialog::unlockCoin() +{ + COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->unlockCoin(outpt); + contextMenuItem->setDisabled(false); + contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); + updateLabelLocked(); +} + +// copy label "Quantity" to clipboard +void AssetControlDialog::clipboardQuantity() +{ + GUIUtil::setClipboard(ui->labelAssetControlQuantity->text()); +} + +// copy label "Amount" to clipboard +void AssetControlDialog::clipboardAmount() +{ + GUIUtil::setClipboard(ui->labelAssetControlAmount->text().left(ui->labelAssetControlAmount->text().indexOf(" "))); +} + +// copy label "Fee" to clipboard +void AssetControlDialog::clipboardFee() +{ + GUIUtil::setClipboard(ui->labelAssetControlFee->text().left(ui->labelAssetControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); +} + +// copy label "After fee" to clipboard +void AssetControlDialog::clipboardAfterFee() +{ + GUIUtil::setClipboard(ui->labelAssetControlAfterFee->text().left(ui->labelAssetControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); +} + +// copy label "Bytes" to clipboard +void AssetControlDialog::clipboardBytes() +{ + GUIUtil::setClipboard(ui->labelAssetControlBytes->text().replace(ASYMP_UTF8, "")); +} + +// copy label "Dust" to clipboard +void AssetControlDialog::clipboardLowOutput() +{ + GUIUtil::setClipboard(ui->labelAssetControlLowOutput->text()); +} + +// copy label "Change" to clipboard +void AssetControlDialog::clipboardChange() +{ + GUIUtil::setClipboard(ui->labelAssetControlChange->text().left(ui->labelAssetControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); +} + +// treeview: sort +void AssetControlDialog::sortView(int column, Qt::SortOrder order) +{ + sortColumn = column; + sortOrder = order; + ui->treeWidget->sortItems(column, order); + ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); +} + +// treeview: clicked on header +void AssetControlDialog::headerSectionClicked(int logicalIndex) +{ + if (logicalIndex == COLUMN_CHECKBOX) // click on most left column -> do nothing + { + ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); + } + else + { + if (sortColumn == logicalIndex) + sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); + else + { + sortColumn = logicalIndex; + sortOrder = ((sortColumn == COLUMN_LABEL || sortColumn == COLUMN_ADDRESS) ? Qt::AscendingOrder : Qt::DescendingOrder); // if label or address then default => asc, else default => desc + } + + sortView(sortColumn, sortOrder); + } +} + +// toggle tree mode +void AssetControlDialog::radioTreeMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// toggle list mode +void AssetControlDialog::radioListMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// checkbox clicked by user +void AssetControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) +{ + if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); + + if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) + assetControl->UnSelect(outpt); + else if (item->isDisabled()) // locked (this happens if "check all" through parent node) + item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + else + assetControl->Select(outpt); + + // selection changed -> update labels + if (ui->treeWidget->isEnabled()) // do not update on every click for (un)select all + AssetControlDialog::updateLabels(model, this); + } + + // TODO: Remove this temporary qt5 fix after Qt5.3 and Qt5.4 are no longer used. + // Fixed in Qt5.5 and above: https://bugreports.qt.io/browse/QTBUG-43473 +#if QT_VERSION >= 0x050000 + else if (column == COLUMN_CHECKBOX && item->childCount() > 0) + { + if (item->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked && item->child(0)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) + item->setCheckState(COLUMN_CHECKBOX, Qt::Checked); + } +#endif +} + +// shows count of locked unspent outputs +void AssetControlDialog::updateLabelLocked() +{ + std::vector vOutpts; + model->listLockedCoins(vOutpts); + if (vOutpts.size() > 0) + { + ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); + ui->labelLocked->setVisible(true); + } + else ui->labelLocked->setVisible(false); +} + +void AssetControlDialog::updateLabels(WalletModel *model, QDialog* dialog) +{ + if (!model) + return; + + // nPayAmount + CAmount nPayAmount = 0; + bool fDust = false; + CMutableTransaction txDummy; + for (const CAmount &amount : AssetControlDialog::payAmounts) + { + nPayAmount += amount; + + if (amount > 0) + { + CTxOut txout(amount, (CScript)std::vector(24, 0)); + txDummy.vout.push_back(txout); + fDust |= IsDust(txout, ::dustRelayFee); + } + } + + std::string strAssetName = ""; + CAmount nAssetAmount = 0; + CAmount nAmount = 0; + CAmount nPayFee = 0; + CAmount nAfterFee = 0; + CAmount nChange = 0; + unsigned int nBytes = 0; + unsigned int nBytesInputs = 0; + unsigned int nQuantity = 0; + bool fWitness = false; + + std::vector vCoinControl; + std::vector vOutputs; + assetControl->ListSelected(vCoinControl); + model->getOutputs(vCoinControl, vOutputs); + + for (const COutput& out : vOutputs) { + // unselect already spent, very unlikely scenario, this could happen + // when selected are spent elsewhere, like rpc or another computer + uint256 txhash = out.tx->GetHash(); + COutPoint outpt(txhash, out.i); + if (model->isSpent(outpt)) + { + assetControl->UnSelect(outpt); + continue; + } + + // Quantity + nQuantity++; + + // Amount + CAmount nCoinAmount; + GetAssetInfoFromScript(out.tx->tx->vout[out.i].scriptPubKey, strAssetName, nCoinAmount); + nAssetAmount += nCoinAmount; + + // Bytes + CTxDestination address; + int witnessversion = 0; + std::vector witnessprogram; + if (out.tx->tx->vout[out.i].scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) + { + nBytesInputs += (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4); + fWitness = true; + } + else if(ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address)) + { + CPubKey pubkey; + CKeyID *keyid = boost::get(&address); + if (keyid && model->getPubKey(*keyid, pubkey)) + { + nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); + } + else + nBytesInputs += 148; // in all error cases, simply assume 148 here + } + else nBytesInputs += 148; + } + + // calculation + if (nQuantity > 0) + { + // Bytes + nBytes = nBytesInputs + ((AssetControlDialog::payAmounts.size() > 0 ? AssetControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here + if (fWitness) + { + // there is some fudging in these numbers related to the actual virtual transaction size calculation that will keep this estimate from being exact. + // usually, the result will be an overestimate within a couple of satoshis so that the confirmation dialog ends up displaying a slightly smaller fee. + // also, the witness stack size value is a variable sized integer. usually, the number of stack items will be well under the single byte var int limit. + nBytes += 2; // account for the serialized marker and flag bytes + nBytes += nQuantity; // account for the witness byte that holds the number of stack items for each input. + } + + // in the subtract fee from amount case, we can tell if zero change already and subtract the bytes, so that fee calculation afterwards is accurate + if (AssetControlDialog::fSubtractFeeFromAmount) + if (nAmount - nPayAmount == 0) + nBytes -= 34; + + // Fee + nPayFee = GetMinimumFee(nBytes, *assetControl, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */); + + if (nPayAmount > 0) + { + nChange = nAssetAmount - nPayAmount; + + if (nChange == 0 && !AssetControlDialog::fSubtractFeeFromAmount) + nBytes -= 34; + } + + // after fee + nAfterFee = std::max(nPayFee, 0); + } + + // actually update labels + int nDisplayUnit = RavenUnits::RVN; + if (model && model->getOptionsModel()) + nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + QLabel *l1 = dialog->findChild("labelAssetControlQuantity"); + QLabel *l2 = dialog->findChild("labelAssetControlAmount"); + QLabel *l3 = dialog->findChild("labelAssetControlFee"); + QLabel *l4 = dialog->findChild("labelAssetControlAfterFee"); + QLabel *l5 = dialog->findChild("labelAssetControlBytes"); + QLabel *l7 = dialog->findChild("labelAssetControlLowOutput"); + QLabel *l8 = dialog->findChild("labelAssetControlChange"); + + // enable/disable "dust" and "change" + dialog->findChild("labelAssetControlLowOutputText")->setEnabled(nPayAmount > 0); + dialog->findChild("labelAssetControlLowOutput") ->setEnabled(nPayAmount > 0); + dialog->findChild("labelAssetControlChangeText") ->setEnabled(nPayAmount > 0); + dialog->findChild("labelAssetControlChange") ->setEnabled(nPayAmount > 0); + + // stats + l1->setText(QString::number(nQuantity)); // Quantity + l2->setText(RavenUnits::formatWithCustomName(QString::fromStdString(strAssetName), nAssetAmount)); // Amount + l3->setText(RavenUnits::formatWithUnit(nDisplayUnit, nPayFee)); // Fee + l4->setText(RavenUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // After Fee + l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Bytes + l7->setText(fDust ? tr("yes") : tr("no")); // Dust + l8->setText(RavenUnits::formatWithCustomName(QString::fromStdString(strAssetName), nChange)); // Change + if (nPayFee > 0) + { + l3->setText(ASYMP_UTF8 + l3->text()); + l4->setText(ASYMP_UTF8 + l4->text()); + } + + // turn label red when dust + l7->setStyleSheet((fDust) ? "color:red;" : ""); + + // tool tips + QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller than the current dust threshold."); + + // how many satoshis the estimated fee can vary per byte we guess wrong + double dFeeVary = (nBytes != 0) ? (double)nPayFee / nBytes : 0; + + QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); + + l3->setToolTip(toolTip4); + l4->setToolTip(toolTip4); + l7->setToolTip(toolTipDust); + l8->setToolTip(toolTip4); + dialog->findChild("labelAssetControlFeeText") ->setToolTip(l3->toolTip()); + dialog->findChild("labelAssetControlAfterFeeText") ->setToolTip(l4->toolTip()); + dialog->findChild("labelAssetControlBytesText") ->setToolTip(l5->toolTip()); + dialog->findChild("labelAssetControlLowOutputText")->setToolTip(l7->toolTip()); + dialog->findChild("labelAssetControlChangeText") ->setToolTip(l8->toolTip()); + + // Insufficient funds + QLabel *label = dialog->findChild("labelAssetControlInsuffFunds"); + if (label) + label->setVisible(nChange < 0); +} + +void AssetControlDialog::updateView() +{ + if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) + return; + + bool treeMode = ui->radioTreeMode->isChecked(); + + ui->treeWidget->clear(); + ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox + ui->treeWidget->setAlternatingRowColors(!treeMode); + QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; + + int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + std::map > > mapCoins; + model->listAssets(mapCoins); + + QString assetToDisplay = ui->assetList->currentText(); + + // Double check to make sure that the asset selected has coins in the map + if (!mapCoins.count(assetToDisplay)) + return; + + // For now we only support for one assets coins being shown at a time + // So we only loop through coins for that specific asset + auto mapAssetCoins = mapCoins.at(assetToDisplay); + + for (const std::pair> &coins : mapAssetCoins) { + CAssetControlWidgetItem *itemWalletAddress = new CAssetControlWidgetItem(); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + QString sWalletAddress = coins.first; + QString sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); + if (sWalletLabel.isEmpty()) + sWalletLabel = tr("(no label)"); + + if (treeMode) { + // wallet address + ui->treeWidget->addTopLevelItem(itemWalletAddress); + + itemWalletAddress->setFlags(flgTristate); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + // label + itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); + + // address + itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); + + // asset name + itemWalletAddress->setText(COLUMN_ASSET_NAME, assetToDisplay); + } + + CAmount nSum = 0; + int nChildren = 0; + for (const COutput &out : coins.second) { + std::string strAssetName; + CAmount nAmount; + if (!GetAssetInfoFromScript(out.tx->tx->vout[out.i].scriptPubKey, strAssetName, nAmount)) + continue; + + if (strAssetName != assetToDisplay.toStdString()) + continue; + + nSum += nAmount; + nChildren++; + + CAssetControlWidgetItem *itemOutput; + if (treeMode) itemOutput = new CAssetControlWidgetItem(itemWalletAddress); + else itemOutput = new CAssetControlWidgetItem(ui->treeWidget); + itemOutput->setFlags(flgCheckbox); + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + // address + CTxDestination outputAddress; + QString sAddress = ""; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, outputAddress)) { + sAddress = QString::fromStdString(EncodeDestination(outputAddress)); + + // if listMode or change => show raven address. In tree mode, address is not shown again for direct wallet address outputs + if (!treeMode || (!(sAddress == sWalletAddress))) { + itemOutput->setText(COLUMN_ADDRESS, sAddress); + // asset name + itemOutput->setText(COLUMN_ASSET_NAME, QString::fromStdString(strAssetName)); + } + } + + // label + if (!(sAddress == sWalletAddress)) // change + { + // tooltip from where the change comes from + itemOutput->setToolTip(COLUMN_LABEL, + tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress)); + itemOutput->setText(COLUMN_LABEL, tr("(change)")); + } else if (!treeMode) { + QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress); + if (sLabel.isEmpty()) + sLabel = tr("(no label)"); + itemOutput->setText(COLUMN_LABEL, sLabel); + } + + // amount + itemOutput->setText(COLUMN_AMOUNT, RavenUnits::format(nDisplayUnit, nAmount)); + itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, + QVariant((qlonglong) nAmount)); // padding so that sorting works correctly + + // date + itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime())); + itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong) out.tx->GetTxTime())); + + // confirmations + itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.nDepth)); + itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong) out.nDepth)); + + // transaction hash + uint256 txhash = out.tx->GetHash(); + itemOutput->setText(COLUMN_TXHASH, QString::fromStdString(txhash.GetHex())); + + // vout index + itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i)); + + // disable locked coins + if (model->isLockedCoin(txhash, out.i)) { + COutPoint outpt(txhash, out.i); + assetControl->UnSelect(outpt); // just to be sure + itemOutput->setDisabled(true); + itemOutput->setIcon(COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); + } + + // set checkbox + if (assetControl->IsSelected(COutPoint(txhash, out.i))) + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); + } + + // amount + if (treeMode) { + itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); + itemWalletAddress->setText(COLUMN_AMOUNT, RavenUnits::format(nDisplayUnit, nSum)); + itemWalletAddress->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong) nSum)); + } + } + + // expand all partially selected + if (treeMode) + { + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) + ui->treeWidget->topLevelItem(i)->setExpanded(true); + } + + // sort view + sortView(sortColumn, sortOrder); + ui->treeWidget->setEnabled(true); +} + +void AssetControlDialog::viewAdministratorClicked() +{ + assetControl->UnSelectAll(); + AssetControlDialog::updateLabels(model, this); + updateAssetList(); +} + +void AssetControlDialog::updateAssetList(bool fSetOnStart) +{ + if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) + return; + + bool showAdministrator = ui->viewAdministrator->isChecked(); + if (fSetOnStart) { + showAdministrator = IsAssetNameAnOwner(assetControl->strAssetSelected); + ui->viewAdministrator->setChecked(showAdministrator); + } + // Get the assets + std::vector assets; + if (showAdministrator) + GetAllAdministrativeAssets(model->getWallet(), assets); + else + GetAllMyAssets(model->getWallet(), assets); + + QStringList list; + for (auto name : assets) { + list << QString::fromStdString(name); + } + ui->assetList->clear(); + + // Add the assets into the dropdown menu + ui->assetList->addItem("Select as asset to view"); + ui->assetList->addItems(list); + + int index = ui->assetList->findText(QString::fromStdString(assetControl->strAssetSelected)); + if ( index != -1 ) { // -1 for not found + fOnStartUp = fSetOnStart; + ui->assetList->setCurrentText(QString::fromStdString(assetControl->strAssetSelected)); + } + + updateView(); +} + +void AssetControlDialog::onAssetSelected(QString name) +{ + if (fOnStartUp) { + fOnStartUp = false; + } else { + assetControl->UnSelectAll(); + } + + AssetControlDialog::updateLabels(model, this); + updateView(); +} diff --git a/src/qt/assetcontroldialog.h b/src/qt/assetcontroldialog.h new file mode 100644 index 0000000000..817f94d055 --- /dev/null +++ b/src/qt/assetcontroldialog.h @@ -0,0 +1,119 @@ +// Copyright (c) 2011-2016 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef RAVEN_QT_ASSETCONTROLDIALOG_H +#define RAVEN_QT_ASSETCONTROLDIALOG_H + +#include "amount.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class PlatformStyle; +class WalletModel; + +class CCoinControl; + +namespace Ui { + class AssetControlDialog; +} + +#define ASYMP_UTF8 "\xE2\x89\x88" + +class CAssetControlWidgetItem : public QTreeWidgetItem +{ +public: + explicit CAssetControlWidgetItem(QTreeWidget *parent, int type = Type) : QTreeWidgetItem(parent, type) {} + explicit CAssetControlWidgetItem(int type = Type) : QTreeWidgetItem(type) {} + explicit CAssetControlWidgetItem(QTreeWidgetItem *parent, int type = Type) : QTreeWidgetItem(parent, type) {} + + bool operator<(const QTreeWidgetItem &other) const; +}; + +class AssetControlDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AssetControlDialog(const PlatformStyle *platformStyle, QWidget *parent = 0); + ~AssetControlDialog(); + + void setModel(WalletModel *model); + + // static because also called from sendcoinsdialog + static void updateLabels(WalletModel*, QDialog*); + + //update the list of assets + void updateAssetList(bool fSetOnStart = false); + + static QList payAmounts; + static CCoinControl *assetControl; + static bool fSubtractFeeFromAmount; + bool fOnStartUp; + +private: + Ui::AssetControlDialog *ui; + WalletModel *model; + int sortColumn; + Qt::SortOrder sortOrder; + + QMenu *contextMenu; + QTreeWidgetItem *contextMenuItem; + QAction *copyTransactionHashAction; + QAction *lockAction; + QAction *unlockAction; + + const PlatformStyle *platformStyle; + + void sortView(int, Qt::SortOrder); + void updateView(); + + enum + { + COLUMN_CHECKBOX = 0, + COLUMN_ASSET_NAME, + COLUMN_AMOUNT, + COLUMN_LABEL, + COLUMN_ADDRESS, + COLUMN_DATE, + COLUMN_CONFIRMATIONS, + COLUMN_TXHASH, + COLUMN_VOUT_INDEX, + }; + friend class CAssetControlWidgetItem; + +private Q_SLOTS: + void showMenu(const QPoint &); + void copyAmount(); + void copyLabel(); + void copyAddress(); + void copyTransactionHash(); + void lockCoin(); + void unlockCoin(); + void clipboardQuantity(); + void clipboardAmount(); + void clipboardFee(); + void clipboardAfterFee(); + void clipboardBytes(); + void clipboardLowOutput(); + void clipboardChange(); + void radioTreeMode(bool); + void radioListMode(bool); + void viewItemChanged(QTreeWidgetItem*, int); + void headerSectionClicked(int); + void buttonBoxClicked(QAbstractButton*); + void buttonSelectAllClicked(); + void updateLabelLocked(); + void viewAdministratorClicked(); + void onAssetSelected(QString name); +}; + +#endif // RAVEN_QT_ASSETCONTROLDIALOG_H diff --git a/src/qt/assetcontroltreewidget.cpp b/src/qt/assetcontroltreewidget.cpp new file mode 100644 index 0000000000..47f8a38fe2 --- /dev/null +++ b/src/qt/assetcontroltreewidget.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "assetcontroltreewidget.h" +#include "assetcontroldialog.h" + +AssetControlTreeWidget::AssetControlTreeWidget(QWidget *parent) : + QTreeWidget(parent) +{ + +} + +void AssetControlTreeWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Space) // press spacebar -> select checkbox + { + event->ignore(); + if (this->currentItem()) { + int COLUMN_CHECKBOX = 0; + this->currentItem()->setCheckState(COLUMN_CHECKBOX, ((this->currentItem()->checkState(COLUMN_CHECKBOX) == Qt::Checked) ? Qt::Unchecked : Qt::Checked)); + } + } + else if (event->key() == Qt::Key_Escape) // press esc -> close dialog + { + event->ignore(); + AssetControlDialog *assetControlDialog = (AssetControlDialog*)this->parentWidget(); + assetControlDialog->done(QDialog::Accepted); + } + else + { + this->QTreeWidget::keyPressEvent(event); + } +} diff --git a/src/qt/assetcontroltreewidget.h b/src/qt/assetcontroltreewidget.h new file mode 100644 index 0000000000..99db4e7359 --- /dev/null +++ b/src/qt/assetcontroltreewidget.h @@ -0,0 +1,23 @@ +// Copyright (c) 2011-2014 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef RAVEN_QT_ASSETCONTROLTREEWIDGET_H +#define RAVEN_QT_ASSETCONTROLTREEWIDGET_H + +#include +#include + +class AssetControlTreeWidget : public QTreeWidget +{ + Q_OBJECT + +public: + explicit AssetControlTreeWidget(QWidget *parent = 0); + +protected: + virtual void keyPressEvent(QKeyEvent *event); +}; + +#endif // RAVEN_QT_ASSETCONTROLTREEWIDGET_H diff --git a/src/qt/assetsdialog.cpp b/src/qt/assetsdialog.cpp index 056d498dd3..56a1538424 100644 --- a/src/qt/assetsdialog.cpp +++ b/src/qt/assetsdialog.cpp @@ -10,7 +10,7 @@ #include "addresstablemodel.h" #include "ravenunits.h" #include "clientmodel.h" -#include "coincontroldialog.h" +#include "assetcontroldialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" @@ -78,7 +78,7 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send")); } - GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this); + GUIUtil::setupAddressWidget(ui->lineEditAssetControlChange, this); addEntry(); @@ -86,9 +86,9 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); // Coin Control - connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); - connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); - connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); + connect(ui->pushButtonAssetControl, SIGNAL(clicked()), this, SLOT(assetControlButtonClicked())); + connect(ui->checkBoxAssetControlChange, SIGNAL(stateChanged(int)), this, SLOT(assetControlChangeChecked(int))); + connect(ui->lineEditAssetControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(assetControlChangeEdited(const QString &))); // Coin Control: clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); @@ -98,20 +98,20 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); - connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity())); - connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount())); - connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee())); - connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee())); - connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes())); - connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput())); - connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange())); - ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); - ui->labelCoinControlAmount->addAction(clipboardAmountAction); - ui->labelCoinControlFee->addAction(clipboardFeeAction); - ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); - ui->labelCoinControlBytes->addAction(clipboardBytesAction); - ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); - ui->labelCoinControlChange->addAction(clipboardChangeAction); + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardBytes())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardChange())); + ui->labelAssetControlQuantity->addAction(clipboardQuantityAction); + ui->labelAssetControlAmount->addAction(clipboardAmountAction); + ui->labelAssetControlFee->addAction(clipboardFeeAction); + ui->labelAssetControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelAssetControlBytes->addAction(clipboardBytesAction); + ui->labelAssetControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelAssetControlChange->addAction(clipboardChangeAction); // init transaction fee section QSettings settings; @@ -186,29 +186,29 @@ void AssetsDialog::setModel(WalletModel *_model) updateDisplayUnit(); // Coin Control - connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels())); - connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool))); + connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(assetControlUpdateLabels())); + connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(assetControlFeatureChanged(bool))); - ui->frameCoinControl->setVisible(false); + ui->frameAssetControl->setVisible(false); //TODO Turn on the coin control features for assets -// ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures()); - coinControlUpdateLabels(); + ui->frameAssetControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures()); + assetControlUpdateLabels(); // fee section for (const int &n : confTargets) { ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n)); } connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel())); - connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(assetControlUpdateLabels())); connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls())); - connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); - connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels())); + connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(assetControlUpdateLabels())); + connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(assetControlUpdateLabels())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls())); - connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(assetControlUpdateLabels())); connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel())); - connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(assetControlUpdateLabels())); ui->customFee->setSingleStep(GetRequiredFee(1000)); updateFeeSectionControls(); updateMinFeeLabel(); @@ -289,12 +289,21 @@ void AssetsDialog::on_sendButton_clicked() vTransfers.emplace_back(std::make_pair(CAssetTransfer(recipient.assetName.toStdString(), recipient.amount), recipient.address.toStdString())); } + // Always use a CCoinControl instance, use the AssetControlDialog instance if CoinControl has been enabled + CCoinControl ctrl; + if (model->getOptionsModel()->getCoinControlFeatures()) + ctrl = *AssetControlDialog::assetControl; + + ctrl.fForAssets = true; + + updateAssetControlState(ctrl); + CWalletTx tx; CReserveKey reservekey(model->getWallet()); std::pair error; CAmount nFeeRequired; - if (!CreateTransferAssetTransaction(model->getWallet(), vTransfers, "", error, tx, reservekey, nFeeRequired)) { + if (!CreateTransferAssetTransaction(model->getWallet(), ctrl, vTransfers, "", error, tx, reservekey, nFeeRequired)) { QMessageBox msgBox; msgBox.setText(QString::fromStdString(error.second)); msgBox.exec(); @@ -379,8 +388,8 @@ void AssetsDialog::on_sendButton_clicked() if (sendStatus.status == WalletModel::OK) { accept(); - CoinControlDialog::coinControl->UnSelectAll(); - coinControlUpdateLabels(); + AssetControlDialog::assetControl->UnSelectAll(); + assetControlUpdateLabels(); } fNewRecipientAllowed = true; } @@ -411,20 +420,31 @@ SendAssetsEntry *AssetsDialog::addEntry() { LOCK(cs_main); std::vector assets; - GetAllMyAssets(assets); + if (model) + GetAllMyAssets(model->getWallet(), assets); + else // If the model isn't present. Grab the list of assets that the cache thinks you own + GetAllMyAssetsFromCache(assets); QStringList list; - for (auto name : assets) { - if (!IsAssetNameAnOwner(name)) - list << QString::fromStdString(name); + bool fIsOwner = false; + bool fIsAssetControl = false; + if (AssetControlDialog::assetControl->HasSelected()) { + list << QString::fromStdString(AssetControlDialog::assetControl->strAssetSelected); + fIsOwner = IsAssetNameAnOwner(AssetControlDialog::assetControl->strAssetSelected); + fIsAssetControl = true; + } else { + for (auto name : assets) { + if (!IsAssetNameAnOwner(name)) + list << QString::fromStdString(name); + } } SendAssetsEntry *entry = new SendAssetsEntry(platformStyle, list, this); entry->setModel(model); ui->entries->addWidget(entry); connect(entry, SIGNAL(removeEntry(SendAssetsEntry*)), this, SLOT(removeEntry(SendAssetsEntry*))); - connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels())); - connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels())); + connect(entry, SIGNAL(payAmountChanged()), this, SLOT(assetControlUpdateLabels())); + connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(assetControlUpdateLabels())); // Focus the field, so that entry can start immediately entry->clear(); @@ -435,14 +455,20 @@ SendAssetsEntry *AssetsDialog::addEntry() if(bar) bar->setSliderPosition(bar->maximum()); + entry->IsAssetControl(fIsAssetControl, fIsOwner); + + if (list.size() == 1) + entry->setCurrentIndex(1); + updateTabsAndLabels(); + return entry; } void AssetsDialog::updateTabsAndLabels() { setupTabChain(0); - coinControlUpdateLabels(); + assetControlUpdateLabels(); } void AssetsDialog::removeEntry(SendAssetsEntry* entry) @@ -658,7 +684,7 @@ void AssetsDialog::updateMinFeeLabel() ); } -void AssetsDialog::updateCoinControlState(CCoinControl& ctrl) +void AssetsDialog::updateAssetControlState(CCoinControl& ctrl) { if (ui->radioCustomFee->isChecked()) { ctrl.m_feerate = CFeeRate(ui->customFee->value()); @@ -676,7 +702,7 @@ void AssetsDialog::updateSmartFeeLabel() if(!model || !model->getOptionsModel()) return; CCoinControl coin_control; - updateCoinControlState(coin_control); + updateAssetControlState(coin_control); coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels FeeCalculation feeCalc; CFeeRate feeRate = CFeeRate(GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc)); @@ -703,147 +729,148 @@ void AssetsDialog::updateSmartFeeLabel() } // Coin Control: copy label "Quantity" to clipboard -void AssetsDialog::coinControlClipboardQuantity() +void AssetsDialog::assetControlClipboardQuantity() { - GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); + GUIUtil::setClipboard(ui->labelAssetControlQuantity->text()); } // Coin Control: copy label "Amount" to clipboard -void AssetsDialog::coinControlClipboardAmount() +void AssetsDialog::assetControlClipboardAmount() { - GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" "))); + GUIUtil::setClipboard(ui->labelAssetControlAmount->text().left(ui->labelAssetControlAmount->text().indexOf(" "))); } // Coin Control: copy label "Fee" to clipboard -void AssetsDialog::coinControlClipboardFee() +void AssetsDialog::assetControlClipboardFee() { - GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlFee->text().left(ui->labelAssetControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); } // Coin Control: copy label "After fee" to clipboard -void AssetsDialog::coinControlClipboardAfterFee() +void AssetsDialog::assetControlClipboardAfterFee() { - GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlAfterFee->text().left(ui->labelAssetControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Bytes" to clipboard -void AssetsDialog::coinControlClipboardBytes() +void AssetsDialog::assetControlClipboardBytes() { - GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlBytes->text().replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Dust" to clipboard -void AssetsDialog::coinControlClipboardLowOutput() +void AssetsDialog::assetControlClipboardLowOutput() { - GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); + GUIUtil::setClipboard(ui->labelAssetControlLowOutput->text()); } // Coin Control: copy label "Change" to clipboard -void AssetsDialog::coinControlClipboardChange() +void AssetsDialog::assetControlClipboardChange() { - GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlChange->text().left(ui->labelAssetControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); } // Coin Control: settings menu - coin control enabled/disabled by user -void AssetsDialog::coinControlFeatureChanged(bool checked) +void AssetsDialog::assetControlFeatureChanged(bool checked) { - ui->frameCoinControl->setVisible(checked); + ui->frameAssetControl->setVisible(checked); if (!checked && model) // coin control features disabled - CoinControlDialog::coinControl->SetNull(); + AssetControlDialog::assetControl->SetNull(); - coinControlUpdateLabels(); + assetControlUpdateLabels(); } // Coin Control: button inputs -> show actual coin control dialog -void AssetsDialog::coinControlButtonClicked() +void AssetsDialog::assetControlButtonClicked() { - CoinControlDialog dlg(platformStyle); + AssetControlDialog dlg(platformStyle); dlg.setModel(model); dlg.exec(); - coinControlUpdateLabels(); + assetControlUpdateLabels(); + assetControlUpdateSendCoinsDialog(); } // Coin Control: checkbox custom change address -void AssetsDialog::coinControlChangeChecked(int state) +void AssetsDialog::assetControlChangeChecked(int state) { if (state == Qt::Unchecked) { - CoinControlDialog::coinControl->destChange = CNoDestination(); - ui->labelCoinControlChangeLabel->clear(); + AssetControlDialog::assetControl->destChange = CNoDestination(); + ui->labelAssetControlChangeLabel->clear(); } else // use this to re-validate an already entered address - coinControlChangeEdited(ui->lineEditCoinControlChange->text()); + assetControlChangeEdited(ui->lineEditAssetControlChange->text()); - ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked)); + ui->lineEditAssetControlChange->setEnabled((state == Qt::Checked)); } // Coin Control: custom change address changed -void AssetsDialog::coinControlChangeEdited(const QString& text) +void AssetsDialog::assetControlChangeEdited(const QString& text) { if (model && model->getAddressTableModel()) { // Default to no change address until verified - CoinControlDialog::coinControl->destChange = CNoDestination(); - ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); + AssetControlDialog::assetControl->destChange = CNoDestination(); + ui->labelAssetControlChangeLabel->setStyleSheet("QLabel{color:red;}"); const CTxDestination dest = DecodeDestination(text.toStdString()); if (text.isEmpty()) // Nothing entered { - ui->labelCoinControlChangeLabel->setText(""); + ui->labelAssetControlChangeLabel->setText(""); } else if (!IsValidDestination(dest)) // Invalid address { - ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Raven address")); + ui->labelAssetControlChangeLabel->setText(tr("Warning: Invalid Raven address")); } else // Valid address { if (!model->IsSpendable(dest)) { - ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address")); + ui->labelAssetControlChangeLabel->setText(tr("Warning: Unknown change address")); // confirmation dialog QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm custom change address"), tr("The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?"), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); if(btnRetVal == QMessageBox::Yes) - CoinControlDialog::coinControl->destChange = dest; + AssetControlDialog::assetControl->destChange = dest; else { - ui->lineEditCoinControlChange->setText(""); - ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); - ui->labelCoinControlChangeLabel->setText(""); + ui->lineEditAssetControlChange->setText(""); + ui->labelAssetControlChangeLabel->setStyleSheet("QLabel{color:black;}"); + ui->labelAssetControlChangeLabel->setText(""); } } else // Known change address { - ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); + ui->labelAssetControlChangeLabel->setStyleSheet("QLabel{color:black;}"); // Query label QString associatedLabel = model->getAddressTableModel()->labelForAddress(text); if (!associatedLabel.isEmpty()) - ui->labelCoinControlChangeLabel->setText(associatedLabel); + ui->labelAssetControlChangeLabel->setText(associatedLabel); else - ui->labelCoinControlChangeLabel->setText(tr("(no label)")); + ui->labelAssetControlChangeLabel->setText(tr("(no label)")); - CoinControlDialog::coinControl->destChange = dest; + AssetControlDialog::assetControl->destChange = dest; } } } } // Coin Control: update labels -void AssetsDialog::coinControlUpdateLabels() +void AssetsDialog::assetControlUpdateLabels() { if (!model || !model->getOptionsModel()) return; - updateCoinControlState(*CoinControlDialog::coinControl); + updateAssetControlState(*AssetControlDialog::assetControl); // set pay amounts - CoinControlDialog::payAmounts.clear(); - CoinControlDialog::fSubtractFeeFromAmount = false; + AssetControlDialog::payAmounts.clear(); + AssetControlDialog::fSubtractFeeFromAmount = false; for(int i = 0; i < ui->entries->count(); ++i) { @@ -851,27 +878,27 @@ void AssetsDialog::coinControlUpdateLabels() if(entry && !entry->isHidden()) { SendAssetsRecipient rcp = entry->getValue(); - CoinControlDialog::payAmounts.append(rcp.amount); + AssetControlDialog::payAmounts.append(rcp.amount); // if (rcp.fSubtractFeeFromAmount) -// CoinControlDialog::fSubtractFeeFromAmount = true; +// AssetControlDialog::fSubtractFeeFromAmount = true; } } - if (CoinControlDialog::coinControl->HasSelected()) + if (AssetControlDialog::assetControl->HasSelected()) { // actual coin control calculation - CoinControlDialog::updateLabels(model, this); + AssetControlDialog::updateLabels(model, this); // show coin control stats - ui->labelCoinControlAutomaticallySelected->hide(); - ui->widgetCoinControl->show(); + ui->labelAssetControlAutomaticallySelected->hide(); + ui->widgetAssetControl->show(); } else { // hide coin control stats - ui->labelCoinControlAutomaticallySelected->show(); - ui->widgetCoinControl->hide(); - ui->labelCoinControlInsuffFunds->hide(); + ui->labelAssetControlAutomaticallySelected->show(); + ui->widgetAssetControl->hide(); + ui->labelAssetControlInsuffFunds->hide(); } } @@ -937,4 +964,20 @@ void AssetsDialog::mineButtonClicked() generateBlocks(coinbase_script, num_generate, max_tries, true); } + +void AssetsDialog::assetControlUpdateSendCoinsDialog() +{ + AssetControlDialog::assetControl->HasSelected(); + for(int i = 0; i < ui->entries->count(); ++i) + { + SendAssetsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); + if(entry) + { + removeEntry(entry); + } + } + + addEntry(); + +} /** RVN END */ diff --git a/src/qt/assetsdialog.h b/src/qt/assetsdialog.h index 532f74d831..b6d34f6412 100644 --- a/src/qt/assetsdialog.h +++ b/src/qt/assetsdialog.h @@ -70,7 +70,7 @@ public Q_SLOTS: void minimizeFeeSection(bool fMinimize); void updateFeeMinimizedLabel(); // Update the passed in CCoinControl with state from the GUI - void updateCoinControlState(CCoinControl& ctrl); + void updateAssetControlState(CCoinControl& ctrl); private Q_SLOTS: void on_sendButton_clicked(); @@ -78,18 +78,18 @@ private Q_SLOTS: void on_buttonMinimizeFee_clicked(); void removeEntry(SendAssetsEntry* entry); void updateDisplayUnit(); - void coinControlFeatureChanged(bool); - void coinControlButtonClicked(); - void coinControlChangeChecked(int); - void coinControlChangeEdited(const QString &); - void coinControlUpdateLabels(); - void coinControlClipboardQuantity(); - void coinControlClipboardAmount(); - void coinControlClipboardFee(); - void coinControlClipboardAfterFee(); - void coinControlClipboardBytes(); - void coinControlClipboardLowOutput(); - void coinControlClipboardChange(); + void assetControlFeatureChanged(bool); + void assetControlButtonClicked(); + void assetControlChangeChecked(int); + void assetControlChangeEdited(const QString &); + void assetControlUpdateLabels(); + void assetControlClipboardQuantity(); + void assetControlClipboardAmount(); + void assetControlClipboardFee(); + void assetControlClipboardAfterFee(); + void assetControlClipboardBytes(); + void assetControlClipboardLowOutput(); + void assetControlClipboardChange(); void setMinimumFee(); void updateFeeSectionControls(); void updateMinFeeLabel(); @@ -100,6 +100,7 @@ private Q_SLOTS: void ressieAssetButtonClicked(); void refreshButtonClicked(); void mineButtonClicked(); + void assetControlUpdateSendCoinsDialog(); /** RVN END */ Q_SIGNALS: diff --git a/src/qt/createassetdialog.cpp b/src/qt/createassetdialog.cpp index 572b8717cc..c2b1704142 100644 --- a/src/qt/createassetdialog.cpp +++ b/src/qt/createassetdialog.cpp @@ -84,7 +84,7 @@ void CreateAssetDialog::setUpValues() // Setup the asset list ui->assetList->hide(); std::vector names; - GetAllOwnedAssets(model->getWallet(), names); + GetAllAdministrativeAssets(model->getWallet(), names); for (auto item : names) { std::string name = QString::fromStdString(item).split("!").first().toStdString(); if (name.size() != 30) @@ -539,6 +539,7 @@ QString CreateAssetDialog::GetAssetName() return ui->assetList->currentText() + "/" + ui->nameText->text(); else if (type == ISSUE_UNIQUE) return ui->assetList->currentText() + "#" + ui->nameText->text(); + return ""; } void CreateAssetDialog::UpdateAssetNameMaxSize() diff --git a/src/qt/forms/assetcontroldialog.ui b/src/qt/forms/assetcontroldialog.ui new file mode 100644 index 0000000000..e8df076bc4 --- /dev/null +++ b/src/qt/forms/assetcontroldialog.ui @@ -0,0 +1,517 @@ + + + AssetControlDialog + + + + 0 + 0 + 1000 + 500 + + + + Asset Selection + + + + + + 0 + + + 10 + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + + 75 + true + + + + Quantity: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0 + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 75 + true + + + + Bytes: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0 + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + + 75 + true + + + + Amount: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 RVN + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + false + + + + 75 + true + + + + Dust: + + + + + + + false + + + IBeamCursor + + + Qt::ActionsContextMenu + + + no + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + + 75 + true + + + + Fee: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 RVN + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + + 75 + true + + + + After Fee: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 RVN + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + false + + + + 75 + true + + + + Change: + + + + + + + false + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 RVN + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + 0 + 40 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + 14 + + + + + + 0 + 0 + + + + (un)select all + + + false + + + + + + + + 0 + 0 + + + + Tree mode + + + + + + + + 0 + 0 + + + + List mode + + + true + + + + + + + (1 locked) + + + + + + + 30 + + + + + + + View assets that you have the ownership asset for + + + View Administrator Assets + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::CustomContextMenu + + + false + + + 11 + + + true + + + false + + + + + + + + + Asset + + + + + Amount + + + + + Received with label + + + + + Received with address + + + + + Date + + + + + Confirmations + + + Confirmed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + AssetControlTreeWidget + QTreeWidget +
assetcontroltreewidget.h
+
+
+ + +
diff --git a/src/qt/forms/assetsdialog.ui b/src/qt/forms/assetsdialog.ui index 4f72ab1269..1639d43cc7 100644 --- a/src/qt/forms/assetsdialog.ui +++ b/src/qt/forms/assetsdialog.ui @@ -91,7 +91,7 @@ - + 0 @@ -110,7 +110,7 @@ QFrame::Sunken - + 0 @@ -127,7 +127,7 @@ 6 - + 0 @@ -138,12 +138,12 @@ 10 - + 15 - + 0 @@ -160,14 +160,14 @@ font-weight:bold; - Coin Control Features + Asset Control Features - + 8 @@ -175,7 +175,7 @@ 10 - + @@ -188,7 +188,7 @@ - + automatically selected @@ -198,7 +198,7 @@ - + 75 @@ -217,7 +217,7 @@ - + Qt::Horizontal @@ -232,7 +232,7 @@ - + 0 @@ -248,7 +248,7 @@ - + 0 @@ -262,7 +262,7 @@ 0 - + 20 @@ -273,7 +273,7 @@ 10 - + 10 @@ -290,7 +290,7 @@ 6 - + 75 @@ -306,7 +306,7 @@ - + IBeamCursor @@ -325,7 +325,7 @@ - + 75 @@ -338,7 +338,7 @@ - + IBeamCursor @@ -356,7 +356,7 @@ - + 10 @@ -373,7 +373,7 @@ 6 - + 75 @@ -389,7 +389,7 @@ - + IBeamCursor @@ -405,7 +405,7 @@ - + 75 @@ -418,7 +418,7 @@ - + IBeamCursor @@ -436,7 +436,7 @@ - + 10 @@ -453,7 +453,7 @@ 6 - + 75 @@ -469,7 +469,7 @@ - + IBeamCursor @@ -487,7 +487,7 @@ - + 10 @@ -504,7 +504,7 @@ 6 - + 75 @@ -520,7 +520,7 @@ - + IBeamCursor @@ -536,7 +536,7 @@ - + 75 @@ -549,7 +549,7 @@ - + IBeamCursor @@ -572,7 +572,7 @@ - + 12 @@ -586,7 +586,7 @@ 5 - + If this is activated, but the change address is empty or invalid, change will be sent to a newly generated address. @@ -596,7 +596,7 @@ - + false @@ -609,7 +609,7 @@ - + 0 @@ -633,7 +633,7 @@ - + Qt::Vertical diff --git a/src/qt/forms/sendassetsentry.ui b/src/qt/forms/sendassetsentry.ui index dafcb111f8..3bf346a3f1 100644 --- a/src/qt/forms/sendassetsentry.ui +++ b/src/qt/forms/sendassetsentry.ui @@ -7,7 +7,7 @@ 0 0 729 - 150 + 202 @@ -59,12 +59,12 @@ - + This transfer will send the ownership asset to the address specified - Send Ownership Asset + Send Administrator Asset diff --git a/src/qt/ravenunits.cpp b/src/qt/ravenunits.cpp index 7051bcbd3a..8cea621299 100644 --- a/src/qt/ravenunits.cpp +++ b/src/qt/ravenunits.cpp @@ -125,6 +125,11 @@ QString RavenUnits::formatWithUnit(int unit, const CAmount& amount, bool plussig return format(unit, amount, plussign, separators) + QString(" ") + name(unit); } +QString RavenUnits::formatWithCustomName(QString customName, const CAmount& amount, bool plussign, SeparatorStyle separators) +{ + return format(RVN, amount, plussign, separators) + QString(" ") + customName; +} + QString RavenUnits::formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators) { QString str(formatWithUnit(unit, amount, plussign, separators)); diff --git a/src/qt/ravenunits.h b/src/qt/ravenunits.h index 3caa34b233..230d5d0bee 100644 --- a/src/qt/ravenunits.h +++ b/src/qt/ravenunits.h @@ -89,6 +89,8 @@ class RavenUnits: public QAbstractListModel static QString format(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard); //! Format as string (with unit) static QString formatWithUnit(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard); + //! Format as string (with custom name) + static QString formatWithCustomName(QString customName, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard); //! Format as HTML string (with unit) static QString formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard); //! Parse string to coin amount diff --git a/src/qt/reissueassetdialog.cpp b/src/qt/reissueassetdialog.cpp index 5eab53e04a..22dbbb99a9 100644 --- a/src/qt/reissueassetdialog.cpp +++ b/src/qt/reissueassetdialog.cpp @@ -59,6 +59,9 @@ ReissueAssetDialog::~ReissueAssetDialog() /** Helper Methods */ void ReissueAssetDialog::setUpValues() { + if (!model) + return; + ui->reissuableBox->setCheckState(Qt::CheckState::Checked); ui->ipfsText->hide(); ui->ipfsHashLabel->hide(); @@ -71,7 +74,7 @@ void ReissueAssetDialog::setUpValues() LOCK(cs_main); std::vector assets; - GetAllOwnedAssets(model->getWallet(), assets); + GetAllAdministrativeAssets(model->getWallet(), assets); ui->comboBox->addItem("Select an asset"); @@ -345,6 +348,9 @@ void ReissueAssetDialog::onAddressNameChanged(QString address) void ReissueAssetDialog::onReissueAssetClicked() { + if (!model) + return; + QString address; if (ui->addressText->text().isEmpty()) { address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, "", ""); @@ -367,7 +373,6 @@ void ReissueAssetDialog::onReissueAssetClicked() CReissueAsset reissueAsset(name.toStdString(), quantity, reissuable ? 1 : 0, ipfsDecoded); - CWalletTx tx; CReserveKey reservekey(model->getWallet()); std::pair error; diff --git a/src/qt/sendassetsentry.cpp b/src/qt/sendassetsentry.cpp index ce7c5beb63..c1b548e5e3 100644 --- a/src/qt/sendassetsentry.cpp +++ b/src/qt/sendassetsentry.cpp @@ -13,6 +13,7 @@ #include "optionsmodel.h" #include "platformstyle.h" #include "walletmodel.h" +#include "assetcontroldialog.h" #include #include @@ -47,13 +48,14 @@ SendAssetsEntry::SendAssetsEntry(const PlatformStyle *_platformStyle, const QStr ui->payTo_is->setFont(GUIUtil::fixedPitchFont()); // Connect signals + connect(ui->payAmount, SIGNAL(valueChanged(double)), this, SIGNAL(payAmountChanged())); connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(ui->deleteButton_is, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(ui->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(ui->assetSelectionBox, SIGNAL(activated(int)), this, SLOT(onAssetSelected(int))); - connect(ui->ownerCheckBox, SIGNAL(clicked()), this, SLOT(onSendOwnershipChanged())); + connect(ui->administratorCheckBox, SIGNAL(clicked()), this, SLOT(onSendOwnershipChanged())); - ui->assetSelectionBox->addItem("Select an asset to transfer"); + ui->assetSelectionBox->addItem(tr("Select an asset to transfer")); ui->assetSelectionBox->addItems(myAssetsNames); ui->payAmount->setValue(0.00000000); @@ -178,8 +180,8 @@ QWidget *SendAssetsEntry::setupTabChain(QWidget *prev) QWidget::setTabOrder(ui->payTo, ui->addAsLabel); QWidget::setTabOrder(ui->addressBookButton, ui->pasteButton); QWidget::setTabOrder(ui->pasteButton, ui->deleteButton); - QWidget::setTabOrder(ui->payAmount, ui->ownerCheckBox); - QWidget::setTabOrder(ui->ownerCheckBox, ui->assetAmountLabel); + QWidget::setTabOrder(ui->payAmount, ui->administratorCheckBox); + QWidget::setTabOrder(ui->administratorCheckBox, ui->assetAmountLabel); return ui->deleteButton; } @@ -215,6 +217,8 @@ void SendAssetsEntry::setValue(const SendAssetsRecipient &value) ui->payTo->setText(recipient.address); // this may set a label from addressbook if (!recipient.label.isEmpty()) // if a label had been set from the addressbook, don't overwrite with an empty label ui->addAsLabel->setText(recipient.label); + + ui->payAmount->setValue(recipient.amount / COIN); } } @@ -284,14 +288,13 @@ void SendAssetsEntry::onAssetSelected(int index) } ui->assetAmountLabel->setText( - "Balance: " + QString::fromStdString(ValueFromAmountString(amount, units)) + " " + name + bang); + "Wallet Balance: " + QString::fromStdString(ValueFromAmountString(amount, units)) + " " + name + bang); ui->messageLabel->hide(); ui->messageTextLabel->hide(); // If it is an ownership asset lock the amount if (!isOwnerAsset) { - ui->payAmount->setMaximum(amount / COIN); ui->payAmount->setDecimals(asset.units); ui->payAmount->setDisabled(false); } @@ -306,42 +309,74 @@ void SendAssetsEntry::onAssetSelected(int index) void SendAssetsEntry::onSendOwnershipChanged() { - if (ui->ownerCheckBox->isChecked()) { + if(!model) + return; + + if (ui->administratorCheckBox->isChecked()) { LOCK(cs_main); - std::vector names; - GetAllOwnedAssets(model->getWallet(), names); + if (!fUsingAssetControl) { + std::vector names; + GetAllAdministrativeAssets(model->getWallet(), names); - QStringList list; - for (auto name: names) - list << QString::fromStdString(name); + QStringList list; + for (auto name: names) + list << QString::fromStdString(name); - ui->assetSelectionBox->clear(); - ui->assetSelectionBox->addItem("Select an asset to transfer"); - ui->assetSelectionBox->addItems(list); - ui->assetSelectionBox->setCurrentIndex(0); + ui->assetSelectionBox->clear(); + ui->assetSelectionBox->addItem(tr("Select an asset to transfer")); + ui->assetSelectionBox->addItems(list); + ui->assetSelectionBox->setCurrentIndex(0); + } ui->payAmount->setValue(OWNER_ASSET_AMOUNT / COIN); ui->payAmount->setDecimals(MAX_UNIT); ui->payAmount->setDisabled(true); ui->assetAmountLabel->clear(); - ui->ownershipWarningMessage->setText("Warning: This asset transfer contains an ownership asset. You will no longer have ownership of this asset. Make sure this is what you want to do"); + ui->ownershipWarningMessage->setText(tr("Warning: This asset transfer contains an administrator asset. You will no longer be the administrator of this asset. Make sure this is what you want to do")); ui->ownershipWarningMessage->setStyleSheet("color: red"); ui->ownershipWarningMessage->show(); } else { LOCK(cs_main); - std::vector names; - GetAllMyAssets(names); - QStringList list; - for (auto name : names) { - if (!IsAssetNameAnOwner(name)) - list << QString::fromStdString(name); - } - ui->assetSelectionBox->clear(); + if (!fUsingAssetControl) { + std::vector names; + GetAllMyAssets(model->getWallet(), names); + QStringList list; + for (auto name : names) { + if (!IsAssetNameAnOwner(name)) + list << QString::fromStdString(name); + } + ui->assetSelectionBox->clear(); - ui->assetSelectionBox->addItem("Select an asset to transfer"); - ui->assetSelectionBox->addItems(list); - ui->assetSelectionBox->setCurrentIndex(0); + ui->assetSelectionBox->addItem(tr("Select an asset to transfer")); + ui->assetSelectionBox->addItems(list); + ui->assetSelectionBox->setCurrentIndex(0); + } ui->ownershipWarningMessage->hide(); } } + +void SendAssetsEntry::CheckOwnerBox() { + ui->administratorCheckBox->setChecked(true); + fUsingAssetControl = true; + onSendOwnershipChanged(); +} + +void SendAssetsEntry::IsAssetControl(bool fIsAssetControl, bool fIsOwner) +{ + if (fIsOwner) { + CheckOwnerBox(); + } + if (fIsAssetControl) { + ui->administratorCheckBox->setDisabled(true); + fUsingAssetControl = true; + } +} + +void SendAssetsEntry::setCurrentIndex(int index) +{ + if (index < ui->assetSelectionBox->count()) { + ui->assetSelectionBox->setCurrentIndex(index); + ui->assetSelectionBox->activated(index); + } +} diff --git a/src/qt/sendassetsentry.h b/src/qt/sendassetsentry.h index 548a899ba3..dac5dcd101 100644 --- a/src/qt/sendassetsentry.h +++ b/src/qt/sendassetsentry.h @@ -39,6 +39,9 @@ class SendAssetsEntry : public QStackedWidget void setValue(const SendAssetsRecipient &value); void setAddress(const QString &address); + void CheckOwnerBox(); + void IsAssetControl(bool fIsAssetControl, bool fIsOwner); + void setCurrentIndex(int index); /** Set up the tab chain manually, as Qt messes up the tab chain by default in some cases * (issue https://bugreports.qt-project.org/browse/QTBUG-10907). @@ -47,6 +50,9 @@ class SendAssetsEntry : public QStackedWidget void setFocus(); + bool fUsingAssetControl; + + public Q_SLOTS: void clear(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 335fc0bb4f..3a03eee3ee 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -663,6 +663,31 @@ void WalletModel::listCoins(std::map >& mapCoins) } } +/** RVN START */ +// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) +void WalletModel::listAssets(std::map > >& mapCoins) const +{ + std::map > > mapSortedByAssetName; + auto list = wallet->ListAssets(); + + for (auto& group : list) { + auto address = QString::fromStdString(EncodeDestination(group.first)); + + for (auto& coin : group.second) { + auto out = coin.tx->tx->vout[coin.i]; + std::string strAssetName; + CAmount nAmount; + if (!GetAssetInfoFromScript(out.scriptPubKey, strAssetName, nAmount)) + continue; + + QString assetName = QString::fromStdString(strAssetName); + auto& assetMap = mapCoins[assetName]; + assetMap[address].emplace_back(coin); + } + } +} +/** RVN END */ + bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const { LOCK2(cs_main, wallet->cs_wallet); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 66fb9a0e98..457b9b5520 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -265,7 +265,10 @@ class WalletModel : public QObject void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); bool isSpent(const COutPoint& outpoint) const; void listCoins(std::map >& mapCoins) const; - + /** RVN START */ + // Map of asset name to map of address to CTxOut + void listAssets(std::map > >& mapCoins) const; + /** RVN END */ bool isLockedCoin(uint256 hash, unsigned int n) const; void lockCoin(COutPoint& output); void unlockCoin(COutPoint& output); diff --git a/src/rpc/assets.cpp b/src/rpc/assets.cpp index ad06429f3c..b38fffee82 100644 --- a/src/rpc/assets.cpp +++ b/src/rpc/assets.cpp @@ -552,8 +552,10 @@ UniValue transfer(const JSONRPCRequest& request) CWalletTx transaction; CAmount nRequiredFee; + CCoinControl ctrl; + // Create the Transaction - if (!CreateTransferAssetTransaction(pwallet, vTransfers, "", error, transaction, reservekey, nRequiredFee)) + if (!CreateTransferAssetTransaction(pwallet, ctrl, vTransfers, "", error, transaction, reservekey, nRequiredFee)) throw JSONRPCError(error.first, error.second); // Send the Transaction to the network diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index b1a55749d6..d147bead47 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -33,6 +33,14 @@ class CCoinControl //! Fee estimation mode to control arguments to estimateSmartFee FeeEstimateMode m_fee_mode; + /** RVN START */ + //! Name of the asset that is selected, used when sending assets with coincontrol + std::string strAssetSelected; + //! If this is being used for assets + bool fForAssets; + /** RVN END */ + + CCoinControl() { SetNull(); @@ -49,6 +57,8 @@ class CCoinControl m_confirm_target.reset(); signalRbf = fWalletRbf; m_fee_mode = FeeEstimateMode::UNSET; + strAssetSelected = ""; + fForAssets = false; } bool HasSelected() const @@ -69,11 +79,14 @@ class CCoinControl void UnSelect(const COutPoint& output) { setSelected.erase(output); + if (!setSelected.size()) + strAssetSelected = ""; } void UnSelectAll() { setSelected.clear(); + strAssetSelected = ""; } void ListSelected(std::vector& vOutpoints) const diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7a1d431de5..92a2ee3c6f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2248,8 +2248,14 @@ void CWallet::AvailableCoinsAll(std::vector& vCoins, std::maptx->vout.size(); i++) { - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint((*it).first, i))) + if (coinControl && coinControl->fForAssets && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint((*it).first, i)) && pcoin->tx->vout[i].scriptPubKey.IsAssetScript()) { continue; + } + else { + if (coinControl && !coinControl->fForAssets && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && + !coinControl->IsSelected(COutPoint((*it).first, i))) + continue; + } if (IsLockedCoin((*it).first, i)) continue; @@ -2383,6 +2389,60 @@ void CWallet::AvailableCoinsAll(std::vector& vCoins, std::map> CWallet::ListAssets() const +{ + // TODO: Add AssertLockHeld(cs_wallet) here. + // + // Because the return value from this function contains pointers to + // CWalletTx objects, callers to this function really should acquire the + // cs_wallet lock before calling it. However, the current caller doesn't + // acquire this lock yet. There was an attempt to add the missing lock in + // https://github.com/RavenProject/Ravencoin/pull/10340, but that change has been + // postponed until after https://github.com/RavenProject/Ravencoin/pull/10244 to + // avoid adding some extra complexity to the Qt code. + + std::map> result; + + std::map > mapAssets; + AvailableAssets(mapAssets); + + LOCK2(cs_main, cs_wallet); + for (auto asset : mapAssets) { + for (auto &coin : asset.second) { + CTxDestination address; + if (coin.fSpendable && + ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { + result[address].emplace_back(std::move(coin)); + } + } + } + + std::vector lockedCoins; + ListLockedCoins(lockedCoins); + for (const auto& output : lockedCoins) { + auto it = mapWallet.find(output.hash); + if (it != mapWallet.end()) { + if (!it->second.tx->vout[output.n].scriptPubKey.IsAssetScript()) // If not an asset script skip it + continue; + int depth = it->second.GetDepthInMainChain(); + if (depth >= 0 && output.n < it->second.tx->vout.size() && + IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { + CTxDestination address; + if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { + result[address].emplace_back( + &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + } + } + } + } + + return result; +} + +/** RVN END */ + std::map> CWallet::ListCoins() const { // TODO: Add AssertLockHeld(cs_wallet) here. @@ -2420,7 +2480,7 @@ std::map> CWallet::ListCoins() const CTxDestination address; if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { result[address].emplace_back( - &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); } } } @@ -2649,7 +2709,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm std::vector vCoins(vAvailableCoins); // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) + if (coinControl && !coinControl->fForAssets && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) { for (const COutput& out : vCoins) { @@ -2666,7 +2726,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm CAmount nValueFromPresetInputs = 0; std::vector vPresetInputs; - if (coinControl) + if (coinControl && !coinControl->fForAssets) coinControl->ListSelected(vPresetInputs); for (const COutPoint& outpoint : vPresetInputs) { @@ -2684,7 +2744,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm } // remove preset inputs from vCoins - for (std::vector::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();) + for (std::vector::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && !coinControl->fForAssets && coinControl->HasSelected();) { if (setPresetCoins.count(CInputCoin(it->tx, it->i))) it = vCoins.erase(it); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 195c3f5d63..ac6d2a32b7 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -871,6 +871,12 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface */ std::map> ListCoins() const; + /** + * Return list of available assets and locked assets grouped by non-change output address. + */ + std::map> ListAssets() const; + + /** * Find non-change parent output. */