diff --git a/electrum/gui/qml/components/OpenChannel.qml b/electrum/gui/qml/components/OpenChannel.qml index c996858cc..c416278fe 100644 --- a/electrum/gui/qml/components/OpenChannel.qml +++ b/electrum/gui/qml/components/OpenChannel.qml @@ -46,8 +46,10 @@ Pane { icon.height: constants.iconSizeMedium icon.width: constants.iconSizeMedium onClicked: { - channelopener.nodeid = AppController.clipboardToText() - node.text = channelopener.nodeid + if (channelopener.validate_nodeid(AppController.clipboardToText()) { + channelopener.nodeid = AppController.clipboardToText() + node.text = channelopener.nodeid + } } } ToolButton { @@ -58,8 +60,10 @@ Pane { onClicked: { var page = app.stack.push(Qt.resolvedUrl('Scan.qml')) page.onFound.connect(function() { - channelopener.nodeid = page.scanData - node.text = channelopener.nodeid + if (channelopener.validate_nodeid(page.scanData)) { + channelopener.nodeid = page.scanData + node.text = channelopener.nodeid + } }) } } @@ -67,13 +71,20 @@ Pane { // trampoline ComboBox { - id: tnode visible: !Config.useGossip Layout.columnSpan: 3 Layout.fillWidth: true model: channelopener.trampolineNodeNames onCurrentValueChanged: { - channelopener.nodeid = tnode.currentValue + if (activeFocus) + channelopener.nodeid = currentValue + } + // preselect a random node + Component.onCompleted: { + if (!Config.useGossip) { + currentIndex = Math.floor(Math.random() * channelopener.trampolineNodeNames.length) + channelopener.nodeid = currentValue + } } } diff --git a/electrum/gui/qml/components/controls/ChannelDelegate.qml b/electrum/gui/qml/components/controls/ChannelDelegate.qml index d3b4a76d3..9de2e9102 100644 --- a/electrum/gui/qml/components/controls/ChannelDelegate.qml +++ b/electrum/gui/qml/components/controls/ChannelDelegate.qml @@ -43,6 +43,16 @@ ItemDelegate { Layout.preferredHeight: constants.iconSizeLarge opacity: _closed ? 0.5 : 1.0 + Image { + visible: model.is_trampoline + source: "../../../icons/kangaroo.png" + anchors { + right: parent.right + bottom: parent.bottom + } + width: parent.width * 2/3 + height: parent.height * 2/3 + } } RowLayout { diff --git a/electrum/gui/qml/qechannellistmodel.py b/electrum/gui/qml/qechannellistmodel.py index 9fafbf828..c9f7bdad6 100644 --- a/electrum/gui/qml/qechannellistmodel.py +++ b/electrum/gui/qml/qechannellistmodel.py @@ -17,7 +17,7 @@ class QEChannelListModel(QAbstractListModel, QtEventListener): # define listmodel rolemap _ROLE_NAMES=('cid','state','state_code','initiator','capacity','can_send', 'can_receive','l_csv_delay','r_csv_delay','send_frozen','receive_frozen', - 'type','node_id','node_alias','short_cid','funding_tx') + 'type','node_id','node_alias','short_cid','funding_tx','is_trampoline') _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES)) _ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS)) @@ -82,6 +82,7 @@ class QEChannelListModel(QAbstractListModel, QtEventListener): item['capacity'] = QEAmount(amount_sat=lnc.get_capacity()) item['can_send'] = QEAmount(amount_msat=lnc.available_to_spend(LOCAL)) item['can_receive'] = QEAmount(amount_msat=lnc.available_to_spend(REMOTE)) + item['is_trampoline'] = lnworker.is_trampoline_peer(lnc.node_id) return item numOpenChannelsChanged = pyqtSignal() diff --git a/electrum/gui/qml/qechannelopener.py b/electrum/gui/qml/qechannelopener.py index 8c5767380..80411506b 100644 --- a/electrum/gui/qml/qechannelopener.py +++ b/electrum/gui/qml/qechannelopener.py @@ -84,19 +84,16 @@ class QEChannelOpener(QObject): def validate(self): nodeid_valid = False if self._nodeid: + self._logger.debug(f'checking if {self._nodeid} is valid') if not self._wallet.wallet.config.get('use_gossip', False): self._peer = hardcoded_trampoline_nodes()[self._nodeid] nodeid_valid = True else: try: - node_pubkey, host_port = extract_nodeid(self._nodeid) - host, port = host_port.split(':',1) - self._peer = LNPeerAddr(host, int(port), node_pubkey) + self._peer = nodeid_to_lnpeer(self._nodeid) nodeid_valid = True - except ConnStringFormatError as e: - self.validationError.emit('invalid_nodeid', repr(e)) - except ValueError as e: - self.validationError.emit('invalid_nodeid', repr(e)) + except: + pass if not nodeid_valid: self._valid = False @@ -112,6 +109,19 @@ class QEChannelOpener(QObject): self._valid = True self.validChanged.emit() + @pyqtSlot(str, result=bool) + def validate_nodeid(self, nodeid): + try: + nodeid_to_lnpeer(nodeid) + except: + return False + return True + + def nodeid_to_lnpeer(self, nodeid): + node_pubkey, host_port = extract_nodeid(nodeid) + host, port = host_port.split(':',1) + return LNPeerAddr(host, int(port), node_pubkey) + # FIXME "max" button in amount_dialog should enforce LN_MAX_FUNDING_SAT @pyqtSlot() @pyqtSlot(bool)