Skip to content

Commit

Permalink
Subtract fee from amount
Browse files Browse the repository at this point in the history
  • Loading branch information
cozz committed Jul 24, 2014
1 parent 0d1f8b3 commit 94e2d7c
Show file tree
Hide file tree
Showing 15 changed files with 246 additions and 54 deletions.
9 changes: 7 additions & 2 deletions src/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class CTxOut

uint256 GetHash() const;

bool IsDust(CFeeRate minRelayTxFee) const
int64_t GetDustThreshold(CFeeRate& minRelayTxFee) const
{
// "Dust" is defined in terms of CTransaction::minRelayTxFee,
// which has units satoshis-per-kilobyte.
Expand All @@ -185,7 +185,12 @@ class CTxOut
// so dust is a txout less than 546 satoshis
// with default minRelayTxFee.
size_t nSize = GetSerializeSize(SER_DISK,0)+148u;
return (nValue < 3*minRelayTxFee.GetFee(nSize));
return 3*minRelayTxFee.GetFee(nSize);
}

bool IsDust(CFeeRate minRelayTxFee) const
{
return (nValue < GetDustThreshold(minRelayTxFee));
}

friend bool operator==(const CTxOut& a, const CTxOut& b)
Expand Down
23 changes: 18 additions & 5 deletions src/qt/coincontroldialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
using namespace std;
QList<qint64> CoinControlDialog::payAmounts;
CCoinControl* CoinControlDialog::coinControl = new CCoinControl();
bool CoinControlDialog::fSubtractFeeFromAmount = false;

CoinControlDialog::CoinControlDialog(QWidget *parent) :
QDialog(parent),
Expand Down Expand Up @@ -528,6 +529,11 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
// Voluntary Fee
nPayFee = payTxFee.GetFee(max((unsigned int)1000, nBytes));

// 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;

// Min Fee
if (nPayFee == 0)
{
Expand All @@ -543,20 +549,27 @@ 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)
{
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;
}

Expand Down Expand Up @@ -599,7 +612,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
{
l3->setText("~" + l3->text());
l4->setText("~" + l4->text());
if (nChange > 0)
if (nChange > 0 && !CoinControlDialog::fSubtractFeeFromAmount)
l8->setText("~" + l8->text());
}

Expand Down
1 change: 1 addition & 0 deletions src/qt/coincontroldialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class CoinControlDialog : public QDialog

static QList<qint64> payAmounts;
static CCoinControl *coinControl;
static bool fSubtractFeeFromAmount;

private:
Ui::CoinControlDialog *ui;
Expand Down
16 changes: 15 additions & 1 deletion src/qt/forms/sendcoinsentry.ui
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,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&amp;ubtract fee from amount</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="messageLabel">
Expand Down
13 changes: 9 additions & 4 deletions src/qt/sendcoinsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,6 @@ void SendCoinsDialog::on_sendButton_clicked()
}

fNewRecipientAllowed = false;


WalletModel::UnlockContext ctx(model->requestUnlock());
if(!ctx.isValid())
{
Expand Down Expand Up @@ -170,7 +168,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);
Expand Down Expand Up @@ -284,6 +282,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();

Expand Down Expand Up @@ -585,11 +584,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())
Expand Down
6 changes: 5 additions & 1 deletion src/qt/sendcoinsentry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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()));
Expand Down Expand Up @@ -87,6 +88,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();
Expand Down Expand Up @@ -158,6 +160,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;
}
Expand All @@ -167,7 +170,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;
Expand Down
1 change: 1 addition & 0 deletions src/qt/sendcoinsentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public slots:
signals:
void removeEntry(SendCoinsEntry *entry);
void payAmountChanged();
void subtractFeeFromAmountChanged();

private slots:
void deleteClicked();
Expand Down
21 changes: 16 additions & 5 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "addresstablemodel.h"
#include "guiconstants.h"
#include "guiutil.h"
#include "recentrequeststablemodel.h"
#include "transactiontablemodel.h"

Expand Down Expand Up @@ -194,8 +195,9 @@ bool WalletModel::validateAddress(const QString &address)
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
{
qint64 total = 0;
bool fSubtractFeeFromAmount = false;
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
std::vector<std::pair<CScript, int64_t> > vecSend;
std::vector<CRecipient> vecSend;

if(recipients.empty())
{
Expand All @@ -208,6 +210,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...
int64_t subtotal = 0;
Expand All @@ -219,7 +224,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, int64_t>(scriptPubKey, out.amount()));
int64_t nAmount = out.amount();
CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount};
vecSend.push_back(recipient);
}
if (subtotal <= 0)
{
Expand All @@ -242,7 +249,8 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact

CScript scriptPubKey;
scriptPubKey.SetDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
vecSend.push_back(std::pair<CScript, int64_t>(scriptPubKey, rcp.amount));
CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount};
vecSend.push_back(recipient);

total += rcp.amount;
}
Expand All @@ -264,16 +272,19 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact

transaction.newPossibleKeyChange(wallet);
int64_t 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);
}
Expand Down
6 changes: 4 additions & 2 deletions src/qt/walletmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, quint64 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 insecure payment request, this is used for storing
// the addresses, e.g. address-A<br />address-B<br />address-C.
Expand All @@ -56,6 +56,8 @@ class SendCoinsRecipient
// Empty if no authentication or invalid signature/cert/etc.
QString authenticatedMerchant;

bool fSubtractFeeFromAmount; // memory only

static const int CURRENT_VERSION = 1;
int nVersion;

Expand Down
32 changes: 32 additions & 0 deletions src/qt/walletmodeltransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,38 @@ void WalletModelTransaction::setTransactionFee(qint64 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())
{
int64_t 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++;
}
}
}

qint64 WalletModelTransaction::getTotalTransactionAmount()
{
qint64 totalTransactionAmount = 0;
Expand Down
4 changes: 3 additions & 1 deletion src/qt/walletmodeltransaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ class WalletModelTransaction
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;
qint64 fee;
Expand Down
3 changes: 3 additions & 0 deletions src/rpcclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getnetworkhashps", 0 },
{ "getnetworkhashps", 1 },
{ "sendtoaddress", 1 },
{ "sendtoaddress", 4 },
{ "settxfee", 0 },
{ "getreceivedbyaddress", 1 },
{ "getreceivedbyaccount", 1 },
Expand All @@ -48,6 +49,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "move", 3 },
{ "sendfrom", 2 },
{ "sendfrom", 3 },
{ "sendfrom", 6 },
{ "listtransactions", 1 },
{ "listtransactions", 2 },
{ "listtransactions", 3 },
Expand All @@ -59,6 +61,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listsinceblock", 2 },
{ "sendmany", 1 },
{ "sendmany", 2 },
{ "sendmany", 4 },
{ "addmultisigaddress", 0 },
{ "addmultisigaddress", 1 },
{ "createmultisig", 0 },
Expand Down
Loading

0 comments on commit 94e2d7c

Please sign in to comment.