mirror of
https://github.com/monero-project/monero-gui.git
synced 2024-11-16 15:58:11 +00:00
Merge pull request #1446
9bb6a25
Add support for creating hardware wallet (stoffu)
This commit is contained in:
commit
3fd37b96a0
11 changed files with 282 additions and 8 deletions
BIN
images/createWalletFromDevice.png
Executable file
BIN
images/createWalletFromDevice.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
8
main.qml
8
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
|
||||
|
|
2
qml.qrc
2
qml.qrc
|
@ -85,9 +85,11 @@
|
|||
<file>images/prevPage.png</file>
|
||||
<file>wizard/WizardOptions.qml</file>
|
||||
<file>images/createWallet.png</file>
|
||||
<file>images/createWalletFromDevice.png</file>
|
||||
<file>images/openAccount.png</file>
|
||||
<file>images/recoverWallet.png</file>
|
||||
<file>wizard/WizardCreateWallet.qml</file>
|
||||
<file>wizard/WizardCreateWalletFromDevice.qml</file>
|
||||
<file>images/greyTriangle.png</file>
|
||||
<file>images/copyToClipboard.png</file>
|
||||
<file>wizard/WizardPassword.qml</file>
|
||||
|
|
|
@ -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<bool> future = QtConcurrent::run(this, &Wallet::init,
|
||||
daemonAddress, upperTransactionLimit, isRecovering, restoreHeight);
|
||||
daemonAddress, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight);
|
||||
QFutureWatcher<bool> * watcher = new QFutureWatcher<bool>();
|
||||
|
||||
connect(watcher, &QFutureWatcher<bool>::finished,
|
||||
|
|
|
@ -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 = "");
|
||||
|
|
|
@ -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<Monero::NetworkType>(nettype),
|
||||
deviceName.toStdString(), restoreHeight, subaddressLookahead.toStdString());
|
||||
m_currentWallet = new Wallet(w);
|
||||
return m_currentWallet;
|
||||
}
|
||||
|
||||
QString WalletManager::closeWallet()
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
123
wizard/WizardCreateWalletFromDevice.qml
Normal file
123
wizard/WizardCreateWalletFromDevice.qml
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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): <major>:<minor>") + 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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue