simplify history-related commands:
- reduce number of methods - use nametuples instead of dicts - only two types: OnchainHistoryItem and LightningHistoryItem - channel open/closes are groups - move capital gains into separate RPC
This commit is contained in:
@@ -816,26 +816,26 @@ class Commands:
|
|||||||
await self.addtransaction(result, wallet=wallet)
|
await self.addtransaction(result, wallet=wallet)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@command('w')
|
def get_year_timestamps(self, year:int):
|
||||||
async def onchain_history(self, year=None, show_addresses=False, show_fiat=False, wallet: Abstract_Wallet = None,
|
kwargs = {}
|
||||||
from_height=None, to_height=None):
|
|
||||||
"""Wallet onchain history. Returns the transaction history of your wallet."""
|
|
||||||
kwargs = {
|
|
||||||
'show_addresses': show_addresses,
|
|
||||||
'from_height': from_height,
|
|
||||||
'to_height': to_height,
|
|
||||||
}
|
|
||||||
if year:
|
if year:
|
||||||
import time
|
import time
|
||||||
start_date = datetime.datetime(year, 1, 1)
|
start_date = datetime.datetime(year, 1, 1)
|
||||||
end_date = datetime.datetime(year+1, 1, 1)
|
end_date = datetime.datetime(year+1, 1, 1)
|
||||||
kwargs['from_timestamp'] = time.mktime(start_date.timetuple())
|
kwargs['from_timestamp'] = time.mktime(start_date.timetuple())
|
||||||
kwargs['to_timestamp'] = time.mktime(end_date.timetuple())
|
kwargs['to_timestamp'] = time.mktime(end_date.timetuple())
|
||||||
if show_fiat:
|
return kwargs
|
||||||
from .exchange_rate import FxThread
|
|
||||||
kwargs['fx'] = self.daemon.fx if self.daemon else FxThread(config=self.config)
|
|
||||||
|
|
||||||
return json_normalize(wallet.get_detailed_history(**kwargs))
|
@command('w')
|
||||||
|
async def onchain_capital_gains(self, year=None, wallet: Abstract_Wallet = None):
|
||||||
|
"""
|
||||||
|
Capital gains, using utxo pricing.
|
||||||
|
This cannot be used with lightning.
|
||||||
|
"""
|
||||||
|
kwargs = self.get_year_timestamps(year)
|
||||||
|
from .exchange_rate import FxThread
|
||||||
|
fx = self.daemon.fx if self.daemon else FxThread(config=self.config)
|
||||||
|
return json_normalize(wallet.get_onchain_capital_gains(fx, **kwargs))
|
||||||
|
|
||||||
@command('wp')
|
@command('wp')
|
||||||
async def bumpfee(self, tx, new_fee_rate, from_coins=None, decrease_payment=False, password=None, unsigned=False, wallet: Abstract_Wallet = None):
|
async def bumpfee(self, tx, new_fee_rate, from_coins=None, decrease_payment=False, password=None, unsigned=False, wallet: Abstract_Wallet = None):
|
||||||
@@ -867,11 +867,34 @@ class Commands:
|
|||||||
wallet.sign_transaction(new_tx, password)
|
wallet.sign_transaction(new_tx, password)
|
||||||
return new_tx.serialize()
|
return new_tx.serialize()
|
||||||
|
|
||||||
|
@command('w')
|
||||||
|
async def onchain_history(self, show_fiat=False, year=None, show_addresses=False, wallet: Abstract_Wallet = None):
|
||||||
|
"""Wallet onchain history. Returns the transaction history of your wallet."""
|
||||||
|
kwargs = self.get_year_timestamps(year)
|
||||||
|
onchain_history = wallet.get_onchain_history(**kwargs)
|
||||||
|
out = [x.to_dict() for x in onchain_history.values()]
|
||||||
|
if show_fiat:
|
||||||
|
from .exchange_rate import FxThread
|
||||||
|
fx = self.daemon.fx if self.daemon else FxThread(config=self.config)
|
||||||
|
else:
|
||||||
|
fx = None
|
||||||
|
for item in out:
|
||||||
|
if show_addresses:
|
||||||
|
tx = wallet.db.get_transaction(item['txid'])
|
||||||
|
item['inputs'] = list(map(lambda x: x.to_json(), tx.inputs()))
|
||||||
|
item['outputs'] = list(map(lambda x: {'address': x.get_ui_address_str(), 'value_sat': x.value},
|
||||||
|
tx.outputs()))
|
||||||
|
if fx:
|
||||||
|
fiat_fields = wallet.get_tx_item_fiat(tx_hash=item['txid'], amount_sat=item['amount_sat'], fx=fx, tx_fee=item['fee_sat'])
|
||||||
|
item.update(fiat_fields)
|
||||||
|
return json_normalize(out)
|
||||||
|
|
||||||
@command('wl')
|
@command('wl')
|
||||||
async def lightning_history(self, show_fiat=False, wallet: Abstract_Wallet = None):
|
async def lightning_history(self, wallet: Abstract_Wallet = None):
|
||||||
""" lightning history """
|
""" lightning history. """
|
||||||
lightning_history = wallet.lnworker.get_history() if wallet.lnworker else []
|
lightning_history = wallet.lnworker.get_lightning_history() if wallet.lnworker else {}
|
||||||
return json_normalize(lightning_history)
|
sorted_hist= sorted(lightning_history.values(), key=lambda x: x.timestamp)
|
||||||
|
return json_normalize([x.to_dict() for x in sorted_hist])
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
async def setlabel(self, key, label, wallet: Abstract_Wallet = None):
|
async def setlabel(self, key, label, wallet: Abstract_Wallet = None):
|
||||||
|
|||||||
@@ -144,11 +144,13 @@ Pane {
|
|||||||
Layout.topMargin: constants.paddingSmall
|
Layout.topMargin: constants.paddingSmall
|
||||||
text: qsTr('Payment hash')
|
text: qsTr('Payment hash')
|
||||||
color: Material.accentColor
|
color: Material.accentColor
|
||||||
|
visible: lnpaymentdetails.paymentHash
|
||||||
}
|
}
|
||||||
|
|
||||||
TextHighlightPane {
|
TextHighlightPane {
|
||||||
Layout.columnSpan: 2
|
Layout.columnSpan: 2
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
visible: lnpaymentdetails.paymentHash
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -177,11 +179,13 @@ Pane {
|
|||||||
Layout.topMargin: constants.paddingSmall
|
Layout.topMargin: constants.paddingSmall
|
||||||
text: qsTr('Preimage')
|
text: qsTr('Preimage')
|
||||||
color: Material.accentColor
|
color: Material.accentColor
|
||||||
|
visible: lnpaymentdetails.preimage
|
||||||
}
|
}
|
||||||
|
|
||||||
TextHighlightPane {
|
TextHighlightPane {
|
||||||
Layout.columnSpan: 2
|
Layout.columnSpan: 2
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
visible: lnpaymentdetails.preimage
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -96,16 +96,16 @@ class QELnPaymentDetails(QObject):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# TODO this is horribly inefficient. need a payment getter/query method
|
# TODO this is horribly inefficient. need a payment getter/query method
|
||||||
tx = self._wallet.wallet.lnworker.get_lightning_history()[bfh(self._key)]
|
tx = self._wallet.wallet.lnworker.get_lightning_history()[self._key]
|
||||||
self._logger.debug(str(tx))
|
self._logger.debug(str(tx))
|
||||||
|
|
||||||
self._fee.msatsInt = 0 if not tx['fee_msat'] else int(tx['fee_msat'])
|
self._fee.msatsInt = 0 if not tx.fee_msat else int(tx.fee_msat)
|
||||||
self._amount.msatsInt = int(tx['amount_msat'])
|
self._amount.msatsInt = int(tx.amount_msat)
|
||||||
self._label = tx['label']
|
self._label = tx.label
|
||||||
self._date = format_time(tx['timestamp'])
|
self._date = format_time(tx.timestamp)
|
||||||
self._timestamp = tx['timestamp']
|
self._timestamp = tx.timestamp
|
||||||
self._status = 'settled' # TODO: other states? get_lightning_history is deciding the filter for us :(
|
self._status = 'settled' # TODO: other states? get_lightning_history is deciding the filter for us :(
|
||||||
self._phash = tx['payment_hash']
|
self._phash = tx.payment_hash
|
||||||
self._preimage = tx['preimage']
|
self._preimage = tx.preimage
|
||||||
|
|
||||||
self.detailsChanged.emit()
|
self.detailsChanged.emit()
|
||||||
|
|||||||
@@ -127,15 +127,14 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
|
|||||||
#self._logger.debug(str(tx_item))
|
#self._logger.debug(str(tx_item))
|
||||||
item = tx_item
|
item = tx_item
|
||||||
|
|
||||||
item['key'] = item['txid'] if 'txid' in item else item['payment_hash']
|
item['key'] = item.get('txid') or item.get('group_id') or item['payment_hash']
|
||||||
|
|
||||||
if 'lightning' not in item:
|
if 'lightning' not in item:
|
||||||
item['lightning'] = False
|
item['lightning'] = False
|
||||||
|
|
||||||
if item['lightning']:
|
if item['lightning']:
|
||||||
item['value'] = QEAmount(amount_sat=item['value'].value, amount_msat=item['amount_msat'])
|
item['value'] = QEAmount(amount_sat=item['value'].value, amount_msat=item['amount_msat'])
|
||||||
if item['type'] == 'payment':
|
item['incoming'] = True if item['amount_msat'] > 0 else False
|
||||||
item['incoming'] = True if item['direction'] == 'received' else False
|
|
||||||
item['confirmations'] = 0
|
item['confirmations'] = 0
|
||||||
else:
|
else:
|
||||||
item['value'] = QEAmount(amount_sat=item['value'].value)
|
item['value'] = QEAmount(amount_sat=item['value'].value)
|
||||||
|
|||||||
@@ -330,20 +330,13 @@ class QETxDetails(QObject, QtEventListener):
|
|||||||
self._sighash_danger = self._wallet.wallet.check_sighash(self._tx)
|
self._sighash_danger = self._wallet.wallet.check_sighash(self._tx)
|
||||||
|
|
||||||
if self._wallet.wallet.lnworker:
|
if self._wallet.wallet.lnworker:
|
||||||
# Calling lnworker.get_onchain_history and wallet.get_full_history here
|
# Calling wallet.get_full_history here is inefficient.
|
||||||
# is inefficient. We should probably pass the tx_item to the constructor.
|
# We should probably pass the tx_item to the constructor.
|
||||||
lnworker_history = self._wallet.wallet.lnworker.get_onchain_history()
|
full_history = self._wallet.wallet.get_full_history()
|
||||||
if self._txid in lnworker_history:
|
item = full_history.get('group:' + self._txid)
|
||||||
item = lnworker_history[self._txid]
|
self._lnamount.satsInt = int(item['ln_value'].value) if item else 0
|
||||||
group_id = item.get('group_id')
|
else:
|
||||||
if group_id:
|
self._lnamount.satsInt = 0
|
||||||
full_history = self._wallet.wallet.get_full_history()
|
|
||||||
group_item = full_history['group:' + group_id]
|
|
||||||
self._lnamount.satsInt = int(group_item['ln_value'].value)
|
|
||||||
else:
|
|
||||||
self._lnamount.satsInt = int(item['amount_msat'] / 1000)
|
|
||||||
else:
|
|
||||||
self._lnamount.satsInt = 0
|
|
||||||
|
|
||||||
self._is_complete = self._tx.is_complete()
|
self._is_complete = self._tx.is_complete()
|
||||||
self._is_rbf_enabled = self._tx.is_rbf_enabled()
|
self._is_rbf_enabled = self._tx.is_rbf_enabled()
|
||||||
|
|||||||
@@ -601,11 +601,10 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
|||||||
self.main_window.show_message(_("Enable fiat exchange rate with history."))
|
self.main_window.show_message(_("Enable fiat exchange rate with history."))
|
||||||
return
|
return
|
||||||
fx = self.main_window.fx
|
fx = self.main_window.fx
|
||||||
h = self.wallet.get_detailed_history(
|
summary = self.wallet.get_onchain_capital_gains(
|
||||||
from_timestamp=time.mktime(self.start_date.timetuple()) if self.start_date else None,
|
from_timestamp=time.mktime(self.start_date.timetuple()) if self.start_date else None,
|
||||||
to_timestamp=time.mktime(self.end_date.timetuple()) if self.end_date else None,
|
to_timestamp=time.mktime(self.end_date.timetuple()) if self.end_date else None,
|
||||||
fx=fx)
|
fx=fx)
|
||||||
summary = h['summary']
|
|
||||||
if not summary:
|
if not summary:
|
||||||
self.main_window.show_message(_("Nothing to summarize."))
|
self.main_window.show_message(_("Nothing to summarize."))
|
||||||
return
|
return
|
||||||
@@ -738,7 +737,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
|||||||
# 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 = idx.internalPointer().get_data()
|
tx_item = idx.internalPointer().get_data()
|
||||||
if tx_item.get('lightning') and tx_item['type'] == 'payment':
|
if tx_item.get('lightning'):
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
menu.addAction(_("Details"), lambda: self.main_window.show_lightning_transaction(tx_item))
|
menu.addAction(_("Details"), lambda: self.main_window.show_lightning_transaction(tx_item))
|
||||||
cc = self.add_copy_menu(menu, idx)
|
cc = self.add_copy_menu(menu, idx)
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ class LightningTxDialog(WindowModalDialog):
|
|||||||
WindowModalDialog.__init__(self, parent, _("Lightning Payment"))
|
WindowModalDialog.__init__(self, parent, _("Lightning Payment"))
|
||||||
self.main_window = parent
|
self.main_window = parent
|
||||||
self.config = parent.config
|
self.config = parent.config
|
||||||
self.is_sent = tx_item['direction'] == PaymentDirection.SENT
|
|
||||||
self.label = tx_item['label']
|
self.label = tx_item['label']
|
||||||
self.timestamp = tx_item['timestamp']
|
self.timestamp = tx_item['timestamp']
|
||||||
self.amount = Decimal(tx_item['amount_msat']) / 1000
|
self.amount = Decimal(tx_item['amount_msat']) / 1000
|
||||||
@@ -61,8 +60,8 @@ class LightningTxDialog(WindowModalDialog):
|
|||||||
self.setLayout(vbox)
|
self.setLayout(vbox)
|
||||||
amount_str = self.main_window.format_amount_and_units(self.amount, timestamp=self.timestamp)
|
amount_str = self.main_window.format_amount_and_units(self.amount, timestamp=self.timestamp)
|
||||||
vbox.addWidget(QLabel(_("Amount") + f": {amount_str}"))
|
vbox.addWidget(QLabel(_("Amount") + f": {amount_str}"))
|
||||||
if self.is_sent:
|
fee_msat = tx_item.get('fee_msat')
|
||||||
fee_msat = tx_item['fee_msat']
|
if fee_msat is not None:
|
||||||
fee_sat = Decimal(fee_msat) / 1000 if fee_msat is not None else None
|
fee_sat = Decimal(fee_msat) / 1000 if fee_msat is not None else None
|
||||||
fee_str = self.main_window.format_amount_and_units(fee_sat, timestamp=self.timestamp)
|
fee_str = self.main_window.format_amount_and_units(fee_sat, timestamp=self.timestamp)
|
||||||
vbox.addWidget(QLabel(_("Fee: {}").format(fee_str)))
|
vbox.addWidget(QLabel(_("Fee: {}").format(fee_str)))
|
||||||
|
|||||||
@@ -808,14 +808,15 @@ class TxDialog(QDialog, MessageBoxMixin):
|
|||||||
if txid is not None and fx.is_enabled() and amount is not None:
|
if txid is not None and fx.is_enabled() and amount is not None:
|
||||||
tx_item_fiat = self.wallet.get_tx_item_fiat(
|
tx_item_fiat = self.wallet.get_tx_item_fiat(
|
||||||
tx_hash=txid, amount_sat=abs(amount), fx=fx, tx_fee=fee)
|
tx_hash=txid, amount_sat=abs(amount), fx=fx, tx_fee=fee)
|
||||||
lnworker_history = self.wallet.lnworker.get_onchain_history() if self.wallet.lnworker else {}
|
|
||||||
if txid in lnworker_history:
|
if self.wallet.lnworker:
|
||||||
item = lnworker_history[txid]
|
# if it is a group, collect ln amount
|
||||||
ln_amount = item['amount_msat'] / 1000
|
full_history = self.wallet.get_full_history()
|
||||||
if amount is None:
|
item = full_history.get('group:' + txid)
|
||||||
tx_mined_status = self.wallet.adb.get_tx_height(txid)
|
ln_amount = item['ln_value'].value if item else None
|
||||||
else:
|
else:
|
||||||
ln_amount = None
|
ln_amount = None
|
||||||
|
|
||||||
self.broadcast_button.setEnabled(tx_details.can_broadcast)
|
self.broadcast_button.setEnabled(tx_details.can_broadcast)
|
||||||
can_sign = not self.tx.is_complete() and \
|
can_sign = not self.tx.is_complete() and \
|
||||||
(self.wallet.can_sign(self.tx) or bool(self.external_keypairs))
|
(self.wallet.can_sign(self.tx) or bool(self.external_keypairs))
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from .util import (
|
|||||||
profiler, OldTaskGroup, ESocksProxy, NetworkRetryManager, JsonRPCClient, NotEnoughFunds, EventListener,
|
profiler, OldTaskGroup, ESocksProxy, NetworkRetryManager, JsonRPCClient, NotEnoughFunds, EventListener,
|
||||||
event_listener, bfh, InvoiceError, resolve_dns_srv, is_ip_address, log_exceptions, ignore_exceptions,
|
event_listener, bfh, InvoiceError, resolve_dns_srv, is_ip_address, log_exceptions, ignore_exceptions,
|
||||||
make_aiohttp_session, timestamp_to_datetime, random_shuffled_copy, is_private_netaddress,
|
make_aiohttp_session, timestamp_to_datetime, random_shuffled_copy, is_private_netaddress,
|
||||||
UnrelatedTransactionException
|
UnrelatedTransactionException, LightningHistoryItem
|
||||||
)
|
)
|
||||||
from .invoices import Invoice, PR_UNPAID, PR_PAID, PR_INFLIGHT, PR_FAILED, LN_EXPIRY_NEVER, BaseInvoice
|
from .invoices import Invoice, PR_UNPAID, PR_PAID, PR_INFLIGHT, PR_FAILED, LN_EXPIRY_NEVER, BaseInvoice
|
||||||
from .bitcoin import COIN, opcodes, make_op_return, address_to_scripthash, DummyAddress
|
from .bitcoin import COIN, opcodes, make_op_return, address_to_scripthash, DummyAddress
|
||||||
@@ -48,7 +48,6 @@ from .transaction import (
|
|||||||
from .crypto import (
|
from .crypto import (
|
||||||
sha256, chacha20_encrypt, chacha20_decrypt, pw_encode_with_version_and_mac, pw_decode_with_version_and_mac
|
sha256, chacha20_encrypt, chacha20_decrypt, pw_encode_with_version_and_mac, pw_decode_with_version_and_mac
|
||||||
)
|
)
|
||||||
|
|
||||||
from .lntransport import LNTransport, LNResponderTransport, LNTransportBase, LNPeerAddr, split_host_port, extract_nodeid, ConnStringFormatError
|
from .lntransport import LNTransport, LNResponderTransport, LNTransportBase, LNPeerAddr, split_host_port, extract_nodeid, ConnStringFormatError
|
||||||
from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT
|
from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT
|
||||||
from .lnaddr import lnencode, LnAddr, lndecode
|
from .lnaddr import lnencode, LnAddr, lndecode
|
||||||
@@ -983,7 +982,11 @@ class LNWallet(LNWorker):
|
|||||||
timestamp = min([htlc_with_status.htlc.timestamp for htlc_with_status in plist])
|
timestamp = min([htlc_with_status.htlc.timestamp for htlc_with_status in plist])
|
||||||
return direction, amount_msat, fee_msat, timestamp
|
return direction, amount_msat, fee_msat, timestamp
|
||||||
|
|
||||||
def get_lightning_history(self):
|
def get_lightning_history(self) -> Dict[str, LightningHistoryItem]:
|
||||||
|
"""
|
||||||
|
side effect: sets defaults labels
|
||||||
|
note that the result is not ordered
|
||||||
|
"""
|
||||||
out = {}
|
out = {}
|
||||||
for payment_hash, plist in self.get_payments(status='settled').items():
|
for payment_hash, plist in self.get_payments(status='settled').items():
|
||||||
if len(plist) == 0:
|
if len(plist) == 0:
|
||||||
@@ -995,94 +998,94 @@ class LNWallet(LNWorker):
|
|||||||
if not label and direction == PaymentDirection.FORWARDING:
|
if not label and direction == PaymentDirection.FORWARDING:
|
||||||
label = _('Forwarding')
|
label = _('Forwarding')
|
||||||
preimage = self.get_preimage(payment_hash).hex()
|
preimage = self.get_preimage(payment_hash).hex()
|
||||||
item = {
|
group_id = self.swap_manager.get_group_id_for_payment_hash(payment_hash)
|
||||||
'type': 'payment',
|
item = LightningHistoryItem(
|
||||||
'label': label,
|
type = 'payment',
|
||||||
'timestamp': timestamp or 0,
|
payment_hash = payment_hash.hex(),
|
||||||
'date': timestamp_to_datetime(timestamp),
|
preimage = preimage,
|
||||||
'direction': direction,
|
amount_msat = amount_msat,
|
||||||
'amount_msat': amount_msat,
|
fee_msat = fee_msat,
|
||||||
'fee_msat': fee_msat,
|
group_id = group_id,
|
||||||
'payment_hash': key,
|
timestamp = timestamp or 0,
|
||||||
'preimage': preimage,
|
label=label,
|
||||||
}
|
)
|
||||||
item['group_id'] = self.swap_manager.get_group_id_for_payment_hash(payment_hash)
|
|
||||||
out[payment_hash] = item
|
out[payment_hash] = item
|
||||||
|
for chan in itertools.chain(self.channels.values(), self.channel_backups.values()): # type: AbstractChannel
|
||||||
|
item = chan.get_funding_height()
|
||||||
|
if item is None:
|
||||||
|
continue
|
||||||
|
funding_txid, funding_height, funding_timestamp = item
|
||||||
|
label = _('Open channel') + ' ' + chan.get_id_for_log()
|
||||||
|
self.wallet.set_default_label(funding_txid, label)
|
||||||
|
self.wallet.set_group_label(funding_txid, label)
|
||||||
|
item = LightningHistoryItem(
|
||||||
|
type = 'channel_opening',
|
||||||
|
label = label,
|
||||||
|
group_id = funding_txid,
|
||||||
|
timestamp = funding_timestamp,
|
||||||
|
amount_msat = chan.balance(LOCAL, ctn=0),
|
||||||
|
fee_msat = None,
|
||||||
|
payment_hash = None,
|
||||||
|
preimage = None,
|
||||||
|
)
|
||||||
|
out[funding_txid] = item
|
||||||
|
item = chan.get_closing_height()
|
||||||
|
if item is None:
|
||||||
|
continue
|
||||||
|
closing_txid, closing_height, closing_timestamp = item
|
||||||
|
label = _('Close channel') + ' ' + chan.get_id_for_log()
|
||||||
|
self.wallet.set_default_label(closing_txid, label)
|
||||||
|
self.wallet.set_group_label(closing_txid, label)
|
||||||
|
item = LightningHistoryItem(
|
||||||
|
type = 'channel_closing',
|
||||||
|
label = label,
|
||||||
|
group_id = closing_txid,
|
||||||
|
timestamp = closing_timestamp,
|
||||||
|
amount_msat = -chan.balance(LOCAL),
|
||||||
|
fee_msat = None,
|
||||||
|
payment_hash = None,
|
||||||
|
preimage = None,
|
||||||
|
)
|
||||||
|
out[closing_txid] = item
|
||||||
|
|
||||||
|
# sanity check
|
||||||
|
balance_msat = sum([x.amount_msat for x in out.values()])
|
||||||
|
lb = sum(chan.balance(LOCAL) if not chan.is_closed() else 0
|
||||||
|
for chan in self.channels.values())
|
||||||
|
assert balance_msat == lb
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def get_onchain_history(self):
|
def get_groups_for_onchain_history(self) -> Dict[str, str]:
|
||||||
out = {}
|
"""
|
||||||
|
returns dict: txid -> group_id
|
||||||
|
side effect: sets default labels
|
||||||
|
"""
|
||||||
|
groups = {}
|
||||||
# add funding events
|
# add funding events
|
||||||
for chan in itertools.chain(self.channels.values(), self.channel_backups.values()): # type: AbstractChannel
|
for chan in itertools.chain(self.channels.values(), self.channel_backups.values()): # type: AbstractChannel
|
||||||
item = chan.get_funding_height()
|
item = chan.get_funding_height()
|
||||||
if item is None:
|
if item is None:
|
||||||
continue
|
continue
|
||||||
funding_txid, funding_height, funding_timestamp = item
|
funding_txid, funding_height, funding_timestamp = item
|
||||||
tx_height = self.wallet.adb.get_tx_height(funding_txid)
|
groups[funding_txid] = funding_txid
|
||||||
self.wallet.set_default_label(chan.funding_outpoint.to_str(), _('Open channel') + ' ' + chan.get_id_for_log())
|
|
||||||
item = {
|
|
||||||
'channel_id': chan.channel_id.hex(),
|
|
||||||
'type': 'channel_opening',
|
|
||||||
'label': self.wallet.get_label_for_txid(funding_txid),
|
|
||||||
'txid': funding_txid,
|
|
||||||
'amount_msat': chan.balance(LOCAL, ctn=0),
|
|
||||||
'direction': PaymentDirection.RECEIVED,
|
|
||||||
'timestamp': tx_height.timestamp,
|
|
||||||
'monotonic_timestamp': tx_height.timestamp or TX_TIMESTAMP_INF,
|
|
||||||
'date': timestamp_to_datetime(tx_height.timestamp),
|
|
||||||
'fee_sat': None,
|
|
||||||
'fee_msat': None,
|
|
||||||
'height': tx_height.height,
|
|
||||||
'confirmations': tx_height.conf,
|
|
||||||
'txpos_in_block': tx_height.txpos,
|
|
||||||
} # FIXME this data structure needs to be kept in ~sync with wallet.get_onchain_history
|
|
||||||
out[funding_txid] = item
|
|
||||||
item = chan.get_closing_height()
|
item = chan.get_closing_height()
|
||||||
if item is None:
|
if item is None:
|
||||||
continue
|
continue
|
||||||
closing_txid, closing_height, closing_timestamp = item
|
closing_txid, closing_height, closing_timestamp = item
|
||||||
tx_height = self.wallet.adb.get_tx_height(closing_txid)
|
groups[closing_txid] = closing_txid
|
||||||
self.wallet.set_default_label(closing_txid, _('Close channel') + ' ' + chan.get_id_for_log())
|
|
||||||
item = {
|
|
||||||
'channel_id': chan.channel_id.hex(),
|
|
||||||
'txid': closing_txid,
|
|
||||||
'label': self.wallet.get_label_for_txid(closing_txid),
|
|
||||||
'type': 'channel_closure',
|
|
||||||
'amount_msat': -chan.balance(LOCAL),
|
|
||||||
'direction': PaymentDirection.SENT,
|
|
||||||
'timestamp': tx_height.timestamp,
|
|
||||||
'monotonic_timestamp': tx_height.timestamp or TX_TIMESTAMP_INF,
|
|
||||||
'date': timestamp_to_datetime(tx_height.timestamp),
|
|
||||||
'fee_sat': None,
|
|
||||||
'fee_msat': None,
|
|
||||||
'height': tx_height.height,
|
|
||||||
'confirmations': tx_height.conf,
|
|
||||||
'txpos_in_block': tx_height.txpos,
|
|
||||||
} # FIXME this data structure needs to be kept in ~sync with wallet.get_onchain_history
|
|
||||||
out[closing_txid] = item
|
|
||||||
|
|
||||||
d = self.swap_manager.get_groups_for_onchain_history()
|
d = self.swap_manager.get_groups_for_onchain_history()
|
||||||
for k,v in d.items():
|
for txid, v in d.items():
|
||||||
group_id = v.get('group_id')
|
group_id = v['group_id']
|
||||||
group_label = v.get('group_label')
|
label = v.get('label')
|
||||||
if group_id and group_label:
|
group_label = v.get('group_label') or label
|
||||||
self.wallet.set_default_label(group_id, group_label)
|
groups[txid] = group_id
|
||||||
out.update(d)
|
if label:
|
||||||
return out
|
self.wallet.set_default_label(txid, label)
|
||||||
|
if group_label:
|
||||||
|
self.wallet.set_group_label(group_id, group_label)
|
||||||
|
|
||||||
def get_history(self):
|
return groups
|
||||||
out = list(self.get_lightning_history().values()) + list(self.get_onchain_history().values())
|
|
||||||
# sort by timestamp
|
|
||||||
out.sort(key=lambda x: (x.get('timestamp') or float("inf")))
|
|
||||||
balance_msat = 0
|
|
||||||
for item in out:
|
|
||||||
balance_msat += item['amount_msat']
|
|
||||||
item['balance_msat'] = balance_msat
|
|
||||||
|
|
||||||
lb = sum(chan.balance(LOCAL) if not chan.is_closed() else 0
|
|
||||||
for chan in self.channels.values())
|
|
||||||
assert balance_msat == lb
|
|
||||||
return out
|
|
||||||
|
|
||||||
def channel_peers(self) -> List[bytes]:
|
def channel_peers(self) -> List[bytes]:
|
||||||
node_ids = [chan.node_id for chan in self.channels.values() if not chan.is_closed()]
|
node_ids = [chan.node_id for chan in self.channels.values() if not chan.is_closed()]
|
||||||
|
|||||||
@@ -1234,8 +1234,6 @@ class SwapManager(Logger):
|
|||||||
label += f' (refundable in {-delta} blocks)' # fixme: only if unspent
|
label += f' (refundable in {-delta} blocks)' # fixme: only if unspent
|
||||||
d[txid] = {
|
d[txid] = {
|
||||||
'group_id': txid,
|
'group_id': txid,
|
||||||
'amount_msat': 0, # must be zero for onchain tx
|
|
||||||
'type': 'swap',
|
|
||||||
'label': label,
|
'label': label,
|
||||||
'group_label': group_label,
|
'group_label': group_label,
|
||||||
}
|
}
|
||||||
@@ -1244,8 +1242,7 @@ class SwapManager(Logger):
|
|||||||
# to the group (see wallet.get_full_history)
|
# to the group (see wallet.get_full_history)
|
||||||
d[swap.spending_txid] = {
|
d[swap.spending_txid] = {
|
||||||
'group_id': txid,
|
'group_id': txid,
|
||||||
'amount_msat': 0, # must be zero for onchain tx
|
'group_label': group_label,
|
||||||
'type': 'swap',
|
|
||||||
'label': _('Refund transaction'),
|
'label': _('Refund transaction'),
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
@@ -1254,10 +1251,7 @@ class SwapManager(Logger):
|
|||||||
# add group_id to swap transactions
|
# add group_id to swap transactions
|
||||||
swap = self.get_swap(payment_hash)
|
swap = self.get_swap(payment_hash)
|
||||||
if swap:
|
if swap:
|
||||||
if swap.is_reverse:
|
return swap.spending_txid if swap.is_reverse else swap.funding_txid
|
||||||
return swap.spending_txid
|
|
||||||
else:
|
|
||||||
return swap.funding_txid
|
|
||||||
|
|
||||||
|
|
||||||
class SwapServerTransport(Logger):
|
class SwapServerTransport(Logger):
|
||||||
|
|||||||
@@ -2184,3 +2184,56 @@ def get_nostr_ann_pow_amount(nostr_pubk: bytes, nonce: Optional[int]) -> int:
|
|||||||
digest = hash_function(hash_preimage + nonce.to_bytes(32, 'big')).digest()
|
digest = hash_function(hash_preimage + nonce.to_bytes(32, 'big')).digest()
|
||||||
digest = int.from_bytes(digest, 'big')
|
digest = int.from_bytes(digest, 'big')
|
||||||
return hash_len_bits - digest.bit_length()
|
return hash_len_bits - digest.bit_length()
|
||||||
|
|
||||||
|
|
||||||
|
class OnchainHistoryItem(NamedTuple):
|
||||||
|
txid: str
|
||||||
|
amount_sat: int
|
||||||
|
fee_sat: int
|
||||||
|
balance_sat: int
|
||||||
|
tx_mined_status: TxMinedInfo
|
||||||
|
group_id: Optional[str]
|
||||||
|
label: str
|
||||||
|
monotonic_timestamp: int
|
||||||
|
group_id: Optional[str]
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'txid': self.txid,
|
||||||
|
'amount_sat': self.amount_sat,
|
||||||
|
'fee_sat': self.fee_sat,
|
||||||
|
'height': self.tx_mined_status.height,
|
||||||
|
'confirmations': self.tx_mined_status.conf,
|
||||||
|
'timestamp': self.tx_mined_status.timestamp,
|
||||||
|
'monotonic_timestamp': self.monotonic_timestamp,
|
||||||
|
'incoming': True if self.amount_sat>0 else False,
|
||||||
|
'bc_value': Satoshis(self.amount_sat),
|
||||||
|
'bc_balance': Satoshis(self.balance_sat),
|
||||||
|
'date': timestamp_to_datetime(self.tx_mined_status.timestamp),
|
||||||
|
'txpos_in_block': self.tx_mined_status.txpos,
|
||||||
|
'wanted_height': self.tx_mined_status.wanted_height,
|
||||||
|
'label': self.label,
|
||||||
|
'group_id': self.group_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
class LightningHistoryItem(NamedTuple):
|
||||||
|
payment_hash: str
|
||||||
|
preimage: str
|
||||||
|
amount_msat: int
|
||||||
|
fee_msat: Optional[int]
|
||||||
|
type: str
|
||||||
|
group_id: Optional[str]
|
||||||
|
timestamp: int
|
||||||
|
label: str
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'type': self.type,
|
||||||
|
'label': self.label,
|
||||||
|
'timestamp': self.timestamp or 0,
|
||||||
|
'date': timestamp_to_datetime(self.timestamp),
|
||||||
|
'amount_msat': self.amount_msat,
|
||||||
|
'fee_msat': self.fee_msat,
|
||||||
|
'payment_hash': self.payment_hash,
|
||||||
|
'preimage': self.preimage,
|
||||||
|
'group_id': self.group_id,
|
||||||
|
'ln_value': Satoshis(Decimal(self.amount_msat) / 1000),
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ from .util import read_json_file, write_json_file, UserFacingException, FileImpo
|
|||||||
from .util import EventListener, event_listener
|
from .util import EventListener, event_listener
|
||||||
from . import descriptor
|
from . import descriptor
|
||||||
from .descriptor import Descriptor
|
from .descriptor import Descriptor
|
||||||
|
from .util import OnchainHistoryItem, LightningHistoryItem
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .network import Network
|
from .network import Network
|
||||||
@@ -1012,11 +1013,11 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
"""
|
"""
|
||||||
with self.lock, self.transaction_lock:
|
with self.lock, self.transaction_lock:
|
||||||
if self._last_full_history is None:
|
if self._last_full_history is None:
|
||||||
self._last_full_history = self.get_full_history(None, include_lightning=False)
|
self._last_full_history = self.get_onchain_history()
|
||||||
# populate cache in chronological order (confirmed tx only)
|
# populate cache in chronological order (confirmed tx only)
|
||||||
# todo: get_full_history should return unconfirmed tx topologically sorted
|
# todo: get_full_history should return unconfirmed tx topologically sorted
|
||||||
for _txid, tx_item in self._last_full_history.items():
|
for _txid, tx_item in self._last_full_history.items():
|
||||||
if tx_item['height'] > 0:
|
if tx_item.tx_mined_status.height > 0:
|
||||||
self.get_tx_parents(_txid)
|
self.get_tx_parents(_txid)
|
||||||
|
|
||||||
result = self._tx_parents_cache.get(txid, None)
|
result = self._tx_parents_cache.get(txid, None)
|
||||||
@@ -1145,28 +1146,53 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
# return last balance
|
# return last balance
|
||||||
return balance
|
return balance
|
||||||
|
|
||||||
def get_onchain_history(self, *, domain=None):
|
def get_onchain_history(
|
||||||
|
self, *,
|
||||||
|
domain=None,
|
||||||
|
from_timestamp=None,
|
||||||
|
to_timestamp=None,
|
||||||
|
from_height=None,
|
||||||
|
to_height=None) -> Dict[str, OnchainHistoryItem]:
|
||||||
|
# sanity check
|
||||||
|
if (from_timestamp is not None or to_timestamp is not None) \
|
||||||
|
and (from_height is not None or to_height is not None):
|
||||||
|
raise UserFacingException('timestamp and block height based filtering cannot be used together')
|
||||||
|
# call lnworker first, because it adds accounting addresses
|
||||||
|
groups = self.lnworker.get_groups_for_onchain_history() if self.lnworker else {}
|
||||||
if domain is None:
|
if domain is None:
|
||||||
domain = self.get_addresses()
|
domain = self.get_addresses()
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
transactions = OrderedDictWithIndex()
|
||||||
monotonic_timestamp = 0
|
monotonic_timestamp = 0
|
||||||
for hist_item in self.adb.get_history(domain=domain):
|
for hist_item in self.adb.get_history(domain=domain):
|
||||||
monotonic_timestamp = max(monotonic_timestamp, (hist_item.tx_mined_status.timestamp or TX_TIMESTAMP_INF))
|
timestamp = (hist_item.tx_mined_status.timestamp or TX_TIMESTAMP_INF)
|
||||||
d = {
|
height = hist_item.tx_mined_status
|
||||||
'txid': hist_item.txid,
|
if from_timestamp and (timestamp or now) < from_timestamp:
|
||||||
'fee_sat': hist_item.fee,
|
continue
|
||||||
'height': hist_item.tx_mined_status.height,
|
if to_timestamp and (timestamp or now) >= to_timestamp:
|
||||||
'confirmations': hist_item.tx_mined_status.conf,
|
continue
|
||||||
'timestamp': hist_item.tx_mined_status.timestamp,
|
if from_height is not None and from_height > height > 0:
|
||||||
'monotonic_timestamp': monotonic_timestamp,
|
continue
|
||||||
'incoming': True if hist_item.delta>0 else False,
|
if to_height is not None and (height >= to_height or height <= 0):
|
||||||
'bc_value': Satoshis(hist_item.delta),
|
continue
|
||||||
'bc_balance': Satoshis(hist_item.balance),
|
monotonic_timestamp = max(monotonic_timestamp, timestamp)
|
||||||
'date': timestamp_to_datetime(hist_item.tx_mined_status.timestamp),
|
txid = hist_item.txid
|
||||||
'label': self.get_label_for_txid(hist_item.txid),
|
group_id = groups.get(txid)
|
||||||
'txpos_in_block': hist_item.tx_mined_status.txpos,
|
label = self.get_label_for_txid(txid)
|
||||||
'wanted_height': hist_item.tx_mined_status.wanted_height,
|
tx_item = OnchainHistoryItem(
|
||||||
}
|
txid=hist_item.txid,
|
||||||
yield d
|
amount_sat=hist_item.delta,
|
||||||
|
fee_sat=hist_item.fee,
|
||||||
|
balance_sat=hist_item.balance,
|
||||||
|
tx_mined_status=hist_item.tx_mined_status,
|
||||||
|
label=label,
|
||||||
|
monotonic_timestamp=monotonic_timestamp,
|
||||||
|
group_id=group_id,
|
||||||
|
)
|
||||||
|
transactions[hist_item.txid] = tx_item
|
||||||
|
|
||||||
|
return transactions
|
||||||
|
|
||||||
def create_invoice(self, *, outputs: List[PartialTxOutput], message, pr, URI) -> Invoice:
|
def create_invoice(self, *, outputs: List[PartialTxOutput], message, pr, URI) -> Invoice:
|
||||||
height = self.adb.get_local_height()
|
height = self.adb.get_local_height()
|
||||||
@@ -1342,44 +1368,26 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
return is_paid, conf_needed
|
return is_paid, conf_needed
|
||||||
|
|
||||||
@profiler
|
@profiler
|
||||||
def get_full_history(self, fx=None, *, onchain_domain=None, include_lightning=True, include_fiat=False):
|
def get_full_history(self, fx=None, *, onchain_domain=None, include_lightning=True, include_fiat=False) -> dict:
|
||||||
|
"""
|
||||||
|
includes both onchain and lightning
|
||||||
|
includes grouping information
|
||||||
|
"""
|
||||||
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)
|
||||||
for tx_item in onchain_history:
|
for tx_item in onchain_history.values():
|
||||||
txid = tx_item['txid']
|
txid = tx_item.txid
|
||||||
transactions_tmp[txid] = tx_item
|
transactions_tmp[txid] = tx_item.to_dict()
|
||||||
# add lnworker onchain transactions to transactions_tmp
|
transactions_tmp[txid]['lightning'] = False
|
||||||
# add group_id to tx that are in a group
|
|
||||||
lnworker_history = self.lnworker.get_onchain_history() if self.lnworker and include_lightning else {}
|
|
||||||
for txid, item in lnworker_history.items():
|
|
||||||
if txid in transactions_tmp:
|
|
||||||
tx_item = transactions_tmp[txid]
|
|
||||||
tx_item['group_id'] = item.get('group_id') # for swaps
|
|
||||||
tx_item['label'] = item['label']
|
|
||||||
tx_item['type'] = item['type']
|
|
||||||
ln_value = Decimal(item['amount_msat']) / 1000 # for channel open/close tx
|
|
||||||
tx_item['ln_value'] = Satoshis(ln_value)
|
|
||||||
if channel_id := item.get('channel_id'):
|
|
||||||
tx_item['channel_id'] = channel_id
|
|
||||||
else:
|
|
||||||
if item['type'] == 'swap':
|
|
||||||
# swap items do not have all the fields. We can skip skip them
|
|
||||||
# because they will eventually be in onchain_history
|
|
||||||
# TODO: use attr.s objects instead of dicts
|
|
||||||
continue
|
|
||||||
transactions_tmp[txid] = item
|
|
||||||
ln_value = Decimal(item['amount_msat']) / 1000 # for channel open/close tx
|
|
||||||
item['ln_value'] = Satoshis(ln_value)
|
|
||||||
# add lightning_transactions
|
# add lightning_transactions
|
||||||
lightning_history = self.lnworker.get_lightning_history() if self.lnworker and include_lightning else {}
|
lightning_history = self.lnworker.get_lightning_history() if self.lnworker and include_lightning else {}
|
||||||
for tx_item in lightning_history.values():
|
for tx_item in lightning_history.values():
|
||||||
txid = tx_item.get('txid')
|
key = tx_item.payment_hash or 'ln:' + tx_item.group_id
|
||||||
ln_value = Decimal(tx_item['amount_msat']) / 1000
|
transactions_tmp[key] = tx_item.to_dict()
|
||||||
tx_item['lightning'] = True
|
transactions_tmp[key]['lightning'] = True
|
||||||
tx_item['ln_value'] = Satoshis(ln_value)
|
|
||||||
key = tx_item.get('txid') or tx_item['payment_hash']
|
|
||||||
transactions_tmp[key] = tx_item
|
|
||||||
# sort on-chain and LN stuff into new dict, by timestamp
|
# sort on-chain and LN stuff into new dict, by timestamp
|
||||||
# (we rely on this being a *stable* sort)
|
# (we rely on this being a *stable* sort)
|
||||||
def sort_key(x):
|
def sort_key(x):
|
||||||
@@ -1396,10 +1404,10 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
else:
|
else:
|
||||||
key = 'group:' + group_id
|
key = 'group:' + group_id
|
||||||
parent = transactions.get(key)
|
parent = transactions.get(key)
|
||||||
label = self.get_label_for_txid(group_id)
|
group_label = self.get_label_for_group(group_id)
|
||||||
if parent is None:
|
if parent is None:
|
||||||
parent = {
|
parent = {
|
||||||
'label': label,
|
'label': group_label,
|
||||||
'fiat_value': Fiat(Decimal(0), fx.ccy) if fx else None,
|
'fiat_value': Fiat(Decimal(0), fx.ccy) if fx else None,
|
||||||
'bc_value': Satoshis(0),
|
'bc_value': Satoshis(0),
|
||||||
'ln_value': Satoshis(0),
|
'ln_value': Satoshis(0),
|
||||||
@@ -1455,20 +1463,12 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
return transactions
|
return transactions
|
||||||
|
|
||||||
@profiler
|
@profiler
|
||||||
def get_detailed_history(
|
def get_onchain_capital_gains(self, fx, **kwargs):
|
||||||
self,
|
|
||||||
from_timestamp=None,
|
|
||||||
to_timestamp=None,
|
|
||||||
fx=None,
|
|
||||||
show_addresses=False,
|
|
||||||
from_height=None,
|
|
||||||
to_height=None):
|
|
||||||
# History with capital gains, using utxo pricing
|
# History with capital gains, using utxo pricing
|
||||||
# FIXME: Lightning capital gains would requires FIFO
|
# FIXME: Lightning capital gains would requires FIFO
|
||||||
if (from_timestamp is not None or to_timestamp is not None) \
|
from_timestamp = kwargs.get('from_timestamp')
|
||||||
and (from_height is not None or to_height is not None):
|
to_timestamp = kwargs.get('to_timestamp')
|
||||||
raise UserFacingException('timestamp and block height based filtering cannot be used together')
|
history = self.get_onchain_history(**kwargs)
|
||||||
|
|
||||||
show_fiat = fx and fx.is_enabled() and fx.has_history()
|
show_fiat = fx and fx.is_enabled() and fx.has_history()
|
||||||
out = []
|
out = []
|
||||||
income = 0
|
income = 0
|
||||||
@@ -1476,26 +1476,13 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
capital_gains = Decimal(0)
|
capital_gains = Decimal(0)
|
||||||
fiat_income = Decimal(0)
|
fiat_income = Decimal(0)
|
||||||
fiat_expenditures = Decimal(0)
|
fiat_expenditures = Decimal(0)
|
||||||
now = time.time()
|
for txid, hitem in history.items():
|
||||||
for item in self.get_onchain_history():
|
item = hitem.to_dict()
|
||||||
|
if item['bc_value'].value == 0:
|
||||||
|
continue
|
||||||
timestamp = item['timestamp']
|
timestamp = item['timestamp']
|
||||||
if from_timestamp and (timestamp or now) < from_timestamp:
|
|
||||||
continue
|
|
||||||
if to_timestamp and (timestamp or now) >= to_timestamp:
|
|
||||||
continue
|
|
||||||
height = item['height']
|
|
||||||
if from_height is not None and from_height > height > 0:
|
|
||||||
continue
|
|
||||||
if to_height is not None and (height >= to_height or height <= 0):
|
|
||||||
continue
|
|
||||||
tx_hash = item['txid']
|
tx_hash = item['txid']
|
||||||
tx = self.db.get_transaction(tx_hash)
|
|
||||||
tx_fee = item['fee_sat']
|
tx_fee = item['fee_sat']
|
||||||
item['fee'] = Satoshis(tx_fee) if tx_fee is not None else None
|
|
||||||
if show_addresses:
|
|
||||||
item['inputs'] = list(map(lambda x: x.to_json(), tx.inputs()))
|
|
||||||
item['outputs'] = list(map(lambda x: {'address': x.get_ui_address_str(), 'value': Satoshis(x.value)},
|
|
||||||
tx.outputs()))
|
|
||||||
# fixme: use in and out values
|
# fixme: use in and out values
|
||||||
value = item['bc_value'].value
|
value = item['bc_value'].value
|
||||||
if value < 0:
|
if value < 0:
|
||||||
@@ -1506,7 +1493,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
if show_fiat:
|
if show_fiat:
|
||||||
fiat_fields = self.get_tx_item_fiat(tx_hash=tx_hash, amount_sat=value, fx=fx, tx_fee=tx_fee)
|
fiat_fields = self.get_tx_item_fiat(tx_hash=tx_hash, amount_sat=value, fx=fx, tx_fee=tx_fee)
|
||||||
fiat_value = fiat_fields['fiat_value'].value
|
fiat_value = fiat_fields['fiat_value'].value
|
||||||
item.update(fiat_fields)
|
|
||||||
if value < 0:
|
if value < 0:
|
||||||
capital_gains += fiat_fields['capital_gain'].value
|
capital_gains += fiat_fields['capital_gain'].value
|
||||||
fiat_expenditures += -fiat_value
|
fiat_expenditures += -fiat_value
|
||||||
@@ -1517,12 +1503,8 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
if out:
|
if out:
|
||||||
first_item = out[0]
|
first_item = out[0]
|
||||||
last_item = out[-1]
|
last_item = out[-1]
|
||||||
if from_height or to_height:
|
start_height = first_item['height'] - 1
|
||||||
start_height = from_height
|
end_height = last_item['height']
|
||||||
end_height = to_height
|
|
||||||
else:
|
|
||||||
start_height = first_item['height'] - 1
|
|
||||||
end_height = last_item['height']
|
|
||||||
|
|
||||||
b = first_item['bc_balance'].value
|
b = first_item['bc_balance'].value
|
||||||
v = first_item['bc_value'].value
|
v = first_item['bc_value'].value
|
||||||
@@ -1583,10 +1565,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
summary = {}
|
summary = {}
|
||||||
return {
|
return summary
|
||||||
'transactions': out,
|
|
||||||
'summary': summary
|
|
||||||
}
|
|
||||||
|
|
||||||
def acquisition_price(self, coins, price_func, ccy):
|
def acquisition_price(self, coins, price_func, ccy):
|
||||||
return Decimal(sum(self.coin_price(coin.prevout.txid.hex(), price_func, ccy, self.adb.get_txin_value(coin)) for coin in coins))
|
return Decimal(sum(self.coin_price(coin.prevout.txid.hex(), price_func, ccy, self.adb.get_txin_value(coin)) for coin in coins))
|
||||||
@@ -1644,6 +1623,12 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
def _get_default_label_for_outpoint(self, outpoint: str) -> str:
|
def _get_default_label_for_outpoint(self, outpoint: str) -> str:
|
||||||
return self._default_labels.get(outpoint)
|
return self._default_labels.get(outpoint)
|
||||||
|
|
||||||
|
def get_label_for_group(self, group_id: str) -> str:
|
||||||
|
return self._default_labels.get('group:' + group_id)
|
||||||
|
|
||||||
|
def set_group_label(self, group_id: str, label: str):
|
||||||
|
self._default_labels['group:' + group_id] = label
|
||||||
|
|
||||||
def get_label_for_txid(self, tx_hash: str) -> str:
|
def get_label_for_txid(self, tx_hash: str) -> str:
|
||||||
return self._labels.get(tx_hash) or self._get_default_label_for_txid(tx_hash)
|
return self._labels.get(tx_hash) or self._get_default_label_for_txid(tx_hash)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user