From 9ac58d0bc0741d4d86256146f73aa1cc1e86fecb Mon Sep 17 00:00:00 2001 From: Joel Lehtonen Date: Mon, 5 Dec 2022 18:42:56 +0200 Subject: [PATCH 1/7] exchange_rate: Allow formatting amount with custom currency --- electrum/exchange_rate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py index f779a332f..cdd6d76ab 100644 --- a/electrum/exchange_rate.py +++ b/electrum/exchange_rate.py @@ -554,8 +554,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) From 01d31dd61d284221c0f9c89ae901a2cfb38f0875 Mon Sep 17 00:00:00 2001 From: Joel Lehtonen Date: Mon, 5 Dec 2022 18:44:05 +0200 Subject: [PATCH 2/7] exchange_rate: Add precisions of some cryptocurrencies There are many cryptocurrencies available in CoinGecko and some other exchange rate providers. If the user wants to use a cryptocurrency as a display currency, the precisions used to be 2. This patch adds precisions of some cryptocurrencies. --- electrum/exchange_rate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py index cdd6d76ab..a47923660 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: From 02dba54ab93e9f63a97d42a4746785b21468f269 Mon Sep 17 00:00:00 2001 From: Joel Lehtonen Date: Mon, 5 Dec 2022 18:49:21 +0200 Subject: [PATCH 3/7] commands: Add command "convert_currency" This adds command "convert_currency" which allows the user to do currency conversions via command line or JSON RPC. Wallet not required. --- electrum/commands.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/electrum/commands.py b/electrum/commands.py index 62f1cab05..3f391af0f 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,34 @@ class Commands: 'onchain_amount': format_satoshis(onchain_amount_sat), } + @command('n') + async def convert_currency(self, from_amount, from_ccy = None, to_ccy = 'BTC'): + """Converts the given amount of currency to another using the + configured exchange rate source. + """ + # Currency codes are uppercase + from_ccy = self.daemon.fx.ccy if from_ccy is None else from_ccy.upper() + to_ccy = to_ccy.upper() + # 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('Currency to convert from is unknown') + if rate_to.is_nan(): + raise Exception('Currency to convert to is unknown') + # Conversion + try: + amount = Decimal(from_amount) / rate_from * rate_to + except InvalidOperation: + raise Exception("from_amount is not a number") + return { + "amount": self.daemon.fx.ccy_amount_str(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 @@ -1350,6 +1378,7 @@ param_descriptions = { 'redeem_script': 'redeem script (hexadecimal)', 'lightning_amount': "Amount sent or received in a submarine swap. Set it to 'dryrun' to receive a value", 'onchain_amount': "Amount sent or received in a submarine swap. Set it to 'dryrun' to receive a value", + 'from_amount': 'Amount to convert', } command_options = { @@ -1401,6 +1430,8 @@ 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_ccy': (None, "Currency to convert from"), + 'to_ccy': (None, "Currency to convert to (default: BTC)"), } From 09170bdd1faf66477d7a6eae8cc70bf07547f519 Mon Sep 17 00:00:00 2001 From: Joel Lehtonen Date: Tue, 6 Dec 2022 03:30:24 +0200 Subject: [PATCH 4/7] commands: Improve error messages in convert_currency Co-authored-by: ghost43 --- electrum/commands.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 3f391af0f..13c0bd3a1 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -1327,6 +1327,8 @@ class Commands: """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 = self.daemon.fx.ccy if from_ccy is None else from_ccy.upper() to_ccy = to_ccy.upper() @@ -1335,9 +1337,9 @@ class Commands: rate_to = self.daemon.fx.exchange.get_cached_spot_quote(to_ccy) # Test if currencies exist if rate_from.is_nan(): - raise Exception('Currency to convert from is unknown') + raise Exception(f'Currency to convert from ({from_ccy}) is unknown or rate is unavailable') if rate_to.is_nan(): - raise Exception('Currency to convert to is unknown') + raise Exception(f'Currency to convert to ({to_ccy}) is unknown or rate is unavailable') # Conversion try: amount = Decimal(from_amount) / rate_from * rate_to From 0b540956fb02078a1a734e69f6f5038334fb260d Mon Sep 17 00:00:00 2001 From: Joel Lehtonen Date: Tue, 6 Dec 2022 02:19:12 +0200 Subject: [PATCH 5/7] exchange_rate: "BTC" to "BTC" rate is not guaranteed to be present Which ccy rates are available depends on the configured exchange (config key use_exchange) and the configured currency (config key currency). Only for some exchanges, the fx.ccy-BTC fx rate is available (depends on the ExchangeBase.get_rates implementation). As they say, for hodlers 1 BTC = 1 BTC. --- electrum/exchange_rate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py index a47923660..260e9770b 100644 --- a/electrum/exchange_rate.py +++ b/electrum/exchange_rate.py @@ -176,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') From 62711a57bc094332d86cae81b381810de237832a Mon Sep 17 00:00:00 2001 From: Joel Lehtonen Date: Tue, 6 Dec 2022 03:27:57 +0200 Subject: [PATCH 6/7] commands: Better default currencies in convert_currency Co-authored-by: ghost43 --- electrum/commands.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 13c0bd3a1..a727ba26f 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -1323,14 +1323,22 @@ class Commands: } @command('n') - async def convert_currency(self, from_amount, from_ccy = None, to_ccy = 'BTC'): + async def convert_currency(self, from_amount=1, from_ccy = None, to_ccy = None): """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'") + # Default currencies + if from_ccy is None and to_ccy is None: + from_ccy = 'BTC' + to_ccy = self.daemon.fx.ccy + elif from_ccy is None: + from_ccy = 'BTC' + elif to_ccy is None: + to_ccy = 'BTC' # Currency codes are uppercase - from_ccy = self.daemon.fx.ccy if from_ccy is None else from_ccy.upper() + from_ccy = from_ccy.upper() to_ccy = to_ccy.upper() # Get current rates rate_from = self.daemon.fx.exchange.get_cached_spot_quote(from_ccy) @@ -1342,11 +1350,13 @@ class Commands: raise Exception(f'Currency to convert to ({to_ccy}) is unknown or rate is unavailable') # Conversion try: - amount = Decimal(from_amount) / rate_from * rate_to + from_amount = Decimal(from_amount) + to_amount = from_amount / rate_from * rate_to except InvalidOperation: raise Exception("from_amount is not a number") return { - "amount": self.daemon.fx.ccy_amount_str(amount, False, to_ccy), + "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(), @@ -1380,7 +1390,6 @@ param_descriptions = { 'redeem_script': 'redeem script (hexadecimal)', 'lightning_amount': "Amount sent or received in a submarine swap. Set it to 'dryrun' to receive a value", 'onchain_amount': "Amount sent or received in a submarine swap. Set it to 'dryrun' to receive a value", - 'from_amount': 'Amount to convert', } command_options = { @@ -1432,8 +1441,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 (default: BTC)"), + 'to_ccy': (None, "Currency to convert to"), } From a24a928e9f6dc143bf29055389939cfb6ee102dd Mon Sep 17 00:00:00 2001 From: Joel Lehtonen OH64K Date: Tue, 6 Dec 2022 16:23:59 +0200 Subject: [PATCH 7/7] commands: Make conversion to/from BTC the default As suggested by SomberNight in PR #8091, the difference is that this commit handles currencies in case-insensitive manner. Co-authored-by: ghost43 --- electrum/commands.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index a727ba26f..f3a952f84 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -1323,23 +1323,20 @@ class Commands: } @command('n') - async def convert_currency(self, from_amount=1, from_ccy = None, to_ccy = None): + 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'") - # Default currencies - if from_ccy is None and to_ccy is None: - from_ccy = 'BTC' - to_ccy = self.daemon.fx.ccy - elif from_ccy is None: - from_ccy = 'BTC' - elif to_ccy is None: - to_ccy = 'BTC' # 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)