Skip to content

Commit 292623a

Browse files
cozzlaanwj
authored andcommitted
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).
1 parent 90a43c1 commit 292623a

16 files changed

+248
-62
lines changed

qa/rpc-tests/wallet.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,21 @@ def run_test (self):
102102
assert_equal(self.nodes[2].getbalance(), 100)
103103
assert_equal(self.nodes[2].getbalance("from1"), 100-21)
104104

105+
# Send 10 XBT normal
106+
address = self.nodes[0].getnewaddress("test")
107+
self.nodes[2].settxfee(Decimal('0.001'))
108+
txid = self.nodes[2].sendtoaddress(address, 10, "", "", False)
109+
self.nodes[2].setgenerate(True, 1)
110+
self.sync_all()
111+
assert_equal(self.nodes[2].getbalance(), Decimal('89.99900000'))
112+
assert_equal(self.nodes[0].getbalance(), Decimal('10.00000000'))
113+
114+
# Send 10 XBT with subtract fee from amount
115+
txid = self.nodes[2].sendtoaddress(address, 10, "", "", True)
116+
self.nodes[2].setgenerate(True, 1)
117+
self.sync_all()
118+
assert_equal(self.nodes[2].getbalance(), Decimal('79.99900000'))
119+
assert_equal(self.nodes[0].getbalance(), Decimal('19.99900000'))
105120

106121
if __name__ == '__main__':
107122
WalletTest ().main ()

src/primitives/transaction.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class CTxOut
134134

135135
uint256 GetHash() const;
136136

137-
bool IsDust(CFeeRate minRelayTxFee) const
137+
CAmount GetDustThreshold(const CFeeRate &minRelayTxFee) const
138138
{
139139
// "Dust" is defined in terms of CTransaction::minRelayTxFee,
140140
// which has units satoshis-per-kilobyte.
@@ -145,7 +145,12 @@ class CTxOut
145145
// so dust is a txout less than 546 satoshis
146146
// with default minRelayTxFee.
147147
size_t nSize = GetSerializeSize(SER_DISK,0)+148u;
148-
return (nValue < 3*minRelayTxFee.GetFee(nSize));
148+
return 3*minRelayTxFee.GetFee(nSize);
149+
}
150+
151+
bool IsDust(const CFeeRate &minRelayTxFee) const
152+
{
153+
return (nValue < GetDustThreshold(minRelayTxFee));
149154
}
150155

151156
friend bool operator==(const CTxOut& a, const CTxOut& b)

src/qt/coincontroldialog.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
using namespace std;
3434
QList<CAmount> CoinControlDialog::payAmounts;
3535
CCoinControl* CoinControlDialog::coinControl = new CCoinControl();
36+
bool CoinControlDialog::fSubtractFeeFromAmount = false;
3637

3738
CoinControlDialog::CoinControlDialog(QWidget *parent) :
3839
QDialog(parent),
@@ -541,6 +542,11 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
541542
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)
542543
sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority, mempoolEstimatePriority);
543544

545+
// 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
546+
if (CoinControlDialog::fSubtractFeeFromAmount)
547+
if (nAmount - nPayAmount == 0)
548+
nBytes -= 34;
549+
544550
// Fee
545551
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
546552

@@ -556,20 +562,27 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
556562

557563
if (nPayAmount > 0)
558564
{
559-
nChange = nAmount - nPayFee - nPayAmount;
565+
nChange = nAmount - nPayAmount;
566+
if (!CoinControlDialog::fSubtractFeeFromAmount)
567+
nChange -= nPayFee;
560568

561569
// Never create dust outputs; if we would, just add the dust to the fee.
562570
if (nChange > 0 && nChange < CENT)
563571
{
564572
CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0));
565573
if (txout.IsDust(::minRelayTxFee))
566574
{
567-
nPayFee += nChange;
568-
nChange = 0;
575+
if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust
576+
nChange = txout.GetDustThreshold(::minRelayTxFee);
577+
else
578+
{
579+
nPayFee += nChange;
580+
nChange = 0;
581+
}
569582
}
570583
}
571584

572-
if (nChange == 0)
585+
if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount)
573586
nBytes -= 34;
574587
}
575588

@@ -612,7 +625,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
612625
{
613626
l3->setText(ASYMP_UTF8 + l3->text());
614627
l4->setText(ASYMP_UTF8 + l4->text());
615-
if (nChange > 0)
628+
if (nChange > 0 && !CoinControlDialog::fSubtractFeeFromAmount)
616629
l8->setText(ASYMP_UTF8 + l8->text());
617630
}
618631

src/qt/coincontroldialog.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class CoinControlDialog : public QDialog
4343

