diff --git a/lnbits/extensions/bleskomat/crud.py b/lnbits/extensions/bleskomat/crud.py index 470d87cbb..690793501 100644 --- a/lnbits/extensions/bleskomat/crud.py +++ b/lnbits/extensions/bleskomat/crud.py @@ -6,19 +6,30 @@ from . import db from .models import Bleskomat, BleskomatLnurl from .helpers import generate_bleskomat_lnurl_hash + async def create_bleskomat( *, wallet_id: str, name: str, fiat_currency: str, exchange_rate_provider: str, fee: str ) -> Bleskomat: bleskomat_id = uuid4().hex api_key_id = secrets.token_hex(8) api_key_secret = secrets.token_hex(32) - api_key_encoding = "hex"; + api_key_encoding = "hex" await db.execute( """ INSERT INTO bleskomats (id, wallet, api_key_id, api_key_secret, api_key_encoding, name, fiat_currency, exchange_rate_provider, fee) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, - (bleskomat_id, wallet_id, api_key_id, api_key_secret, api_key_encoding, name, fiat_currency, exchange_rate_provider, fee), + ( + bleskomat_id, + wallet_id, + api_key_id, + api_key_secret, + api_key_encoding, + name, + fiat_currency, + exchange_rate_provider, + fee, + ), ) bleskomat = await get_bleskomat(bleskomat_id) assert bleskomat, "Newly created bleskomat couldn't be retrieved" @@ -65,7 +76,19 @@ async def create_bleskomat_lnurl( INSERT INTO bleskomat_lnurls (id, bleskomat, wallet, hash, tag, params, api_key_id, initial_uses, remaining_uses, created_time, updated_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, - (bleskomat_lnurl_id, bleskomat.id, bleskomat.wallet, hash, tag, params, bleskomat.api_key_id, uses, uses, now, now), + ( + bleskomat_lnurl_id, + bleskomat.id, + bleskomat.wallet, + hash, + tag, + params, + bleskomat.api_key_id, + uses, + uses, + now, + now, + ), ) bleskomat_lnurl = await get_bleskomat_lnurl(secret) assert bleskomat_lnurl, "Newly created bleskomat LNURL couldn't be retrieved" diff --git a/lnbits/extensions/bleskomat/exchange_rates.py b/lnbits/extensions/bleskomat/exchange_rates.py index 15547370c..6d9297c60 100644 --- a/lnbits/extensions/bleskomat/exchange_rates.py +++ b/lnbits/extensions/bleskomat/exchange_rates.py @@ -2,39 +2,41 @@ import httpx import json import os -fiat_currencies = json.load(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'fiat_currencies.json'), 'r')) +fiat_currencies = json.load( + open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "fiat_currencies.json"), "r") +) exchange_rate_providers = { "bitfinex": { "name": "Bitfinex", "domain": "bitfinex.com", "api_url": "https://api.bitfinex.com/v1/pubticker/{from}{to}", - "getter": lambda data, replacements: data["last_price"] + "getter": lambda data, replacements: data["last_price"], }, "bitstamp": { "name": "Bitstamp", "domain": "bitstamp.net", "api_url": "https://www.bitstamp.net/api/v2/ticker/{from}{to}/", - "getter": lambda data, replacements: data["last"] + "getter": lambda data, replacements: data["last"], }, "coinbase": { "name": "Coinbase", "domain": "coinbase.com", "api_url": "https://api.coinbase.com/v2/exchange-rates?currency={FROM}", - "getter": lambda data, replacements: data["data"]["rates"][replacements["TO"]] + "getter": lambda data, replacements: data["data"]["rates"][replacements["TO"]], }, "coinmate": { "name": "CoinMate", "domain": "coinmate.io", "api_url": "https://coinmate.io/api/ticker?currencyPair={FROM}_{TO}", - "getter": lambda data, replacements: data["data"]["last"] + "getter": lambda data, replacements: data["data"]["last"], }, "kraken": { "name": "Kraken", "domain": "kraken.com", "api_url": "https://api.kraken.com/0/public/Ticker?pair=XBT{TO}", - "getter": lambda data, replacements: data["result"]["XXBTZ" + replacements["TO"]]["c"][0] - } + "getter": lambda data, replacements: data["result"]["XXBTZ" + replacements["TO"]]["c"][0], + }, } exchange_rate_providers_serializable = {} @@ -48,12 +50,7 @@ for ref, exchange_rate_provider in exchange_rate_providers.items(): async def fetch_fiat_exchange_rate(currency: str, provider: str): - replacements = { - "FROM" : "BTC", - "from" : "btc", - "TO" : currency.upper(), - "to" : currency.lower() - } + replacements = {"FROM": "BTC", "from": "btc", "TO": currency.upper(), "to": currency.lower()} url = exchange_rate_providers[provider]["api_url"] for key in replacements.keys(): diff --git a/lnbits/extensions/bleskomat/helpers.py b/lnbits/extensions/bleskomat/helpers.py index 439795be0..9b745c797 100644 --- a/lnbits/extensions/bleskomat/helpers.py +++ b/lnbits/extensions/bleskomat/helpers.py @@ -21,11 +21,7 @@ def generate_bleskomat_lnurl_signature(payload: str, api_key_secret: str, api_ke key = base64.b64decode(api_key_secret) else: key = bytes(f"{api_key_secret}") - return hmac.new( - key=key, - msg=payload.encode(), - digestmod=hashlib.sha256 - ).hexdigest() + return hmac.new(key=key, msg=payload.encode(), digestmod=hashlib.sha256).hexdigest() def generate_bleskomat_lnurl_secret(api_key_id: str, signature: str): @@ -58,19 +54,21 @@ class LnurlValidationError(Exception): def prepare_lnurl_params(tag: str, query: Dict[str, str]): params = {} if not is_supported_lnurl_subprotocol(tag): - raise LnurlValidationError(f"Unsupported subprotocol: \"{tag}\"") + raise LnurlValidationError(f'Unsupported subprotocol: "{tag}"') if tag == "withdrawRequest": params["minWithdrawable"] = float(query["minWithdrawable"]) params["maxWithdrawable"] = float(query["maxWithdrawable"]) params["defaultDescription"] = query["defaultDescription"] if not params["minWithdrawable"] > 0: - raise LnurlValidationError("\"minWithdrawable\" must be greater than zero") + raise LnurlValidationError('"minWithdrawable" must be greater than zero') if not params["maxWithdrawable"] >= params["minWithdrawable"]: - raise LnurlValidationError("\"maxWithdrawable\" must be greater than or equal to \"minWithdrawable\"") + raise LnurlValidationError('"maxWithdrawable" must be greater than or equal to "minWithdrawable"') return params encode_uri_component_safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()" + + def query_to_signing_payload(query: Dict[str, str]) -> str: # Sort the query by key, then stringify it to create the payload. sorted_keys = sorted(query.keys(), key=str.lower) @@ -84,35 +82,17 @@ def query_to_signing_payload(query: Dict[str, str]) -> str: unshorten_rules = { - "query": { - "n": "nonce", - "s": "signature", - "t": "tag" - }, - "tags": { - "c": "channelRequest", - "l": "login", - "p": "payRequest", - "w": "withdrawRequest" - }, + "query": {"n": "nonce", "s": "signature", "t": "tag"}, + "tags": {"c": "channelRequest", "l": "login", "p": "payRequest", "w": "withdrawRequest"}, "params": { - "channelRequest": { - "pl": "localAmt", - "pp": "pushAmt" - }, + "channelRequest": {"pl": "localAmt", "pp": "pushAmt"}, "login": {}, - "payRequest": { - "pn": "minSendable", - "px": "maxSendable", - "pm": "metadata" - }, - "withdrawRequest": { - "pn": "minWithdrawable", - "px": "maxWithdrawable", - "pd": "defaultDescription" - } - } + "payRequest": {"pn": "minSendable", "px": "maxSendable", "pm": "metadata"}, + "withdrawRequest": {"pn": "minWithdrawable", "px": "maxWithdrawable", "pd": "defaultDescription"}, + }, } + + def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]: new_query = {} rules = unshorten_rules @@ -121,14 +101,14 @@ def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]: elif "t" in query: tag = query["t"] else: - raise LnurlValidationError("Missing required query parameter: \"tag\"") + raise LnurlValidationError('Missing required query parameter: "tag"') # Unshorten tag: if tag in rules["tags"]: long_tag = rules["tags"][tag] new_query["tag"] = long_tag tag = long_tag if not tag in rules["params"]: - raise LnurlValidationError(f"Unknown tag: \"{tag}\"") + raise LnurlValidationError(f'Unknown tag: "{tag}"') for key in query: if key in rules["params"][tag]: short_param_key = key diff --git a/lnbits/extensions/bleskomat/lnurl_api.py b/lnbits/extensions/bleskomat/lnurl_api.py index 35d93032d..c7df81d1b 100644 --- a/lnbits/extensions/bleskomat/lnurl_api.py +++ b/lnbits/extensions/bleskomat/lnurl_api.py @@ -47,7 +47,7 @@ async def api_bleskomat_lnurl(): # The API key ID, nonce, and tag should be present in the query string. for field in ["id", "nonce", "tag"]: if not field in query: - raise LnurlHttpError(f"Failed API key signature check: Missing \"{field}\"", HTTPStatus.BAD_REQUEST) + raise LnurlHttpError(f'Failed API key signature check: Missing "{field}"', HTTPStatus.BAD_REQUEST) # URL signing scheme is described here: # https://github.com/chill117/lnurl-node#how-to-implement-url-signing-scheme @@ -72,8 +72,7 @@ async def api_bleskomat_lnurl(): params = prepare_lnurl_params(tag, query) if "f" in query: rate = await fetch_fiat_exchange_rate( - currency=query["f"], - provider=bleskomat.exchange_rate_provider + currency=query["f"], provider=bleskomat.exchange_rate_provider ) # Convert fee (%) to decimal: fee = float(bleskomat.fee) / 100 @@ -88,13 +87,7 @@ async def api_bleskomat_lnurl(): raise LnurlHttpError(e.message, HTTPStatus.BAD_REQUEST) # Create a new LNURL using the query parameters provided in the signed URL. params = json.JSONEncoder().encode(params) - lnurl = await create_bleskomat_lnurl( - bleskomat=bleskomat, - secret=secret, - tag=tag, - params=params, - uses=1 - ) + lnurl = await create_bleskomat_lnurl(bleskomat=bleskomat, secret=secret, tag=tag, params=params, uses=1) # Reply with LNURL response object. return jsonify(lnurl.get_info_response_object(secret)), HTTPStatus.OK diff --git a/lnbits/extensions/bleskomat/models.py b/lnbits/extensions/bleskomat/models.py index 8a671a716..8ea973385 100644 --- a/lnbits/extensions/bleskomat/models.py +++ b/lnbits/extensions/bleskomat/models.py @@ -39,7 +39,7 @@ class BleskomatLnurl(NamedTuple): def get_info_response_object(self, secret: str) -> Dict[str, str]: tag = self.tag params = json.loads(self.params) - response = { "tag": tag } + response = {"tag": tag} if tag == "withdrawRequest": for key in ["minWithdrawable", "maxWithdrawable", "defaultDescription"]: response[key] = params[key] @@ -54,7 +54,7 @@ class BleskomatLnurl(NamedTuple): if tag == "withdrawRequest": for field in ["pr"]: if not field in query: - raise LnurlValidationError(f"Missing required parameter: \"{field}\"") + raise LnurlValidationError(f'Missing required parameter: "{field}"') # Check the bolt11 invoice(s) provided. pr = query["pr"] if "," in pr: @@ -62,13 +62,13 @@ class BleskomatLnurl(NamedTuple): try: invoice = bolt11.decode(pr) except ValueError as e: - raise LnurlValidationError("Invalid parameter (\"pr\"): Lightning payment request expected") + raise LnurlValidationError('Invalid parameter ("pr"): Lightning payment request expected') if invoice.amount_msat < params["minWithdrawable"]: - raise LnurlValidationError("Amount in invoice must be greater than or equal to \"minWithdrawable\"") + raise LnurlValidationError('Amount in invoice must be greater than or equal to "minWithdrawable"') if invoice.amount_msat > params["maxWithdrawable"]: - raise LnurlValidationError("Amount in invoice must be less than or equal to \"maxWithdrawable\"") + raise LnurlValidationError('Amount in invoice must be less than or equal to "maxWithdrawable"') else: - raise LnurlValidationError(f"Unknown subprotocol: \"{tag}\"") + raise LnurlValidationError(f'Unknown subprotocol: "{tag}"') async def execute_action(self, query: Dict[str, str]): self.validate_action(query) @@ -105,6 +105,6 @@ class BleskomatLnurl(NamedTuple): WHERE id = ? AND remaining_uses > 0 """, - (now, self.id) + (now, self.id), ) return result.rowcount > 0 diff --git a/lnbits/extensions/bleskomat/views.py b/lnbits/extensions/bleskomat/views.py index 16e986eee..52f63499f 100644 --- a/lnbits/extensions/bleskomat/views.py +++ b/lnbits/extensions/bleskomat/views.py @@ -7,6 +7,7 @@ from . import bleskomat_ext from .exchange_rates import exchange_rate_providers_serializable, fiat_currencies from .helpers import get_callback_url + @bleskomat_ext.route("/") @validate_uuids(["usr"], required=True) @check_user_exists() @@ -14,6 +15,6 @@ async def index(): bleskomat_vars = { "callback_url": get_callback_url(), "exchange_rate_providers": exchange_rate_providers_serializable, - "fiat_currencies": fiat_currencies + "fiat_currencies": fiat_currencies, } return await render_template("bleskomat/index.html", user=g.user, bleskomat_vars=bleskomat_vars) diff --git a/lnbits/extensions/bleskomat/views_api.py b/lnbits/extensions/bleskomat/views_api.py index aed4d02ea..8256ece86 100644 --- a/lnbits/extensions/bleskomat/views_api.py +++ b/lnbits/extensions/bleskomat/views_api.py @@ -58,13 +58,13 @@ async def api_bleskomat_create_or_update(bleskomat_id=None): try: fiat_currency = g.data["fiat_currency"] exchange_rate_provider = g.data["exchange_rate_provider"] - rate = await fetch_fiat_exchange_rate( - currency=fiat_currency, - provider=exchange_rate_provider - ) + rate = await fetch_fiat_exchange_rate(currency=fiat_currency, provider=exchange_rate_provider) except Exception as e: print(e) - return jsonify({"message": f"Failed to fetch BTC/{fiat_currency} currency pair from \"{exchange_rate_provider}\""}), HTTPStatus.INTERNAL_SERVER_ERROR + return ( + jsonify({"message": f'Failed to fetch BTC/{fiat_currency} currency pair from "{exchange_rate_provider}"'}), + HTTPStatus.INTERNAL_SERVER_ERROR, + ) if bleskomat_id: bleskomat = await get_bleskomat(bleskomat_id) diff --git a/lnbits/extensions/withdraw/crud.py b/lnbits/extensions/withdraw/crud.py index 11f7d949d..18f77db03 100644 --- a/lnbits/extensions/withdraw/crud.py +++ b/lnbits/extensions/withdraw/crud.py @@ -124,4 +124,5 @@ async def create_hash_check( async def get_hash_check(the_hash: str, lnurl_id: str) -> Optional[HashCheck]: row = await db.fetchone("SELECT * FROM hash_check WHERE id = ?", (the_hash,)) + return HashCheck.from_row(row) if row else None \ No newline at end of file