mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 04:18:10 +00:00
common/tools: add maxfee.py for updating maxfee_kb
This commit is contained in:
parent
9849d84a5e
commit
f6b7622dd2
@ -85,6 +85,15 @@ See docstrings for the most important functions: `coin_info()` and `support_info
|
|||||||
The file `coindef.py` is a protobuf definition for passing coin data to Trezor
|
The file `coindef.py` is a protobuf definition for passing coin data to Trezor
|
||||||
from the outside.
|
from the outside.
|
||||||
|
|
||||||
|
### `marketcap.py`
|
||||||
|
|
||||||
|
Module for obtaining market cap and price data used by `coins_details.py` and `maxfee.py`.
|
||||||
|
|
||||||
|
### `maxfee.py`
|
||||||
|
|
||||||
|
Updates the `maxfee_kb` coin property based on a specified maximum per-transaction fee. The command
|
||||||
|
fetches current price data from https://coinmarketcap.com/ to convert from fiat-denominated maximum
|
||||||
|
fee.
|
||||||
|
|
||||||
# Release Workflow
|
# Release Workflow
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ COINMAKETCAP_CACHE = os.path.join(os.path.dirname(__file__), "coinmarketcap.json
|
|||||||
COINMARKETCAP_API_BASE = "https://pro-api.coinmarketcap.com/v1/"
|
COINMARKETCAP_API_BASE = "https://pro-api.coinmarketcap.com/v1/"
|
||||||
|
|
||||||
MARKET_CAPS = {}
|
MARKET_CAPS = {}
|
||||||
|
PRICES = {}
|
||||||
|
|
||||||
|
|
||||||
def call(endpoint, api_key, params=None):
|
def call(endpoint, api_key, params=None):
|
||||||
@ -20,7 +21,7 @@ def call(endpoint, api_key, params=None):
|
|||||||
|
|
||||||
|
|
||||||
def init(api_key, refresh=None):
|
def init(api_key, refresh=None):
|
||||||
global MARKET_CAPS
|
global MARKET_CAPS, PRICES
|
||||||
|
|
||||||
force_refresh = refresh is True
|
force_refresh = refresh is True
|
||||||
disable_refresh = refresh is False
|
disable_refresh = refresh is False
|
||||||
@ -59,20 +60,24 @@ def init(api_key, refresh=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError("market cap data unavailable") from e
|
raise RuntimeError("market cap data unavailable") from e
|
||||||
|
|
||||||
coin_data = {}
|
cap_data = {}
|
||||||
|
price_data = {}
|
||||||
for coin in coinmarketcap_data["data"]:
|
for coin in coinmarketcap_data["data"]:
|
||||||
slug = coin["slug"]
|
slug = coin["slug"]
|
||||||
|
symbol = coin["symbol"]
|
||||||
platform = coin["meta"]["platform"]
|
platform = coin["meta"]["platform"]
|
||||||
market_cap = coin["quote"]["USD"]["market_cap"]
|
market_cap = coin["quote"]["USD"]["market_cap"]
|
||||||
|
price = coin["quote"]["USD"]["price"]
|
||||||
if market_cap is not None:
|
if market_cap is not None:
|
||||||
coin_data[slug] = int(market_cap)
|
cap_data[slug] = int(market_cap)
|
||||||
|
price_data[symbol] = price
|
||||||
if platform is not None and platform["name"] == "Ethereum":
|
if platform is not None and platform["name"] == "Ethereum":
|
||||||
address = platform["token_address"].lower()
|
address = platform["token_address"].lower()
|
||||||
coin_data[address] = int(market_cap)
|
cap_data[address] = int(market_cap)
|
||||||
|
price_data[symbol] = price
|
||||||
|
|
||||||
MARKET_CAPS = coin_data
|
MARKET_CAPS = cap_data
|
||||||
|
PRICES = price_data
|
||||||
return coin_data
|
|
||||||
|
|
||||||
|
|
||||||
def marketcap(coin):
|
def marketcap(coin):
|
||||||
@ -89,3 +94,7 @@ def marketcap(coin):
|
|||||||
if cap is None:
|
if cap is None:
|
||||||
cap = MARKET_CAPS.get(coin["shortcut"].lower())
|
cap = MARKET_CAPS.get(coin["shortcut"].lower())
|
||||||
return cap
|
return cap
|
||||||
|
|
||||||
|
|
||||||
|
def fiat_price(coin_symbol):
|
||||||
|
return PRICES.get(coin_symbol)
|
||||||
|
125
common/tools/maxfee.py
Normal file
125
common/tools/maxfee.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Updates maxfee_kb in given JSON coin definitions."""
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
import coin_info
|
||||||
|
import marketcap
|
||||||
|
|
||||||
|
DEFAULT_SKIP_RE = (
|
||||||
|
r"^Bitcoin$",
|
||||||
|
r"^Regtest$",
|
||||||
|
r"Testnet",
|
||||||
|
)
|
||||||
|
MAX_DELTA_PERCENT = 25
|
||||||
|
|
||||||
|
|
||||||
|
def round_sats(sats, precision=1):
|
||||||
|
"""
|
||||||
|
Truncates `sats` down to a number with more trailing zeros.
|
||||||
|
|
||||||
|
The result is comprised of `precision`+1 of leading digits followed by rest of zeros.
|
||||||
|
|
||||||
|
>>> round_sats(123456789, precision=2)
|
||||||
|
123000000
|
||||||
|
>>> round_sats(123456789, precision=0)
|
||||||
|
100000000
|
||||||
|
"""
|
||||||
|
exp = math.floor(math.log10(sats))
|
||||||
|
div = 10 ** (exp - precision)
|
||||||
|
return sats // div * div
|
||||||
|
|
||||||
|
|
||||||
|
def compute_maxfee(maxcost, price, txsize):
|
||||||
|
coins_per_tx = maxcost / price
|
||||||
|
sats_per_tx = coins_per_tx * 10 ** 8
|
||||||
|
tx_per_kb = 1024.0 / txsize
|
||||||
|
sats_per_kb = sats_per_tx * tx_per_kb
|
||||||
|
return int(sats_per_kb)
|
||||||
|
|
||||||
|
|
||||||
|
def delta_percent(old, new):
|
||||||
|
return int(abs(new - old) / old * 100.0)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(verbose):
|
||||||
|
log_level = logging.DEBUG if verbose else logging.WARNING
|
||||||
|
root = logging.getLogger()
|
||||||
|
root.setLevel(log_level)
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
handler.setLevel(log_level)
|
||||||
|
root.addHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
# fmt: off
|
||||||
|
@click.argument("filename", nargs=-1, type=click.Path(writable=True))
|
||||||
|
@click.option("-m", "--cost", type=float, default=10.0, show_default=True, help="Maximum transaction fee in USD")
|
||||||
|
@click.option("-s", "--txsize", type=int, default=250, show_default=True, help="Transaction size in bytes")
|
||||||
|
@click.option("-S", "--skip", type=str, multiple=True, help="Regex of coin name to skip, can be used multiple times")
|
||||||
|
@click.option("-r", "--refresh", "refresh", flag_value=True, default=None, help="Force refresh market cap info")
|
||||||
|
@click.option("-R", "--no-refresh", "refresh", flag_value=False, default=None, help="Force use cached market cap info")
|
||||||
|
@click.option("-A", "--api-key", required=True, envvar="COINMARKETCAP_API_KEY", help="Coinmarketcap API key")
|
||||||
|
@click.option("-v", "--verbose", is_flag=True, help="Display more info")
|
||||||
|
# fmt: on
|
||||||
|
def main(filename, cost, skip, txsize, refresh, api_key, verbose):
|
||||||
|
"""
|
||||||
|
Updates maxfee_kb in JSON coin definitions.
|
||||||
|
|
||||||
|
The new value is calculated from the --cost argument which specifies maximum
|
||||||
|
transaction fee in fiat denomination. The fee is converted to coin value
|
||||||
|
using current price data. Then per-kilobyte fee is computed using given
|
||||||
|
transaction size.
|
||||||
|
|
||||||
|
If no filenames are provided, all definitions except Bitcoin and testnets
|
||||||
|
are updated.
|
||||||
|
"""
|
||||||
|
setup_logging(verbose)
|
||||||
|
marketcap.init(api_key, refresh=refresh)
|
||||||
|
|
||||||
|
if len(filename) > 0:
|
||||||
|
coin_files = filename
|
||||||
|
else:
|
||||||
|
coin_files = glob.glob(os.path.join(coin_info.DEFS_DIR, "bitcoin", "*.json"))
|
||||||
|
if len(skip) == 0:
|
||||||
|
skip = DEFAULT_SKIP_RE
|
||||||
|
|
||||||
|
for filename in sorted(coin_files):
|
||||||
|
coin = coin_info.load_json(filename)
|
||||||
|
short = coin["coin_shortcut"]
|
||||||
|
|
||||||
|
if any(re.search(s, coin["coin_name"]) is not None for s in skip):
|
||||||
|
logging.warning("{}:\tskipping because --skip matches".format(short))
|
||||||
|
continue
|
||||||
|
|
||||||
|
price = marketcap.fiat_price(short)
|
||||||
|
if price is None:
|
||||||
|
logging.error("{}:\tno price data, skipping".format(short))
|
||||||
|
continue
|
||||||
|
|
||||||
|
old_maxfee_kb = coin["maxfee_kb"]
|
||||||
|
new_maxfee_kb = round_sats(compute_maxfee(cost, price, txsize))
|
||||||
|
if old_maxfee_kb != new_maxfee_kb:
|
||||||
|
coin["maxfee_kb"] = new_maxfee_kb
|
||||||
|
with open(filename, "w") as fh:
|
||||||
|
json.dump(coin, fh, indent=2)
|
||||||
|
fh.write("\n")
|
||||||
|
logging.info(
|
||||||
|
"{}:\tupdated {} -> {}".format(short, old_maxfee_kb, new_maxfee_kb)
|
||||||
|
)
|
||||||
|
delta = delta_percent(old_maxfee_kb, new_maxfee_kb)
|
||||||
|
if delta > MAX_DELTA_PERCENT:
|
||||||
|
logging.warning("{}:\tprice has changed by {} %".format(short, delta))
|
||||||
|
else:
|
||||||
|
logging.info("{}:\tno change".format(short))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
Reference in New Issue
Block a user