4444
static QList<CAmount> payAmounts;
4545
static CCoinControl *coinControl;
46+
static bool fSubtractFeeFromAmount;
4647

4748
private:
4849
Ui::CoinControlDialog *ui;

src/qt/forms/sendcoinsentry.ui

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,21 @@
157157
</widget>
158158
</item>
159159
<item row="2" column="1">
160-
<widget class="BitcoinAmountField" name="payAmount"/>
160+
<layout class="QHBoxLayout" name="horizontalLayoutAmount" stretch="0,1">
161+
<item>
162+
<widget class="BitcoinAmountField" name="payAmount"/>
163+
</item>
164+
<item>
165+
<widget class="QCheckBox" name="checkboxSubtractFeeFromAmount">
166+
<property name="toolTip">
167+
<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>
168+
</property>
169+
<property name="text">
170+
<string>S&amp;ubtract fee from amount</string>
171+
</property>
172+
</widget>
173+
</item>
174+
</layout>
161175
</item>
162176
<item row="3" column="0">
163177
<widget class="QLabel" name="messageLabel">

src/qt/sendcoinsdialog.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,6 @@ void SendCoinsDialog::on_sendButton_clicked()
221221
}
222222

223223
fNewRecipientAllowed = false;
224-
225-
226224
WalletModel::UnlockContext ctx(model->requestUnlock());
227225
if(!ctx.isValid())
228226
{
@@ -252,7 +250,7 @@ void SendCoinsDialog::on_sendButton_clicked()
252250

253251
// Format confirmation message
254252
QStringList formatted;
255-
foreach(const SendCoinsRecipient &rcp, recipients)
253+
foreach(const SendCoinsRecipient &rcp, currentTransaction.getRecipients())
256254
{
257255
// generate bold amount string
258256
QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
@@ -369,6 +367,7 @@ SendCoinsEntry *SendCoinsDialog::addEntry()
369367
ui->entries->addWidget(entry);
370368
connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
371369
connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
370+
connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
372371

373372
updateTabsAndLabels();
374373

@@ -784,11 +783,17 @@ void SendCoinsDialog::coinControlUpdateLabels()
784783

785784
// set pay amounts
786785
CoinControlDialog::payAmounts.clear();
786+
CoinControlDialog::fSubtractFeeFromAmount = false;
787787
for(int i = 0; i < ui->entries->count(); ++i)
788788
{
789789
SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
790790
if(entry)
791-
CoinControlDialog::payAmounts.append(entry->getValue().amount);
791+
{
792+
SendCoinsRecipient rcp = entry->getValue();
793+
CoinControlDialog::payAmounts.append(rcp.amount);
794+
if (rcp.fSubtractFeeFromAmount)
795+
CoinControlDialog::fSubtractFeeFromAmount = true;
796+
}
792797
}
793798

794799
if (CoinControlDialog::coinControl->HasSelected())

src/qt/sendcoinsentry.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) :
4444

4545
// Connect signals
4646
connect(ui->payAmount, SIGNAL(valueChanged()), this, SIGNAL(payAmountChanged()));
47+
connect(ui->checkboxSubtractFeeFromAmount, SIGNAL(toggled(bool)), this, SIGNAL(subtractFeeFromAmountChanged()));
4748
connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked()));
4849
connect(ui->deleteButton_is, SIGNAL(clicked()), this, SLOT(deleteClicked()));
4950
connect(ui->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked()));
@@ -94,6 +95,7 @@ void SendCoinsEntry::clear()
9495
ui->payTo->clear();
9596
ui->addAsLabel->clear();
9697
ui->payAmount->clear();
98+
ui->checkboxSubtractFeeFromAmount->setCheckState(Qt::Unchecked);
9799
ui->messageTextLabel->clear();
98100
ui->messageTextLabel->hide();
99101
ui->messageLabel->hide();
@@ -165,6 +167,7 @@ SendCoinsRecipient SendCoinsEntry::getValue()
165167
recipient.label = ui->addAsLabel->text();
166168
recipient.amount = ui->payAmount->value();
167169
recipient.message = ui->messageTextLabel->text();
170+
recipient.fSubtractFeeFromAmount = (ui->checkboxSubtractFeeFromAmount->checkState() == Qt::Checked);
168171

