From fad9f87303f477cd457f3d084f6d2a707c90c02d Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 16 Aug 2022 12:08:51 +0200 Subject: [PATCH] qml: accept raw tx from send page paste/qrscan and show TxDetails --- electrum/gui/qml/components/Send.qml | 23 +++++++++++-- electrum/gui/qml/components/TxDetails.qml | 36 +++++++++++++++----- electrum/gui/qml/qebitcoin.py | 11 +++++- electrum/gui/qml/qetxdetails.py | 41 +++++++++++++++++++---- 4 files changed, 92 insertions(+), 19 deletions(-) diff --git a/electrum/gui/qml/components/Send.qml b/electrum/gui/qml/components/Send.qml index fc6f97e15..260fe21bf 100644 --- a/electrum/gui/qml/components/Send.qml +++ b/electrum/gui/qml/components/Send.qml @@ -63,7 +63,16 @@ Pane { icon.source: '../../icons/paste.png' icon.height: constants.iconSizeMedium icon.width: constants.iconSizeMedium - onClicked: invoice.recipient = AppController.clipboardToText() + onClicked: { + var text = AppController.clipboardToText() + if (bitcoin.verify_raw_tx(text)) { + app.stack.push(Qt.resolvedUrl('TxDetails.qml'), + { rawtx: text } + ) + } else { + invoice.recipient = text + } + } } ToolButton { icon.source: '../../icons/qrcode.png' @@ -73,7 +82,14 @@ Pane { onClicked: { var page = app.stack.push(Qt.resolvedUrl('Scan.qml')) page.onFound.connect(function() { - invoice.recipient = page.scanData + var text = page.scanData + if (bitcoin.verify_raw_tx(text)) { + app.stack.push(Qt.resolvedUrl('TxDetails.qml'), + { rawtx: text } + ) + } else { + invoice.recipient = text + } }) } } @@ -379,4 +395,7 @@ Pane { } } + Bitcoin { + id: bitcoin + } } diff --git a/electrum/gui/qml/components/TxDetails.qml b/electrum/gui/qml/components/TxDetails.qml index 9a2b29ed1..ea747a9ea 100644 --- a/electrum/gui/qml/components/TxDetails.qml +++ b/electrum/gui/qml/components/TxDetails.qml @@ -15,6 +15,7 @@ Pane { property string title: qsTr("Transaction details") property string txid + property string rawtx property alias label: txdetails.label @@ -50,9 +51,24 @@ Pane { width: parent.width columns: 2 + RowLayout { + Layout.fillWidth: true + Layout.columnSpan: 2 + visible: txdetails.isUnrelated + Image { + source: '../../icons/warning.png' + Layout.preferredWidth: constants.iconSizeSmall + Layout.preferredHeight: constants.iconSizeSmall + } + Label { + text: qsTr('Transaction is unrelated to this wallet') + color: Material.accentColor + } + } + Label { Layout.fillWidth: true - visible: txdetails.lnAmount.satsInt == 0 + visible: !txdetails.isUnrelated && txdetails.lnAmount.satsInt == 0 text: txdetails.amount.satsInt > 0 ? qsTr('Amount received') : qsTr('Amount sent') @@ -61,7 +77,7 @@ Pane { Label { Layout.fillWidth: true - visible: txdetails.lnAmount.satsInt != 0 + visible: !txdetails.isUnrelated && txdetails.lnAmount.satsInt != 0 text: txdetails.lnAmount.satsInt > 0 ? qsTr('Amount received in channels') : qsTr('Amount withdrawn from channels') @@ -70,6 +86,7 @@ Pane { } RowLayout { + visible: !txdetails.isUnrelated Layout.fillWidth: true Label { visible: txdetails.lnAmount.satsInt == 0 @@ -88,30 +105,30 @@ Pane { } Item { - visible: Daemon.fx.enabled; Layout.preferredWidth: 1; Layout.preferredHeight: 1 + visible: !txdetails.isUnrelated && Daemon.fx.enabled; Layout.preferredWidth: 1; Layout.preferredHeight: 1 } Label { - visible: Daemon.fx.enabled && txdetails.lnAmount.satsInt == 0 + visible: !txdetails.isUnrelated && Daemon.fx.enabled && txdetails.lnAmount.satsInt == 0 text: Daemon.fx.fiatValue(txdetails.amount, false) + ' ' + Daemon.fx.fiatCurrency } Label { - visible: Daemon.fx.enabled && txdetails.lnAmount.satsInt != 0 + visible: !txdetails.isUnrelated && Daemon.fx.enabled && txdetails.lnAmount.satsInt != 0 text: Daemon.fx.fiatValue(txdetails.lnAmount, false) + ' ' + Daemon.fx.fiatCurrency } Label { Layout.fillWidth: true - visible: txdetails.amount.satsInt < 0 + visible: txdetails.fee.satsInt != 0 text: qsTr('Transaction fee') color: Material.accentColor } RowLayout { Layout.fillWidth: true - visible: txdetails.amount.satsInt < 0 + visible: txdetails.fee.satsInt != 0 Label { text: Config.formatSats(txdetails.fee) font.family: FixedFont @@ -135,12 +152,12 @@ Pane { Label { text: qsTr('Mempool depth') color: Material.accentColor - visible: !txdetails.isMined + visible: !txdetails.isMined && txdetails.canBroadcast } Label { text: txdetails.mempoolDepth - visible: !txdetails.isMined + visible: !txdetails.isMined && txdetails.canBroadcast } Label { @@ -314,6 +331,7 @@ Pane { id: txdetails wallet: Daemon.currentWallet txid: root.txid + rawtx: root.rawtx onLabelChanged: root.detailsChanged() } } diff --git a/electrum/gui/qml/qebitcoin.py b/electrum/gui/qml/qebitcoin.py index 91b7cf611..b3f9cd82c 100644 --- a/electrum/gui/qml/qebitcoin.py +++ b/electrum/gui/qml/qebitcoin.py @@ -9,8 +9,9 @@ from electrum.bip32 import is_bip32_derivation, xpub_type from electrum.logging import get_logger from electrum.slip39 import decode_mnemonic, Slip39Error from electrum.util import parse_URI, create_bip21_uri, InvalidBitcoinURI, get_asyncio_loop -from .qetypes import QEAmount +from electrum.transaction import tx_from_any +from .qetypes import QEAmount class QEBitcoin(QObject): def __init__(self, config, parent=None): @@ -152,3 +153,11 @@ class QEBitcoin(QObject): extra_params['exp'] = str(expiry) return create_bip21_uri(address, satoshis.satsInt, message, extra_query_params=extra_params) + + @pyqtSlot(str, result=bool) + def verify_raw_tx(self, rawtx): + try: + tx_from_any(rawtx) + return True + except: + return False diff --git a/electrum/gui/qml/qetxdetails.py b/electrum/gui/qml/qetxdetails.py index 42a530405..996cd2ea4 100644 --- a/electrum/gui/qml/qetxdetails.py +++ b/electrum/gui/qml/qetxdetails.py @@ -2,6 +2,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.logging import get_logger from electrum.util import format_time +from electrum.transaction import tx_from_any from .qewallet import QEWallet from .qetypes import QEAmount @@ -13,9 +14,12 @@ class QETxDetails(QObject): _logger = get_logger(__name__) _wallet = None - _txid = None + _txid = '' + _rawtx = '' _label = '' + _tx = None + _status = '' _amount = QEAmount(amount_sat=0) _lnamount = QEAmount(amount_sat=0) @@ -30,6 +34,7 @@ class QETxDetails(QObject): _can_cpfp = False _can_save_as_local = False _can_remove = False + _is_unrelated = False _is_mined = False @@ -67,6 +72,22 @@ class QETxDetails(QObject): self.txidChanged.emit() self.update() + @pyqtProperty(str, notify=detailsChanged) + def rawtx(self): + return self._rawtx + + @rawtx.setter + def rawtx(self, rawtx: str): + if self._rawtx != rawtx: + self._logger.debug('rawtx set -> %s' % rawtx) + self._rawtx = rawtx + try: + self._tx = tx_from_any(rawtx, deserialize=True) + self._logger.debug('tx type is %s' % str(type(self._tx))) + self.txid = self._tx.txid() # triggers update() + except Exception as e: + self._logger.error(repr(e)) + labelChanged = pyqtSignal() @pyqtProperty(str, notify=labelChanged) def label(self): @@ -159,24 +180,29 @@ class QETxDetails(QObject): def canRemove(self): return self._can_remove + @pyqtProperty(bool, notify=detailsChanged) + def isUnrelated(self): + return self._is_unrelated + def update(self): if self._wallet is None: self._logger.error('wallet undefined') return - # abusing get_input_tx to get tx from txid - tx = self._wallet.wallet.get_input_tx(self._txid) + if not self._rawtx: + # abusing get_input_tx to get tx from txid + self._tx = self._wallet.wallet.get_input_tx(self._txid) - #self._logger.debug(repr(tx.to_json())) + #self._logger.debug(repr(self._tx.to_json())) - self._inputs = list(map(lambda x: x.to_json(), tx.inputs())) + self._inputs = list(map(lambda x: x.to_json(), self._tx.inputs())) self._outputs = list(map(lambda x: { 'address': x.get_ui_address_str(), 'value': QEAmount(amount_sat=x.value), 'is_mine': self._wallet.wallet.is_mine(x.get_ui_address_str()) - }, tx.outputs())) + }, self._tx.outputs())) - txinfo = self._wallet.wallet.get_tx_info(tx) + txinfo = self._wallet.wallet.get_tx_info(self._tx) #self._logger.debug(repr(txinfo)) @@ -204,6 +230,7 @@ class QETxDetails(QObject): else: self._lnamount.satsInt = 0 + self._is_unrelated = txinfo.amount is None and self._lnamount.isEmpty self._is_lightning_funding_tx = txinfo.is_lightning_funding_tx self._can_bump = txinfo.can_bump self._can_dscancel = txinfo.can_dscancel