From 021cf64c1e82208b32200d491601c6c9cf2a5a56 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 23 Jun 2021 16:13:48 +0100 Subject: [PATCH 001/116] Added eclair backend --- .env.example | 6 +- lnbits/wallets/__init__.py | 1 + lnbits/wallets/eclair.py | 166 +++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 lnbits/wallets/eclair.py diff --git a/.env.example b/.env.example index cc70644c..38add8fd 100644 --- a/.env.example +++ b/.env.example @@ -14,7 +14,7 @@ LNBITS_FORCE_HTTPS=true LNBITS_SERVICE_FEE="0.0" # Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, LndWallet (gRPC), -# LndRestWallet, CLightningWallet, LNbitsWallet, SparkWallet +# LndRestWallet, CLightningWallet, LNbitsWallet, SparkWallet, EclairWallet LNBITS_BACKEND_WALLET_CLASS=VoidWallet # VoidWallet is just a fallback that works without any actual Lightning capabilities, # just so you can see the UI before dealing with this file. @@ -56,3 +56,7 @@ LNTXBOT_KEY=LNTXBOT_ADMIN_KEY # OpenNodeWallet OPENNODE_API_ENDPOINT=https://api.opennode.com/ OPENNODE_KEY=OPENNODE_ADMIN_KEY + +# EclairWallet +ECLAIR_URL=http://127.0.0.1:8080 +ECLAIR_PASS=eclair_password diff --git a/lnbits/wallets/__init__.py b/lnbits/wallets/__init__.py index 10a17c6f..e1b37f6f 100644 --- a/lnbits/wallets/__init__.py +++ b/lnbits/wallets/__init__.py @@ -9,3 +9,4 @@ from .lnpay import LNPayWallet from .lnbits import LNbitsWallet from .lndrest import LndRestWallet from .spark import SparkWallet +from .eclair import EclairWallet diff --git a/lnbits/wallets/eclair.py b/lnbits/wallets/eclair.py new file mode 100644 index 00000000..2f29501c --- /dev/null +++ b/lnbits/wallets/eclair.py @@ -0,0 +1,166 @@ +import trio +import json +import httpx +import random +import base64 +import urllib.parse +from os import getenv +from typing import Optional, AsyncGenerator +from trio_websocket import open_websocket_url + +from .base import ( + StatusResponse, + InvoiceResponse, + PaymentResponse, + PaymentStatus, + Wallet, +) + +class EclairError(Exception): + pass + + +class UnknownError(Exception): + pass + + +class EclairWallet(Wallet): + def __init__(self): + url = getenv("ECLAIR_URL") + self.url = url[:-1] if url.endswith("/") else url + + passw = getenv("ECLAIR_PASS") + encodedAuth = base64.b64encode(f":{passw}".encode("utf-8")) + auth = str(encodedAuth, "utf-8") + self.auth = {"Authorization": f"Basic {auth}"} + + def __getattr__(self, key): + async def call(*args, **kwargs): + if args and kwargs: + raise TypeError( + f"must supply either named arguments or a list of arguments, not both: {args} {kwargs}" + ) + elif args: + params = args + elif kwargs: + params = kwargs + else: + params = {} + + try: + async with httpx.AsyncClient() as client: + r = await client.post( + self.url + "/" + key, + headers=self.auth, + data=params, + timeout=40, + ) + except (OSError, httpx.ConnectError, httpx.RequestError) as exc: + raise UnknownError("error connecting to eclair: " + str(exc)) + + try: + data = r.json() + if "error" in data: + print('ERROR', data["error"]) + raise EclairError(data["error"]) + except: + raise UnknownError(r.text) + + #if r.error: + # print('ERROR', r) + # if r.status_code == 401: + # raise EclairError("Access key invalid!") + + #raise EclairError(data.error) + return data + + return call + + async def status(self) -> StatusResponse: + try: + funds = await self.usablebalances() + except (httpx.ConnectError, httpx.RequestError): + return StatusResponse("Couldn't connect to Eclair server", 0) + except (EclairError, UnknownError) as e: + return StatusResponse(str(e), 0) + if not funds: + return StatusResponse("Funding wallet has no funds", 0) + + return StatusResponse( + None, + funds[0]["canSend"] * 1000, + ) + + async def create_invoice( + self, + amount: int, + memo: Optional[str] = None, + description_hash: Optional[bytes] = None, + ) -> InvoiceResponse: + if description_hash: + raise Unsupported("description_hash") + + try: + r = await self.createinvoice( + amountMsat=amount * 1000, + description=memo or "", + exposeprivatechannels=True, + ) + ok, checking_id, payment_request, error_message = True, r["paymentHash"], r["serialized"], "" + except (EclairError, UnknownError) as e: + ok, payment_request, error_message = False, None, str(e) + + return InvoiceResponse(ok, checking_id, payment_request, error_message) + + async def pay_invoice(self, bolt11: str) -> PaymentResponse: + try: + r = await self.payinvoice(invoice=bolt11, blocking=True) + except (EclairError, UnknownError) as exc: + return PaymentResponse(False, None, 0, None, str(exc)) + + preimage = r["paymentPreimage"] + return PaymentResponse(True, r["paymentHash"], 0, preimage, None) + + async def get_invoice_status(self, checking_id: str) -> PaymentStatus: + try: + r = await self.getreceivedinfo(paymentHash=checking_id) + + except (EclairError, UnknownError): + return PaymentStatus(None) + + if r["status"]["type"] != "received": + return PaymentStatus(False) + return PaymentStatus(True) + + async def get_payment_status(self, checking_id: str) -> PaymentStatus: + # check if it's 32 bytes hex + if len(checking_id) != 64: + return PaymentStatus(None) + try: + int(checking_id, 16) + except ValueError: + return PaymentStatus(None) + + try: + r = await self.getsentinfo(paymentHash=checking_id) + except (EclairError, UnknownError): + return PaymentStatus(None) + + raise KeyError("supplied an invalid checking_id") + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + url = urllib.parse.urlsplit(self.url) + ws_url = f"ws://{url.netloc}/ws" + + try: + async with open_websocket_url(ws_url, extra_headers=[('Authorization', self.auth["Authorization"])]) as ws: + message = await ws.get_message() + if message["type"] == "payment-received": + print('Received message: %s' % message) + yield message["paymentHash"] + + except OSError as ose: + pass + + print("lost connection to eclair's websocket, retrying in 5 seconds") + await trio.sleep(5) From 4585d97324aa2ce7ea244c2b5ab35293a97634f7 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 23 Jun 2021 18:50:01 +0100 Subject: [PATCH 002/116] small fix on paid_invoices_stream --- lnbits/wallets/eclair.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lnbits/wallets/eclair.py b/lnbits/wallets/eclair.py index 2f29501c..9c8fa540 100644 --- a/lnbits/wallets/eclair.py +++ b/lnbits/wallets/eclair.py @@ -152,15 +152,17 @@ class EclairWallet(Wallet): url = urllib.parse.urlsplit(self.url) ws_url = f"ws://{url.netloc}/ws" - try: - async with open_websocket_url(ws_url, extra_headers=[('Authorization', self.auth["Authorization"])]) as ws: - message = await ws.get_message() - if message["type"] == "payment-received": - print('Received message: %s' % message) - yield message["paymentHash"] + while True: + try: + async with open_websocket_url(ws_url, extra_headers=[('Authorization', self.auth["Authorization"])]) as ws: + message = await ws.get_message() + if "payment-received" in message["type"]: + print('Received message: %s' % message) + yield message["paymentHash"] - except OSError as ose: - pass + except OSError as ose: + print('OSE', ose) + pass - print("lost connection to eclair's websocket, retrying in 5 seconds") - await trio.sleep(5) + print("lost connection to eclair's websocket, retrying in 5 seconds") + await trio.sleep(5) From 756b121105848b4042f3e54bc5857afd637a46d4 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 25 Jun 2021 10:15:50 +0100 Subject: [PATCH 003/116] print errors for tracking/debugging --- lnbits/wallets/eclair.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lnbits/wallets/eclair.py b/lnbits/wallets/eclair.py index 9c8fa540..59d2ffdf 100644 --- a/lnbits/wallets/eclair.py +++ b/lnbits/wallets/eclair.py @@ -61,7 +61,7 @@ class EclairWallet(Wallet): try: data = r.json() if "error" in data: - print('ERROR', data["error"]) + print(f"ERROR-{key}", data["error"]) raise EclairError(data["error"]) except: raise UnknownError(r.text) @@ -156,8 +156,9 @@ class EclairWallet(Wallet): try: async with open_websocket_url(ws_url, extra_headers=[('Authorization', self.auth["Authorization"])]) as ws: message = await ws.get_message() + print('Received message: %s' % message) + if "payment-received" in message["type"]: - print('Received message: %s' % message) yield message["paymentHash"] except OSError as ose: From 914b9f3ffe06432eb8623905ae74d92634ae826f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 16 Feb 2022 22:23:26 +0100 Subject: [PATCH 004/116] allow amounts >0 --- lnbits/core/templates/core/_api_docs.html | 2 +- lnbits/core/views/api.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/core/templates/core/_api_docs.html b/lnbits/core/templates/core/_api_docs.html index c7f3f9ad..0e74f38e 100644 --- a/lnbits/core/templates/core/_api_docs.html +++ b/lnbits/core/templates/core/_api_docs.html @@ -61,7 +61,7 @@ curl -X POST {{ request.base_url }}api/v1/payments -d '{"out": false, "amount": <int>, "memo": <string>, "webhook": - <url:string>}' -H "X-Api-Key: {{ wallet.inkey }}" -H + <url:string>, "unit": <string>}' -H "X-Api-Key: {{ wallet.inkey }}" -H "Content-type: application/json" diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 107a2684..a5358275 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -123,7 +123,7 @@ async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)): class CreateInvoiceData(BaseModel): out: Optional[bool] = True - amount: int = Query(None, ge=1) + amount: int = Query(None, ge=0) memo: str = None unit: Optional[str] = "sat" description_hash: Optional[str] = None From 30ab519c3a5bfc0485ca12e2f9b2b3c217b3aad2 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 16 Feb 2022 22:41:12 +0100 Subject: [PATCH 005/116] amount is float and sats are int --- lnbits/core/views/api.py | 87 +++++++++++----------------------------- 1 file changed, 24 insertions(+), 63 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index a5358275..e54c30d4 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -68,13 +68,9 @@ async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)): @core_app.put("/api/v1/wallet/balance/{amount}") -async def api_update_balance( - amount: int, wallet: WalletTypeInfo = Depends(get_key_type) -): +async def api_update_balance(amount: int, wallet: WalletTypeInfo = Depends(get_key_type)): if wallet.wallet.user not in LNBITS_ADMIN_USERS: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user" - ) + raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user") payHash = urlsafe_short_hash() await create_payment( @@ -97,9 +93,7 @@ async def api_update_balance( @core_app.put("/api/v1/wallet/{new_name}") -async def api_update_wallet( - new_name: str, wallet: WalletTypeInfo = Depends(WalletAdminKeyChecker()) -): +async def api_update_wallet(new_name: str, wallet: WalletTypeInfo = Depends(WalletAdminKeyChecker())): await update_wallet(wallet.wallet.id, new_name) return { "id": wallet.wallet.id, @@ -111,19 +105,15 @@ async def api_update_wallet( @core_app.get("/api/v1/payments") async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)): await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True) - pendingPayments = await get_payments( - wallet_id=wallet.wallet.id, pending=True, exclude_uncheckable=True - ) + pendingPayments = await get_payments(wallet_id=wallet.wallet.id, pending=True, exclude_uncheckable=True) for payment in pendingPayments: - await check_invoice_status( - wallet_id=payment.wallet_id, payment_hash=payment.payment_hash - ) + await check_invoice_status(wallet_id=payment.wallet_id, payment_hash=payment.payment_hash) return await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True) class CreateInvoiceData(BaseModel): out: Optional[bool] = True - amount: int = Query(None, ge=0) + amount: float = Query(None, ge=0) memo: str = None unit: Optional[str] = "sat" description_hash: Optional[str] = None @@ -142,7 +132,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet): description_hash = b"" memo = data.memo if data.unit == "sat": - amount = data.amount + amount = int(data.amount) else: price_in_sats = await fiat_amount_as_satoshis(data.amount, data.unit) amount = price_in_sats @@ -242,9 +232,7 @@ async def api_payments_create( status_code=HTTPStatus.BAD_REQUEST, detail="BOLT11 string is invalid or not given", ) - return await api_payments_pay_invoice( - invoiceData.bolt11, wallet.wallet - ) # admin key + return await api_payments_pay_invoice(invoiceData.bolt11, wallet.wallet) # admin key # invoice key return await api_payments_create_invoice(invoiceData, wallet.wallet) @@ -258,9 +246,7 @@ class CreateLNURLData(BaseModel): @core_app.post("/api/v1/payments/lnurl") -async def api_payments_pay_lnurl( - data: CreateLNURLData, wallet: WalletTypeInfo = Depends(get_key_type) -): +async def api_payments_pay_lnurl(data: CreateLNURLData, wallet: WalletTypeInfo = Depends(get_key_type)): domain = urlparse(data.callback).netloc async with httpx.AsyncClient() as client: @@ -292,11 +278,11 @@ async def api_payments_pay_lnurl( detail=f"{domain} returned an invalid invoice. Expected {data.amount} msat, got {invoice.amount_msat}.", ) - # if invoice.description_hash != data.description_hash: - # raise HTTPException( - # status_code=HTTPStatus.BAD_REQUEST, - # detail=f"{domain} returned an invalid invoice. Expected description_hash == {data.description_hash}, got {invoice.description_hash}.", - # ) + # if invoice.description_hash != data.description_hash: + # raise HTTPException( + # status_code=HTTPStatus.BAD_REQUEST, + # detail=f"{domain} returned an invalid invoice. Expected description_hash == {data.description_hash}, got {invoice.description_hash}.", + # ) extra = {} @@ -354,12 +340,8 @@ async def subscribe(request: Request, wallet: Wallet): @core_app.get("/api/v1/payments/sse") -async def api_payments_sse( - request: Request, wallet: WalletTypeInfo = Depends(get_key_type) -): - return EventSourceResponse( - subscribe(request, wallet), ping=20, media_type="text/event-stream" - ) +async def api_payments_sse(request: Request, wallet: WalletTypeInfo = Depends(get_key_type)): + return EventSourceResponse(subscribe(request, wallet), ping=20, media_type="text/event-stream") @core_app.get("/api/v1/payments/{payment_hash}") @@ -368,9 +350,7 @@ async def api_payment(payment_hash): await check_invoice_status(payment.wallet_id, payment_hash) payment = await get_standalone_payment(payment_hash) if not payment: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Payment does not exist." - ) + raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Payment does not exist.") elif not payment.pending: return {"paid": True, "preimage": payment.preimage} @@ -382,9 +362,7 @@ async def api_payment(payment_hash): return {"paid": not payment.pending, "preimage": payment.preimage} -@core_app.get( - "/api/v1/lnurlscan/{code}", dependencies=[Depends(WalletInvoiceKeyChecker())] -) +@core_app.get("/api/v1/lnurlscan/{code}", dependencies=[Depends(WalletInvoiceKeyChecker())]) async def api_lnurlscan(code: str): try: url = lnurl.decode(code) @@ -394,17 +372,10 @@ async def api_lnurlscan(code: str): name_domain = code.split("@") if len(name_domain) == 2 and len(name_domain[1].split(".")) == 2: name, domain = name_domain - url = ( - ("http://" if domain.endswith(".onion") else "https://") - + domain - + "/.well-known/lnurlp/" - + name - ) + url = ("http://" if domain.endswith(".onion") else "https://") + domain + "/.well-known/lnurlp/" + name # will proceed with these values else: - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl" - ) + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl") # params is what will be returned to the client params: Dict = {"domain": domain} @@ -463,20 +434,14 @@ async def api_lnurlscan(code: str): params.update(balanceCheck=data["balanceCheck"]) # format callback url and send to client - parsed_callback = parsed_callback._replace( - query=urlencode(qs, doseq=True) - ) + parsed_callback = parsed_callback._replace(query=urlencode(qs, doseq=True)) params.update(callback=urlunparse(parsed_callback)) if tag == "payRequest": params.update(kind="pay") params.update(fixed=data["minSendable"] == data["maxSendable"]) - params.update( - description_hash=hashlib.sha256( - data["metadata"].encode("utf-8") - ).hexdigest() - ) + params.update(description_hash=hashlib.sha256(data["metadata"].encode("utf-8")).hexdigest()) metadata = json.loads(data["metadata"]) for [k, v] in metadata: if k == "text/plain": @@ -528,9 +493,7 @@ async def api_payments_decode(data: str = Query(None)): async def api_perform_lnurlauth(callback: str): err = await perform_lnurlauth(callback) if err: - raise HTTPException( - status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail=err.reason - ) + raise HTTPException(status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail=err.reason) return "" @@ -553,9 +516,7 @@ async def api_fiat_as_sats(data: ConversionData): output["sats"] = int(data.amount) output["BTC"] = data.amount / 100000000 for currency in data.to.split(","): - output[currency.strip().upper()] = await satoshis_amount_as_fiat( - data.amount, currency.strip() - ) + output[currency.strip().upper()] = await satoshis_amount_as_fiat(data.amount, currency.strip()) return output else: output[data.from_.upper()] = data.amount From 9fb30080abf9068e58ae4b5ee9bab0d3c90c54e7 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 16 Feb 2022 22:42:27 +0100 Subject: [PATCH 006/116] black --- lnbits/core/views/api.py | 73 ++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index e54c30d4..39257520 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -68,9 +68,13 @@ async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)): @core_app.put("/api/v1/wallet/balance/{amount}") -async def api_update_balance(amount: int, wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_update_balance( + amount: int, wallet: WalletTypeInfo = Depends(get_key_type) +): if wallet.wallet.user not in LNBITS_ADMIN_USERS: - raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user") + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user" + ) payHash = urlsafe_short_hash() await create_payment( @@ -93,7 +97,9 @@ async def api_update_balance(amount: int, wallet: WalletTypeInfo = Depends(get_k @core_app.put("/api/v1/wallet/{new_name}") -async def api_update_wallet(new_name: str, wallet: WalletTypeInfo = Depends(WalletAdminKeyChecker())): +async def api_update_wallet( + new_name: str, wallet: WalletTypeInfo = Depends(WalletAdminKeyChecker()) +): await update_wallet(wallet.wallet.id, new_name) return { "id": wallet.wallet.id, @@ -105,9 +111,13 @@ async def api_update_wallet(new_name: str, wallet: WalletTypeInfo = Depends(Wall @core_app.get("/api/v1/payments") async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)): await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True) - pendingPayments = await get_payments(wallet_id=wallet.wallet.id, pending=True, exclude_uncheckable=True) + pendingPayments = await get_payments( + wallet_id=wallet.wallet.id, pending=True, exclude_uncheckable=True + ) for payment in pendingPayments: - await check_invoice_status(wallet_id=payment.wallet_id, payment_hash=payment.payment_hash) + await check_invoice_status( + wallet_id=payment.wallet_id, payment_hash=payment.payment_hash + ) return await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True) @@ -232,7 +242,9 @@ async def api_payments_create( status_code=HTTPStatus.BAD_REQUEST, detail="BOLT11 string is invalid or not given", ) - return await api_payments_pay_invoice(invoiceData.bolt11, wallet.wallet) # admin key + return await api_payments_pay_invoice( + invoiceData.bolt11, wallet.wallet + ) # admin key # invoice key return await api_payments_create_invoice(invoiceData, wallet.wallet) @@ -246,7 +258,9 @@ class CreateLNURLData(BaseModel): @core_app.post("/api/v1/payments/lnurl") -async def api_payments_pay_lnurl(data: CreateLNURLData, wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_payments_pay_lnurl( + data: CreateLNURLData, wallet: WalletTypeInfo = Depends(get_key_type) +): domain = urlparse(data.callback).netloc async with httpx.AsyncClient() as client: @@ -340,8 +354,12 @@ async def subscribe(request: Request, wallet: Wallet): @core_app.get("/api/v1/payments/sse") -async def api_payments_sse(request: Request, wallet: WalletTypeInfo = Depends(get_key_type)): - return EventSourceResponse(subscribe(request, wallet), ping=20, media_type="text/event-stream") +async def api_payments_sse( + request: Request, wallet: WalletTypeInfo = Depends(get_key_type) +): + return EventSourceResponse( + subscribe(request, wallet), ping=20, media_type="text/event-stream" + ) @core_app.get("/api/v1/payments/{payment_hash}") @@ -350,7 +368,9 @@ async def api_payment(payment_hash): await check_invoice_status(payment.wallet_id, payment_hash) payment = await get_standalone_payment(payment_hash) if not payment: - raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Payment does not exist.") + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Payment does not exist." + ) elif not payment.pending: return {"paid": True, "preimage": payment.preimage} @@ -362,7 +382,9 @@ async def api_payment(payment_hash): return {"paid": not payment.pending, "preimage": payment.preimage} -@core_app.get("/api/v1/lnurlscan/{code}", dependencies=[Depends(WalletInvoiceKeyChecker())]) +@core_app.get( + "/api/v1/lnurlscan/{code}", dependencies=[Depends(WalletInvoiceKeyChecker())] +) async def api_lnurlscan(code: str): try: url = lnurl.decode(code) @@ -372,10 +394,17 @@ async def api_lnurlscan(code: str): name_domain = code.split("@") if len(name_domain) == 2 and len(name_domain[1].split(".")) == 2: name, domain = name_domain - url = ("http://" if domain.endswith(".onion") else "https://") + domain + "/.well-known/lnurlp/" + name + url = ( + ("http://" if domain.endswith(".onion") else "https://") + + domain + + "/.well-known/lnurlp/" + + name + ) # will proceed with these values else: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl") + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl" + ) # params is what will be returned to the client params: Dict = {"domain": domain} @@ -434,14 +463,20 @@ async def api_lnurlscan(code: str): params.update(balanceCheck=data["balanceCheck"]) # format callback url and send to client - parsed_callback = parsed_callback._replace(query=urlencode(qs, doseq=True)) + parsed_callback = parsed_callback._replace( + query=urlencode(qs, doseq=True) + ) params.update(callback=urlunparse(parsed_callback)) if tag == "payRequest": params.update(kind="pay") params.update(fixed=data["minSendable"] == data["maxSendable"]) - params.update(description_hash=hashlib.sha256(data["metadata"].encode("utf-8")).hexdigest()) + params.update( + description_hash=hashlib.sha256( + data["metadata"].encode("utf-8") + ).hexdigest() + ) metadata = json.loads(data["metadata"]) for [k, v] in metadata: if k == "text/plain": @@ -493,7 +528,9 @@ async def api_payments_decode(data: str = Query(None)): async def api_perform_lnurlauth(callback: str): err = await perform_lnurlauth(callback) if err: - raise HTTPException(status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail=err.reason) + raise HTTPException( + status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail=err.reason + ) return "" @@ -516,7 +553,9 @@ async def api_fiat_as_sats(data: ConversionData): output["sats"] = int(data.amount) output["BTC"] = data.amount / 100000000 for currency in data.to.split(","): - output[currency.strip().upper()] = await satoshis_amount_as_fiat(data.amount, currency.strip()) + output[currency.strip().upper()] = await satoshis_amount_as_fiat( + data.amount, currency.strip() + ) return output else: output[data.from_.upper()] = data.amount From cd846233050388538f5dfacf1c57b7610c9abed9 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 17 Feb 2022 09:14:07 +0100 Subject: [PATCH 007/116] amount label adaptive to uni - look at me Im a frontent dev now --- lnbits/core/templates/core/wallet.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 2b6ec5de..68db5863 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -418,7 +418,7 @@ dense v-model.number="receive.data.amount" type="number" - label="Amount ({{LNBITS_DENOMINATION}}) *" + :label="'Amount (' + receive.unit + ') *'" :step="receive.unit != 'sat' ? '0.001' : '1'" :min="receive.minMax[0]" :max="receive.minMax[1]" From d559c604fa841716c01c5f95ee8a3c589724971f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 17 Feb 2022 09:29:10 +0100 Subject: [PATCH 008/116] invoice without memo --- lnbits/core/views/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 39257520..e353b03b 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -140,7 +140,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet): memo = "" else: description_hash = b"" - memo = data.memo + memo = data.memo if data.memo is not None else "LNbits" if data.unit == "sat": amount = int(data.amount) else: From 15c85574596dfd9cf12237e7bc9bbaf97bd6ad50 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 17 Feb 2022 10:16:11 +0100 Subject: [PATCH 009/116] allow empty memo --- lnbits/core/templates/core/wallet.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 68db5863..c7dc6fde 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -437,7 +437,7 @@ From 13a72d9a41abe3dc378c6c8c28203113b06c9c47 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 17 Feb 2022 11:42:08 +0100 Subject: [PATCH 010/116] replace string with LNBITS_SITE_TITLE --- lnbits/core/views/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index e353b03b..d607e149 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -31,6 +31,7 @@ from lnbits.utils.exchange_rates import ( fiat_amount_as_satoshis, satoshis_amount_as_fiat, ) +from lnbits.settings import LNBITS_SITE_TITLE from .. import core_app, db from ..crud import ( @@ -140,7 +141,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet): memo = "" else: description_hash = b"" - memo = data.memo if data.memo is not None else "LNbits" + memo = data.memo or LNBITS_SITE_TITLE if data.unit == "sat": amount = int(data.amount) else: From 251ecb8f1dd802ed8b798a62dc5cc5d1ac74c5cb Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 17 Feb 2022 21:01:14 +0000 Subject: [PATCH 011/116] url_for params --- lnbits/core/templates/core/index.html | 2 +- lnbits/core/views/generic.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html index 88dc496d..f363a841 100644 --- a/lnbits/core/templates/core/index.html +++ b/lnbits/core/templates/core/index.html @@ -11,7 +11,7 @@ color="primary" @click="processing" type="a" - href="{{ url_for('core.lnurlwallet', lightning=lnurl) }}" + href="{{ url_for('core.lnurlwallet') }}?lightning={{ lnurl }}" > Press to claim bitcoin diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index d917ffab..16a2fbac 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -15,8 +15,8 @@ from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer, url_for from lnbits.settings import ( - LNBITS_ALLOWED_USERS, LNBITS_ADMIN_USERS, + LNBITS_ALLOWED_USERS, LNBITS_SITE_TITLE, SERVICE_FEE, ) @@ -226,7 +226,7 @@ async def lnurl_balance_notify(request: Request, service: str): redeem_lnurl_withdraw(bc.wallet, bc.url) -@core_html_routes.get("/lnurlwallet", response_class=RedirectResponse) +@core_html_routes.get("/lnurlwallet", response_class=RedirectResponse, name="core.lnurlwallet") async def lnurlwallet(request: Request): async with db.connect() as conn: account = await create_account(conn=conn) From 302d79a30f35a2816616219ed1aaf39595821442 Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Thu, 17 Feb 2022 23:33:55 +0000 Subject: [PATCH 012/116] Improved bitcoin theme --- lnbits/static/scss/base.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/static/scss/base.scss b/lnbits/static/scss/base.scss index afafd50d..28d6810c 100644 --- a/lnbits/static/scss/base.scss +++ b/lnbits/static/scss/base.scss @@ -8,8 +8,8 @@ $themes: ( marginal-text: #fff ), 'bitcoin': ( - primary: #ff9853, - secondary: #ff7353, + primary: #ff9900, + secondary: #ff8800, dark: #2d293b, info: #333646, marginal-bg: #2d293b, From 10492ec3d0c41bcbbac1b2ada75efd384f404a4f Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Thu, 17 Feb 2022 23:40:29 +0000 Subject: [PATCH 013/116] Changed bitcoin theme back --- lnbits/static/scss/base.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/static/scss/base.scss b/lnbits/static/scss/base.scss index 28d6810c..afafd50d 100644 --- a/lnbits/static/scss/base.scss +++ b/lnbits/static/scss/base.scss @@ -8,8 +8,8 @@ $themes: ( marginal-text: #fff ), 'bitcoin': ( - primary: #ff9900, - secondary: #ff8800, + primary: #ff9853, + secondary: #ff7353, dark: #2d293b, info: #333646, marginal-bg: #2d293b, From 6496fac35fc9a160a91ba5ec14b907ae43e8408b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 18 Feb 2022 12:35:25 +0100 Subject: [PATCH 014/116] fill mask for fiat --- lnbits/core/templates/core/wallet.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index c7dc6fde..95436f86 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -417,9 +417,11 @@ filled dense v-model.number="receive.data.amount" - type="number" :label="'Amount (' + receive.unit + ') *'" - :step="receive.unit != 'sat' ? '0.001' : '1'" + :mask="receive.unit != 'sat' ? '#.##' : '#'" + fill-mask="0" + reverse-fill-mask + :step="receive.unit != 'sat' ? '0.01' : '1'" :min="receive.minMax[0]" :max="receive.minMax[1]" :readonly="receive.lnurl && receive.lnurl.fixed" From 4793ea0d18331b9837a3a0c0163d395c8a10ef14 Mon Sep 17 00:00:00 2001 From: Candle <50766841+CandleHater@users.noreply.github.com> Date: Fri, 18 Feb 2022 16:35:32 +0100 Subject: [PATCH 015/116] added "missing" libffi/libpq package When I tryed to run "pipenv install --dev" on a Pi 4 with the latest Raspberry OS it failed with two dependencies. These additional installations fixed it. --- docs/devs/installation.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/devs/installation.md b/docs/devs/installation.md index c8efb1c6..729da1ca 100644 --- a/docs/devs/installation.md +++ b/docs/devs/installation.md @@ -12,6 +12,8 @@ LNbits uses [Pipenv][pipenv] to manage Python packages. ```sh git clone https://github.com/lnbits/lnbits-legend.git cd lnbits-legend/ + +sudo apt-get install -y python3-venv pipenv shell # pipenv --python 3.9 shell (if you wish to use a version of Python higher than 3.7) pipenv install --dev @@ -19,6 +21,9 @@ pipenv install --dev # If any of the modules fails to install, try checking and upgrading your setupTool module # pip install -U setuptools + +# install libffi/libpq in case "pipenv install" fails +# sudo apt-get install -y libffi-dev libpq-dev ``` ## Running the server From 2c161a079de9e44ed1b97bf12ef56e240a273cd0 Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Fri, 18 Feb 2022 17:16:03 +0000 Subject: [PATCH 016/116] Update installation.md --- docs/devs/installation.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/devs/installation.md b/docs/devs/installation.md index c8efb1c6..632c34f2 100644 --- a/docs/devs/installation.md +++ b/docs/devs/installation.md @@ -41,4 +41,6 @@ E.g. when you want to use LND you have to `pipenv run pip install lndgrpc` and ` Take a look at [Polar][polar] for an excellent way of spinning up a Lightning Network dev environment. -**Note**: We reccomend using Caddy for a reverse-proxy, if you want to serve your install through a domain, alternatively you can use [ngrok](https://ngrok.com/). +**Notes**: +We reccomend using Caddy for a reverse-proxy, if you want to serve your install through a domain, alternatively you can use [ngrok](https://ngrok.com/). +Screen works well if you want LNbits to continue running when you close your terminal session. From d7b3a289503e252459d8e876e283467bd911996f Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Fri, 18 Feb 2022 17:16:18 +0000 Subject: [PATCH 017/116] Update installation.md --- docs/devs/installation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/devs/installation.md b/docs/devs/installation.md index 632c34f2..be4b1473 100644 --- a/docs/devs/installation.md +++ b/docs/devs/installation.md @@ -42,5 +42,6 @@ E.g. when you want to use LND you have to `pipenv run pip install lndgrpc` and ` Take a look at [Polar][polar] for an excellent way of spinning up a Lightning Network dev environment. **Notes**: + We reccomend using Caddy for a reverse-proxy, if you want to serve your install through a domain, alternatively you can use [ngrok](https://ngrok.com/). Screen works well if you want LNbits to continue running when you close your terminal session. From 76edfa21342d76952a3e1a436311c0de8760e0af Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Fri, 18 Feb 2022 17:16:49 +0000 Subject: [PATCH 018/116] Update installation.md --- docs/devs/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/devs/installation.md b/docs/devs/installation.md index be4b1473..344b1d6e 100644 --- a/docs/devs/installation.md +++ b/docs/devs/installation.md @@ -43,5 +43,5 @@ Take a look at [Polar][polar] for an excellent way of spinning up a Lightning Ne **Notes**: -We reccomend using Caddy for a reverse-proxy, if you want to serve your install through a domain, alternatively you can use [ngrok](https://ngrok.com/). -Screen works well if you want LNbits to continue running when you close your terminal session. +* We reccomend using Caddy for a reverse-proxy, if you want to serve your install through a domain, alternatively you can use [ngrok](https://ngrok.com/). +* Screen works well if you want LNbits to continue running when you close your terminal session. From 41a9689014e40e2830bce53e519248172e56a53d Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Fri, 18 Feb 2022 17:17:18 +0000 Subject: [PATCH 019/116] Update installation.md --- docs/devs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/devs/installation.md b/docs/devs/installation.md index 344b1d6e..6f84b8be 100644 --- a/docs/devs/installation.md +++ b/docs/devs/installation.md @@ -43,5 +43,5 @@ Take a look at [Polar][polar] for an excellent way of spinning up a Lightning Ne **Notes**: -* We reccomend using Caddy for a reverse-proxy, if you want to serve your install through a domain, alternatively you can use [ngrok](https://ngrok.com/). +* We reccomend using Caddy for a reverse-proxy if you want to serve your install through a domain, alternatively you can use [ngrok](https://ngrok.com/). * Screen works well if you want LNbits to continue running when you close your terminal session. From fbafe2b8f1989b9f793f294983cbf7a612bcfbd1 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 21 Feb 2022 09:17:36 +0100 Subject: [PATCH 020/116] Retry backend connection on startup --- lnbits/app.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 64e4ba4e..0043ba65 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -84,18 +84,20 @@ def create_app(config_object="lnbits.settings") -> FastAPI: def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") async def check_wallet_status(): - error_message, balance = await WALLET.status() - if error_message: + success = False + while not success: + error_message, balance = await WALLET.status() + if not error_message: + break warnings.warn( f" × The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'", RuntimeWarning, ) - - sys.exit(4) - else: - print( - f" ✔️ {WALLET.__class__.__name__} seems to be connected and with a balance of {balance} msat." - ) + print("Retrying connection to backend in 5 seconds...") + await asyncio.sleep(5) + print( + f" ✔️ {WALLET.__class__.__name__} seems to be connected and with a balance of {balance} msat." + ) def register_routes(app: FastAPI) -> None: From 36952981c21ee235d5997155bf29e2ba2de712b2 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 21 Feb 2022 09:25:45 +0100 Subject: [PATCH 021/116] remove success var --- lnbits/app.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index 0043ba65..ca770167 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -84,8 +84,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI: def check_funding_source(app: FastAPI) -> None: @app.on_event("startup") async def check_wallet_status(): - success = False - while not success: + while True: error_message, balance = await WALLET.status() if not error_message: break From 27694ee23d9785fd45c1dc86b75b6416fc46b074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loun=C3=A8s=20Ksouri?= Date: Tue, 22 Feb 2022 08:40:23 +0100 Subject: [PATCH 022/116] refactor: GitHub Action to build and push to Docker Hub on tag --- .github/workflows/on-push.yml | 58 ------------------------------ .github/workflows/on-tag.yml | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 58 deletions(-) delete mode 100644 .github/workflows/on-push.yml create mode 100644 .github/workflows/on-tag.yml diff --git a/.github/workflows/on-push.yml b/.github/workflows/on-push.yml deleted file mode 100644 index 4d008dec..00000000 --- a/.github/workflows/on-push.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Docker build on push - -env: - DOCKER_CLI_EXPERIMENTAL: enabled - -on: - push: - branches: - - master - -jobs: - build: - runs-on: ubuntu-20.04 - name: Build and push lnbits image - steps: - - name: Login to Docker Hub - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - - - name: Checkout project - uses: actions/checkout@v2 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - id: qemu - - - name: Setup Docker buildx action - uses: docker/setup-buildx-action@v1 - id: buildx - - - name: Show available Docker buildx platforms - run: echo ${{ steps.buildx.outputs.platforms }} - - - name: Cache Docker layers - uses: actions/cache@v2 - id: cache - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Run Docker buildx against commit hash - run: | - docker buildx build \ - --cache-from "type=local,src=/tmp/.buildx-cache" \ - --cache-to "type=local,dest=/tmp/.buildx-cache" \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --tag ${{ secrets.DOCKER_USERNAME }}/lnbits:${GITHUB_SHA:0:7} \ - --output "type=registry" ./ - - - name: Run Docker buildx against latest - run: | - docker buildx build \ - --cache-from "type=local,src=/tmp/.buildx-cache" \ - --cache-to "type=local,dest=/tmp/.buildx-cache" \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --tag ${{ secrets.DOCKER_USERNAME }}/lnbits:latest \ - --output "type=registry" ./ \ No newline at end of file diff --git a/.github/workflows/on-tag.yml b/.github/workflows/on-tag.yml new file mode 100644 index 00000000..f6fa53e9 --- /dev/null +++ b/.github/workflows/on-tag.yml @@ -0,0 +1,68 @@ +name: Build and push Docker image on tag + +env: + DOCKER_CLI_EXPERIMENTAL: enabled + +on: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+-*" + +jobs: + build: + runs-on: ubuntu-20.04 + name: Build and push lnbits image + steps: + - name: Login to Docker Hub + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + + - name: Checkout project + uses: actions/checkout@v2 + + - name: Import environment variables + id: import-env + shell: bash + run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + + - name: Show set environment variables + run: | + printf " TAG: %s\n" "$TAG" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + id: qemu + + - name: Setup Docker buildx action + uses: docker/setup-buildx-action@v1 + id: buildx + + - name: Show available Docker buildx platforms + run: echo ${{ steps.buildx.outputs.platforms }} + + - name: Cache Docker layers + uses: actions/cache@v2 + id: cache + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Run Docker buildx against tag + run: | + docker buildx build \ + --cache-from "type=local,src=/tmp/.buildx-cache" \ + --cache-to "type=local,dest=/tmp/.buildx-cache" \ + --platform linux/amd64,linux/arm64 \ + --tag ${{ secrets.DOCKER_USERNAME }}/lnbits-legend:${TAG} \ + --output "type=registry" ./ + + - name: Run Docker buildx against latest + run: | + docker buildx build \ + --cache-from "type=local,src=/tmp/.buildx-cache" \ + --cache-to "type=local,dest=/tmp/.buildx-cache" \ + --platform linux/amd64,linux/arm64 \ + --tag ${{ secrets.DOCKER_USERNAME }}/lnbits-legend:latest \ + --output "type=registry" ./ \ No newline at end of file From a132387fa893d632271b2ef2188817a5ef601ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loun=C3=A8s=20Ksouri?= Date: Tue, 22 Feb 2022 08:41:19 +0100 Subject: [PATCH 023/116] fix: add build deps for arm --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 243f298b..f04c67b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,8 +8,9 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH" # Install build deps RUN apt-get update -RUN apt-get install -y --no-install-recommends build-essential +RUN apt-get install -y --no-install-recommends build-essential pkg-config RUN python -m pip install --upgrade pip +RUN pip install wheel # Install runtime deps COPY requirements.txt /tmp/requirements.txt From 31bcb32e2d9f0f6176b8ede12e37f9fb32865dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loun=C3=A8s=20Ksouri?= Date: Tue, 22 Feb 2022 08:41:57 +0100 Subject: [PATCH 024/116] feat: dynamic port and host using environment variables --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f04c67b5..7b8e523d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,9 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH" WORKDIR /app COPY --chown=1000:1000 lnbits /app/lnbits +ENV LNBITS_PORT="5000" +ENV LNBITS_HOST="0.0.0.0" + EXPOSE 5000 -CMD ["uvicorn", "lnbits.__main__:app", "--port", "5000", "--host", "0.0.0.0"] +CMD ["sh", "-c", "uvicorn lnbits.__main__:app --port $LNBITS_PORT --host $LNBITS_HOST"] From 4f3e48e280883f214e0f4bcf78ff85484297757f Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Feb 2022 15:16:39 +0000 Subject: [PATCH 025/116] fix lnurl encoded in QR --- lnbits/extensions/withdraw/templates/withdraw/display.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/withdraw/templates/withdraw/display.html b/lnbits/extensions/withdraw/templates/withdraw/display.html index 5552c77f..a20f32d8 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/display.html +++ b/lnbits/extensions/withdraw/templates/withdraw/display.html @@ -10,7 +10,7 @@ From a5a33311060421169c7d0c7052a5ca85858e10c5 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Feb 2022 16:23:50 +0000 Subject: [PATCH 026/116] revert to fallback scheme + make request case insensitive --- lnbits/core/static/js/wallet.js | 4 ++-- lnbits/extensions/withdraw/templates/withdraw/display.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index 8d58302b..ae98919a 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -364,12 +364,12 @@ new Vue({ }, decodeRequest: function () { this.parse.show = true - + let req = this.parse.data.request.toLowerCase() if (this.parse.data.request.startsWith('lightning:')) { this.parse.data.request = this.parse.data.request.slice(10) } else if (this.parse.data.request.startsWith('lnurl:')) { this.parse.data.request = this.parse.data.request.slice(6) - } else if (this.parse.data.request.indexOf('lightning=lnurl1') !== -1) { + } else if (req.indexOf('lightning=lnurl1') !== -1) { this.parse.data.request = this.parse.data.request .split('lightning=')[1] .split('&')[0] diff --git a/lnbits/extensions/withdraw/templates/withdraw/display.html b/lnbits/extensions/withdraw/templates/withdraw/display.html index a20f32d8..5552c77f 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/display.html +++ b/lnbits/extensions/withdraw/templates/withdraw/display.html @@ -10,7 +10,7 @@ From eb7b369b6df940d46c70cd52778f0ebdc954c285 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Feb 2022 16:45:35 +0000 Subject: [PATCH 027/116] fix payments decode --- lnbits/core/views/api.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index d607e149..151eac38 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -24,24 +24,24 @@ from lnbits.decorators import ( WalletTypeInfo, get_key_type, ) -from lnbits.helpers import url_for +from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.requestvars import g +from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE from lnbits.utils.exchange_rates import ( currencies, fiat_amount_as_satoshis, satoshis_amount_as_fiat, ) -from lnbits.settings import LNBITS_SITE_TITLE from .. import core_app, db from ..crud import ( + create_payment, get_payments, get_standalone_payment, - save_balance_check, - update_wallet, - create_payment, get_wallet, + save_balance_check, update_payment_status, + update_wallet, ) from ..services import ( InvoiceFailure, @@ -52,8 +52,6 @@ from ..services import ( perform_lnurlauth, ) from ..tasks import api_invoice_listeners -from lnbits.settings import LNBITS_ADMIN_USERS -from lnbits.helpers import urlsafe_short_hash @core_app.get("/api/v1/wallet") @@ -503,12 +501,13 @@ async def api_lnurlscan(code: str): @core_app.post("/api/v1/payments/decode") async def api_payments_decode(data: str = Query(None)): + print(data) try: - if data["data"][:5] == "LNURL": - url = lnurl.decode(data["data"]) + if data[:5] == "LNURL": + url = lnurl.decode(data) return {"domain": url} else: - invoice = bolt11.decode(data["data"]) + invoice = bolt11.decode(data) return { "payment_hash": invoice.payment_hash, "amount_msat": invoice.amount_msat, From 510a28f3d05d5b1ad839a7d73cf7fe5f8e289e97 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Feb 2022 20:38:35 +0000 Subject: [PATCH 028/116] create_charge data --- lnbits/extensions/streamalerts/views_api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/streamalerts/views_api.py b/lnbits/extensions/streamalerts/views_api.py index e19abd73..177803d4 100644 --- a/lnbits/extensions/streamalerts/views_api.py +++ b/lnbits/extensions/streamalerts/views_api.py @@ -7,6 +7,7 @@ from starlette.responses import RedirectResponse from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type +from lnbits.extensions.satspay.models import CreateCharge from lnbits.extensions.streamalerts.models import ( CreateDonation, CreateService, @@ -116,14 +117,15 @@ async def api_create_donation(data: CreateDonation, request: Request): name = data.name description = f"{sats} sats donation from {name} to {service.twitchuser}" - charge = await create_charge( + create_charge_data = CreateCharge( amount=sats, completelink=f"https://twitch.tv/{service.twitchuser}", completelinktext="Back to Stream!", webhook=webhook_base + "/streamalerts/api/v1/postdonation", description=description, - **charge_details, + **charge_details ) + charge = await create_charge(user=charge_details["user"], data=data) await create_donation( id=charge.id, wallet=service.wallet, From 71e11b73875b284dd5c098b794a3cb45de2dbb4d Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Feb 2022 21:12:46 +0000 Subject: [PATCH 029/116] fix typo --- lnbits/extensions/streamalerts/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/streamalerts/views_api.py b/lnbits/extensions/streamalerts/views_api.py index 177803d4..fa7ff7d2 100644 --- a/lnbits/extensions/streamalerts/views_api.py +++ b/lnbits/extensions/streamalerts/views_api.py @@ -125,7 +125,7 @@ async def api_create_donation(data: CreateDonation, request: Request): description=description, **charge_details ) - charge = await create_charge(user=charge_details["user"], data=data) + charge = await create_charge(user=charge_details["user"], data=create_charge_data) await create_donation( id=charge.id, wallet=service.wallet, From 8930152b0fa0d8600db561160fbf323a79f761c8 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Tue, 22 Feb 2022 21:22:07 +0000 Subject: [PATCH 030/116] fix models int to str --- lnbits/extensions/streamalerts/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/streamalerts/models.py b/lnbits/extensions/streamalerts/models.py index 6511e916..4a365cba 100644 --- a/lnbits/extensions/streamalerts/models.py +++ b/lnbits/extensions/streamalerts/models.py @@ -60,7 +60,7 @@ class Service(BaseModel): onchain: Optional[str] servicename: str # Currently, this will just always be "Streamlabs" authenticated: bool # Whether a token (see below) has been acquired yet - token: Optional[int] # The token with which to authenticate requests + token: Optional[str] # The token with which to authenticate requests @classmethod def from_row(cls, row: Row) -> "Service": From a374526fafd83fe1b84edff71c8580f8a715060a Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 23 Feb 2022 10:13:52 +0000 Subject: [PATCH 031/116] make Anonymous default name --- .../extensions/streamalerts/templates/streamalerts/display.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/streamalerts/templates/streamalerts/display.html b/lnbits/extensions/streamalerts/templates/streamalerts/display.html index a10e64d8..8a0b2d59 100644 --- a/lnbits/extensions/streamalerts/templates/streamalerts/display.html +++ b/lnbits/extensions/streamalerts/templates/streamalerts/display.html @@ -62,7 +62,7 @@ donationDialog: { show: false, data: { - name: '', + name: null, sats: '', message: '' } From 270cac37ac92944787b6e307997d9a1b7571269f Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 23 Feb 2022 10:37:15 +0000 Subject: [PATCH 032/116] fix typo in table name --- lnbits/extensions/events/crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/events/crud.py b/lnbits/extensions/events/crud.py index 9c6e5c6b..9e04476d 100644 --- a/lnbits/extensions/events/crud.py +++ b/lnbits/extensions/events/crud.py @@ -76,7 +76,7 @@ async def delete_ticket(payment_hash: str) -> None: async def delete_event_tickets(event_id: str) -> None: - await db.execute("DELETE FROM events.tickets WHERE event = ?", (event_id,)) + await db.execute("DELETE FROM events.ticket WHERE event = ?", (event_id,)) # EVENTS From e82ce89e9a1f61939182052bb71cf04a321f4d14 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 23 Feb 2022 13:32:23 +0000 Subject: [PATCH 033/116] change namedtuple to basemodel --- lnbits/extensions/tipjar/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/tipjar/models.py b/lnbits/extensions/tipjar/models.py index 0bfea50c..92f25ab3 100644 --- a/lnbits/extensions/tipjar/models.py +++ b/lnbits/extensions/tipjar/models.py @@ -1,5 +1,5 @@ from sqlite3 import Row -from typing import NamedTuple, Optional +from typing import Optional from fastapi.param_functions import Query from pydantic import BaseModel @@ -26,7 +26,7 @@ class createTip(BaseModel): message: str = "" -class Tip(NamedTuple): +class Tip(BaseModel): """A Tip represents a single donation""" id: str # This ID always corresponds to a satspay charge ID @@ -55,7 +55,7 @@ class createTips(BaseModel): message: str -class TipJar(NamedTuple): +class TipJar(BaseModel): """A TipJar represents a user's tip jar""" id: int From 4a588dda1bcfdc78e33f713caaec07d54139a380 Mon Sep 17 00:00:00 2001 From: benarc Date: Wed, 23 Feb 2022 13:51:30 +0000 Subject: [PATCH 034/116] Added ad space and option to turn off api docs --- .env.example | 3 +++ lnbits/core/templates/core/wallet.html | 8 +++++++- lnbits/helpers.py | 3 +++ lnbits/settings.py | 2 ++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 51259007..e4952359 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,9 @@ LNBITS_ADMIN_USERS="" LNBITS_ADMIN_EXTENSIONS="ngrok" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" +LNBITS_AD_SPACE="" # csv ad image filepaths or urls, extensions can choose to honor +LNBITS_HIDE_API=false # Hides wallet api, extensions can choose to honor + # Disable extensions for all users, use "all" to disable all extensions LNBITS_DISABLED_EXTENSIONS="amilk" diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 95436f86..0fb6f7e4 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -273,7 +273,11 @@ -
+ + {% if HIDE_API %} +
+ {% else %} +
@@ -378,6 +382,8 @@ + {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %} + {% endfor %} {% endif %}
diff --git a/lnbits/helpers.py b/lnbits/helpers.py index d2616375..cb6f8ee7 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -160,6 +160,9 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates: ["lnbits/templates", "lnbits/core/templates", *additional_folders] ) ) + if settings.LNBITS_AD_SPACE: + t.env.globals["AD_SPACE"] = settings.LNBITS_AD_SPACE + t.env.globals["HIDE_API"] = settings.LNBITS_HIDE_API t.env.globals["SITE_TITLE"] = settings.LNBITS_SITE_TITLE t.env.globals["LNBITS_DENOMINATION"] = settings.LNBITS_DENOMINATION t.env.globals["SITE_TAGLINE"] = settings.LNBITS_SITE_TAGLINE diff --git a/lnbits/settings.py b/lnbits/settings.py index 26699bc0..a24f1ed8 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -34,6 +34,8 @@ LNBITS_DISABLED_EXTENSIONS: List[str] = env.list( "LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str ) +LNBITS_AD_SPACE = env.list("LNBITS_AD_SPACE", default=[]) +LNBITS_HIDE_API = env.bool("LNBITS_HIDE_API", default=True) LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits") LNBITS_DENOMINATION = env.str("LNBITS_DENOMINATION", default="sats") LNBITS_SITE_TAGLINE = env.str( From f7ff9d27d2255a5b7b14ff491e6ebf31150b2739 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 23 Feb 2022 21:03:25 +0000 Subject: [PATCH 035/116] fix copilots not being fetched --- lnbits/extensions/copilot/crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/copilot/crud.py b/lnbits/extensions/copilot/crud.py index e456334e..d0da044e 100644 --- a/lnbits/extensions/copilot/crud.py +++ b/lnbits/extensions/copilot/crud.py @@ -88,7 +88,7 @@ async def get_copilot(copilot_id: str) -> Copilots: async def get_copilots(user: str) -> List[Copilots]: rows = await db.fetchall( - "SELECT * FROM copilot.newer_copilots WHERE user = ?", (user,) + 'SELECT * FROM copilot.newer_copilots WHERE "user" = ?', (user,) ) return [Copilots(**row) for row in rows] From 42b444f54dfcb6926feaa232579568eae8729095 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 23 Feb 2022 21:40:11 +0000 Subject: [PATCH 036/116] make "anonymous" as default name --- lnbits/extensions/streamalerts/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/streamalerts/views_api.py b/lnbits/extensions/streamalerts/views_api.py index fa7ff7d2..0a678d8b 100644 --- a/lnbits/extensions/streamalerts/views_api.py +++ b/lnbits/extensions/streamalerts/views_api.py @@ -114,7 +114,7 @@ async def api_create_donation(data: CreateDonation, request: Request): service_id = data.service service = await get_service(service_id) charge_details = await get_charge_details(service.id) - name = data.name + name = data.name if data.name else "Anonymous" description = f"{sats} sats donation from {name} to {service.twitchuser}" create_charge_data = CreateCharge( From 241c81c790424c76e843f9cca0b75e651ea7dbbc Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Wed, 23 Feb 2022 22:09:38 +0000 Subject: [PATCH 037/116] fix update mempool --- lnbits/extensions/watchonly/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 1122ef5c..0cca67bc 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -112,7 +112,7 @@ async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type) async def api_update_mempool( endpoint: str = Query(...), w: WalletTypeInfo = Depends(require_admin_key) ): - mempool = await update_mempool(endpoint, user=w.wallet.user) + mempool = await update_mempool(**{"endpoint": endpoint}, user=w.wallet.user) return mempool.dict() From c1fa5bbcbf8797a86499f82483588c409e5c50aa Mon Sep 17 00:00:00 2001 From: benarc Date: Thu, 24 Feb 2022 12:32:37 +0000 Subject: [PATCH 038/116] Change HIDE to false --- lnbits/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/settings.py b/lnbits/settings.py index a24f1ed8..9ccd9e4e 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -35,7 +35,7 @@ LNBITS_DISABLED_EXTENSIONS: List[str] = env.list( ) LNBITS_AD_SPACE = env.list("LNBITS_AD_SPACE", default=[]) -LNBITS_HIDE_API = env.bool("LNBITS_HIDE_API", default=True) +LNBITS_HIDE_API = env.bool("LNBITS_HIDE_API", default=False) LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits") LNBITS_DENOMINATION = env.str("LNBITS_DENOMINATION", default="sats") LNBITS_SITE_TAGLINE = env.str( From e96701cb59f6b61278b0e4873de7fed8ddca6f30 Mon Sep 17 00:00:00 2001 From: benarc Date: Thu, 24 Feb 2022 16:03:10 +0000 Subject: [PATCH 039/116] Added template banner --- lnbits/core/templates/core/wallet.html | 852 +++++++++++++------------ lnbits/static/images/templatead.png | Bin 0 -> 7911 bytes 2 files changed, 436 insertions(+), 416 deletions(-) create mode 100644 lnbits/static/images/templatead.png diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index 0fb6f7e4..e9c6e496 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -277,446 +277,466 @@ {% if HIDE_API %}
{% else %} -
- - -
- {{ SITE_TITLE }} Wallet: {{ wallet.name }} -
-
- - - - - {% include "core/_api_docs.html" %} +
+ + +
+ {{ SITE_TITLE }} Wallet: {{ wallet.name }} +
+
+ - {% if wallet.lnurlwithdraw_full %} - - - -

- This is an LNURL-withdraw QR code for slurping everything from - this wallet. Do not share with anyone. -

-
+ + {% include "core/_api_docs.html" %} + + + {% if wallet.lnurlwithdraw_full %} + + + +

+ This is an LNURL-withdraw QR code for slurping everything + from this wallet. Do not share with anyone. +

+
+ + +

+ It is compatible with balanceCheck and + balanceNotify so your wallet may keep pulling + the funds continuously from here after the first withdraw. +

+ + + + + {% endif %} + + + + +

+ This QR code contains your wallet URL with full access. You + can scan it from your phone to open your wallet from there. +

- -

- It is compatible with balanceCheck and - balanceNotify so your wallet may keep pulling the - funds continuously from here after the first withdraw. -

-
-
-
- - {% endif %} - - - - -

- This QR code contains your wallet URL with full access. You - can scan it from your phone to open your wallet from there. -

- -
-
-
- - - - -
- -
- Update name -
-
-
- - - - -

- This whole wallet will be deleted, the funds will be - UNRECOVERABLE. -

- Delete wallet -
-
-
- - - - {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %} - {% endfor %} {% endif %} + + + + + + + +
+ +
+ Update name +
+
+
+ + + + +

+ This whole wallet will be deleted, the funds will be + UNRECOVERABLE. +

+ Delete wallet +
+
+
+ + + + {% endif %} {% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD = + ADS.split(';') %} + + {% endfor %} {% endif %} +
- - - {% raw %} - - -

- {{receive.lnurl.domain}} is requesting an invoice: -

- {% endraw %} {% if LNBITS_DENOMINATION != 'sats' %} - - {% else %} - - - {% endif %} - - - {% raw %} -
- - - Withdraw from {{receive.lnurl.domain}} - - Create invoice - - Cancel -
- -
-
- - -
- Copy invoice - Close -
-
- {% endraw %} -
- - - -
-
- {% raw %} {{ parseFloat(String(parse.invoice.fsat).replaceAll(",", "")) - / 100 }} {% endraw %} {{LNBITS_DENOMINATION}} {% raw %} -
-
- {{ parse.invoice.fsat }}{% endraw %} {{LNBITS_DENOMINATION}} {% raw %} -
- -

- Description: {{ parse.invoice.description }}
- Expire date: {{ parse.invoice.expireDate }}
- Hash: {{ parse.invoice.hash }} -

- {% endraw %} -
- Pay - Cancel -
-
- Not enough funds! - Cancel -
-
-
- {% raw %} - -

- Authenticate with {{ parse.lnurlauth.domain }}? + + {% raw %} + + +

+ {{receive.lnurl.domain}} is requesting an invoice:

- -

- For every website and for every LNbits wallet, a new keypair will be - deterministically generated so your identity can't be tied to your - LNbits wallet or linked across websites. No other data will be shared - with {{ parse.lnurlauth.domain }}. -

-

Your public key for {{ parse.lnurlauth.domain }} is:

-

- {{ parse.lnurlauth.pubkey }} -

-
- Login - Cancel -
-
- {% endraw %} -
-
- {% raw %} - -

- {{ parse.lnurlpay.domain }} is requesting {{ - parse.lnurlpay.maxSendable | msatoshiFormat }} {{LNBITS_DENOMINATION}} - -
- and a {{parse.lnurlpay.commentAllowed}}-char comment -
-

-

- {{ parse.lnurlpay.targetUser || parse.lnurlpay.domain }} is - requesting
- between {{ parse.lnurlpay.minSendable | msatoshiFormat }} and - {{ parse.lnurlpay.maxSendable | msatoshiFormat }} - {% endraw %} {{LNBITS_DENOMINATION}} {% raw %} - -
- and a {{parse.lnurlpay.commentAllowed}}-char comment -
-

- -
-

- {{ parse.lnurlpay.description }} -

-

- -

-
-
-
- {% endraw %} - - {% raw %} -
-
- -
-
-
- Send {{LNBITS_DENOMINATION}} - Cancel -
-
- {% endraw %} -
-
- + {% endraw %} {% if LNBITS_DENOMINATION != 'sats' %} - -
+ v-model.number="receive.data.amount" + label="Amount ({{LNBITS_DENOMINATION}}) *" + mask="#.##" + fill-mask="0" + reverse-fill-mask + :min="receive.minMax[0]" + :max="receive.minMax[1]" + :readonly="receive.lnurl && receive.lnurl.fixed" + > + {% else %} + + + {% endif %} + + + {% raw %} +
Read + + Withdraw from {{receive.lnurl.domain}} + + Create invoice + + Cancel +
+ + + + + +
+ Copy invoice + Close +
+
+ {% endraw %} + + + + +
+
+ {% raw %} {{ parseFloat(String(parse.invoice.fsat).replaceAll(",", + "")) / 100 }} {% endraw %} {{LNBITS_DENOMINATION}} {% raw %} +
+
+ {{ parse.invoice.fsat }}{% endraw %} {{LNBITS_DENOMINATION}} {% raw %} +
+ +

+ Description: {{ parse.invoice.description }}
+ Expire date: {{ parse.invoice.expireDate }}
+ Hash: {{ parse.invoice.hash }} +

+ {% endraw %} +
+ Pay + Cancel +
+
+ Not enough funds! Cancel
- +
+
+ {% raw %} + +

+ Authenticate with {{ parse.lnurlauth.domain }}? +

+ +

+ For every website and for every LNbits wallet, a new keypair will be + deterministically generated so your identity can't be tied to your + LNbits wallet or linked across websites. No other data will be + shared with {{ parse.lnurlauth.domain }}. +

+

Your public key for {{ parse.lnurlauth.domain }} is:

+

+ {{ parse.lnurlauth.pubkey }} +

+
+ Login + Cancel +
+
+ {% endraw %} +
+
+ {% raw %} + +

+ {{ parse.lnurlpay.domain }} is requesting {{ + parse.lnurlpay.maxSendable | msatoshiFormat }} + {{LNBITS_DENOMINATION}} + +
+ and a {{parse.lnurlpay.commentAllowed}}-char comment +
+

+

+ {{ parse.lnurlpay.targetUser || parse.lnurlpay.domain }} is + requesting
+ between {{ parse.lnurlpay.minSendable | msatoshiFormat }} and + {{ parse.lnurlpay.maxSendable | msatoshiFormat }} + {% endraw %} {{LNBITS_DENOMINATION}} {% raw %} + +
+ and a {{parse.lnurlpay.commentAllowed}}-char comment +
+

+ +
+

+ {{ parse.lnurlpay.description }} +

+

+ +

+
+
+
+ {% endraw %} + + {% raw %} +
+
+ +
+
+
+ Send {{LNBITS_DENOMINATION}} + Cancel +
+
+ {% endraw %} +
- - - -
- - Cancel - + + + +
+ Read + Cancel +
+
+
+ + + +
+ + Cancel + +
-
-
-
+ + - - -
- -
-
- Cancel -
-
-
+ + +
+ +
+
+ Cancel +
+
+
- - - - - - - - - + + + + + + + - - - - + + + + + - - -{% if service_fee > 0 %} -
- - -
Warning
-

- Login functionality to be released in v0.2, for now, - make sure you bookmark this page for future access to your - wallet! -

-

- This service is in BETA, and we hold no responsibility for people losing - access to funds. To encourage you to run your own LNbits installation, any - balance on {% raw %}{{ disclaimerDialog.location.host }}{% endraw %} will - incur a charge of {{ service_fee }}% service fee per - week. -

-
- Copy wallet URL - I understand -
-
-
-{% endif %} {% endblock %} + + + {% if service_fee > 0 %} +
+ + +
Warning
+

+ Login functionality to be released in v0.2, for now, + make sure you bookmark this page for future access to your + wallet! +

+

+ This service is in BETA, and we hold no responsibility for people losing + access to funds. To encourage you to run your own LNbits installation, + any balance on {% raw %}{{ disclaimerDialog.location.host }}{% endraw %} + will incur a charge of + {{ service_fee }}% service fee per week. +

+
+ Copy wallet URL + I understand +
+
+
+ {% endif %} {% endblock %} +
diff --git a/lnbits/static/images/templatead.png b/lnbits/static/images/templatead.png new file mode 100644 index 0000000000000000000000000000000000000000..c369b91aba5064becfbb4bc0dd41e293849d8729 GIT binary patch literal 7911 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 Z4Da|??67|Q4m1>vxJkkIF literal 0 HcmV?d00001 From 9b95a7743d722145c85cc680897d4c6d80aee168 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 28 Feb 2022 10:08:50 +0000 Subject: [PATCH 040/116] fix user keyword to "user" --- lnbits/extensions/jukebox/crud.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/jukebox/crud.py b/lnbits/extensions/jukebox/crud.py index 14dc4760..caaac7e5 100644 --- a/lnbits/extensions/jukebox/crud.py +++ b/lnbits/extensions/jukebox/crud.py @@ -12,7 +12,7 @@ async def create_jukebox( juke_id = urlsafe_short_hash() result = await db.execute( """ - INSERT INTO jukebox.jukebox (id, user, title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price, profit) + INSERT INTO jukebox.jukebox (id, "user", title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price, profit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( @@ -41,6 +41,7 @@ async def update_jukebox( q = ", ".join([f"{field[0]} = ?" for field in data]) items = [f"{field[1]}" for field in data] items.append(juke_id) + q = q.replace("user", '"user"', 1) # hack to make user be "user"! await db.execute(f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (items)) row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,)) return Jukebox(**row) if row else None @@ -57,11 +58,11 @@ async def get_jukebox_by_user(user: str) -> Optional[Jukebox]: async def get_jukeboxs(user: str) -> List[Jukebox]: - rows = await db.fetchall("SELECT * FROM jukebox.jukebox WHERE user = ?", (user,)) + rows = await db.fetchall('SELECT * FROM jukebox.jukebox WHERE "user" = ?', (user,)) for row in rows: if row.sp_playlists == None: await delete_jukebox(row.id) - rows = await db.fetchall("SELECT * FROM jukebox.jukebox WHERE user = ?", (user,)) + rows = await db.fetchall('SELECT * FROM jukebox.jukebox WHERE "user" = ?', (user,)) return [Jukebox(**row) for row in rows] From 6c0aa8fa8d9eb4690b7fc4722a3cc36a5334035a Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Mon, 28 Feb 2022 10:09:40 +0000 Subject: [PATCH 041/116] info/error if no music is playing yet on jukebox --- lnbits/extensions/jukebox/views_api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/jukebox/views_api.py b/lnbits/extensions/jukebox/views_api.py index 421ebf3a..3ba8cbf2 100644 --- a/lnbits/extensions/jukebox/views_api.py +++ b/lnbits/extensions/jukebox/views_api.py @@ -75,7 +75,6 @@ async def api_check_credentials_check( juke_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key) ): jukebox = await get_jukebox(juke_id) - return jukebox @@ -442,7 +441,7 @@ async def api_get_jukebox_currently( token = await api_get_token(juke_id) if token == False: raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="INvoice not paid" + status_code=HTTPStatus.FORBIDDEN, detail="Invoice not paid" ) elif retry: raise HTTPException( @@ -456,5 +455,5 @@ async def api_get_jukebox_currently( ) except: raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Something went wrong" + status_code=HTTPStatus.NOT_FOUND, detail="Something went wrong, or no song is playing yet" ) From ac61cd5427db67eb1adddc90c4473a9bd59614d6 Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Tue, 1 Mar 2022 19:07:07 +0000 Subject: [PATCH 042/116] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b88640ad..21eff295 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ LNbits # LNbits v0.3 BETA, free and open-source lightning-network wallet/accounts system (Join us on [https://t.me/lnbits](https://t.me/lnbits)) +(LNbits is beta, for responsible disclosure of any concerns please contact lnbits@pm.me) Use [lnbits.com](https://lnbits.com), or run your own LNbits server! From d73c3950b9d382aff3a39e57c1a51b5f038ef09b Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Tue, 1 Mar 2022 19:07:21 +0000 Subject: [PATCH 043/116] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 21eff295..020f617c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ LNbits # LNbits v0.3 BETA, free and open-source lightning-network wallet/accounts system (Join us on [https://t.me/lnbits](https://t.me/lnbits)) + (LNbits is beta, for responsible disclosure of any concerns please contact lnbits@pm.me) Use [lnbits.com](https://lnbits.com), or run your own LNbits server! From 251e6a823355261bcfec643d125c5dd28081f016 Mon Sep 17 00:00:00 2001 From: Graham Krizek Date: Tue, 1 Mar 2022 13:58:54 -0600 Subject: [PATCH 044/116] Add a fallback to LND_REST_CERT to use the system chain if its not present (#539) --- lnbits/wallets/lndrest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py index 1c2a86a0..aa9b7b0f 100644 --- a/lnbits/wallets/lndrest.py +++ b/lnbits/wallets/lndrest.py @@ -43,7 +43,7 @@ class LndRestWallet(Wallet): self.macaroon = load_macaroon(macaroon) self.auth = {"Grpc-Metadata-macaroon": self.macaroon} - self.cert = getenv("LND_REST_CERT") + self.cert = getenv("LND_REST_CERT", True) async def status(self) -> StatusResponse: try: From baf55afc2867927053743760c3e952aff45c8bcc Mon Sep 17 00:00:00 2001 From: Joseph Pentland Date: Tue, 1 Mar 2022 21:11:39 +0100 Subject: [PATCH 045/116] api: Fix conversion from fiat to satoshis (#516) Conversion to satoshis incorrectly used data.to as fiat currency rather than data._from. --- lnbits/core/views/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index d607e149..4ece3288 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -560,6 +560,6 @@ async def api_fiat_as_sats(data: ConversionData): return output else: output[data.from_.upper()] = data.amount - output["sats"] = await fiat_amount_as_satoshis(data.amount, data.to) + output["sats"] = await fiat_amount_as_satoshis(data.amount, data._from) output["BTC"] = output["sats"] / 100000000 return output From 8fb70041d2e2c96c5abc8b93f0d80a814047ff51 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 3 Mar 2022 21:35:58 +0000 Subject: [PATCH 046/116] fix flatrate per word toggle --- lnbits/extensions/lnticket/templates/lnticket/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/lnticket/templates/lnticket/index.html b/lnbits/extensions/lnticket/templates/lnticket/index.html index 1607925b..91536161 100644 --- a/lnbits/extensions/lnticket/templates/lnticket/index.html +++ b/lnbits/extensions/lnticket/templates/lnticket/index.html @@ -421,12 +421,13 @@ }, updateformDialog: function (formId) { var link = _.findWhere(this.forms, {id: formId}) + console.log("LINK", link) this.formDialog.data.id = link.id this.formDialog.data.wallet = link.wallet this.formDialog.data.name = link.name this.formDialog.data.description = link.description - this.formDialog.data.flatrate = link.flatrate + this.formDialog.data.flatrate = Boolean(link.flatrate) this.formDialog.data.amount = link.amount this.formDialog.show = true }, From 2399b058553b58fc4e7dca80bb95b5989d0e3e8f Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Thu, 3 Mar 2022 23:05:34 +0000 Subject: [PATCH 047/116] open ticket in dialog for large text --- .../lnticket/templates/lnticket/index.html | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/lnticket/templates/lnticket/index.html b/lnbits/extensions/lnticket/templates/lnticket/index.html index 91536161..6572d98a 100644 --- a/lnbits/extensions/lnticket/templates/lnticket/index.html +++ b/lnbits/extensions/lnticket/templates/lnticket/index.html @@ -117,9 +117,10 @@ {% raw %} @@ -136,9 +137,19 @@ :href="'mailto:' + props.row.email" > + + Click to show ticket + - {{ col.value }} + {{ col.label == "Ticket" ? col.value.length > 20 ? `${col.value.substring(0, 20)}...` : col.value : col.value }} @@ -249,6 +260,29 @@
+ + + + {% raw %} + +

+ {{this.ticketDialog.data.name}} sent a ticket +

+
+ {{this.ticketDialog.data.email}} +
+ {{this.ticketDialog.data.date}} +
+ + +

{{this.ticketDialog.data.content}}

+
+ {% endraw %} + + + +
+
{% endblock %} {% block scripts %} {{ window_vars(user) }} +{% endblock %} \ No newline at end of file diff --git a/lnbits/extensions/withdraw/templates/withdraw/index.html b/lnbits/extensions/withdraw/templates/withdraw/index.html index 4cffbc38..de145855 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/index.html +++ b/lnbits/extensions/withdraw/templates/withdraw/index.html @@ -1,40 +1,28 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block scripts %} {{ window_vars(user) }} +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block scripts %} {{ window_vars(user) }} {% endblock %} {% block page %}
-
- - - Quick vouchers - Advanced withdraw link(s) - - +
+ + + Quick vouchers + Advanced withdraw link(s) + + - - -
-
-
Withdraw links
-
-
- Export to CSV -
-
- - {% raw %} -