Browse Source

Merge pull request 'Beta-9' (#386) from tobtoht/feather:beta-9 into master

Reviewed-on: feather/feather#386
1.0.0-fixes
tobtoht 3 months ago
parent
commit
6aa7261ef4
  1. 2
      CMakeLists.txt
  2. 4
      Dockerfile.linux
  3. 10
      Dockerfile.windows
  4. 46
      contrib/generate-restore-heights/heights.py
  5. 8
      src/HistoryWidget.cpp
  6. 75
      src/MainWindow.cpp
  7. 1
      src/MainWindow.h
  8. 2
      src/ReceiveWidget.ui
  9. 10
      src/SettingsDialog.ui
  10. 4
      src/WindowManager.cpp
  11. 2
      src/WindowManager.h
  12. 1
      src/assets.qrc
  13. 1626
      src/assets/mnemonic_25_english.txt
  14. 12
      src/assets/nodes.json
  15. 116
      src/assets/restore_heights_monero_mainnet.txt
  16. 79
      src/assets/restore_heights_monero_stagenet.txt
  17. 9
      src/dialog/TxConfAdvDialog.cpp
  18. 3
      src/dialog/TxConfAdvDialog.h
  19. 51
      src/dialog/TxConfAdvDialog.ui
  20. 2
      src/dialog/TxConfDialog.cpp
  21. 12
      src/dialog/TxImportDialog.cpp
  22. 6
      src/dialog/ViewOnlyDialog.cpp
  23. 3
      src/dialog/WalletInfoDialog.ui
  24. 5
      src/libwalletqt/Wallet.cpp
  25. 3
      src/libwalletqt/Wallet.h
  26. 2
      src/main.cpp
  27. 17
      src/utils/Utils.cpp
  28. 2
      src/utils/config.cpp
  29. 9
      src/widgets/NodeWidget.cpp
  30. 51
      src/wizard/PageWalletRestoreSeed.cpp
  31. 7
      src/wizard/PageWalletRestoreSeed.h
  32. 12
      src/wizard/PageWalletRestoreSeed.ui
  33. 2
      src/wizard/WalletWizard.cpp
  34. 4
      src/wizard/WalletWizard.h

2
CMakeLists.txt

@ -32,7 +32,7 @@ if(DEBUG)
set(CMAKE_VERBOSE_MAKEFILE ON)
endif()
set(MONERO_HEAD "d0662146e12d9c0ac9905b4423bb27bd68a4444e")
set(MONERO_HEAD "d4257af2e7503fc6dc09fc704606230d353a0a02")
set(BUILD_GUI_DEPS ON)
option(ARCH "Target architecture" "x86-64")
set(BUILD_64 ON)

4
Dockerfile.linux

@ -28,9 +28,9 @@ RUN bash verify-packages.sh && \
# OpenSSL: Required for CMake, Qt 5.15.2, libwallet, Tor
ENV OPENSSL_ROOT_DIR=/usr/local/openssl/
RUN git clone -b OpenSSL_1_1_1k --depth 1 https://github.com/openssl/openssl.git && \
RUN git clone -b OpenSSL_1_1_1l --depth 1 https://github.com/openssl/openssl.git && \
cd openssl && \
git reset --hard fd78df59b0f656aefe96e39533130454aa957c00 && \
git reset --hard fb047ebc87b18bdc4cf9ddee9ee1f5ed93e56aff && \
./config no-shared no-dso --prefix=/usr/local/openssl && \
make -j$THREADS && \
make -j$THREADS install_sw && \

10
Dockerfile.windows

@ -131,11 +131,11 @@ RUN wget https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.16.tar.gz && \
rm -rf $(pwd)
# OpenSSL -> Tor
RUN wget https://www.openssl.org/source/openssl-1.1.1k.tar.gz && \
echo "892a0875b9872acd04a9fde79b1f943075d5ea162415de3047c327df33fbaee5 openssl-1.1.1k.tar.gz" | sha256sum -c && \
tar -xzf openssl-1.1.1k.tar.gz && \
rm openssl-1.1.1k.tar.gz && \
cd openssl-1.1.1k && \
RUN wget https://www.openssl.org/source/openssl-1.1.1l.tar.gz && \
echo "0b7a3e5e59c34827fe0c3a74b7ec8baef302b98fa80088d7f9153aa16fa76bd1 openssl-1.1.1l.tar.gz" | sha256sum -c && \
tar -xzf openssl-1.1.1l.tar.gz && \
rm openssl-1.1.1l.tar.gz && \
cd openssl-1.1.1l && \
./Configure mingw64 no-shared no-dso --cross-compile-prefix=x86_64-w64-mingw32- --prefix=/usr/local/openssl && \
make -j$THREADS && \
make -j$THREADS install_sw && \

46
contrib/generate-restore-heights/heights.py

@ -0,0 +1,46 @@
import requests
import argparse
parser = argparse.ArgumentParser(description='Generate restore height list.')
parser.add_argument('-P', '--port',
default='18081',
dest='port',
help='Daemon port',
type=int)
args = parser.parse_args()
DAEMON_ADDRESS = f"http://127.0.0.1:{args.port}"
def get_block_timestamp(height: int):
data = {
"jsonrpc": "2.0",
"id": "0",
"method": "get_block_header_by_height",
"params": {
"height": height
}
}
res = requests.get(DAEMON_ADDRESS+"/json_rpc", json=data)
return res.json()['result']['block_header']['timestamp']
def get_height():
data = {
"jsonrpc": "2.0",
"id": "0",
"method": "get_block_count"
}
res = requests.get(DAEMON_ADDRESS+"/json_rpc", json=data)
return res.json()['result']['count']
timestamps = {}
current_height = get_height()
timestamps[1] = get_block_timestamp(1)
for height in range(1500, current_height, 1500):
timestamps[height] = get_block_timestamp(height)
for val in timestamps:
print(f"{timestamps[val]}:{val}")

8
src/HistoryWidget.cpp

@ -84,7 +84,7 @@ void HistoryWidget::showContextMenu(const QPoint &point) {
bool unconfirmed = tx->isFailed() || tx->isPending();
if (unconfirmed && tx->direction() != TransactionInfo::Direction_In) {
menu.addAction(icons()->icon("info2.svg"), "Resend transaction", this, &HistoryWidget::onResendTransaction);
menu.addAction("Resend transaction", this, &HistoryWidget::onResendTransaction);
}
menu.addMenu(m_copyMenu);
@ -144,9 +144,9 @@ void HistoryWidget::createTxProof() {
auto *tx = ui->history->currentEntry();
if (!tx) return;
auto *dialog = new TxProofDialog(this, m_ctx, tx);
dialog->exec();
dialog->deleteLater();
TxProofDialog dialog{this, m_ctx, tx};
dialog.getTxKey();
dialog.exec();
}
void HistoryWidget::copy(copyField field) {

75
src/MainWindow.cpp

@ -236,7 +236,7 @@ void MainWindow::initMenu() {
connect(ui->actionViewOnly, &QAction::triggered, this, &MainWindow::showViewOnlyDialog);
// [Wallet] -> [Advanced]
connect(ui->actionStore_wallet, &QAction::triggered, [this]{m_ctx->wallet->store();});
connect(ui->actionStore_wallet, &QAction::triggered, this, &MainWindow::tryStoreWallet);
connect(ui->actionUpdate_balance, &QAction::triggered, [this]{m_ctx->updateBalance();});
connect(ui->actionRefresh_tabs, &QAction::triggered, [this]{m_ctx->refreshModels();});
connect(ui->actionRescan_spent, &QAction::triggered, this, &MainWindow::rescanSpent);
@ -530,6 +530,16 @@ void MainWindow::setStatusText(const QString &text, bool override, int timeout)
}
}
void MainWindow::tryStoreWallet() {
if (m_ctx->wallet->connectionStatus() == Wallet::ConnectionStatus::ConnectionStatus_Synchronizing) {
QMessageBox::warning(this, "Save wallet", "Unable to save wallet during synchronization.\n\n"
"Wait until synchronization is finished and try again.");
return;
}
m_ctx->wallet->store();
}
void MainWindow::onSynchronized() {
this->updateNetStats();
this->setStatusText("Synchronized");
@ -582,19 +592,17 @@ void MainWindow::onConnectionStatusChanged(int status)
}
void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address) {
auto tx_status = tx->status();
QString err{"Can't create transaction: "};
if (tx_status != PendingTransaction::Status_Ok){
if (tx->status() != PendingTransaction::Status_Ok) {
QString tx_err = tx->errorString();
qCritical() << tx_err;
if (m_ctx->wallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion)
err = QString("%1 Wrong daemon version: %2").arg(err).arg(tx_err);
err = QString("%1 Wrong node version: %2").arg(err).arg(tx_err);
else
err = QString("%1 %2").arg(err).arg(tx_err);
if (tx_err.contains("Daemon response did not include the requested real output")) {
if (tx_err.contains("Node response did not include the requested real output")) {
QString currentNode = m_ctx->nodes->connection().toAddress();
err += QString("\nYou are currently connected to: %1\n\n"
@ -605,42 +613,43 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
m_ctx->wallet->disposeTransaction(tx);
return;
}
else if (tx->txCount() == 0) {
err = QString("%1 %2").arg(err).arg("No unmixable outputs to sweep.");
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
m_ctx->wallet->disposeTransaction(tx);
return;
}
else {
const auto &description = m_ctx->tmpTxDescription;
// Show advanced dialog on multi-destination transactions
if (address.size() > 1) {
TxConfAdvDialog dialog_adv{m_ctx, description, this};
dialog_adv.setTransaction(tx);
dialog_adv.exec();
return;
}
m_ctx->addCacheTransaction(tx->txid()[0], tx->signedTxToHex(0));
TxConfDialog dialog{m_ctx, tx, address[0], description, this};
switch (dialog.exec()) {
case QDialog::Rejected:
{
if (!dialog.showAdvanced)
m_ctx->onCancelTransaction(tx, address);
break;
}
case QDialog::Accepted:
m_ctx->commitTransaction(tx);
break;
}
// Show advanced dialog on multi-destination transactions
if (address.size() > 1) {
TxConfAdvDialog dialog_adv{m_ctx, m_ctx->tmpTxDescription, this};
dialog_adv.setTransaction(tx);
dialog_adv.exec();
return;
}
if (dialog.showAdvanced) {
TxConfAdvDialog dialog_adv{m_ctx, description, this};
dialog_adv.setTransaction(tx);
dialog_adv.exec();
TxConfDialog dialog{m_ctx, tx, address[0], m_ctx->tmpTxDescription, this};
switch (dialog.exec()) {
case QDialog::Rejected:
{
if (!dialog.showAdvanced)
m_ctx->onCancelTransaction(tx, address);
break;
}
case QDialog::Accepted:
m_ctx->commitTransaction(tx);
break;
}
if (dialog.showAdvanced) {
TxConfAdvDialog dialog_adv{m_ctx, m_ctx->tmpTxDescription, this};
dialog_adv.setTransaction(tx);
dialog_adv.exec();
}
}
@ -716,7 +725,7 @@ void MainWindow::showConnectionStatusDialog() {
QString statusMsg;
switch(status){
case Wallet::ConnectionStatus_Disconnected:
statusMsg = "Wallet is disconnected from daemon.";
statusMsg = "Wallet is disconnected from node.";
break;
case Wallet::ConnectionStatus_Connecting: {
auto node = m_ctx->nodes->connection();
@ -724,7 +733,7 @@ void MainWindow::showConnectionStatusDialog() {
break;
}
case Wallet::ConnectionStatus_WrongVersion:
statusMsg = "Wallet is connected to incompatible daemon.";
statusMsg = "Wallet is connected to incompatible node.";
break;
case Wallet::ConnectionStatus_Synchronizing:
case Wallet::ConnectionStatus_Synchronized: {

1
src/MainWindow.h

@ -180,6 +180,7 @@ private slots:
void onUpdatesAvailable(const QJsonObject &updates);
void toggleSearchbar(bool enabled);
void onSetStatusText(const QString &text);
void tryStoreWallet();
private:
friend WindowManager;

2
src/ReceiveWidget.ui

@ -106,7 +106,7 @@
<item>
<widget class="QPushButton" name="btn_generateSubaddress">
<property name="text">
<string>Generate Subaddress</string>
<string>Create new address</string>
</property>
</widget>
</item>

10
src/SettingsDialog.ui

@ -413,6 +413,11 @@
<string>xmrchain.net</string>
</property>
</item>
<item>
<property name="text">
<string>melo.tools</string>
</property>
</item>
<item>
<property name="text">
<string>moneroblocks.info</string>
@ -423,6 +428,11 @@
<string>blockchair.com</string>
</property>
</item>
<item>
<property name="text">
<string>blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">

4
src/WindowManager.cpp

@ -190,7 +190,7 @@ void WindowManager::onWalletOpenPasswordRequired(bool invalidPassword, const QSt
case QDialog::Rejected:
{
m_openWalletTriedOnce = false;
this->showWizard(WalletWizard::Page_OpenWallet);
m_wizard->show();
return;
}
}
@ -212,7 +212,7 @@ bool WindowManager::autoOpenWallet() {
// ######################## WALLET CREATION ########################
void WindowManager::tryCreateWallet(FeatherSeed seed, const QString &path, const QString &password,
void WindowManager::tryCreateWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedLanguage,
const QString &seedOffset) {
if(Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);

2
src/WindowManager.h

@ -45,7 +45,7 @@ private slots:
void onWalletPassphraseNeeded(bool on_device);
private:
void tryCreateWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset);
void tryCreateWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedLanguage, const QString &seedOffset);
void tryCreateWalletFromDevice(const QString &path, const QString &password, const QString &deviceName, int restoreHeight);
void tryCreateWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight);

1
src/assets.qrc

@ -119,7 +119,6 @@
<file>assets/images/xmrig.ico</file>
<file>assets/images/xmrig.svg</file>
<file>assets/images/zoom.png</file>
<file>assets/mnemonic_25_english.txt</file>
<file>assets/restore_heights_monero_mainnet.txt</file>
<file>assets/restore_heights_monero_stagenet.txt</file>
</qresource>

1626
src/assets/mnemonic_25_english.txt
File diff suppressed because it is too large
View File

12
src/assets/nodes.json

@ -1,13 +1,12 @@
{
"mainnet": {
"tor": [
"monero26mmldsallmxok2kwamne4ve3mybvvn2yijsvss7ey63hc4yyd.onion:18081",
"mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081",
"moneroxmrxw44lku6qniyarpwgznpcwml4drq7vb24ppatlcg4kmxpqd.onion:18089",
"3hvpnd4xejtzcuowvru2wfjum5wjf7synigm44rrizr3k4v5vzam2bad.onion:18081",
"rbpgdckle3h3vi4wwwrh75usqtoc5r3alohy7yyx57isynvay63nacyd.onion:18089",
"6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081",
"56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089"
"56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089",
"melo7jwjspngus3xhbt5kxeqc4njhokyvh55jfmehplglgmb7a6rb6yd.onion:18081"
],
"clearnet": [
"node.melo.tools:18081",
@ -25,14 +24,17 @@
]
},
"testnet": {
"tor": [],
"tor": [
"melot6vncfusj5gfi73gaosea2mc572n3n7f7gof75zkwg6oibkhk3id.onion:28081"
],
"clearnet": [
"testnet.melo.tools:28081"
]
},
"stagenet": {
"tor": [
"ct36dsbe3oubpbebpxmiqz4uqk6zb6nhmkhoekileo4fts23rvuse2qd.onion:38081"
"ct36dsbe3oubpbebpxmiqz4uqk6zb6nhmkhoekileo4fts23rvuse2qd.onion:38081",
"melos2hbwolcavwbgytdwbmcx7t3uh2eu36p3vnbttibosnpzmtp6hqd.onion:38081"
],
"clearnet": [
"super.fast.node.xmr.pm:38089",

116
src/assets/restore_heights_monero_mainnet.txt

@ -1509,4 +1509,118 @@
1609151786:2262000
1609331787:2263500
1609513572:2265000
1609692129:2266500
1609692129:2266500
1609867599:2268000
1610047219:2269500
1610228186:2271000
1610407595:2272500
1610589258:2274000
1610772298:2275500
1610951384:2277000
1611133107:2278500
1611312080:2280000
1611490215:2281500
1611673294:2283000
1611853050:2284500
1612029885:2286000
1612206719:2287500
1612387526:2289000
1612569532:2290500
1612750081:2292000
1612930978:2293500
1613113853:2295000
1613289586:2296500
1613470569:2298000
1613650566:2299500
1613830056:2301000
1614009139:2302500
1614188174:2304000
1614364622:2305500
1614547318:2307000
1614725907:2308500
1614906615:2310000
1615094144:2311500
1615272359:2313000
1615451290:2314500
1615629691:2316000
1615805209:2317500
1615991354:2319000
1616169054:2320500
1616349055:2322000
1616525847:2323500
1616708533:2325000
1616893535:2326500
1617071845:2328000
1617249163:2329500
1617430317:2331000
1617607600:2332500
1617790288:2334000
1617973699:2335500
1618147282:2337000
1618329866:2338500
1618511460:2340000
1618685970:2341500
1618865550:2343000
1619048569:2344500
1619230022:2346000
1619407133:2347500
1619590095:2349000
1619769449:2350500
1619948697:2352000
1620131734:2353500
1620308138:2355000
1620490090:2356500
1620668363:2358000
1620849947:2359500
1621033633:2361000
1621207215:2362500
1621393360:2364000
1621567773:2365500
1621750649:2367000
1621933277:2368500
1622108971:2370000
1622288557:2371500
1622467139:2373000
1622651471:2374500
1622833171:2376000
1623011441:2377500
1623188483:2379000
1623370163:2380500
1623550839:2382000
1623728565:2383500
1623910801:2385000
1624094652:2386500
1624268730:2388000
1624454173:2389500
1624633102:2391000
1624813544:2392500
1624995509:2394000
1625174760:2395500
1625350875:2397000
1625530310:2398500
1625715884:2400000
1625890512:2401500
1626080398:2403000
1626251832:2404500
1626434873:2406000
1626616459:2407500
1626797788:2409000
1626976609:2410500
1627159689:2412000
1627336518:2413500
1627515996:2415000
1627696970:2416500
1627873363:2418000
1628056769:2419500
1628233063:2421000
1628413022:2422500
1628596304:2424000
1628777705:2425500
1628957276:2427000
1629135964:2428500
1629318052:2430000
1629495981:2431500
1629677343:2433000
1629854603:2434500
1630032622:2436000
1630217808:2437500

79
src/assets/restore_heights_monero_stagenet.txt

@ -528,4 +528,81 @@
1615573554:790500
1615731420:792000
1615933315:793500
1616104918:795000
1616104918:795000
1616280486:796500
1616483958:798000
1616677488:799500
1616864492:801000
1617011783:802500
1617243009:804000
1617434167:805500
1617609667:807000
1617795845:808500
1617993128:810000
1618181526:811500
1618362660:813000
1618537048:814500
1618700152:816000
1618891334:817500
1619058998:819000
1619232663:820500
1619427622:822000
1619628127:823500
1619863363:825000
1619998315:826500
1620216657:828000
1620416386:829500
1620602417:831000
1620784230:832500
1620977670:834000
1621176309:835500
1621352150:837000
1621529837:838500
1621715183:840000
1621891016:841500
1622068671:843000
1622236700:844500
1622393818:846000
1622572603:847500
1622767566:849000
1622941282:850500
1623107663:852000
1623352063:853500
1623502520:855000
1623696345:856500
1623903078:858000
1624067589:859500
1624277066:861000
1624466865:862500
1624643841:864000
1624818125:865500
1625006084:867000
1625182383:868500
1625348128:870000
1625542485:871500
1625690595:873000
1625875030:874500
1626049431:876000
1626230916:877500
1626454343:879000
1626627856:880500
1626839863:882000
1627022662:883500
1627206043:885000
1627390139:886500
1627549087:888000
1627727762:889500
1627910975:891000
1628095914:892500
1628304203:894000
1628485223:895500
1628636024:897000
1628850117:898500
1629035762:900000
1629206719:901500
1629395524:903000
1629579720:904500
1629754526:906000
1629907575:907500
1630095527:909000
1630287335:910500

9
src/dialog/TxConfAdvDialog.cpp

@ -19,6 +19,7 @@ TxConfAdvDialog::TxConfAdvDialog(QSharedPointer<AppContext> ctx, const QString &
, m_ctx(std::move(ctx))
, m_exportUnsignedMenu(new QMenu(this))
, m_exportSignedMenu(new QMenu(this))
, m_exportTxKeyMenu(new QMenu(this))
{
ui->setupUi(this);
@ -31,6 +32,9 @@ TxConfAdvDialog::TxConfAdvDialog(QSharedPointer<AppContext> ctx, const QString &
m_exportSignedMenu->addAction("Save to file", this, &TxConfAdvDialog::signedSaveFile);
ui->btn_exportSigned->setMenu(m_exportSignedMenu);
m_exportTxKeyMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::txKeyCopy);
ui->btn_exportTxKey->setMenu(m_exportTxKeyMenu);
if (m_ctx->wallet->viewOnly()) {
ui->btn_exportSigned->hide();
ui->btn_send->hide();
@ -80,6 +84,7 @@ void TxConfAdvDialog::setUnsignedTransaction(UnsignedTransaction *utx) {
ui->btn_exportUnsigned->hide();
ui->btn_exportSigned->hide();
ui->btn_exportTxKey->hide();
ui->btn_sign->show();
ui->btn_send->hide();
@ -166,6 +171,10 @@ void TxConfAdvDialog::signedCopy() {
Utils::copyToClipboard(m_tx->signedTxToHex(0));
}
void TxConfAdvDialog::txKeyCopy() {
Utils::copyToClipboard(m_tx->transaction(0)->txKey());
}
void TxConfAdvDialog::signedQrCode() {
}

3
src/dialog/TxConfAdvDialog.h

@ -41,12 +41,15 @@ private:
void signedQrCode();
void signedSaveFile();
void txKeyCopy();
QScopedPointer<Ui::TxConfAdvDialog> ui;
QSharedPointer<AppContext> m_ctx;
PendingTransaction *m_tx = nullptr;
UnsignedTransaction *m_utx = nullptr;
QMenu *m_exportUnsignedMenu;
QMenu *m_exportSignedMenu;
QMenu *m_exportTxKeyMenu;
};
#endif //FEATHER_TXCONFADVDIALOG_H

51
src/dialog/TxConfAdvDialog.ui

@ -21,19 +21,30 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txid">
<property name="text">
<string>txid</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLineEdit" name="txid">
<property name="text">
<string>txid</string>
</property>
<property name="readOnly">
<bool>true</bool>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
@ -251,6 +262,16 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btn_exportTxKey">
<property name="text">
<string>Export tx key</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
@ -272,16 +293,16 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_send">
<widget class="QPushButton" name="btn_close">
<property name="text">
<string>Send</string>
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_close">
<widget class="QPushButton" name="btn_send">
<property name="text">
<string>Close</string>
<string>Send</string>
</property>
</widget>
</item>

2
src/dialog/TxConfDialog.cpp

@ -72,12 +72,10 @@ TxConfDialog::TxConfDialog(QSharedPointer<AppContext> ctx, PendingTransaction *t
ui->label_address->setToolTip("Wallet change/primary address");
}
ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Send");
connect(ui->btn_Advanced, &QPushButton::clicked, this, &TxConfDialog::setShowAdvanced);
m_ctx->addCacheTransaction(tx->txid()[0], tx->signedTxToHex(0)); // Todo: Iterate over all txs
this->adjustSize();
}

12
src/dialog/TxImportDialog.cpp

@ -35,6 +35,14 @@ TxImportDialog::TxImportDialog(QWidget *parent, QSharedPointer<AppContext> ctx)
void TxImportDialog::loadTx() {
QString txid = ui->line_txid->text();
if (m_ctx->wallet->haveTransaction(txid)) {
QMessageBox::warning(this, "Warning", "This transaction already exists in the wallet. "
"If you can't find it in your history, "
"check if it belongs to a different account (Wallet -> Account)");
return;
}
FeatherNode node = m_ctx->nodes->connection();
if (node.isLocal()) {
@ -94,6 +102,10 @@ void TxImportDialog::onImport() {
bool double_spend_seen = tx.value("double_spend_seen").toBool();
if (m_ctx->wallet->importTransaction(tx.value("tx_hash").toString(), output_indices, height, timestamp, false, pool, double_spend_seen)) {
if (!m_ctx->wallet->haveTransaction(txid)) {
QMessageBox::warning(this, "Import transaction", "This transaction does not belong to this wallet.");
return;
}
QMessageBox::information(this, "Import transaction", "Transaction imported successfully.");
} else {
QMessageBox::warning(this, "Import transaction", "Transaction import failed.");

6
src/dialog/ViewOnlyDialog.cpp

@ -22,7 +22,11 @@ ViewOnlyDialog::ViewOnlyDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
connect(ui->btn_Copy, &QPushButton::clicked, this, &ViewOnlyDialog::copyToClipboad);
connect(ui->btn_Save, &QPushButton::clicked, this, &ViewOnlyDialog::onWriteViewOnlyWallet);
ui->btn_Save->setEnabled(!m_ctx->wallet->viewOnly());
if (m_ctx->wallet->viewOnly()) {
ui->btn_Save->setEnabled(false);
ui->btn_Save->setToolTip("Wallet is already view-only");
}
this->adjustSize();
}

3
src/dialog/WalletInfoDialog.ui

@ -130,6 +130,9 @@
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>

5
src/libwalletqt/Wallet.cpp

@ -601,6 +601,11 @@ QString Wallet::printScannedPoolTxs()
return QString::fromStdString(m_walletImpl->printScannedPoolTxs());
}
bool Wallet::haveTransaction(const QString &txid)
{
return m_walletImpl->haveTransaction(txid.toStdString());
}
void Wallet::startRefresh()
{
m_refreshEnabled = true;

3
src/libwalletqt/Wallet.h

@ -246,6 +246,9 @@ public:
QString printAddressBook();
QString printScannedPoolTxs();
//! does wallet have txid
bool haveTransaction(const QString &txid);
//! refreshes the wallet
bool refresh(bool historyAndSubaddresses = false);

2
src/main.cpp

@ -139,7 +139,7 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
}
// Setup logging
QString logPath = QString("%1/daemon.log").arg(configDir);
QString logPath = QString("%1/libwallet.log").arg(configDir);
Monero::Utils::onStartup();
Monero::Wallet::init("", "feather", logPath.toStdString(), true);

17
src/utils/Utils.cpp

@ -100,7 +100,7 @@ bool dirExists(const QString &path) {
QString defaultWalletDir() {
QString portablePath = QCoreApplication::applicationDirPath().append("/%1");
if (QFile::exists(portablePath.arg(".portable"))) {
if (QFile::exists(portablePath.arg(".portable")) || QFile::exists(portablePath.arg(".portable.txt"))) {
return portablePath.arg("feather_data/wallets");
}
@ -357,6 +357,21 @@ QString blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettyp
return QString("https://blockchair.com/monero/transaction/%1").arg(txid);
}
}
else if (blockExplorer == "melo.tools") {
switch (nettype) {
case NetworkType::MAINNET:
return QString("https://melo.tools/explorer/mainnet/tx/%1").arg(txid);
case NetworkType::STAGENET:
return QString("https://melo.tools/explorer/stagenet/tx/%1").arg(txid);
case NetworkType::TESTNET:
return QString("https://melo.tools/explorer/testnet/tx/%1").arg(txid);
}
}
else if (blockExplorer == "blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion") {
if (nettype == NetworkType::MAINNET) {
return QString("http://blkchairbknpn73cfjhevhla7rkp4ed5gg2knctvv7it4lioy22defid.onion/monero/transaction/%1").arg(txid);
}
}
switch (nettype) {
case NetworkType::MAINNET:

2
src/utils/config.cpp

@ -158,7 +158,7 @@ Config::Config(QObject* parent)
QDir Config::defaultConfigDir() {
QString portablePath = QCoreApplication::applicationDirPath().append("/%1");
if (QFile::exists(portablePath.arg(".portable"))) {
if (QFile::exists(portablePath.arg(".portable")) || QFile::exists(portablePath.arg(".portable.txt"))) {
return portablePath.arg("feather_data");
}

9
src/widgets/NodeWidget.cpp

@ -135,8 +135,9 @@ void NodeWidget::onCustomAddClicked(){
auto currentNodes = m_ctx->nodes->customNodes();
QString currentNodesText;
for (auto &entry: currentNodes)
currentNodesText += QString("%1\n").arg(entry.url.toString());
for (auto &entry: currentNodes) {
currentNodesText += QString("%1\n").arg(entry.toFullAddress());
}
bool ok;
QString text = QInputDialog::getMultiLineText(this, "Add custom node(s).", "E.g: user:password@127.0.0.1:18081", currentNodesText, &ok);
@ -145,9 +146,9 @@ void NodeWidget::onCustomAddClicked(){
QList<FeatherNode> nodesList;
auto newNodesList = text.split("\n");
for(auto &newNodeText: newNodesList) {
for (auto &newNodeText: newNodesList) {
newNodeText = newNodeText.replace("\r", "").trimmed();
if(newNodeText.isEmpty())
if (newNodeText.isEmpty())
continue;
auto node = FeatherNode(newNodeText);

51
src/wizard/PageWalletRestoreSeed.cpp

@ -12,13 +12,27 @@
#include "utils/FeatherSeed.h"
#include "constants.h"
#include <mnemonics/electrum-words.h>
PageWalletRestoreSeed::PageWalletRestoreSeed(WizardFields *fields, QWidget *parent)
: QWizardPage(parent)
, ui(new Ui::PageWalletRestoreSeed)
, m_fields(fields)
{
ui->setupUi(this);
ui->label_errorString->hide();
std::vector<const Language::Base*> wordlists = crypto::ElectrumWords::get_language_list();
for (const auto& wordlist: wordlists) {
QStringList words_qt;
std::vector<std::string> words_std = wordlist->get_word_list();
for (const auto& word: words_std) {
words_qt += QString::fromStdString(word);
}
QString language = QString::fromStdString(wordlist->get_english_language_name());
ui->combo_seedLanguage->addItem(language);
m_wordlists[language] = words_qt;
}
QStringList bip39English;
for (int i = 0; i != 2048; i++)
@ -27,26 +41,18 @@ PageWalletRestoreSeed::PageWalletRestoreSeed(WizardFields *fields, QWidget *pare
// (illegible word with a known location). This can be tested by replacing a word with xxxx
bip39English << "xxxx";
QByteArray data = Utils::fileOpen(":/assets/mnemonic_25_english.txt");
QStringList moneroEnglish;
for (const auto &seed_word: data.split('\n'))
moneroEnglish << seed_word;
m_tevador.length = 14;
m_tevador.setWords(bip39English);
m_legacy.length = 25;
m_legacy.setWords(moneroEnglish);
m_legacy.setWords(m_wordlists["English"]);
ui->combo_seedLanguage->setCurrentText("English");
ui->seedEdit->setAcceptRichText(false);
ui->seedEdit->setMaximumHeight(150);
#ifndef QT_NO_CURSOR
QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
QGuiApplication::restoreOverrideCursor();
#endif
connect(ui->seedBtnGroup, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), this, &PageWalletRestoreSeed::onSeedTypeToggled);
connect(ui->combo_seedLanguage, &QComboBox::currentTextChanged, this, &PageWalletRestoreSeed::onSeedLanguageChanged);
this->onSeedTypeToggled();
}
@ -56,11 +62,13 @@ void PageWalletRestoreSeed::onSeedTypeToggled() {
m_mode = &m_tevador;
m_fields->seedType = SeedType::TEVADOR;
ui->seedEdit->setPlaceholderText("Enter 14 word seed..");
ui->group_seedLanguage->hide();
}
else if (ui->radio25->isChecked()) {
m_mode = &m_legacy;
m_fields->seedType = SeedType::MONERO;
ui->seedEdit->setPlaceholderText("Enter 25 word seed..");
ui->group_seedLanguage->show();
}
ui->label_errorString->hide();
@ -69,6 +77,11 @@ void PageWalletRestoreSeed::onSeedTypeToggled() {
ui->seedEdit->setText("");
}
void PageWalletRestoreSeed::onSeedLanguageChanged(const QString &language) {
m_legacy.setWords(m_wordlists[language]);
m_fields->seedLanguage = language;
}
int PageWalletRestoreSeed::nextId() const {
if (m_mode == &m_legacy) {
return WalletWizard::Page_SetRestoreHeight;
@ -90,8 +103,8 @@ bool PageWalletRestoreSeed::validatePage() {
ui->seedEdit->setStyleSheet("");
auto errStyle = "QTextEdit{border: 1px solid red;}";
auto seed = ui->seedEdit->toPlainText().replace("\n", "").replace("\r", "").trimmed();
auto seedSplit = seed.split(" ");
auto seed = ui->seedEdit->toPlainText().replace("\n", " ").replace("\r", "").trimmed();
QStringList seedSplit = seed.split(" ", Qt::SkipEmptyParts);
if (seedSplit.length() != m_mode->length) {
ui->label_errorString->show();
@ -100,8 +113,14 @@ bool PageWalletRestoreSeed::validatePage() {
return false;
}
// libwallet will accept e.g. "brötchen" or "BRÖTCHEN" instead of "Brötchen"
QStringList lowercaseWords;
for (const auto &word : m_mode->words) {
lowercaseWords << word.toLower();
}
for (const auto &word : seedSplit) {
if (!m_mode->words.contains(word)) {
if (!lowercaseWords.contains(word.toLower())) {
ui->label_errorString->show();
ui->label_errorString->setText(QString("Mnemonic seed contains an unknown word: %1").arg(word));
ui->seedEdit->setStyleSheet(errStyle);
@ -119,7 +138,7 @@ bool PageWalletRestoreSeed::validatePage() {
QMessageBox::information(this, "Corrected erasure", QString("xxxx -> %1").arg(_seed.correction));
}
m_fields->seed = seed;
m_fields->seed = seedSplit.join(" ");
m_fields->seedOffsetPassphrase = ui->line_seedOffset->text();
return true;

7
src/wizard/PageWalletRestoreSeed.h

@ -32,8 +32,8 @@ private:
seedType()
{
completer.setModel(&completerModel);
completer.setModelSorting(QCompleter::CaseInsensitivelySortedModel);
completer.setCaseSensitivity(Qt::CaseInsensitive);
completer.setModelSorting(QCompleter::CaseSensitivelySortedModel);
completer.setCaseSensitivity(Qt::CaseSensitive);
completer.setWrapAround(false);
}
@ -49,6 +49,7 @@ private:
};
void onSeedTypeToggled();
void onSeedLanguageChanged(const QString &language);
Ui::PageWalletRestoreSeed *ui;
WizardFields *m_fields;
@ -57,6 +58,8 @@ private:
seedType m_legacy;
seedType *m_mode;
QMap<QString, QStringList> m_wordlists;
};
#endif

12
src/wizard/PageWalletRestoreSeed.ui

@ -46,6 +46,18 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="group_seedLanguage">
<property name="title">
<string>Select seed language:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QComboBox" name="combo_seedLanguage"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="TextEdit" name="seedEdit">
<property name="acceptRichText">

2
src/wizard/WalletWizard.cpp

@ -127,5 +127,5 @@ void WalletWizard::onCreateWallet() {
if (m_wizardFields.mode == WizardMode::RestoreFromSeed && m_wizardFields.seedType == SeedType::MONERO)
seed.setRestoreHeight(m_wizardFields.restoreHeight);
emit createWallet(seed, walletPath, m_wizardFields.password, m_wizardFields.seedOffsetPassphrase);
emit createWallet(seed, walletPath, m_wizardFields.password, m_wizardFields.seedLanguage, m_wizardFields.seedOffsetPassphrase);
}

4
src/wizard/WalletWizard.h

@ -12,6 +12,7 @@
#include "model/WalletKeysFilesModel.h"
#include "utils/RestoreHeightLookup.h"
#include "utils/config.h"
#include "constants.h"
enum WizardMode {
CreateWallet = 0,
@ -32,6 +33,7 @@ struct WizardFields {
QString walletDir;
QString seed;
QString seedOffsetPassphrase;
QString seedLanguage = constants::seedLanguage;
QString password;
QString modeText;
QString address;
@ -72,7 +74,7 @@ signals:
void createWalletFromDevice(const QString &path, const QString &password, const QString &deviceName, int restoreHeight);
void createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic = false);
void createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset = "");
void createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedLanguage, const QString &seedOffset = "");
private slots:
void onCreateWallet();

Loading…
Cancel
Save