URI: 
       tlightning_open_channel.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tlightning_open_channel.py (8063B)
       ---
            1 from typing import TYPE_CHECKING
            2 
            3 from kivy.lang import Builder
            4 from kivy.factory import Factory
            5 
            6 from electrum.gui.kivy.i18n import _
            7 from electrum.lnaddr import lndecode
            8 from electrum.util import bh2u
            9 from electrum.bitcoin import COIN
           10 import electrum.simple_config as config
           11 from electrum.logging import Logger
           12 from electrum.lnutil import ln_dummy_address
           13 
           14 from .label_dialog import LabelDialog
           15 
           16 if TYPE_CHECKING:
           17     from ...main_window import ElectrumWindow
           18 
           19 
           20 Builder.load_string('''
           21 #:import KIVY_GUI_PATH electrum.gui.kivy.KIVY_GUI_PATH
           22 
           23 <LightningOpenChannelDialog@Popup>
           24     use_gossip: False
           25     id: s
           26     name: 'lightning_open_channel'
           27     title: _('Open Lightning Channel')
           28     pubkey: ''
           29     amount: ''
           30     is_max: False
           31     ipport: ''
           32     BoxLayout
           33         spacing: '12dp'
           34         padding: '12dp'
           35         orientation: 'vertical'
           36         SendReceiveBlueBottom:
           37             id: blue_bottom
           38             size_hint: 1, None
           39             height: self.minimum_height
           40             BoxLayout:
           41                 size_hint: 1, None
           42                 height: blue_bottom.item_height
           43                 Image:
           44                     source: f'atlas://{KIVY_GUI_PATH}/theming/light/globe'
           45                     size_hint: None, None
           46                     size: '22dp', '22dp'
           47                     pos_hint: {'center_y': .5}
           48                 BlueButton:
           49                     text: s.pubkey if s.pubkey else (_('Node ID') if root.use_gossip else _('Trampoline node'))
           50                     shorten: True
           51                     on_release: s.suggest_node()
           52             CardSeparator:
           53                 color: blue_bottom.foreground_color
           54             BoxLayout:
           55                 size_hint: 1, None
           56                 height: blue_bottom.item_height
           57                 Image:
           58                     source: f'atlas://{KIVY_GUI_PATH}/theming/light/calculator'
           59                     size_hint: None, None
           60                     size: '22dp', '22dp'
           61                     pos_hint: {'center_y': .5}
           62                 BlueButton:
           63                     text: s.amount if s.amount else _('Amount')
           64                     on_release: app.amount_dialog(s, True)
           65         TopLabel:
           66             text: _('Paste or scan a node ID, a connection string or a lightning invoice.') if root.use_gossip else _('Choose a trampoline node and the amount')
           67         BoxLayout:
           68             size_hint: 1, None
           69             height: '48dp'
           70             IconButton:
           71                 icon: f'atlas://{KIVY_GUI_PATH}/theming/light/copy'
           72                 size_hint: 0.5, None
           73                 height: '48dp'
           74                 on_release: s.do_paste()
           75             IconButton:
           76                 icon: f'atlas://{KIVY_GUI_PATH}/theming/light/camera'
           77                 size_hint: 0.5, None
           78                 height: '48dp'
           79                 on_release: app.scan_qr(on_complete=s.on_qr)
           80             Button:
           81                 text: _('Suggest')
           82                 size_hint: 1, None
           83                 height: '48dp'
           84                 on_release: s.suggest_node()
           85             Button:
           86                 text: _('Clear')
           87                 size_hint: 1, None
           88                 height: '48dp'
           89                 on_release: s.do_clear()
           90         Widget:
           91             size_hint: 1, 1
           92         BoxLayout:
           93             size_hint: 1, None
           94             Widget:
           95                 size_hint: 2, None
           96             Button:
           97                 text: _('Open')
           98                 size_hint: 1, None
           99                 height: '48dp'
          100                 on_release: s.open_channel()
          101                 disabled: not root.pubkey or not root.amount
          102 ''')
          103 
          104 class LightningOpenChannelDialog(Factory.Popup, Logger):
          105     def ipport_dialog(self):
          106         def callback(text):
          107             self.ipport = text
          108         d = LabelDialog(_('IP/port in format:\n[host]:[port]'), self.ipport, callback)
          109         d.open()
          110 
          111     def suggest_node(self):
          112         if self.use_gossip:
          113             suggested = self.app.wallet.lnworker.suggest_peer()
          114             if suggested:
          115                 self.pubkey = suggested.hex()
          116             else:
          117                 _, _, percent = self.app.wallet.network.lngossip.get_sync_progress_estimate()
          118                 if percent is None:
          119                     percent = "??"
          120                 self.pubkey = f"Please wait, graph is updating ({percent}% / 30% done)."
          121         else:
          122             self.trampoline_index += 1
          123             self.trampoline_index = self.trampoline_index % len(self.trampoline_names)
          124             self.pubkey = self.trampoline_names[self.trampoline_index]
          125 
          126     def __init__(self, app, lnaddr=None, msg=None):
          127         Factory.Popup.__init__(self)
          128         Logger.__init__(self)
          129         self.app = app  # type: ElectrumWindow
          130         self.lnaddr = lnaddr
          131         self.msg = msg
          132         self.use_gossip = bool(self.app.network.channel_db)
          133         if not self.use_gossip:
          134             from electrum.lnworker import hardcoded_trampoline_nodes
          135             self.trampolines = hardcoded_trampoline_nodes()
          136             self.trampoline_names = list(self.trampolines.keys())
          137             self.trampoline_index = 0
          138             self.pubkey = ''
          139 
          140     def open(self, *args, **kwargs):
          141         super(LightningOpenChannelDialog, self).open(*args, **kwargs)
          142         if self.lnaddr:
          143             fee = self.app.electrum_config.fee_per_kb()
          144             if not fee:
          145                 fee = config.FEERATE_FALLBACK_STATIC_FEE
          146             self.amount = self.app.format_amount_and_units(self.lnaddr.amount * COIN + fee * 2)  # FIXME magic number?!
          147             self.pubkey = bh2u(self.lnaddr.pubkey.serialize())
          148         if self.msg:
          149             self.app.show_info(self.msg)
          150 
          151     def do_clear(self):
          152         self.pubkey = ''
          153         self.amount = ''
          154 
          155     def do_paste(self):
          156         contents = self.app._clipboard.paste()
          157         if not contents:
          158             self.app.show_info(_("Clipboard is empty"))
          159             return
          160         self.pubkey = contents
          161 
          162     def on_qr(self, conn_str):
          163         self.pubkey = conn_str
          164 
          165     # FIXME "max" button in amount_dialog should enforce LN_MAX_FUNDING_SAT
          166     def open_channel(self):
          167         if not self.pubkey or not self.amount:
          168             self.app.show_info(_('All fields must be filled out'))
          169             return
          170         if self.use_gossip:
          171             conn_str = self.pubkey
          172             if self.ipport:
          173                 conn_str += '@' + self.ipport.strip()
          174         else:
          175             conn_str = str(self.trampolines[self.pubkey])
          176         amount = '!' if self.is_max else self.app.get_amount(self.amount)
          177         self.app.protected('Create a new channel?', self.do_open_channel, (conn_str, amount))
          178         self.dismiss()
          179 
          180     def do_open_channel(self, conn_str, amount, password):
          181         coins = self.app.wallet.get_spendable_coins(None, nonlocal_only=True)
          182         lnworker = self.app.wallet.lnworker
          183         try:
          184             funding_tx = lnworker.mktx_for_open_channel(coins=coins, funding_sat=amount)
          185         except Exception as e:
          186             self.logger.exception("Problem opening channel")
          187             self.app.show_error(_('Problem opening channel: ') + '\n' + repr(e))
          188             return
          189         # read funding_sat from tx; converts '!' to int value
          190         funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
          191         try:
          192             chan, funding_tx = lnworker.open_channel(
          193                 connect_str=conn_str,
          194                 funding_tx=funding_tx,
          195                 funding_sat=funding_sat,
          196                 push_amt_sat=0,
          197                 password=password)
          198         except Exception as e:
          199             self.logger.exception("Problem opening channel")
          200             self.app.show_error(_('Problem opening channel: ') + '\n' + repr(e))
          201             return
          202         n = chan.constraints.funding_txn_minimum_depth
          203         message = '\n'.join([
          204             _('Channel established.'),
          205             _('Remote peer ID') + ':' + chan.node_id.hex(),
          206             _('This channel will be usable after {} confirmations').format(n)
          207         ])
          208         if not funding_tx.is_complete():
          209             message += '\n\n' + _('Please sign and broadcast the funding transaction')
          210         self.app.show_info(message)
          211         if not funding_tx.is_complete():
          212             self.app.tx_dialog(funding_tx)