169172
return recipient;
170173
}
@@ -174,7 +177,8 @@ QWidget *SendCoinsEntry::setupTabChain(QWidget *prev)
174177
QWidget::setTabOrder(prev, ui->payTo);
175178
QWidget::setTabOrder(ui->payTo, ui->addAsLabel);
176179
QWidget *w = ui->payAmount->setupTabChain(ui->addAsLabel);
177-
QWidget::setTabOrder(w, ui->addressBookButton);
180+
QWidget::setTabOrder(w, ui->checkboxSubtractFeeFromAmount);
181+
QWidget::setTabOrder(ui->checkboxSubtractFeeFromAmount, ui->addressBookButton);
178182
QWidget::setTabOrder(ui->addressBookButton, ui->pasteButton);
179183
QWidget::setTabOrder(ui->pasteButton, ui->deleteButton);
180184
return ui->deleteButton;

src/qt/sendcoinsentry.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public slots:
5151
signals:
5252
void removeEntry(SendCoinsEntry *entry);
5353
void payAmountChanged();
54+
void subtractFeeFromAmountChanged();
5455

5556
private slots:
5657
void deleteClicked();

src/qt/walletmodel.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "addresstablemodel.h"
88
#include "guiconstants.h"
9+
#include "guiutil.h"
910
#include "paymentserver.h"
1011
#include "recentrequeststablemodel.h"
1112
#include "transactiontablemodel.h"
@@ -192,8 +193,9 @@ bool WalletModel::validateAddress(const QString &address)
192193
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
193194
{
194195
CAmount total = 0;
196+
bool fSubtractFeeFromAmount = false;
195197
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
196-
std::vector<std::pair<CScript, CAmount> > vecSend;
198+
std::vector<CRecipient> vecSend;
197199

198200
if(recipients.empty())
199201
{
@@ -206,6 +208,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
206208
// Pre-check input data for validity
207209
foreach(const SendCoinsRecipient &rcp, recipients)
208210
{
211+
if (rcp.fSubtractFeeFromAmount)
212+
fSubtractFeeFromAmount = true;
213+
209214
if (rcp.paymentRequest.IsInitialized())
210215
{ // PaymentRequest...
211216
CAmount subtotal = 0;
@@ -217,7 +222,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
217222
subtotal += out.amount();
218223
const unsigned char* scriptStr = (const unsigned char*)out.script().data();
219224
CScript scriptPubKey(scriptStr, scriptStr+out.script().size());
220-
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, out.amount()));
225+
CAmount nAmount = out.amount();
226+
CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount};
227+
vecSend.push_back(recipient);
221228
}
222229
if (subtotal <= 0)
223230
{
@@ -239,7 +246,8 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
239246
++nAddresses;
240247

241248
CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
242-
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, rcp.amount));
249+
CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount};
250+
vecSend.push_back(recipient);
243251

244252
total += rcp.amount;
245253
}
@@ -260,17 +268,21 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
260268
LOCK2(cs_main, wallet->cs_wallet);
261269

262270
transaction.newPossibleKeyChange(wallet);
271+
263272
CAmount nFeeRequired = 0;
273+
int nChangePosRet = -1;
264274
std::string strFailReason;
265275

266276
CWalletTx *newTx = transaction.getTransaction();
267277
CReserveKey *keyChange = transaction.getPossibleKeyChange();
268-
bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason, coinControl);
278+
bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl);
269279
transaction.setTransactionFee(nFeeRequired);
280+
if (fSubtractFeeFromAmount && fCreated)
281+
transaction.reassignAmounts(nChangePosRet);
270282

271283
if(!fCreated)
272284
{
273-
if((total + nFeeRequired) > nBalance)
285+
if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance)
274286
{
275287
return SendCoinsReturn(AmountWithFeeExceedsBalance);
276288
}

src/qt/walletmodel.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ QT_END_NAMESPACE
3636
class SendCoinsRecipient
3737
{
3838
public:
39-
explicit SendCoinsRecipient() : amount(0), nVersion(SendCoinsRecipient::CURRENT_VERSION) { }
39+
explicit SendCoinsRecipient() : amount(0), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) { }
4040
explicit SendCoinsRecipient(const QString &addr, const QString &label, const CAmount& amount, const QString &message):
41-
address(addr), label(label), amount(amount), message(message), nVersion(SendCoinsRecipient::CURRENT_VERSION) {}
41+
address(addr), label(label), amount(amount), message(message), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {}
4242

4343
// If from an unauthenticated payment request, this is used for storing
4444
// the addresses, e.g. address-A<br />address-B<br />address-C.
@@ -56,6 +56,8 @@ class SendCoinsRecipient
5656
// Empty if no authentication or invalid signature/cert/etc.
5757
QString authenticatedMerchant;
5858

59+
bool fSubtractFeeFromAmount; // memory only
60+
5961
static const int CURRENT_VERSION = 1;
6062
int nVersion;
6163

0 commit comments

Comments
 (0)