mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-29 08:21:27 +00:00
Group swap transactions in Qt history (fixes #6237)
- use tree structure of QTreeView - grouped items have a 'group_id' field - rename 'Normal' swap as 'Forward'
This commit is contained in:
parent
f3c4b8698d
commit
4bda882695
6 changed files with 237 additions and 75 deletions
94
electrum/gui/qt/custom_model.py
Normal file
94
electrum/gui/qt/custom_model.py
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
# loosely based on
|
||||||
|
# http://trevorius.com/scrapbook/uncategorized/pyqt-custom-abstractitemmodel/
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
|
class CustomNode:
|
||||||
|
|
||||||
|
def __init__(self, model, data):
|
||||||
|
self.model = model
|
||||||
|
self._data = data
|
||||||
|
self._children = []
|
||||||
|
self._parent = None
|
||||||
|
self._row = 0
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return self._data
|
||||||
|
|
||||||
|
def get_data_for_role(self, index, role):
|
||||||
|
# define in child class
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def childCount(self):
|
||||||
|
return len(self._children)
|
||||||
|
|
||||||
|
def child(self, row):
|
||||||
|
if row >= 0 and row < self.childCount():
|
||||||
|
return self._children[row]
|
||||||
|
|
||||||
|
def parent(self):
|
||||||
|
return self._parent
|
||||||
|
|
||||||
|
def row(self):
|
||||||
|
return self._row
|
||||||
|
|
||||||
|
def addChild(self, child):
|
||||||
|
child._parent = self
|
||||||
|
child._row = len(self._children)
|
||||||
|
self._children.append(child)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CustomModel(QtCore.QAbstractItemModel):
|
||||||
|
|
||||||
|
def __init__(self, parent, columncount):
|
||||||
|
QtCore.QAbstractItemModel.__init__(self, parent)
|
||||||
|
self._root = CustomNode(self, None)
|
||||||
|
self._columncount = columncount
|
||||||
|
|
||||||
|
def rowCount(self, index):
|
||||||
|
if index.isValid():
|
||||||
|
return index.internalPointer().childCount()
|
||||||
|
return self._root.childCount()
|
||||||
|
|
||||||
|
def columnCount(self, index):
|
||||||
|
return self._columncount
|
||||||
|
|
||||||
|
def addChild(self, node, _parent):
|
||||||
|
if not _parent or not _parent.isValid():
|
||||||
|
parent = self._root
|
||||||
|
else:
|
||||||
|
parent = _parent.internalPointer()
|
||||||
|
parent.addChild(self, node)
|
||||||
|
|
||||||
|
def index(self, row, column, _parent=None):
|
||||||
|
if not _parent or not _parent.isValid():
|
||||||
|
parent = self._root
|
||||||
|
else:
|
||||||
|
parent = _parent.internalPointer()
|
||||||
|
|
||||||
|
if not QtCore.QAbstractItemModel.hasIndex(self, row, column, _parent):
|
||||||
|
return QtCore.QModelIndex()
|
||||||
|
|
||||||
|
child = parent.child(row)
|
||||||
|
if child:
|
||||||
|
return QtCore.QAbstractItemModel.createIndex(self, row, column, child)
|
||||||
|
else:
|
||||||
|
return QtCore.QModelIndex()
|
||||||
|
|
||||||
|
def parent(self, index):
|
||||||
|
if index.isValid():
|
||||||
|
node = index.internalPointer()
|
||||||
|
if node:
|
||||||
|
p = node.parent()
|
||||||
|
if p:
|
||||||
|
return QtCore.QAbstractItemModel.createIndex(self, p.row(), 0, p)
|
||||||
|
else:
|
||||||
|
return QtCore.QModelIndex()
|
||||||
|
return QtCore.QModelIndex()
|
||||||
|
|
||||||
|
def data(self, index, role):
|
||||||
|
if not index.isValid():
|
||||||
|
return None
|
||||||
|
node = index.internalPointer()
|
||||||
|
return node.get_data_for_role(index, role)
|
|
@ -43,9 +43,10 @@ from electrum.address_synchronizer import TX_HEIGHT_LOCAL, TX_HEIGHT_FUTURE
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import (block_explorer_URL, profiler, TxMinedInfo,
|
from electrum.util import (block_explorer_URL, profiler, TxMinedInfo,
|
||||||
OrderedDictWithIndex, timestamp_to_datetime,
|
OrderedDictWithIndex, timestamp_to_datetime,
|
||||||
Satoshis, format_time)
|
Satoshis, Fiat, format_time)
|
||||||
from electrum.logging import get_logger, Logger
|
from electrum.logging import get_logger, Logger
|
||||||
|
|
||||||
|
from .custom_model import CustomNode, CustomModel
|
||||||
from .util import (read_QIcon, MONOSPACE_FONT, Buttons, CancelButton, OkButton,
|
from .util import (read_QIcon, MONOSPACE_FONT, Buttons, CancelButton, OkButton,
|
||||||
filename_field, MyTreeView, AcceptFileDragDrop, WindowModalDialog,
|
filename_field, MyTreeView, AcceptFileDragDrop, WindowModalDialog,
|
||||||
CloseButton, webopen)
|
CloseButton, webopen)
|
||||||
|
@ -106,37 +107,16 @@ class HistorySortModel(QSortFilterProxyModel):
|
||||||
def get_item_key(tx_item):
|
def get_item_key(tx_item):
|
||||||
return tx_item.get('txid') or tx_item['payment_hash']
|
return tx_item.get('txid') or tx_item['payment_hash']
|
||||||
|
|
||||||
class HistoryModel(QAbstractItemModel, Logger):
|
|
||||||
|
|
||||||
def __init__(self, parent: 'ElectrumWindow'):
|
class HistoryNode(CustomNode):
|
||||||
QAbstractItemModel.__init__(self, parent)
|
|
||||||
Logger.__init__(self)
|
|
||||||
self.parent = parent
|
|
||||||
self.view = None # type: HistoryList
|
|
||||||
self.transactions = OrderedDictWithIndex()
|
|
||||||
self.tx_status_cache = {} # type: Dict[str, Tuple[int, str]]
|
|
||||||
|
|
||||||
def set_view(self, history_list: 'HistoryList'):
|
def get_data_for_role(self, index: QModelIndex, role: Qt.ItemDataRole) -> QVariant:
|
||||||
# FIXME HistoryModel and HistoryList mutually depend on each other.
|
|
||||||
# After constructing both, this method needs to be called.
|
|
||||||
self.view = history_list # type: HistoryList
|
|
||||||
self.set_visibility_of_columns()
|
|
||||||
|
|
||||||
def columnCount(self, parent: QModelIndex):
|
|
||||||
return len(HistoryColumns)
|
|
||||||
|
|
||||||
def rowCount(self, parent: QModelIndex):
|
|
||||||
return len(self.transactions)
|
|
||||||
|
|
||||||
def index(self, row: int, column: int, parent: QModelIndex):
|
|
||||||
return self.createIndex(row, column)
|
|
||||||
|
|
||||||
def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> QVariant:
|
|
||||||
# note: this method is performance-critical.
|
# note: this method is performance-critical.
|
||||||
# it is called a lot, and so must run extremely fast.
|
# it is called a lot, and so must run extremely fast.
|
||||||
assert index.isValid()
|
assert index.isValid()
|
||||||
col = index.column()
|
col = index.column()
|
||||||
tx_item = self.transactions.value_from_pos(index.row())
|
window = self.model.parent
|
||||||
|
tx_item = self.get_data()
|
||||||
is_lightning = tx_item.get('lightning', False)
|
is_lightning = tx_item.get('lightning', False)
|
||||||
timestamp = tx_item['timestamp']
|
timestamp = tx_item['timestamp']
|
||||||
if is_lightning:
|
if is_lightning:
|
||||||
|
@ -149,10 +129,10 @@ class HistoryModel(QAbstractItemModel, Logger):
|
||||||
tx_hash = tx_item['txid']
|
tx_hash = tx_item['txid']
|
||||||
conf = tx_item['confirmations']
|
conf = tx_item['confirmations']
|
||||||
try:
|
try:
|
||||||
status, status_str = self.tx_status_cache[tx_hash]
|
status, status_str = self.model.tx_status_cache[tx_hash]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
tx_mined_info = self.tx_mined_info_from_tx_item(tx_item)
|
tx_mined_info = self.model.tx_mined_info_from_tx_item(tx_item)
|
||||||
status, status_str = self.parent.wallet.get_tx_status(tx_hash, tx_mined_info)
|
status, status_str = window.wallet.get_tx_status(tx_hash, tx_mined_info)
|
||||||
|
|
||||||
if role == Qt.UserRole:
|
if role == Qt.UserRole:
|
||||||
# for sorting
|
# for sorting
|
||||||
|
@ -217,37 +197,48 @@ class HistoryModel(QAbstractItemModel, Logger):
|
||||||
bc_value = tx_item['bc_value'].value if 'bc_value' in tx_item else 0
|
bc_value = tx_item['bc_value'].value if 'bc_value' in tx_item else 0
|
||||||
ln_value = tx_item['ln_value'].value if 'ln_value' in tx_item else 0
|
ln_value = tx_item['ln_value'].value if 'ln_value' in tx_item else 0
|
||||||
value = bc_value + ln_value
|
value = bc_value + ln_value
|
||||||
v_str = self.parent.format_amount(value, is_diff=True, whitespaces=True)
|
v_str = window.format_amount(value, is_diff=True, whitespaces=True)
|
||||||
return QVariant(v_str)
|
return QVariant(v_str)
|
||||||
elif col == HistoryColumns.BALANCE:
|
elif col == HistoryColumns.BALANCE:
|
||||||
balance = tx_item['balance'].value
|
balance = tx_item['balance'].value
|
||||||
balance_str = self.parent.format_amount(balance, whitespaces=True)
|
balance_str = window.format_amount(balance, whitespaces=True)
|
||||||
return QVariant(balance_str)
|
return QVariant(balance_str)
|
||||||
elif col == HistoryColumns.FIAT_VALUE and 'fiat_value' in tx_item:
|
elif col == HistoryColumns.FIAT_VALUE and 'fiat_value' in tx_item:
|
||||||
value_str = self.parent.fx.format_fiat(tx_item['fiat_value'].value)
|
value_str = window.fx.format_fiat(tx_item['fiat_value'].value)
|
||||||
return QVariant(value_str)
|
return QVariant(value_str)
|
||||||
elif col == HistoryColumns.FIAT_ACQ_PRICE and \
|
elif col == HistoryColumns.FIAT_ACQ_PRICE and \
|
||||||
tx_item['value'].value < 0 and 'acquisition_price' in tx_item:
|
tx_item['value'].value < 0 and 'acquisition_price' in tx_item:
|
||||||
# fixme: should use is_mine
|
# fixme: should use is_mine
|
||||||
acq = tx_item['acquisition_price'].value
|
acq = tx_item['acquisition_price'].value
|
||||||
return QVariant(self.parent.fx.format_fiat(acq))
|
return QVariant(window.fx.format_fiat(acq))
|
||||||
elif col == HistoryColumns.FIAT_CAP_GAINS and 'capital_gain' in tx_item:
|
elif col == HistoryColumns.FIAT_CAP_GAINS and 'capital_gain' in tx_item:
|
||||||
cg = tx_item['capital_gain'].value
|
cg = tx_item['capital_gain'].value
|
||||||
return QVariant(self.parent.fx.format_fiat(cg))
|
return QVariant(window.fx.format_fiat(cg))
|
||||||
elif col == HistoryColumns.TXID:
|
elif col == HistoryColumns.TXID:
|
||||||
return QVariant(tx_hash) if not is_lightning else QVariant('')
|
return QVariant(tx_hash) if not is_lightning else QVariant('')
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
def parent(self, index: QModelIndex):
|
|
||||||
return QModelIndex()
|
|
||||||
|
|
||||||
def hasChildren(self, index: QModelIndex):
|
class HistoryModel(CustomModel, Logger):
|
||||||
return not index.isValid()
|
|
||||||
|
|
||||||
def update_label(self, row):
|
def __init__(self, parent: 'ElectrumWindow'):
|
||||||
tx_item = self.transactions.value_from_pos(row)
|
CustomModel.__init__(self, parent, len(HistoryColumns))
|
||||||
|
Logger.__init__(self)
|
||||||
|
self.parent = parent
|
||||||
|
self.view = None # type: HistoryList
|
||||||
|
self.transactions = OrderedDictWithIndex()
|
||||||
|
self.tx_status_cache = {} # type: Dict[str, Tuple[int, str]]
|
||||||
|
|
||||||
|
def set_view(self, history_list: 'HistoryList'):
|
||||||
|
# FIXME HistoryModel and HistoryList mutually depend on each other.
|
||||||
|
# After constructing both, this method needs to be called.
|
||||||
|
self.view = history_list # type: HistoryList
|
||||||
|
self.set_visibility_of_columns()
|
||||||
|
|
||||||
|
def update_label(self, index):
|
||||||
|
tx_item = index.internalPointer().get_data()
|
||||||
tx_item['label'] = self.parent.wallet.get_label(get_item_key(tx_item))
|
tx_item['label'] = self.parent.wallet.get_label(get_item_key(tx_item))
|
||||||
topLeft = bottomRight = self.createIndex(row, HistoryColumns.DESCRIPTION)
|
topLeft = bottomRight = self.createIndex(index.row(), HistoryColumns.DESCRIPTION)
|
||||||
self.dataChanged.emit(topLeft, bottomRight, [Qt.DisplayRole])
|
self.dataChanged.emit(topLeft, bottomRight, [Qt.DisplayRole])
|
||||||
self.parent.utxo_list.update()
|
self.parent.utxo_list.update()
|
||||||
|
|
||||||
|
@ -280,14 +271,56 @@ class HistoryModel(QAbstractItemModel, Logger):
|
||||||
include_lightning=self.should_include_lightning_payments())
|
include_lightning=self.should_include_lightning_payments())
|
||||||
if transactions == list(self.transactions.values()):
|
if transactions == list(self.transactions.values()):
|
||||||
return
|
return
|
||||||
old_length = len(self.transactions)
|
old_length = self._root.childCount()
|
||||||
if old_length != 0:
|
if old_length != 0:
|
||||||
self.beginRemoveRows(QModelIndex(), 0, old_length)
|
self.beginRemoveRows(QModelIndex(), 0, old_length)
|
||||||
self.transactions.clear()
|
self.transactions.clear()
|
||||||
|
self._root = HistoryNode(self, None)
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
self.beginInsertRows(QModelIndex(), 0, len(transactions)-1)
|
parents = {}
|
||||||
|
for tx_item in transactions.values():
|
||||||
|
node = HistoryNode(self, tx_item)
|
||||||
|
group_id = tx_item.get('group_id')
|
||||||
|
if group_id is None:
|
||||||
|
self._root.addChild(node)
|
||||||
|
else:
|
||||||
|
parent = parents.get(group_id)
|
||||||
|
if parent is None:
|
||||||
|
# create parent if it does not exist
|
||||||
|
self._root.addChild(node)
|
||||||
|
parents[group_id] = node
|
||||||
|
else:
|
||||||
|
# if parent has no children, create two children
|
||||||
|
if parent.childCount() == 0:
|
||||||
|
child_data = dict(parent.get_data())
|
||||||
|
node1 = HistoryNode(self, child_data)
|
||||||
|
parent.addChild(node1)
|
||||||
|
parent._data['label'] = tx_item.get('group_label')
|
||||||
|
parent._data['bc_value'] = child_data.get('bc_value', Satoshis(0))
|
||||||
|
parent._data['ln_value'] = child_data.get('ln_value', Satoshis(0))
|
||||||
|
# add child to parent
|
||||||
|
parent.addChild(node)
|
||||||
|
# update parent data
|
||||||
|
parent._data['balance'] = tx_item['balance']
|
||||||
|
parent._data['value'] += tx_item['value']
|
||||||
|
if 'bc_value' in tx_item:
|
||||||
|
parent._data['bc_value'] += tx_item['bc_value']
|
||||||
|
if 'ln_value' in tx_item:
|
||||||
|
parent._data['ln_value'] += tx_item['ln_value']
|
||||||
|
if 'fiat_value' in tx_item:
|
||||||
|
parent._data['fiat_value'] += tx_item['fiat_value']
|
||||||
|
if tx_item.get('txid') == group_id:
|
||||||
|
parent._data['lightning'] = False
|
||||||
|
parent._data['txid'] = tx_item['txid']
|
||||||
|
parent._data['timestamp'] = tx_item['timestamp']
|
||||||
|
parent._data['height'] = tx_item['height']
|
||||||
|
parent._data['confirmations'] = tx_item['confirmations']
|
||||||
|
|
||||||
|
new_length = self._root.childCount()
|
||||||
|
self.beginInsertRows(QModelIndex(), 0, new_length-1)
|
||||||
self.transactions = transactions
|
self.transactions = transactions
|
||||||
self.endInsertRows()
|
self.endInsertRows()
|
||||||
|
|
||||||
if selected_row:
|
if selected_row:
|
||||||
self.view.selectionModel().select(self.createIndex(selected_row, 0), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent)
|
self.view.selectionModel().select(self.createIndex(selected_row, 0), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent)
|
||||||
self.view.filter()
|
self.view.filter()
|
||||||
|
@ -319,8 +352,8 @@ class HistoryModel(QAbstractItemModel, Logger):
|
||||||
set_visible(HistoryColumns.FIAT_ACQ_PRICE, history and cap_gains)
|
set_visible(HistoryColumns.FIAT_ACQ_PRICE, history and cap_gains)
|
||||||
set_visible(HistoryColumns.FIAT_CAP_GAINS, history and cap_gains)
|
set_visible(HistoryColumns.FIAT_CAP_GAINS, history and cap_gains)
|
||||||
|
|
||||||
def update_fiat(self, row, idx):
|
def update_fiat(self, idx):
|
||||||
tx_item = self.transactions.value_from_pos(row)
|
tx_item = idx.internalPointer().get_data()
|
||||||
key = tx_item['txid']
|
key = tx_item['txid']
|
||||||
fee = tx_item.get('fee')
|
fee = tx_item.get('fee')
|
||||||
value = tx_item['value'].value
|
value = tx_item['value'].value
|
||||||
|
@ -399,7 +432,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||||
|
|
||||||
def tx_item_from_proxy_row(self, proxy_row):
|
def tx_item_from_proxy_row(self, proxy_row):
|
||||||
hm_idx = self.model().mapToSource(self.model().index(proxy_row, 0))
|
hm_idx = self.model().mapToSource(self.model().index(proxy_row, 0))
|
||||||
return self.hm.transactions.value_from_pos(hm_idx.row())
|
return hm_idx.internalPointer().get_data()
|
||||||
|
|
||||||
def should_hide(self, proxy_row):
|
def should_hide(self, proxy_row):
|
||||||
if self.start_timestamp and self.end_timestamp:
|
if self.start_timestamp and self.end_timestamp:
|
||||||
|
@ -427,7 +460,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||||
self.wallet = self.parent.wallet # type: Abstract_Wallet
|
self.wallet = self.parent.wallet # type: Abstract_Wallet
|
||||||
self.sortByColumn(HistoryColumns.STATUS, Qt.AscendingOrder)
|
self.sortByColumn(HistoryColumns.STATUS, Qt.AscendingOrder)
|
||||||
self.editable_columns |= {HistoryColumns.FIAT_VALUE}
|
self.editable_columns |= {HistoryColumns.FIAT_VALUE}
|
||||||
|
self.setRootIsDecorated(True)
|
||||||
self.header().setStretchLastSection(False)
|
self.header().setStretchLastSection(False)
|
||||||
for col in HistoryColumns:
|
for col in HistoryColumns:
|
||||||
sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents
|
sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents
|
||||||
|
@ -563,18 +596,18 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||||
|
|
||||||
def on_edited(self, index, user_role, text):
|
def on_edited(self, index, user_role, text):
|
||||||
index = self.model().mapToSource(index)
|
index = self.model().mapToSource(index)
|
||||||
row, column = index.row(), index.column()
|
tx_item = index.internalPointer().get_data()
|
||||||
tx_item = self.hm.transactions.value_from_pos(row)
|
column = index.column()
|
||||||
key = get_item_key(tx_item)
|
key = get_item_key(tx_item)
|
||||||
if column == HistoryColumns.DESCRIPTION:
|
if column == HistoryColumns.DESCRIPTION:
|
||||||
if self.wallet.set_label(key, text): #changed
|
if self.wallet.set_label(key, text): #changed
|
||||||
self.hm.update_label(row)
|
self.hm.update_label(index)
|
||||||
self.parent.update_completions()
|
self.parent.update_completions()
|
||||||
elif column == HistoryColumns.FIAT_VALUE:
|
elif column == HistoryColumns.FIAT_VALUE:
|
||||||
self.wallet.set_fiat_value(key, self.parent.fx.ccy, text, self.parent.fx, tx_item['value'].value)
|
self.wallet.set_fiat_value(key, self.parent.fx.ccy, text, self.parent.fx, tx_item['value'].value)
|
||||||
value = tx_item['value'].value
|
value = tx_item['value'].value
|
||||||
if value is not None:
|
if value is not None:
|
||||||
self.hm.update_fiat(row, index)
|
self.hm.update_fiat(index)
|
||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
@ -621,7 +654,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||||
if not idx.isValid():
|
if not idx.isValid():
|
||||||
# can happen e.g. before list is populated for the first time
|
# can happen e.g. before list is populated for the first time
|
||||||
return
|
return
|
||||||
tx_item = self.hm.transactions.value_from_pos(idx.row())
|
tx_item = idx.internalPointer().get_data()
|
||||||
if tx_item.get('lightning') and tx_item['type'] == 'payment':
|
if tx_item.get('lightning') and tx_item['type'] == 'payment':
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
menu.addAction(_("View Payment"), lambda: self.parent.show_lightning_transaction(tx_item))
|
menu.addAction(_("View Payment"), lambda: self.parent.show_lightning_transaction(tx_item))
|
||||||
|
@ -758,5 +791,5 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||||
|
|
||||||
def get_text_and_userrole_from_coordinate(self, row, col):
|
def get_text_and_userrole_from_coordinate(self, row, col):
|
||||||
idx = self.model().mapToSource(self.model().index(row, col))
|
idx = self.model().mapToSource(self.model().index(row, col))
|
||||||
tx_item = self.hm.transactions.value_from_pos(idx.row())
|
tx_item = idx.internalPointer().get_data()
|
||||||
return self.hm.data(idx, Qt.DisplayRole).value(), get_item_key(tx_item)
|
return self.hm.data(idx, Qt.DisplayRole).value(), get_item_key(tx_item)
|
||||||
|
|
|
@ -633,15 +633,15 @@ class LNWallet(LNWorker):
|
||||||
'payment_hash': key,
|
'payment_hash': key,
|
||||||
'preimage': preimage,
|
'preimage': preimage,
|
||||||
}
|
}
|
||||||
# add txid to merge item with onchain item
|
# add group_id to swap transactions
|
||||||
swap = self.swap_manager.get_swap(payment_hash)
|
swap = self.swap_manager.get_swap(payment_hash)
|
||||||
if swap:
|
if swap:
|
||||||
if swap.is_reverse:
|
if swap.is_reverse:
|
||||||
#item['txid'] = swap.spending_txid
|
item['group_id'] = swap.spending_txid
|
||||||
item['label'] = 'Reverse swap' + ' ' + self.config.format_amount_and_units(swap.lightning_amount)
|
item['group_label'] = 'Reverse swap' + ' ' + self.config.format_amount_and_units(swap.lightning_amount)
|
||||||
else:
|
else:
|
||||||
#item['txid'] = swap.funding_txid
|
item['group_id'] = swap.funding_txid
|
||||||
item['label'] = 'Normal swap' + ' ' + self.config.format_amount_and_units(swap.onchain_amount)
|
item['group_label'] = 'Forward swap' + ' ' + self.config.format_amount_and_units(swap.onchain_amount)
|
||||||
# done
|
# done
|
||||||
out[payment_hash] = item
|
out[payment_hash] = item
|
||||||
return out
|
return out
|
||||||
|
@ -680,6 +680,31 @@ class LNWallet(LNWorker):
|
||||||
'fee_msat': None,
|
'fee_msat': None,
|
||||||
}
|
}
|
||||||
out[closing_txid] = item
|
out[closing_txid] = item
|
||||||
|
# add info about submarine swaps
|
||||||
|
settled_payments = self.get_settled_payments()
|
||||||
|
current_height = self.network.get_local_height()
|
||||||
|
for payment_hash_hex, swap in self.swap_manager.swaps.items():
|
||||||
|
txid = swap.spending_txid if swap.is_reverse else swap.funding_txid
|
||||||
|
if txid is None:
|
||||||
|
continue
|
||||||
|
if payment_hash_hex in settled_payments:
|
||||||
|
plist = settled_payments[payment_hash_hex]
|
||||||
|
info = self.get_payment_info(bytes.fromhex(payment_hash_hex))
|
||||||
|
amount_msat, fee_msat, timestamp = self.get_payment_value(info, plist)
|
||||||
|
else:
|
||||||
|
amount_msat = 0
|
||||||
|
label = 'Reverse swap' if swap.is_reverse else 'Forward swap'
|
||||||
|
delta = current_height - swap.locktime
|
||||||
|
if not swap.is_redeemed and swap.spending_txid is None and delta < 0:
|
||||||
|
label += f' (refundable in {-delta} blocks)' # fixme: only if unspent
|
||||||
|
out[txid] = {
|
||||||
|
'txid': txid,
|
||||||
|
'group_id': txid,
|
||||||
|
'amount_msat': 0,
|
||||||
|
#'amount_msat': amount_msat, # must not be added
|
||||||
|
'type': 'swap',
|
||||||
|
'label': label
|
||||||
|
}
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def get_history(self):
|
def get_history(self):
|
||||||
|
|
|
@ -197,6 +197,9 @@ class SwapManager(Logger):
|
||||||
swap = self.swaps.get(payment_hash.hex())
|
swap = self.swaps.get(payment_hash.hex())
|
||||||
if swap:
|
if swap:
|
||||||
return swap
|
return swap
|
||||||
|
payment_hash = self.prepayments.get(payment_hash)
|
||||||
|
if payment_hash:
|
||||||
|
return self.swaps.get(payment_hash.hex())
|
||||||
|
|
||||||
def add_lnwatcher_callback(self, swap: SwapData) -> None:
|
def add_lnwatcher_callback(self, swap: SwapData) -> None:
|
||||||
callback = lambda: self._claim_swap(swap)
|
callback = lambda: self._claim_swap(swap)
|
||||||
|
@ -361,6 +364,7 @@ class SwapManager(Logger):
|
||||||
self.add_lnwatcher_callback(swap)
|
self.add_lnwatcher_callback(swap)
|
||||||
# initiate payment.
|
# initiate payment.
|
||||||
if fee_invoice:
|
if fee_invoice:
|
||||||
|
self.prepayments[prepay_hash] = preimage_hash
|
||||||
asyncio.ensure_future(self.lnworker._pay(fee_invoice, attempts=10))
|
asyncio.ensure_future(self.lnworker._pay(fee_invoice, attempts=10))
|
||||||
# initiate payment.
|
# initiate payment.
|
||||||
success, log = await self.lnworker._pay(invoice, attempts=10)
|
success, log = await self.lnworker._pay(invoice, attempts=10)
|
||||||
|
|
|
@ -177,6 +177,9 @@ class Satoshis(object):
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not (self == other)
|
return not (self == other)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return Satoshis(self.value + other.value)
|
||||||
|
|
||||||
|
|
||||||
# note: this is not a NamedTuple as then its json encoding cannot be customized
|
# note: this is not a NamedTuple as then its json encoding cannot be customized
|
||||||
class Fiat(object):
|
class Fiat(object):
|
||||||
|
@ -216,6 +219,10 @@ class Fiat(object):
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not (self == other)
|
return not (self == other)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
assert self.ccy == other.ccy
|
||||||
|
return Fiat(self.value + other.value, self.ccy)
|
||||||
|
|
||||||
|
|
||||||
class MyEncoder(json.JSONEncoder):
|
class MyEncoder(json.JSONEncoder):
|
||||||
def default(self, obj):
|
def default(self, obj):
|
||||||
|
|
|
@ -820,24 +820,23 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||||
transactions_tmp = OrderedDictWithIndex()
|
transactions_tmp = OrderedDictWithIndex()
|
||||||
# add on-chain txns
|
# add on-chain txns
|
||||||
onchain_history = self.get_onchain_history(domain=onchain_domain)
|
onchain_history = self.get_onchain_history(domain=onchain_domain)
|
||||||
|
lnworker_history = self.lnworker.get_onchain_history() if self.lnworker and include_lightning else {}
|
||||||
for tx_item in onchain_history:
|
for tx_item in onchain_history:
|
||||||
txid = tx_item['txid']
|
txid = tx_item['txid']
|
||||||
transactions_tmp[txid] = tx_item
|
transactions_tmp[txid] = tx_item
|
||||||
# add LN txns
|
# add lnworker info here
|
||||||
if self.lnworker and include_lightning:
|
if txid in lnworker_history:
|
||||||
lightning_history = self.lnworker.get_history()
|
item = lnworker_history[txid]
|
||||||
else:
|
tx_item['group_id'] = item.get('group_id') # for swaps
|
||||||
lightning_history = []
|
tx_item['label'] = item['label']
|
||||||
for i, tx_item in enumerate(lightning_history):
|
tx_item['type'] = item['type']
|
||||||
|
ln_value = Decimal(item['amount_msat']) / 1000 # for channel open/close tx
|
||||||
|
tx_item['ln_value'] = Satoshis(ln_value)
|
||||||
|
# add lightning_transactions
|
||||||
|
lightning_history = self.lnworker.get_lightning_history() if self.lnworker and include_lightning else {}
|
||||||
|
for tx_item in lightning_history.values():
|
||||||
txid = tx_item.get('txid')
|
txid = tx_item.get('txid')
|
||||||
ln_value = Decimal(tx_item['amount_msat']) / 1000
|
ln_value = Decimal(tx_item['amount_msat']) / 1000
|
||||||
if txid and txid in transactions_tmp:
|
|
||||||
item = transactions_tmp[txid]
|
|
||||||
item['label'] = tx_item['label']
|
|
||||||
item['type'] = tx_item['type']
|
|
||||||
item['channel_id'] = tx_item['channel_id']
|
|
||||||
item['ln_value'] = Satoshis(ln_value)
|
|
||||||
else:
|
|
||||||
tx_item['lightning'] = True
|
tx_item['lightning'] = True
|
||||||
tx_item['ln_value'] = Satoshis(ln_value)
|
tx_item['ln_value'] = Satoshis(ln_value)
|
||||||
key = tx_item.get('txid') or tx_item['payment_hash']
|
key = tx_item.get('txid') or tx_item['payment_hash']
|
||||||
|
|
Loading…
Add table
Reference in a new issue