From dbe5ff22b896228c1dabe51fbf4e3286d7c9b81b Mon Sep 17 00:00:00 2001 From: Black Coffee Date: Thu, 28 Jul 2022 16:02:51 +0100 Subject: [PATCH 01/42] Ran prettier --- .../lnurlpayout/templates/lnurlpayout/_api_docs.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lnbits/extensions/lnurlpayout/templates/lnurlpayout/_api_docs.html b/lnbits/extensions/lnurlpayout/templates/lnurlpayout/_api_docs.html index 4f921bb57..afe24c423 100644 --- a/lnbits/extensions/lnurlpayout/templates/lnurlpayout/_api_docs.html +++ b/lnbits/extensions/lnurlpayout/templates/lnurlpayout/_api_docs.html @@ -4,12 +4,7 @@ label="API info" :content-inset-level="0.5" > - + @@ -38,7 +33,6 @@ expand-separator label="Create a lnurlpayout" > - Date: Wed, 23 Nov 2022 21:47:26 +0000 Subject: [PATCH 02/42] Adds universal websocket manager any extension can use Connect to the `ws:///api/v1/ws/{item_id}` endpoint POST data to the websocket with `https:///api/v1/ws/{item_id}` --- lnbits/core/views/api.py | 46 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 983d5a261..8f7c11a2e 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -12,7 +12,7 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse import async_timeout import httpx import pyqrcode -from fastapi import Depends, Header, Query, Request +from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect from fastapi.exceptions import HTTPException from fastapi.params import Body from loguru import logger @@ -697,3 +697,47 @@ async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): "delta_msats": delta, "timestamp": int(time.time()), } + + +##################UNIVERSAL WEBSOCKET MANAGER######################## + +class websocketConnectionManager: + def __init__(self): + self.active_connections: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket, item_id: str): + await websocket.accept() + websocket.id = item_id + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_personal_message(self, message: str, item_id: str): + for connection in self.active_connections: + if connection.id == item_id: + await connection.send_text(message) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + +manager = websocketConnectionManager() + +@core_app.websocket("/api/v1/ws/{item_id}") +async def websocket_endpoint(websocket: WebSocket, item_id: str): + await manager.connect(websocket, item_id) + try: + while True: + data = await websocket.receive_text() + except WebSocketDisconnect: + manager.disconnect(websocket) + +@core_app.post("/api/v1/ws/{item_id}") +async def websocket_endpoint(item_id: str, data: str): + await updater(item_id, data) + +async def updater(item_id, data): + return await manager.send_personal_message( + f"{data}", item_id + ) \ No newline at end of file From 51ca515d2631ebe9008b74876cd69552296c286b Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 23 Nov 2022 21:51:32 +0000 Subject: [PATCH 03/42] renamed for clarity --- lnbits/core/views/api.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 8f7c11a2e..41adf1a76 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -713,15 +713,11 @@ class websocketConnectionManager: def disconnect(self, websocket: WebSocket): self.active_connections.remove(websocket) - async def send_personal_message(self, message: str, item_id: str): + async def send_data(self, message: str, item_id: str): for connection in self.active_connections: if connection.id == item_id: await connection.send_text(message) - async def broadcast(self, message: str): - for connection in self.active_connections: - await connection.send_text(message) - manager = websocketConnectionManager() @core_app.websocket("/api/v1/ws/{item_id}") @@ -738,6 +734,6 @@ async def websocket_endpoint(item_id: str, data: str): await updater(item_id, data) async def updater(item_id, data): - return await manager.send_personal_message( + return await manager.send_data( f"{data}", item_id ) \ No newline at end of file From 4b707b5a30da4f81cbfae103859296eae7006746 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 23 Nov 2022 22:22:33 +0000 Subject: [PATCH 04/42] updated function names --- lnbits/core/views/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 41adf1a76..60a266511 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -721,7 +721,7 @@ class websocketConnectionManager: manager = websocketConnectionManager() @core_app.websocket("/api/v1/ws/{item_id}") -async def websocket_endpoint(websocket: WebSocket, item_id: str): +async def websocket_connect(websocket: WebSocket, item_id: str): await manager.connect(websocket, item_id) try: while True: @@ -730,7 +730,7 @@ async def websocket_endpoint(websocket: WebSocket, item_id: str): manager.disconnect(websocket) @core_app.post("/api/v1/ws/{item_id}") -async def websocket_endpoint(item_id: str, data: str): +async def websocket_update(item_id: str, data: str): await updater(item_id, data) async def updater(item_id, data): From 2f08255e9289a601e73ee10c24a603f7d24f43a5 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 23 Nov 2022 22:27:09 +0000 Subject: [PATCH 05/42] added get option --- lnbits/core/views/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 60a266511..a35b55ada 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -733,6 +733,10 @@ async def websocket_connect(websocket: WebSocket, item_id: str): async def websocket_update(item_id: str, data: str): await updater(item_id, data) +@core_app.get("/api/v1/ws/{item_id}/{data}") +async def websocket_update(item_id: str, data: str): + await updater(item_id, data) + async def updater(item_id, data): return await manager.send_data( f"{data}", item_id From 152991fbec6b78b1d9949119419cc00d199f3068 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 23 Nov 2022 22:31:11 +0000 Subject: [PATCH 06/42] added try for return --- lnbits/core/views/api.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index a35b55ada..b7bc7084b 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -731,11 +731,19 @@ async def websocket_connect(websocket: WebSocket, item_id: str): @core_app.post("/api/v1/ws/{item_id}") async def websocket_update(item_id: str, data: str): - await updater(item_id, data) + try: + await updater(item_id, data) + return {"sent": True, "data": data} + except: + return {"sent": False, "data": data} @core_app.get("/api/v1/ws/{item_id}/{data}") async def websocket_update(item_id: str, data: str): - await updater(item_id, data) + try: + await updater(item_id, data) + return {"sent": True, "data": data} + except: + return {"sent": False, "data": data} async def updater(item_id, data): return await manager.send_data( From cea4f9350c574a0b274ec967b3f7f493cd8c6374 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 23 Nov 2022 22:42:32 +0000 Subject: [PATCH 07/42] black --- lnbits/core/views/api.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index b7bc7084b..2cc7aacbf 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -701,6 +701,7 @@ async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): ##################UNIVERSAL WEBSOCKET MANAGER######################## + class websocketConnectionManager: def __init__(self): self.active_connections: List[WebSocket] = [] @@ -718,8 +719,10 @@ class websocketConnectionManager: if connection.id == item_id: await connection.send_text(message) + manager = websocketConnectionManager() + @core_app.websocket("/api/v1/ws/{item_id}") async def websocket_connect(websocket: WebSocket, item_id: str): await manager.connect(websocket, item_id) @@ -729,6 +732,7 @@ async def websocket_connect(websocket: WebSocket, item_id: str): except WebSocketDisconnect: manager.disconnect(websocket) + @core_app.post("/api/v1/ws/{item_id}") async def websocket_update(item_id: str, data: str): try: @@ -737,6 +741,7 @@ async def websocket_update(item_id: str, data: str): except: return {"sent": False, "data": data} + @core_app.get("/api/v1/ws/{item_id}/{data}") async def websocket_update(item_id: str, data: str): try: @@ -745,7 +750,6 @@ async def websocket_update(item_id: str, data: str): except: return {"sent": False, "data": data} + async def updater(item_id, data): - return await manager.send_data( - f"{data}", item_id - ) \ No newline at end of file + return await manager.send_data(f"{data}", item_id) From fde128e96116797016b90cd01cbda12ff90cad1e Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 23 Nov 2022 23:35:02 +0000 Subject: [PATCH 08/42] Better naming --- lnbits/core/views/api.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 2cc7aacbf..e65206593 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -720,23 +720,23 @@ class websocketConnectionManager: await connection.send_text(message) -manager = websocketConnectionManager() +websocketManager = websocketConnectionManager() @core_app.websocket("/api/v1/ws/{item_id}") async def websocket_connect(websocket: WebSocket, item_id: str): - await manager.connect(websocket, item_id) + await websocketManager.connect(websocket, item_id) try: while True: data = await websocket.receive_text() except WebSocketDisconnect: - manager.disconnect(websocket) + websocketManager.disconnect(websocket) @core_app.post("/api/v1/ws/{item_id}") async def websocket_update(item_id: str, data: str): try: - await updater(item_id, data) + await websocketUpdater(item_id, data) return {"sent": True, "data": data} except: return {"sent": False, "data": data} @@ -745,11 +745,11 @@ async def websocket_update(item_id: str, data: str): @core_app.get("/api/v1/ws/{item_id}/{data}") async def websocket_update(item_id: str, data: str): try: - await updater(item_id, data) + await websocketUpdater(item_id, data) return {"sent": True, "data": data} except: return {"sent": False, "data": data} -async def updater(item_id, data): - return await manager.send_data(f"{data}", item_id) +async def websocketUpdater(item_id, data): + return await websocketManager.send_data(f"{data}", item_id) From f876f0659f6387f57597a17484c9b11126515490 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 24 Nov 2022 00:21:39 +0000 Subject: [PATCH 09/42] Moved into correct files, and added payment example --- lnbits/core/services.py | 24 +++++++++++++++++++++++- lnbits/core/tasks.py | 4 +++- lnbits/core/views/api.py | 27 ++------------------------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 5d993b4c5..72a0c84b7 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -6,7 +6,7 @@ from typing import Dict, Optional, Tuple from urllib.parse import parse_qs, urlparse import httpx -from fastapi import Depends +from fastapi import Depends, WebSocket, WebSocketDisconnect from lnurl import LnurlErrorResponse from lnurl import decode as decode_lnurl # type: ignore from loguru import logger @@ -382,3 +382,25 @@ async def check_transaction_status( # WARN: this same value must be used for balance check and passed to WALLET.pay_invoice(), it may cause a vulnerability if the values differ def fee_reserve(amount_msat: int) -> int: return max(int(RESERVE_FEE_MIN), int(amount_msat * RESERVE_FEE_PERCENT / 100.0)) + +class websocketConnectionManager: + def __init__(self): + self.active_connections: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket, item_id: str): + await websocket.accept() + websocket.id = item_id + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_data(self, message: str, item_id: str): + for connection in self.active_connections: + if connection.id == item_id: + await connection.send_text(message) + +websocketManager = websocketConnectionManager() + +async def websocketUpdater(item_id, data): + return await websocketManager.send_data(f"{data}", item_id) \ No newline at end of file diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py index b57e26257..01d04a606 100644 --- a/lnbits/core/tasks.py +++ b/lnbits/core/tasks.py @@ -10,6 +10,7 @@ from lnbits.tasks import SseListenersDict, register_invoice_listener from . import db from .crud import get_balance_notify from .models import Payment +from .services import websocketUpdater api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict( "api_invoice_listeners" @@ -38,6 +39,7 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue): logger.trace("received invoice paid event") # send information to sse channel await dispatch_api_invoice_listeners(payment) + await websocketUpdater(payment.wallet_id, payment) # dispatch webhook if payment.webhook and not payment.webhook_status: @@ -88,4 +90,4 @@ async def mark_webhook_sent(payment: Payment, status: int) -> None: WHERE hash = ? """, (status, payment.payment_hash), - ) + ) \ No newline at end of file diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index e65206593..3465ef248 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -56,6 +56,8 @@ from ..services import ( create_invoice, pay_invoice, perform_lnurlauth, + websocketManager, + websocketUpdater ) from ..tasks import api_invoice_listeners @@ -702,27 +704,6 @@ async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): ##################UNIVERSAL WEBSOCKET MANAGER######################## -class websocketConnectionManager: - def __init__(self): - self.active_connections: List[WebSocket] = [] - - async def connect(self, websocket: WebSocket, item_id: str): - await websocket.accept() - websocket.id = item_id - self.active_connections.append(websocket) - - def disconnect(self, websocket: WebSocket): - self.active_connections.remove(websocket) - - async def send_data(self, message: str, item_id: str): - for connection in self.active_connections: - if connection.id == item_id: - await connection.send_text(message) - - -websocketManager = websocketConnectionManager() - - @core_app.websocket("/api/v1/ws/{item_id}") async def websocket_connect(websocket: WebSocket, item_id: str): await websocketManager.connect(websocket, item_id) @@ -749,7 +730,3 @@ async def websocket_update(item_id: str, data: str): return {"sent": True, "data": data} except: return {"sent": False, "data": data} - - -async def websocketUpdater(item_id, data): - return await websocketManager.send_data(f"{data}", item_id) From c2a737ab84ae4260dcd3a7a2cb93e62ade72e9da Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 24 Nov 2022 00:34:46 +0000 Subject: [PATCH 10/42] Black --- lnbits/core/services.py | 5 ++++- lnbits/core/tasks.py | 2 +- lnbits/core/views/api.py | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 72a0c84b7..b03562d91 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -383,6 +383,7 @@ async def check_transaction_status( def fee_reserve(amount_msat: int) -> int: return max(int(RESERVE_FEE_MIN), int(amount_msat * RESERVE_FEE_PERCENT / 100.0)) + class websocketConnectionManager: def __init__(self): self.active_connections: List[WebSocket] = [] @@ -400,7 +401,9 @@ class websocketConnectionManager: if connection.id == item_id: await connection.send_text(message) + websocketManager = websocketConnectionManager() + async def websocketUpdater(item_id, data): - return await websocketManager.send_data(f"{data}", item_id) \ No newline at end of file + return await websocketManager.send_data(f"{data}", item_id) diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py index 01d04a606..734c3f01f 100644 --- a/lnbits/core/tasks.py +++ b/lnbits/core/tasks.py @@ -90,4 +90,4 @@ async def mark_webhook_sent(payment: Payment, status: int) -> None: WHERE hash = ? """, (status, payment.payment_hash), - ) \ No newline at end of file + ) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 3465ef248..9a359c0af 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -56,8 +56,8 @@ from ..services import ( create_invoice, pay_invoice, perform_lnurlauth, - websocketManager, - websocketUpdater + websocketManager, + websocketUpdater, ) from ..tasks import api_invoice_listeners From dce4da96b71eeaa932bce183e436975b6cfb2e1f Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 24 Nov 2022 00:46:39 +0000 Subject: [PATCH 11/42] fixed function name clash --- lnbits/core/views/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 9a359c0af..65d7c03e7 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -715,7 +715,7 @@ async def websocket_connect(websocket: WebSocket, item_id: str): @core_app.post("/api/v1/ws/{item_id}") -async def websocket_update(item_id: str, data: str): +async def websocket_update_post(item_id: str, data: str): try: await websocketUpdater(item_id, data) return {"sent": True, "data": data} @@ -724,7 +724,7 @@ async def websocket_update(item_id: str, data: str): @core_app.get("/api/v1/ws/{item_id}/{data}") -async def websocket_update(item_id: str, data: str): +async def websocket_update_get(item_id: str, data: str): try: await websocketUpdater(item_id, data) return {"sent": True, "data": data} From 3cea3a0ba86f8c79d53690e4ad275bd00e5033de Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 24 Nov 2022 11:18:38 +0000 Subject: [PATCH 12/42] Removed payments websocket example To be prob added back at a later date --- lnbits/core/tasks.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py index 734c3f01f..b57e26257 100644 --- a/lnbits/core/tasks.py +++ b/lnbits/core/tasks.py @@ -10,7 +10,6 @@ from lnbits.tasks import SseListenersDict, register_invoice_listener from . import db from .crud import get_balance_notify from .models import Payment -from .services import websocketUpdater api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict( "api_invoice_listeners" @@ -39,7 +38,6 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue): logger.trace("received invoice paid event") # send information to sse channel await dispatch_api_invoice_listeners(payment) - await websocketUpdater(payment.wallet_id, payment) # dispatch webhook if payment.webhook and not payment.webhook_status: From 746e119046dad05207b3afd6f8c3c90944923b7c Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 28 Nov 2022 13:04:35 +0000 Subject: [PATCH 13/42] added vlads suggestion --- lnbits/core/services.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index b03562d91..285525405 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -2,7 +2,7 @@ import asyncio import json from binascii import unhexlify from io import BytesIO -from typing import Dict, Optional, Tuple +from typing import Dict, Optional, Tuple, List from urllib.parse import parse_qs, urlparse import httpx @@ -384,25 +384,30 @@ def fee_reserve(amount_msat: int) -> int: return max(int(RESERVE_FEE_MIN), int(amount_msat * RESERVE_FEE_PERCENT / 100.0)) -class websocketConnectionManager: +class WebsocketConnectionManager: def __init__(self): self.active_connections: List[WebSocket] = [] + return async def connect(self, websocket: WebSocket, item_id: str): await websocket.accept() - websocket.id = item_id - self.active_connections.append(websocket) + if item_id not in self.active_connections: + websocket.id = item_id + self.active_connections.append(websocket) + return def disconnect(self, websocket: WebSocket): self.active_connections.remove(websocket) + return async def send_data(self, message: str, item_id: str): for connection in self.active_connections: if connection.id == item_id: await connection.send_text(message) + return -websocketManager = websocketConnectionManager() +websocketManager = WebsocketConnectionManager() async def websocketUpdater(item_id, data): From aefd1fad6926e94cdc57854d29731f40ea8d0787 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 28 Nov 2022 13:13:45 +0000 Subject: [PATCH 14/42] isort --- lnbits/core/services.py | 25 ++++++------------- lnbits/core/views/api.py | 47 ++++++++++-------------------------- lnbits/core/views/generic.py | 22 +++++------------ 3 files changed, 27 insertions(+), 67 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 285525405..798680a23 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -2,7 +2,7 @@ import asyncio import json from binascii import unhexlify from io import BytesIO -from typing import Dict, Optional, Tuple, List +from typing import Dict, List, Optional, Tuple from urllib.parse import parse_qs, urlparse import httpx @@ -13,27 +13,18 @@ from loguru import logger from lnbits import bolt11 from lnbits.db import Connection -from lnbits.decorators import ( - WalletTypeInfo, - get_key_type, - require_admin_key, - require_invoice_key, -) +from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key, + require_invoice_key) from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.requestvars import g -from lnbits.settings import FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT, WALLET +from lnbits.settings import (FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT, + WALLET) from lnbits.wallets.base import PaymentResponse, PaymentStatus from . import db -from .crud import ( - check_internal, - create_payment, - delete_wallet_payment, - get_wallet, - get_wallet_payment, - update_payment_details, - update_payment_status, -) +from .crud import (check_internal, create_payment, delete_wallet_payment, + get_wallet, get_wallet_payment, update_payment_details, + update_payment_status) from .models import Payment try: diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index ac56eb5ed..c6c5f9925 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -9,10 +9,10 @@ from io import BytesIO from typing import Dict, List, Optional, Tuple, Union from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse -import async_timeout import httpx import pyqrcode -from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect +from fastapi import (Depends, Header, Query, Request, WebSocket, + WebSocketDisconnect) from fastapi.exceptions import HTTPException from fastapi.params import Body from loguru import logger @@ -21,44 +21,23 @@ from pydantic.fields import Field from sse_starlette.sse import EventSourceResponse, ServerSentEvent from starlette.responses import HTMLResponse, StreamingResponse +import async_timeout from lnbits import bolt11, lnurl from lnbits.core.models import Payment, Wallet -from lnbits.decorators import ( - WalletTypeInfo, - get_key_type, - require_admin_key, - require_invoice_key, -) +from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key, + require_invoice_key) from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET -from lnbits.utils.exchange_rates import ( - currencies, - fiat_amount_as_satoshis, - satoshis_amount_as_fiat, -) +from lnbits.utils.exchange_rates import (currencies, fiat_amount_as_satoshis, + satoshis_amount_as_fiat) from .. import core_app, db -from ..crud import ( - create_payment, - get_payments, - get_standalone_payment, - get_total_balance, - get_wallet, - get_wallet_for_key, - save_balance_check, - update_payment_status, - update_wallet, -) -from ..services import ( - InvoiceFailure, - PaymentFailure, - check_transaction_status, - create_invoice, - pay_invoice, - perform_lnurlauth, - websocketManager, - websocketUpdater, -) +from ..crud import (create_payment, get_payments, get_standalone_payment, + get_total_balance, get_wallet, get_wallet_for_key, + save_balance_check, update_payment_status, update_wallet) +from ..services import (InvoiceFailure, PaymentFailure, + check_transaction_status, create_invoice, pay_invoice, + perform_lnurlauth, websocketManager, websocketUpdater) from ..tasks import api_invoice_listeners diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 31a7b0300..c16c8a410 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -15,24 +15,14 @@ from lnbits.core import db 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_ADMIN_USERS, - LNBITS_ALLOWED_USERS, - LNBITS_CUSTOM_LOGO, - LNBITS_SITE_TITLE, - SERVICE_FEE, -) +from lnbits.settings import (LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS, + LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE, + SERVICE_FEE) from ...helpers import get_valid_extensions -from ..crud import ( - create_account, - create_wallet, - delete_wallet, - get_balance_check, - get_user, - save_balance_notify, - update_user_extension, -) +from ..crud import (create_account, create_wallet, delete_wallet, + get_balance_check, get_user, save_balance_notify, + update_user_extension) from ..services import pay_invoice, redeem_lnurl_withdraw core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) From bb84f6b0e8ea74db4b615ab8ae4da0ef4d2849ab Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 28 Nov 2022 13:24:10 +0000 Subject: [PATCH 15/42] Black --- lnbits/core/services.py | 23 ++++++++++++------ lnbits/core/views/api.py | 45 ++++++++++++++++++++++++++---------- lnbits/core/views/generic.py | 22 +++++++++++++----- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 798680a23..d0f6ebd35 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -13,18 +13,27 @@ from loguru import logger from lnbits import bolt11 from lnbits.db import Connection -from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key, - require_invoice_key) +from lnbits.decorators import ( + WalletTypeInfo, + get_key_type, + require_admin_key, + require_invoice_key, +) from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.requestvars import g -from lnbits.settings import (FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT, - WALLET) +from lnbits.settings import FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT, WALLET from lnbits.wallets.base import PaymentResponse, PaymentStatus from . import db -from .crud import (check_internal, create_payment, delete_wallet_payment, - get_wallet, get_wallet_payment, update_payment_details, - update_payment_status) +from .crud import ( + check_internal, + create_payment, + delete_wallet_payment, + get_wallet, + get_wallet_payment, + update_payment_details, + update_payment_status, +) from .models import Payment try: diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index c6c5f9925..1a7ee8f53 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -11,8 +11,7 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse import httpx import pyqrcode -from fastapi import (Depends, Header, Query, Request, WebSocket, - WebSocketDisconnect) +from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect from fastapi.exceptions import HTTPException from fastapi.params import Body from loguru import logger @@ -24,20 +23,42 @@ from starlette.responses import HTMLResponse, StreamingResponse import async_timeout from lnbits import bolt11, lnurl from lnbits.core.models import Payment, Wallet -from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key, - require_invoice_key) +from lnbits.decorators import ( + WalletTypeInfo, + get_key_type, + require_admin_key, + require_invoice_key, +) from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET -from lnbits.utils.exchange_rates import (currencies, fiat_amount_as_satoshis, - satoshis_amount_as_fiat) +from lnbits.utils.exchange_rates import ( + currencies, + fiat_amount_as_satoshis, + satoshis_amount_as_fiat, +) from .. import core_app, db -from ..crud import (create_payment, get_payments, get_standalone_payment, - get_total_balance, get_wallet, get_wallet_for_key, - save_balance_check, update_payment_status, update_wallet) -from ..services import (InvoiceFailure, PaymentFailure, - check_transaction_status, create_invoice, pay_invoice, - perform_lnurlauth, websocketManager, websocketUpdater) +from ..crud import ( + create_payment, + get_payments, + get_standalone_payment, + get_total_balance, + get_wallet, + get_wallet_for_key, + save_balance_check, + update_payment_status, + update_wallet, +) +from ..services import ( + InvoiceFailure, + PaymentFailure, + check_transaction_status, + create_invoice, + pay_invoice, + perform_lnurlauth, + websocketManager, + websocketUpdater, +) from ..tasks import api_invoice_listeners diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index c16c8a410..31a7b0300 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -15,14 +15,24 @@ from lnbits.core import db 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_ADMIN_USERS, LNBITS_ALLOWED_USERS, - LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE, - SERVICE_FEE) +from lnbits.settings import ( + LNBITS_ADMIN_USERS, + LNBITS_ALLOWED_USERS, + LNBITS_CUSTOM_LOGO, + LNBITS_SITE_TITLE, + SERVICE_FEE, +) from ...helpers import get_valid_extensions -from ..crud import (create_account, create_wallet, delete_wallet, - get_balance_check, get_user, save_balance_notify, - update_user_extension) +from ..crud import ( + create_account, + create_wallet, + delete_wallet, + get_balance_check, + get_user, + save_balance_notify, + update_user_extension, +) from ..services import pay_invoice, redeem_lnurl_withdraw core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) From 187d709098c4dd6819d682f210b2e501b37ad840 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 28 Nov 2022 13:28:11 +0000 Subject: [PATCH 16/42] isort --- lnbits/core/views/api.py | 45 +++++++++++----------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 1a7ee8f53..c6c5f9925 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -11,7 +11,8 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse import httpx import pyqrcode -from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect +from fastapi import (Depends, Header, Query, Request, WebSocket, + WebSocketDisconnect) from fastapi.exceptions import HTTPException from fastapi.params import Body from loguru import logger @@ -23,42 +24,20 @@ from starlette.responses import HTMLResponse, StreamingResponse import async_timeout from lnbits import bolt11, lnurl from lnbits.core.models import Payment, Wallet -from lnbits.decorators import ( - WalletTypeInfo, - get_key_type, - require_admin_key, - require_invoice_key, -) +from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key, + require_invoice_key) from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET -from lnbits.utils.exchange_rates import ( - currencies, - fiat_amount_as_satoshis, - satoshis_amount_as_fiat, -) +from lnbits.utils.exchange_rates import (currencies, fiat_amount_as_satoshis, + satoshis_amount_as_fiat) from .. import core_app, db -from ..crud import ( - create_payment, - get_payments, - get_standalone_payment, - get_total_balance, - get_wallet, - get_wallet_for_key, - save_balance_check, - update_payment_status, - update_wallet, -) -from ..services import ( - InvoiceFailure, - PaymentFailure, - check_transaction_status, - create_invoice, - pay_invoice, - perform_lnurlauth, - websocketManager, - websocketUpdater, -) +from ..crud import (create_payment, get_payments, get_standalone_payment, + get_total_balance, get_wallet, get_wallet_for_key, + save_balance_check, update_payment_status, update_wallet) +from ..services import (InvoiceFailure, PaymentFailure, + check_transaction_status, create_invoice, pay_invoice, + perform_lnurlauth, websocketManager, websocketUpdater) from ..tasks import api_invoice_listeners From 33631d6375a3d2354d454fe1be8fd2bd861948f9 Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Tue, 29 Nov 2022 11:14:51 +0100 Subject: [PATCH 17/42] tpos identifies itself via user-agent --- lnbits/extensions/tpos/views_api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index fe63a2471..811d21161 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -3,6 +3,7 @@ from http import HTTPStatus import httpx from fastapi import Query from fastapi.params import Depends +from lnbits.settings import LNBITS_COMMIT from lnurl import decode as decode_lnurl from loguru import logger from starlette.exceptions import HTTPException @@ -134,7 +135,8 @@ async def api_tpos_pay_invoice( async with httpx.AsyncClient() as client: try: - r = await client.get(lnurl, follow_redirects=True) + headers = {"user-agent": f"lnbits/tpos commit {LNBITS_COMMIT[:7]}"} + r = await client.get(lnurl, follow_redirects=True, headers=headers) if r.is_error: lnurl_response = {"success": False, "detail": "Error loading"} else: @@ -145,6 +147,7 @@ async def api_tpos_pay_invoice( r2 = await client.get( resp["callback"], follow_redirects=True, + headers=headers, params={ "k1": resp["k1"], "pr": payment_request, From 9756e6fad8f762a260fc96027b385f953613cfd3 Mon Sep 17 00:00:00 2001 From: Gene Takavic Date: Tue, 29 Nov 2022 11:28:17 +0100 Subject: [PATCH 18/42] formating --- lnbits/extensions/tpos/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 811d21161..e13dee9b7 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -3,7 +3,6 @@ from http import HTTPStatus import httpx from fastapi import Query from fastapi.params import Depends -from lnbits.settings import LNBITS_COMMIT from lnurl import decode as decode_lnurl from loguru import logger from starlette.exceptions import HTTPException @@ -13,6 +12,7 @@ from lnbits.core.models import Payment from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key +from lnbits.settings import LNBITS_COMMIT from . import tpos_ext from .crud import create_tpos, delete_tpos, get_tpos, get_tposs From 31d9f2e2ee6eb04c1a99cd8f7f54c9ecd607da12 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 29 Nov 2022 11:09:54 +0000 Subject: [PATCH 19/42] Removed returns and reverted socket check so multiple clients can join --- lnbits/core/services.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lnbits/core/services.py b/lnbits/core/services.py index d0f6ebd35..8a88411a1 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -387,24 +387,19 @@ def fee_reserve(amount_msat: int) -> int: class WebsocketConnectionManager: def __init__(self): self.active_connections: List[WebSocket] = [] - return async def connect(self, websocket: WebSocket, item_id: str): await websocket.accept() - if item_id not in self.active_connections: - websocket.id = item_id - self.active_connections.append(websocket) - return + websocket.id = item_id + self.active_connections.append(websocket) def disconnect(self, websocket: WebSocket): self.active_connections.remove(websocket) - return async def send_data(self, message: str, item_id: str): for connection in self.active_connections: if connection.id == item_id: await connection.send_text(message) - return websocketManager = WebsocketConnectionManager() From 9a9733c1ce9d3e79b375c4319072384ceb5c667c Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 29 Nov 2022 11:23:34 +0000 Subject: [PATCH 20/42] Auto stash before merge of "universalwebsocket" and "origin/universalwebsocket" --- lnbits/core/views/api.py | 47 ++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 835967789..b7d83565a 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -11,10 +11,7 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse import httpx import pyqrcode - -from fastapi import (Depends, Header, Query, Request, WebSocket, - WebSocketDisconnect) - +from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect from fastapi.exceptions import HTTPException from fastapi.params import Body from loguru import logger @@ -26,20 +23,42 @@ from starlette.responses import HTMLResponse, StreamingResponse import async_timeout from lnbits import bolt11, lnurl from lnbits.core.models import Payment, Wallet -from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key, - require_invoice_key) +from lnbits.decorators import ( + WalletTypeInfo, + get_key_type, + require_admin_key, + require_invoice_key, +) from lnbits.helpers import url_for, urlsafe_short_hash from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET -from lnbits.utils.exchange_rates import (currencies, fiat_amount_as_satoshis, - satoshis_amount_as_fiat) +from lnbits.utils.exchange_rates import ( + currencies, + fiat_amount_as_satoshis, + satoshis_amount_as_fiat, +) from .. import core_app, db -from ..crud import (create_payment, get_payments, get_standalone_payment, - get_total_balance, get_wallet, get_wallet_for_key, - save_balance_check, update_payment_status, update_wallet) -from ..services import (InvoiceFailure, PaymentFailure, - check_transaction_status, create_invoice, pay_invoice, - perform_lnurlauth, websocketManager, websocketUpdater) +from ..crud import ( + create_payment, + get_payments, + get_standalone_payment, + get_total_balance, + get_wallet, + get_wallet_for_key, + save_balance_check, + update_payment_status, + update_wallet, +) +from ..services import ( + InvoiceFailure, + PaymentFailure, + check_transaction_status, + create_invoice, + pay_invoice, + perform_lnurlauth, + websocketManager, + websocketUpdater, +) from ..tasks import api_invoice_listeners From 20b06e4922fca248b17229281d50cd48f2f3e970 Mon Sep 17 00:00:00 2001 From: Black Coffee Date: Wed, 30 Nov 2022 14:14:31 +0000 Subject: [PATCH 21/42] Corrected casing of LNbits (not LNBits) throughout repo --- docs/guide/installation.md | 4 ++-- lnbits/extensions/boltcards/README.md | 6 +++--- lnbits/extensions/invoices/templates/invoices/index.html | 2 +- lnbits/extensions/jukebox/README.md | 4 ++-- lnbits/extensions/livestream/README.md | 4 ++-- lnbits/extensions/lndhub/README.md | 2 +- lnbits/extensions/offlineshop/README.md | 4 ++-- lnbits/extensions/satspay/README.md | 2 +- lnbits/extensions/splitpayments/README.md | 2 +- lnbits/extensions/subdomains/README.md | 2 +- lnbits/extensions/watchonly/README.md | 2 +- lnbits/extensions/withdraw/README.md | 6 +++--- lnbits/server.py | 2 +- tests/extensions/invoices/conftest.py | 2 +- tests/extensions/invoices/test_invoices_api.py | 2 +- 15 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 072c4d91c..aef26b4a3 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -106,7 +106,7 @@ docker run --detach --publish 5000:5000 --name lnbits-legend --volume ${PWD}/.en ## Option 5: Fly.io -Fly.io is a docker container hosting platform that has a generous free tier. You can host LNBits for free on Fly.io for personal use. +Fly.io is a docker container hosting platform that has a generous free tier. You can host LNbits for free on Fly.io for personal use. First, sign up for an account at [Fly.io](https://fly.io) (no credit card required). @@ -169,7 +169,7 @@ kill_timeout = 30 ... ``` -Next, create a volume to store the sqlite database for LNBits. Be sure to choose the same region for the volume that you chose earlier. +Next, create a volume to store the sqlite database for LNbits. Be sure to choose the same region for the volume that you chose earlier. ``` fly volumes create lnbits_data --size 1 diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md index f9c594093..b86de62cb 100644 --- a/lnbits/extensions/boltcards/README.md +++ b/lnbits/extensions/boltcards/README.md @@ -6,7 +6,7 @@ This extension allows you to link your Bolt Card (or other compatible NXP NTAG d **Disclaimer:** ***Use this only if you either know what you are doing or are a reckless lightning pioneer. Only you are responsible for all your sats, cards and other devices. Always backup all your card keys!*** -***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNBits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [bolt-nfc-android-app](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp). +***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNbits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [bolt-nfc-android-app](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp). ## About the keys @@ -25,12 +25,12 @@ So far, regarding the keys, the app can only write a new key set on an empty car - Read the card with the app. Note UID so you can fill it in the extension later. - Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}` - - `{external_id}` should be replaced with the External ID found in the LNBits dialog. + - `{external_id}` should be replaced with the External ID found in the LNbits dialog. - Add new card in the extension. - Set a max sats per transaction. Any transaction greater than this amount will be rejected. - Set a max sats per day. After the card spends this amount of sats in a day, additional transactions will be rejected. - - Set a card name. This is just for your reference inside LNBits. + - Set a card name. This is just for your reference inside LNbits. - Set the card UID. This is the unique identifier on your NFC card and is 7 bytes. - If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field. - Advanced Options diff --git a/lnbits/extensions/invoices/templates/invoices/index.html b/lnbits/extensions/invoices/templates/invoices/index.html index e3093e3cb..4ef3b7f1c 100644 --- a/lnbits/extensions/invoices/templates/invoices/index.html +++ b/lnbits/extensions/invoices/templates/invoices/index.html @@ -118,7 +118,7 @@ dense v-model.trim="formDialog.data.company_name" label="Company Name" - placeholder="LNBits Labs" + placeholder="LNbits Labs" > -5. Open the LNBits subdomains extension and register your domain +5. Open the LNbits subdomains extension and register your domain 6. Click on the button in the table to open the public form that was generated for your domain - Extension also supports webhooks so you can get notified when someone buys a new subdomain\ diff --git a/lnbits/extensions/watchonly/README.md b/lnbits/extensions/watchonly/README.md index 45abdb936..d154d8a34 100644 --- a/lnbits/extensions/watchonly/README.md +++ b/lnbits/extensions/watchonly/README.md @@ -4,7 +4,7 @@ Monitor an extended public key and generate deterministic fresh public keys with this simple watch only wallet. Invoice payments can also be generated, both through a publically shareable page and API. -You can now use this wallet on the LNBits [SatsPayServer](https://github.com/lnbits/lnbits/blob/master/lnbits/extensions/satspay/README.md) extension +You can now use this wallet on the LNbits [SatsPayServer](https://github.com/lnbits/lnbits/blob/master/lnbits/extensions/satspay/README.md) extension Video demo diff --git a/lnbits/extensions/withdraw/README.md b/lnbits/extensions/withdraw/README.md index 7bf7c232c..fce2c6e5a 100644 --- a/lnbits/extensions/withdraw/README.md +++ b/lnbits/extensions/withdraw/README.md @@ -14,7 +14,7 @@ LNURL withdraw is a **very powerful tool** and should not have his use limited t #### Quick Vouchers -LNBits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes that you can print and distribute as rewards, onboarding people into Lightning Network, gifts, etc... +LNbits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes that you can print and distribute as rewards, onboarding people into Lightning Network, gifts, etc... 1. Create Quick Vouchers\ ![quick vouchers](https://i.imgur.com/IUfwdQz.jpg) @@ -37,12 +37,12 @@ LNBits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes t - set a title for the LNURLw (it will show up in users wallet) - define the minimum and maximum a user can withdraw, if you want a fixed amount set them both to an equal value - set how many times can the LNURLw be scanned, if it's a one time use or it can be scanned 100 times - - LNBits has the "_Time between withdraws_" setting, you can define how long the LNURLw will be unavailable between scans + - LNbits has the "_Time between withdraws_" setting, you can define how long the LNURLw will be unavailable between scans - you can set the time in _seconds, minutes or hours_ - the "_Use unique withdraw QR..._" reduces the chance of your LNURL withdraw being exploited and depleted by one person, by generating a new QR code every time it's scanned 2. Print, share or display your LNURLw link or it's QR code\ ![lnurlw created](https://i.imgur.com/X00twiX.jpg) -**LNBits bonus:** If a user doesn't have a Lightning Network wallet and scans the LNURLw QR code with their smartphone camera, or a QR scanner app, they can follow the link provided to claim their satoshis and get an instant LNBits wallet! +**LNbits bonus:** If a user doesn't have a Lightning Network wallet and scans the LNURLw QR code with their smartphone camera, or a QR scanner app, they can follow the link provided to claim their satoshis and get an instant LNbits wallet! ![](https://i.imgur.com/2zZ7mi8.jpg) diff --git a/lnbits/server.py b/lnbits/server.py index 7aaaa964b..7a5c1947b 100644 --- a/lnbits/server.py +++ b/lnbits/server.py @@ -11,7 +11,7 @@ from lnbits.settings import FORWARDED_ALLOW_IPS, HOST, PORT ) ) @click.option("--port", default=PORT, help="Port to listen on") -@click.option("--host", default=HOST, help="Host to run LNBits on") +@click.option("--host", default=HOST, help="Host to run LNbits on") @click.option( "--forwarded-allow-ips", default=FORWARDED_ALLOW_IPS, help="Allowed proxy servers" ) diff --git a/tests/extensions/invoices/conftest.py b/tests/extensions/invoices/conftest.py index 09ac42ecb..00b9c2375 100644 --- a/tests/extensions/invoices/conftest.py +++ b/tests/extensions/invoices/conftest.py @@ -22,7 +22,7 @@ async def accounting_invoice(invoices_wallet): invoice_data = CreateInvoiceData( status="open", currency="USD", - company_name="LNBits, Inc", + company_name="LNbits, Inc", first_name="Ben", last_name="Arc", items=[{"amount": 10.20, "description": "Item costs 10.20"}], diff --git a/tests/extensions/invoices/test_invoices_api.py b/tests/extensions/invoices/test_invoices_api.py index eaadd07b3..ed236a8fd 100644 --- a/tests/extensions/invoices/test_invoices_api.py +++ b/tests/extensions/invoices/test_invoices_api.py @@ -20,7 +20,7 @@ async def test_invoices_api_create_invoice_valid(client, invoices_wallet): query = { "status": "open", "currency": "EUR", - "company_name": "LNBits, Inc.", + "company_name": "LNbits, Inc.", "first_name": "Ben", "last_name": "Arc", "email": "ben@legend.arc", From 4aeb7683e5b178475cb329aa520cade314290223 Mon Sep 17 00:00:00 2001 From: benarc Date: Thu, 1 Dec 2022 13:15:46 +0000 Subject: [PATCH 22/42] Added Response --- 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 b7d83565a..a2a937360 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -11,7 +11,7 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse import httpx import pyqrcode -from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect +from fastapi import Depends, Header, Query, Response, Request, WebSocket, WebSocketDisconnect from fastapi.exceptions import HTTPException from fastapi.params import Body from loguru import logger From a220acb5837f6624c550d8f161c4d61ebd890e8c Mon Sep 17 00:00:00 2001 From: benarc Date: Thu, 1 Dec 2022 14:41:57 +0000 Subject: [PATCH 23/42] Removed id, using param instead --- lnbits/bolt11.py | 6 +++--- lnbits/core/services.py | 12 ++++++------ lnbits/core/views/api.py | 14 +++++++++++--- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lnbits/bolt11.py b/lnbits/bolt11.py index 32b43feb6..08f1f1e59 100644 --- a/lnbits/bolt11.py +++ b/lnbits/bolt11.py @@ -166,7 +166,7 @@ def lnencode(addr, privkey): if addr.amount: amount = Decimal(str(addr.amount)) # We can only send down to millisatoshi. - if amount * 10**12 % 10: + if amount * 10 ** 12 % 10: raise ValueError( "Cannot encode {}: too many decimal places".format(addr.amount) ) @@ -271,7 +271,7 @@ class LnAddr(object): def shorten_amount(amount): """Given an amount in bitcoin, shorten it""" # Convert to pico initially - amount = int(amount * 10**12) + amount = int(amount * 10 ** 12) units = ["p", "n", "u", "m", ""] for unit in units: if amount % 1000 == 0: @@ -290,7 +290,7 @@ def _unshorten_amount(amount: str) -> int: # * `u` (micro): multiply by 0.000001 # * `n` (nano): multiply by 0.000000001 # * `p` (pico): multiply by 0.000000000001 - units = {"p": 10**12, "n": 10**9, "u": 10**6, "m": 10**3} + units = {"p": 10 ** 12, "n": 10 ** 9, "u": 10 ** 6, "m": 10 ** 3} unit = str(amount)[-1] # BOLT #11: diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 8a88411a1..623f78139 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -329,12 +329,12 @@ async def perform_lnurlauth( sign_len = 6 + r_len + s_len signature = BytesIO() - signature.write(0x30.to_bytes(1, "big", signed=False)) + signature.write(0x30 .to_bytes(1, "big", signed=False)) signature.write((sign_len - 2).to_bytes(1, "big", signed=False)) - signature.write(0x02.to_bytes(1, "big", signed=False)) + signature.write(0x02 .to_bytes(1, "big", signed=False)) signature.write(r_len.to_bytes(1, "big", signed=False)) signature.write(r) - signature.write(0x02.to_bytes(1, "big", signed=False)) + signature.write(0x02 .to_bytes(1, "big", signed=False)) signature.write(s_len.to_bytes(1, "big", signed=False)) signature.write(s) @@ -388,9 +388,9 @@ class WebsocketConnectionManager: def __init__(self): self.active_connections: List[WebSocket] = [] - async def connect(self, websocket: WebSocket, item_id: str): + async def connect(self, websocket: WebSocket): await websocket.accept() - websocket.id = item_id + logger.debug(websocket) self.active_connections.append(websocket) def disconnect(self, websocket: WebSocket): @@ -398,7 +398,7 @@ class WebsocketConnectionManager: async def send_data(self, message: str, item_id: str): for connection in self.active_connections: - if connection.id == item_id: + if connection.path_params["item_id"] == item_id: await connection.send_text(message) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index a2a937360..f78219bf1 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -9,9 +9,18 @@ from io import BytesIO from typing import Dict, List, Optional, Tuple, Union from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse +import async_timeout import httpx import pyqrcode -from fastapi import Depends, Header, Query, Response, Request, WebSocket, WebSocketDisconnect +from fastapi import ( + Depends, + Header, + Query, + Request, + Response, + WebSocket, + WebSocketDisconnect, +) from fastapi.exceptions import HTTPException from fastapi.params import Body from loguru import logger @@ -20,7 +29,6 @@ from pydantic.fields import Field from sse_starlette.sse import EventSourceResponse, ServerSentEvent from starlette.responses import HTMLResponse, StreamingResponse -import async_timeout from lnbits import bolt11, lnurl from lnbits.core.models import Payment, Wallet from lnbits.decorators import ( @@ -706,7 +714,7 @@ async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): @core_app.websocket("/api/v1/ws/{item_id}") async def websocket_connect(websocket: WebSocket, item_id: str): - await websocketManager.connect(websocket, item_id) + await websocketManager.connect(websocket) try: while True: data = await websocket.receive_text() From 27b641fe6f9705e3d022aac8e8f35a2c468b6553 Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Thu, 1 Dec 2022 16:39:40 +0000 Subject: [PATCH 24/42] more precise venv python version 3.9 --- docs/guide/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index aef26b4a3..b531abdeb 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -75,8 +75,8 @@ LNBITS_DATA_FOLDER=data LNBITS_BACKEND_WALLET_CLASS=LNbitsWallet LNBITS_ENDPOINT ```sh git clone https://github.com/lnbits/lnbits-legend.git cd lnbits-legend/ -# ensure you have virtualenv installed, on debian/ubuntu 'apt install python3-venv' -python3 -m venv venv +# ensure you have virtualenv installed, on debian/ubuntu 'apt install python3.9-venv' +python3.9 -m venv venv # If you have problems here, try `sudo apt install -y pkg-config libpq-dev` ./venv/bin/pip install -r requirements.txt # create the data folder and the .env file From 694a4e5054f7e002c45d02c6b888eb64fd4ba2d1 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 1 Dec 2022 18:31:58 +0000 Subject: [PATCH 25/42] replaced extension specific websockets with the generic websocket --- lnbits/extensions/copilot/tasks.py | 6 +-- .../copilot/templates/copilot/compose.html | 4 +- lnbits/extensions/copilot/views.py | 45 ----------------- lnbits/extensions/copilot/views_api.py | 4 +- lnbits/extensions/lnurldevice/tasks.py | 8 ++- .../templates/lnurldevice/index.html | 4 +- lnbits/extensions/lnurldevice/views.py | 49 +------------------ 7 files changed, 13 insertions(+), 107 deletions(-) diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py index 48ad7813d..949e0d94b 100644 --- a/lnbits/extensions/copilot/tasks.py +++ b/lnbits/extensions/copilot/tasks.py @@ -7,11 +7,11 @@ from starlette.exceptions import HTTPException from lnbits.core import db as core_db from lnbits.core.models import Payment +from lnbits.core.services import websocketUpdater from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_copilot -from .views import updater async def wait_for_paid_invoices(): @@ -65,9 +65,9 @@ async def on_invoice_paid(payment: Payment) -> None: except (httpx.ConnectError, httpx.RequestError): await mark_webhook_sent(payment, -1) if payment.extra.get("comment"): - await updater(copilot.id, data, payment.extra.get("comment")) + await websocketUpdater(copilot.id, str(data) + "-" + str(payment.extra.get("comment"))) - await updater(copilot.id, data, "none") + await websocketUpdater(copilot.id, str(data) + "-none") async def mark_webhook_sent(payment: Payment, status: int) -> None: diff --git a/lnbits/extensions/copilot/templates/copilot/compose.html b/lnbits/extensions/copilot/templates/copilot/compose.html index b4022ee0f..ea44114c1 100644 --- a/lnbits/extensions/copilot/templates/copilot/compose.html +++ b/lnbits/extensions/copilot/templates/copilot/compose.html @@ -238,7 +238,7 @@ document.domain + ':' + location.port + - '/copilot/ws/' + + '/api/v1/ws/' + self.copilot.id } else { localUrl = @@ -246,7 +246,7 @@ document.domain + ':' + location.port + - '/copilot/ws/' + + '/api/v1/ws/' + self.copilot.id } this.connection = new WebSocket(localUrl) diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py index b4a2354a0..3b1ebf032 100644 --- a/lnbits/extensions/copilot/views.py +++ b/lnbits/extensions/copilot/views.py @@ -35,48 +35,3 @@ async def panel(request: Request): return copilot_renderer().TemplateResponse( "copilot/panel.html", {"request": request} ) - - -##################WEBSOCKET ROUTES######################## - - -class ConnectionManager: - def __init__(self): - self.active_connections: List[WebSocket] = [] - - async def connect(self, websocket: WebSocket, copilot_id: str): - await websocket.accept() - websocket.id = copilot_id # type: ignore - self.active_connections.append(websocket) - - def disconnect(self, websocket: WebSocket): - self.active_connections.remove(websocket) - - async def send_personal_message(self, message: str, copilot_id: str): - for connection in self.active_connections: - if connection.id == copilot_id: # type: ignore - await connection.send_text(message) - - async def broadcast(self, message: str): - for connection in self.active_connections: - await connection.send_text(message) - - -manager = ConnectionManager() - - -@copilot_ext.websocket("/ws/{copilot_id}", name="copilot.websocket_by_id") -async def websocket_endpoint(websocket: WebSocket, copilot_id: str): - await manager.connect(websocket, copilot_id) - try: - while True: - data = await websocket.receive_text() - except WebSocketDisconnect: - manager.disconnect(websocket) - - -async def updater(copilot_id, data, comment): - copilot = await get_copilot(copilot_id) - if not copilot: - return - await manager.send_personal_message(f"{data + '-' + comment}", copilot_id) diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py index 46611a2ea..72ead648b 100644 --- a/lnbits/extensions/copilot/views_api.py +++ b/lnbits/extensions/copilot/views_api.py @@ -16,7 +16,7 @@ from .crud import ( update_copilot, ) from .models import CreateCopilotData -from .views import updater +from lnbits.core.services import websocketUpdater #######################COPILOT########################## @@ -92,7 +92,7 @@ async def api_copilot_ws_relay( status_code=HTTPStatus.NOT_FOUND, detail="Copilot does not exist" ) try: - await updater(copilot_id, data, comment) + await websocketUpdater(copilot_id, str(data) + "-" + str(comment)) except: raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your copilot") return "" diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index d3248ad57..8ad9772ca 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -8,12 +8,11 @@ from fastapi import HTTPException from lnbits import bolt11 from lnbits.core.models import Payment -from lnbits.core.services import pay_invoice +from lnbits.core.services import pay_invoice, websocketUpdater from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment -from .views import updater async def wait_for_paid_invoices(): @@ -36,9 +35,8 @@ async def on_invoice_paid(payment: Payment) -> None: lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=payment.extra.get("id"), payhash="used" ) - return await updater( + return await websocketUpdater( lnurldevicepayment.deviceid, - lnurldevicepayment.pin, - lnurldevicepayment.payload, + str(lnurldevicepayment.pin) + "-" + str(lnurldevicepayment.payload), ) return diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 25dcf8c99..c8488bdf6 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -157,8 +157,8 @@ unelevated color="primary" size="md" - @click="copyText(wslocation + '/lnurldevice/ws/' + settingsDialog.data.id, 'Link copied to clipboard!')" - >{% raw %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% + @click="copyText(wslocation + '/api/v1/ws/' + settingsDialog.data.id, 'Link copied to clipboard!')" + >{% raw %}{{wslocation}}/api/v1/ws/{{settingsDialog.data.id}}{% endraw %} Click to copy URL Date: Thu, 1 Dec 2022 18:41:58 +0000 Subject: [PATCH 26/42] format --- lnbits/bolt11.py | 6 +++--- lnbits/core/services.py | 6 +++--- lnbits/extensions/copilot/tasks.py | 4 +++- .../extensions/lnurldevice/templates/lnurldevice/index.html | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lnbits/bolt11.py b/lnbits/bolt11.py index 08f1f1e59..32b43feb6 100644 --- a/lnbits/bolt11.py +++ b/lnbits/bolt11.py @@ -166,7 +166,7 @@ def lnencode(addr, privkey): if addr.amount: amount = Decimal(str(addr.amount)) # We can only send down to millisatoshi. - if amount * 10 ** 12 % 10: + if amount * 10**12 % 10: raise ValueError( "Cannot encode {}: too many decimal places".format(addr.amount) ) @@ -271,7 +271,7 @@ class LnAddr(object): def shorten_amount(amount): """Given an amount in bitcoin, shorten it""" # Convert to pico initially - amount = int(amount * 10 ** 12) + amount = int(amount * 10**12) units = ["p", "n", "u", "m", ""] for unit in units: if amount % 1000 == 0: @@ -290,7 +290,7 @@ def _unshorten_amount(amount: str) -> int: # * `u` (micro): multiply by 0.000001 # * `n` (nano): multiply by 0.000000001 # * `p` (pico): multiply by 0.000000000001 - units = {"p": 10 ** 12, "n": 10 ** 9, "u": 10 ** 6, "m": 10 ** 3} + units = {"p": 10**12, "n": 10**9, "u": 10**6, "m": 10**3} unit = str(amount)[-1] # BOLT #11: diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 623f78139..beb0f97ad 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -329,12 +329,12 @@ async def perform_lnurlauth( sign_len = 6 + r_len + s_len signature = BytesIO() - signature.write(0x30 .to_bytes(1, "big", signed=False)) + signature.write(0x30.to_bytes(1, "big", signed=False)) signature.write((sign_len - 2).to_bytes(1, "big", signed=False)) - signature.write(0x02 .to_bytes(1, "big", signed=False)) + signature.write(0x02.to_bytes(1, "big", signed=False)) signature.write(r_len.to_bytes(1, "big", signed=False)) signature.write(r) - signature.write(0x02 .to_bytes(1, "big", signed=False)) + signature.write(0x02.to_bytes(1, "big", signed=False)) signature.write(s_len.to_bytes(1, "big", signed=False)) signature.write(s) diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py index 949e0d94b..384070cdf 100644 --- a/lnbits/extensions/copilot/tasks.py +++ b/lnbits/extensions/copilot/tasks.py @@ -65,7 +65,9 @@ async def on_invoice_paid(payment: Payment) -> None: except (httpx.ConnectError, httpx.RequestError): await mark_webhook_sent(payment, -1) if payment.extra.get("comment"): - await websocketUpdater(copilot.id, str(data) + "-" + str(payment.extra.get("comment"))) + await websocketUpdater( + copilot.id, str(data) + "-" + str(payment.extra.get("comment")) + ) await websocketUpdater(copilot.id, str(data) + "-none") diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index c8488bdf6..04d645830 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -158,8 +158,8 @@ color="primary" size="md" @click="copyText(wslocation + '/api/v1/ws/' + settingsDialog.data.id, 'Link copied to clipboard!')" - >{% raw %}{{wslocation}}/api/v1/ws/{{settingsDialog.data.id}}{% - endraw %} Click to copy URL + >{% raw %}{{wslocation}}/api/v1/ws/{{settingsDialog.data.id}}{% endraw + %} Click to copy URL Date: Thu, 1 Dec 2022 18:45:00 +0000 Subject: [PATCH 27/42] isort --- lnbits/extensions/copilot/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py index 72ead648b..39d0f7fd9 100644 --- a/lnbits/extensions/copilot/views_api.py +++ b/lnbits/extensions/copilot/views_api.py @@ -5,6 +5,7 @@ from fastapi.param_functions import Query from fastapi.params import Depends from starlette.exceptions import HTTPException +from lnbits.core.services import websocketUpdater from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import copilot_ext @@ -16,7 +17,6 @@ from .crud import ( update_copilot, ) from .models import CreateCopilotData -from lnbits.core.services import websocketUpdater #######################COPILOT########################## From 44e12f9d3ab9349b5363cfe01a6f264ca23a9886 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 1 Dec 2022 19:04:05 +0000 Subject: [PATCH 28/42] updated dev article --- docs/devs/websockets.md | 55 ++----------------- .../templates/lnurldevice/index.html | 2 +- 2 files changed, 7 insertions(+), 50 deletions(-) diff --git a/docs/devs/websockets.md b/docs/devs/websockets.md index 0638e4f28..9ea53a624 100644 --- a/docs/devs/websockets.md +++ b/docs/devs/websockets.md @@ -9,53 +9,10 @@ nav_order: 2 Websockets ================= -`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from the `copilot` extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension): +`websockets` are a great way to add a two way instant data channel between server and client. +LNbits has a useful in built websocket tool. With a websocket client connect to (obv change `somespecificid`) `wss://legend.lnbits.com/api/v1/ws/somespecificid` (you can use an online websocket tester). Now make a get to `https://legend.lnbits.com/api/v1/ws/somespecificid/somedata`. You can send data to that websocket by using `from lnbits.core.services import websocketUpdater` and the function `websocketUpdater("somespecificid", "somdata")`. -```sh -from fastapi import Request, WebSocket, WebSocketDisconnect - -class ConnectionManager: - def __init__(self): - self.active_connections: List[WebSocket] = [] - - async def connect(self, websocket: WebSocket, extension_id: str): - await websocket.accept() - websocket.id = extension_id - self.active_connections.append(websocket) - - def disconnect(self, websocket: WebSocket): - self.active_connections.remove(websocket) - - async def send_personal_message(self, message: str, extension_id: str): - for connection in self.active_connections: - if connection.id == extension_id: - await connection.send_text(message) - - async def broadcast(self, message: str): - for connection in self.active_connections: - await connection.send_text(message) - - -manager = ConnectionManager() - - -@extension_ext.websocket("/ws/{extension_id}", name="extension.websocket_by_id") -async def websocket_endpoint(websocket: WebSocket, extension_id: str): - await manager.connect(websocket, extension_id) - try: - while True: - data = await websocket.receive_text() - except WebSocketDisconnect: - manager.disconnect(websocket) - - -async def updater(extension_id, data): - extension = await get_extension(extension_id) - if not extension: - return - await manager.send_personal_message(f"{data}", extension_id) -``` Example vue-js function for listening to the websocket: @@ -67,16 +24,16 @@ initWs: async function () { document.domain + ':' + location.port + - '/extension/ws/' + - self.extension.id + '/api/v1/ws/' + + self.item.id } else { localUrl = 'ws://' + document.domain + ':' + location.port + - '/extension/ws/' + - self.extension.id + '/api/v1/ws/' + + self.item.id } this.ws = new WebSocket(localUrl) this.ws.addEventListener('message', async ({data}) => { diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 04d645830..b2165590e 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -657,7 +657,7 @@ lnurlValueFetch: function (lnurl, switchId) { this.lnurlValue = lnurl this.websocketConnector( - 'wss://' + window.location.host + '/lnurldevice/ws/' + switchId + 'wss://' + window.location.host + '/api/v1/ws/' + switchId ) }, addSwitch: function () { From 3ad0463dfea9f5822a48ccebd9e28510dae3901b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 2 Dec 2022 13:40:59 +0200 Subject: [PATCH 29/42] feat: extract utxos. from PSBT --- lnbits/extensions/watchonly/models.py | 10 +++++----- lnbits/extensions/watchonly/views_api.py | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index d8c278ff8..c6265d6c5 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -76,9 +76,13 @@ class CreatePsbt(BaseModel): tx_size: int +class SerializedTransaction(BaseModel): + tx_hex: str + + class ExtractPsbt(BaseModel): psbtBase64 = "" # // todo snake case - inputs: List[TransactionInput] + inputs: List[SerializedTransaction] network = "Mainnet" @@ -87,10 +91,6 @@ class SignedTransaction(BaseModel): tx_json: Optional[str] -class BroadcastTransaction(BaseModel): - tx_hex: str - - class Config(BaseModel): mempool_endpoint = "https://mempool.space" receive_gap_limit = 20 diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 9030b9c3d..97f731c3e 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -31,11 +31,11 @@ from .crud import ( ) from .helpers import parse_key from .models import ( - BroadcastTransaction, Config, CreatePsbt, CreateWallet, ExtractPsbt, + SerializedTransaction, SignedTransaction, WalletAccount, ) @@ -291,6 +291,24 @@ async def api_psbt_create( raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) +@watchonly_ext.put("/api/v1/psbt/utxos") +async def api_psbt_extract_tx( + req: Request, w: WalletTypeInfo = Depends(require_admin_key) +): + """Extract previous unspent transaction outputs (tx_id, vout) from PSBT""" + + body = await req.json() + try: + psbt = PSBT.from_base64(body["psbtBase64"]) + res = [] + for _, inp in enumerate(psbt.inputs): + res.append({"tx_id": inp.txid.hex(), "vout": inp.vout}) + + return res + except Exception as e: + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) + + @watchonly_ext.put("/api/v1/psbt/extract") async def api_psbt_extract_tx( data: ExtractPsbt, w: WalletTypeInfo = Depends(require_admin_key) @@ -327,7 +345,7 @@ async def api_psbt_extract_tx( @watchonly_ext.post("/api/v1/tx") async def api_tx_broadcast( - data: BroadcastTransaction, w: WalletTypeInfo = Depends(require_admin_key) + data: SerializedTransaction, w: WalletTypeInfo = Depends(require_admin_key) ): try: config = await get_config(w.wallet.user) From 8b0c004883753623415a7dfb20f9f92556a043c5 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 2 Dec 2022 13:41:26 +0200 Subject: [PATCH 30/42] feat: allow external signed PSBT --- .../static/components/payment/payment.js | 22 ++++++- .../extensions/watchonly/static/js/index.js | 14 ++++- .../watchonly/templates/watchonly/index.html | 59 ++++++++++++++++++- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/watchonly/static/components/payment/payment.js b/lnbits/extensions/watchonly/static/components/payment/payment.js index 9f38df1d0..e9689003f 100644 --- a/lnbits/extensions/watchonly/static/components/payment/payment.js +++ b/lnbits/extensions/watchonly/static/components/payment/payment.js @@ -272,15 +272,35 @@ async function payment(path) { this.showChecking = false } }, + + fetchUtxoHexForPsbt: async function (psbtBase64) { + if (this.tx?.inputs && this.tx?.inputs.length) return this.tx.inputs + + const {data: psbtUtxos} = await LNbits.api.request( + 'PUT', + '/watchonly/api/v1/psbt/utxos', + this.adminkey, + {psbtBase64} + ) + + const inputs = [] + for (const utxo of psbtUtxos) { + const txHex = await this.fetchTxHex(utxo.tx_id) + inputs.push({tx_hex: txHex}) + } + return inputs + }, extractTxFromPsbt: async function (psbtBase64) { try { + const inputs = await this.fetchUtxoHexForPsbt(psbtBase64) + const {data} = await LNbits.api.request( 'PUT', '/watchonly/api/v1/psbt/extract', this.adminkey, { psbtBase64, - inputs: this.tx.inputs, + inputs, network: this.network } ) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index 7e410104d..c87983030 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -54,7 +54,10 @@ const watchOnly = async () => { showPayment: false, fetchedUtxos: false, utxosFilter: '', - network: null + network: null, + + showEnterSignedPsbt: false, + signedBase64Psbt: null } }, computed: { @@ -173,6 +176,15 @@ const watchOnly = async () => { this.$refs.paymentRef.updateSignedPsbt(psbtBase64) }, + showEnterSignedPsbtDialog: function () { + this.showEnterSignedPsbt = true + }, + + checkPsbt: function () { + console.log('### checkPsbt', this.signedBase64Psbt) + this.$refs.paymentRef.updateSignedPsbt(this.signedBase64Psbt) + }, + //################### UTXOs ################### scanAllAddresses: async function () { await this.refreshAddresses() diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index 67f898101..3dab74d56 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -52,14 +52,37 @@ >
- New Payment + + + + New Payment + Create a new payment by selecting Inputs and + Outputs + + + + + From Signed PSBT + Paste a signed PSBT + + + + + + + +
Enter the Signed PSBT
+
+ +

