2018-04-06 20:11:14 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""Fetch information about coins and tokens supported by Trezor and update it in coins_details.json."""
|
2018-07-27 10:04:44 +00:00
|
|
|
import os
|
2018-04-06 20:11:14 +00:00
|
|
|
import time
|
|
|
|
import json
|
2018-07-20 14:49:45 +00:00
|
|
|
import logging
|
2018-04-06 20:11:14 +00:00
|
|
|
import requests
|
2018-07-30 13:05:19 +00:00
|
|
|
import sys
|
2018-07-30 12:25:53 +00:00
|
|
|
import coin_info
|
2018-04-06 20:11:14 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
OPTIONAL_KEYS = ("links", "notes", "wallet")
|
2018-07-27 10:04:44 +00:00
|
|
|
ALLOWED_SUPPORT_STATUS = ("yes", "no", "planned", "soon")
|
2018-07-20 14:49:45 +00:00
|
|
|
|
2018-07-30 12:25:53 +00:00
|
|
|
OVERRIDES = coin_info.load_json("coins_details.override.json")
|
|
|
|
VERSIONS = coin_info.latest_releases()
|
2018-04-06 20:11:14 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
COINMAKETCAP_CACHE = os.path.join(os.path.dirname(__file__), "coinmarketcap.json")
|
2018-06-21 14:35:29 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
COINMARKETCAP_TICKERS_URL = (
|
|
|
|
"https://api.coinmarketcap.com/v2/ticker/?start={}&convert=USD&limit=100"
|
|
|
|
)
|
2018-07-30 13:05:19 +00:00
|
|
|
COINMARKETCAP_GLOBAL_URL = "https://api.coinmarketcap.com/v2/global/"
|
2018-07-20 14:49:45 +00:00
|
|
|
|
|
|
|
|
2018-05-22 17:42:49 +00:00
|
|
|
def coinmarketcap_init():
|
2018-04-06 20:11:14 +00:00
|
|
|
try:
|
2018-07-27 10:04:44 +00:00
|
|
|
mtime = os.path.getmtime(COINMAKETCAP_CACHE)
|
|
|
|
if mtime > time.time() - 3600:
|
|
|
|
print("Using cached market cap data")
|
|
|
|
with open(COINMAKETCAP_CACHE) as f:
|
|
|
|
return json.load(f)
|
|
|
|
except Exception:
|
2018-07-20 14:49:45 +00:00
|
|
|
pass
|
2018-05-22 17:42:49 +00:00
|
|
|
|
|
|
|
print("Updating coins from coinmarketcap")
|
|
|
|
total = None
|
2018-07-27 10:04:44 +00:00
|
|
|
ctr = 0
|
|
|
|
coin_data = {}
|
2018-05-22 17:42:49 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
while total is None or ctr < total:
|
|
|
|
url = COINMARKETCAP_TICKERS_URL.format(ctr + 1)
|
2018-05-22 17:42:49 +00:00
|
|
|
data = requests.get(url).json()
|
2018-07-27 10:04:44 +00:00
|
|
|
|
2018-05-22 17:42:49 +00:00
|
|
|
if total is None:
|
2018-07-20 14:49:45 +00:00
|
|
|
total = data["metadata"]["num_cryptocurrencies"]
|
2018-07-27 10:04:44 +00:00
|
|
|
ctr += len(data["data"])
|
2018-05-22 17:42:49 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
for coin in data["data"].values():
|
|
|
|
slug = coin["website_slug"]
|
|
|
|
market_cap = coin["quotes"]["USD"]["market_cap"]
|
|
|
|
if market_cap is not None:
|
|
|
|
coin_data[slug] = int(market_cap)
|
|
|
|
|
|
|
|
print("Fetched {} of {} coins".format(ctr, total))
|
2018-05-22 17:42:49 +00:00
|
|
|
time.sleep(1)
|
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
with open(COINMAKETCAP_CACHE, "w") as f:
|
|
|
|
json.dump(coin_data, f)
|
|
|
|
|
|
|
|
return coin_data
|
|
|
|
|
|
|
|
|
|
|
|
MARKET_CAPS = coinmarketcap_init()
|
2018-05-22 17:42:49 +00:00
|
|
|
|
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
def marketcap(coin):
|
|
|
|
cap = None
|
|
|
|
if "coinmarketcap_alias" in coin:
|
2018-07-27 10:04:44 +00:00
|
|
|
cap = MARKET_CAPS.get(coin["coinmarketcap_alias"])
|
|
|
|
if cap is None:
|
2018-07-20 14:49:45 +00:00
|
|
|
slug = coin["name"].replace(" ", "-").lower()
|
2018-07-27 10:04:44 +00:00
|
|
|
cap = MARKET_CAPS.get(slug)
|
|
|
|
if cap is None:
|
|
|
|
cap = MARKET_CAPS.get(coin["shortcut"].lower())
|
2018-07-20 14:49:45 +00:00
|
|
|
return cap
|
2018-04-06 20:11:14 +00:00
|
|
|
|
2018-06-21 14:35:29 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
def update_marketcaps(coins):
|
2018-07-20 14:49:45 +00:00
|
|
|
for coin in coins.values():
|
2018-07-27 10:04:44 +00:00
|
|
|
coin["marketcap_usd"] = marketcap(coin) or 0
|
2018-04-06 20:11:14 +00:00
|
|
|
|
2018-06-21 14:35:29 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
def summary(coins):
|
|
|
|
t1_coins = 0
|
|
|
|
t2_coins = 0
|
|
|
|
supported_marketcap = 0
|
|
|
|
for coin in coins.values():
|
|
|
|
if coin.get("hidden"):
|
|
|
|
continue
|
2018-07-20 14:49:45 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
t1_enabled = coin["t1_enabled"] == "yes"
|
|
|
|
t2_enabled = coin["t2_enabled"] == "yes"
|
|
|
|
if t1_enabled:
|
|
|
|
t1_coins += 1
|
|
|
|
if t2_enabled:
|
|
|
|
t2_coins += 1
|
|
|
|
if t1_enabled or t2_enabled:
|
|
|
|
supported_marketcap += coin.get("marketcap_usd", 0)
|
2018-05-16 15:30:28 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
total_marketcap = None
|
2018-04-06 20:11:14 +00:00
|
|
|
try:
|
2018-07-27 10:04:44 +00:00
|
|
|
ret = requests.get(COINMARKETCAP_GLOBAL_URL).json()
|
|
|
|
total_marketcap = int(ret["data"]["quotes"]["USD"]["total_market_cap"])
|
2018-04-06 20:11:14 +00:00
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
return dict(
|
|
|
|
updated_at=int(time.time()),
|
|
|
|
updated_at_readable=time.asctime(),
|
|
|
|
t1_coins=t1_coins,
|
|
|
|
t2_coins=t2_coins,
|
|
|
|
marketcap_usd=supported_marketcap,
|
|
|
|
total_marketcap_usd=total_marketcap,
|
|
|
|
)
|
2018-04-11 16:37:30 +00:00
|
|
|
|
2018-06-21 14:35:29 +00:00
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
def _is_supported(support, trezor_version):
|
|
|
|
version = VERSIONS[str(trezor_version)]
|
|
|
|
nominal = support.get("trezor" + str(trezor_version))
|
|
|
|
if nominal is None:
|
|
|
|
return "no"
|
|
|
|
elif isinstance(nominal, bool):
|
|
|
|
return "yes" if nominal else "no"
|
2018-05-24 14:25:44 +00:00
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
try:
|
|
|
|
nominal_version = tuple(map(int, nominal.split(".")))
|
|
|
|
return "yes" if nominal_version <= version else "soon"
|
|
|
|
except ValueError:
|
|
|
|
return nominal
|
|
|
|
|
|
|
|
|
|
|
|
def _webwallet_support(coin, support):
|
|
|
|
"""Check the "webwallet" support property.
|
|
|
|
If set, check that at least one of the backends run on trezor.io.
|
|
|
|
If yes, assume we support the coin in our wallet.
|
|
|
|
Otherwise it's probably working with a custom backend, which means don't
|
|
|
|
link to our wallet.
|
|
|
|
"""
|
|
|
|
if not support.get("webwallet"):
|
|
|
|
return False
|
|
|
|
return any(".trezor.io" in url for url in coin["blockbook"] + coin["bitcore"])
|
|
|
|
|
|
|
|
|
|
|
|
def update_coins(coins, support_info):
|
|
|
|
res = {}
|
|
|
|
for coin in coins:
|
|
|
|
key = coin["key"]
|
|
|
|
support = support_info[key]
|
|
|
|
details = dict(
|
|
|
|
type="coin",
|
|
|
|
shortcut=coin["shortcut"],
|
|
|
|
name=coin["coin_label"],
|
|
|
|
links=dict(Homepage=coin["website"], Github=coin["github"]),
|
|
|
|
t1_enabled=_is_supported(support, 1),
|
|
|
|
t2_enabled=_is_supported(support, 2),
|
|
|
|
wallet={},
|
|
|
|
)
|
|
|
|
if _webwallet_support(coin, support):
|
|
|
|
details["wallet"]["Trezor"] = "https://wallet.trezor.io"
|
|
|
|
if support.get("other"):
|
|
|
|
details["wallet"].update(support["other"])
|
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
res[key] = details
|
2018-07-20 14:49:45 +00:00
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
def update_erc20(coins, support_info):
|
|
|
|
# TODO skip disabled networks?
|
|
|
|
res = {}
|
|
|
|
for coin in coins:
|
|
|
|
key = coin["key"]
|
|
|
|
support = support_info[key]
|
|
|
|
details = dict(
|
|
|
|
type="erc20",
|
|
|
|
network=coin["chain"],
|
|
|
|
address=coin["address"],
|
|
|
|
shortcut=coin["shortcut"],
|
|
|
|
name=coin["name"],
|
|
|
|
links={},
|
|
|
|
wallet=dict(
|
|
|
|
MyCrypto="https://mycrypto.com",
|
|
|
|
MyEtherWallet="https://www.myetherwallet.com",
|
|
|
|
),
|
|
|
|
t1_enabled=support["trezor1"],
|
|
|
|
t2_enabled=support["trezor2"],
|
|
|
|
)
|
|
|
|
if coin.get("website"):
|
|
|
|
details["links"]["Homepage"] = coin["website"]
|
|
|
|
if coin.get("social", {}).get("github"):
|
|
|
|
details["links"]["Github"] = coin["social"]["github"]
|
|
|
|
|
|
|
|
res[key] = details
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
def update_simple(coins, support_info, type):
|
|
|
|
res = {}
|
|
|
|
for coin in coins:
|
|
|
|
key = coin["key"]
|
|
|
|
support = support_info[key]
|
|
|
|
|
|
|
|
details = dict(
|
|
|
|
name=coin["name"],
|
|
|
|
shortcut=coin["shortcut"],
|
|
|
|
type=type,
|
|
|
|
t1_enabled=_is_supported(support, 1),
|
|
|
|
t2_enabled=_is_supported(support, 2),
|
|
|
|
)
|
2018-07-27 10:04:44 +00:00
|
|
|
for k in OPTIONAL_KEYS:
|
|
|
|
if k in coin:
|
|
|
|
details[k] = coin[k]
|
2018-04-06 21:10:35 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
res[key] = details
|
2018-04-11 16:37:30 +00:00
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
return res
|
2018-04-06 21:10:35 +00:00
|
|
|
|
2018-05-24 14:25:44 +00:00
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
def update_ethereum_networks(coins, support_info):
|
|
|
|
res = update_simple(coins, support_info, "coin")
|
|
|
|
for coin in coins:
|
2018-07-27 10:04:44 +00:00
|
|
|
res[coin["key"]].update(
|
|
|
|
wallet=dict(
|
|
|
|
MyCrypto="https://mycrypto.com",
|
|
|
|
MyEtherWallet="https://www.myetherwallet.com",
|
|
|
|
),
|
|
|
|
links=dict(Homepage=coin.get("url")),
|
2018-07-20 14:49:45 +00:00
|
|
|
)
|
|
|
|
return res
|
2018-04-11 16:37:30 +00:00
|
|
|
|
2018-06-21 14:35:29 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
def check_missing_data(coins):
|
|
|
|
for k, coin in coins.items():
|
2018-05-25 09:29:37 +00:00
|
|
|
hide = False
|
2018-05-24 14:25:44 +00:00
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
if "Homepage" not in coin.get("links", {}):
|
2018-04-11 16:37:30 +00:00
|
|
|
print("%s: Missing homepage" % k)
|
2018-05-25 09:29:37 +00:00
|
|
|
hide = True
|
2018-07-27 10:04:44 +00:00
|
|
|
if coin["t1_enabled"] not in ALLOWED_SUPPORT_STATUS:
|
2018-04-11 16:37:30 +00:00
|
|
|
print("%s: Unknown t1_enabled" % k)
|
2018-05-25 09:29:37 +00:00
|
|
|
hide = True
|
2018-07-27 10:04:44 +00:00
|
|
|
if coin["t2_enabled"] not in ALLOWED_SUPPORT_STATUS:
|
2018-04-11 16:37:30 +00:00
|
|
|
print("%s: Unknown t2_enabled" % k)
|
2018-05-25 09:29:37 +00:00
|
|
|
hide = True
|
2018-07-20 14:49:45 +00:00
|
|
|
if (
|
|
|
|
"Trezor" in coin.get("wallet", {})
|
|
|
|
and coin["wallet"]["Trezor"] != "https://wallet.trezor.io"
|
|
|
|
):
|
2018-06-21 13:14:34 +00:00
|
|
|
print("%s: Strange URL for Trezor Wallet" % k)
|
2018-05-25 09:29:37 +00:00
|
|
|
hide = True
|
2018-04-06 20:11:14 +00:00
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
if len(coin.get("wallet", {})) == 0:
|
2018-06-22 12:43:51 +00:00
|
|
|
print("%s: Missing wallet" % k)
|
2018-05-25 09:29:37 +00:00
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
if "Testnet" in coin["name"]:
|
|
|
|
print("%s: Hiding testnet" % k)
|
|
|
|
hide = True
|
|
|
|
|
2018-05-25 09:29:37 +00:00
|
|
|
if hide:
|
2018-07-20 14:49:45 +00:00
|
|
|
if coin.get("hidden") != 1:
|
2018-06-21 13:14:34 +00:00
|
|
|
print("%s: HIDING COIN!" % k)
|
|
|
|
|
2018-05-25 09:29:37 +00:00
|
|
|
# If any of important detail is missing, hide coin from list
|
2018-07-20 14:49:45 +00:00
|
|
|
coin["hidden"] = 1
|
2018-05-25 09:29:37 +00:00
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
if not hide and coin.get("hidden"):
|
2018-05-25 09:29:37 +00:00
|
|
|
print("%s: Details are OK, but coin is still hidden" % k)
|
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
# summary of hidden coins
|
|
|
|
for k, coin in coins.items():
|
|
|
|
if coin.get("hidden") == 1:
|
2018-05-25 09:29:37 +00:00
|
|
|
print("%s: Coin is hidden" % k)
|
2018-05-24 14:25:44 +00:00
|
|
|
|
2018-06-21 14:35:29 +00:00
|
|
|
|
2018-07-20 14:49:45 +00:00
|
|
|
def apply_overrides(coins):
|
|
|
|
for key, override in OVERRIDES.items():
|
|
|
|
if key not in coins:
|
2018-07-27 10:04:44 +00:00
|
|
|
LOG.warning("override without coin: {}".format(key))
|
2018-07-20 14:49:45 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
def recursive_update(orig, new):
|
|
|
|
if isinstance(new, dict) and isinstance(orig, dict):
|
|
|
|
for k, v in new.items():
|
|
|
|
orig[k] = recursive_update(orig.get(k), v)
|
|
|
|
else:
|
|
|
|
return new
|
|
|
|
|
|
|
|
coin = coins[key]
|
|
|
|
recursive_update(coin, override)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2018-07-30 13:05:19 +00:00
|
|
|
# setup logging
|
|
|
|
root = logging.getLogger()
|
|
|
|
root.setLevel(logging.DEBUG)
|
|
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
|
|
handler.setLevel(logging.DEBUG)
|
|
|
|
root.addHandler(handler)
|
|
|
|
|
2018-07-30 12:25:53 +00:00
|
|
|
defs = coin_info.get_all()
|
|
|
|
support_info = coin_info.support_info(defs, erc20_versions=VERSIONS)
|
2018-07-20 14:49:45 +00:00
|
|
|
|
|
|
|
coins = {}
|
2018-07-30 12:25:53 +00:00
|
|
|
coins.update(update_coins(defs.coins, support_info))
|
|
|
|
coins.update(update_erc20(defs.erc20, support_info))
|
|
|
|
coins.update(update_ethereum_networks(defs.eth, support_info))
|
|
|
|
coins.update(update_simple(defs.nem, support_info, "mosaic"))
|
|
|
|
coins.update(update_simple(defs.misc, support_info, "coin"))
|
2018-07-20 14:49:45 +00:00
|
|
|
|
|
|
|
apply_overrides(coins)
|
2018-07-27 10:04:44 +00:00
|
|
|
update_marketcaps(coins)
|
|
|
|
check_missing_data(coins)
|
2018-04-06 20:11:14 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
info = summary(coins)
|
|
|
|
details = dict(coins=coins, info=info)
|
2018-07-20 14:49:45 +00:00
|
|
|
|
2018-07-27 10:04:44 +00:00
|
|
|
print(json.dumps(info, sort_keys=True, indent=4))
|
2018-07-30 12:25:53 +00:00
|
|
|
with open(os.path.join(coin_info.DEFS_DIR, "coins_details.json"), "w") as f:
|
2018-07-27 10:04:44 +00:00
|
|
|
json.dump(details, f, sort_keys=True, indent=4)
|