Commit b26a3340 authored by Moul's avatar Moul

[feat] #22: Display transactions history in a table

parent 67771283
...@@ -20,6 +20,7 @@ along with Silkaj. If not, see <https://www.gnu.org/licenses/>. ...@@ -20,6 +20,7 @@ along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
from click import group, help_option, version_option, option, pass_context from click import group, help_option, version_option, option, pass_context
from silkaj.tx import send_transaction from silkaj.tx import send_transaction
from silkaj.tx_history import transaction_history
from silkaj.money import cmd_amount from silkaj.money import cmd_amount
from silkaj.cert import send_certification from silkaj.cert import send_certification
from silkaj.commands import ( from silkaj.commands import (
...@@ -88,6 +89,7 @@ cli.add_command(cmd_amount) ...@@ -88,6 +89,7 @@ cli.add_command(cmd_amount)
cli.add_command(list_blocks) cli.add_command(list_blocks)
cli.add_command(send_certification) cli.add_command(send_certification)
cli.add_command(difficulties) cli.add_command(difficulties)
cli.add_command(transaction_history)
cli.add_command(id_pubkey_correspondence) cli.add_command(id_pubkey_correspondence)
cli.add_command(currency_info) cli.add_command(currency_info)
cli.add_command(license_command) cli.add_command(license_command)
......
"""
Copyright 2016-2019 Maël Azimi <m.a@moul.re>
Silkaj is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Silkaj is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
"""
from click import command, argument, option, echo_via_pager, get_terminal_size
from texttable import Texttable
from operator import itemgetter, neg, eq, ne
from time import time
from duniterpy.api.bma.tx import history
from duniterpy.documents.transaction import Transaction
from silkaj.network_tools import ClientInstance
from silkaj.tools import convert_time, coroutine
from silkaj.crypto_tools import check_public_key
from silkaj.wot import identity_of, identities_from_pubkeys
from silkaj.money import get_amount_from_pubkey, amount_in_current_base, UDValue
from silkaj.tools import CurrencySymbol
@command("history", help="Display transaction history")
@argument("pubkey")
@option("--uids", "-u", is_flag=True, help="Display uids")
@coroutine
async def transaction_history(pubkey, uids):
if not check_public_key(pubkey, True):
return
client = ClientInstance().client
ud_value = await UDValue().ud_value
currency_symbol = await CurrencySymbol().symbol
header = await generate_header(pubkey, currency_symbol, ud_value)
received_txs, sent_txs = list(), list()
await get_transactions_history(client, pubkey, received_txs, sent_txs)
remove_duplicate_txs(received_txs, sent_txs)
txs_list = await generate_table(
received_txs, sent_txs, pubkey, ud_value, currency_symbol, uids
)
table = Texttable(max_width=get_terminal_size()[0])
table.add_rows(txs_list)
await client.close()
echo_via_pager(header + table.draw())
async def generate_header(pubkey, currency_symbol, ud_value):
try:
idty = await identity_of(pubkey)
except:
idty = dict([("uid", "")])
balance = await get_amount_from_pubkey(pubkey)
return "Transactions history from: {uid} {pubkey}\n\
Current balance: {balance} {currency}, {balance_ud} UD {currency} on the {date}\n\
".format(
uid=idty["uid"],
pubkey=pubkey,
currency=currency_symbol,
balance=balance[1] / 100,
balance_ud=round(balance[1] / ud_value, 2),
date=convert_time(time(), "all"),
)
async def get_transactions_history(client, pubkey, received_txs, sent_txs):
"""
Get transaction history
Store txs in Transaction object
"""
tx_history = await client(history, pubkey)
currency = tx_history["currency"]
for received in tx_history["history"]["received"]:
received_txs.append(Transaction.from_bma_history(currency, received))
for sent in tx_history["history"]["sent"]:
sent_txs.append(Transaction.from_bma_history(currency, sent))
def remove_duplicate_txs(received_txs, sent_txs):
"""
Remove duplicate transactions from history
Remove received tx which contains output back return
that we don’t want to displayed
A copy of received_txs is necessary to remove elements
"""
for received_tx in list(received_txs):
if received_tx in sent_txs:
received_txs.remove(received_tx)
async def generate_table(
received_txs, sent_txs, pubkey, ud_value, currency_symbol, uids
):
"""
Generate information in a list of lists for texttabe
Merge received and sent txs
Insert table header at the end not to disturb its generation
Sort txs temporarily
"""
received_txs_table, sent_txs_table = list(), list()
await parse_received_tx(received_txs_table, received_txs, pubkey, ud_value, uids)
await parse_sent_tx(sent_txs_table, sent_txs, pubkey, ud_value, uids)
txs_table = received_txs_table + sent_txs_table
table_titles = [
"Date",
"Issuers/Recipients",
"Amounts {}".format(currency_symbol),
"Amounts UD{}".format(currency_symbol),
"Comment",
]
txs_table.insert(0, table_titles)
txs_table.sort(key=itemgetter(0), reverse=True)
return txs_table
async def parse_received_tx(received_txs_table, received_txs, pubkey, ud_value, uids):
"""
Extract issuers’ pubkeys
Get identities from pubkeys
Convert time into human format
Assign identities
Get amounts and assign amounts and amounts_ud
Append comment
"""
issuers = list()
for received_tx in received_txs:
for issuer in received_tx.issuers:
issuers.append(issuer)
identities = await identities_from_pubkeys(issuers, uids)
for received_tx in received_txs:
tx_list = list()
tx_list.append(convert_time(received_tx.time, "all"))
tx_list.append(str())
for i, issuer in enumerate(received_tx.issuers):
tx_list[1] += prefix(None, None, i) + assign_idty_from_pubkey(
issuer, identities
)
amounts = tx_amount(received_tx, pubkey, received_func)[0]
tx_list.append(amounts / 100)
tx_list.append(amounts / ud_value)
tx_list.append(received_tx.comment)
received_txs_table.append(tx_list)
async def parse_sent_tx(sent_txs_table, sent_txs, pubkey, ud_value, uids):
"""
Extract recipients’ pubkeys from outputs
Get identities from pubkeys
Convert time into human format
Store "Total" and total amounts according to the number of outputs
If not output back return:
Assign amounts, amounts_ud, identities, and comment
"""
pubkeys = list()
for sent_tx in sent_txs:
outputs = tx_amount(sent_tx, pubkey, sent_func)[1]
for output in outputs:
if output_available(output.condition, ne, pubkey):
pubkeys.append(output.condition.left.pubkey)
identities = await identities_from_pubkeys(pubkeys, uids)
for sent_tx in sent_txs:
tx_list = list()
tx_list.append(convert_time(sent_tx.time, "all"))
total_amount, outputs = tx_amount(sent_tx, pubkey, sent_func)
if len(outputs) > 1:
tx_list.append("Total")
amounts = str(total_amount / 100)
amounts_ud = str(round(total_amount / ud_value, 2))
else:
tx_list.append(str())
amounts = str()
amounts_ud = str()
for i, output in enumerate(outputs):
if output_available(output.condition, ne, pubkey):
amounts += prefix(None, outputs, i) + str(
neg(amount_in_current_base(output)) / 100
)
amounts_ud += prefix(None, outputs, i) + str(
round(neg(amount_in_current_base(output)) / ud_value, 2)
)
tx_list[1] += prefix(tx_list[1], outputs, 0) + assign_idty_from_pubkey(
output.condition.left.pubkey, identities
)
tx_list.append(amounts)
tx_list.append(amounts_ud)
tx_list.append(sent_tx.comment)
sent_txs_table.append(tx_list)
def tx_amount(tx, pubkey, function):
"""
Determine transaction amount from output sources
"""
amount = 0
outputs = list()
for output in tx.outputs:
if output_available(output.condition, ne, pubkey):
outputs.append(output)
amount += function(output, pubkey)
return amount, outputs
def received_func(output, pubkey):
if output_available(output.condition, eq, pubkey):
return amount_in_current_base(output)
return 0
def sent_func(output, pubkey):
if output_available(output.condition, ne, pubkey):
return neg(amount_in_current_base(output))
return 0
def output_available(condition, comparison, value):
"""
Check if output source is available
Currently only handle simple SIG condition
XHX, CLTV, CSV should be handled when present in the blockchain
"""
if hasattr(condition.left, "pubkey"):
return comparison(condition.left.pubkey, value)
else:
return False
def assign_idty_from_pubkey(pubkey, identities):
idty = pubkey[:18]
for identity in identities:
if pubkey == identity["pubkey"]:
idty = identity["uid"] + " - " + pubkey[:13]
return idty
def prefix(tx_addresses, outputs, occurence):
"""
Pretty print with texttable
Break line when several values in a cell
Handle Total line in case of multi-output txs
"""
if tx_addresses == "Total":
return "\n"
if not outputs:
return ""
return "\n" if occurence + len(outputs) > 1 else ""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment