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)