aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCozz Lovan <cozzlovan@yahoo.com>2014-07-23 14:34:36 +0200
committerWladimir J. van der Laan <laanwj@gmail.com>2015-03-13 11:04:18 +0100
commit292623adf59edea52b2927b3d67fd2ff7f997882 (patch)
treeafec21b4e7ff1c2b551b060f5f435895d43d15d6
parent90a43c1e93318584d5443b513c2c0e937acb966a (diff)
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).
-rwxr-xr-xqa/rpc-tests/wallet.py15
-rw-r--r--src/primitives/transaction.h9
-rw-r--r--src/qt/coincontroldialog.cpp23
-rw-r--r--src/qt/coincontroldialog.h1
-rw-r--r--src/qt/forms/sendcoinsentry.ui16
-rw-r--r--src/qt/sendcoinsdialog.cpp13
-rw-r--r--src/qt/sendcoinsentry.cpp6
-rw-r--r--src/qt/sendcoinsentry.h1
-rw-r--r--src/qt/walletmodel.cpp22
-rw-r--r--src/qt/walletmodel.h6
-rw-r--r--src/qt/walletmodeltransaction.cpp32
-rw-r--r--src/qt/walletmodeltransaction.h4
-rw-r--r--src/rpcclient.cpp2
-rw-r--r--src/rpcwallet.cpp61
-rw-r--r--src/wallet.cpp87
-rw-r--r--src/wallet.h12
16 files changed, 248 insertions, 62 deletions
diff --git a/qa/rpc-tests/wallet.py b/qa/rpc-tests/wallet.py
index 3e63493dc6..84d3023996 100755
--- a/qa/rpc-tests/wallet.py
+++ b/qa/rpc-tests/wallet.py
@@ -102,6 +102,21 @@ class WalletTest (BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), 100)
assert_equal(self.nodes[2].getbalance("from1"), 100-21)
+ # Send 10 XBT normal
+ address = self.nodes[0].getnewaddress("test")
+ self.nodes[2].settxfee(Decimal('0.001'))
+ txid = self.nodes[2].sendtoaddress(address, 10, "", "", False)
+ self.nodes[2].setgenerate(True, 1)
+ self.sync_all()
+ assert_equal(self.nodes[2].getbalance(), Decimal('89.99900000'))
+ assert_equal(self.nodes[0].getbalance(), Decimal('10.00000000'))
+
+ # Send 10 XBT with subtract fee from amount
+ txid = self.nodes[2].sendtoaddress(address, 10, "", "", True)
+ self.nodes[2].setgenerate(True, 1)
+ self.sync_all()
+ assert_equal(self.nodes[2].getbalance(), Decimal('79.99900000'))
+ assert_equal(self.nodes[0].getbalance(), Decimal('19.99900000'))
if __name__ == '__main__':
WalletTest ().main ()
diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h
index 1b5a47e0da..fdb8c50a46 100644
--- a/src/primitives/transaction.h
+++ b/src/primitives/transaction.h
@@ -134,7 +134,7 @@ public:
uint256 GetHash() const;
- bool IsDust(CFeeRate minRelayTxFee) const
+ CAmount GetDustThreshold(const CFeeRate &minRelayTxFee) const
{
// "Dust" is defined in terms of CTransaction::minRelayTxFee,
// which has units satoshis-per-kilobyte.
@@ -145,7 +145,12 @@ public:
// 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(const CFeeRate &minRelayTxFee) const
+ {
+ return (nValue < GetDustThreshold(minRelayTxFee));
}
friend bool operator==(const CTxOut& a, const CTxOut& b)
diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp
index 3f4f082b8c..5042ff06a2 100644
--- a/src/qt/coincontroldialog.cpp
+++ b/src/qt/coincontroldialog.cpp
@@ -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());
}
diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h
index 5a91876f1f..5ec382838f 100644
--- a/src/qt/coincontroldialog.h
+++ b/src/qt/coincontroldialog.h
@@ -43,6 +43,7 @@ public:
static QList<CAmount> payAmounts;
static CCoinControl *coinControl;
+ static bool fSubtractFeeFromAmount;
private:
Ui::CoinControlDialog *ui;
diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui
index 9f8c0a4844..b362928438 100644
--- a/src/qt/forms/sendcoinsentry.ui
+++ b/src/qt/forms/sendcoinsentry.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&amp;ubtract fee from amount</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="messageLabel">
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 4338e58e26..55ca65c8e6 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -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())
diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp
index 6db6eee75b..6ac650e74f 100644
--- a/src/qt/sendcoinsentry.cpp
+++ b/src/qt/sendcoinsentry.cpp
@@ -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;
diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h
index 4cb00cd36a..c2d1185bdd 100644
--- a/src/qt/sendcoinsentry.h
+++ b/src/qt/sendcoinsentry.h
@@ -51,6 +51,7 @@ public slots:
signals:
void removeEntry(SendCoinsEntry *entry);
void payAmountChanged();
+ void subtractFeeFromAmountChanged();
private slots:
void deleteClicked();
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 79f5191fc0..1baa5eb932 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -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);
}
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h
index 4a9a12beaa..de915165f9 100644
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -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;
diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp
index 8f32e46148..c97add6bef 100644
--- a/src/qt/walletmodeltransaction.cpp
+++ b/src/qt/walletmodeltransaction.cpp
@@ -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;
diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h
index b6bb6d67f6..7765fea4af 100644
--- a/src/qt/walletmodeltransaction.h
+++ b/src/qt/walletmodeltransaction.h
@@ -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;
diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp
index 4e45bc32ab..a45ea9839b 100644
--- a/src/rpcclient.cpp
+++ b/src/rpcclient.cpp
@@ -32,6 +32,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getnetworkhashps", 0 },
{ "getnetworkhashps", 1 },
{ "sendtoaddress", 1 },
+ { "sendtoaddress", 4 },
{ "settxfee", 0 },
{ "getreceivedbyaddress", 1 },
{ "getreceivedbyaccount", 1 },
@@ -59,6 +60,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listsinceblock", 2 },
{ "sendmany", 1 },
{ "sendmany", 2 },
+ { "sendmany", 4 },
{ "addmultisigaddress", 0 },
{ "addmultisigaddress", 1 },
{ "createmultisig", 0 },
diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp
index d097b6a0fa..142487bc8c 100644
--- a/src/rpcwallet.cpp
+++ b/src/rpcwallet.cpp
@@ -317,7 +317,7 @@ Value getaddressesbyaccount(const Array& params, bool fHelp)
return ret;
}
-static void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx& wtxNew)
+static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew)
{
CAmount curBalance = pwalletMain->GetBalance();
@@ -335,11 +335,14 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx&
CReserveKey reservekey(pwalletMain);
CAmount nFeeRequired;
std::string strError;
- if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError)) {
- if (nValue + nFeeRequired > curBalance)
- throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)));
- else
- throw JSONRPCError(RPC_WALLET_ERROR, strError);
+ vector<CRecipient> vecSend;
+ int nChangePosRet = -1;
+ CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
+ vecSend.push_back(recipient);
+ if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) {
+ if (!fSubtractFeeFromAmount && nValue + nFeeRequired > pwalletMain->GetBalance())
+ strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired));
+ throw JSONRPCError(RPC_WALLET_ERROR, strError);
}
if (!pwalletMain->CommitTransaction(wtxNew, reservekey))
throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.");
@@ -347,9 +350,9 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx&
Value sendtoaddress(const Array& params, bool fHelp)
{
- if (fHelp || params.size() < 2 || params.size() > 4)
+ if (fHelp || params.size() < 2 || params.size() > 5)
throw runtime_error(
- "sendtoaddress \"bitcoinaddress\" amount ( \"comment\" \"comment-to\" )\n"
+ "sendtoaddress \"bitcoinaddress\" amount ( \"comment\" \"comment-to\" subtractfeefromamount )\n"
"\nSend an amount to a given address. The amount is a real and is rounded to the nearest 0.00000001\n"
+ HelpRequiringPassphrase() +
"\nArguments:\n"
@@ -360,11 +363,14 @@ Value sendtoaddress(const Array& params, bool fHelp)
"4. \"comment-to\" (string, optional) A comment to store the name of the person or organization \n"
" to which you're sending the transaction. This is not part of the \n"
" transaction, just kept in your wallet.\n"
+ "5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n"
+ " The recipient will receive less bitcoins than you enter in the amount field.\n"
"\nResult:\n"
"\"transactionid\" (string) The transaction id.\n"
"\nExamples:\n"
+ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1")
+ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"donation\" \"seans outpost\"")
+ + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true")
+ HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"")
);
@@ -384,9 +390,13 @@ Value sendtoaddress(const Array& params, bool fHelp)
if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty())
wtx.mapValue["to"] = params[3].get_str();
+ bool fSubtractFeeFromAmount = false;
+ if (params.size() > 4)
+ fSubtractFeeFromAmount = params[4].get_bool();
+
EnsureWalletIsUnlocked();
- SendMoney(address.Get(), nAmount, wtx);
+ SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, wtx);
return wtx.GetHash().GetHex();
}
@@ -840,7 +850,7 @@ Value sendfrom(const Array& params, bool fHelp)
if (nAmount > nBalance)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
- SendMoney(address.Get(), nAmount, wtx);
+ SendMoney(address.Get(), nAmount, false, wtx);
return wtx.GetHash().GetHex();
}
@@ -848,9 +858,9 @@ Value sendfrom(const Array& params, bool fHelp)
Value sendmany(const Array& params, bool fHelp)
{
- if (fHelp || params.size() < 2 || params.size() > 4)
+ if (fHelp || params.size() < 2 || params.size() > 5)
throw runtime_error(
- "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" )\n"
+ "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" {\"address\":true,...} )\n"
"\nSend multiple times. Amounts are double-precision floating point numbers."
+ HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
@@ -862,6 +872,14 @@ Value sendmany(const Array& params, bool fHelp)
" }\n"
"3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n"
"4. \"comment\" (string, optional) A comment\n"
+ "5. subtractfeefromamount (string, optional) A json object with addresses and booleans.\n"
+ " The fee will be equally deducted from the amount of each selected address.\n"
+ " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
+ " Default for each address is false. If no addresses are specified here, the sender pays the fee.\n"
+ " {\n"
+ " \"address\":true (boolean) Subtract fee from this address\n"
+ " ,...\n"
+ " }\n"
"\nResult:\n"
"\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
" the number of addresses.\n"
@@ -870,6 +888,8 @@ Value sendmany(const Array& params, bool fHelp)
+ HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") +
"\nSend two amounts to two different addresses setting the confirmation and comment:\n"
+ HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") +
+ "\nSend two amounts to two different addresses, subtract fee from amount:\n"
+ + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":true,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":true}\"") +
"\nAs a json rpc call\n"
+ HelpExampleRpc("sendmany", "\"\", \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\", 6, \"testing\"")
);
@@ -887,8 +907,12 @@ Value sendmany(const Array& params, bool fHelp)
if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty())
wtx.mapValue["comment"] = params[3].get_str();
+ Object subtractFeeFromAmount;
+ if (params.size() > 4)
+ subtractFeeFromAmount = params[4].get_obj();
+
set<CBitcoinAddress> setAddress;
- vector<pair<CScript, CAmount> > vecSend;
+ vector<CRecipient> vecSend;
CAmount totalAmount = 0;
BOOST_FOREACH(const Pair& s, sendTo)
@@ -905,7 +929,13 @@ Value sendmany(const Array& params, bool fHelp)
CAmount nAmount = AmountFromValue(s.value_);
totalAmount += nAmount;
- vecSend.push_back(make_pair(scriptPubKey, nAmount));
+ bool fSubtractFeeFromAmount = false;
+ BOOST_FOREACH(const Pair& s2, subtractFeeFromAmount)
+ if (s2.name_ == s.name_ && s2.value_.get_bool() == true)
+ fSubtractFeeFromAmount = true;
+
+ CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount};
+ vecSend.push_back(recipient);
}
EnsureWalletIsUnlocked();
@@ -918,8 +948,9 @@ Value sendmany(const Array& params, bool fHelp)
// Send
CReserveKey keyChange(pwalletMain);
CAmount nFeeRequired = 0;
+ int nChangePosRet = -1;
string strFailReason;
- bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason);
+ bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason);
if (!fCreated)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
if (!pwalletMain->CommitTransaction(wtx, keyChange))
diff --git a/src/wallet.cpp b/src/wallet.cpp
index b51c4d4b14..9dfd34de9f 100644
--- a/src/wallet.cpp
+++ b/src/wallet.cpp
@@ -1549,21 +1549,22 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet)));
}
-
-
-
-bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
- CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl)
+bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend,
+ CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl)
{
CAmount nValue = 0;
- BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend)
+ unsigned int nSubtractFeeFromAmount = 0;
+ BOOST_FOREACH (const CRecipient& recipient, vecSend)
{
- if (nValue < 0)
+ if (nValue < 0 || recipient.nAmount < 0)
{
strFailReason = _("Transaction amounts must be positive");
return false;
}
- nValue += s.second;
+ nValue += recipient.nAmount;
+
+ if (recipient.fSubtractFeeFromAmount)
+ nSubtractFeeFromAmount++;
}
if (vecSend.empty() || nValue < 0)
{
@@ -1606,16 +1607,40 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
txNew.vin.clear();
txNew.vout.clear();
wtxNew.fFromMe = true;
+ nChangePosRet = -1;
+ bool fFirst = true;
- CAmount nTotalValue = nValue + nFeeRet;
+ CAmount nTotalValue = nValue;
+ if (nSubtractFeeFromAmount == 0)
+ nTotalValue += nFeeRet;
double dPriority = 0;
// vouts to the payees
- BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend)
+ BOOST_FOREACH (const CRecipient& recipient, vecSend)
{
- CTxOut txout(s.second, s.first);
+ CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
+
+ if (recipient.fSubtractFeeFromAmount)
+ {
+ txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
+
+ if (fFirst) // first receiver pays the remainder not divisible by output count
+ {
+ fFirst = false;
+ txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
+ }
+ }
+
if (txout.IsDust(::minRelayTxFee))
{
- strFailReason = _("Transaction amount too small");
+ if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
+ {
+ if (txout.nValue < 0)
+ strFailReason = _("The transaction amount is too small to pay the fee");
+ else
+ strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
+ }
+ else
+ strFailReason = _("Transaction amount too small");
return false;
}
txNew.vout.push_back(txout);
@@ -1642,7 +1667,9 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
dPriority += (double)nCredit * age;
}
- CAmount nChange = nValueIn - nValue - nFeeRet;
+ CAmount nChange = nValueIn - nValue;
+ if (nSubtractFeeFromAmount == 0)
+ nChange -= nFeeRet;
if (nChange > 0)
{
@@ -1676,6 +1703,28 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
CTxOut newTxOut(nChange, scriptChange);
+ // We do not move dust-change to fees, because the sender would end up paying more than requested.
+ // This would be against the purpose of the all-inclusive feature.
+ // So instead we raise the change and deduct from the recipient.
+ if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(::minRelayTxFee))
+ {
+ CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue;
+ newTxOut.nValue += nDust; // raise change until no more dust
+ for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient
+ {
+ if (vecSend[i].fSubtractFeeFromAmount)
+ {
+ txNew.vout[i].nValue -= nDust;
+ if (txNew.vout[i].IsDust(::minRelayTxFee))
+ {
+ strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
+ return false;
+ }
+ break;
+ }
+ }
+ }
+
// Never create dust outputs; if we would, just
// add the dust to the fee.
if (newTxOut.IsDust(::minRelayTxFee))
@@ -1686,7 +1735,8 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
else
{
// Insert change txn at random position:
- vector<CTxOut>::iterator position = txNew.vout.begin()+GetRandInt(txNew.vout.size()+1);
+ nChangePosRet = GetRandInt(txNew.vout.size()+1);
+ vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosRet;
txNew.vout.insert(position, newTxOut);
}
}
@@ -1755,15 +1805,8 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
}
}
}
- return true;
-}
-bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue,
- CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl)
-{
- vector< pair<CScript, CAmount> > vecSend;
- vecSend.push_back(make_pair(scriptPubKey, nValue));
- return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason, coinControl);
+ return true;
}
/**
diff --git a/src/wallet.h b/src/wallet.h
index 6ed87d1e68..a5a2558f94 100644
--- a/src/wallet.h
+++ b/src/wallet.h
@@ -103,6 +103,12 @@ public:
StringMap destdata;
};
+struct CRecipient
+{
+ CScript scriptPubKey;
+ CAmount nAmount;
+ bool fSubtractFeeFromAmount;
+};
typedef std::map<std::string, std::string> mapValue_t;
@@ -611,10 +617,8 @@ public:
CAmount GetWatchOnlyBalance() const;
CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const;
- bool CreateTransaction(const std::vector<std::pair<CScript, CAmount> >& vecSend,
- CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL);
- bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue,
- CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL);
+ bool CreateTransaction(const std::vector<CRecipient>& vecSend,
+ CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl *coinControl = NULL);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
static CFeeRate minTxFee;