Skip to content

Commit

Permalink
Automate spend from spark to EX-address in Qt (#1448)
Browse files Browse the repository at this point in the history
* Automation of sending private balance to an exchange address

* Fixed fee calculation

* Changed wording of messages
  • Loading branch information
psolstice authored Jun 11, 2024
1 parent 6f5267c commit 7ddce40
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 6 deletions.
136 changes: 131 additions & 5 deletions src/qt/sendcoinsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@ void SendCoinsDialog::on_sendButton_clicked()
}

// prepare transaction for getting txFee earlier
WalletModelTransaction currentTransaction(recipients);
std::vector<WalletModelTransaction> transactions;
WalletModel::SendCoinsReturn prepareStatus;
std::vector<std::pair<CWalletTx, CAmount>> wtxAndFees;
Expand All @@ -310,13 +309,68 @@ void SendCoinsDialog::on_sendButton_clicked()
ctrl.nConfirmTarget = 0;

int sparkAddressCount = 0;
int exchangeAddressCount = 0;
for(int i = 0; i < recipients.size(); ++i){
bool check = model->validateSparkAddress(recipients[i].address);
if(check) {
if (model->validateSparkAddress(recipients[i].address))
sparkAddressCount++;
if (model->validateExchangeAddress(recipients[i].address))
exchangeAddressCount++;
}

bool fGoThroughTransparentAddress = false;
__decltype(recipients) exchangeRecipients;
CScript intermediateAddressScript;
CAmount extraFee = 0;

if (fAnonymousMode && exchangeAddressCount > 0) {
CAmount exchangeAddressAmount = 0;
// if the transaction is performed in two stages through the intermediate address we need to calculate the size of the second transaction
uint32_t secondTxSize = 8 /*CTransaction: nVersion, nLockTime*/ + 1 /*vinSize*/ + 148 /*vin[0]*/ + 20 /*safety*/ + 1 /*voutSize*/;

fGoThroughTransparentAddress = true;

// remove exchange addresses from recipients array and add them to exchangeRecipients array
for(int i = 0; i < recipients.size(); ){
if (model->validateExchangeAddress(recipients[i].address)) {
exchangeAddressAmount += recipients[i].amount;
// we use different fee calculation system and therefore can't reliably do the calculation
// of fee for the second transaction if some of recipients have this flag set
recipients[i].fSubtractFeeFromAmount = false;
exchangeRecipients.push_back(recipients[i]);

secondTxSize += 8 /*amount*/ + 1 /*scriptSize*/ + 26 /*scriptPubKey*/;

recipients.erase(recipients.begin() + i);
}
else {
++i;
}
}

LOCK2(cs_main, pwalletMain->cs_wallet);
// create a new transparent address and add it to the recipients array
if (!pwalletMain->IsLocked()) {
pwalletMain->TopUpKeyPool();
}
CPubKey newKey;
if (!pwalletMain->GetKeyFromPool(newKey)) {
fNewRecipientAllowed = true;
return;
}
pwalletMain->SetAddressBook(newKey.GetID(), "", "receive");
intermediateAddressScript = GetScriptForDestination(newKey.GetID());

extraFee = CWallet::GetMinimumFee(secondTxSize, 0, mempool);

SendCoinsRecipient newRecipient;
newRecipient.address = CBitcoinAddress(newKey.GetID()).ToString().c_str();
newRecipient.amount = exchangeAddressAmount + extraFee;
newRecipient.fSubtractFeeFromAmount = false;
recipients.push_back(newRecipient);
}

WalletModelTransaction currentTransaction(recipients);

CAmount mintSparkAmount = 0;
CAmount txFee = 0;
CAmount totalAmount = 0;
Expand Down Expand Up @@ -364,6 +418,14 @@ void SendCoinsDialog::on_sendButton_clicked()
return;
}

// If the transaction is performed in two stages through the intermediate address we need to show the real
// recipients (for informational purposes), replacing the intermediate transparent address with the exchange address(es)
__decltype(recipients) realRecipients = recipients;
if (fGoThroughTransparentAddress) {
realRecipients.erase(realRecipients.end() - 1);
realRecipients.append(exchangeRecipients);
}

// Format confirmation message
QStringList formatted;
if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount) && spark::IsSparkAllowed())
Expand Down Expand Up @@ -407,7 +469,7 @@ void SendCoinsDialog::on_sendButton_clicked()
formatted.append(recipientElement);
}
} else if ((fAnonymousMode == true) && (recipients.size() == 1) && spark::IsSparkAllowed()) {
for (auto &rcp : recipients)
for (auto &rcp : realRecipients)
{
// generate bold amount string
CAmount namount = rcp.amount;
Expand All @@ -434,7 +496,7 @@ void SendCoinsDialog::on_sendButton_clicked()
formatted.append(recipientElement);
}
} else {
for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients())
for (auto &rcp : realRecipients)
{
// generate bold amount string
QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
Expand All @@ -459,6 +521,14 @@ void SendCoinsDialog::on_sendButton_clicked()
formatted.append(recipientElement);
}
}

