diff --git a/lnbits/core/models.py b/lnbits/core/models.py index e292362a..31383667 100644 --- a/lnbits/core/models.py +++ b/lnbits/core/models.py @@ -88,7 +88,7 @@ class Payment(BaseModel): preimage: str payment_hash: str expiry: Optional[float] - extra: Optional[Dict] = {} + extra: Dict = {} wallet_id: str webhook: Optional[str] webhook_status: Optional[int] diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py index 4fae31f9..cc5d5193 100644 --- a/lnbits/extensions/boltcards/crud.py +++ b/lnbits/extensions/boltcards/crud.py @@ -1,5 +1,5 @@ import secrets -from datetime import date, datetime +from datetime import datetime from typing import List, Optional, Union from lnbits.helpers import urlsafe_short_hash @@ -124,7 +124,6 @@ async def get_card_by_otp(otp: str) -> Optional[Card]: async def delete_card(card_id: str) -> None: # Delete cards - card = await get_card(card_id) await db.execute("DELETE FROM boltcards.cards WHERE id = ?", (card_id,)) # Delete hits hits = await get_hits([card_id]) @@ -146,7 +145,7 @@ async def update_card_counter(counter: int, id: str): async def enable_disable_card(enable: bool, id: str) -> Optional[Card]: - row = await db.execute( + await db.execute( "UPDATE boltcards.cards SET enable = ? WHERE id = ?", (enable, id), ) @@ -161,7 +160,7 @@ async def update_card_otp(otp: str, id: str): async def get_hit(hit_id: str) -> Optional[Hit]: - row = await db.fetchone(f"SELECT * FROM boltcards.hits WHERE id = ?", (hit_id)) + row = await db.fetchone(f"SELECT * FROM boltcards.hits WHERE id = ?", (hit_id,)) if not row: return None @@ -182,7 +181,7 @@ async def get_hits(cards_ids: Union[str, List[str]]) -> List[Hit]: return [Hit(**row) for row in rows] -async def get_hits_today(card_id: str) -> Optional[Hit]: +async def get_hits_today(card_id: str) -> List[Hit]: rows = await db.fetchall( f"SELECT * FROM boltcards.hits WHERE card_id = ?", (card_id,), @@ -259,7 +258,7 @@ async def create_refund(hit_id, refund_amount) -> Refund: async def get_refund(refund_id: str) -> Optional[Refund]: row = await db.fetchone( - f"SELECT * FROM boltcards.refunds WHERE id = ?", (refund_id) + f"SELECT * FROM boltcards.refunds WHERE id = ?", (refund_id,) ) if not row: return None @@ -267,7 +266,7 @@ async def get_refund(refund_id: str) -> Optional[Refund]: return Refund.parse_obj(refund) -async def get_refunds(hits_ids: Union[str, List[str]]) -> List[Refund]: +async def get_refunds(hits_ids: List[Hit]) -> List[Refund]: if len(hits_ids) == 0: return [] diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py index 3a99073a..d0430372 100644 --- a/lnbits/extensions/boltcards/lnurl.py +++ b/lnbits/extensions/boltcards/lnurl.py @@ -3,13 +3,9 @@ import secrets from http import HTTPStatus from urllib.parse import urlparse -from fastapi import Request -from fastapi.param_functions import Query -from fastapi.params import Depends, Query -from lnurl import encode as lnurl_encode # type: ignore -from lnurl.types import LnurlPayMetadata # type: ignore -from starlette.exceptions import HTTPException -from starlette.requests import Request +from fastapi import HTTPException, Query, Request +from lnurl import encode as lnurl_encode +from lnurl.types import LnurlPayMetadata from starlette.responses import HTMLResponse from lnbits import bolt11 @@ -28,14 +24,13 @@ from .crud import ( update_card_counter, update_card_otp, ) -from .models import CreateCardData from .nxp424 import decryptSUN, getSunMAC ###############LNURLWITHDRAW################# # /boltcards/api/v1/scan?p=00000000000000000000000000000000&c=0000000000000000 @boltcards_ext.get("/api/v1/scan/{external_id}") -async def api_scan(p, c, request: Request, external_id: str = None): +async def api_scan(p, c, request: Request, external_id: str = Query(None)): # some wallets send everything as lower case, no bueno p = p.upper() c = c.upper() @@ -63,6 +58,7 @@ async def api_scan(p, c, request: Request, external_id: str = None): await update_card_counter(ctr_int, card.id) # gathering some info for hit record + assert request.client ip = request.client.host if "x-real-ip" in request.headers: ip = request.headers["x-real-ip"] @@ -95,7 +91,6 @@ async def api_scan(p, c, request: Request, external_id: str = None): name="boltcards.lnurl_callback", ) async def lnurl_callback( - request: Request, pr: str = Query(None), k1: str = Query(None), ): @@ -120,7 +115,9 @@ async def lnurl_callback( return {"status": "ERROR", "reason": "Failed to decode payment request"} card = await get_card(hit.card_id) + assert card hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000)) + assert hit try: await pay_invoice( wallet_id=card.wallet, @@ -155,7 +152,7 @@ async def api_auth(a, request: Request): response = { "card_name": card.card_name, - "id": 1, + "id": str(1), "k0": card.k0, "k1": card.k1, "k2": card.k2, @@ -163,7 +160,7 @@ async def api_auth(a, request: Request): "k4": card.k2, "lnurlw_base": "lnurlw://" + lnurlw_base, "protocol_name": "new_bolt_card_response", - "protocol_version": 1, + "protocol_version": str(1), } return response @@ -179,7 +176,9 @@ async def api_auth(a, request: Request): ) async def lnurlp_response(req: Request, hit_id: str = Query(None)): hit = await get_hit(hit_id) + assert hit card = await get_card(hit.card_id) + assert card if not hit: return {"status": "ERROR", "reason": f"LNURL-pay record not found."} if not card.enable: @@ -199,17 +198,17 @@ async def lnurlp_response(req: Request, hit_id: str = Query(None)): response_class=HTMLResponse, name="boltcards.lnurlp_callback", ) -async def lnurlp_callback( - req: Request, hit_id: str = Query(None), amount: str = Query(None) -): +async def lnurlp_callback(hit_id: str = Query(None), amount: str = Query(None)): hit = await get_hit(hit_id) + assert hit card = await get_card(hit.card_id) + assert card if not hit: return {"status": "ERROR", "reason": f"LNURL-pay record not found."} - payment_hash, payment_request = await create_invoice( + _, payment_request = await create_invoice( wallet_id=card.wallet, - amount=int(amount) / 1000, + amount=int(int(amount) / 1000), memo=f"Refund {hit_id}", unhashed_description=LnurlPayMetadata( json.dumps([["text/plain", "Refund"]]) diff --git a/lnbits/extensions/boltcards/models.py b/lnbits/extensions/boltcards/models.py index 47ca1df0..5ea4be15 100644 --- a/lnbits/extensions/boltcards/models.py +++ b/lnbits/extensions/boltcards/models.py @@ -1,14 +1,11 @@ +import json from sqlite3 import Row -from typing import Optional -from fastapi import Request -from fastapi.params import Query +from fastapi import Query, Request from lnurl import Lnurl -from lnurl import encode as lnurl_encode # type: ignore -from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore -from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl import encode as lnurl_encode +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel -from pydantic.main import BaseModel ZERO_KEY = "00000000000000000000000000000000" @@ -32,6 +29,7 @@ class Card(BaseModel): otp: str time: int + @classmethod def from_row(cls, row: Row) -> "Card": return cls(**dict(row)) @@ -40,7 +38,7 @@ class Card(BaseModel): return lnurl_encode(url) async def lnurlpay_metadata(self) -> LnurlPayMetadata: - return LnurlPayMetadata(json.dumps([["text/plain", self.title]])) + return LnurlPayMetadata(json.dumps([["text/plain", self.card_name]])) class CreateCardData(BaseModel): @@ -69,6 +67,7 @@ class Hit(BaseModel): amount: int time: int + @classmethod def from_row(cls, row: Row) -> "Hit": return cls(**dict(row)) @@ -79,5 +78,6 @@ class Refund(BaseModel): refund_amount: int time: int + @classmethod def from_row(cls, row: Row) -> "Refund": return cls(**dict(row)) diff --git a/lnbits/extensions/boltcards/tasks.py b/lnbits/extensions/boltcards/tasks.py index c1e99b76..375e7000 100644 --- a/lnbits/extensions/boltcards/tasks.py +++ b/lnbits/extensions/boltcards/tasks.py @@ -1,8 +1,6 @@ import asyncio import json -import httpx - from lnbits.core import db as core_db from lnbits.core.models import Payment from lnbits.helpers import get_current_extension_name @@ -21,22 +19,23 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: + if not payment.extra.get("refund"): return if payment.extra.get("wh_status"): # this webhook has already been sent return - hit = await get_hit(payment.extra.get("refund")) + + hit = await get_hit(str(payment.extra.get("refund"))) if hit: - refund = await create_refund( - hit_id=hit.id, refund_amount=(payment.amount / 1000) - ) + await create_refund(hit_id=hit.id, refund_amount=(payment.amount / 1000)) await mark_webhook_sent(payment, 1) async def mark_webhook_sent(payment: Payment, status: int) -> None: + payment.extra["wh_status"] = status await core_db.execute( diff --git a/lnbits/extensions/boltcards/views.py b/lnbits/extensions/boltcards/views.py index 8fcbb7de..273cfcbf 100644 --- a/lnbits/extensions/boltcards/views.py +++ b/lnbits/extensions/boltcards/views.py @@ -1,5 +1,4 @@ -from fastapi import FastAPI, Request -from fastapi.params import Depends +from fastapi import Depends, Request from fastapi.templating import Jinja2Templates from starlette.responses import HTMLResponse diff --git a/lnbits/extensions/boltcards/views_api.py b/lnbits/extensions/boltcards/views_api.py index c18c33d0..feca12e0 100644 --- a/lnbits/extensions/boltcards/views_api.py +++ b/lnbits/extensions/boltcards/views_api.py @@ -1,10 +1,6 @@ -import secrets from http import HTTPStatus -from fastapi.params import Depends, Query -from loguru import logger -from starlette.exceptions import HTTPException -from starlette.requests import Request +from fastapi import Depends, HTTPException, Query from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key @@ -15,13 +11,11 @@ from .crud import ( delete_card, enable_disable_card, get_card, - get_card_by_otp, get_card_by_uid, get_cards, get_hits, get_refunds, update_card, - update_card_otp, ) from .models import CreateCardData @@ -33,7 +27,8 @@ async def api_cards( wallet_ids = [g.wallet.id] if all_wallets: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + wallet_ids = user.wallet_ids if user else [] return [card.dict() for card in await get_cards(wallet_ids)] @@ -41,9 +36,8 @@ async def api_cards( @boltcards_ext.post("/api/v1/cards", status_code=HTTPStatus.CREATED) @boltcards_ext.put("/api/v1/cards/{card_id}", status_code=HTTPStatus.OK) async def api_card_create_or_update( - # req: Request, data: CreateCardData, - card_id: str = None, + card_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key), ): try: @@ -95,6 +89,7 @@ async def api_card_create_or_update( status_code=HTTPStatus.BAD_REQUEST, ) card = await create_card(wallet_id=wallet.wallet.id, data=data) + assert card return card.dict() @@ -110,6 +105,7 @@ async def enable_card( if card.wallet != wallet.wallet.id: raise HTTPException(detail="Not your card.", status_code=HTTPStatus.FORBIDDEN) card = await enable_disable_card(enable=enable, id=card_id) + assert card return card.dict() @@ -136,7 +132,8 @@ async def api_hits( wallet_ids = [g.wallet.id] if all_wallets: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + wallet_ids = user.wallet_ids if user else [] cards = await get_cards(wallet_ids) cards_ids = [] @@ -153,15 +150,13 @@ async def api_refunds( wallet_ids = [g.wallet.id] if all_wallets: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + wallet_ids = user.wallet_ids if user else [] cards = await get_cards(wallet_ids) cards_ids = [] for card in cards: cards_ids.append(card.id) hits = await get_hits(cards_ids) - hits_ids = [] - for hit in hits: - hits_ids.append(hit.id) - return [refund.dict() for refund in await get_refunds(hits_ids)] + return [refund.dict() for refund in await get_refunds(hits)] diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py index 9de17a1c..bf49171c 100644 --- a/lnbits/extensions/cashu/tasks.py +++ b/lnbits/extensions/cashu/tasks.py @@ -28,6 +28,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra and not payment.extra.get("tag") == "cashu": + if payment.extra.get("tag") != "cashu": return + return diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py index 384070cd..4975b5a3 100644 --- a/lnbits/extensions/copilot/tasks.py +++ b/lnbits/extensions/copilot/tasks.py @@ -24,12 +24,12 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - webhook = None - data = None - if not payment.extra or payment.extra.get("tag") != "copilot": + if payment.extra.get("tag") != "copilot": # not an copilot invoice return + webhook = None + data = None copilot = await get_copilot(payment.extra.get("copilotid", -1)) if not copilot: diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py index 5475139c..24e8ef25 100644 --- a/lnbits/extensions/gerty/crud.py +++ b/lnbits/extensions/gerty/crud.py @@ -122,7 +122,7 @@ async def get_mempool_info(endPoint: str, gerty) -> dict: ), ) return response.json() - if int(time.time()) - row.time > 20: + if float(time.time()) - row.time > 20: async with httpx.AsyncClient() as client: response = await client.get(gerty.mempool_endpoint + url) await db.execute( diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py index 654dfcbf..afef2cfd 100644 --- a/lnbits/extensions/gerty/migrations.py +++ b/lnbits/extensions/gerty/migrations.py @@ -1,3 +1,6 @@ +import time + + async def m001_initial(db): """ Initial Gertys table. @@ -57,3 +60,46 @@ async def m005_add_gerty_model_col(db): support for Gerty model col """ await db.execute("ALTER TABLE gerty.gertys ADD COLUMN urls TEXT;") + + +async def m006_add_gerty_model_col(db): + """ + Add UUID ID's to links and migrates existing data + """ + await db.execute("ALTER TABLE gerty.mempool RENAME TO mempool_old") + await db.execute( + f""" + CREATE TABLE gerty.mempool ( + id TEXT PRIMARY KEY, + mempool_endpoint TEXT NOT NULL, + endpoint TEXT NOT NULL, + data TEXT NOT NULL, + time FLOAT + ); + """ + ) + + for row in [ + list(row) for row in await db.fetchall("SELECT * FROM gerty.mempool_old") + ]: + await db.execute( + """ + INSERT INTO gerty.mempool ( + id, + mempool_endpoint, + endpoint, + data, + time + ) + VALUES (?, ?, ?, ?, ?) + """, + ( + row[0], + row[1], + row[2], + row[3], + time.time(), + ), + ) + + await db.execute("DROP TABLE gerty.mempool_old") diff --git a/lnbits/extensions/gerty/templates/gerty/gerty.html b/lnbits/extensions/gerty/templates/gerty/gerty.html index 06a29e22..899942c1 100644 --- a/lnbits/extensions/gerty/templates/gerty/gerty.html +++ b/lnbits/extensions/gerty/templates/gerty/gerty.html @@ -6,7 +6,7 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %} v-if="fun_exchange_market_rate || fun_satoshi_quotes" > - +
Servers to check
@@ -126,7 +126,7 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %} v-else-if="item[1].value >= 300" square size="sm" - color="yellow" + color="orange" text-color="white" icon="sentiment_dissatisfied" > diff --git a/lnbits/extensions/invoices/tasks.py b/lnbits/extensions/invoices/tasks.py index ae76b9e3..c8a829db 100644 --- a/lnbits/extensions/invoices/tasks.py +++ b/lnbits/extensions/invoices/tasks.py @@ -25,9 +25,6 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra: - return - if payment.extra.get("tag") != "invoices": return diff --git a/lnbits/extensions/jukebox/tasks.py b/lnbits/extensions/jukebox/tasks.py index 8a68fd27..37489edb 100644 --- a/lnbits/extensions/jukebox/tasks.py +++ b/lnbits/extensions/jukebox/tasks.py @@ -17,8 +17,8 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra: - if payment.extra.get("tag") != "jukebox": - # not a jukebox invoice - return - await update_jukebox_payment(payment.payment_hash, paid=True) + if payment.extra.get("tag") != "jukebox": + # not a jukebox invoice + return + + await update_jukebox_payment(payment.payment_hash, paid=True) diff --git a/lnbits/extensions/lnaddress/cloudflare.py b/lnbits/extensions/lnaddress/cloudflare.py index 981a37b0..679cb515 100644 --- a/lnbits/extensions/lnaddress/cloudflare.py +++ b/lnbits/extensions/lnaddress/cloudflare.py @@ -16,7 +16,7 @@ async def cloudflare_create_record(domain: Domains, ip: str): "Content-Type": "application/json", } - cf_response = "" + cf_response = {} async with httpx.AsyncClient() as client: try: r = await client.post( @@ -31,9 +31,9 @@ async def cloudflare_create_record(domain: Domains, ip: str): }, timeout=40, ) - cf_response = json.loads(r.text) + cf_response = r.json() except AssertionError: - cf_response = "Error occured" + cf_response = {"error": "Error occured"} return cf_response @@ -53,3 +53,4 @@ async def cloudflare_deleterecord(domain: Domains, domain_id: str): cf_response = r.text except AssertionError: cf_response = "Error occured" + return cf_response diff --git a/lnbits/extensions/lnaddress/crud.py b/lnbits/extensions/lnaddress/crud.py index 25338215..0e590ec8 100644 --- a/lnbits/extensions/lnaddress/crud.py +++ b/lnbits/extensions/lnaddress/crud.py @@ -128,6 +128,7 @@ async def get_addresses(wallet_ids: Union[str, List[str]]) -> List[Addresses]: async def set_address_paid(payment_hash: str) -> Addresses: address = await get_address(payment_hash) + assert address if address.paid == False: await db.execute( @@ -146,6 +147,7 @@ async def set_address_paid(payment_hash: str) -> Addresses: async def set_address_renewed(address_id: str, duration: int): address = await get_address(address_id) + assert address extend_duration = int(address.duration) + duration await db.execute( diff --git a/lnbits/extensions/lnaddress/lnurl.py b/lnbits/extensions/lnaddress/lnurl.py index 6f799439..c4c3cea5 100644 --- a/lnbits/extensions/lnaddress/lnurl.py +++ b/lnbits/extensions/lnaddress/lnurl.py @@ -1,17 +1,9 @@ -import hashlib -import json from datetime import datetime, timedelta import httpx -from fastapi.params import Query -from lnurl import ( # type: ignore - LnurlErrorResponse, - LnurlPayActionResponse, - LnurlPayResponse, -) +from fastapi import Query, Request +from lnurl import LnurlErrorResponse from loguru import logger -from starlette.requests import Request -from starlette.responses import HTMLResponse from . import lnaddress_ext from .crud import get_address, get_address_by_username, get_domain @@ -52,6 +44,7 @@ async def lnurl_callback(address_id, amount: int = Query(...)): amount_received = amount domain = await get_domain(address.domain) + assert domain base_url = ( address.wallet_endpoint[:-1] @@ -79,7 +72,7 @@ async def lnurl_callback(address_id, amount: int = Query(...)): ) r = call.json() - except AssertionError as e: + except Exception: return LnurlErrorResponse(reason="ERROR") # resp = LnurlPayActionResponse(pr=r["payment_request"], routes=[]) diff --git a/lnbits/extensions/lnaddress/models.py b/lnbits/extensions/lnaddress/models.py index 248f856c..77eb3cd3 100644 --- a/lnbits/extensions/lnaddress/models.py +++ b/lnbits/extensions/lnaddress/models.py @@ -1,9 +1,9 @@ import json from typing import Optional -from fastapi.params import Query +from fastapi import Query from lnurl.types import LnurlPayMetadata -from pydantic.main import BaseModel +from pydantic import BaseModel class CreateDomain(BaseModel): diff --git a/lnbits/extensions/lnaddress/tasks.py b/lnbits/extensions/lnaddress/tasks.py index 0c377eec..3699c463 100644 --- a/lnbits/extensions/lnaddress/tasks.py +++ b/lnbits/extensions/lnaddress/tasks.py @@ -1,6 +1,7 @@ import asyncio import httpx +from loguru import logger from lnbits.core.models import Payment from lnbits.helpers import get_current_extension_name @@ -21,7 +22,9 @@ async def wait_for_paid_invoices(): async def call_webhook_on_paid(payment_hash): ### Use webhook to notify about cloudflare registration address = await get_address(payment_hash) + assert address domain = await get_domain(address.domain) + assert domain if not domain.webhook: return @@ -39,24 +42,23 @@ async def call_webhook_on_paid(payment_hash): }, timeout=40, ) - except AssertionError: - webhook = None + r.raise_for_status() + except Exception as e: + logger.error(f"lnaddress: error calling webhook on paid: {str(e)}") async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") == "lnaddress": + if payment.extra.get("tag") == "lnaddress": await payment.set_pending(False) await set_address_paid(payment_hash=payment.payment_hash) await call_webhook_on_paid(payment_hash=payment.payment_hash) elif payment.extra.get("tag") == "renew lnaddress": - await payment.set_pending(False) await set_address_renewed( address_id=payment.extra["id"], duration=payment.extra["duration"] ) await call_webhook_on_paid(payment_hash=payment.payment_hash) - else: return diff --git a/lnbits/extensions/lnaddress/views.py b/lnbits/extensions/lnaddress/views.py index 8c838f0c..d1a7be83 100644 --- a/lnbits/extensions/lnaddress/views.py +++ b/lnbits/extensions/lnaddress/views.py @@ -1,10 +1,8 @@ from http import HTTPStatus from urllib.parse import urlparse -from fastapi import Request -from fastapi.params import Depends +from fastapi import Depends, HTTPException, Request from fastapi.templating import Jinja2Templates -from starlette.exceptions import HTTPException from starlette.responses import HTMLResponse from lnbits.core.crud import get_wallet @@ -35,6 +33,7 @@ async def display(domain_id, request: Request): await purge_addresses(domain_id) wallet = await get_wallet(domain.wallet) + assert wallet url = urlparse(str(request.url)) return lnaddress_renderer().TemplateResponse( diff --git a/lnbits/extensions/lnaddress/views_api.py b/lnbits/extensions/lnaddress/views_api.py index 46ef6b99..d9e50e9d 100644 --- a/lnbits/extensions/lnaddress/views_api.py +++ b/lnbits/extensions/lnaddress/views_api.py @@ -1,9 +1,7 @@ from http import HTTPStatus from urllib.parse import urlparse -from fastapi import Request -from fastapi.params import Depends, Query -from starlette.exceptions import HTTPException +from fastapi import Depends, HTTPException, Query, Request from lnbits.core.crud import get_user from lnbits.core.services import check_transaction_status, create_invoice @@ -11,7 +9,7 @@ from lnbits.decorators import WalletTypeInfo, get_key_type from lnbits.extensions.lnaddress.models import CreateAddress, CreateDomain from . import lnaddress_ext -from .cloudflare import cloudflare_create_record, cloudflare_deleterecord +from .cloudflare import cloudflare_create_record from .crud import ( check_address_available, create_address, @@ -35,7 +33,8 @@ async def api_domains( wallet_ids = [g.wallet.id] if all_wallets: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + wallet_ids = user.wallet_ids if user else [] return [domain.dict() for domain in await get_domains(wallet_ids)] @@ -69,7 +68,7 @@ async def api_domain_create( cf_response = await cloudflare_create_record(domain=domain, ip=root_url) - if not cf_response or cf_response["success"] != True: + if not cf_response or not cf_response["success"]: await delete_domain(domain.id) raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, @@ -106,7 +105,8 @@ async def api_addresses( wallet_ids = [g.wallet.id] if all_wallets: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + wallet_ids = user.wallet_ids if user else [] return [address.dict() for address in await get_addresses(wallet_ids)] @@ -227,7 +227,9 @@ async def api_lnaddress_make_address( @lnaddress_ext.get("/api/v1/addresses/{payment_hash}") async def api_address_send_address(payment_hash): address = await get_address(payment_hash) + assert address domain = await get_domain(address.domain) + assert domain try: status = await check_transaction_status(domain.wallet, payment_hash) is_paid = not status.pending diff --git a/lnbits/extensions/lnticket/tasks.py b/lnbits/extensions/lnticket/tasks.py index e84a7512..746ebea9 100644 --- a/lnbits/extensions/lnticket/tasks.py +++ b/lnbits/extensions/lnticket/tasks.py @@ -19,7 +19,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra or payment.extra.get("tag") != "lnticket": + if payment.extra.get("tag") != "lnticket": # not a lnticket invoice return diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index 423c6a46..182df743 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -120,7 +120,11 @@ async def create_lnurldevicepayment( payhash: Optional[str] = None, sats: Optional[int] = 0, ) -> lnurldevicepayment: - lnurldevicepayment_id = urlsafe_short_hash() + device = await get_lnurldevice(deviceid) + if device.device == "atm": + lnurldevicepayment_id = shortuuid.uuid(name=payload) + else: + lnurldevicepayment_id = urlsafe_short_hash() await db.execute( """ INSERT INTO lnurldevice.lnurldevicepayment ( diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index e0384159..34de20fa 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -5,12 +5,14 @@ from http import HTTPStatus from io import BytesIO from typing import Optional +import shortuuid from embit import bech32, compact from fastapi import Request from fastapi.param_functions import Query from loguru import logger from starlette.exceptions import HTTPException +from lnbits import bolt11 from lnbits.core.services import create_invoice from lnbits.core.views.api import pay_invoice from lnbits.utils.exchange_rates import fiat_amount_as_satoshis @@ -102,11 +104,6 @@ async def lnurl_v1_params( "status": "ERROR", "reason": f"lnurldevice {device_id} not found on this server", } - paymentcheck = await get_lnurlpayload(p) - if device.device == "atm": - if paymentcheck: - if paymentcheck.payhash != "payment_hash": - return {"status": "ERROR", "reason": f"Payment already claimed"} if device.device == "switch": price_msat = ( await fiat_amount_as_satoshis(float(profit), device.currency) @@ -163,13 +160,21 @@ async def lnurl_v1_params( if device.device != "atm": return {"status": "ERROR", "reason": "Not ATM device."} price_msat = int(price_msat * (1 - (device.profit / 100)) / 1000) - lnurldevicepayment = await create_lnurldevicepayment( - deviceid=device.id, - payload=p, - sats=price_msat * 1000, - pin=pin, - payhash="payment_hash", - ) + lnurldevicepayment = await get_lnurldevicepayment(shortuuid.uuid(name=p)) + if lnurldevicepayment: + logger.debug("lnurldevicepayment") + logger.debug(lnurldevicepayment) + logger.debug("lnurldevicepayment") + if lnurldevicepayment.payload == lnurldevicepayment.payhash: + return {"status": "ERROR", "reason": f"Payment already claimed"} + else: + lnurldevicepayment = await create_lnurldevicepayment( + deviceid=device.id, + payload=p, + sats=price_msat * 1000, + pin=pin, + payhash="payment_hash", + ) if not lnurldevicepayment: return {"status": "ERROR", "reason": "Could not create payment."} return { @@ -222,15 +227,20 @@ async def lnurl_callback( status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found." ) if device.device == "atm": + if lnurldevicepayment.payload == lnurldevicepayment.payhash: + return {"status": "ERROR", "reason": f"Payment already claimed"} if not pr: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="No payment request" ) + invoice = bolt11.decode(pr) + if not invoice.payment_hash: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not valid payment request" + ) else: if lnurldevicepayment.payload != k1: return {"status": "ERROR", "reason": "Bad K1"} - if lnurldevicepayment.payhash != "payment_hash": - return {"status": "ERROR", "reason": f"Payment already claimed"} lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload ) diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py index 2b574d42..ea01e04f 100644 --- a/lnbits/extensions/lnurlp/tasks.py +++ b/lnbits/extensions/lnurlp/tasks.py @@ -22,7 +22,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment): - if not payment.extra or payment.extra.get("tag") != "lnurlp": + if payment.extra.get("tag") != "lnurlp": return if payment.extra.get("wh_status"): diff --git a/lnbits/extensions/market/tasks.py b/lnbits/extensions/market/tasks.py index 004ebb4d..b102e0f1 100644 --- a/lnbits/extensions/market/tasks.py +++ b/lnbits/extensions/market/tasks.py @@ -23,9 +23,6 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra: - return - if payment.extra.get("tag") != "market": return diff --git a/lnbits/extensions/nostrnip5/tasks.py b/lnbits/extensions/nostrnip5/tasks.py index 30e8cec6..f0d0c965 100644 --- a/lnbits/extensions/nostrnip5/tasks.py +++ b/lnbits/extensions/nostrnip5/tasks.py @@ -18,8 +18,6 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra: - return if payment.extra.get("tag") != "nostrnip5": return diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 78433838..4fb14695 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -12,14 +12,13 @@ from . import db from .helpers import fetch_onchain_balance from .models import Charges, CreateCharge, SatsPayThemes -###############CHARGES########################## - -async def create_charge(user: str, data: CreateCharge) -> Charges: +async def create_charge(user: str, data: CreateCharge) -> Optional[Charges]: data = CreateCharge(**data.dict()) charge_id = urlsafe_short_hash() if data.onchainwallet: config = await get_config(user) + assert config data.extra = json.dumps( {"mempool_endpoint": config.mempool_endpoint, "network": config.network} ) @@ -92,7 +91,7 @@ async def update_charge(charge_id: str, **kwargs) -> Optional[Charges]: return Charges.from_row(row) if row else None -async def get_charge(charge_id: str) -> Charges: +async def get_charge(charge_id: str) -> Optional[Charges]: row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,)) return Charges.from_row(row) if row else None @@ -111,6 +110,7 @@ async def delete_charge(charge_id: str) -> None: async def check_address_balance(charge_id: str) -> Optional[Charges]: charge = await get_charge(charge_id) + assert charge if not charge.paid: if charge.onchainaddress: @@ -131,7 +131,7 @@ async def check_address_balance(charge_id: str) -> Optional[Charges]: ################## SETTINGS ################### -async def save_theme(data: SatsPayThemes, css_id: str = None): +async def save_theme(data: SatsPayThemes, css_id: Optional[str]): # insert or update if css_id: await db.execute( @@ -162,7 +162,7 @@ async def save_theme(data: SatsPayThemes, css_id: str = None): return await get_theme(css_id) -async def get_theme(css_id: str) -> SatsPayThemes: +async def get_theme(css_id: str) -> Optional[SatsPayThemes]: row = await db.fetchone("SELECT * FROM satspay.themes WHERE css_id = ?", (css_id,)) return SatsPayThemes.from_row(row) if row else None diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py index b21a3ae2..8596d368 100644 --- a/lnbits/extensions/satspay/helpers.py +++ b/lnbits/extensions/satspay/helpers.py @@ -32,6 +32,7 @@ def public_charge(charge: Charges): async def call_webhook(charge: Charges): async with httpx.AsyncClient() as client: try: + assert charge.webhook r = await client.post( charge.webhook, json=public_charge(charge), @@ -54,6 +55,8 @@ async def fetch_onchain_balance(charge: Charges): if charge.config.network == "Testnet" else charge.config.mempool_endpoint ) + assert endpoint + assert charge.onchainaddress async with httpx.AsyncClient() as client: r = await client.get(endpoint + "/api/address/" + charge.onchainaddress) return r.json()["chain_stats"]["funded_txo_sum"] diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index ce54b44a..992e5eb6 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -22,10 +22,12 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: + if payment.extra.get("tag") != "charge": # not a charge invoice return + assert payment.memo charge = await get_charge(payment.memo) if not charge: logger.error("this should never happen", payment) @@ -33,6 +35,7 @@ async def on_invoice_paid(payment: Payment) -> None: await payment.set_pending(False) charge = await check_address_balance(charge_id=charge.id) + assert charge if charge.must_call_webhook(): resp = await call_webhook(charge) diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 90f8a6b9..15a4403d 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -1,10 +1,7 @@ from http import HTTPStatus -from fastapi import Response -from fastapi.param_functions import Depends +from fastapi import Depends, HTTPException, Request, Response from fastapi.templating import Jinja2Templates -from starlette.exceptions import HTTPException -from starlette.requests import Request from starlette.responses import HTMLResponse from lnbits.core.models import User diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 08c731cb..98c338ed 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -1,9 +1,8 @@ import json from http import HTTPStatus -from fastapi import Depends, Query +from fastapi import Depends, HTTPException, Query from loguru import logger -from starlette.exceptions import HTTPException from lnbits.decorators import ( WalletTypeInfo, @@ -29,8 +28,6 @@ from .crud import ( from .helpers import call_webhook, public_charge from .models import CreateCharge, SatsPayThemes -#############################CHARGES########################## - @satspay_ext.post("/api/v1/charge") async def api_charge_create( @@ -38,6 +35,7 @@ async def api_charge_create( ): try: charge = await create_charge(user=wallet.wallet.user, data=data) + assert charge return { **charge.dict(), **{"time_elapsed": charge.time_elapsed}, @@ -51,13 +49,15 @@ async def api_charge_create( ) -@satspay_ext.put("/api/v1/charge/{charge_id}") +@satspay_ext.put( + "/api/v1/charge/{charge_id}", dependencies=[Depends(require_admin_key)] +) async def api_charge_update( data: CreateCharge, - wallet: WalletTypeInfo = Depends(require_admin_key), - charge_id=None, + charge_id: str, ): charge = await update_charge(charge_id=charge_id, data=data) + assert charge return charge.dict() @@ -78,10 +78,8 @@ async def api_charges_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): return "" -@satspay_ext.get("/api/v1/charge/{charge_id}") -async def api_charge_retrieve( - charge_id, wallet: WalletTypeInfo = Depends(get_key_type) -): +@satspay_ext.get("/api/v1/charge/{charge_id}", dependencies=[Depends(get_key_type)]) +async def api_charge_retrieve(charge_id: str): charge = await get_charge(charge_id) if not charge: @@ -97,8 +95,8 @@ async def api_charge_retrieve( } -@satspay_ext.delete("/api/v1/charge/{charge_id}") -async def api_charge_delete(charge_id, wallet: WalletTypeInfo = Depends(get_key_type)): +@satspay_ext.delete("/api/v1/charge/{charge_id}", dependencies=[Depends(get_key_type)]) +async def api_charge_delete(charge_id: str): charge = await get_charge(charge_id) if not charge: @@ -155,7 +153,7 @@ async def api_themes_save( theme = await save_theme(css_id=css_id, data=data) else: data.user = wallet.wallet.user - theme = await save_theme(data=data) + theme = await save_theme(data=data, css_id="no_id") return theme @@ -169,8 +167,8 @@ async def api_themes_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): return "" -@satspay_ext.delete("/api/v1/themes/{theme_id}") -async def api_theme_delete(theme_id, wallet: WalletTypeInfo = Depends(get_key_type)): +@satspay_ext.delete("/api/v1/themes/{theme_id}", dependencies=[Depends(get_key_type)]) +async def api_theme_delete(theme_id): theme = await get_theme(theme_id) if not theme: diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py index 096cbef9..26249bb1 100644 --- a/lnbits/extensions/scrub/tasks.py +++ b/lnbits/extensions/scrub/tasks.py @@ -27,7 +27,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment): # (avoid loops) - if payment.extra and payment.extra.get("tag") == "scrubed": + if payment.extra.get("tag") == "scrubed": # already scrubbed return diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py index d9f53f20..33768805 100644 --- a/lnbits/extensions/splitpayments/tasks.py +++ b/lnbits/extensions/splitpayments/tasks.py @@ -20,7 +20,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra or payment.extra.get("tag") == "splitpayments": + if payment.extra.get("tag") == "splitpayments": # already a splitted payment, ignore return diff --git a/lnbits/extensions/streamalerts/crud.py b/lnbits/extensions/streamalerts/crud.py index 1e7239a9..37583117 100644 --- a/lnbits/extensions/streamalerts/crud.py +++ b/lnbits/extensions/streamalerts/crud.py @@ -25,15 +25,20 @@ async def get_charge_details(service_id): These might be different depending for services implemented in the future. """ - details = {"time": 1440} service = await get_service(service_id) + assert service + wallet_id = service.wallet wallet = await get_wallet(wallet_id) + assert wallet + user = wallet.user - details["user"] = user - details["lnbitswallet"] = wallet_id - details["onchainwallet"] = service.onchain - return details + return { + "time": 1440, + "user": user, + "lnbitswallet": wallet_id, + "onchainwallet": service.onchain, + } async def create_donation( @@ -71,7 +76,7 @@ async def create_donation( return donation -async def post_donation(donation_id: str) -> tuple: +async def post_donation(donation_id: str) -> dict: """Post donations to their respective third party APIs If the donation has already been posted, it will not be posted again. @@ -97,7 +102,6 @@ async def post_donation(donation_id: str) -> tuple: } async with httpx.AsyncClient() as client: response = await client.post(url, data=data) - status = [s for s in list(HTTPStatus) if s == response.status_code][0] elif service.servicename == "StreamElements": return {"message": "StreamElements not yet supported!"} else: diff --git a/lnbits/extensions/streamalerts/models.py b/lnbits/extensions/streamalerts/models.py index 4a365cba..ae0ffab5 100644 --- a/lnbits/extensions/streamalerts/models.py +++ b/lnbits/extensions/streamalerts/models.py @@ -1,8 +1,8 @@ from sqlite3 import Row from typing import Optional -from fastapi.params import Query -from pydantic.main import BaseModel +from fastapi import Query +from pydantic import BaseModel class CreateService(BaseModel): diff --git a/lnbits/extensions/streamalerts/views.py b/lnbits/extensions/streamalerts/views.py index 595b841e..ac63e9c5 100644 --- a/lnbits/extensions/streamalerts/views.py +++ b/lnbits/extensions/streamalerts/views.py @@ -1,6 +1,6 @@ from http import HTTPStatus -from fastapi.param_functions import Depends +from fastapi import Depends from fastapi.templating import Jinja2Templates from starlette.exceptions import HTTPException from starlette.requests import Request diff --git a/lnbits/extensions/streamalerts/views_api.py b/lnbits/extensions/streamalerts/views_api.py index 058f5126..0134fe82 100644 --- a/lnbits/extensions/streamalerts/views_api.py +++ b/lnbits/extensions/streamalerts/views_api.py @@ -1,6 +1,6 @@ from http import HTTPStatus -from fastapi.params import Depends, Query +from fastapi import Depends, Query from starlette.exceptions import HTTPException from starlette.requests import Request from starlette.responses import RedirectResponse @@ -84,6 +84,8 @@ async def api_authenticate_service( """ service = await get_service(service_id) + assert service + if service.state != state: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, detail="State doesn't match!" @@ -113,6 +115,7 @@ async def api_create_donation(data: CreateDonation, request: Request): webhook_base = request.url.scheme + "://" + request.headers["Host"] service_id = data.service service = await get_service(service_id) + assert service charge_details = await get_charge_details(service.id) name = data.name if data.name else "Anonymous" @@ -157,7 +160,8 @@ async def api_post_donation(request: Request, data: ValidateDonation): @streamalerts_ext.get("/api/v1/services") async def api_get_services(g: WalletTypeInfo = Depends(get_key_type)): """Return list of all services assigned to wallet with given invoice key""" - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + wallet_ids = user.wallet_ids if user else [] services = [] for wallet_id in wallet_ids: new_services = await get_services(wallet_id) @@ -170,7 +174,8 @@ async def api_get_donations(g: WalletTypeInfo = Depends(get_key_type)): """Return list of all donations assigned to wallet with given invoice key """ - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + user = await get_user(g.wallet.user) + wallet_ids = user.wallet_ids if user else [] donations = [] for wallet_id in wallet_ids: new_donations = await get_donations(wallet_id) diff --git a/lnbits/extensions/subdomains/tasks.py b/lnbits/extensions/subdomains/tasks.py index c5a7f47b..f9e0c8ee 100644 --- a/lnbits/extensions/subdomains/tasks.py +++ b/lnbits/extensions/subdomains/tasks.py @@ -20,7 +20,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra or payment.extra.get("tag") != "lnsubdomain": + if payment.extra.get("tag") != "lnsubdomain": # not an lnurlp invoice return diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index f1417810..9a405540 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -20,8 +20,6 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra: - return if payment.extra.get("tag") != "tpos": return diff --git a/pyproject.toml b/pyproject.toml index 5cfbf4dc..03dbbc8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,12 +91,8 @@ files = "lnbits" exclude = """(?x)( ^lnbits/extensions/bleskomat. | ^lnbits/extensions/boltz. - | ^lnbits/extensions/boltcards. | ^lnbits/extensions/livestream. - | ^lnbits/extensions/lnaddress. | ^lnbits/extensions/lnurldevice. - | ^lnbits/extensions/satspay. - | ^lnbits/extensions/streamalerts. | ^lnbits/extensions/watchonly. | ^lnbits/wallets/lnd_grpc_files. )"""