Merge pull request #5831
1d9b378
qa/rpc-tests/wallet: Tests for sendmany (Luke Dashjr)40a7573
rpcwallet/sendmany: Just take an array of addresses to subtract fees from, rather than an Object with all values being identical (Luke Dashjr)292623a
Subtract fee from amount (Cozz Lovan)90a43c1
[Qt] Code-movement-only: Format confirmation message in sendcoinsdialog (Cozz Lovan)
This commit is contained in:
commit
df5c246ba3
|
@ -102,6 +102,35 @@ class WalletTest (BitcoinTestFramework):
|
||||||
assert_equal(self.nodes[2].getbalance(), 100)
|
assert_equal(self.nodes[2].getbalance(), 100)
|
||||||
assert_equal(self.nodes[2].getbalance("from1"), 100-21)
|
assert_equal(self.nodes[2].getbalance("from1"), 100-21)
|
||||||
|
|
||||||
|
# Send 10 BTC 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 BTC 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'))
|
||||||
|
|
||||||
|
# Sendmany 10 BTC
|
||||||
|
txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [])
|
||||||
|
self.nodes[2].setgenerate(True, 1)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(self.nodes[2].getbalance(), Decimal('69.99800000'))
|
||||||
|
assert_equal(self.nodes[0].getbalance(), Decimal('29.99900000'))
|
||||||
|
|
||||||
|
# Sendmany 10 BTC with subtract fee from amount
|
||||||
|
txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [address])
|
||||||
|
self.nodes[2].setgenerate(True, 1)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(self.nodes[2].getbalance(), Decimal('59.99800000'))
|
||||||
|
assert_equal(self.nodes[0].getbalance(), Decimal('39.99800000'))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
WalletTest ().main ()
|
WalletTest ().main ()
|
||||||
|
|
|
@ -135,7 +135,7 @@ public:
|
||||||
|
|
||||||
uint256 GetHash() const;
|
uint256 GetHash() const;
|
||||||
|
|
||||||
bool IsDust(CFeeRate minRelayTxFee) const
|
CAmount GetDustThreshold(const CFeeRate &minRelayTxFee) const
|
||||||
{
|
{
|
||||||
// "Dust" is defined in terms of CTransaction::minRelayTxFee,
|
// "Dust" is defined in terms of CTransaction::minRelayTxFee,
|
||||||
// which has units satoshis-per-kilobyte.
|
// which has units satoshis-per-kilobyte.
|
||||||
|
@ -146,7 +146,12 @@ public:
|
||||||
// so dust is a txout less than 546 satoshis
|
// so dust is a txout less than 546 satoshis
|
||||||
// with default minRelayTxFee.
|
// with default minRelayTxFee.
|
||||||
size_t nSize = GetSerializeSize(SER_DISK,0)+148u;
|
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)
|
friend bool operator==(const CTxOut& a, const CTxOut& b)
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
using namespace std;
|
using namespace std;
|
||||||
QList<CAmount> CoinControlDialog::payAmounts;
|
QList<CAmount> CoinControlDialog::payAmounts;
|
||||||
CCoinControl* CoinControlDialog::coinControl = new CCoinControl();
|
CCoinControl* CoinControlDialog::coinControl = new CCoinControl();
|
||||||
|
bool CoinControlDialog::fSubtractFeeFromAmount = false;
|
||||||
|
|
||||||
CoinControlDialog::CoinControlDialog(QWidget *parent) :
|
CoinControlDialog::CoinControlDialog(QWidget *parent) :
|
||||||
QDialog(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)
|
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);
|
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
|
// Fee
|
||||||
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
|
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
|
||||||
|
|
||||||
|
@ -556,20 +562,27 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||||
|
|
||||||
if (nPayAmount > 0)
|
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.
|
// Never create dust outputs; if we would, just add the dust to the fee.
|
||||||
if (nChange > 0 && nChange < CENT)
|
if (nChange > 0 && nChange < CENT)
|
||||||
{
|
{
|
||||||
CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0));
|
CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0));
|
||||||
if (txout.IsDust(::minRelayTxFee))
|
if (txout.IsDust(::minRelayTxFee))
|
||||||
|
{
|
||||||
|
if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust
|
||||||
|
nChange = txout.GetDustThreshold(::minRelayTxFee);
|
||||||
|
else
|
||||||
{
|
{
|
||||||
nPayFee += nChange;
|
nPayFee += nChange;
|
||||||
nChange = 0;
|
nChange = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (nChange == 0)
|
if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount)
|
||||||
nBytes -= 34;
|
nBytes -= 34;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -612,7 +625,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||||
{
|
{
|
||||||
l3->setText(ASYMP_UTF8 + l3->text());
|
l3->setText(ASYMP_UTF8 + l3->text());
|
||||||
l4->setText(ASYMP_UTF8 + l4->text());
|
l4->setText(ASYMP_UTF8 + l4->text());
|
||||||
if (nChange > 0)
|
if (nChange > 0 && !CoinControlDialog::fSubtractFeeFromAmount)
|
||||||
l8->setText(ASYMP_UTF8 + l8->text());
|
l8->setText(ASYMP_UTF8 + l8->text());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ public:
|
||||||
|
|
||||||
static QList<CAmount> payAmounts;
|
static QList<CAmount> payAmounts;
|
||||||
static CCoinControl *coinControl;
|
static CCoinControl *coinControl;
|
||||||
|
static bool fSubtractFeeFromAmount;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::CoinControlDialog *ui;
|
Ui::CoinControlDialog *ui;
|
||||||
|
|
|
@ -157,8 +157,22 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayoutAmount" stretch="0,1">
|
||||||
|
<item>
|
||||||
<widget class="BitcoinAmountField" name="payAmount"/>
|
<widget class="BitcoinAmountField" name="payAmount"/>
|
||||||
</item>
|
</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">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="messageLabel">
|
<widget class="QLabel" name="messageLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
|
@ -220,9 +220,37 @@ void SendCoinsDialog::on_sendButton_clicked()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fNewRecipientAllowed = false;
|
||||||
|
WalletModel::UnlockContext ctx(model->requestUnlock());
|
||||||
|
if(!ctx.isValid())
|
||||||
|
{
|
||||||
|
// Unlock wallet was cancelled
|
||||||
|
fNewRecipientAllowed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare transaction for getting txFee earlier
|
||||||
|
WalletModelTransaction currentTransaction(recipients);
|
||||||
|
WalletModel::SendCoinsReturn prepareStatus;
|
||||||
|
if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled
|
||||||
|
prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl);
|
||||||
|
else
|
||||||
|
prepareStatus = model->prepareTransaction(currentTransaction);
|
||||||
|
|
||||||
|
// process prepareStatus and on error generate message shown to user
|
||||||
|
processSendCoinsReturn(prepareStatus,
|
||||||
|
BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
|
||||||
|
|
||||||
|
if(prepareStatus.status != WalletModel::OK) {
|
||||||
|
fNewRecipientAllowed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAmount txFee = currentTransaction.getTransactionFee();
|
||||||
|
|
||||||
// Format confirmation message
|
// Format confirmation message
|
||||||
QStringList formatted;
|
QStringList formatted;
|
||||||
foreach(const SendCoinsRecipient &rcp, recipients)
|
foreach(const SendCoinsRecipient &rcp, currentTransaction.getRecipients())
|
||||||
{
|
{
|
||||||
// generate bold amount string
|
// generate bold amount string
|
||||||
QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
|
QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
|
||||||
|
@ -257,35 +285,6 @@ void SendCoinsDialog::on_sendButton_clicked()
|
||||||
formatted.append(recipientElement);
|
formatted.append(recipientElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
fNewRecipientAllowed = false;
|
|
||||||
|
|
||||||
|
|
||||||
WalletModel::UnlockContext ctx(model->requestUnlock());
|
|
||||||
if(!ctx.isValid())
|
|
||||||
{
|
|
||||||
// Unlock wallet was cancelled
|
|
||||||
fNewRecipientAllowed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare transaction for getting txFee earlier
|
|
||||||
WalletModelTransaction currentTransaction(recipients);
|
|
||||||
WalletModel::SendCoinsReturn prepareStatus;
|
|
||||||
if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled
|
|
||||||
prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl);
|
|
||||||
else
|
|
||||||
prepareStatus = model->prepareTransaction(currentTransaction);
|
|
||||||
|
|
||||||
// process prepareStatus and on error generate message shown to user
|
|
||||||
processSendCoinsReturn(prepareStatus,
|
|
||||||
BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()));
|
|
||||||
|
|
||||||
if(prepareStatus.status != WalletModel::OK) {
|
|
||||||
fNewRecipientAllowed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CAmount txFee = currentTransaction.getTransactionFee();
|
|
||||||
QString questionString = tr("Are you sure you want to send?");
|
QString questionString = tr("Are you sure you want to send?");
|
||||||
questionString.append("<br /><br />%1");
|
questionString.append("<br /><br />%1");
|
||||||
|
|
||||||
|
@ -368,6 +367,7 @@ SendCoinsEntry *SendCoinsDialog::addEntry()
|
||||||
ui->entries->addWidget(entry);
|
ui->entries->addWidget(entry);
|
||||||
connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
|
connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
|
||||||
connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
|
connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
|
||||||
|
connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
|
||||||
|
|
||||||
updateTabsAndLabels();
|
updateTabsAndLabels();
|
||||||
|
|
||||||
|
@ -783,11 +783,17 @@ void SendCoinsDialog::coinControlUpdateLabels()
|
||||||
|
|
||||||
// set pay amounts
|
// set pay amounts
|
||||||
CoinControlDialog::payAmounts.clear();
|
CoinControlDialog::payAmounts.clear();
|
||||||
|
CoinControlDialog::fSubtractFeeFromAmount = false;
|
||||||
for(int i = 0; i < ui->entries->count(); ++i)
|
for(int i = 0; i < ui->entries->count(); ++i)
|
||||||
{
|
{
|
||||||
SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
|
SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
|
||||||
if(entry)
|
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())
|
if (CoinControlDialog::coinControl->HasSelected())
|
||||||
|
|
|
@ -44,6 +44,7 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) :
|
||||||
|
|
||||||
// Connect signals
|
// Connect signals
|
||||||
connect(ui->payAmount, SIGNAL(valueChanged()), this, SIGNAL(payAmountChanged()));
|
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, SIGNAL(clicked()), this, SLOT(deleteClicked()));
|
||||||
connect(ui->deleteButton_is, 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->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked()));
|
||||||
|
@ -94,6 +95,7 @@ void SendCoinsEntry::clear()
|
||||||
ui->payTo->clear();
|
ui->payTo->clear();
|
||||||
ui->addAsLabel->clear();
|
ui->addAsLabel->clear();
|
||||||
ui->payAmount->clear();
|
ui->payAmount->clear();
|
||||||
|
ui->checkboxSubtractFeeFromAmount->setCheckState(Qt::Unchecked);
|
||||||
ui->messageTextLabel->clear();
|
ui->messageTextLabel->clear();
|
||||||
ui->messageTextLabel->hide();
|
ui->messageTextLabel->hide();
|
||||||
ui->messageLabel->hide();
|
ui->messageLabel->hide();
|
||||||
|
@ -165,6 +167,7 @@ SendCoinsRecipient SendCoinsEntry::getValue()
|
||||||
recipient.label = ui->addAsLabel->text();
|
recipient.label = ui->addAsLabel->text();
|
||||||
recipient.amount = ui->payAmount->value();
|
recipient.amount = ui->payAmount->value();
|
||||||
recipient.message = ui->messageTextLabel->text();
|
recipient.message = ui->messageTextLabel->text();
|
||||||
|
recipient.fSubtractFeeFromAmount = (ui->checkboxSubtractFeeFromAmount->checkState() == Qt::Checked);
|
||||||
|
|
||||||
return recipient;
|
return recipient;
|
||||||
}
|
}
|
||||||
|
@ -174,7 +177,8 @@ QWidget *SendCoinsEntry::setupTabChain(QWidget *prev)
|
||||||
QWidget::setTabOrder(prev, ui->payTo);
|
QWidget::setTabOrder(prev, ui->payTo);
|
||||||
QWidget::setTabOrder(ui->payTo, ui->addAsLabel);
|
QWidget::setTabOrder(ui->payTo, ui->addAsLabel);
|
||||||
QWidget *w = ui->payAmount->setupTabChain(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->addressBookButton, ui->pasteButton);
|
||||||
QWidget::setTabOrder(ui->pasteButton, ui->deleteButton);
|
QWidget::setTabOrder(ui->pasteButton, ui->deleteButton);
|
||||||
return ui->deleteButton;
|
return ui->deleteButton;
|
||||||
|
|
|
@ -51,6 +51,7 @@ public slots:
|
||||||
signals:
|
signals:
|
||||||
void removeEntry(SendCoinsEntry *entry);
|
void removeEntry(SendCoinsEntry *entry);
|
||||||
void payAmountChanged();
|
void payAmountChanged();
|
||||||
|
void subtractFeeFromAmountChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void deleteClicked();
|
void deleteClicked();
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "addresstablemodel.h"
|
#include "addresstablemodel.h"
|
||||||
#include "guiconstants.h"
|
#include "guiconstants.h"
|
||||||
|
#include "guiutil.h"
|
||||||
#include "paymentserver.h"
|
#include "paymentserver.h"
|
||||||
#include "recentrequeststablemodel.h"
|
#include "recentrequeststablemodel.h"
|
||||||
#include "transactiontablemodel.h"
|
#include "transactiontablemodel.h"
|
||||||
|
@ -192,8 +193,9 @@ bool WalletModel::validateAddress(const QString &address)
|
||||||
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
|
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
|
||||||
{
|
{
|
||||||
CAmount total = 0;
|
CAmount total = 0;
|
||||||
|
bool fSubtractFeeFromAmount = false;
|
||||||
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
|
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
|
||||||
std::vector<std::pair<CScript, CAmount> > vecSend;
|
std::vector<CRecipient> vecSend;
|
||||||
|
|
||||||
if(recipients.empty())
|
if(recipients.empty())
|
||||||
{
|
{
|
||||||
|
@ -206,6 +208,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||||
// Pre-check input data for validity
|
// Pre-check input data for validity
|
||||||
foreach(const SendCoinsRecipient &rcp, recipients)
|
foreach(const SendCoinsRecipient &rcp, recipients)
|
||||||
{
|
{
|
||||||
|
if (rcp.fSubtractFeeFromAmount)
|
||||||
|
fSubtractFeeFromAmount = true;
|
||||||
|
|
||||||
if (rcp.paymentRequest.IsInitialized())
|
if (rcp.paymentRequest.IsInitialized())
|
||||||
{ // PaymentRequest...
|
{ // PaymentRequest...
|
||||||
CAmount subtotal = 0;
|
CAmount subtotal = 0;
|
||||||
|
@ -217,7 +222,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||||
subtotal += out.amount();
|
subtotal += out.amount();
|
||||||
const unsigned char* scriptStr = (const unsigned char*)out.script().data();
|
const unsigned char* scriptStr = (const unsigned char*)out.script().data();
|
||||||
CScript scriptPubKey(scriptStr, scriptStr+out.script().size());
|
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)
|
if (subtotal <= 0)
|
||||||
{
|
{
|
||||||
|
@ -239,7 +246,8 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||||
++nAddresses;
|
++nAddresses;
|
||||||
|
|
||||||
CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
|
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;
|
total += rcp.amount;
|
||||||
}
|
}
|
||||||
|
@ -260,17 +268,21 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||||
LOCK2(cs_main, wallet->cs_wallet);
|
LOCK2(cs_main, wallet->cs_wallet);
|
||||||
|
|
||||||
transaction.newPossibleKeyChange(wallet);
|
transaction.newPossibleKeyChange(wallet);
|
||||||
|
|
||||||
CAmount nFeeRequired = 0;
|
CAmount nFeeRequired = 0;
|
||||||
|
int nChangePosRet = -1;
|
||||||
std::string strFailReason;
|
std::string strFailReason;
|
||||||
|
|
||||||
CWalletTx *newTx = transaction.getTransaction();
|
CWalletTx *newTx = transaction.getTransaction();
|
||||||
CReserveKey *keyChange = transaction.getPossibleKeyChange();
|
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);
|
transaction.setTransactionFee(nFeeRequired);
|
||||||
|
if (fSubtractFeeFromAmount && fCreated)
|
||||||
|
transaction.reassignAmounts(nChangePosRet);
|
||||||
|
|
||||||
if(!fCreated)
|
if(!fCreated)
|
||||||
{
|
{
|
||||||
if((total + nFeeRequired) > nBalance)
|
if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance)
|
||||||
{
|
{
|
||||||
return SendCoinsReturn(AmountWithFeeExceedsBalance);
|
return SendCoinsReturn(AmountWithFeeExceedsBalance);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,9 @@ QT_END_NAMESPACE
|
||||||
class SendCoinsRecipient
|
class SendCoinsRecipient
|
||||||
{
|
{
|
||||||
public:
|
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):
|
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
|
// If from an unauthenticated payment request, this is used for storing
|
||||||
// the addresses, e.g. address-A<br />address-B<br />address-C.
|
// 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.
|
// Empty if no authentication or invalid signature/cert/etc.
|
||||||
QString authenticatedMerchant;
|
QString authenticatedMerchant;
|
||||||
|
|
||||||
|
bool fSubtractFeeFromAmount; // memory only
|
||||||
|
|
||||||
static const int CURRENT_VERSION = 1;
|
static const int CURRENT_VERSION = 1;
|
||||||
int nVersion;
|
int nVersion;
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,38 @@ void WalletModelTransaction::setTransactionFee(const CAmount& newFee)
|
||||||
fee = 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 WalletModelTransaction::getTotalTransactionAmount()
|
||||||
{
|
{
|
||||||
CAmount totalTransactionAmount = 0;
|
CAmount totalTransactionAmount = 0;
|
||||||
|
|
|
@ -35,8 +35,10 @@ public:
|
||||||
void newPossibleKeyChange(CWallet *wallet);
|
void newPossibleKeyChange(CWallet *wallet);
|
||||||
CReserveKey *getPossibleKeyChange();
|
CReserveKey *getPossibleKeyChange();
|
||||||
|
|
||||||
|
void reassignAmounts(int nChangePosRet); // needed for the subtract-fee-from-amount feature
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QList<SendCoinsRecipient> recipients;
|
QList<SendCoinsRecipient> recipients;
|
||||||
CWalletTx *walletTransaction;
|
CWalletTx *walletTransaction;
|
||||||
CReserveKey *keyChange;
|
CReserveKey *keyChange;
|
||||||
CAmount fee;
|
CAmount fee;
|
||||||
|
|
|
@ -32,6 +32,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "getnetworkhashps", 0 },
|
{ "getnetworkhashps", 0 },
|
||||||
{ "getnetworkhashps", 1 },
|
{ "getnetworkhashps", 1 },
|
||||||
{ "sendtoaddress", 1 },
|
{ "sendtoaddress", 1 },
|
||||||
|
{ "sendtoaddress", 4 },
|
||||||
{ "settxfee", 0 },
|
{ "settxfee", 0 },
|
||||||
{ "getreceivedbyaddress", 1 },
|
{ "getreceivedbyaddress", 1 },
|
||||||
{ "getreceivedbyaccount", 1 },
|
{ "getreceivedbyaccount", 1 },
|
||||||
|
@ -59,6 +60,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "listsinceblock", 2 },
|
{ "listsinceblock", 2 },
|
||||||
{ "sendmany", 1 },
|
{ "sendmany", 1 },
|
||||||
{ "sendmany", 2 },
|
{ "sendmany", 2 },
|
||||||
|
{ "sendmany", 4 },
|
||||||
{ "addmultisigaddress", 0 },
|
{ "addmultisigaddress", 0 },
|
||||||
{ "addmultisigaddress", 1 },
|
{ "addmultisigaddress", 1 },
|
||||||
{ "createmultisig", 0 },
|
{ "createmultisig", 0 },
|
||||||
|
|
|
@ -317,7 +317,7 @@ Value getaddressesbyaccount(const Array& params, bool fHelp)
|
||||||
return ret;
|
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();
|
CAmount curBalance = pwalletMain->GetBalance();
|
||||||
|
|
||||||
|
@ -335,10 +335,13 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx&
|
||||||
CReserveKey reservekey(pwalletMain);
|
CReserveKey reservekey(pwalletMain);
|
||||||
CAmount nFeeRequired;
|
CAmount nFeeRequired;
|
||||||
std::string strError;
|
std::string strError;
|
||||||
if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError)) {
|
vector<CRecipient> vecSend;
|
||||||
if (nValue + nFeeRequired > curBalance)
|
int nChangePosRet = -1;
|
||||||
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)));
|
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
|
||||||
else
|
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);
|
throw JSONRPCError(RPC_WALLET_ERROR, strError);
|
||||||
}
|
}
|
||||||
if (!pwalletMain->CommitTransaction(wtxNew, reservekey))
|
if (!pwalletMain->CommitTransaction(wtxNew, reservekey))
|
||||||
|
@ -347,9 +350,9 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx&
|
||||||
|
|
||||||
Value sendtoaddress(const Array& params, bool fHelp)
|
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(
|
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"
|
"\nSend an amount to a given address. The amount is a real and is rounded to the nearest 0.00000001\n"
|
||||||
+ HelpRequiringPassphrase() +
|
+ HelpRequiringPassphrase() +
|
||||||
"\nArguments:\n"
|
"\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"
|
"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"
|
" to which you're sending the transaction. This is not part of the \n"
|
||||||
" transaction, just kept in your wallet.\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"
|
"\nResult:\n"
|
||||||
"\"transactionid\" (string) The transaction id.\n"
|
"\"transactionid\" (string) The transaction id.\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
+ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1")
|
+ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1")
|
||||||
+ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"donation\" \"seans outpost\"")
|
+ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"donation\" \"seans outpost\"")
|
||||||
|
+ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true")
|
||||||
+ HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"")
|
+ 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())
|
if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty())
|
||||||
wtx.mapValue["to"] = params[3].get_str();
|
wtx.mapValue["to"] = params[3].get_str();
|
||||||
|
|
||||||
|
bool fSubtractFeeFromAmount = false;
|
||||||
|
if (params.size() > 4)
|
||||||
|
fSubtractFeeFromAmount = params[4].get_bool();
|
||||||
|
|
||||||
EnsureWalletIsUnlocked();
|
EnsureWalletIsUnlocked();
|
||||||
|
|
||||||
SendMoney(address.Get(), nAmount, wtx);
|
SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, wtx);
|
||||||
|
|
||||||
return wtx.GetHash().GetHex();
|
return wtx.GetHash().GetHex();
|
||||||
}
|
}
|
||||||
|
@ -840,7 +850,7 @@ Value sendfrom(const Array& params, bool fHelp)
|
||||||
if (nAmount > nBalance)
|
if (nAmount > nBalance)
|
||||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
|
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();
|
return wtx.GetHash().GetHex();
|
||||||
}
|
}
|
||||||
|
@ -848,9 +858,9 @@ Value sendfrom(const Array& params, bool fHelp)
|
||||||
|
|
||||||
Value sendmany(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(
|
throw runtime_error(
|
||||||
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" )\n"
|
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] )\n"
|
||||||
"\nSend multiple times. Amounts are double-precision floating point numbers."
|
"\nSend multiple times. Amounts are double-precision floating point numbers."
|
||||||
+ HelpRequiringPassphrase() + "\n"
|
+ HelpRequiringPassphrase() + "\n"
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
|
@ -862,6 +872,14 @@ Value sendmany(const Array& params, bool fHelp)
|
||||||
" }\n"
|
" }\n"
|
||||||
"3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\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"
|
"4. \"comment\" (string, optional) A comment\n"
|
||||||
|
"5. subtractfeefromamount (string, optional) A json array with addresses.\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"
|
||||||
|
" If no addresses are specified here, the sender pays the fee.\n"
|
||||||
|
" [\n"
|
||||||
|
" \"address\" (string) Subtract fee from this address\n"
|
||||||
|
" ,...\n"
|
||||||
|
" ]\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
"\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
|
"\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
|
||||||
" the number of addresses.\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}\"") +
|
+ HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") +
|
||||||
"\nSend two amounts to two different addresses setting the confirmation and comment:\n"
|
"\nSend two amounts to two different addresses setting the confirmation and comment:\n"
|
||||||
+ HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") +
|
+ 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\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") +
|
||||||
"\nAs a json rpc call\n"
|
"\nAs a json rpc call\n"
|
||||||
+ HelpExampleRpc("sendmany", "\"\", \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\", 6, \"testing\"")
|
+ 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())
|
if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty())
|
||||||
wtx.mapValue["comment"] = params[3].get_str();
|
wtx.mapValue["comment"] = params[3].get_str();
|
||||||
|
|
||||||
|
Array subtractFeeFromAmount;
|
||||||
|
if (params.size() > 4)
|
||||||
|
subtractFeeFromAmount = params[4].get_array();
|
||||||
|
|
||||||
set<CBitcoinAddress> setAddress;
|
set<CBitcoinAddress> setAddress;
|
||||||
vector<pair<CScript, CAmount> > vecSend;
|
vector<CRecipient> vecSend;
|
||||||
|
|
||||||
CAmount totalAmount = 0;
|
CAmount totalAmount = 0;
|
||||||
BOOST_FOREACH(const Pair& s, sendTo)
|
BOOST_FOREACH(const Pair& s, sendTo)
|
||||||
|
@ -905,7 +929,13 @@ Value sendmany(const Array& params, bool fHelp)
|
||||||
CAmount nAmount = AmountFromValue(s.value_);
|
CAmount nAmount = AmountFromValue(s.value_);
|
||||||
totalAmount += nAmount;
|
totalAmount += nAmount;
|
||||||
|
|
||||||
vecSend.push_back(make_pair(scriptPubKey, nAmount));
|
bool fSubtractFeeFromAmount = false;
|
||||||
|
BOOST_FOREACH(const Value& addr, subtractFeeFromAmount)
|
||||||
|
if (addr.get_str() == s.name_)
|
||||||
|
fSubtractFeeFromAmount = true;
|
||||||
|
|
||||||
|
CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount};
|
||||||
|
vecSend.push_back(recipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureWalletIsUnlocked();
|
EnsureWalletIsUnlocked();
|
||||||
|
@ -918,8 +948,9 @@ Value sendmany(const Array& params, bool fHelp)
|
||||||
// Send
|
// Send
|
||||||
CReserveKey keyChange(pwalletMain);
|
CReserveKey keyChange(pwalletMain);
|
||||||
CAmount nFeeRequired = 0;
|
CAmount nFeeRequired = 0;
|
||||||
|
int nChangePosRet = -1;
|
||||||
string strFailReason;
|
string strFailReason;
|
||||||
bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason);
|
bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason);
|
||||||
if (!fCreated)
|
if (!fCreated)
|
||||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
|
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
|
||||||
if (!pwalletMain->CommitTransaction(wtx, keyChange))
|
if (!pwalletMain->CommitTransaction(wtx, keyChange))
|
||||||
|
|
|
@ -1549,21 +1549,22 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
|
||||||
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet)));
|
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend,
|
||||||
|
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl)
|
||||||
|
|
||||||
bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
|
|
||||||
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl)
|
|
||||||
{
|
{
|
||||||
CAmount nValue = 0;
|
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");
|
strFailReason = _("Transaction amounts must be positive");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
nValue += s.second;
|
nValue += recipient.nAmount;
|
||||||
|
|
||||||
|
if (recipient.fSubtractFeeFromAmount)
|
||||||
|
nSubtractFeeFromAmount++;
|
||||||
}
|
}
|
||||||
if (vecSend.empty() || nValue < 0)
|
if (vecSend.empty() || nValue < 0)
|
||||||
{
|
{
|
||||||
|
@ -1606,15 +1607,39 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
|
||||||
txNew.vin.clear();
|
txNew.vin.clear();
|
||||||
txNew.vout.clear();
|
txNew.vout.clear();
|
||||||
wtxNew.fFromMe = true;
|
wtxNew.fFromMe = true;
|
||||||
|
nChangePosRet = -1;
|
||||||
|
bool fFirst = true;
|
||||||
|
|
||||||
CAmount nTotalValue = nValue + nFeeRet;
|
CAmount nTotalValue = nValue;
|
||||||
|
if (nSubtractFeeFromAmount == 0)
|
||||||
|
nTotalValue += nFeeRet;
|
||||||
double dPriority = 0;
|
double dPriority = 0;
|
||||||
// vouts to the payees
|
// 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))
|
if (txout.IsDust(::minRelayTxFee))
|
||||||
{
|
{
|
||||||
|
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");
|
strFailReason = _("Transaction amount too small");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1642,7 +1667,9 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
|
||||||
dPriority += (double)nCredit * age;
|
dPriority += (double)nCredit * age;
|
||||||
}
|
}
|
||||||
|
|
||||||
CAmount nChange = nValueIn - nValue - nFeeRet;
|
CAmount nChange = nValueIn - nValue;
|
||||||
|
if (nSubtractFeeFromAmount == 0)
|
||||||
|
nChange -= nFeeRet;
|
||||||
|
|
||||||
if (nChange > 0)
|
if (nChange > 0)
|
||||||
{
|
{
|
||||||
|
@ -1676,6 +1703,28 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
|
||||||
|
|
||||||
CTxOut newTxOut(nChange, scriptChange);
|
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
|
// Never create dust outputs; if we would, just
|
||||||
// add the dust to the fee.
|
// add the dust to the fee.
|
||||||
if (newTxOut.IsDust(::minRelayTxFee))
|
if (newTxOut.IsDust(::minRelayTxFee))
|
||||||
|
@ -1686,7 +1735,8 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Insert change txn at random position:
|
// 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);
|
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,
|
return true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
12
src/wallet.h
12
src/wallet.h
|
@ -103,6 +103,12 @@ public:
|
||||||
StringMap destdata;
|
StringMap destdata;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CRecipient
|
||||||
|
{
|
||||||
|
CScript scriptPubKey;
|
||||||
|
CAmount nAmount;
|
||||||
|
bool fSubtractFeeFromAmount;
|
||||||
|
};
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> mapValue_t;
|
typedef std::map<std::string, std::string> mapValue_t;
|
||||||
|
|
||||||
|
@ -611,10 +617,8 @@ public:
|
||||||
CAmount GetWatchOnlyBalance() const;
|
CAmount GetWatchOnlyBalance() const;
|
||||||
CAmount GetUnconfirmedWatchOnlyBalance() const;
|
CAmount GetUnconfirmedWatchOnlyBalance() const;
|
||||||
CAmount GetImmatureWatchOnlyBalance() const;
|
CAmount GetImmatureWatchOnlyBalance() const;
|
||||||
bool CreateTransaction(const std::vector<std::pair<CScript, CAmount> >& vecSend,
|
bool CreateTransaction(const std::vector<CRecipient>& vecSend,
|
||||||
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL);
|
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, 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 CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
|
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
|
||||||
|
|
||||||
static CFeeRate minTxFee;
|
static CFeeRate minTxFee;
|
||||||
|
|
Loading…
Reference in New Issue