mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
UIUX: Better balance display (#1841)
* Better alignment and introduction of spaces and colours for BTC amounts. Co-authored-by: Kim Neunert <k9ert@gmx.de> Co-authored-by: moneymanolis <moneymanolis@protonmail.com>
This commit is contained in:
parent
66d56a2c8e
commit
7f0ac699e9
19 changed files with 723 additions and 251 deletions
|
|
@ -9,6 +9,7 @@
|
|||
"spec_fees.js",
|
||||
"spec_rescan.js",
|
||||
"spec_qr_signing.js",
|
||||
"spec_balances_amounts.js",
|
||||
"spec_wallet_send.js",
|
||||
"spec_wallet_utxo.js",
|
||||
"spec_plugins.js",
|
||||
|
|
|
|||
115
cypress/integration/spec_balances_amounts.js
Normal file
115
cypress/integration/spec_balances_amounts.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
describe('Test the rendering of balances and amounts', () => {
|
||||
before(() => {
|
||||
Cypress.config('includeShadowDom', true)
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
// Keeps the session cookie alive, Cypress by default clears all cookies before each test
|
||||
beforeEach(() => {
|
||||
cy.viewport(1200,660)
|
||||
Cypress.Cookies.preserveOnce('session')
|
||||
})
|
||||
|
||||
it('Total balance of 20 BTC', () => {
|
||||
/* This is how the DOM looks like
|
||||
<th id="fullbalance_amount" class="right-align">
|
||||
20.0
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
<span class="thousand-digits-in-btc-amount">
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
</span>
|
||||
<span class="last-digits-in-btc-amount">
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
</span>
|
||||
</th>
|
||||
*/
|
||||
cy.selectWallet('Ghost wallet')
|
||||
cy.get('#fullbalance_amount').should('have.text', '20.00000000') // should('have.text') returns ALL textContents (descendants and unvisible text)
|
||||
cy.get('#fullbalance_amount').find('span').first().should('have.text', '0').and('not.be.visible')
|
||||
cy.get('#fullbalance_amount').find('.thousand-digits-in-btc-amount').children().each((element) => {
|
||||
cy.wrap(element).should('have.text', '0')
|
||||
cy.wrap(element).should('not.be.visible')
|
||||
});
|
||||
cy.get('#fullbalance_amount').find('.last-digits-in-btc-amount').children().each((element) => {
|
||||
cy.wrap(element).should('have.text', '0')
|
||||
cy.wrap(element).should('not.be.visible')
|
||||
});
|
||||
})
|
||||
|
||||
it('Unconfirmed balance of 0.05 BTC', () => {
|
||||
/* This is how the DOM looks like
|
||||
<th id="unconfirmed_amount" class="right-align">
|
||||
0.05
|
||||
<span class="thousand-digits-in-btc-amount">
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
</span>
|
||||
<span class="last-digits-in-btc-amount">
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
<span class="unselectable transparent-text">0</span>
|
||||
</span>
|
||||
</th>
|
||||
*/
|
||||
// We get 5 mio. sats from a funding wallet
|
||||
// TODO: If this funding wallet is used more, move it to a seperate spec file
|
||||
cy.addHotDevice('Satoshis hot keys','bitcoin')
|
||||
cy.addWallet('Funding wallet', 'segwit', 'funded', 'btc', 'singlesig', 'Satoshis hot keys')
|
||||
cy.selectWallet('Funding wallet')
|
||||
cy.get('#btn_send').click()
|
||||
cy.get('#recipient_0').find('#address').invoke('val', 'bcrt1qvtdx75y4554ngrq6aff3xdqnvjhmct5wck95qs')
|
||||
cy.get('#recipient_0').find('#amount').type(0.05, { force: true })
|
||||
cy.get('#toggle_advanced').click()
|
||||
cy.get('.fee_container').find('#fee_option_manual').click()
|
||||
cy.get('#fee_manual').find('#fee_rate').clear( { force: true })
|
||||
cy.get('#fee_manual').find('#fee_rate').type(5, { force: true }) // Should be a fee of 709 sats.
|
||||
cy.get('#create_psbt_btn').click()
|
||||
cy.get('body').contains("Paste signed transaction")
|
||||
cy.get('#satoshis_hot_keys_tx_sign_btn').click()
|
||||
cy.get('#satoshis_hot_keys_hot_sign_btn').click()
|
||||
cy.get('#hot_enter_passphrase__submit').click()
|
||||
cy.get('#broadcast_local_btn').click()
|
||||
cy.reload()
|
||||
cy.selectWallet('Ghost wallet')
|
||||
cy.get('#unconfirmed_amount').should('have.text', '0.05000000')
|
||||
cy.get('#unconfirmed_amount').find('.thousand-digits-in-btc-amount').children().each((element) => {
|
||||
cy.wrap(element).should('have.text', '0')
|
||||
cy.wrap(element).should('not.be.visible')
|
||||
});
|
||||
cy.get('#unconfirmed_amount').find('.last-digits-in-btc-amount').children().each((element) => {
|
||||
cy.wrap(element).should('have.text', '0')
|
||||
cy.wrap(element).should('not.be.visible')
|
||||
});
|
||||
})
|
||||
|
||||
it('Total balance with all digits', () => {
|
||||
/* This is how the DOM looks like
|
||||
<th id="fullbalance_amount" class="right-align">
|
||||
19.94
|
||||
<span class="thousand-digits-in-btc-amount">999</span>
|
||||
<span class="last-digits-in-btc-amount">291</span>
|
||||
</th>
|
||||
*/
|
||||
// Let's use the funding wallet
|
||||
// Works as long as the fee was 709 and the original balance of the funding wallet was 20 BTC
|
||||
cy.selectWallet('Funding wallet')
|
||||
cy.get('#fullbalance_amount').should('have.text', '19.94999291')
|
||||
cy.get('#fullbalance_amount').find('.thousand-digits-in-btc-amount').should('have.text', '999')
|
||||
cy.get('#fullbalance_amount').find('.thousand-digits-in-btc-amount').should('have.css', 'color','rgb(145, 145, 145)')
|
||||
cy.get('#fullbalance_amount').find('.last-digits-in-btc-amount').should('have.text', '291')
|
||||
cy.get('#fullbalance_amount').find('.last-digits-in-btc-amount').should('have.css', 'color','rgb(121, 121, 121)')
|
||||
cy.get('#fullbalance_amount').children().each((element) => {
|
||||
cy.wrap(element).should('be.visible')
|
||||
cy.log(element)
|
||||
});
|
||||
})
|
||||
|
||||
// TODO: Test (new) amount display once implemented, e.g. in sending dialogue, could probably be done in one of the tests above.
|
||||
|
||||
})
|
||||
|
|
@ -216,7 +216,7 @@ Cypress.Commands.add("deleteWallet", (name) => {
|
|||
Cypress.Commands.add("selectWallet", (name) => {
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.text().includes(name)) {
|
||||
cy.contains(name).click()
|
||||
cy.contains(name).click( {force: true} )
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -225,8 +225,8 @@ Cypress.Commands.add("mine2wallet", (chain) => {
|
|||
// Fund it and check the balance
|
||||
// Only works if a wallet is selected, use addHotWallet / selectWallet commands before if needed
|
||||
cy.get('#btn_transactions').click()
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }).then(($span) => {
|
||||
const oldBalance = parseFloat($span.text())
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") }).then(($header) => {
|
||||
const oldBalance = parseFloat($header.text())
|
||||
if (chain=="elm" || chain=="elements") {
|
||||
cy.task("elm:mine")
|
||||
} else if (chain=="btc" || chain=="bitcoin") {
|
||||
|
|
@ -235,8 +235,8 @@ Cypress.Commands.add("mine2wallet", (chain) => {
|
|||
throw new Error("Unknown chain: " + chain)
|
||||
}
|
||||
cy.waitUntil( () => cy.reload().get('#fullbalance_amount', { timeout: 3000 })
|
||||
.then(($span) => {
|
||||
const n = parseFloat($span.text())
|
||||
.then(($header) => {
|
||||
const n = parseFloat($header.text())
|
||||
return n > oldBalance
|
||||
})
|
||||
, {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import sys
|
|||
import time
|
||||
from pathlib import Path
|
||||
from threading import Event
|
||||
from xmlrpc.client import Boolean
|
||||
|
||||
import click
|
||||
import psutil
|
||||
from flask import Config
|
||||
from requests.exceptions import ConnectionError
|
||||
|
||||
from ..config import DEFAULT_CONFIG
|
||||
from ..process_controller.elementsd_controller import ElementsPlainController
|
||||
|
|
@ -352,15 +354,15 @@ def noded(
|
|||
|
||||
if node_impl == "elements":
|
||||
prepare_elements_default_wallet(my_node)
|
||||
|
||||
if mining:
|
||||
miner_loop(
|
||||
node_impl,
|
||||
my_node,
|
||||
config_obj["SPECTER_DATA_FOLDER"],
|
||||
mining_every_x_seconds,
|
||||
echo,
|
||||
)
|
||||
# Mining/NOP loop (necessary to keep the Python process running)
|
||||
endless_loop(
|
||||
node_impl,
|
||||
my_node,
|
||||
mining,
|
||||
config_obj["SPECTER_DATA_FOLDER"],
|
||||
mining_every_x_seconds,
|
||||
echo,
|
||||
)
|
||||
|
||||
|
||||
def prepare_elements_default_wallet(my_node):
|
||||
|
|
@ -386,61 +388,75 @@ def prepare_elements_default_wallet(my_node):
|
|||
rpc.generatetoaddress(101, unconfidential)
|
||||
|
||||
|
||||
def miner_loop(node_impl, my_node, data_folder, mining_every_x_seconds, echo):
|
||||
"An endless loop mining bitcoin"
|
||||
def endless_loop(
|
||||
node_impl, my_node, mining: Boolean, data_folder, mining_every_x_seconds, echo
|
||||
):
|
||||
"""This loop can enable continuous mining"""
|
||||
|
||||
echo(
|
||||
"Now, mining a block every %f seconds, avoid it via --no-mining"
|
||||
% mining_every_x_seconds
|
||||
)
|
||||
mine_2_specter_wallets(node_impl, my_node, data_folder, echo)
|
||||
|
||||
# make them spendable
|
||||
my_node.mine(block_count=100)
|
||||
echo(
|
||||
f"height: {my_node.rpcconn.get_rpc().getblockchaininfo()['blocks']} | ",
|
||||
nl=False,
|
||||
)
|
||||
# To stop the Python process
|
||||
exit = Event()
|
||||
|
||||
def exit_now(signo, _frame):
|
||||
def exit_now(signum, frame):
|
||||
echo(f"Signal {signum} received. Terminating the Python process. Bye, bye!")
|
||||
exit.set()
|
||||
|
||||
for sig in ("HUP", "INT"):
|
||||
signal.signal(getattr(signal, "SIG" + sig), exit_now)
|
||||
signal.signal(signal.SIGINT, exit_now)
|
||||
signal.signal(signal.SIGHUP, exit_now)
|
||||
signal.signal(signal.SIGTERM, exit_now)
|
||||
# SIGKILL cannot be caught
|
||||
|
||||
if mining:
|
||||
echo(
|
||||
"Now, mining a block every %f seconds, avoid it via --no-mining"
|
||||
% mining_every_x_seconds
|
||||
)
|
||||
mine_2_specter_wallets(node_impl, my_node, data_folder, echo)
|
||||
# make them spendable
|
||||
my_node.mine(block_count=100)
|
||||
echo(
|
||||
f"height: {my_node.rpcconn.get_rpc().getblockchaininfo()['blocks']} | ",
|
||||
nl=False,
|
||||
)
|
||||
else:
|
||||
echo("Press Ctrl-C to abort and stop the node")
|
||||
|
||||
prevent_mining_file = Path("prevent_mining")
|
||||
i = 0
|
||||
while True:
|
||||
while not exit.is_set():
|
||||
try:
|
||||
current_height = my_node.rpcconn.get_rpc().getblockchaininfo()["blocks"]
|
||||
exit.wait(mining_every_x_seconds)
|
||||
if not prevent_mining_file.is_file():
|
||||
# Having a prevent_mining_file overrides the mining cli option
|
||||
if mining and not prevent_mining_file.is_file():
|
||||
my_node.mine()
|
||||
else:
|
||||
echo("%i" % (i % 10), prefix=False, nl=False)
|
||||
if i % 10 == 9:
|
||||
echo(" ", prefix=False, nl=False)
|
||||
i += 1
|
||||
if i >= 50:
|
||||
i = 0
|
||||
echo("", prefix=False)
|
||||
echo(
|
||||
f"height: {current_height} | ",
|
||||
nl=False,
|
||||
)
|
||||
elif mining:
|
||||
echo("X", prefix=False, nl=False)
|
||||
continue
|
||||
|
||||
echo("%i" % (i % 10), prefix=False, nl=False)
|
||||
if i % 10 == 9:
|
||||
echo(" ", prefix=False, nl=False)
|
||||
i += 1
|
||||
if i >= 50:
|
||||
i = 0
|
||||
echo("", prefix=False)
|
||||
echo(
|
||||
f"height: {current_height} | ",
|
||||
nl=False,
|
||||
)
|
||||
|
||||
except ConnectionError as nce:
|
||||
# This terminates the Python processes if the bitcoind / elementsd (child) processes are somehow terminated
|
||||
echo("Exiting endless loop due to lost RPC connection.")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"Caught {e}, Couldn't mine, assume SIGTERM occured => exiting!"
|
||||
f"Caught {e.__module__}, Couldn't mine, assume SIGTERM occured => exiting!"
|
||||
)
|
||||
echo(f"THE_END(@height:{current_height})")
|
||||
if prevent_mining_file.is_file():
|
||||
echo("Deleting file prevent_mining")
|
||||
prevent_mining_file.unlink()
|
||||
break
|
||||
if prevent_mining_file.is_file():
|
||||
echo("Deleting file prevent_mining")
|
||||
prevent_mining_file.unlink()
|
||||
echo(f"THE_END(@height:{current_height})")
|
||||
|
||||
|
||||
def mine_2_specter_wallets(node_impl, my_node, data_folder, echo):
|
||||
|
|
@ -451,6 +467,7 @@ def mine_2_specter_wallets(node_impl, my_node, data_folder, echo):
|
|||
# Using the dict key, not the wallet name
|
||||
exception = "fresh_wallet"
|
||||
try:
|
||||
logger.debug(f"Funding wallets in {data_folder}/wallets")
|
||||
for address in fetch_wallet_addresses_for_mining(
|
||||
node_impl, data_folder, exception
|
||||
):
|
||||
|
|
|
|||
|
|
@ -560,8 +560,6 @@ def fetch_wallet_addresses_for_mining(node_impl, data_folder, exception=None):
|
|||
Parses all the wallet jsons in the folder (default ~/.specter/wallets/regtest) and returns an array with the addresses.
|
||||
Pass a wallet name via the exception argument so that this wallet's addresses are not included.
|
||||
"""
|
||||
print(f"{data_folder}/wallets")
|
||||
print(os.listdir(f"{data_folder}"))
|
||||
addresses_all = []
|
||||
for folder in [
|
||||
folder for folder in os.listdir(data_folder) if folder.startswith("wallets")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from flask import current_app as app
|
|||
from flask import Blueprint
|
||||
from jinja2 import pass_context
|
||||
from ..helpers import to_ascii20
|
||||
from ..util.common import format_btc_amount_as_sats, format_btc_amount
|
||||
|
||||
filters_bp = Blueprint("filters", __name__)
|
||||
|
||||
|
|
@ -27,6 +28,43 @@ def timedatetime(context, s):
|
|||
return format(datetime.fromtimestamp(s), "%d.%m.%Y %H:%M")
|
||||
|
||||
|
||||
@pass_context
|
||||
@filters_bp.app_template_filter("average_of_attribute")
|
||||
def average_of_attribute(context, values, attribute):
|
||||
dicts = [
|
||||
getattr(value, attribute)
|
||||
for value in values
|
||||
if getattr(value, attribute) is not None
|
||||
]
|
||||
return sum(dicts) / len(dicts) if dicts else None
|
||||
|
||||
|
||||
@pass_context
|
||||
@filters_bp.app_template_filter("btcunitamount_fixed_decimals")
|
||||
def btcunitamount_fixed_decimals(
|
||||
context,
|
||||
value,
|
||||
maximum_digits_to_strip=7,
|
||||
minimum_digits_to_strip=6,
|
||||
enable_digit_formatting=True,
|
||||
):
|
||||
if app.specter.hide_sensitive_info:
|
||||
return "#########"
|
||||
if value is None:
|
||||
return "Unknown"
|
||||
if value < 0 and app.specter.is_liquid:
|
||||
return "Confidential"
|
||||
if app.specter.unit == "sat":
|
||||
return format_btc_amount_as_sats(value)
|
||||
|
||||
return format_btc_amount(
|
||||
value,
|
||||
maximum_digits_to_strip=maximum_digits_to_strip,
|
||||
minimum_digits_to_strip=minimum_digits_to_strip,
|
||||
enable_digit_formatting=enable_digit_formatting,
|
||||
)
|
||||
|
||||
|
||||
@pass_context
|
||||
@filters_bp.app_template_filter("btcamount")
|
||||
def btcamount(context, value):
|
||||
|
|
|
|||
|
|
@ -1310,3 +1310,31 @@ input:checked + .slider:before {
|
|||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
/************** Styles for formatting btc amounts ********************************/
|
||||
.unselectable {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.transparent-text {
|
||||
opacity: 0;
|
||||
}
|
||||
.thousand-digits-in-btc-amount{
|
||||
color: #919191;
|
||||
padding-left: 2px;
|
||||
}
|
||||
.last-digits-in-btc-amount{
|
||||
color: #797979;
|
||||
padding-left: 2px;
|
||||
}
|
||||
.thousand-digits-in-sats-amount{
|
||||
color: #919191;
|
||||
}
|
||||
.last-digits-in-sats-amount{
|
||||
color: #797979;
|
||||
}
|
||||
|
|
@ -1,117 +1,183 @@
|
|||
|
||||
{#
|
||||
total_wallet_balances - Title showing the wallet total balance + amount confirmed and unconfirmed (if there is any unconfirmed amount).
|
||||
Parameters:
|
||||
- wallet: Wallet object
|
||||
- specter: Specter object
|
||||
|
||||
total_wallet_balances(
|
||||
_("Some title"),
|
||||
amount_total,
|
||||
amount_confirmed,
|
||||
amount_unconfirmed,
|
||||
amount_immature,
|
||||
balance, # needs to be passed for liquid
|
||||
rescan_progress, # Possible values: True, False, 0..1. Disable: False or 0, Enable otherwise.
|
||||
wallet_alias, # wallet_alias to check the rescan_progress.
|
||||
# If the wallet alias exists it shows specter.info["utxorescan"], otherwise rescan_progress is used
|
||||
specter)
|
||||
|
||||
Calling example for a single wallet:
|
||||
total_wallet_balances("", wallet.amount_total, wallet.amount_confirmed, wallet.amount_unconfirmed, wallet.amount_immature, wallet.balance, wallet.rescan_progress, wallet.alias, specter)
|
||||
#}
|
||||
{% macro total_wallet_balances(wallet, specter) -%}
|
||||
{% macro total_wallet_balances(title, amount_total, amount_confirmed, amount_unconfirmed, amount_immature, balance, rescan_progress, alias, specter) -%}
|
||||
<style type="text/css">
|
||||
table {
|
||||
font-size: 1.1em;
|
||||
border: none;
|
||||
border-collapse: initial;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
font-weight: inherit;
|
||||
border-bottom: initial;
|
||||
padding:initial;
|
||||
padding-left:5px;
|
||||
}
|
||||
tr {
|
||||
background: initial;
|
||||
}
|
||||
.btn {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
button.btn {
|
||||
display: inline-flex;
|
||||
font-size: 0.9em;
|
||||
padding: 2px 10px 2px 10px;
|
||||
height: initial;
|
||||
min-height: initial;
|
||||
width: initial;
|
||||
min-width: initial;
|
||||
}
|
||||
.right-align {
|
||||
text-align: right;
|
||||
}
|
||||
h4 {
|
||||
font-weight: unset;
|
||||
}
|
||||
</style>
|
||||
<div style="display: flex; flex-direction: column; min-width: 300px; align-items: center">
|
||||
<span style="line-height:30px; font-size: 1.1em;">Balance:</span>
|
||||
<div class="smaller-card">
|
||||
<div>
|
||||
Total amount: <span id="fullbalance_amount">{{ wallet.amount_total | btcunitamount }}</span>
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
<div class="smaller-card">
|
||||
{% if title %}
|
||||
<div style="text-align: center; width:100%; line-height:30px; font-size: 1.2em;">{{ title }}</div>
|
||||
{% endif %}
|
||||
<table>
|
||||
<tr>
|
||||
<th>Total amount:</th>
|
||||
<th id="fullbalance_amount" class="right-align">{{ amount_total | btcunitamount_fixed_decimals | safe }}</th>
|
||||
<th>
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
{% endif %}
|
||||
{% if specter.price_check %}
|
||||
<th class="right-align">
|
||||
<span class="note">({{ amount_total | altunit }})</span>
|
||||
</th>
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
{% if amount_unconfirmed > 0 or amount_immature > 0 %}
|
||||
<tr>
|
||||
<th>Confirmed:</th>
|
||||
<th id="confirmed_amount" class="right-align">{{ amount_confirmed | btcunitamount_fixed_decimals | safe }}</th>
|
||||
<th>
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
{% endif %}
|
||||
{% if specter.price_check %}
|
||||
<th class="right-align">
|
||||
<span class="note">({{ amount_confirmed | altunit }})</span>
|
||||
</th>
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
{% if amount_unconfirmed > 0 %}
|
||||
<tr>
|
||||
<th>Unconfirmed:</th>
|
||||
<th id="unconfirmed_amount" class="right-align">{{ amount_unconfirmed | btcunitamount_fixed_decimals | safe }}</th>
|
||||
<th>
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
{% endif %}
|
||||
{% if specter.price_check %}
|
||||
<th class="right-align">
|
||||
<span class="note">({{ amount_unconfirmed | altunit }})</span>
|
||||
</th>
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if specter.price_check %}
|
||||
<span class="note">({{ wallet.amount_total | altunit }})</span>
|
||||
{% if amount_immature > 0 %}
|
||||
<tr>
|
||||
<th style="display: flex;">
|
||||
<span>Immature:</span>
|
||||
<tool-tip width="200px">
|
||||
<h4 slot="title">{{ _("What are immature outputs?") }}</h4>
|
||||
<span slot="paragraph">
|
||||
{{ _('The UTXO of coinbase transactions (mining rewards) have the special condition that they cannot be spent for at least 100 blocks. Their amount is not included in the total balance here.') }}
|
||||
{% if specter.chain == "regtest" %}{{ _('You see these outputs because you are using regtest and have mined blocks.')}}{% endif %}
|
||||
</span>
|
||||
</tool-tip>
|
||||
<th class="right-align">{{ amount_immature | btcunitamount_fixed_decimals | safe }}</th>
|
||||
</th>
|
||||
<th>
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
{% endif %}
|
||||
{% if specter.price_check %}
|
||||
<th class="right-align">
|
||||
<span class="note">({{ amount_immature | altunit }})</span>
|
||||
</th>
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if wallet.amount_unconfirmed > 0 or wallet.amount_immature > 0 %}
|
||||
<div>
|
||||
Confirmed: {{ wallet.amount_confirmed | btcunitamount }}
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
{% if specter.is_liquid %}
|
||||
{% if balance.get("assets", {}) %}
|
||||
<tr>
|
||||
<th>Liquid Assets:</th>
|
||||
<th colspan="2">
|
||||
{% include 'includes/overlay/liquid_assets_registry.html' %}
|
||||
<button type="button" class="btn" onclick="showPageOverlay('liquid-assets-registry')">{{ _("Show assets list") }}</button>
|
||||
</th>
|
||||
</tr>
|
||||
{% for asset in balance.get("assets",{}).keys() | sort %}
|
||||
<th></th>
|
||||
{% set balance = balance.get("assets",{}).get(asset, {}) %}
|
||||
<th class="right-align">
|
||||
{{ (balance.get("trusted", 0) + balance.get("untrusted_pending", 0) + balance.get("immature", 0)) | btcunitamount_fixed_decimals | safe }}</th>
|
||||
<th>
|
||||
<asset-label data-asset="{{asset}}" data-label="{{asset | assetlabel}}" edit-mode="enabled"></asset-label>
|
||||
</th>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if specter.price_check %}
|
||||
<span class="note">({{ wallet.amount_confirmed | altunit }})</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if wallet.amount_unconfirmed > 0 %}
|
||||
<div>
|
||||
Unconfirmed: {{ wallet.amount_unconfirmed | btcunitamount }}
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
{% endif %}
|
||||
{% if specter.price_check %}
|
||||
<span class="note">({{ wallet.amount_unconfirmed | altunit }})</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if wallet.amount_immature > 0 and specter.chain == "regtest" %}
|
||||
<div style="color: grey; display: flex;">
|
||||
Immature: {{ wallet.amount_immature | btcunitamount }}
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
{% endif %}
|
||||
{% if specter.price_check %}
|
||||
<span class="note" style="color: grey">({{ wallet.amount_immature | altunit }})</span>
|
||||
{% endif %}
|
||||
<tool-tip width="200px">
|
||||
<h4 slot="title">{{ _("What are immature outputs?") }}</h4>
|
||||
<span slot="paragraph">
|
||||
{{ _('The UTXO of coinbase transactions (mining rewards) have the special condition that they cannot be spent for at least 100 blocks. Their amount is not included in the total balance here. You see these outputs because you are using regtest and have mined blocks.') }}
|
||||
</span>
|
||||
</tool-tip>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% elif wallet.amount_immature > 0 and specter.chain == "main" %}
|
||||
<div style="color: #ccc; display: flex">
|
||||
Immature: {{ wallet.amount_immature | btcunitamount }}
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
{% endif %}
|
||||
{% if specter.price_check %}
|
||||
<span class="note" style="color: grey">({{ wallet.amount_immature | altunit }})</span>
|
||||
{% endif %}
|
||||
<tool-tip width="200px">
|
||||
<h4 slot="title">{{ _("What are immature outputs?") }}</h4>
|
||||
<span slot="paragraph">
|
||||
{{ _('The UTXO of coinbase transactions (mining rewards) have the special condition that they cannot be spent for at least 100 blocks. Their amount is not included in the total balance here.') }}
|
||||
</span>
|
||||
</tool-tip>
|
||||
</div>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% if rescan_progress or specter.utxorescanwallet == alias %}
|
||||
<div id="wallet_rescan_data" style="display: contents;">
|
||||
{% if rescan_progress %}
|
||||
<h4>Rescanning blockchain: <span id="{{ alias }}_balances_wallet_rescan_percents">{{ rescan_progress * 100 }}</span>%</h4>
|
||||
<span class="warning">
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"><br>
|
||||
Total balance and transactions history may show outdated data during scanning.<br>Please wait until the scanning is complete before you start using the wallet.
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if specter.utxorescanwallet == alias %}
|
||||
<h4>Scanning the UTXO set: <span id="{{ alias }}_balances_wallet_rescan_percents">{{ specter.info["utxorescan"] }}</span>%</h4>
|
||||
<span class="warning">
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"><br>
|
||||
Total balance may show outdated data during scanning.<br>Please wait until the scanning is complete before you start using the wallet.
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if specter.is_liquid %}
|
||||
{% if wallet.balance.get("assets", {}) %}
|
||||
<div style="margin-bottom: 15px;">
|
||||
<small style="line-height:30px">Assets:<br>
|
||||
{% for asset in wallet.balance.get("assets",{}).keys() | sort %}
|
||||
{% set balance = wallet.balance.get("assets",{}).get(asset, {}) %}
|
||||
<div style="margin: 0 10px; display: inline-block;">
|
||||
{{ (balance.get("trusted", 0) + balance.get("untrusted_pending", 0) + balance.get("immature", 0)) | btcamount }}
|
||||
<asset-label data-asset="{{asset}}" data-label="{{asset | assetlabel}}" edit-mode="enabled"></asset-label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include 'includes/overlay/liquid_assets_registry.html' %}
|
||||
<small style="line-height: 30px;">
|
||||
<button type="button" class="btn" style="margin: auto;" onclick="showPageOverlay('liquid-assets-registry')">{{ _("Show assets list") }}</button>
|
||||
</small>
|
||||
<br>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if wallet.rescan_progress or specter.utxorescanwallet == wallet.alias %}
|
||||
<span id="wallet_rescan_data" style="display: contents;">
|
||||
<h2>Rescanning blockchain: <span id="{{ wallet.alias }}_balances_wallet_rescan_percents">{{ "%.2f"|format(specter.info["utxorescan"] if specter.utxorescanwallet == wallet.alias else wallet.rescan_progress * 100) }}</span>%</h2>
|
||||
<span class="warning">
|
||||
<img src="{{ url_for('static', filename='img/info_sign.svg') }}" style="width: 20px;"/><br>
|
||||
Total balance and transactions history may show outdated data during scanning.<br>Please wait until the scanning is complete before you start using the wallet.
|
||||
</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,16 @@
|
|||
{% set tab = 'history' %}
|
||||
{% block content %}
|
||||
{% from 'wallet/history/components/total_wallet_balances.jinja' import total_wallet_balances %}
|
||||
{{ total_wallet_balances(wallet, specter) }}
|
||||
{{ total_wallet_balances(
|
||||
"",
|
||||
wallet.amount_total,
|
||||
wallet.amount_confirmed,
|
||||
wallet.amount_unconfirmed,
|
||||
wallet.amount_immature,
|
||||
wallet.balance,
|
||||
wallet.rescan_progress,
|
||||
wallet.alias,
|
||||
specter) }}
|
||||
<div class="table-holder">
|
||||
{% include "includes/services-data.html" %}
|
||||
{% include "includes/tx-row.html" %}
|
||||
|
|
|
|||
|
|
@ -18,48 +18,23 @@
|
|||
{% include "includes/address-label.html" %}
|
||||
<h1 id="title">{{ _("Wallets Overview") }}</h1>
|
||||
<p style="width: 80%; text-align: center; margin-left: auto; margin-right: auto;">{{ _("Here you can see the combined balance and transactions history of all your Specter wallets.") }}</p>
|
||||
<h1>
|
||||
<small style="line-height:30px">Total balance:</small><br>
|
||||
<span style="color: #fff">
|
||||
{% set fullbalance = specter.wallet_manager.wallets.values() | sum(attribute='fullbalance') %}
|
||||
{% set balance = specter.wallet_manager.joined_balance() %}
|
||||
<span id="fullbalance_amount">{{ fullbalance | btcunitamount }}</span>
|
||||
{% if specter.unit == 'sat' %}
|
||||
sats
|
||||
{% else %}
|
||||
{% if specter.is_testnet %}t{%endif%}{% if specter.is_liquid %}L{%endif%}BTC
|
||||
{% endif %}
|
||||
</span><br>
|
||||
<span class="note">{{ fullbalance | altunit }}</span>
|
||||
{% if balance.get("untrusted_pending", 0) or balance.get("immature", 0) %}<br>
|
||||
<small>( {{ balance.get("trusted", 0) | btcunitamount }} {{ _("confirmed") }},
|
||||
{% if balance.get("untrusted_pending", 0) %}
|
||||
{{ balance.get("untrusted_pending", 0) | btcunitamount }} {{ _("pending") }}
|
||||
{% endif %}
|
||||
{% if balance.get("immature", 0) %}
|
||||
{{ balance.get("immature", 0) | btcunitamount }} {{ _("immature") }}
|
||||
{% endif %}
|
||||
)</small>
|
||||
{% endif %}
|
||||
{% if specter.is_liquid %}
|
||||
{% if balance.get("assets", {}) %}
|
||||
<div style="margin-bottom: 15px;">
|
||||
<small style="line-height:30px">{{ _("Assets:") }}<br>
|
||||
{% for asset in balance.get("assets",{}).keys() | sort %}
|
||||
{% set asset_balance = balance.get("assets",{}).get(asset, {}) %}
|
||||
<div style="margin: 0 10px; display: inline-block;">
|
||||
{{ (asset_balance.get("trusted", 0) + asset_balance.get("untrusted_pending", 0) + asset_balance.get("immature", 0)) | btcamount }}
|
||||
<asset-label data-asset="{{asset}}" data-label="{{asset | assetlabel}}" edit-mode="enabled"></asset-label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include 'includes/overlay/liquid_assets_registry.html' %}
|
||||
<small><button type="button" class="btn" style="margin: auto;" onclick="showPageOverlay('liquid-assets-registry')">{{ _("Show assets list") }}</button></small>
|
||||
<br>
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% from 'wallet/history/components/total_wallet_balances.jinja' import total_wallet_balances %}
|
||||
{% set amount_total = specter.wallet_manager.wallets.values() | sum(attribute='amount_total') %}
|
||||
{% set amount_confirmed = specter.wallet_manager.wallets.values() | sum(attribute='amount_confirmed') %}
|
||||
{% set amount_unconfirmed = specter.wallet_manager.wallets.values() | sum(attribute='amount_unconfirmed') %}
|
||||
{% set amount_immature = specter.wallet_manager.wallets.values() | sum(attribute='amount_immature') %}
|
||||
{% set balance = specter.wallet_manager.joined_balance() %}
|
||||
{% set rescan_progress = specter.wallet_manager.wallets.values() | average_of_attribute(attribute='rescan_progress') %}
|
||||
{{ total_wallet_balances(
|
||||
_("Combined Wallet Balances"),
|
||||
amount_total,
|
||||
amount_confirmed,
|
||||
amount_unconfirmed,
|
||||
amount_immature,
|
||||
balance,
|
||||
rescan_progress,
|
||||
"",
|
||||
specter) }}
|
||||
<div class="table-holder">
|
||||
{% include "includes/tx-row.html" %}
|
||||
{% include "includes/tx-data.html" %}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import re
|
|||
from datetime import datetime
|
||||
import json
|
||||
from flask_babel.speaklater import LazyString
|
||||
from typing import Union
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -29,6 +30,100 @@ def snake_case2camelcase(word):
|
|||
return "".join(x.capitalize() or "_" for x in word.split("_"))
|
||||
|
||||
|
||||
def format_btc_amount_as_sats(
|
||||
value: Union[float, str],
|
||||
enable_digit_formatting=False,
|
||||
) -> str:
|
||||
s = "{:,.0f}".format(round(float(value) * 1e8))
|
||||
|
||||
# combine the "," with the left number to an array
|
||||
array = []
|
||||
for letter in s:
|
||||
if letter == ",":
|
||||
array[-1] += letter
|
||||
else:
|
||||
array.append(letter)
|
||||
|
||||
if enable_digit_formatting:
|
||||
if len(array) >= 4:
|
||||
left_index = -6 if len(array) >= 6 else -len(array)
|
||||
array[
|
||||
left_index
|
||||
] = f'<span class="thousand-digits-in-sats-amount">{array[left_index]}'
|
||||
array[-4] = f"{array[-4]}</span>"
|
||||
|
||||
left_index = -3 if len(array) >= 3 else -len(array)
|
||||
array[
|
||||
left_index
|
||||
] = f'<span class="last-digits-in-sats-amount">{array[left_index]}'
|
||||
array[-1] = f"{array[-1]}</span>"
|
||||
|
||||
return "".join(array)
|
||||
|
||||
|
||||
def format_btc_amount(
|
||||
value: Union[float, str],
|
||||
maximum_digits_to_strip=7,
|
||||
minimum_digits_to_strip=6,
|
||||
enable_digit_formatting=True,
|
||||
) -> str:
|
||||
"""
|
||||
Formats the btc amount such that it can be right aligned such
|
||||
that the decimal separator will be always at the same x position.
|
||||
|
||||
Stripping trailing 0's is done via just making the 0's transparent.
|
||||
|
||||
Args:
|
||||
value (Union[float, str]): Will convert string to float.
|
||||
The float is expected to be in the unit (L)BTC with 8 relevant digits
|
||||
maximum_digits_to_strip (int, optional): No more than maximum_digits_to_strip
|
||||
trailing 0's will be stripped. Defaults to 7.
|
||||
minimum_digits_to_strip (int, optional): Only strip any trailing 0's if
|
||||
there are at least minimum_digits_to_strip. Defaults to 6.
|
||||
enable_digit_formatting (bool, optional): Will group the Satoshis into blocks of 3,
|
||||
e.g. 0.03 123 456, and color the blocks. Defaults to True.
|
||||
|
||||
Returns:
|
||||
str: The formatted btc amount as html code.
|
||||
"""
|
||||
value = round(float(value), 8)
|
||||
formatted_amount = "{:,.8f}".format(value)
|
||||
|
||||
count_digits_that_can_be_stripped = 0
|
||||
for i in reversed(range(len(formatted_amount))):
|
||||
if formatted_amount[i] == "0":
|
||||
count_digits_that_can_be_stripped += 1
|
||||
continue
|
||||
break
|
||||
|
||||
array = list(formatted_amount)
|
||||
if count_digits_that_can_be_stripped >= minimum_digits_to_strip:
|
||||
# loop through the float number, e.g. 0.03 000 000, from the right and replace 0's or the '.' until you hit anything != 0
|
||||
for i in reversed(range(len(array))):
|
||||
if array[i] == "0" and len(array) - i <= maximum_digits_to_strip:
|
||||
array[
|
||||
i
|
||||
] = f'<span class="unselectable transparent-text">{array[i]}</span>'
|
||||
# since this digit == 0, then continue the loop and check the next digit
|
||||
continue
|
||||
# the following if branch is only relevant if last_digits_to_strip == 8, i.e. all digits can be stripped
|
||||
elif formatted_amount[i] == ".":
|
||||
array[
|
||||
i
|
||||
] = f'<span class="unselectable transparent-text">{array[i]}</span>'
|
||||
# since this character == '.', then the loop must be broken now
|
||||
# always break the loop. Only the digit == 0 can prevent this break
|
||||
break
|
||||
|
||||
if enable_digit_formatting:
|
||||
array[-6] = f'<span class="thousand-digits-in-btc-amount">{array[-6]}'
|
||||
array[-4] = f"{array[-4]}</span>"
|
||||
array[-3] = f'<span class="last-digits-in-btc-amount">{array[-3]}'
|
||||
array[-1] = f"{array[-1]}</span>"
|
||||
|
||||
return "".join(array)
|
||||
|
||||
|
||||
def robust_json_dumps(obj):
|
||||
def default(o):
|
||||
if isinstance(o, datetime):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/* This is the place to put all your styles */
|
||||
|
||||
pre{
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
{% extends "base.jinja" %}
|
||||
{% block head %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for(service.id +'_endpoint' + '.static', filename='devhelp/css/styles.css') }}">
|
||||
{% endblock %}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.jinja" %}
|
||||
{% extends "devhelp/base.jinja" %}
|
||||
{% block main %}
|
||||
<img src="{{ url_for(service.id +'_endpoint' + '.static', filename=service.logo) }}" width="50"/>
|
||||
{% from 'devhelp/components/devhelp_menu.jinja' import devhelp_menu with context %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
{% extends "devhelp/components/devhelp_tab.jinja" %}
|
||||
{% block title %}address-data{% endblock %}
|
||||
{% set tab = 'html' %}
|
||||
{% block content %}
|
||||
|
||||
<h1>total_wallet_balances</h1>
|
||||
|
||||
{% from 'wallet/history/components/total_wallet_balances.jinja' import total_wallet_balances %}
|
||||
|
||||
<h3>Usage</h3>
|
||||
<pre>
|
||||
total_wallet_balances(
|
||||
_("Some title"),
|
||||
amount_total,
|
||||
amount_confirmed,
|
||||
amount_unconfirmed,
|
||||
amount_immature,
|
||||
balance, # needs to be passed for liquid
|
||||
rescan_progress, # Possible values: True, False, 0..1. Disable: False or 0, Enable otherwise.
|
||||
wallet_alias, # wallet_alias to check the rescan_progress.
|
||||
# If the wallet alias exists it shows specter.info["utxorescan"], otherwise rescan_progress is used
|
||||
specter)
|
||||
|
||||
|
||||
</pre>
|
||||
|
||||
<h2>Example without rescanning</h2>
|
||||
<p>For the macro to correctly display the strings, the values for the amounts have to have 8 decimals.<br>
|
||||
Truncation of 0s depends on the defaults chosen in filters.py
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
total_wallet_balances(
|
||||
_("Some customizable text"),
|
||||
2.02067075,
|
||||
1.02065575,
|
||||
1.00001500,
|
||||
0.00020000,
|
||||
0,
|
||||
False,
|
||||
"",
|
||||
specter
|
||||
)
|
||||
</pre>
|
||||
|
||||
{{ total_wallet_balances(
|
||||
_("Some customizable text"),
|
||||
2.02067075,
|
||||
1.02065575,
|
||||
1.00001500,
|
||||
0.00020000,
|
||||
None,
|
||||
False,
|
||||
"",
|
||||
specter) }}
|
||||
|
||||
<h2 style="margin-top: 20px">Example with full blockchain rescan</h2>
|
||||
<p>Here we are simulating the rendering of the display if a full rescan is under way (thus the additional box below).</p>
|
||||
|
||||
<pre>
|
||||
total_wallet_balances(
|
||||
_("Some customizable text"),
|
||||
2.02067075,
|
||||
1.02065575,
|
||||
1.00001500,
|
||||
2.00000000,
|
||||
None,
|
||||
0.33,
|
||||
"some_existent_wallet_alias",
|
||||
specter
|
||||
)
|
||||
</pre>
|
||||
|
||||
{{ total_wallet_balances(
|
||||
_("Some customizable text"),
|
||||
2.02067075,
|
||||
1.02065575,
|
||||
1.00001500,
|
||||
2.00000000,
|
||||
None,
|
||||
0.33,
|
||||
"some_existent_wallet_alias",
|
||||
specter) }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
This Extension is only useful for Developing and testing purposes.
|
||||
</p>
|
||||
<p>
|
||||
You can checkout and test the different HTML-Components. Nothing else to see here.
|
||||
You can checkout and test the different HTML-Components.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
|
|
@ -17,4 +17,14 @@
|
|||
<li><a href="html/tx-table.jinja">tx-table</a></li>
|
||||
<li><a href="html/address-row.jinja">address-row</a></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Same for some macros.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="html/macro_total_wallet_balances.jinja">total_wallet_balances</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import logging
|
||||
import mock
|
||||
import pytest
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from cryptoadvance.specter.cli import bitcoind, elementsd
|
||||
from click.testing import CliRunner
|
||||
from mock import patch, MagicMock, call
|
||||
|
||||
|
||||
def test_bitcoind(caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(bitcoind, ["--no-mining", "--nodocker", "--cleanuphard"])
|
||||
print(result.output)
|
||||
if result.exception != None:
|
||||
# Makes searching for issues much more convenient
|
||||
traceback.print_tb(result.exception.__traceback__)
|
||||
print(result.exception, file=sys.stderr)
|
||||
assert result.exit_code == 0
|
||||
assert (
|
||||
"bitcoin-cli: bitcoin-cli -regtest -rpcport=18443 -rpcuser=bitcoin -rpcpassword=secret getblockchaininfo"
|
||||
in result.output
|
||||
)
|
||||
# This might take a lot of time because we're waiting on the bitcoind to terminate
|
||||
|
||||
|
||||
def test_elements(caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(elementsd, ["--no-mining", "--cleanuphard"])
|
||||
print(result.output)
|
||||
if result.exception != None:
|
||||
if "Couldn't find executable elementsd" in str(result.exception):
|
||||
pytest.skip(str(result.exception))
|
||||
|
||||
# Makes searching for issues much more convenient
|
||||
traceback.print_tb(result.exception.__traceback__)
|
||||
print(result.exception, file=sys.stderr)
|
||||
assert result.exit_code == 0
|
||||
assert (
|
||||
"elements-cli: elements-cli -regtest -rpcport=18884 -rpcuser=liquid -rpcpassword=secret getblockchaininfo"
|
||||
in result.output
|
||||
)
|
||||
# This might take a lot of time because we're waiting on the bitcoind to terminate
|
||||
|
|
@ -2,6 +2,8 @@ from cryptoadvance.specter.util.common import (
|
|||
camelcase2snake_case,
|
||||
snake_case2camelcase,
|
||||
str2bool,
|
||||
format_btc_amount,
|
||||
format_btc_amount_as_sats,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -29,3 +31,71 @@ def test_camelcase2snake_case():
|
|||
def test_snake_case2camelcase():
|
||||
assert snake_case2camelcase("service") == "Service"
|
||||
assert snake_case2camelcase("device_Type") == "DeviceType"
|
||||
|
||||
|
||||
def test_format_btc_amount():
|
||||
btc_amount = 1.05678000
|
||||
assert (
|
||||
format_btc_amount(btc_amount)
|
||||
== """1.05<span class="thousand-digits-in-btc-amount">678</span>\
|
||||
<span class="last-digits-in-btc-amount">000</span>"""
|
||||
)
|
||||
# All 0s stripped
|
||||
btc_amount = 1.05000000 # 1.05
|
||||
assert (
|
||||
format_btc_amount(btc_amount)
|
||||
== """1.05<span class="thousand-digits-in-btc-amount">\
|
||||
<span class="unselectable transparent-text">0</span><span class="unselectable transparent-text">0</span>\
|
||||
<span class="unselectable transparent-text">0</span></span>\
|
||||
<span class="last-digits-in-btc-amount">\
|
||||
<span class="unselectable transparent-text">0</span><span class="unselectable transparent-text">0</span>\
|
||||
<span class="unselectable transparent-text">0</span></span>"""
|
||||
)
|
||||
# Maximum amount of 0s stripped
|
||||
btc_amount = 40.00000000 # 40.0
|
||||
assert (
|
||||
format_btc_amount(btc_amount)
|
||||
== """40.0<span class="unselectable transparent-text">0</span><span class="thousand-digits-in-btc-amount">\
|
||||
<span class="unselectable transparent-text">0</span><span class="unselectable transparent-text">0</span>\
|
||||
<span class="unselectable transparent-text">0</span></span>\
|
||||
<span class="last-digits-in-btc-amount">\
|
||||
<span class="unselectable transparent-text">0</span><span class="unselectable transparent-text">0</span>\
|
||||
<span class="unselectable transparent-text">0</span></span>"""
|
||||
)
|
||||
# Last three 0s stripped
|
||||
btc_amount = 1.05678000 # 1.05678
|
||||
assert (
|
||||
format_btc_amount(btc_amount, minimum_digits_to_strip=3)
|
||||
== """1.05<span class="thousand-digits-in-btc-amount">678</span>\
|
||||
<span class="last-digits-in-btc-amount">\
|
||||
<span class="unselectable transparent-text">0</span>\
|
||||
<span class="unselectable transparent-text">0</span>\
|
||||
<span class="unselectable transparent-text">0</span></span>"""
|
||||
)
|
||||
|
||||
|
||||
def test_format_btc_amount_as_sats():
|
||||
btc_amount = 0.00560000
|
||||
assert (
|
||||
format_btc_amount_as_sats(btc_amount, enable_digit_formatting=True)
|
||||
== '<span class="thousand-digits-in-sats-amount">560,</span><span class="last-digits-in-sats-amount">000</span>'
|
||||
)
|
||||
assert format_btc_amount_as_sats(btc_amount) == "560,000"
|
||||
btc_amount = 0.10560000
|
||||
assert (
|
||||
format_btc_amount_as_sats(btc_amount, enable_digit_formatting=True)
|
||||
== '10,<span class="thousand-digits-in-sats-amount">560,</span><span class="last-digits-in-sats-amount">000</span>'
|
||||
)
|
||||
assert format_btc_amount_as_sats(btc_amount) == "10,560,000"
|
||||
btc_amount = 1.0
|
||||
assert (
|
||||
format_btc_amount_as_sats(btc_amount, enable_digit_formatting=True)
|
||||
== '100,<span class="thousand-digits-in-sats-amount">000,</span><span class="last-digits-in-sats-amount">000</span>'
|
||||
)
|
||||
assert format_btc_amount_as_sats(btc_amount) == "100,000,000"
|
||||
btc_amount = 1.56000000
|
||||
assert (
|
||||
format_btc_amount_as_sats(btc_amount, enable_digit_formatting=True)
|
||||
== '156,<span class="thousand-digits-in-sats-amount">000,</span><span class="last-digits-in-sats-amount">000</span>'
|
||||
)
|
||||
assert format_btc_amount_as_sats(btc_amount) == "156,000,000"
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ function start_node {
|
|||
fi
|
||||
fi
|
||||
echo "--> Starting $node_impl with $addopts ..."
|
||||
python3 -m cryptoadvance.specter $DEBUG $node_impl $addopts --port $node_port --create-conn-json --config $SPECTER_CONFIG &
|
||||
python3 -m cryptoadvance.specter $DEBUG $node_impl $addopts --no-mining --port $node_port --create-conn-json --config $SPECTER_CONFIG &
|
||||
if [ "$node_impl" = "bitcoind" ]; then
|
||||
bitcoind_pid=$!
|
||||
else
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue