diff --git a/electrum/gui/qml/components/OpenChannel.qml b/electrum/gui/qml/components/OpenChannel.qml index 369b0da11..b317b4963 100644 --- a/electrum/gui/qml/components/OpenChannel.qml +++ b/electrum/gui/qml/components/OpenChannel.qml @@ -23,8 +23,10 @@ Pane { text: qsTr('Node') } + // gossip TextArea { id: node + visible: Config.useGossip Layout.columnSpan: 2 Layout.fillWidth: true font.family: FixedFont @@ -37,6 +39,7 @@ Pane { } RowLayout { + visible: Config.useGossip spacing: 0 ToolButton { icon.source: '../../icons/paste.png' @@ -62,6 +65,18 @@ Pane { } } + // trampoline + ComboBox { + id: tnode + visible: !Config.useGossip + Layout.columnSpan: 3 + Layout.fillWidth: true + model: channelopener.trampolineNodeNames + onCurrentValueChanged: { + channelopener.nodeid = tnode.currentValue + } + } + Label { text: qsTr('Amount') } @@ -85,9 +100,7 @@ Pane { id: is_max text: qsTr('Max') onCheckedChanged: { - if (checked) { - channelopener.amount = MAX - } + channelopener.amount = checked ? MAX : Config.unitsToSats(amount.text) } } } diff --git a/electrum/gui/qml/components/Preferences.qml b/electrum/gui/qml/components/Preferences.qml index c1be7b0da..702ee379c 100644 --- a/electrum/gui/qml/components/Preferences.qml +++ b/electrum/gui/qml/components/Preferences.qml @@ -105,6 +105,22 @@ Pane { Daemon.fx.rateSource = currentValue } } + + Label { + text: qsTr('Lightning Routing') + enabled: Daemon.currentWallet.isLightning + } + + ComboBox { + id: lnRoutingType + valueRole: 'key' + textRole: 'label' + enabled: Daemon.currentWallet.isLightning && false + model: ListModel { + ListElement { key: 'gossip'; label: qsTr('Gossip') } + ListElement { key: 'trampoline'; label: qsTr('Trampoline') } + } + } } } @@ -118,5 +134,6 @@ Pane { historicRates.checked = Daemon.fx.historicRates rateSources.currentIndex = rateSources.indexOfValue(Daemon.fx.rateSource) fiatEnable.checked = Daemon.fx.enabled + lnRoutingType.currentIndex = Config.useGossip ? 0 : 1 } } diff --git a/electrum/gui/qml/qechannelopener.py b/electrum/gui/qml/qechannelopener.py index dde5c6c24..4d5eb1cab 100644 --- a/electrum/gui/qml/qechannelopener.py +++ b/electrum/gui/qml/qechannelopener.py @@ -2,7 +2,8 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.logging import get_logger from electrum.util import format_time -from electrum.lnutil import extract_nodeid, ConnStringFormatError +from electrum.lnutil import extract_nodeid, ConnStringFormatError, LNPeerAddr +from electrum.lnworker import hardcoded_trampoline_nodes from electrum.gui import messages from .qewallet import QEWallet @@ -23,6 +24,8 @@ class QEChannelOpener(QObject): validationError = pyqtSignal([str,str], arguments=['code','message']) conflictingBackup = pyqtSignal([str], arguments=['message']) + dataChanged = pyqtSignal() # generic notify signal + walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) def wallet(self): @@ -69,14 +72,28 @@ class QEChannelOpener(QObject): def openTx(self): return self._opentx + @pyqtProperty(list, notify=dataChanged) + def trampolineNodeNames(self): + return list(hardcoded_trampoline_nodes().keys()) + + # FIXME min channel funding amount + # FIXME have requested funding amount def validate(self): nodeid_valid = False if self._nodeid: - try: - self._node_pubkey, self._host_port = extract_nodeid(self._nodeid) + if not self._wallet.wallet.config.get('use_gossip', False): + self._peer = hardcoded_trampoline_nodes()[self._nodeid] nodeid_valid = True - except ConnStringFormatError as e: - self.validationError.emit('invalid_nodeid', repr(e)) + else: + try: + node_pubkey, host_port = extract_nodeid(self._nodeid) + host, port = host_port.split(':',1) + self._peer = LNPeerAddr(host, int(port), node_pubkey) + 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)) if not nodeid_valid: self._valid = False @@ -99,16 +116,12 @@ class QEChannelOpener(QObject): if not self.valid: return - #if self.use_gossip: - #conn_str = self.pubkey - #if self.ipport: - #conn_str += '@' + self.ipport.strip() - #else: - #conn_str = str(self.trampolines[self.pubkey]) + self._logger.debug('Connect String: %s' % str(self._peer)) + amount = '!' if self._amount.isMax else self._amount.satsInt lnworker = self._wallet.wallet.lnworker - if lnworker.has_conflicting_backup_with(node_pubkey) and not confirm_backup_conflict: + if lnworker.has_conflicting_backup_with(self._peer.pubkey) and not confirm_backup_conflict: self.conflictingBackup.emit(messages.MGS_CONFLICTING_BACKUP_INSTANCE) return @@ -117,7 +130,7 @@ class QEChannelOpener(QObject): make_tx = lambda rbf: lnworker.mktx_for_open_channel( coins=coins, funding_sat=amount, - node_id=self._node_pubkey, + node_id=self._peer.pubkey, fee_est=None) #on_pay = lambda tx: self.app.protected('Create a new channel?', self.do_open_channel, (tx, conn_str)) #d = ConfirmTxDialog( diff --git a/electrum/gui/qml/qeconfig.py b/electrum/gui/qml/qeconfig.py index 64ab26873..76bca9e64 100644 --- a/electrum/gui/qml/qeconfig.py +++ b/electrum/gui/qml/qeconfig.py @@ -70,6 +70,16 @@ class QEConfig(QObject): self.config.amt_add_thousands_sep = checked self.thousandsSeparatorChanged.emit() + useGossipChanged = pyqtSignal() + @pyqtProperty(bool, notify=useGossipChanged) + def useGossip(self): + return self.config.get('use_gossip', False) + + @useGossip.setter + def useGossip(self, gossip): + self.config.set_key('use_gossip', gossip) + self.useGossipChanged.emit() + @pyqtSlot('qint64', result=str) @pyqtSlot('qint64', bool, result=str) @pyqtSlot(QEAmount, result=str) diff --git a/electrum/gui/qml/qetxfinalizer.py b/electrum/gui/qml/qetxfinalizer.py index a49b97389..25f2c2d95 100644 --- a/electrum/gui/qml/qetxfinalizer.py +++ b/electrum/gui/qml/qetxfinalizer.py @@ -11,8 +11,10 @@ from .qewallet import QEWallet from .qetypes import QEAmount class QETxFinalizer(QObject): - def __init__(self, parent=None): + def __init__(self, parent=None, *, make_tx=None, accept=None): super().__init__(parent) + self.f_make_tx = make_tx + self.f_accept = accept self._tx = None _logger = get_logger(__name__) @@ -207,6 +209,11 @@ class QETxFinalizer(QObject): @profiler def make_tx(self): + if self.f_make_tx: + tx = self.f_make_tx() + return tx + + # default impl coins = self._wallet.wallet.get_spendable_coins(None) outputs = [PartialTxOutput.from_address_and_value(self.address, self._amount.satsInt)] tx = self._wallet.wallet.make_unsigned_transaction(coins=coins,outputs=outputs, fee=None,rbf=self._rbf) @@ -268,4 +275,8 @@ class QETxFinalizer(QObject): self._logger.debug('no valid tx') return + if self.f_accept: + self.f_accept(self._tx) + return + self._wallet.sign_and_broadcast(self._tx)