diff --git a/common/defs/wallets.json b/common/defs/wallets.json index 09f4b297b..2b918515d 100644 --- a/common/defs/wallets.json +++ b/common/defs/wallets.json @@ -123,5 +123,13 @@ }, "eth:WAN": { "Wanchain Wallet": "https://www.wanchain.org/getstarted/" + }, + "eth:AUX": { + "MyEtherWallet": null + }, + "eth:XDC": { + "MyCrypto": null, + "MyEtherWallet": null, + "XDC Wallet": "https://wallet.xinfin.network" } } diff --git a/common/tools/coin_info.py b/common/tools/coin_info.py index e2f37e9af..a4473980d 100755 --- a/common/tools/coin_info.py +++ b/common/tools/coin_info.py @@ -50,6 +50,9 @@ class SupportInfoItem(TypedDict): SupportInfo = dict[str, SupportInfoItem] +WalletItems = dict[str, str] +WalletInfo = dict[str, WalletItems] + class Coin(TypedDict): # Necessary fields for BTC - from BTC_CHECKS @@ -86,7 +89,7 @@ class Coin(TypedDict): # Other fields optionally coming from JSON links: dict[str, str] - wallet: dict[str, str] + wallet: WalletItems curve: str decimals: int @@ -540,6 +543,115 @@ def support_info(coins: Iterable[Coin] | CoinsInfo | dict[str, Coin]) -> Support return support +# ====== wallet info ====== + +WALLET_SUITE = {"Trezor Suite": "https://suite.trezor.io"} +WALLET_NEM = {"Nano Wallet": "https://nemplatform.com/wallets/#desktop"} +WALLETS_ETH_3RDPARTY = { + "MyEtherWallet": "https://www.myetherwallet.com", + "MyCrypto": "https://mycrypto.com", +} + + +def get_wallet_data() -> WalletInfo: + """Get wallet data from `wallets.json`.""" + return load_json("wallets.json") + + +def _suite_support(coin: Coin, support: SupportInfoItem) -> bool: + """Check the "suite" 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["suite"]: + return False + return any(".trezor.io" in url for url in coin["blockbook"]) + + +def wallet_info_single( + support_data: SupportInfo, + eth_networks_support_data: SupportInfo, + wallet_data: WalletInfo, + coin: Coin, +) -> WalletItems: + """Adds together a dict of all wallets for a coin.""" + wallets: WalletItems = {} + + key = coin["key"] + + # Add wallets from the coin itself + # (usually not there, only for the `misc` category) + wallets.update(coin.get("wallet", {})) + + # Each coin category has different further logic + if key.startswith("bitcoin:"): + if _suite_support(coin, support_data[key]): + wallets.update(WALLET_SUITE) + elif key.startswith("eth:"): + if support_data[key]["suite"]: + wallets.update(WALLET_SUITE) + else: + wallets.update(WALLETS_ETH_3RDPARTY) + elif key.startswith("erc20:"): + if eth_networks_support_data[coin["chain"]]["suite"]: + wallets.update(WALLET_SUITE) + else: + wallets.update(WALLETS_ETH_3RDPARTY) + elif key.startswith("nem:"): + wallets.update(WALLET_NEM) + elif key.startswith("misc:"): + pass # no special logic here + else: + raise ValueError(f"Unknown coin category: {key}") + + # Add wallets from `wallets.json` + # This must come last as it offers the ability to override existing wallets + # (for example with `"MyEtherWallet": null` we delete the MyEtherWallet from the coin) + wallets.update(wallet_data.get(key, {})) + + # Removing potentially disabled wallets from the last step + wallets = {name: url for name, url in wallets.items() if url} + + return wallets + + +def wallet_info(coins: Iterable[Coin] | CoinsInfo | dict[str, Coin]) -> WalletInfo: + """Generate Trezor wallet information. + + Takes a collection of coins and generates a WalletItems entry for each. + The WalletItems is a dict with keys being the names of the wallets and + values being the URLs to those - same format as in `wallets.json`. + + The `coins` argument can be a `CoinsInfo` object, a list or a dict of + coin items. + + Wallet information is taken from `wallets.json`. + """ + if isinstance(coins, CoinsInfo): + coins = coins.as_list() + elif isinstance(coins, dict): + coins = coins.values() + + support_data = support_info(coins) + wallet_data = get_wallet_data() + + # Needed to find out suitable wallets for all the erc20 coins (Suite vs 3rd party) + eth_networks = [coin for coin in coins if coin["key"].startswith("eth:")] + eth_networks_support_data = { + n["chain"]: support_data[n["key"]] for n in eth_networks + } + + wallet: WalletInfo = {} + for coin in coins: + wallet[coin["key"]] = wallet_info_single( + support_data, eth_networks_support_data, wallet_data, coin + ) + + return wallet + + # ====== data cleanup functions ====== diff --git a/common/tools/cointool.py b/common/tools/cointool.py index ee2d4daa6..68960affc 100755 --- a/common/tools/cointool.py +++ b/common/tools/cointool.py @@ -711,6 +711,7 @@ device_choice = click.Choice(["connect", "suite", "trezor1", "trezor2"]) # fmt: off @click.option("-o", "--outfile", type=click.File(mode="w"), default="-") @click.option("-s/-S", "--support/--no-support", default=True, help="Include support data for each coin") +@click.option("-w/-W", "--wallet/--no-wallet", default=True, help="Include wallet data for each coin") @click.option("-p", "--pretty", is_flag=True, help="Generate nicely formatted JSON") @click.option("-l", "--list", "flat_list", is_flag=True, help="Output a flat list of coins") @click.option("-i", "--include", metavar="FIELD", multiple=True, help="Include only these fields (-i shortcut -i name)") @@ -726,6 +727,7 @@ device_choice = click.Choice(["connect", "suite", "trezor1", "trezor2"]) def dump( outfile: TextIO, support: bool, + wallet: bool, pretty: bool, flat_list: bool, include: tuple[str, ...], @@ -770,6 +772,9 @@ def dump( Also devices can be used as filters. For example to find out which coins are supported in Suite and connect but not on Trezor 1, it is possible to say '-d suite -d connect -D trezor1'. + + Includes even the wallet data, unless turned off by '-W'. + These can be filtered by using '-f', for example `-f 'wallet=*exodus*'` (* are necessary) """ if exclude_tokens: exclude_type += ("erc20",) @@ -786,6 +791,7 @@ def dump( # getting initial info coins = coin_info.coin_info() support_info = coin_info.support_info(coins.as_list()) + wallet_info = coin_info.wallet_info(coins) # optionally adding support info if support: @@ -793,6 +799,12 @@ def dump( for coin in category: coin["support"] = support_info[coin["key"]] + # optionally adding wallet info + if wallet: + for category in coins.values(): + for coin in category: + coin["wallet"] = wallet_info[coin["key"]] + # filter types if include_type: coins_dict = {k: v for k, v in coins.items() if k in include_type}