Subtract fee from amount
Fixes #2724 and #1570. Adds the automatically-subtract-the-fee-from-the-amount-and-send-whats-left feature to the GUI and RPC (sendtoaddress,sendmany).
This commit is contained in:
committed by
Wladimir J. van der Laan
parent
90a43c1e93
commit
292623adf5
@@ -33,6 +33,7 @@
|
||||
using namespace std;
|
||||
QList<CAmount> CoinControlDialog::payAmounts;
|
||||
CCoinControl* CoinControlDialog::coinControl = new CCoinControl();
|
||||
bool CoinControlDialog::fSubtractFeeFromAmount = false;
|
||||
|
||||
CoinControlDialog::CoinControlDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
@@ -541,6 +542,11 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
dPriority = dPriorityInputs / (nBytes - nBytesInputs + (nQuantityUncompressed * 29)); // 29 = 180 - 151 (uncompressed public keys are over the limit. max 151 bytes of the input are ignored for priority)
|
||||
sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority, mempoolEstimatePriority);
|
||||
|
||||
// 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 (CoinControlDialog::fSubtractFeeFromAmount)
|
||||
if (nAmount - nPayAmount == 0)
|
||||
nBytes -= 34;
|
||||
|
||||
// Fee
|
||||
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
|
||||
|
||||
@@ -556,7 +562,9 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
|
||||
if (nPayAmount > 0)
|
||||
{
|
||||
nChange = nAmount - nPayFee - nPayAmount;
|
||||
nChange = nAmount - nPayAmount;
|
||||
if (!CoinControlDialog::fSubtractFeeFromAmount)
|
||||
nChange -= nPayFee;
|
||||
|
||||
// Never create dust outputs; if we would, just add the dust to the fee.
|
||||
if (nChange > 0 && nChange < CENT)
|
||||
@@ -564,12 +572,17 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0));
|
||||
if (txout.IsDust(::minRelayTxFee))
|
||||
{
|
||||
nPayFee += nChange;
|
||||
nChange = 0;
|
||||
if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust
|
||||
nChange = txout.GetDustThreshold(::minRelayTxFee);
|
||||
else
|
||||
{
|
||||
nPayFee += nChange;
|
||||
nChange = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nChange == 0)
|
||||
if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount)
|
||||
nBytes -= 34;
|
||||
}
|
||||
|
||||
@@ -612,7 +625,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
{
|
||||
l3->setText(ASYMP_UTF8 + l3->text());
|
||||
l4->setText(ASYMP_UTF8 + l4->text());
|
||||
if (nChange > 0)
|
||||
if (nChange > 0 && !CoinControlDialog::fSubtractFeeFromAmount)
|
||||
l8->setText(ASYMP_UTF8 + l8->text());
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
|
||||
static QList<CAmount> payAmounts;
|
||||
static CCoinControl *coinControl;
|
||||
static bool fSubtractFeeFromAmount;
|
||||
|
||||
private:
|
||||
Ui::CoinControlDialog *ui;
|
||||
|
||||
@@ -157,7 +157,21 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="BitcoinAmountField" name="payAmount"/>
|
||||
<layout class="QHBoxLayout" name="horizontalLayoutAmount" stretch="0,1">
|
||||
<item>
|
||||
<widget class="BitcoinAmountField" name="payAmount"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkboxSubtractFeeFromAmount">
|
||||
<property name="toolTip">
|
||||
<string>The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>S&ubtract fee from amount</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="messageLabel">
|
||||
|
||||
@@ -221,8 +221,6 @@ void SendCoinsDialog::on_sendButton_clicked()
|
||||
}
|
||||
|
||||
fNewRecipientAllowed = false;
|
||||
|
||||
|
||||
WalletModel::UnlockContext ctx(model->requestUnlock());
|
||||
if(!ctx.isValid())
|
||||
{
|
||||
@@ -252,7 +250,7 @@ void SendCoinsDialog::on_sendButton_clicked()
|
||||
|
||||
// Format confirmation message
|
||||
QStringList formatted;
|
||||
foreach(const SendCoinsRecipient &rcp, recipients)
|
||||
foreach(const SendCoinsRecipient &rcp, currentTransaction.getRecipients())
|
||||
{
|
||||
// generate bold amount string
|
||||
QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
|
||||
@@ -369,6 +367,7 @@ SendCoinsEntry *SendCoinsDialog::addEntry()
|
||||
ui->entries->addWidget(entry);
|
||||
connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
|
||||
connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
|
||||
connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
|
||||
|
||||
updateTabsAndLabels();
|
||||
|
||||
@@ -784,11 +783,17 @@ void SendCoinsDialog::coinControlUpdateLabels()
|
||||
|
||||
// set pay amounts
|
||||
CoinControlDialog::payAmounts.clear();
|
||||
CoinControlDialog::fSubtractFeeFromAmount = false;
|
||||
for(int i = 0; i < ui->entries->count(); ++i)
|
||||
{
|
||||
SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
|
||||
if(entry)
|
||||
CoinControlDialog::payAmounts.append(entry->getValue().amount);
|
||||
{
|
||||
SendCoinsRecipient rcp = entry->getValue();
|
||||
CoinControlDialog::payAmounts.append(rcp.amount);
|
||||
if (rcp.fSubtractFeeFromAmount)
|
||||
CoinControlDialog::fSubtractFeeFromAmount = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CoinControlDialog::coinControl->HasSelected())
|
||||
|
||||
@@ -44,6 +44,7 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) :
|
||||
|
||||
// Connect signals
|
||||
connect(ui->payAmount, SIGNAL(valueChanged()), this, SIGNAL(payAmountChanged()));
|
||||
connect(ui->checkboxSubtractFeeFromAmount, SIGNAL(toggled(bool)), this, SIGNAL(subtractFeeFromAmountChanged()));
|
||||
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()));
|
||||
@@ -94,6 +95,7 @@ void SendCoinsEntry::clear()
|
||||
ui->payTo->clear();
|
||||
ui->addAsLabel->clear();
|
||||
ui->payAmount->clear();
|
||||
ui->checkboxSubtractFeeFromAmount->setCheckState(Qt::Unchecked);
|
||||
ui->messageTextLabel->clear();
|
||||
ui->messageTextLabel->hide();
|
||||
ui->messageLabel->hide();
|
||||
@@ -165,6 +167,7 @@ SendCoinsRecipient SendCoinsEntry::getValue()
|
||||
recipient.label = ui->addAsLabel->text();
|
||||
recipient.amount = ui->payAmount->value();
|
||||
recipient.message = ui->messageTextLabel->text();
|
||||
recipient.fSubtractFeeFromAmount = (ui->checkboxSubtractFeeFromAmount->checkState() == Qt::Checked);
|
||||
|
||||
return recipient;
|
||||
}
|
||||
@@ -174,7 +177,8 @@ QWidget *SendCoinsEntry::setupTabChain(QWidget *prev)
|
||||
QWidget::setTabOrder(prev, ui->payTo);
|
||||
QWidget::setTabOrder(ui->payTo, ui->addAsLabel);
|
||||
QWidget *w = ui->payAmount->setupTabChain(ui->addAsLabel);
|
||||
QWidget::setTabOrder(w, ui->addressBookButton);
|
||||
QWidget::setTabOrder(w, ui->checkboxSubtractFeeFromAmount);
|
||||
QWidget::setTabOrder(ui->checkboxSubtractFeeFromAmount, ui->addressBookButton);
|
||||
QWidget::setTabOrder(ui->addressBookButton, ui->pasteButton);
|
||||
QWidget::setTabOrder(ui->pasteButton, ui->deleteButton);
|
||||
return ui->deleteButton;
|
||||
|
||||
@@ -51,6 +51,7 @@ public slots:
|
||||
signals:
|
||||
void removeEntry(SendCoinsEntry *entry);
|
||||
void payAmountChanged();
|
||||
void subtractFeeFromAmountChanged();
|
||||
|
||||
private slots:
|
||||
void deleteClicked();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "addresstablemodel.h"
|
||||
#include "guiconstants.h"
|
||||
#include "guiutil.h"
|
||||
#include "paymentserver.h"
|
||||
#include "recentrequeststablemodel.h"
|
||||
#include "transactiontablemodel.h"
|
||||
@@ -192,8 +193,9 @@ bool WalletModel::validateAddress(const QString &address)
|
||||
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
|
||||
{
|
||||
CAmount total = 0;
|
||||
bool fSubtractFeeFromAmount = false;
|
||||
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
|
||||
std::vector<std::pair<CScript, CAmount> > vecSend;
|
||||
std::vector<CRecipient> vecSend;
|
||||
|
||||
if(recipients.empty())
|
||||
{
|
||||
@@ -206,6 +208,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
// Pre-check input data for validity
|
||||
foreach(const SendCoinsRecipient &rcp, recipients)
|
||||
{
|
||||
if (rcp.fSubtractFeeFromAmount)
|
||||
fSubtractFeeFromAmount = true;
|
||||
|
||||
if (rcp.paymentRequest.IsInitialized())
|
||||
{ // PaymentRequest...
|
||||
CAmount subtotal = 0;
|
||||
@@ -217,7 +222,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
subtotal += out.amount();
|
||||
const unsigned char* scriptStr = (const unsigned char*)out.script().data();
|
||||
CScript scriptPubKey(scriptStr, scriptStr+out.script().size());
|
||||
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, out.amount()));
|
||||
CAmount nAmount = out.amount();
|
||||
CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount};
|
||||
vecSend.push_back(recipient);
|
||||
}
|
||||
if (subtotal <= 0)
|
||||
{
|
||||
@@ -239,7 +246,8 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
++nAddresses;
|
||||
|
||||
CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
|
||||
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, rcp.amount));
|
||||
CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount};
|
||||
vecSend.push_back(recipient);
|
||||
|
||||
total += rcp.amount;
|
||||
}
|
||||
@@ -260,17 +268,21 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
|
||||
transaction.newPossibleKeyChange(wallet);
|
||||
|
||||
CAmount nFeeRequired = 0;
|
||||
int nChangePosRet = -1;
|
||||
std::string strFailReason;
|
||||
|
||||
CWalletTx *newTx = transaction.getTransaction();
|
||||
CReserveKey *keyChange = transaction.getPossibleKeyChange();
|
||||
bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason, coinControl);
|
||||
bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl);
|
||||
transaction.setTransactionFee(nFeeRequired);
|
||||
if (fSubtractFeeFromAmount && fCreated)
|
||||
transaction.reassignAmounts(nChangePosRet);
|
||||
|
||||
if(!fCreated)
|
||||
{
|
||||
if((total + nFeeRequired) > nBalance)
|
||||
if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance)
|
||||
{
|
||||
return SendCoinsReturn(AmountWithFeeExceedsBalance);
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ QT_END_NAMESPACE
|
||||
class SendCoinsRecipient
|
||||
{
|
||||
public:
|
||||
explicit SendCoinsRecipient() : amount(0), nVersion(SendCoinsRecipient::CURRENT_VERSION) { }
|
||||
explicit SendCoinsRecipient() : amount(0), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) { }
|
||||
explicit SendCoinsRecipient(const QString &addr, const QString &label, const CAmount& amount, const QString &message):
|
||||
address(addr), label(label), amount(amount), message(message), nVersion(SendCoinsRecipient::CURRENT_VERSION) {}
|
||||
address(addr), label(label), amount(amount), message(message), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {}
|
||||
|
||||
// If from an unauthenticated payment request, this is used for storing
|
||||
// the addresses, e.g. address-A<br />address-B<br />address-C.
|
||||
@@ -56,6 +56,8 @@ public:
|
||||
// Empty if no authentication or invalid signature/cert/etc.
|
||||
QString authenticatedMerchant;
|
||||
|
||||
bool fSubtractFeeFromAmount; // memory only
|
||||
|
||||
static const int CURRENT_VERSION = 1;
|
||||
int nVersion;
|
||||
|
||||
|
||||
@@ -46,6 +46,38 @@ void WalletModelTransaction::setTransactionFee(const CAmount& newFee)
|
||||
fee = newFee;
|
||||
}
|
||||
|
||||
void WalletModelTransaction::reassignAmounts(int nChangePosRet)
|
||||
{
|
||||
int i = 0;
|
||||
for (QList<SendCoinsRecipient>::iterator it = recipients.begin(); it != recipients.end(); ++it)
|
||||
{
|
||||
SendCoinsRecipient& rcp = (*it);
|
||||
|
||||
if (rcp.paymentRequest.IsInitialized())
|
||||
{
|
||||
CAmount subtotal = 0;
|
||||
const payments::PaymentDetails& details = rcp.paymentRequest.getDetails();
|
||||
for (int j = 0; j < details.outputs_size(); j++)
|
||||
{
|
||||
const payments::Output& out = details.outputs(j);
|
||||
if (out.amount() <= 0) continue;
|
||||
if (i == nChangePosRet)
|
||||
i++;
|
||||
subtotal += walletTransaction->vout[i].nValue;
|
||||
i++;
|
||||
}
|
||||
rcp.amount = subtotal;
|
||||
}
|
||||
else // normal recipient (no payment request)
|
||||
{
|
||||
if (i == nChangePosRet)
|
||||
i++;
|
||||
rcp.amount = walletTransaction->vout[i].nValue;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CAmount WalletModelTransaction::getTotalTransactionAmount()
|
||||
{
|
||||
CAmount totalTransactionAmount = 0;
|
||||
|
||||
@@ -35,8 +35,10 @@ public:
|
||||
void newPossibleKeyChange(CWallet *wallet);
|
||||
CReserveKey *getPossibleKeyChange();
|
||||
|
||||
void reassignAmounts(int nChangePosRet); // needed for the subtract-fee-from-amount feature
|
||||
|
||||
private:
|
||||
const QList<SendCoinsRecipient> recipients;
|
||||
QList<SendCoinsRecipient> recipients;
|
||||
CWalletTx *walletTransaction;
|
||||
CReserveKey *keyChange;
|
||||
CAmount fee;
|
||||
|
||||
Reference in New Issue
Block a user