Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate spend from spark to EX-address in Qt #1448

Merged
merged 3 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading