From 488049e4b429acb1ba05d3467af1b7f8138c7a06 Mon Sep 17 00:00:00 2001 From: selsta Date: Tue, 21 Jul 2026 02:03:38 +0200 Subject: [PATCH] wizard: support key-based monero-wallet restore QR codes --- components/QRCodeScanner.qml | 42 +++++++++++++++++++++++++++++++++ pages/Keys.qml | 2 +- wizard/WizardRestoreWallet1.qml | 5 +++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/components/QRCodeScanner.qml b/components/QRCodeScanner.qml index 7479c75f6a..5917870e75 100644 --- a/components/QRCodeScanner.qml +++ b/components/QRCodeScanner.qml @@ -45,6 +45,41 @@ Rectangle { state: "Stopped" signal qrcode_decoded(string address, string payment_id, string amount, string tx_description, string recipient_name, var extra_parameters) + property bool walletRestoreMode: false + + function parseWalletRestoreUri(data) { + var prefix = "" + if (data.indexOf("monero-wallet:") === 0) + prefix = "monero-wallet:" + else if (data.indexOf("monero_wallet:") === 0) + prefix = "monero_wallet:" + else + return null + + var queryOffset = data.indexOf("?") + var address = data.substring(prefix.length, queryOffset < 0 ? data.length : queryOffset) + if (!walletManager.addressValid(address, appWindow.persistentSettings.nettype)) + return null + + var params = {} + if (queryOffset >= 0) { + var items = data.substring(queryOffset + 1).split("&") + for (var index = 0; index < items.length; ++index) { + var separator = items[index].indexOf("=") + if (separator < 0) + continue + var name = decodeURIComponent(items[index].substring(0, separator)) + var value = decodeURIComponent(items[index].substring(separator + 1)) + if (name === "view_key") + params.secret_view_key = value + else if (name === "spend_key") + params.secret_spend_key = value + else if (name === "height") + params.restore_height = value + } + } + return { "address": address, "extra_parameters": params } + } states: [ State { @@ -67,6 +102,7 @@ Rectangle { root.visible = false finder.enabled = false camera.cameraState = Camera.UnloadedState + root.walletRestoreMode = false } } } @@ -86,6 +122,12 @@ Rectangle { id : finder objectName: "QrFinder" onDecoded : { + var walletRestore = root.walletRestoreMode ? root.parseWalletRestoreUri(data) : null + if (walletRestore !== null) { + root.qrcode_decoded(walletRestore.address, "", "", "", "", walletRestore.extra_parameters) + root.state = "Stopped" + return + } const parsed = walletManager.parse_uri_to_object(data); if (!parsed.error) { root.qrcode_decoded(parsed.address, parsed.payment_id, parsed.amount, parsed.tx_description, parsed.recipient_name, parsed.extra_parameters); diff --git a/pages/Keys.qml b/pages/Keys.qml index 99618d901b..0d277cfdb4 100644 --- a/pages/Keys.qml +++ b/pages/Keys.qml @@ -281,7 +281,7 @@ Rectangle { seedText.text = currentWallet.seed === "" ? qsTr("Mnemonic seed protected by hardware device.") + translationManager.emptyString : currentWallet.seed if(typeof currentWallet != "undefined") { - viewOnlyQRCode.source = "image://qrcode/monero_wallet:" + currentWallet.address(0, 0) + "?view_key="+currentWallet.secretViewKey+"&height="+currentWallet.walletCreationHeight + viewOnlyQRCode.source = "image://qrcode/monero-wallet:" + currentWallet.address(0, 0) + "?view_key="+currentWallet.secretViewKey+"&height="+currentWallet.walletCreationHeight fullWalletQRCode.source = viewOnlyQRCode.source +"&spend_key="+currentWallet.secretSpendKey if(currentWallet.viewOnly) { diff --git a/wizard/WizardRestoreWallet1.qml b/wizard/WizardRestoreWallet1.qml index 29f2c6321b..55b1455a54 100644 --- a/wizard/WizardRestoreWallet1.qml +++ b/wizard/WizardRestoreWallet1.qml @@ -75,6 +75,7 @@ Rectangle { restoreHeight.text = typeof params.restore_height != "undefined" ? params.restore_height : ""; cameraUi.qrcode_decoded.disconnect(updateFromQrCode); + cameraUi.walletRestoreMode = false; } function verifyFromKeys() { @@ -172,8 +173,10 @@ Rectangle { seedRadioButton.checked = false; keysRadioButton.checked = false; wizardController.walletRestoreMode = 'qr'; - cameraUi.state = "Capture"; + cameraUi.walletRestoreMode = true; + cameraUi.qrcode_decoded.disconnect(updateFromQrCode); cameraUi.qrcode_decoded.connect(updateFromQrCode); + cameraUi.state = "Capture"; } } }