diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index e106ace3..e3d0fd35 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -9,9 +9,20 @@ on: jobs: checks: runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install packages run: poetry install - name: Check black diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index 90006d2a..c280ad7d 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -22,14 +22,18 @@ jobs: --health-retries 5 strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies run: | poetry install diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 61601731..d80da678 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -7,14 +7,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies run: | poetry install diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index 6572ccdb..2d7aae6b 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -7,14 +7,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Setup Regtest run: | docker build -t lnbitsdocker/lnbits-legend . @@ -46,14 +50,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Setup Regtest run: | docker build -t lnbitsdocker/lnbits-legend . @@ -86,14 +94,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Setup Regtest run: | docker build -t lnbitsdocker/lnbits-legend . diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 298d7ff0..5d368fbb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -29,14 +30,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies env: VIRTUAL_ENV: ./venv @@ -64,14 +69,18 @@ jobs: --health-retries 5 strategy: matrix: - python-version: [3.9] + python-version: ["3.9"] + poetry-version: ["1.2.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - uses: abatilo/actions-poetry@v2.1.3 + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} - name: Install dependencies run: | poetry install diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 2b058754..87679ed5 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -292,6 +292,43 @@ Save the file and run the following commands: sudo systemctl enable lnbits.service sudo systemctl start lnbits.service ``` +## Reverse proxy with automatic https using Caddy + +Use Caddy to make your LNbits install accessible over clearnet with a domain and https cert. + +Point your domain at the IP of the server you're running LNbits on, by making an `A` record. + +Install Caddy on the server +https://caddyserver.com/docs/install#debian-ubuntu-raspbian + +``` +sudo caddy stop +``` +Create a Caddyfile +``` +sudo nano Caddyfile +``` +Assuming your LNbits is running on port `5000` add: +``` +yourdomain.com { + handle /api/v1/payments/sse* { + reverse_proxy 0.0.0.0:5000 { + header_up X-Forwarded-Host yourdomain.com + transport http { + keepalive off + compression off + } + } + } + reverse_proxy 0.0.0.0:5000 { + header_up X-Forwarded-Host yourdomain.com + } +} +``` +Save and exit `CTRL + x` +``` +sudo caddy start +``` ## Running behind an apache2 reverse proxy over https Install apache2 and enable apache2 mods diff --git a/lnbits/app.py b/lnbits/app.py index f612c32c..51482538 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -34,7 +34,6 @@ from .tasks import ( check_pending_payments, internal_invoice_listener, invoice_listener, - run_deferred_async, webhook_handler, ) @@ -185,7 +184,7 @@ def register_async_tasks(app): loop.create_task(catch_everything_and_restart(invoice_listener)) loop.create_task(catch_everything_and_restart(internal_invoice_listener)) await register_task_listeners() - await run_deferred_async() + # await run_deferred_async() # calle: doesn't do anyting? @app.on_event("shutdown") async def stop_listeners(): diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 961eb7b2..5d993b4c 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -186,9 +186,9 @@ async def pay_invoice( ) # notify receiver asynchronously - from lnbits.tasks import internal_invoice_queue + logger.debug(f"enqueuing internal invoice {internal_checking_id}") await internal_invoice_queue.put(internal_checking_id) else: logger.debug(f"backend: sending payment {temp_id}") diff --git a/lnbits/core/static/js/service-worker.js b/lnbits/core/static/js/service-worker.js index 041b9f32..98ae497e 100644 --- a/lnbits/core/static/js/service-worker.js +++ b/lnbits/core/static/js/service-worker.js @@ -3,7 +3,11 @@ const CACHE_VERSION = 1 const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-` const getApiKey = request => { - return request.headers.get('X-Api-Key') || 'none' + let api_key = request.headers.get('X-Api-Key') + if (!api_key || api_key == 'undefined') { + api_key = 'no_api_key' + } + return api_key } // on activation we clean up the previously registered service workers @@ -26,8 +30,10 @@ self.addEventListener('activate', evt => // If no response is found, it populates the runtime cache with the response // from the network before returning it to the page. self.addEventListener('fetch', event => { - // Skip cross-origin requests, like those for Google Analytics. if ( + !event.request.url.startsWith( + self.location.origin + '/api/v1/payments/sse' + ) && event.request.url.startsWith(self.location.origin) && event.request.method == 'GET' ) { diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py index 07b8a893..b57e2625 100644 --- a/lnbits/core/tasks.py +++ b/lnbits/core/tasks.py @@ -1,30 +1,43 @@ import asyncio -from typing import List +from typing import Dict import httpx from loguru import logger -from lnbits.tasks import register_invoice_listener +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import SseListenersDict, register_invoice_listener from . import db from .crud import get_balance_notify from .models import Payment -api_invoice_listeners: List[asyncio.Queue] = [] +api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict( + "api_invoice_listeners" +) async def register_task_listeners(): + """ + Registers an invoice listener queue for the core tasks. + Incoming payaments in this queue will eventually trigger the signals sent to all other extensions + and fulfill other core tasks such as dispatching webhooks. + """ invoice_paid_queue = asyncio.Queue(5) - register_invoice_listener(invoice_paid_queue) + # we register invoice_paid_queue to receive all incoming invoices + register_invoice_listener(invoice_paid_queue, "core/tasks.py") + # register a worker that will react to invoices asyncio.create_task(wait_for_paid_invoices(invoice_paid_queue)) async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue): + """ + This worker dispatches events to all extensions, dispatches webhooks and balance notifys. + """ while True: payment = await invoice_paid_queue.get() - logger.debug("received invoice paid event") + logger.trace("received invoice paid event") # send information to sse channel - await dispatch_invoice_listener(payment) + await dispatch_api_invoice_listeners(payment) # dispatch webhook if payment.webhook and not payment.webhook_status: @@ -41,16 +54,23 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue): pass -async def dispatch_invoice_listener(payment: Payment): - for send_channel in api_invoice_listeners: +async def dispatch_api_invoice_listeners(payment: Payment): + """ + Emits events to invoice listener subscribed from the API. + """ + for chan_name, send_channel in api_invoice_listeners.items(): try: + logger.debug(f"sending invoice paid event to {chan_name}") send_channel.put_nowait(payment) except asyncio.QueueFull: - logger.debug("removing sse listener", send_channel) - api_invoice_listeners.remove(send_channel) + logger.error(f"removing sse listener {send_channel}:{chan_name}") + api_invoice_listeners.pop(chan_name) async def dispatch_webhook(payment: Payment): + """ + Dispatches the webhook to the webhook url. + """ async with httpx.AsyncClient() as client: data = payment.dict() try: diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 7a2bbbe6..c07df568 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -3,11 +3,13 @@ import binascii import hashlib import json import time +import uuid from http import HTTPStatus 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 @@ -16,7 +18,7 @@ from fastapi.params import Body from loguru import logger from pydantic import BaseModel from pydantic.fields import Field -from sse_starlette.sse import EventSourceResponse +from sse_starlette.sse import EventSourceResponse, ServerSentEvent from starlette.responses import HTMLResponse, StreamingResponse from lnbits import bolt11, lnurl @@ -366,37 +368,48 @@ async def api_payments_pay_lnurl( } -async def subscribe(request: Request, wallet: Wallet): +async def subscribe_wallet_invoices(request: Request, wallet: Wallet): + """ + Subscribe to new invoices for a wallet. Can be wrapped in EventSourceResponse. + Listenes invoming payments for a wallet and yields jsons with payment details. + """ this_wallet_id = wallet.id payment_queue: asyncio.Queue[Payment] = asyncio.Queue(0) - logger.debug("adding sse listener", payment_queue) - api_invoice_listeners.append(payment_queue) + uid = f"{this_wallet_id}_{str(uuid.uuid4())[:8]}" + logger.debug(f"adding sse listener for wallet: {uid}") + api_invoice_listeners[uid] = payment_queue send_queue: asyncio.Queue[Tuple[str, Payment]] = asyncio.Queue(0) async def payment_received() -> None: while True: - payment: Payment = await payment_queue.get() - if payment.wallet_id == this_wallet_id: - logger.debug("payment received", payment) - await send_queue.put(("payment-received", payment)) + try: + async with async_timeout.timeout(1): + payment: Payment = await payment_queue.get() + if payment.wallet_id == this_wallet_id: + logger.debug("sse listener: payment receieved", payment) + await send_queue.put(("payment-received", payment)) + except asyncio.TimeoutError: + pass - asyncio.create_task(payment_received()) + task = asyncio.create_task(payment_received()) try: while True: + if await request.is_disconnected(): + await request.close() + break typ, data = await send_queue.get() - if data: jdata = json.dumps(dict(data.dict(), pending=False)) - # yield dict(id=1, event="this", data="1234") - # await asyncio.sleep(2) yield dict(data=jdata, event=typ) - # yield dict(data=jdata.encode("utf-8"), event=typ.encode("utf-8")) - except asyncio.CancelledError: + except asyncio.CancelledError as e: + logger.debug(f"CancelledError on listener {uid}: {e}") + api_invoice_listeners.pop(uid) + task.cancel() return @@ -405,7 +418,9 @@ async def api_payments_sse( request: Request, wallet: WalletTypeInfo = Depends(get_key_type) ): return EventSourceResponse( - subscribe(request, wallet.wallet), ping=20, media_type="text/event-stream" + subscribe_wallet_invoices(request, wallet.wallet), + ping=20, + media_type="text/event-stream", ) diff --git a/lnbits/core/views/public_api.py b/lnbits/core/views/public_api.py index 2d2cdd66..9b0ebc98 100644 --- a/lnbits/core/views/public_api.py +++ b/lnbits/core/views/public_api.py @@ -46,8 +46,8 @@ async def api_public_payment_longpolling(payment_hash): payment_queue = asyncio.Queue(0) - logger.debug("adding standalone invoice listener", payment_hash, payment_queue) - api_invoice_listeners.append(payment_queue) + logger.debug(f"adding standalone invoice listener for hash: {payment_hash}") + api_invoice_listeners[payment_hash] = payment_queue response = None diff --git a/lnbits/extensions/boltcards/tasks.py b/lnbits/extensions/boltcards/tasks.py index 1b51c98b..c1e99b76 100644 --- a/lnbits/extensions/boltcards/tasks.py +++ b/lnbits/extensions/boltcards/tasks.py @@ -5,6 +5,7 @@ import httpx from lnbits.core import db as core_db from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import create_refund, get_hit @@ -12,7 +13,7 @@ from .crud import create_refund, get_hit async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/boltz/boltz.py b/lnbits/extensions/boltz/boltz.py index 4e5fecd0..ac99d4f4 100644 --- a/lnbits/extensions/boltz/boltz.py +++ b/lnbits/extensions/boltz/boltz.py @@ -34,8 +34,8 @@ from .models import ( from .utils import check_balance, get_timestamp, req_wrap net = NETWORKS[BOLTZ_NETWORK] -logger.debug(f"BOLTZ_URL: {BOLTZ_URL}") -logger.debug(f"Bitcoin Network: {net['name']}") +logger.trace(f"BOLTZ_URL: {BOLTZ_URL}") +logger.trace(f"Bitcoin Network: {net['name']}") async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap: diff --git a/lnbits/extensions/boltz/mempool.py b/lnbits/extensions/boltz/mempool.py index ee305257..a44c0f02 100644 --- a/lnbits/extensions/boltz/mempool.py +++ b/lnbits/extensions/boltz/mempool.py @@ -11,8 +11,8 @@ from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS from .utils import req_wrap -logger.debug(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}") -logger.debug(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}") +logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}") +logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}") websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws" diff --git a/lnbits/extensions/boltz/tasks.py b/lnbits/extensions/boltz/tasks.py index d6f72edf..ace94557 100644 --- a/lnbits/extensions/boltz/tasks.py +++ b/lnbits/extensions/boltz/tasks.py @@ -5,6 +5,7 @@ from loguru import logger from lnbits.core.models import Payment from lnbits.core.services import check_transaction_status +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .boltz import ( @@ -127,7 +128,7 @@ async def check_for_pending_swaps(): async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py index f3c5cff8..c59ef4cc 100644 --- a/lnbits/extensions/copilot/tasks.py +++ b/lnbits/extensions/copilot/tasks.py @@ -7,6 +7,7 @@ from starlette.exceptions import HTTPException from lnbits.core import db as core_db from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_copilot @@ -15,7 +16,7 @@ from .views import updater async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/jukebox/tasks.py b/lnbits/extensions/jukebox/tasks.py index 70a2e65d..5614d926 100644 --- a/lnbits/extensions/jukebox/tasks.py +++ b/lnbits/extensions/jukebox/tasks.py @@ -1,6 +1,7 @@ import asyncio from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import update_jukebox_payment @@ -8,7 +9,7 @@ from .crud import update_jukebox_payment async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/livestream/tasks.py b/lnbits/extensions/livestream/tasks.py index 85bdd5e0..626c698c 100644 --- a/lnbits/extensions/livestream/tasks.py +++ b/lnbits/extensions/livestream/tasks.py @@ -6,7 +6,7 @@ from loguru import logger from lnbits.core import db as core_db from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import urlsafe_short_hash +from lnbits.helpers import get_current_extension_name, urlsafe_short_hash from lnbits.tasks import internal_invoice_listener, register_invoice_listener from .crud import get_livestream_by_track, get_producer, get_track @@ -14,7 +14,7 @@ from .crud import get_livestream_by_track, get_producer, get_track async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/lnaddress/tasks.py b/lnbits/extensions/lnaddress/tasks.py index 9abe10c3..0c377eec 100644 --- a/lnbits/extensions/lnaddress/tasks.py +++ b/lnbits/extensions/lnaddress/tasks.py @@ -3,6 +3,7 @@ import asyncio import httpx from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_address, get_domain, set_address_paid, set_address_renewed @@ -10,7 +11,7 @@ from .crud import get_address, get_domain, set_address_paid, set_address_renewed async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/lnticket/__init__.py b/lnbits/extensions/lnticket/__init__.py index 792b1175..cb793f4d 100644 --- a/lnbits/extensions/lnticket/__init__.py +++ b/lnbits/extensions/lnticket/__init__.py @@ -1,4 +1,5 @@ import asyncio +import json from fastapi import APIRouter diff --git a/lnbits/extensions/lnticket/tasks.py b/lnbits/extensions/lnticket/tasks.py index 7e672115..746ebea9 100644 --- a/lnbits/extensions/lnticket/tasks.py +++ b/lnbits/extensions/lnticket/tasks.py @@ -3,6 +3,7 @@ import asyncio from loguru import logger from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_ticket, set_ticket_paid @@ -10,7 +11,7 @@ from .crud import get_ticket, set_ticket_paid async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py index 525d36ce..86f1579a 100644 --- a/lnbits/extensions/lnurlp/tasks.py +++ b/lnbits/extensions/lnurlp/tasks.py @@ -5,6 +5,7 @@ import httpx from lnbits.core import db as core_db from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_pay_link @@ -12,7 +13,7 @@ from .crud import get_pay_link async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/lnurlpayout/tasks.py b/lnbits/extensions/lnurlpayout/tasks.py index b621876c..71f299be 100644 --- a/lnbits/extensions/lnurlpayout/tasks.py +++ b/lnbits/extensions/lnurlpayout/tasks.py @@ -10,6 +10,7 @@ from lnbits.core.crud import get_wallet from lnbits.core.models import Payment from lnbits.core.services import pay_invoice from lnbits.core.views.api import api_payments_decode +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_lnurlpayout_from_wallet @@ -17,7 +18,7 @@ from .crud import get_lnurlpayout_from_wallet async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index d325405b..46c16bbc 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -4,6 +4,7 @@ from loguru import logger from lnbits.core.models import Payment from lnbits.extensions.satspay.crud import check_address_balance, get_charge +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener # from .crud import get_ticket, set_ticket_paid @@ -11,7 +12,7 @@ from lnbits.tasks import register_invoice_listener async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py index 87e1364b..320d34da 100644 --- a/lnbits/extensions/scrub/tasks.py +++ b/lnbits/extensions/scrub/tasks.py @@ -9,6 +9,7 @@ from fastapi import HTTPException from lnbits import bolt11 from lnbits.core.models import Payment from lnbits.core.services import pay_invoice +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_scrub_by_wallet @@ -16,7 +17,7 @@ from .crud import get_scrub_by_wallet async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/scrub/templates/scrub/index.html b/lnbits/extensions/scrub/templates/scrub/index.html index c063c858..5a1cae58 100644 --- a/lnbits/extensions/scrub/templates/scrub/index.html +++ b/lnbits/extensions/scrub/templates/scrub/index.html @@ -68,6 +68,21 @@
{{SITE_TITLE}} Scrub extension
+

+ Automatically forward funds (Scrub) that get paid to the LNbits + wallet, to an LNURLpay or Lightning Address. +
+ More info in Scrub's + readme. +

+

+ Important: wallet will need a float to account for + any fees, before being able to push a payment +

diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py index 0948e849..b7cf1750 100644 --- a/lnbits/extensions/splitpayments/tasks.py +++ b/lnbits/extensions/splitpayments/tasks.py @@ -6,7 +6,7 @@ from loguru import logger from lnbits.core import db as core_db from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import urlsafe_short_hash +from lnbits.helpers import get_current_extension_name, urlsafe_short_hash from lnbits.tasks import internal_invoice_queue, register_invoice_listener from .crud import get_targets @@ -14,7 +14,7 @@ from .crud import get_targets async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/subdomains/tasks.py b/lnbits/extensions/subdomains/tasks.py index d8f35161..04ee2dd4 100644 --- a/lnbits/extensions/subdomains/tasks.py +++ b/lnbits/extensions/subdomains/tasks.py @@ -3,6 +3,7 @@ import asyncio import httpx from lnbits.core.models import Payment +from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .cloudflare import cloudflare_create_subdomain @@ -11,7 +12,7 @@ from .crud import get_domain, set_subdomain_paid async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index af9663cc..f18d1689 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -4,7 +4,7 @@ import json from lnbits.core import db as core_db from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import urlsafe_short_hash +from lnbits.helpers import get_current_extension_name, urlsafe_short_hash from lnbits.tasks import internal_invoice_queue, register_invoice_listener from .crud import get_tpos @@ -12,7 +12,7 @@ from .crud import get_tpos async def wait_for_paid_invoices(): invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue) + register_invoice_listener(invoice_queue, get_current_extension_name()) while True: payment = await invoice_queue.get() diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index cedaa210..622f5ec8 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -80,6 +80,7 @@ class CreatePsbt(BaseModel): class ExtractPsbt(BaseModel): psbtBase64 = "" # // todo snake case inputs: List[TransactionInput] + network = "Mainnet" class SignedTransaction(BaseModel): diff --git a/lnbits/extensions/watchonly/static/components/payment/payment.html b/lnbits/extensions/watchonly/static/components/payment/payment.html index cde65ca2..6e1d94c7 100644 --- a/lnbits/extensions/watchonly/static/components/payment/payment.html +++ b/lnbits/extensions/watchonly/static/components/payment/payment.html @@ -5,7 +5,7 @@ diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js index 33d07d94..2e80b1e0 100644 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js +++ b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js @@ -15,7 +15,7 @@ async function serialSigner(path) { receivedData: '', config: {}, decryptionKey: null, - sharedSecret: null, // todo: store in secure local storage + sharedSecret: null, hww: { password: null, @@ -51,12 +51,14 @@ async function serialSigner(path) { }, tx: null, // todo: move to hww - showConsole: false + showConsole: false, + showPairedDevices: true } }, computed: { pairedDevices: { + cache: false, get: function () { return ( JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) || @@ -109,7 +111,10 @@ async function serialSigner(path) { // Wait for the serial port to open. await this.selectedPort.open(config) + // do not await this.startSerialPortReading() + // wait to init + sleep(1000) const textEncoder = new TextEncoderStream() this.writableStreamClosed = textEncoder.readable.pipeTo( @@ -225,8 +230,9 @@ async function serialSigner(path) { while (true) { const {value, done} = await readStringUntil('\n') if (value) { - this.handleSerialPortResponse(value) - this.updateSerialPortConsole(value) + const {command, commandData} = await this.extractCommand(value) + this.handleSerialPortResponse(command, commandData) + this.updateSerialPortConsole(command) } if (done) return } @@ -240,8 +246,7 @@ async function serialSigner(path) { } } }, - handleSerialPortResponse: async function (value) { - const {command, commandData} = await this.extractCommand(value) + handleSerialPortResponse: async function (command, commandData) { this.logPublicCommandsResponse(command, commandData) switch (command) { @@ -282,7 +287,7 @@ async function serialSigner(path) { ) break default: - console.log(` %c${value}`, 'background: #222; color: red') + console.log(` %c${command}`, 'background: #222; color: red') } }, logPublicCommandsResponse: function (command, commandData) { @@ -307,6 +312,8 @@ async function serialSigner(path) { }, hwwPing: async function () { try { + // Send an empty ping. The serial port buffer might have some jubk data. Flush it. + await this.sendCommandClearText(COMMAND_PING) await this.sendCommandClearText(COMMAND_PING, [window.location.host]) } catch (error) { this.$q.notify({ @@ -582,7 +589,7 @@ async function serialSigner(path) { hwwCheckPairing: async function () { const iv = window.crypto.getRandomValues(new Uint8Array(16)) const encrypted = await this.encryptMessage( - this.sharedSecret, + this.sharedSecret, // todo: revisit iv, PAIRING_CONTROL_TEXT.length + ' ' + PAIRING_CONTROL_TEXT ) @@ -603,10 +610,10 @@ async function serialSigner(path) { } }, handleCheckPairingResponse: async function (res = '') { - const [statusCode, encryptedMessage] = res.split(' ') + const [statusCode, message] = res.split(' ') switch (statusCode) { case '0': - const controlText = await this.decryptData(encryptedMessage) + const controlText = await this.decryptData(message) if (controlText == PAIRING_CONTROL_TEXT) { this.$q.notify({ type: 'positive', @@ -622,6 +629,16 @@ async function serialSigner(path) { }) } break + case '1': + this.closeSerialPort() + this.$q.notify({ + type: 'warning', + message: + 'Re-pairing failed. Remove (forget) device and try again!', + caption: `Error: ${message}`, + timeout: 10000 + }) + break default: // noting to do here yet break @@ -746,7 +763,7 @@ async function serialSigner(path) { } catch (error) { this.$q.notify({ type: 'warning', - message: 'Failed to ask for help!', + message: 'Failed to wipe!', caption: `${error}`, timeout: 10000 }) @@ -862,6 +879,11 @@ async function serialSigner(path) { sendCommandSecure: async function (command, attrs = []) { const message = [command].concat(attrs).join(' ') const iv = window.crypto.getRandomValues(new Uint8Array(16)) + if (!this.sharedSecret || !this.sharedSecret.length) { + throw new Error( + `Secure connection not estabileshed. Tried to run command: ${command}` + ) + } const encrypted = await this.encryptMessage( this.sharedSecret, iv, @@ -901,6 +923,7 @@ async function serialSigner(path) { }, decryptData: async function (value) { if (!this.sharedSecret) { + console.log('/error Secure session not established!') return '/error Secure session not established!' } try { @@ -921,6 +944,7 @@ async function serialSigner(path) { .trim() return command } catch (error) { + console.log('/error Failed to decrypt message from device!') return '/error Failed to decrypt message from device!' } }, @@ -949,6 +973,11 @@ async function serialSigner(path) { devices.splice(deviceIndex, 1) } this.pairedDevices = devices + this.showPairedDevices = false + setTimeout(() => { + // force UI refresh + this.showPairedDevices = true + }) }, addPairedDevice: function (deviceId, sharedSecretHex, config) { const devices = this.pairedDevices @@ -960,6 +989,11 @@ async function serialSigner(path) { config }) this.pairedDevices = devices + this.showPairedDevices = false + setTimeout(() => { + // force UI refresh + this.showPairedDevices = true + }) }, updatePairedDeviceConfig(deviceId, config) { const device = this.getPairedDevice(deviceId) diff --git a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html index ba52c4fa..e40ca81f 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html +++ b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html @@ -18,9 +18,21 @@ >directly from browser -
Created by, +
Created by Ben Arc, + Tiago Vasconcelos, + motorina0 (using, @@ -149,7 +150,7 @@
{{SITE_TITLE}} Onchain Wallet (watch-only) Extension - (v0.2) + (v0.3)
diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 77d28fee..750d46c9 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -4,6 +4,7 @@ from http import HTTPStatus import httpx from embit import finalizer, script from embit.ec import PublicKey +from embit.networks import NETWORKS from embit.psbt import PSBT, DerivationPath from embit.transaction import Transaction, TransactionInput, TransactionOutput from fastapi import Query, Request @@ -295,6 +296,7 @@ async def api_psbt_create( async def api_psbt_extract_tx( data: ExtractPsbt, w: WalletTypeInfo = Depends(require_admin_key) ): + network = NETWORKS["main"] if data.network == "Mainnet" else NETWORKS["test"] res = SignedTransaction() try: psbt = PSBT.from_base64(data.psbtBase64) @@ -316,7 +318,7 @@ async def api_psbt_extract_tx( for out in transaction.vout: tx["outputs"].append( - {"amount": out.value, "address": out.script_pubkey.address()} + {"amount": out.value, "address": out.script_pubkey.address(network)} ) res.tx_json = json.dumps(tx) except Exception as e: diff --git a/lnbits/helpers.py b/lnbits/helpers.py index e97fc7bb..e213240c 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -183,3 +183,26 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates: t.env.globals["VENDORED_CSS"] = ["/static/bundle.css"] return t + + +def get_current_extension_name() -> str: + """ + Returns the name of the extension that calls this method. + """ + import inspect + import json + import os + + callee_filepath = inspect.stack()[1].filename + callee_dirname, callee_filename = os.path.split(callee_filepath) + + path = os.path.normpath(callee_dirname) + extension_director_name = path.split(os.sep)[-1] + try: + config_path = os.path.join(callee_dirname, "config.json") + with open(config_path) as json_file: + config = json.load(json_file) + ext_name = config["name"] + except: + ext_name = extension_director_name + return ext_name diff --git a/lnbits/tasks.py b/lnbits/tasks.py index 41287ff2..94e43dcf 100644 --- a/lnbits/tasks.py +++ b/lnbits/tasks.py @@ -1,8 +1,9 @@ import asyncio import time import traceback +import uuid from http import HTTPStatus -from typing import Callable, List +from typing import Callable, Dict, List from fastapi.exceptions import HTTPException from loguru import logger @@ -18,20 +19,6 @@ from lnbits.settings import WALLET from .core import db -deferred_async: List[Callable] = [] - - -def record_async(func: Callable) -> Callable: - def recorder(state): - deferred_async.append(func) - - return recorder - - -async def run_deferred_async(): - for func in deferred_async: - asyncio.create_task(catch_everything_and_restart(func)) - async def catch_everything_and_restart(func): try: @@ -50,18 +37,48 @@ async def send_push_promise(a, b) -> None: pass -invoice_listeners: List[asyncio.Queue] = [] +class SseListenersDict(dict): + """ + A dict of sse listeners. + """ + + def __init__(self, name: str = None): + self.name = name or f"sse_listener_{str(uuid.uuid4())[:8]}" + + def __setitem__(self, key, value): + assert type(key) == str, f"{key} is not a string" + assert type(value) == asyncio.Queue, f"{value} is not an asyncio.Queue" + logger.trace(f"sse: adding listener {key} to {self.name}. len = {len(self)+1}") + return super().__setitem__(key, value) + + def __delitem__(self, key): + logger.trace(f"sse: removing listener from {self.name}. len = {len(self)-1}") + return super().__delitem__(key) + + _RaiseKeyError = object() # singleton for no-default behavior + + def pop(self, key, v=_RaiseKeyError) -> None: + logger.trace(f"sse: removing listener from {self.name}. len = {len(self)-1}") + return super().pop(key) -def register_invoice_listener(send_chan: asyncio.Queue): +invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict("invoice_listeners") + + +def register_invoice_listener(send_chan: asyncio.Queue, name: str = None): """ - A method intended for extensions to call when they want to be notified about - new invoice payments incoming. + A method intended for extensions (and core/tasks.py) to call when they want to be notified about + new invoice payments incoming. Will emit all incoming payments. """ - invoice_listeners.append(send_chan) + name_unique = f"{name or 'no_name'}_{str(uuid.uuid4())[:8]}" + logger.trace(f"sse: registering invoice listener {name_unique}") + invoice_listeners[name_unique] = send_chan async def webhook_handler(): + """ + Returns the webhook_handler for the selected wallet if present. Used by API. + """ handler = getattr(WALLET, "webhook_listener", None) if handler: return await handler() @@ -72,18 +89,36 @@ internal_invoice_queue: asyncio.Queue = asyncio.Queue(0) async def internal_invoice_listener(): + """ + internal_invoice_queue will be filled directly in core/services.py + after the payment was deemed to be settled internally. + + Called by the app startup sequence. + """ while True: checking_id = await internal_invoice_queue.get() + logger.info("> got internal payment notification", checking_id) asyncio.create_task(invoice_callback_dispatcher(checking_id)) async def invoice_listener(): + """ + invoice_listener will collect all invoices that come directly + from the backend wallet. + + Called by the app startup sequence. + """ async for checking_id in WALLET.paid_invoices_stream(): logger.info("> got a payment notification", checking_id) asyncio.create_task(invoice_callback_dispatcher(checking_id)) async def check_pending_payments(): + """ + check_pending_payments is called during startup to check for pending payments with + the backend and also to delete expired invoices. Incoming payments will be + checked only once, outgoing pending payments will be checked regularly. + """ outgoing = True incoming = True @@ -133,9 +168,14 @@ async def perform_balance_checks(): async def invoice_callback_dispatcher(checking_id: str): + """ + Takes incoming payments, sets pending=False, and dispatches them to + invoice_listeners from core and extensions. + """ payment = await get_standalone_payment(checking_id, incoming=True) if payment and payment.is_in: - logger.trace("sending invoice callback for payment", checking_id) + logger.trace(f"sse sending invoice callback for payment {checking_id}") await payment.set_pending(False) - for send_chan in invoice_listeners: + for chan_name, send_chan in invoice_listeners.items(): + logger.trace(f"sse sending to chan: {chan_name}") await send_chan.put(payment) diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py index 8424001b..a07ef4d8 100644 --- a/lnbits/wallets/fake.py +++ b/lnbits/wallets/fake.py @@ -8,9 +8,7 @@ from typing import AsyncGenerator, Dict, Optional from environs import Env # type: ignore from loguru import logger -from lnbits.helpers import urlsafe_short_hash - -from ..bolt11 import decode, encode +from ..bolt11 import Invoice, decode, encode from .base import ( InvoiceResponse, PaymentResponse, @@ -24,6 +22,16 @@ env.read_env() class FakeWallet(Wallet): + queue: asyncio.Queue = asyncio.Queue(0) + secret: str = env.str("FAKE_WALLET_SECTRET", default="ToTheMoon1") + privkey: str = hashlib.pbkdf2_hmac( + "sha256", + secret.encode("utf-8"), + ("FakeWallet").encode("utf-8"), + 2048, + 32, + ).hex() + async def status(self) -> StatusResponse: logger.info( "FakeWallet funding source is for using LNbits as a centralised, stand-alone payment system with brrrrrr." @@ -39,18 +47,12 @@ class FakeWallet(Wallet): ) -> InvoiceResponse: # we set a default secret since FakeWallet is used for internal=True invoices # and the user might not have configured a secret yet - secret = env.str("FAKE_WALLET_SECTRET", default="ToTheMoon1") + data: Dict = { "out": False, "amount": amount, "currency": "bc", - "privkey": hashlib.pbkdf2_hmac( - "sha256", - secret.encode("utf-8"), - ("FakeWallet").encode("utf-8"), - 2048, - 32, - ).hex(), + "privkey": self.privkey, "memo": None, "description_hash": None, "description": "", @@ -86,8 +88,9 @@ class FakeWallet(Wallet): invoice = decode(bolt11) if ( hasattr(invoice, "checking_id") - and invoice.checking_id[6:] == data["privkey"][:6] # type: ignore + and invoice.checking_id[:6] == self.privkey[:6] # type: ignore ): + await self.queue.put(invoice) return PaymentResponse(True, invoice.payment_hash, 0) else: return PaymentResponse( @@ -101,7 +104,6 @@ class FakeWallet(Wallet): return PaymentStatus(None) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: - self.queue: asyncio.Queue = asyncio.Queue(0) while True: - value = await self.queue.get() - yield value + value: Invoice = await self.queue.get() + yield value.payment_hash diff --git a/lnbits/wallets/void.py b/lnbits/wallets/void.py index 0de387aa..b74eb245 100644 --- a/lnbits/wallets/void.py +++ b/lnbits/wallets/void.py @@ -23,7 +23,7 @@ class VoidWallet(Wallet): raise Unsupported("") async def status(self) -> StatusResponse: - logger.info( + logger.warning( "This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits." ) return StatusResponse(None, 0) diff --git a/poetry.lock b/poetry.lock index ea83e25e..5b283d75 100644 --- a/poetry.lock +++ b/poetry.lock @@ -46,6 +46,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} + [[package]] name = "attrs" version = "21.2.0" @@ -111,13 +122,16 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] -name = "cerberus" +name = "Cerberus" version = "1.3.4" description = "Lightweight, extensible schema and data validation tool for Python dictionaries." category = "main" optional = false python-versions = ">=2.7" +[package.dependencies] +setuptools = "*" + [[package]] name = "certifi" version = "2021.5.30" @@ -182,7 +196,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.4.4" +version = "6.5.0" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -206,7 +220,7 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] @@ -399,7 +413,7 @@ plugins = ["setuptools"] requirements_deprecated_finder = ["pip-api", "pipreqs"] [[package]] -name = "jinja2" +name = "Jinja2" version = "3.0.1" description = "A very fast and expressive template engine." category = "main" @@ -441,7 +455,7 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"] [[package]] -name = "markupsafe" +name = "MarkupSafe" version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" @@ -575,7 +589,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "4.21.6" +version = "4.21.7" description = "" category = "main" optional = false @@ -638,7 +652,7 @@ python-versions = ">=3.7,<4.0" [[package]] name = "pyln-client" -version = "0.12.0.post1" +version = "0.11.1" description = "Client library and plugin library for Core Lightning" category = "main" optional = false @@ -683,7 +697,7 @@ optional = false python-versions = "*" [[package]] -name = "pyqrcode" +name = "PyQRCode" version = "1.2.1" description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output." category = "main" @@ -694,7 +708,7 @@ python-versions = "*" PNG = ["pypng (>=0.0.13)"] [[package]] -name = "pyscss" +name = "pyScss" version = "1.4.0" description = "pyScss, a Scss compiler for Python" category = "main" @@ -707,7 +721,7 @@ pathlib2 = "*" six = "*" [[package]] -name = "pysocks" +name = "PySocks" version = "1.7.1" description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." category = "main" @@ -777,7 +791,7 @@ python-versions = ">=3.5" cli = ["click (>=5.0)"] [[package]] -name = "pyyaml" +name = "PyYAML" version = "5.4.1" description = "YAML parser and emitter for Python" category = "main" @@ -785,7 +799,7 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] -name = "represent" +name = "Represent" version = "1.6.0.post0" description = "Create __repr__ automatically or declaratively." category = "main" @@ -823,6 +837,19 @@ python-versions = "*" [package.dependencies] cffi = ">=1.3.0" +[[package]] +name = "setuptools" +version = "65.4.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "shortuuid" version = "1.0.1" @@ -848,7 +875,7 @@ optional = false python-versions = ">=3.5" [[package]] -name = "sqlalchemy" +name = "SQLAlchemy" version = "1.3.23" description = "Database Abstraction Library" category = "main" @@ -860,7 +887,7 @@ mssql = ["pyodbc"] mssql_pymssql = ["pymssql"] mssql_pyodbc = ["pyodbc"] mysql = ["mysqlclient"] -oracle = ["cx-oracle"] +oracle = ["cx_oracle"] postgresql = ["psycopg2"] postgresql_pg8000 = ["pg8000 (<1.16.6)"] postgresql_psycopg2binary = ["psycopg2-binary"] @@ -1024,7 +1051,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black ( [metadata] lock-version = "1.1" python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" -content-hash = "d0556d4a307864ba04a1e5da517884e523396c98a00ae09d9192c37b1d2c555b" +content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42" [metadata.files] aiofiles = [ @@ -1043,6 +1070,10 @@ asn1crypto = [ {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, ] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, @@ -1085,7 +1116,7 @@ black = [ {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, ] -cerberus = [ +Cerberus = [ {file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"}, ] certifi = [ @@ -1193,56 +1224,56 @@ colorama = [ {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] coverage = [ - {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, - {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, - {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, - {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, - {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, - {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, - {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, - {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, - {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, - {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, - {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, - {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, - {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, - {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, - {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, - {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] cryptography = [ {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"}, @@ -1397,7 +1428,7 @@ isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] -jinja2 = [ +Jinja2 = [ {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, ] @@ -1409,7 +1440,7 @@ loguru = [ {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, ] -markupsafe = [ +MarkupSafe = [ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, @@ -1542,20 +1573,20 @@ pluggy = [ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] protobuf = [ - {file = "protobuf-4.21.6-cp310-abi3-win32.whl", hash = "sha256:49f88d56a9180dbb7f6199c920f5bb5c1dd0172f672983bb281298d57c2ac8eb"}, - {file = "protobuf-4.21.6-cp310-abi3-win_amd64.whl", hash = "sha256:7a6cc8842257265bdfd6b74d088b829e44bcac3cca234c5fdd6052730017b9ea"}, - {file = "protobuf-4.21.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ba596b9ffb85c909fcfe1b1a23136224ed678af3faf9912d3fa483d5f9813c4e"}, - {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4143513c766db85b9d7c18dbf8339673c8a290131b2a0fe73855ab20770f72b0"}, - {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b6cea204865595a92a7b240e4b65bcaaca3ad5d2ce25d9db3756eba06041138e"}, - {file = "protobuf-4.21.6-cp37-cp37m-win32.whl", hash = "sha256:9666da97129138585b26afcb63ad4887f602e169cafe754a8258541c553b8b5d"}, - {file = "protobuf-4.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:308173d3e5a3528787bb8c93abea81d5a950bdce62840d9760effc84127fb39c"}, - {file = "protobuf-4.21.6-cp38-cp38-win32.whl", hash = "sha256:aa29113ec901281f29d9d27b01193407a98aa9658b8a777b0325e6d97149f5ce"}, - {file = "protobuf-4.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:8f9e60f7d44592c66e7b332b6a7b4b6e8d8b889393c79dbc3a91f815118f8eac"}, - {file = "protobuf-4.21.6-cp39-cp39-win32.whl", hash = "sha256:80e6540381080715fddac12690ee42d087d0d17395f8d0078dfd6f1181e7be4c"}, - {file = "protobuf-4.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:77b355c8604fe285536155286b28b0c4cbc57cf81b08d8357bf34829ea982860"}, - {file = "protobuf-4.21.6-py2.py3-none-any.whl", hash = "sha256:07a0bb9cc6114f16a39c866dc28b6e3d96fa4ffb9cc1033057412547e6e75cb9"}, - {file = "protobuf-4.21.6-py3-none-any.whl", hash = "sha256:c7c864148a237f058c739ae7a05a2b403c0dfa4ce7d1f3e5213f352ad52d57c6"}, - {file = "protobuf-4.21.6.tar.gz", hash = "sha256:6b1040a5661cd5f6e610cbca9cfaa2a17d60e2bb545309bc1b278bb05be44bdd"}, + {file = "protobuf-4.21.7-cp310-abi3-win32.whl", hash = "sha256:c7cb105d69a87416bd9023e64324e1c089593e6dae64d2536f06bcbe49cd97d8"}, + {file = "protobuf-4.21.7-cp310-abi3-win_amd64.whl", hash = "sha256:3ec85328a35a16463c6f419dbce3c0fc42b3e904d966f17f48bae39597c7a543"}, + {file = "protobuf-4.21.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:db9056b6a11cb5131036d734bcbf91ef3ef9235d6b681b2fc431cbfe5a7f2e56"}, + {file = "protobuf-4.21.7-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ca200645d6235ce0df3ccfdff1567acbab35c4db222a97357806e015f85b5744"}, + {file = "protobuf-4.21.7-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b019c79e23a80735cc8a71b95f76a49a262f579d6b84fd20a0b82279f40e2cc1"}, + {file = "protobuf-4.21.7-cp37-cp37m-win32.whl", hash = "sha256:d3f89ccf7182293feba2de2739c8bf34fed1ed7c65a5cf987be00311acac57c1"}, + {file = "protobuf-4.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:a74d96cd960b87b4b712797c741bb3ea3a913f5c2dc4b6cbe9c0f8360b75297d"}, + {file = "protobuf-4.21.7-cp38-cp38-win32.whl", hash = "sha256:8e09d1916386eca1ef1353767b6efcebc0a6859ed7f73cb7fb974feba3184830"}, + {file = "protobuf-4.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:9e355f2a839d9930d83971b9f562395e13493f0e9211520f8913bd11efa53c02"}, + {file = "protobuf-4.21.7-cp39-cp39-win32.whl", hash = "sha256:f370c0a71712f8965023dd5b13277444d3cdfecc96b2c778b0e19acbfd60df6e"}, + {file = "protobuf-4.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:9643684232b6b340b5e63bb69c9b4904cdd39e4303d498d1a92abddc7e895b7f"}, + {file = "protobuf-4.21.7-py2.py3-none-any.whl", hash = "sha256:8066322588d4b499869bf9f665ebe448e793036b552f68c585a9b28f1e393f66"}, + {file = "protobuf-4.21.7-py3-none-any.whl", hash = "sha256:58b81358ec6c0b5d50df761460ae2db58405c063fd415e1101209221a0a810e1"}, + {file = "protobuf-4.21.7.tar.gz", hash = "sha256:71d9dba03ed3432c878a801e2ea51e034b0ea01cf3a4344fb60166cb5f6c8757"}, ] psycopg2-binary = [ {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, @@ -1661,8 +1692,8 @@ pyln-bolt7 = [ {file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"}, ] pyln-client = [ - {file = "pyln-client-0.12.0.post1.tar.gz", hash = "sha256:c80338e8e9f435720c0e5f552dc4016fc8fba16d4b79764f881067e0fcd5d5c7"}, - {file = "pyln_client-0.12.0.post1-py3-none-any.whl", hash = "sha256:cfe3404eb88f294015145e668d774dd754b3baec36b44fe773fa354f1e1e48c1"}, + {file = "pyln-client-0.11.1.tar.gz", hash = "sha256:f5ea648840b030e2bbcf8c66ee72d25a5817f89854434a28d30e887547138c8e"}, + {file = "pyln_client-0.11.1-py3-none-any.whl", hash = "sha256:497db443406b80c98c0434e2938eb1b2a17e88fd9aa63b018124068198df6141"}, ] pyln-proto = [ {file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"}, @@ -1675,14 +1706,14 @@ pyparsing = [ pypng = [ {file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"}, ] -pyqrcode = [ +PyQRCode = [ {file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"}, {file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"}, ] -pyscss = [ +pyScss = [ {file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"}, ] -pysocks = [ +PySocks = [ {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, @@ -1703,7 +1734,7 @@ python-dotenv = [ {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, ] -pyyaml = [ +PyYAML = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, @@ -1734,7 +1765,7 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] -represent = [ +Represent = [ {file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"}, {file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"}, ] @@ -1767,6 +1798,10 @@ secp256k1 = [ {file = "secp256k1-0.14.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c9e7c024ff17e9b9d7c392bb2a917da231d6cb40ab119389ff1f51dca10339a4"}, {file = "secp256k1-0.14.0.tar.gz", hash = "sha256:82c06712d69ef945220c8b53c1a0d424c2ff6a1f64aee609030df79ad8383397"}, ] +setuptools = [ + {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"}, + {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"}, +] shortuuid = [ {file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"}, {file = "shortuuid-1.0.1.tar.gz", hash = "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f"}, @@ -1779,7 +1814,7 @@ sniffio = [ {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, ] -sqlalchemy = [ +SQLAlchemy = [ {file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"}, {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1"}, {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5"}, diff --git a/pyproject.toml b/pyproject.toml index 7f833aa5..19dac860 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ asgiref = "3.4.1" attrs = "21.2.0" bech32 = "1.2.0" bitstring = "3.1.9" -cerberus = "1.3.4" certifi = "2021.5.30" charset-normalizer = "2.0.6" click = "8.0.1" @@ -62,7 +61,9 @@ cffi = "1.15.0" websocket-client = "1.3.3" grpcio = "^1.49.1" protobuf = "^4.21.6" -pyln-client = "^0.12.0" +Cerberus = "^1.3.4" +async-timeout = "^4.0.2" +pyln-client = "0.11.1" [tool.poetry.dev-dependencies] isort = "^5.10.1" diff --git a/requirements.txt b/requirements.txt index 697ea1d4..eb9a6e5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -51,3 +51,5 @@ uvloop==0.16.0 watchfiles==0.16.0 websockets==10.3 websocket-client==1.3.3 +async-timeout==4.0.2 +setuptools==65.4.0 \ No newline at end of file