1 #include <qt/splitutxopage.h>
2 #include <qt/forms/ui_splitutxopage.h>
3 
4 #include <qt/bitcoinunits.h>
5 #include <qt/execrpccommand.h>
6 #include <qt/guiutil.h>
7 #include <qt/optionsmodel.h>
8 #include <qt/sendcoinsdialog.h>
9 #include <qt/walletmodel.h>
10 #include <validation.h>
11 
12 namespace SplitUTXO_NS
13 {
14 static const QString PRC_COMMAND = "splitutxosforaddress";
15 static const QString PARAM_ADDRESS = "address";
16 static const QString PARAM_MIN_VALUE = "minValue";
17 static const QString PARAM_MAX_VALUE = "maxValue";
18 static const QString PARAM_MAX_OUTPUTS = "maxOutputs";
19 }
20 using namespace SplitUTXO_NS;
21 
SplitUTXOPage(QWidget * parent,Mode mode)22 SplitUTXOPage::SplitUTXOPage(QWidget *parent, Mode mode) :
23     QDialog(parent),
24     ui(new Ui::SplitUTXOPage),
25     m_model(nullptr),
26     m_mode(mode)
27 {
28     ui->setupUi(this);
29 
30     switch (m_mode) {
31     case Normal:
32         setWindowTitle(tr("Split coins for address"));
33         ui->labelAddress->setText(tr("Address"));
34         break;
35 
36     case Delegation:
37         setWindowTitle(tr("Split coins for offline staking"));
38         ui->labelAddress->setText(tr("Delegate address"));
39         ui->labelDescription->setText(tr("Split coins for offline staking."));
40         break;
41 
42     case SuperStaker:
43         setWindowTitle(tr("Split coins for super staker"));
44         ui->labelAddress->setText(tr("Staker address"));
45         ui->labelDescription->setText(tr("Split coins for super staker. The UTXO value need to be minimum <b> %1 </b>.").
46                                       arg(BitcoinUnits::formatHtmlWithUnit(BitcoinUnits::BTC, DEFAULT_STAKING_MIN_UTXO_VALUE)));
47         ui->lineEditMinValue->SetMinValue(DEFAULT_STAKING_MIN_UTXO_VALUE);
48         ui->lineEditMaxValue->SetMinValue(DEFAULT_STAKING_MIN_UTXO_VALUE);
49         break;
50     }
51 
52     ui->labelAddress->setToolTip(tr("The qtum address to split utxos."));
53     ui->labelMinValue->setToolTip(tr("Select utxo which value is smaller than value (minimum 0.1 COIN)."));
54     ui->labelMaxValue->setToolTip(tr("Select utxo which value is greater than value (minimum 0.1 COIN)."));
55     ui->labelMaxOutputs->setToolTip(tr("Maximum outputs to create"));
56 
57     ui->lineEditAddress->setSenderAddress(true);
58     ui->txtAddress->setReadOnly(true);
59     ui->txtAddress->setVisible(false);
60 
61     QFont font = QApplication::font();
62     font.setPointSizeF(font.pointSizeF() * 0.8);
63     ui->labelDescription->setFont(font);
64 
65     // Set defaults
66     ui->lineEditMinValue->setValue(DEFAULT_STAKING_MIN_UTXO_VALUE);
67     ui->lineEditMaxValue->setValue(DEFAULT_STAKING_MIN_UTXO_VALUE * 2);
68     ui->spinBoxMaxOutputs->setMinimum(1);
69     ui->spinBoxMaxOutputs->setMaximum(10000);
70     ui->spinBoxMaxOutputs->setValue(100);
71 
72     ui->splitCoinsButton->setEnabled(false);
73 
74     // Create new PRC command line interface
75     QStringList lstMandatory;
76     lstMandatory.append(PARAM_ADDRESS);
77     lstMandatory.append(PARAM_MIN_VALUE);
78     lstMandatory.append(PARAM_MAX_VALUE);
79 
80     QStringList lstOptional;
81     lstOptional.append(PARAM_MAX_OUTPUTS);
82 
83     QMap<QString, QString> lstTranslations;
84     lstTranslations[PARAM_ADDRESS] = ui->labelAddress->text();
85     lstTranslations[PARAM_MIN_VALUE] = ui->labelMinValue->text();
86     lstTranslations[PARAM_MAX_VALUE] = ui->labelMaxValue->text();
87     lstTranslations[PARAM_MAX_OUTPUTS] = ui->labelMaxOutputs->text();
88 
89     m_execRPCCommand = new ExecRPCCommand(PRC_COMMAND, lstMandatory, lstOptional, lstTranslations, this);
90 
91     connect(ui->splitCoinsButton, &QPushButton::clicked, this, &SplitUTXOPage::on_splitCoinsClicked);
92     connect(ui->cancelButton, &QPushButton::clicked, this, &SplitUTXOPage::on_cancelButtonClicked);
93     connect(ui->lineEditAddress, &QComboBox::currentTextChanged, this, &SplitUTXOPage::on_updateSplitCoinsButton);
94 }
95 
~SplitUTXOPage()96 SplitUTXOPage::~SplitUTXOPage()
97 {
98     delete ui;
99 }
100 
setModel(WalletModel * _model)101 void SplitUTXOPage::setModel(WalletModel *_model)
102 {
103     m_model = _model;
104     ui->lineEditAddress->setWalletModel(m_model);
105 
106     if (m_model && m_model->getOptionsModel())
107         connect(m_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SplitUTXOPage::updateDisplayUnit);
108 
109     // update the display unit, to not use the default ("QTUM")
110     updateDisplayUnit();
111 }
112 
setAddress(const QString & address)113 void SplitUTXOPage::setAddress(const QString &address)
114 {
115     ui->lineEditAddress->setVisible(false);
116     ui->txtAddress->setVisible(true);
117     ui->txtAddress->setText(address);
118 
119     if(m_mode == Normal)
120         setWindowTitle(tr("Split coins for address %1").arg(address));
121 
122     ui->splitCoinsButton->setEnabled(true);
123 }
124 
isDataValid()125 bool SplitUTXOPage::isDataValid()
126 {
127     bool dataValid = true;
128     ui->lineEditMinValue->setValid(true);
129     ui->lineEditMaxValue->setValid(true);
130 
131     CAmount minValue = ui->lineEditMinValue->value();
132     CAmount maxValue = ui->lineEditMaxValue->value();
133 
134     if(ui->lineEditAddress->isVisible() && !ui->lineEditAddress->isValidAddress())
135         dataValid = false;
136     if(minValue < COIN/10)
137     {
138         ui->lineEditMinValue->setValid(false);
139         dataValid = false;
140     }
141     if(maxValue < COIN/10)
142     {
143         ui->lineEditMaxValue->setValid(false);
144         dataValid = false;
145     }
146     if(minValue > COIN/10 && maxValue > COIN/10 && minValue > maxValue)
147     {
148         ui->lineEditMinValue->setValid(false);
149         ui->lineEditMaxValue->setValid(false);
150         dataValid = false;
151     }
152 
153     return dataValid;
154 }
155 
clearAll()156 void SplitUTXOPage::clearAll()
157 {
158     if(ui->lineEditAddress->isVisible())
159     {
160         ui->lineEditAddress->setCurrentIndex(-1);
161         ui->splitCoinsButton->setEnabled(false);
162     }
163     ui->lineEditMinValue->setValue(DEFAULT_STAKING_MIN_UTXO_VALUE);
164     ui->lineEditMaxValue->setValue(DEFAULT_STAKING_MIN_UTXO_VALUE * 2);
165     ui->spinBoxMaxOutputs->setValue(100);
166 }
167 
accept()168 void SplitUTXOPage::accept()
169 {
170     clearAll();
171     QDialog::accept();
172 }
173 
reject()174 void SplitUTXOPage::reject()
175 {
176     clearAll();
177     QDialog::reject();
178 }
179 
updateDisplayUnit()180 void SplitUTXOPage::updateDisplayUnit()
181 {
182     if(m_model && m_model->getOptionsModel())
183     {
184         // Update min and max value with the current unit
185         ui->lineEditMinValue->setDisplayUnit(m_model->getOptionsModel()->getDisplayUnit());
186         ui->lineEditMaxValue->setDisplayUnit(m_model->getOptionsModel()->getDisplayUnit());
187     }
188 }
189 
on_splitCoinsClicked()190 void SplitUTXOPage::on_splitCoinsClicked()
191 {
192     if(m_model && m_model->getOptionsModel())
193     {
194         if(!isDataValid())
195             return;
196 
197         // Initialize variables
198         QMap<QString, QString> lstParams;
199         QVariant result;
200         QString errorMessage;
201         QString resultJson;
202         int unit = BitcoinUnits::BTC;
203         QString address = ui->lineEditAddress->isVisible() ? ui->lineEditAddress->currentText() : ui->txtAddress->text();
204         CAmount minValue = ui->lineEditMinValue->value();
205         CAmount maxValue = ui->lineEditMaxValue->value();
206         CAmount maxOutputs = ui->spinBoxMaxOutputs->value();
207 
208         // Append params to the list
209         ExecRPCCommand::appendParam(lstParams, PARAM_ADDRESS, address);
210         ExecRPCCommand::appendParam(lstParams, PARAM_MIN_VALUE, BitcoinUnits::format(unit, minValue, false, BitcoinUnits::separatorNever));
211         ExecRPCCommand::appendParam(lstParams, PARAM_MAX_VALUE, BitcoinUnits::format(unit, maxValue, false, BitcoinUnits::separatorNever));
212         ExecRPCCommand::appendParam(lstParams, PARAM_MAX_OUTPUTS, QString::number(maxOutputs));
213 
214         QString questionString = tr("Are you sure you want to split coins for address");
215         questionString.append(QString("<br/><br/><b>%1</b>?")
216                               .arg(address));
217 
218         SendConfirmationDialog confirmationDialog(tr("Confirm splitting coins for address."), questionString, "", "", SEND_CONFIRM_DELAY, tr("Send"), this);
219         confirmationDialog.exec();
220 
221         QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result();
222         if(retval == QMessageBox::Yes)
223         {
224             if(!m_execRPCCommand->exec(m_model->node(), m_model, lstParams, result, resultJson, errorMessage))
225             {
226                 QMessageBox::warning(this, tr("Split coins for address"), errorMessage);
227             }
228             else
229             {
230                  QVariantMap variantMap = result.toMap();
231 
232                  QString selectedString = variantMap.value("selected").toString();
233                  CAmount selected;
234                  BitcoinUnits::parse(unit, selectedString, &selected);
235 
236                  QString splitedString = variantMap.value("splited").toString();
237                  CAmount splited;
238                  BitcoinUnits::parse(unit, splitedString, &splited);
239 
240                  int displayUnit = m_model->getOptionsModel()->getDisplayUnit();
241 
242                  QString infoString = tr("Selected: %1 less than %2 and above of %3.").
243                          arg(BitcoinUnits::formatHtmlWithUnit(displayUnit, selected)).
244                          arg(BitcoinUnits::formatHtmlWithUnit(displayUnit, minValue)).
245                          arg(BitcoinUnits::formatHtmlWithUnit(displayUnit, maxValue));
246                  infoString.append("<br/><br/>");
247                  infoString.append(tr("Splitted: %1.").arg(BitcoinUnits::formatHtmlWithUnit(displayUnit, splited)));
248 
249                  QMessageBox::information(this, tr("Split coins for address"), infoString);
250 
251                  if(splited == selected || splited == 0)
252                      accept();
253             }
254         }
255     }
256 }
257 
on_cancelButtonClicked()258 void SplitUTXOPage::on_cancelButtonClicked()
259 {
260     reject();
261 }
262 
on_updateSplitCoinsButton()263 void SplitUTXOPage::on_updateSplitCoinsButton()
264 {
265     bool enabled = true;
266     bool validAddress = !ui->lineEditAddress->isVisible() || (ui->lineEditAddress->isVisible() && !ui->lineEditAddress->currentText().isEmpty());
267 
268     if(!validAddress)
269     {
270         enabled = false;
271     }
272 
273     ui->splitCoinsButton->setEnabled(enabled);
274 }
275