diff --git a/images/createWalletFromDevice.png b/images/createWalletFromDevice.png
new file mode 100755
index 00000000..9aa1e2f4
Binary files /dev/null and b/images/createWalletFromDevice.png differ
diff --git a/main.qml b/main.qml
index 07035d53..3f5769ab 100644
--- a/main.qml
+++ b/main.qml
@@ -222,6 +222,9 @@ ApplicationWindow {
if (typeof wizard.m_wallet !== 'undefined') {
console.log("using wizard wallet")
//Set restoreHeight
+ if (persistentSettings.restore_height == 0 && persistentSettings.is_recovering_from_device && walletManager.localDaemonSynced()) {
+ persistentSettings.restore_height = walletManager.blockchainHeight() - 1;
+ }
if(persistentSettings.restore_height > 0){
// We store restore height in own variable for performance reasons.
restoreHeight = persistentSettings.restore_height
@@ -331,7 +334,9 @@ ApplicationWindow {
currentDaemonAddress = localDaemonAddress
console.log("initializing with daemon address: ", currentDaemonAddress)
- currentWallet.initAsync(currentDaemonAddress, 0, persistentSettings.is_recovering, persistentSettings.restore_height);
+ currentWallet.initAsync(currentDaemonAddress, 0, persistentSettings.is_recovering, persistentSettings.is_recovering_from_device, persistentSettings.restore_height);
+ // save wallet keys in case wallet settings have been changed in the init
+ currentWallet.setPassword(walletPassword);
}
function walletPath() {
@@ -1014,6 +1019,7 @@ ApplicationWindow {
property string payment_id
property int restore_height : 0
property bool is_recovering : false
+ property bool is_recovering_from_device : false
property bool customDecorations : true
property string daemonFlags
property int logLevel: 0
diff --git a/qml.qrc b/qml.qrc
index 844cbb7f..870204e8 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -85,9 +85,11 @@
images/prevPage.png
wizard/WizardOptions.qml
images/createWallet.png
+ images/createWalletFromDevice.png
images/openAccount.png
images/recoverWallet.png
wizard/WizardCreateWallet.qml
+ wizard/WizardCreateWalletFromDevice.qml
images/greyTriangle.png
images/copyToClipboard.png
wizard/WizardPassword.qml
diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp
index 0047f8dd..2c6e92c9 100644
--- a/src/libwalletqt/Wallet.cpp
+++ b/src/libwalletqt/Wallet.cpp
@@ -172,12 +172,18 @@ bool Wallet::store(const QString &path)
return m_walletImpl->store(path.toStdString());
}
-bool Wallet::init(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering, quint64 restoreHeight)
+bool Wallet::init(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering, bool isRecoveringFromDevice, quint64 restoreHeight)
{
qDebug() << "init non async";
if (isRecovering){
qDebug() << "RESTORING";
m_walletImpl->setRecoveringFromSeed(true);
+ }
+ if (isRecoveringFromDevice){
+ qDebug() << "RESTORING FROM DEVICE";
+ m_walletImpl->setRecoveringFromDevice(true);
+ }
+ if (isRecovering || isRecoveringFromDevice) {
m_walletImpl->setRefreshFromBlockHeight(restoreHeight);
}
m_walletImpl->init(daemonAddress.toStdString(), upperTransactionLimit, m_daemonUsername.toStdString(), m_daemonPassword.toStdString());
@@ -191,7 +197,7 @@ void Wallet::setDaemonLogin(const QString &daemonUsername, const QString &daemon
m_daemonPassword = daemonPassword;
}
-void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering, quint64 restoreHeight)
+void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering, bool isRecoveringFromDevice, quint64 restoreHeight)
{
qDebug() << "initAsync: " + daemonAddress;
// Change status to disconnected if connected
@@ -201,7 +207,7 @@ void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLim
}
QFuture future = QtConcurrent::run(this, &Wallet::init,
- daemonAddress, upperTransactionLimit, isRecovering, restoreHeight);
+ daemonAddress, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight);
QFutureWatcher * watcher = new QFutureWatcher();
connect(watcher, &QFutureWatcher::finished,
diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h
index df413363..d0f053ac 100644
--- a/src/libwalletqt/Wallet.h
+++ b/src/libwalletqt/Wallet.h
@@ -111,10 +111,10 @@ public:
Q_INVOKABLE bool store(const QString &path = "");
//! initializes wallet
- Q_INVOKABLE bool init(const QString &daemonAddress, quint64 upperTransactionLimit = 0, bool isRecovering = false, quint64 restoreHeight = 0);
+ Q_INVOKABLE bool init(const QString &daemonAddress, quint64 upperTransactionLimit = 0, bool isRecovering = false, bool isRecoveringFromDevice = false, quint64 restoreHeight = 0);
//! initializes wallet asynchronously
- Q_INVOKABLE void initAsync(const QString &daemonAddress, quint64 upperTransactionLimit = 0, bool isRecovering = false, quint64 restoreHeight = 0);
+ Q_INVOKABLE void initAsync(const QString &daemonAddress, quint64 upperTransactionLimit = 0, bool isRecovering = false, bool isRecoveringFromDevice = false, quint64 restoreHeight = 0);
// Set daemon rpc user/pass
Q_INVOKABLE void setDaemonLogin(const QString &daemonUsername = "", const QString &daemonPassword = "");
diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp
index f6eb771a..355527ad 100644
--- a/src/libwalletqt/WalletManager.cpp
+++ b/src/libwalletqt/WalletManager.cpp
@@ -104,6 +104,20 @@ Wallet *WalletManager::createWalletFromKeys(const QString &path, const QString &
return m_currentWallet;
}
+Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString &password, NetworkType::Type nettype,
+ const QString &deviceName, quint64 restoreHeight, const QString &subaddressLookahead)
+{
+ QMutexLocker locker(&m_mutex);
+ if (m_currentWallet) {
+ qDebug() << "Closing open m_currentWallet" << m_currentWallet;
+ delete m_currentWallet;
+ m_currentWallet = NULL;
+ }
+ Monero::Wallet * w = m_pimpl->createWalletFromDevice(path.toStdString(), password.toStdString(), static_cast(nettype),
+ deviceName.toStdString(), restoreHeight, subaddressLookahead.toStdString());
+ m_currentWallet = new Wallet(w);
+ return m_currentWallet;
+}
QString WalletManager::closeWallet()
{
diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h
index dd44c634..75626928 100644
--- a/src/libwalletqt/WalletManager.h
+++ b/src/libwalletqt/WalletManager.h
@@ -62,6 +62,12 @@ public:
const QString &spendkey = "",
quint64 restoreHeight = 0);
+ Q_INVOKABLE Wallet * createWalletFromDevice(const QString &path,
+ const QString &password,
+ NetworkType::Type nettype,
+ const QString &deviceName,
+ quint64 restoreHeight = 0,
+ const QString &subaddressLookahead = "");
/*!
* \brief closeWallet - closes current open wallet and frees memory
* \return wallet address
diff --git a/wizard/WizardCreateWalletFromDevice.qml b/wizard/WizardCreateWalletFromDevice.qml
new file mode 100644
index 00000000..ae63e2a0
--- /dev/null
+++ b/wizard/WizardCreateWalletFromDevice.qml
@@ -0,0 +1,123 @@
+// Copyright (c) 2014-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import QtQuick 2.2
+import moneroComponents.WalletManager 1.0
+import moneroComponents.Wallet 1.0
+import QtQuick.Layouts 1.1
+import QtQuick.Dialogs 1.2
+import 'utils.js' as Utils
+
+ColumnLayout {
+ opacity: 0
+ visible: false
+
+ Behavior on opacity {
+ NumberAnimation { duration: 100; easing.type: Easing.InQuad }
+ }
+
+
+ onOpacityChanged: visible = opacity !== 0
+
+ function onWizardRestarted() {
+ // reset account name field
+ uiItem.accountNameText = defaultAccountName
+ }
+
+ //! function called each time we display this page
+
+ function onPageOpened(settingsOblect) {
+ uiItem.checkNextButton()
+ uiItem.deviceNameDropdown.update()
+ }
+
+ function onPageClosed(settingsObject) {
+ settingsObject['account_name'] = uiItem.accountNameText
+ settingsObject['wallet_path'] = uiItem.walletPath
+ var restoreHeight = parseInt(uiItem.restoreHeight);
+ settingsObject['restore_height'] = isNaN(restoreHeight)? 0 : restoreHeight;
+ settingsObject['subaddress_lookahead'] = uiItem.subaddressLookahead;
+ settingsObject['deviceName'] = uiItem.deviceName;
+ var walletFullPath = wizard.createWalletPath(uiItem.walletPath,uiItem.accountNameText);
+ if (!wizard.walletPathValid(walletFullPath)) {
+ return false;
+ }
+ return createWalletFromDevice(settingsObject)
+ }
+
+ //! function called each time we hide this page
+ //
+
+
+ function createWalletFromDevice(settingsObject) {
+ // TODO: create wallet in temporary filename and a) move it to the path specified by user after the final
+ // page submitted or b) delete it when program closed before reaching final page
+
+ // Always delete the wallet object before creating new - we could be stepping back from recovering wallet
+ if (typeof m_wallet !== 'undefined') {
+ walletManager.closeWallet()
+ console.log("deleting wallet")
+ }
+
+ var tmp_wallet_filename = oshelper.temporaryFilename();
+ console.log("Creating temporary wallet", tmp_wallet_filename)
+ var nettype = appWindow.persistentSettings.nettype;
+ var restoreHeight = settingsObject.restore_height;
+ var subaddressLookahead = settingsObject.subaddress_lookahead;
+ var deviceName = settingsObject.deviceName;
+
+ var wallet = walletManager.createWalletFromDevice(tmp_wallet_filename, "", nettype, deviceName, restoreHeight, subaddressLookahead);
+
+ var success = wallet.status === Wallet.Status_Ok;
+ if (success) {
+ m_wallet = wallet;
+ settingsObject['is_recovering_from_device'] = true;
+ settingsObject['tmp_wallet_filename'] = tmp_wallet_filename
+ } else {
+ console.log(wallet.errorString)
+ walletErrorDialog.text = wallet.errorString;
+ walletErrorDialog.open();
+ walletManager.closeWallet();
+ }
+ return success;
+ }
+
+ WizardManageWalletUI {
+ id: uiItem
+ titleText: qsTr("Create a new wallet from hardware device") + translationManager.emptyString
+ wordsTextItem.clipboardButtonVisible: false
+ wordsTextItem.tipTextVisible: false
+ restoreHeightVisible:true
+ recoverMode: false
+ recoverFromDevice: true
+ }
+
+ Component.onCompleted: {
+ parent.wizardRestarted.connect(onWizardRestarted)
+ }
+}
diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml
index ec685fe4..ae94ed38 100644
--- a/wizard/WizardMain.qml
+++ b/wizard/WizardMain.qml
@@ -54,6 +54,7 @@ ColumnLayout {
"create_wallet" : [welcomePage, optionsPage, createWalletPage, passwordPage, daemonSettingsPage, finishPage ],
"recovery_wallet" : [welcomePage, optionsPage, recoveryWalletPage, passwordPage, daemonSettingsPage, finishPage ],
"create_view_only_wallet" : [ createViewOnlyWalletPage, passwordPage ],
+ "create_wallet_from_device" : [welcomePage, optionsPage, createWalletFromDevicePage, passwordPage, daemonSettingsPage, finishPage ],
}
property string currentPath: "create_wallet"
@@ -162,6 +163,16 @@ ColumnLayout {
rootItem.state = "wizard";
}
+ function openCreateWalletFromDevicePage() {
+ wizardRestarted();
+ print ("show create wallet from device page");
+ currentPath = "create_wallet_from_device"
+ pages = paths[currentPath]
+ wizard.nextButton.visible = true
+ // goto next page
+ switchPage(true);
+ }
+
function createWalletPath(folder_path,account_name){
// Remove trailing slash - (default on windows and mac)
@@ -233,6 +244,7 @@ ColumnLayout {
appWindow.persistentSettings.auto_donations_amount = false //settings.auto_donations_amount
appWindow.persistentSettings.restore_height = (isNaN(settings.restore_height))? 0 : settings.restore_height
appWindow.persistentSettings.is_recovering = (settings.is_recovering === undefined)? false : settings.is_recovering
+ appWindow.persistentSettings.is_recovering_from_device = (settings.is_recovering_from_device === undefined)? false : settings.is_recovering_from_device
}
// reading settings from persistent storage
@@ -263,6 +275,7 @@ ColumnLayout {
onCreateWalletClicked: wizard.openCreateWalletPage()
onRecoveryWalletClicked: wizard.openRecoveryWalletPage()
onOpenWalletClicked: wizard.openOpenWalletPage();
+ onCreateWalletFromDeviceClicked: wizard.openCreateWalletFromDevicePage()
}
WizardCreateWallet {
@@ -283,6 +296,12 @@ ColumnLayout {
Layout.topMargin: wizardTopMargin
}
+ WizardCreateWalletFromDevice {
+ id: createWalletFromDevicePage
+ Layout.bottomMargin: wizardBottomMargin
+ Layout.topMargin: wizardTopMargin
+ }
+
WizardPassword {
id: passwordPage
Layout.bottomMargin: wizardBottomMargin
diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml
index a0c268cf..f1b853e1 100644
--- a/wizard/WizardManageWalletUI.qml
+++ b/wizard/WizardManageWalletUI.qml
@@ -44,6 +44,7 @@ ColumnLayout {
property alias wordsTextItem : memoTextItem
property alias restoreHeight : restoreHeightItem.text
property alias restoreHeightVisible: restoreHeightItem.visible
+ property alias subaddressLookahead : subaddressLookaheadItem.text
property alias walletName : accountName.text
property alias progressDotsModel : progressDots.model
property alias recoverFromKeysAddress: addressLine.text;
@@ -53,6 +54,10 @@ ColumnLayout {
property bool recoverMode: false
// Recover form seed or keys
property bool recoverFromSeedMode: true
+ // Recover form hardware device
+ property bool recoverFromDevice: false
+ property var deviceName: deviceNameModel.get(deviceNameDropdown.currentIndex).column2
+ property alias deviceNameDropdown: deviceNameDropdown
property int rowSpacing: 10
function checkFields(){
@@ -215,7 +220,7 @@ ColumnLayout {
// Recover from seed
RowLayout {
id: recoverFromSeed
- visible: !recoverMode || ( recoverMode && recoverFromSeedMode)
+ visible: !recoverFromDevice && (!recoverMode || ( recoverMode && recoverFromSeedMode))
WizardMemoTextInput {
id : memoTextItem
Layout.fillWidth: true
@@ -303,9 +308,56 @@ ColumnLayout {
fontBold: false
}
}
+
+ // Subaddress lookahead
+ RowLayout {
+ visible: recoverFromDevice
+ LineEdit {
+ id: subaddressLookaheadItem
+ Layout.fillWidth: true
+ Layout.maximumWidth: 600 * scaleRatio
+ Layout.minimumWidth: 200 * scaleRatio
+ placeholderFontBold: true
+ placeholderFontFamily: "Arial"
+ placeholderColor: Style.legacy_placeholderFontColor
+ placeholderText: qsTr("Subaddress lookahead (optional): :") + translationManager.emptyString
+ placeholderOpacity: 1.0
+ borderColor: Qt.rgba(0, 0, 0, 0.15)
+ backgroundColor: "white"
+ fontColor: "black"
+ fontBold: false
+ }
+ }
+
+ // Device name
+ ColumnLayout {
+ visible: recoverFromDevice
+ Label {
+ Layout.topMargin: 20 * scaleRatio
+ fontFamily: "Arial"
+ fontColor: "#555555"
+ fontSize: 14 * scaleRatio
+ text: qsTr("Device name") + translationManager.emptyString
+ }
+ ListModel {
+ id: deviceNameModel
+ ListElement { column1: qsTr("Ledger") ; column2: "Ledger"; }
+// ListElement { column1: qsTr("Trezor") ; column2: "Trezor"; }
+ }
+ StandardDropdown {
+ id: deviceNameDropdown
+ dataModel: deviceNameModel
+ Layout.fillWidth: true
+ Layout.topMargin: 6
+ colorHeaderBackground: "black"
+ releasedColor: "#363636"
+ pressedColor: "#202020"
+ }
+ }
// Wallet store location
ColumnLayout {
+ z: deviceNameDropdown.z - 1
Label {
Layout.fillWidth: true
Layout.topMargin: 20 * scaleRatio
diff --git a/wizard/WizardOptions.qml b/wizard/WizardOptions.qml
index 65a32582..3406c036 100644
--- a/wizard/WizardOptions.qml
+++ b/wizard/WizardOptions.qml
@@ -37,9 +37,10 @@ ColumnLayout {
signal createWalletClicked()
signal recoveryWalletClicked()
signal openWalletClicked()
+ signal createWalletFromDeviceClicked()
opacity: 0
visible: false
- property int buttonSize: (isMobile) ? 80 * scaleRatio : 190 * scaleRatio
+ property int buttonSize: (isMobile) ? 80 * scaleRatio : 140 * scaleRatio
property int buttonImageSize: (isMobile) ? buttonSize - 10 * scaleRatio : buttonSize - 30 * scaleRatio
function onPageClosed() {
@@ -227,6 +228,51 @@ ColumnLayout {
wrapMode: Text.WordWrap
}
}
+
+ GridLayout {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ flow: !isMobile ? GridLayout.TopToBottom : GridLayout.LeftToRight
+ rowSpacing: 20 * scaleRatio
+ columnSpacing: 10 * scaleRatio
+
+ Rectangle {
+ Layout.preferredHeight: page.buttonSize
+ Layout.preferredWidth: page.buttonSize
+ radius: page.buttonSize
+ color: createWalletFromDeviceArea.containsMouse ? "#DBDBDB" : "#FFFFFF"
+
+
+ Image {
+ width: page.buttonImageSize
+ height: page.buttonImageSize
+ fillMode: Image.PreserveAspectFit
+ horizontalAlignment: Image.AlignRight
+ verticalAlignment: Image.AlignTop
+ anchors.centerIn: parent
+ source: "qrc:///images/createWalletFromDevice.png"
+ }
+
+ MouseArea {
+ id: createWalletFromDeviceArea
+ anchors.fill: parent
+ hoverEnabled: true
+ onClicked: {
+ page.createWalletFromDeviceClicked()
+ }
+ }
+ }
+
+ Text {
+ Layout.preferredWidth: page.buttonSize
+ font.family: "Arial"
+ font.pixelSize: 16 * scaleRatio
+ color: "#4A4949"
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ text: qsTr("Create a new wallet from hardware device") + translationManager.emptyString
+ }
+ }
}
ColumnLayout {