diff --git a/electrum/commands.py b/electrum/commands.py index 7b0508a4f..ee0763d48 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -36,7 +36,7 @@ import inspect from collections import defaultdict from functools import wraps, partial from itertools import repeat -from decimal import Decimal +from decimal import Decimal, InvalidOperation from typing import Optional, TYPE_CHECKING, Dict, List import os @@ -1322,6 +1322,43 @@ class Commands: 'onchain_amount': format_satoshis(onchain_amount_sat), } + @command('n') + async def convert_currency(self, from_amount=1, from_ccy = '', to_ccy = ''): + """Converts the given amount of currency to another using the + configured exchange rate source. + """ + if not self.daemon.fx.is_enabled(): + raise Exception("FX is disabled. To enable, run: 'electrum setconfig use_exchange_rate true'") + # Currency codes are uppercase + from_ccy = from_ccy.upper() + to_ccy = to_ccy.upper() + # Default currencies + if from_ccy == '': + from_ccy = "BTC" if to_ccy != "BTC" else self.daemon.fx.ccy + if to_ccy == '': + to_ccy = "BTC" if from_ccy != "BTC" else self.daemon.fx.ccy + # Get current rates + rate_from = self.daemon.fx.exchange.get_cached_spot_quote(from_ccy) + rate_to = self.daemon.fx.exchange.get_cached_spot_quote(to_ccy) + # Test if currencies exist + if rate_from.is_nan(): + raise Exception(f'Currency to convert from ({from_ccy}) is unknown or rate is unavailable') + if rate_to.is_nan(): + raise Exception(f'Currency to convert to ({to_ccy}) is unknown or rate is unavailable') + # Conversion + try: + from_amount = Decimal(from_amount) + to_amount = from_amount / rate_from * rate_to + except InvalidOperation: + raise Exception("from_amount is not a number") + return { + "from_amount": self.daemon.fx.ccy_amount_str(from_amount, False, from_ccy), + "to_amount": self.daemon.fx.ccy_amount_str(to_amount, False, to_ccy), + "from_ccy": from_ccy, + "to_ccy": to_ccy, + "source": self.daemon.fx.exchange.name(), + } + def eval_bool(x: str) -> bool: if x == 'false': return False @@ -1400,6 +1437,9 @@ command_options = { 'connection_string': (None, "Lightning network node ID or network address"), 'new_fee_rate': (None, "The Updated/Increased Transaction fee rate (in sat/byte)"), 'strategies': (None, "Select RBF any one or multiple RBF strategies in any order, separated by ','; Options : 'CoinChooser','DecreaseChange','DecreasePayment' "), + 'from_amount': (None, "Amount to convert (default: 1)"), + 'from_ccy': (None, "Currency to convert from"), + 'to_ccy': (None, "Currency to convert to"), } diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py index f779a332f..260e9770b 100644 --- a/electrum/exchange_rate.py +++ b/electrum/exchange_rate.py @@ -34,7 +34,10 @@ CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0, 'JOD': 3, 'JPY': 0, 'KMF': 0, 'KRW': 0, 'KWD': 3, 'LYD': 3, 'MGA': 1, 'MRO': 1, 'OMR': 3, 'PYG': 0, 'RWF': 0, 'TND': 3, 'UGX': 0, 'UYI': 0, 'VND': 0, - 'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0} + 'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0, + # Cryptocurrencies + 'BTC': 8, 'LTC': 8, 'XRP': 6, 'ETH': 18, + } def to_decimal(x: Union[str, float, int, Decimal]) -> Decimal: @@ -173,6 +176,8 @@ class ExchangeBase(Logger): def get_cached_spot_quote(self, ccy: str) -> Decimal: """Returns the cached exchange rate as a Decimal""" + if ccy == 'BTC': + return Decimal(1) rate = self._quotes.get(ccy) if rate is None: return Decimal('NaN') @@ -554,8 +559,8 @@ class FxThread(ThreadJob, EventListener): def remove_thousands_separator(text): return text.replace(',', '') # FIXME use THOUSAND_SEPARATOR in util - def ccy_amount_str(self, amount, commas): - prec = CCY_PRECISIONS.get(self.ccy, 2) + def ccy_amount_str(self, amount, commas, ccy=None): + prec = CCY_PRECISIONS.get(self.ccy if ccy is None else ccy, 2) fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec)) # FIXME use util.THOUSAND_SEPARATOR and util.DECIMAL_POINT try: rounded_amount = round(amount, prec)