|
|
@ -1,9 +1,12 @@ |
|
|
|
import asyncio |
|
|
|
import binascii |
|
|
|
from kivy.lang import Builder |
|
|
|
from kivy.factory import Factory |
|
|
|
from kivy.uix.popup import Popup |
|
|
|
from kivy.clock import Clock |
|
|
|
from electrum.gui.kivy.uix.context_menu import ContextMenu |
|
|
|
from electrum.util import bh2u |
|
|
|
from electrum.lnutil import LOCAL, REMOTE |
|
|
|
|
|
|
|
Builder.load_string(''' |
|
|
|
<LightningChannelItem@CardItem> |
|
|
@ -15,6 +18,7 @@ Builder.load_string(''' |
|
|
|
|
|
|
|
<LightningChannelsDialog@Popup>: |
|
|
|
name: 'lightning_channels' |
|
|
|
title: 'Lightning channels. Tap to select.' |
|
|
|
BoxLayout: |
|
|
|
id: box |
|
|
|
orientation: 'vertical' |
|
|
@ -86,19 +90,38 @@ class LightningChannelsDialog(Factory.Popup): |
|
|
|
self.clocks = [] |
|
|
|
self.app = app |
|
|
|
self.context_menu = None |
|
|
|
self.app.wallet.lnworker.subscribe_channel_list_updates_from_other_thread(self.rpc_result_handler) |
|
|
|
self.app.wallet.network.register_callback(self.channels_update, ['channels']) |
|
|
|
self.channels_update('bogus evt') |
|
|
|
|
|
|
|
def show_channel_details(self, obj): |
|
|
|
p = Factory.ChannelDetailsPopup() |
|
|
|
p.title = 'Lightning channels details for ' + self.presentable_chan_id(obj._chan) |
|
|
|
p.data = [{'keyName': key, 'value': str(obj.details[key])} for key in obj.details.keys()] |
|
|
|
p.open() |
|
|
|
|
|
|
|
def close_channel(self, obj): |
|
|
|
print("UNIMPLEMENTED asked to close channel", obj.channelId) # TODO |
|
|
|
loop = self.app.wallet.network.asyncio_loop |
|
|
|
coro = asyncio.run_coroutine_threadsafe(self.app.wallet.lnworker.close_channel(obj._chan.channel_id), loop) |
|
|
|
try: |
|
|
|
coro.result(5) |
|
|
|
self.app.show_info('Channel closed') |
|
|
|
except Exception as e: |
|
|
|
self.app.show_info('Could not close channel: ' + repr(e)) # repr because str(Exception()) == '' |
|
|
|
|
|
|
|
def force_close_channel(self, obj): |
|
|
|
loop = self.app.wallet.network.asyncio_loop |
|
|
|
coro = asyncio.run_coroutine_threadsafe(self.app.wallet.lnworker.force_close_channel(obj._chan.channel_id), loop) |
|
|
|
try: |
|
|
|
coro.result(1) |
|
|
|
self.app.show_info('Channel closed, you may need to wait at least ' + str(obj._chan.config[REMOTE].to_self_delay) + ' blocks, because of CSV delays') |
|
|
|
except Exception as e: |
|
|
|
self.app.show_info('Could not force close channel: ' + repr(e)) # repr because str(Exception()) == '' |
|
|
|
|
|
|
|
def show_menu(self, obj): |
|
|
|
self.hide_menu() |
|
|
|
self.context_menu = ContextMenu(obj, [("Close", self.close_channel), |
|
|
|
self.context_menu = ContextMenu(obj, [ |
|
|
|
("Force close", self.force_close_channel), |
|
|
|
("Co-op close", self.close_channel), |
|
|
|
("Details", self.show_channel_details)]) |
|
|
|
self.ids.box.add_widget(self.context_menu) |
|
|
|
|
|
|
@ -107,17 +130,27 @@ class LightningChannelsDialog(Factory.Popup): |
|
|
|
self.ids.box.remove_widget(self.context_menu) |
|
|
|
self.context_menu = None |
|
|
|
|
|
|
|
def rpc_result_handler(self, res): |
|
|
|
def presentable_chan_id(self, i): |
|
|
|
return bh2u(i.short_channel_id) if i.short_channel_id else bh2u(i.channel_id)[:16] |
|
|
|
|
|
|
|
def channels_update(self, evt): |
|
|
|
channel_cards = self.ids.lightning_channels_container |
|
|
|
channel_cards.clear_widgets() |
|
|
|
if "channels" in res: |
|
|
|
for i in res["channels"]: |
|
|
|
lnworker = self.app.wallet.lnworker |
|
|
|
for i in lnworker.channels.values(): |
|
|
|
item = Factory.LightningChannelItem() |
|
|
|
item.screen = self |
|
|
|
print(i) |
|
|
|
item.channelId = i["chan_id"] |
|
|
|
item.active = i["active"] |
|
|
|
item.details = i |
|
|
|
item.channelId = self.presentable_chan_id(i) |
|
|
|
item.active = i.node_id in lnworker.peers |
|
|
|
item.details = self.channel_details(i) |
|
|
|
item._chan = i |
|
|
|
channel_cards.add_widget(item) |
|
|
|
else: |
|
|
|
self.app.show_info(res) |
|
|
|
|
|
|
|
def channel_details(self, chan): |
|
|
|
return {'Node ID': bh2u(chan.node_id), |
|
|
|
'Channel ID': bh2u(chan.channel_id), |
|
|
|
'Capacity': self.app.format_amount_and_units(chan.constraints.capacity), |
|
|
|
'Funding TXID': chan.funding_outpoint.txid, |
|
|
|
'Short Chan ID': bh2u(chan.short_channel_id) if chan.short_channel_id else 'Not available', |
|
|
|
'Available to spend': self.app.format_amount_and_units(chan.available_to_spend(LOCAL) // 1000), |
|
|
|
'State': chan.get_state()} |
|
|
|