+ +

+ +
+ Check PSBT + Close +
+
+
+
+ {% endraw %}
From 88035305c515a9db8d1952e6398dbf2c8c1a3f1b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 2 Dec 2022 13:46:51 +0200 Subject: [PATCH 31/42] fix: hide `New Payment` button --- lnbits/extensions/watchonly/templates/watchonly/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index 3dab74d56..84be5c815 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -53,6 +53,7 @@
Date: Fri, 2 Dec 2022 14:02:59 +0200 Subject: [PATCH 32/42] chore: code clean-up --- lnbits/extensions/watchonly/static/js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index c87983030..880d6b302 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -177,11 +177,11 @@ const watchOnly = async () => { }, showEnterSignedPsbtDialog: function () { + this.signedBase64Psbt = '' this.showEnterSignedPsbt = true }, checkPsbt: function () { - console.log('### checkPsbt', this.signedBase64Psbt) this.$refs.paymentRef.updateSignedPsbt(this.signedBase64Psbt) }, From 6f5f7753ef96dd41e66318846e8fc65e828ac722 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 6 Dec 2022 20:58:09 +0100 Subject: [PATCH 33/42] fix audit endpoint --- 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 f78219bf1..995cf9e70 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -686,7 +686,7 @@ async def img(request: Request, data): ) -@core_app.get("/api/v1/audit/") +@core_app.get("/api/v1/audit") async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): if wallet.wallet.user not in LNBITS_ADMIN_USERS: raise HTTPException( From 97565fde02687fa57e1c1b2a1ec9c820140a847b Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 7 Dec 2022 10:21:45 +0000 Subject: [PATCH 34/42] Adds PoS websocket endpoint Receives all payments to a pos --- lnbits/extensions/tpos/tasks.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index 6369bbc7f..d590762d2 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -3,7 +3,7 @@ import asyncio from loguru import logger from lnbits.core.models import Payment -from lnbits.core.services import create_invoice, pay_invoice +from lnbits.core.services import create_invoice, pay_invoice, websocketUpdater from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener @@ -26,6 +26,16 @@ async def on_invoice_paid(payment: Payment) -> None: tpos = await get_tpos(payment.extra.get("tposId")) tipAmount = payment.extra.get("tipAmount") + strippedPayment = { + "amount":payment.amount, + "fee":payment.fee, + "checking_id":payment.checking_id, + "payment_hash":payment.payment_hash, + "bolt11":payment.bolt11, + } + + await websocketUpdater(payment.extra.get("tposId"), str(strippedPayment)) + if tipAmount is None: # no tip amount return From ae23567fbf9b9c727a48d198d6ef124a0a6bff3f Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Wed, 7 Dec 2022 10:26:30 +0000 Subject: [PATCH 35/42] Update installation.md --- docs/guide/installation.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index b531abdeb..1a7e9a9e0 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -47,6 +47,15 @@ poetry run lnbits # adding --debug in the start-up command above to help your troubleshooting and generate a more verbose output # Note that you have to add the line DEBUG=true in your .env file, too. ``` +#### Updating the server + +``` +# Stop LNbits with `ctrl + x` +cd lnbits-legend/ +git pull +poetry install - - only main +# Start LNbits with `poetry run lnbits` +``` ## Option 2: Nix From d8a21800f2e6a7ba48784e64a7f34e60c395265e Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 7 Dec 2022 10:29:01 +0000 Subject: [PATCH 36/42] Switched steps --- docs/guide/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 1a7e9a9e0..48bf0f1f5 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -50,8 +50,8 @@ poetry run lnbits #### Updating the server ``` -# Stop LNbits with `ctrl + x` cd lnbits-legend/ +# Stop LNbits with `ctrl + x` git pull poetry install - - only main # Start LNbits with `poetry run lnbits` From 5ac3a861ef63303c6022cb139b4e912f2b9867ce Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Wed, 7 Dec 2022 10:39:59 +0000 Subject: [PATCH 37/42] Update installation.md --- docs/guide/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 48bf0f1f5..9f8b26da0 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -53,7 +53,7 @@ poetry run lnbits cd lnbits-legend/ # Stop LNbits with `ctrl + x` git pull -poetry install - - only main +poetry install --only main # Start LNbits with `poetry run lnbits` ``` From 44d74351639056981077b726f0a7eb421b33b7dc Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 7 Dec 2022 10:42:48 +0000 Subject: [PATCH 38/42] format --- lnbits/extensions/tpos/tasks.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index d590762d2..6eb1d5d1c 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -27,11 +27,11 @@ async def on_invoice_paid(payment: Payment) -> None: tipAmount = payment.extra.get("tipAmount") strippedPayment = { - "amount":payment.amount, - "fee":payment.fee, - "checking_id":payment.checking_id, - "payment_hash":payment.payment_hash, - "bolt11":payment.bolt11, + "amount": payment.amount, + "fee": payment.fee, + "checking_id": payment.checking_id, + "payment_hash": payment.payment_hash, + "bolt11": payment.bolt11, } await websocketUpdater(payment.extra.get("tposId"), str(strippedPayment)) From 8c27561a3fd46a52fa4914179a5ceec58b7aa56e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:44:43 +0100 Subject: [PATCH 39/42] fix for #1182 --- lnbits/extensions/lndhub/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/lndhub/views_api.py b/lnbits/extensions/lndhub/views_api.py index 8cbe5a6bf..c01972396 100644 --- a/lnbits/extensions/lndhub/views_api.py +++ b/lnbits/extensions/lndhub/views_api.py @@ -21,7 +21,7 @@ from .utils import decoded_as_lndhub, to_buffer @lndhub_ext.get("/ext/getinfo") async def lndhub_getinfo(): - raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="bad auth") + return {"alias": LNBITS_SITE_TITLE + " - LndHub"} class AuthData(BaseModel): From 0927bc582f9e3d7abd599d202b91524d9573fc95 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:54:28 +0100 Subject: [PATCH 40/42] just site title --- lnbits/extensions/lndhub/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/lndhub/views_api.py b/lnbits/extensions/lndhub/views_api.py index c01972396..2acdc4ec9 100644 --- a/lnbits/extensions/lndhub/views_api.py +++ b/lnbits/extensions/lndhub/views_api.py @@ -21,7 +21,7 @@ from .utils import decoded_as_lndhub, to_buffer @lndhub_ext.get("/ext/getinfo") async def lndhub_getinfo(): - return {"alias": LNBITS_SITE_TITLE + " - LndHub"} + return {"alias": LNBITS_SITE_TITLE} class AuthData(BaseModel): From 0538035084ad37e606ba22822000483ef6902418 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 8 Dec 2022 14:33:52 +0100 Subject: [PATCH 41/42] msat values are ints --- lnbits/core/views/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 995cf9e70..21342d686 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -702,9 +702,9 @@ async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): node_balance, delta = None, None return { - "node_balance_msats": node_balance, - "lnbits_balance_msats": total_balance, - "delta_msats": delta, + "node_balance_msats": int(node_balance), + "lnbits_balance_msats": int(total_balance), + "delta_msats": int(delta), "timestamp": int(time.time()), } From cdefd58e1243e460ecd929498ec70d163ef31a1c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Thu, 8 Dec 2022 21:33:51 +0100 Subject: [PATCH 42/42] fix: requirements.txt bump cashu to 0.5.5 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 06e8642f7..d54714559 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ attrs==22.1.0 ; python_version >= "3.7" and python_version < "4.0" base58==2.1.1 ; python_version >= "3.7" and python_version < "4.0" bech32==1.2.0 ; python_version >= "3.7" and python_version < "4.0" bitstring==3.1.9 ; python_version >= "3.7" and python_version < "4.0" -cashu==0.5.4 ; python_version >= "3.7" and python_version < "4.0" +cashu==0.5.5 ; python_version >= "3.7" and python_version < "4.0" cerberus==1.3.4 ; python_version >= "3.7" and python_version < "4.0" certifi==2022.9.24 ; python_version >= "3.7" and python_version < "4.0" cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0"