if (fGoThroughTransparentAddress) {
QString transparentAddress = "<span style='font-family: monospace;'>" + recipients[recipients.size()-1].address + "</span>";
formatted.append("<br />");
formatted.append(tr("EX-addresses can only receive FIRO from transparent addresses.<br /><br />"
"Your FIRO will go from Spark to a newly generated transparent address %1 and then immediately be sent to the EX-address.").arg(transparentAddress));
}

QString questionString = tr("Are you sure you want to send?");
questionString.append("<br /><br />%1");
double txSize;
Expand All @@ -484,6 +554,15 @@ void SendCoinsDialog::on_sendButton_clicked()

// append transaction size
questionString.append(" (" + QString::number(txSize / 1000) + " kB)");

if (fGoThroughTransparentAddress) {
QString feeString;
feeString.append("<span style='color:#aa0000;'>");
feeString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), extraFee));
feeString.append("</span>");

questionString.append(tr(". An additional transaction fee of %1 will apply to complete the send from the transparent address to the EX-address.").arg(feeString));
}
}

// add total amount in all subdivision units
Expand Down Expand Up @@ -556,6 +635,53 @@ void SendCoinsDialog::on_sendButton_clicked()
CoinControlDialog::coinControl->UnSelectAll();
coinControlUpdateLabels();
}

// Launch the second stage of the transaction if needed
if (fGoThroughTransparentAddress) {
// prepare the coin control so the transaction will use (by default) only the transparent address
// created in the first stage
COutPoint outpoint;
outpoint.hash = currentTransaction.getTransaction()->GetHash();
outpoint.n = UINT_MAX;

const auto &vout = currentTransaction.getTransaction()->tx->vout;
for (size_t i = 0; i < vout.size(); i++) {
if (vout[i].scriptPubKey == intermediateAddressScript) {
outpoint.n = i;
break;
}
}

if (outpoint.n == UINT_MAX) {
sendStatus.status = WalletModel::InvalidAddress;
sendStatus.reasonCommitFailed = "Intermediate address was not found in the transaction";
fNewRecipientAllowed = true;
return;
}

CCoinControl ctrl;
ctrl.fAllowOtherInputs = false;
ctrl.fNoChange = true;
ctrl.Select(outpoint);

WalletModelTransaction secondTransaction(exchangeRecipients);

prepareStatus = model->prepareTransaction(secondTransaction, &ctrl);

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

sendStatus = model->sendCoins(secondTransaction);
// process sendStatus and on error generate message shown to user
processSendCoinsReturn(sendStatus);
}

fNewRecipientAllowed = true;
}

Expand Down
6 changes: 6 additions & 0 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,12 @@ bool WalletModel::validateAddress(const QString &address)
return addressParsed.IsValid();
}

bool WalletModel::validateExchangeAddress(const QString &address)
{
CBitcoinAddress addressParsed(address.toStdString());
return addressParsed.IsValid() && addressParsed.Get().type() == typeid(CExchangeKeyID);
}

WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
{
CAmount total = 0;
Expand Down
1 change: 1 addition & 0 deletions src/qt/walletmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class WalletModel : public QObject

// Check address for validity
bool validateAddress(const QString &address);
bool validateExchangeAddress(const QString &address);
bool validateSparkAddress(const QString &address);
std::pair<CAmount, CAmount> getSparkBalance();

Expand Down
3 changes: 3 additions & 0 deletions src/wallet/coincontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class CCoinControl
{
public:
CTxDestination destChange;
//! If true, don't use any change
bool fNoChange;
//! If false, allows unselected inputs, but requires all selected inputs be used
bool fAllowOtherInputs;
//! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria
Expand All @@ -49,6 +51,7 @@ class CCoinControl
void SetNull()
{
destChange = CNoDestination();
fNoChange = false;
fAllowOtherInputs = false;
fRequireAllInputs = true;
fAllowWatchOnly = false;
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4450,7 +4450,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
const CAmount nChange = nValueIn - nValueToSelect;
CTxOut newTxOut;

if (nChange > 0)
if (nChange > 0 && !(coinControl && coinControl->fNoChange))
{
// Fill a vout to ourself
// TODO: pass in scriptChange instead of reservekey so
Expand Down

0 comments on commit 7ddce40

Please sign in to comment.