diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index e3d0fd35..b6966bfa 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index c280ad7d..11429665 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index d80da678..6868455e 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index 2d7aae6b..99687032 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -51,7 +51,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -95,7 +95,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 487411ed..7409b03e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -31,7 +31,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -67,7 +67,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/README.md b/README.md index 3bc169dd..0f81b99f 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,29 @@ LNbits ![Lightning network wallet](https://i.imgur.com/EHvK6Lq.png) -# LNbits v0.9 BETA, free and open-source lightning-network wallet/accounts system +# LNbits v0.9 BETA, free and open-source Lightning wallet accounts system (Join us on [https://t.me/lnbits](https://t.me/lnbits)) -(LNbits is beta, for responsible disclosure of any concerns please contact lnbits@pm.me) +LNbits is beta, for responsible disclosure of any concerns please contact lnbits@pm.me Use [legend.lnbits.com](https://legend.lnbits.com), or run your own LNbits server! -LNbits is a very simple Python server that sits on top of any funding source, and can be used as: +LNbits is a Python server that sits on top of any funding source. It can be used as: -* Accounts system to mitigate the risk of exposing applications to your full balance, via unique API keys for each wallet -* Extendable platform for exploring lightning-network functionality via LNbits extension framework +* Accounts system to mitigate the risk of exposing applications to your full balance via unique API keys for each wallet +* Extendable platform for exploring Lightning network functionality via the LNbits extension framework * Part of a development stack via LNbits API * Fallback wallet for the LNURL scheme * Instant wallet for LN demonstrations -LNbits can run on top of any lightning-network funding source, currently there is support for LND, c-lightning, Spark, LNpay, OpenNode, lntxbot, with more being added regularly. +LNbits can run on top of any Lightning funding source. It supports LND, CLN, Eclair, Spark, LNpay, OpenNode, lntxbot, LightningTipBot, and with more being added regularly. See [docs.lnbits.org](https://docs.lnbits.org) for more detailed documentation. Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series. -LNbits is inspired by all the great work of [opennode.com](https://www.opennode.com/), and in particular [lnpay.co](https://lnpay.co/). Both work as excellent funding sources for LNbits. +LNbits is inspired by all the great work of [opennode.com](https://www.opennode.com/), and in particular [lnpay.co](https://lnpay.co/). Both work as funding sources for LNbits. ## Running LNbits @@ -58,16 +58,15 @@ Example use would be an ATM, which utilises LNURL, if the user scans the QR with ![lnurl ATM](https://i.imgur.com/Gi6bn3L.jpg) -## LNbits as an insta-wallet +## LNbits as an instant wallet -Wallets can be easily generated and given out to people at events (one click multi-wallet generation to be added soon). -"Go to this website", has a lot less friction than "Download this app". +Wallets can be easily generated and given out to people at events. "Go to this website", has a lot less friction than "Download this app". ![lnurl ATM](https://i.imgur.com/xFWDnwy.png) ## Tip us -If you like this project and might even use or extend it, why not [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)! +If you like this project [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)! [docs]: https://docs.lnbits.org/ diff --git a/docs/devs/extensions.md b/docs/devs/extensions.md index cd81a021..5e5c2e8b 100644 --- a/docs/devs/extensions.md +++ b/docs/devs/extensions.md @@ -28,7 +28,9 @@ Going over the example extension's structure: Adding new dependencies ----------------------- -If for some reason your extensions needs a new python package to work, you can add a new package using `venv`, or `poerty`: +DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are availabe in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others. + +If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `venv`, or `poerty`: ```sh $ poetry add @@ -37,8 +39,7 @@ $ ./venv/bin/pip install ``` **But we need an extra step to make sure LNbits doesn't break in production.** -Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry`. -`nix` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`. +Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`. SQLite to PostgreSQL migration diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 9f8b26da..2bbdfb11 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -206,6 +206,10 @@ poetry add setuptools wheel ./venv/bin/pip install setuptools wheel ``` +#### Poetry + +If your Poetry version is older than 1.2, for `poetry install`, ignore the `--only main` flag. + ### Optional: PostgreSQL database If you want to use LNbits at scale, we recommend using PostgreSQL as the backend database. Install Postgres and setup a database for LNbits: diff --git a/lnbits/bolt11.py b/lnbits/bolt11.py index 0bc40158..4e20208c 100644 --- a/lnbits/bolt11.py +++ b/lnbits/bolt11.py @@ -4,12 +4,12 @@ import time from decimal import Decimal from typing import List, NamedTuple, Optional -import bitstring # type: ignore +import bitstring import embit import secp256k1 from bech32 import CHARSET, bech32_decode, bech32_encode -from ecdsa import SECP256k1, VerifyingKey # type: ignore -from ecdsa.util import sigdecode_string # type: ignore +from ecdsa import SECP256k1, VerifyingKey +from ecdsa.util import sigdecode_string class Route(NamedTuple): diff --git a/lnbits/core/migrations.py b/lnbits/core/migrations.py index 41ba5644..66254d11 100644 --- a/lnbits/core/migrations.py +++ b/lnbits/core/migrations.py @@ -1,7 +1,7 @@ import datetime from loguru import logger -from sqlalchemy.exc import OperationalError # type: ignore +from sqlalchemy.exc import OperationalError from lnbits import bolt11 diff --git a/lnbits/core/models.py b/lnbits/core/models.py index 31383667..eca1bf50 100644 --- a/lnbits/core/models.py +++ b/lnbits/core/models.py @@ -6,9 +6,9 @@ import time from sqlite3 import Row from typing import Dict, List, Optional -from ecdsa import SECP256k1, SigningKey # type: ignore +from ecdsa import SECP256k1, SigningKey from fastapi import Query -from lnurl import encode as lnurl_encode # type: ignore +from lnurl import encode as lnurl_encode from loguru import logger from pydantic import BaseModel diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 8dc973e7..eefb2f99 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -7,7 +7,7 @@ from urllib.parse import parse_qs, urlparse import httpx from fastapi import Depends, WebSocket from lnurl import LnurlErrorResponse -from lnurl import decode as decode_lnurl # type: ignore +from lnurl import decode as decode_lnurl from loguru import logger from lnbits import bolt11 @@ -44,7 +44,7 @@ from .crud import ( from .models import Payment try: - from typing import TypedDict # type: ignore + from typing import TypedDict except ImportError: # pragma: nocover from typing_extensions import TypedDict diff --git a/lnbits/core/templates/admin/index.html b/lnbits/core/templates/admin/index.html index 81357101..3e688fd6 100644 --- a/lnbits/core/templates/admin/index.html +++ b/lnbits/core/templates/admin/index.html @@ -141,15 +141,15 @@ return { settings: {}, lnbits_theme_options: [ - 'classic', - 'bitcoin', - 'flamingo', - 'freedom', - 'mint', - 'autumn', - 'monochrome', - 'salvador' - ], + 'classic', + 'bitcoin', + 'flamingo', + 'freedom', + 'mint', + 'autumn', + 'monochrome', + 'salvador' + ], formData: {}, formAddAdmin: '', formAddUser: '', @@ -204,11 +204,11 @@ value: null, label: 'Certificate' }, - lnd_admin_macaroon: { + lnd_rest_admin_macaroon: { value: null, label: 'Admin Macaroon' }, - lnd_invoice_macaroon: { + lnd_rest_invoice_macaroon: { value: null, label: 'Invoice Macaroon' } diff --git a/lnbits/core/views/public_api.py b/lnbits/core/views/public_api.py index 56afc176..b5773bbe 100644 --- a/lnbits/core/views/public_api.py +++ b/lnbits/core/views/public_api.py @@ -16,7 +16,7 @@ from ..tasks import api_invoice_listeners @core_app.get("/.well-known/lnurlp/{username}") async def lnaddress(username: str, request: Request): - from lnbits.extensions.lnaddress.lnurl import lnurl_response + from lnbits.extensions.lnaddress.lnurl import lnurl_response # type: ignore domain = urlparse(str(request.url)).netloc return await lnurl_response(username, domain, request) diff --git a/lnbits/db.py b/lnbits/db.py index 1bef7bf2..77f3cf33 100644 --- a/lnbits/db.py +++ b/lnbits/db.py @@ -9,7 +9,7 @@ from typing import Optional from loguru import logger from sqlalchemy import create_engine from sqlalchemy_aio.base import AsyncConnection -from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore +from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY from lnbits.settings import settings @@ -129,7 +129,7 @@ class Database(Compat): else: self.type = POSTGRES - import psycopg2 # type: ignore + import psycopg2 def _parse_timestamp(value, _): if value is None: diff --git a/lnbits/extensions/boltz/models.py b/lnbits/extensions/boltz/models.py index c8ec5646..4f4ec9e2 100644 --- a/lnbits/extensions/boltz/models.py +++ b/lnbits/extensions/boltz/models.py @@ -3,7 +3,7 @@ from typing import Dict, List, Optional from fastapi.params import Query from pydantic.main import BaseModel -from sqlalchemy.engine import base # type: ignore +from sqlalchemy.engine import base class SubmarineSwap(BaseModel): @@ -24,9 +24,9 @@ class SubmarineSwap(BaseModel): class CreateSubmarineSwap(BaseModel): - wallet: str = Query(...) # type: ignore - refund_address: str = Query(...) # type: ignore - amount: int = Query(...) # type: ignore + wallet: str = Query(...) + refund_address: str = Query(...) + amount: int = Query(...) class ReverseSubmarineSwap(BaseModel): @@ -48,13 +48,13 @@ class ReverseSubmarineSwap(BaseModel): class CreateReverseSubmarineSwap(BaseModel): - wallet: str = Query(...) # type: ignore - amount: int = Query(...) # type: ignore - instant_settlement: bool = Query(...) # type: ignore + wallet: str = Query(...) + amount: int = Query(...) + instant_settlement: bool = Query(...) # validate on-address, bcrt1 for regtest addresses onchain_address: str = Query( ..., regex="^(bcrt1|bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$" - ) # type: ignore + ) class SwapStatus(BaseModel): diff --git a/lnbits/extensions/boltz/views_api.py b/lnbits/extensions/boltz/views_api.py index 18ca14cb..34f4033e 100644 --- a/lnbits/extensions/boltz/views_api.py +++ b/lnbits/extensions/boltz/views_api.py @@ -111,7 +111,7 @@ async def api_submarineswap( ) async def api_submarineswap_refund( swap_id: str, - g: WalletTypeInfo = Depends(require_admin_key), # type: ignore + g: WalletTypeInfo = Depends(require_admin_key), ): if swap_id == None: raise HTTPException( @@ -160,7 +160,7 @@ async def api_submarineswap_refund( ) async def api_submarineswap_create( data: CreateSubmarineSwap, - wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore + wallet: WalletTypeInfo = Depends(require_admin_key), ): try: swap_data = await create_swap(data) @@ -257,7 +257,7 @@ async def api_reverse_submarineswap_create( }, ) async def api_swap_status( - swap_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) # type: ignore + swap_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) ): swap = await get_submarine_swap(swap_id) or await get_reverse_submarine_swap( swap_id @@ -290,7 +290,7 @@ async def api_swap_status( response_description="list of pending swaps", ) async def api_check_swaps( - g: WalletTypeInfo = Depends(require_admin_key), # type: ignore + g: WalletTypeInfo = Depends(require_admin_key), all_wallets: bool = Query(False), ): wallet_ids = [g.wallet.id] diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py index e6507bba..83d8ce27 100644 --- a/lnbits/extensions/cashu/__init__.py +++ b/lnbits/extensions/cashu/__init__.py @@ -1,6 +1,6 @@ import asyncio -from environs import Env # type: ignore +from environs import Env from fastapi import APIRouter from fastapi.staticfiles import StaticFiles diff --git a/lnbits/extensions/copilot/lnurl.py b/lnbits/extensions/copilot/lnurl.py index d8ededf3..b0bc83bc 100644 --- a/lnbits/extensions/copilot/lnurl.py +++ b/lnbits/extensions/copilot/lnurl.py @@ -6,7 +6,7 @@ from fastapi import Request from fastapi.param_functions import Query from lnurl.types import LnurlPayMetadata from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse # type: ignore +from starlette.responses import HTMLResponse from lnbits.core.services import create_invoice diff --git a/lnbits/extensions/copilot/models.py b/lnbits/extensions/copilot/models.py index b9b43ccf..7ca2fc96 100644 --- a/lnbits/extensions/copilot/models.py +++ b/lnbits/extensions/copilot/models.py @@ -4,11 +4,11 @@ from typing import Dict, Optional from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse from fastapi.param_functions import Query -from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel from starlette.requests import Request -from lnbits.lnurl import encode as lnurl_encode # type: ignore +from lnbits.lnurl import encode as lnurl_encode class CreateCopilotData(BaseModel): diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py index 7b66366d..ff69dfba 100644 --- a/lnbits/extensions/copilot/views.py +++ b/lnbits/extensions/copilot/views.py @@ -2,7 +2,7 @@ from typing import List from fastapi import Depends, Request from fastapi.templating import Jinja2Templates -from starlette.responses import HTMLResponse # type: ignore +from starlette.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists diff --git a/lnbits/extensions/deezy/README.md b/lnbits/extensions/deezy/README.md new file mode 100644 index 00000000..c8c0678a --- /dev/null +++ b/lnbits/extensions/deezy/README.md @@ -0,0 +1,11 @@ +# Deezy: Home for Lightning Liquidity +Swap lightning bitcoin for on-chain bitcoin to get inbound liquidity. Or get an on-chain deposit address for your lightning address. +* [Website](https://deezy.io) +* [Lightning Node](https://amboss.space/node/024bfaf0cabe7f874fd33ebf7c6f4e5385971fc504ef3f492432e9e3ec77e1b5cf) +* [Documentation](https://docs.deezy.io) +* [Discord](https://discord.gg/nEBbrUAvPy) + +# Usage +This extension lets you swap lightning btc for on-chain btc and vice versa. +* Swap Lightning -> BTC to get inbound liquidity +* Swap BTC -> Lightning to generate an on-chain deposit address for your lightning address \ No newline at end of file diff --git a/lnbits/extensions/deezy/__init__.py b/lnbits/extensions/deezy/__init__.py new file mode 100644 index 00000000..05d1c9a7 --- /dev/null +++ b/lnbits/extensions/deezy/__init__.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter +from starlette.staticfiles import StaticFiles + +from lnbits.db import Database +from lnbits.helpers import template_renderer + +db = Database("ext_deezy") + +deezy_ext: APIRouter = APIRouter(prefix="/deezy", tags=["deezy"]) + +deezy_static_files = [ + { + "path": "/deezy/static", + "app": StaticFiles(directory="lnbits/extensions/deezy/static"), + "name": "deezy_static", + } +] + + +def deezy_renderer(): + return template_renderer(["lnbits/extensions/deezy/templates"]) + + +from .views import * # noqa +from .views_api import * # noqa diff --git a/lnbits/extensions/deezy/config.json b/lnbits/extensions/deezy/config.json new file mode 100644 index 00000000..4f945a79 --- /dev/null +++ b/lnbits/extensions/deezy/config.json @@ -0,0 +1,6 @@ +{ + "name": "Deezy", + "short_description": "LN to onchain, onchain to LN swaps", + "tile": "/deezy/static/deezy.png", + "contributors": ["Uthpala"] +} diff --git a/lnbits/extensions/deezy/crud.py b/lnbits/extensions/deezy/crud.py new file mode 100644 index 00000000..75549349 --- /dev/null +++ b/lnbits/extensions/deezy/crud.py @@ -0,0 +1,115 @@ +from http import HTTPStatus +from typing import List, Optional + +from . import db +from .models import BtcToLnSwap, LnToBtcSwap, Token, UpdateLnToBtcSwap + + +async def get_ln_to_btc() -> List[LnToBtcSwap]: + + rows = await db.fetchall( + f"SELECT * FROM deezy.ln_to_btc_swap ORDER BY created_at DESC", + ) + + return [LnToBtcSwap(**row) for row in rows] + + +async def get_btc_to_ln() -> List[BtcToLnSwap]: + + rows = await db.fetchall( + f"SELECT * FROM deezy.btc_to_ln_swap ORDER BY created_at DESC", + ) + + return [BtcToLnSwap(**row) for row in rows] + + +async def get_token() -> Optional[Token]: + + row = await db.fetchone( + f"SELECT * FROM deezy.token ORDER BY created_at DESC", + ) + + return Token(**row) if row else None + + +async def save_token( + data: Token, +) -> Token: + + await db.execute( + """ + INSERT INTO deezy.token ( + deezy_token + ) + VALUES (?) + """, + (data.deezy_token,), + ) + return data + + +async def save_ln_to_btc( + data: LnToBtcSwap, +) -> LnToBtcSwap: + + return await db.execute( + """ + INSERT INTO deezy.ln_to_btc_swap ( + amount_sats, + on_chain_address, + on_chain_sats_per_vbyte, + bolt11_invoice, + fee_sats, + txid, + tx_hex + ) + VALUES (?,?,?,?,?,?,?) + """, + ( + data.amount_sats, + data.on_chain_address, + data.on_chain_sats_per_vbyte, + data.bolt11_invoice, + data.fee_sats, + data.txid, + data.tx_hex, + ), + ) + + +async def update_ln_to_btc(data: UpdateLnToBtcSwap) -> str: + await db.execute( + """ + UPDATE deezy.ln_to_btc_swap + SET txid = ?, tx_hex = ? + WHERE bolt11_invoice = ? + """, + (data.txid, data.tx_hex, data.bolt11_invoice), + ) + + return data.txid + + +async def save_btc_to_ln( + data: BtcToLnSwap, +) -> BtcToLnSwap: + + return await db.execute( + """ + INSERT INTO deezy.btc_to_ln_swap ( + ln_address, + on_chain_address, + secret_access_key, + commitment, + signature + ) + VALUES (?,?,?,?,?) + """, + ( + data.ln_address, + data.on_chain_address, + data.secret_access_key, + data.commitment, + data.signature, + ), + ) diff --git a/lnbits/extensions/deezy/migrations.py b/lnbits/extensions/deezy/migrations.py new file mode 100644 index 00000000..67455d6b --- /dev/null +++ b/lnbits/extensions/deezy/migrations.py @@ -0,0 +1,37 @@ +async def m001_initial(db): + await db.execute( + f""" + CREATE TABLE deezy.ln_to_btc_swap ( + id TEXT PRIMARY KEY, + amount_sats {db.big_int} NOT NULL, + on_chain_address TEXT NOT NULL, + on_chain_sats_per_vbyte INT NOT NULL, + bolt11_invoice TEXT NOT NULL, + fee_sats {db.big_int} NOT NULL, + txid TEXT NULL, + tx_hex TEXT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + """ + ) + await db.execute( + f""" + CREATE TABLE deezy.btc_to_ln_swap ( + id TEXT PRIMARY KEY, + ln_address TEXT NOT NULL, + on_chain_address TEXT NOT NULL, + secret_access_key TEXT NOT NULL, + commitment TEXT NOT NULL, + signature TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + """ + ) + await db.execute( + f""" + CREATE TABLE deezy.token ( + deezy_token TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + """ + ) diff --git a/lnbits/extensions/deezy/models.py b/lnbits/extensions/deezy/models.py new file mode 100644 index 00000000..e69db355 --- /dev/null +++ b/lnbits/extensions/deezy/models.py @@ -0,0 +1,34 @@ +from typing import Optional + +from pydantic.main import BaseModel +from sqlalchemy.engine import base # type: ignore + + +class Token(BaseModel): + deezy_token: str + + +class LnToBtcSwap(BaseModel): + amount_sats: int + on_chain_address: str + on_chain_sats_per_vbyte: int + bolt11_invoice: str + fee_sats: int + txid: str = "" + tx_hex: str = "" + created_at: str = "" + + +class UpdateLnToBtcSwap(BaseModel): + txid: str + tx_hex: str + bolt11_invoice: str + + +class BtcToLnSwap(BaseModel): + ln_address: str + on_chain_address: str + secret_access_key: str + commitment: str + signature: str + created_at: str = "" diff --git a/lnbits/extensions/deezy/static/deezy.png b/lnbits/extensions/deezy/static/deezy.png new file mode 100644 index 00000000..cb526705 Binary files /dev/null and b/lnbits/extensions/deezy/static/deezy.png differ diff --git a/lnbits/extensions/deezy/templates/deezy/_api_docs.html b/lnbits/extensions/deezy/templates/deezy/_api_docs.html new file mode 100644 index 00000000..4a4e9e30 --- /dev/null +++ b/lnbits/extensions/deezy/templates/deezy/_api_docs.html @@ -0,0 +1,253 @@ + + + + +
+ Deezy.io: Do onchain to offchain and vice-versa swaps +
+

+ Link : + + https://deezy.io/ + +

+

+ API DOCS +

+

+ Created by, + Uthpala +

+
+
+
+ + + + + +
+ Get the current info about the swap service for converting LN btc to + on-chain BTC. +
+ + GET (mainnet) + https://api.deezy.io/v1/swap/info + +
+ + GET (testnet) + https://api-testnet.deezy.io/v1/swap/info + +
Response
+
+            {
+              "liquidity_fee_ppm": 2000,
+              "on_chain_bytes_estimate": 300,
+              "max_swap_amount_sats": 100000000,
+              "min_swap_amount_sats": 100000,
+              "available": true
+            }
+          
+
+
+
+ + + +
+ Initiate a new swap to send lightning btc in exchange for on-chain + btc +
+ + POST (mainnet) + https://api.deezy.io/v1/swap + +
+ + POST (testnet) + https://api-testnet.deezy.io/v1/swap + +
Payload
+
+            {
+              "amount_sats": 500000,
+              "on_chain_address": "tb1qrcdhlm0m...",
+              "on_chain_sats_per_vbyte": 2
+            }
+          
+
Response
+
+            {
+              "bolt11_invoice": "lntb603u1p3vmxj7p...",
+              "fee_sats": 600
+            }
+          
+
+
+
+ + + +
+ Lookup the on-chain transaction information for an existing swap +
+ + GET (mainnet) + https://api.deezy.io/v1/swap/lookup + +
+ + GET (testnet) + https://api-testnet.deezy.io/v1/swap/lookup + +
Query Parameter
+
+            "bolt11_invoice": "lntb603u1p3vmxj7pp54...",
+          
+
Response
+
+            {
+              "on_chain_txid": "string",
+              "tx_hex": "string"
+            }
+          
+
+
+
+
+ + + + +
+ Generate an on-chain deposit address for your lnurl or lightning + address. +
+ + POST (mainnet) + https://api.deezy.io/v1/source + +
+ + POST (testnet) + https://api-testnet.deezy.io/v1/source + +
Payload
+
+            {
+              "lnurl_or_lnaddress": "LNURL1DP68GURN8GHJ...",
+              "secret_access_key": "b3c6056d2845867fa7..",
+              "webhook_url": "https://your.website.com/dee.."
+            }
+          
+
Response
+
+            {
+              "address": "bc1qkceyc5...",
+              "secret_access_key": "b3c6056d28458...",
+              "commitment": "for any satoshis sent to bc1..",
+              "signature": "d69j6aj1ssz5egmsr..",
+              "webhook_url": "https://your.website.com/deez.."
+            }
+          
+
+
+
+ + + +
+ Lookup (BTC to LN) swaps +
+ + GET (mainnet) + https://api.deezy.io/v1/source/lookup + +
+ + GET (testnet) + https://api-testnet.deezy.io/v1/source/lookup + +
Response
+
+            {
+              "swaps": [
+                {
+                  "lnurl_or_lnaddress": "string",
+                  "deposit_address": "string",
+                  "utxo_key": "string",
+                  "deposit_amount_sats": 0,
+                  "target_payout_amount_sats": 0,
+                  "paid_amount_sats": 0,
+                  "deezy_fee_sats": 0,
+                  "status": "string"
+                }
+              ],
+              "total_sent_sats": 0,
+              "total_received_sats": 0,
+              "total_pending_payout_sats": 0,
+              "total_deezy_fees_sats": 0
+            }
+          
+
+
+
+
+
diff --git a/lnbits/extensions/deezy/templates/deezy/index.html b/lnbits/extensions/deezy/templates/deezy/index.html new file mode 100644 index 00000000..858d3255 --- /dev/null +++ b/lnbits/extensions/deezy/templates/deezy/index.html @@ -0,0 +1,588 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} +
+
+ + +
Deezy
+

+ An access token is required to use the swap service. Email + support@deezy.io or contact @dannydeezy on telegram to get one. +

+
+
+ Deezy token + Add or Update token +
+

+
+ + + + + + + + + + Send lightning btc and receive on-chain btc + + + + + Send on-chain btc and receive via lightning + + + + +
+
LIGHTNING BTC -> BTC
+ + + + + + + Cancel + + + + +
+
Pay invoice to complete swap
+ + + +
+
+ + + + + + + +
+
+
+
+
BTC -> LIGHTNING BTC
+ + + + Cancel + + + + +
+
Onchain Address
+ + + +
+
+ + + + + + + + + +
+
+
+
+
+ {% raw %} + + + +
Success Bitcoin is on its way
+
+ + + Onchain tx id {{ swapLnToBtc.onchainTxId }} + + + + + +
+
+ {% endraw %} +
+
+ + +
{{SITE_TITLE}} Boltz extension
+
+ + + {% include "deezy/_api_docs.html" %} + +
+
+
+ +
+
+ +
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/deezy/views.py b/lnbits/extensions/deezy/views.py new file mode 100644 index 00000000..131c03b2 --- /dev/null +++ b/lnbits/extensions/deezy/views.py @@ -0,0 +1,21 @@ +from fastapi import FastAPI, Request +from fastapi.params import Depends +from fastapi.templating import Jinja2Templates +from starlette.responses import HTMLResponse + +from lnbits.core.models import User +from lnbits.decorators import check_user_exists + +from . import deezy_ext, deezy_renderer + +templates = Jinja2Templates(directory="templates") + + +@deezy_ext.get("/", response_class=HTMLResponse) +async def index( + request: Request, + user: User = Depends(check_user_exists), # type: ignore +): + return deezy_renderer().TemplateResponse( + "deezy/index.html", {"request": request, "user": user.dict()} + ) diff --git a/lnbits/extensions/deezy/views_api.py b/lnbits/extensions/deezy/views_api.py new file mode 100644 index 00000000..1006edeb --- /dev/null +++ b/lnbits/extensions/deezy/views_api.py @@ -0,0 +1,65 @@ +# views_api.py is for you API endpoints that could be hit by another service + +# add your dependencies here + +# import httpx +# (use httpx just like requests, except instead of response.ok there's only the +# response.is_error that is its inverse) + +from . import deezy_ext +from .crud import ( + get_btc_to_ln, + get_ln_to_btc, + get_token, + save_btc_to_ln, + save_ln_to_btc, + save_token, + update_ln_to_btc, +) +from .models import BtcToLnSwap, LnToBtcSwap, Token, UpdateLnToBtcSwap + + +@deezy_ext.get("/api/v1/token") +async def api_deezy_get_token(): + rows = await get_token() + return rows + + +@deezy_ext.get("/api/v1/ln-to-btc") +async def api_deezy_get_ln_to_btc(): + rows = await get_ln_to_btc() + return rows + + +@deezy_ext.get("/api/v1/btc-to-ln") +async def api_deezy_get_btc_to_ln(): + rows = await get_btc_to_ln() + return rows + + +@deezy_ext.post("/api/v1/store-token") +async def api_deezy_save_toke(data: Token): + await save_token(data) + + return data.deezy_token + + +@deezy_ext.post("/api/v1/store-ln-to-btc") +async def api_deezy_save_ln_to_btc(data: LnToBtcSwap): + response = await save_ln_to_btc(data) + + return response + + +@deezy_ext.post("/api/v1/update-ln-to-btc") +async def api_deezy_update_ln_to_btc(data: UpdateLnToBtcSwap): + response = await update_ln_to_btc(data) + + return response + + +@deezy_ext.post("/api/v1/store-btc-to-ln") +async def api_deezy_save_btc_to_ln(data: BtcToLnSwap): + response = await save_btc_to_ln(data) + + return response diff --git a/lnbits/extensions/events/tasks.py b/lnbits/extensions/events/tasks.py index 5eae7373..945e2d28 100644 --- a/lnbits/extensions/events/tasks.py +++ b/lnbits/extensions/events/tasks.py @@ -1,10 +1,10 @@ import asyncio from lnbits.core.models import Payment -from lnbits.extensions.events.models import CreateTicket from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener +from .models import CreateTicket from .views_api import api_ticket_send_ticket diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index 4ed3932f..5ec9b916 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -7,7 +7,6 @@ from lnbits.core.crud import get_user from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.events.models import CreateEvent, CreateTicket from . import events_ext from .crud import ( @@ -24,6 +23,7 @@ from .crud import ( reg_ticket, update_event, ) +from .models import CreateEvent, CreateTicket # Events diff --git a/lnbits/extensions/example/static/qrcode-example.png b/lnbits/extensions/example/static/qrcode-example.png new file mode 100644 index 00000000..29781f53 Binary files /dev/null and b/lnbits/extensions/example/static/qrcode-example.png differ diff --git a/lnbits/extensions/example/static/qrcode-example1.png b/lnbits/extensions/example/static/qrcode-example1.png new file mode 100644 index 00000000..a6c748f1 Binary files /dev/null and b/lnbits/extensions/example/static/qrcode-example1.png differ diff --git a/lnbits/extensions/example/static/websocket-example.png b/lnbits/extensions/example/static/websocket-example.png new file mode 100644 index 00000000..52171d30 Binary files /dev/null and b/lnbits/extensions/example/static/websocket-example.png differ diff --git a/lnbits/extensions/example/templates/example/index.html b/lnbits/extensions/example/templates/example/index.html index 03f66b5f..36d325bb 100644 --- a/lnbits/extensions/example/templates/example/index.html +++ b/lnbits/extensions/example/templates/example/index.html @@ -51,8 +51,15 @@
- {{SITE_TITLE}} Extension Development Guide - (Collection of resources for extension developers) + Extension Development Guide + (also check the + docs)
@@ -188,8 +195,8 @@

LNbits uses Vue - components for best-in-class high-performance and responsive - performance. + for best-in-class, responsive and high-performance + components.

Typical example of Vue components in a frontend script:

@@ -199,8 +206,7 @@ />

- In a page body, models can be called.
Content can be - conditionally rendered using Vue's + Content can be conditionally rendered using Vue's v-if:

MAGICAL G EXCHANGE RATES + QR CODES + WEBSOCKETS @@ -255,6 +263,85 @@ >:
+ +
QR Codes
+

+ For most purposes use Quasar's inbuilt VueQrcode library: +

+ +

+ LNbits does also include a handy + + QR code enpoint +

+ {% raw %} You can use via + {{protocol + location}}{% endraw + %}/api/v1/qrcode/some-data-you-want-in-a-qrcode:
+
+ +
+ + +
+
+ + +
Websockets
+

+ Fastapi includes a great + websocket tool +

+ {% raw %} +

+ A few LNbits extensions also make use of a weird and useful + websocket/GET tool built into LNbits, such as extensions + Copilot and LNURLDevices
+ You can subscribe to websocket with + wss:{{location}}/api/v1/ws/{SOME-ID}
+ You can post to any clients subscribed to the endpoint with + {{protocol + + location}}/api/v1/ws/{SOME-ID}/{THE-DATA-YOU-WANT-TO-POST}
+
+

+ DEMO: Hit + {{protocol + + location}}/api/v1/ws/32872r23g29/blah%20blah%20blah + in a different browser window to change this text to + `blah blah blah`. +
+
+ Function used in this demo:
+

+ + {% endraw %} @@ -296,6 +383,8 @@ data: function () { return { ///// Declare models/variables ///// + protocol: window.location.protocol, + location: '//' + window.location.hostname, thingDialog: { show: false, data: {} @@ -310,7 +399,7 @@ }, ///// Where functions live ///// methods: { - exampleFunction(data) { + exampleFunction: function (data) { var theData = data LNbits.api .request( @@ -325,6 +414,28 @@ LNbits.utils.notifyApiError(error) // Error will be passed to the frontend }) }, + initWs: async function () { + if (location.protocol !== 'http:') { + localUrl = + 'wss://' + + document.domain + + ':' + + location.port + + '/api/v1/ws/32872r23g29' + } else { + localUrl = + 'ws://' + + document.domain + + ':' + + location.port + + '/api/v1/ws/32872r23g29' + } + this.ws = new WebSocket(localUrl) + this.ws.addEventListener('message', async ({data}) => { + const res = data.toString() + document.getElementById('text-to-change').innerHTML = res + }) + }, sendThingDialog() { console.log(this.thingDialog) } @@ -333,6 +444,7 @@ created: function () { self = this // Often used to run a real object, rather than the event (all a bit confusing really) self.exampleFunction('lorum') + self.initWs() } }) diff --git a/lnbits/extensions/livestream/lnurl.py b/lnbits/extensions/livestream/lnurl.py index 63523f33..e3e1b1be 100644 --- a/lnbits/extensions/livestream/lnurl.py +++ b/lnbits/extensions/livestream/lnurl.py @@ -1,12 +1,9 @@ -import hashlib import math from http import HTTPStatus -from os import name -from fastapi.exceptions import HTTPException -from fastapi.params import Query +from fastapi import HTTPException, Query, Request from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse -from starlette.requests import Request # type: ignore +from lnurl.models import ClearnetUrl, LightningInvoice, MilliSatoshi from lnbits.core.services import create_invoice @@ -29,9 +26,12 @@ async def lnurl_livestream(ls_id, request: Request): ) resp = LnurlPayResponse( - callback=request.url_for("livestream.lnurl_callback", track_id=track.id), - min_sendable=track.min_sendable, - max_sendable=track.max_sendable, + callback=ClearnetUrl( + request.url_for("livestream.lnurl_callback", track_id=track.id), + scheme="https", + ), + minSendable=MilliSatoshi(track.min_sendable), + maxSendable=MilliSatoshi(track.max_sendable), metadata=await track.lnurlpay_metadata(), ) @@ -48,9 +48,12 @@ async def lnurl_track(track_id, request: Request): raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Track not found.") resp = LnurlPayResponse( - callback=request.url_for("livestream.lnurl_callback", track_id=track.id), - min_sendable=track.min_sendable, - max_sendable=track.max_sendable, + callback=ClearnetUrl( + request.url_for("livestream.lnurl_callback", track_id=track.id), + scheme="https", + ), + minSendable=MilliSatoshi(track.min_sendable), + maxSendable=MilliSatoshi(track.max_sendable), metadata=await track.lnurlpay_metadata(), ) @@ -85,6 +88,7 @@ async def lnurl_callback( ).dict() ls = await get_livestream_by_track(track_id) + assert ls extra_amount = amount_received - int(amount_received * (100 - ls.fee_pct) / 100) @@ -101,13 +105,14 @@ async def lnurl_callback( }, ) + assert track.price_msat if amount_received < track.price_msat: success_action = None else: success_action = track.success_action(payment_hash, request=request) resp = LnurlPayActionResponse( - pr=payment_request, success_action=success_action, routes=[] + pr=LightningInvoice(payment_request), successAction=success_action, routes=[] ) return resp.dict() diff --git a/lnbits/extensions/livestream/models.py b/lnbits/extensions/livestream/models.py index 0034f4a7..5d617da9 100644 --- a/lnbits/extensions/livestream/models.py +++ b/lnbits/extensions/livestream/models.py @@ -1,13 +1,12 @@ import json from typing import Optional -from fastapi import Query +from fastapi import Query, Request from lnurl import Lnurl -from lnurl import encode as lnurl_encode # type: ignore -from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore -from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl import encode as lnurl_encode +from lnurl.models import ClearnetUrl, Max144Str, UrlAction +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel -from starlette.requests import Request class CreateTrack(BaseModel): @@ -32,7 +31,7 @@ class Livestream(BaseModel): class Track(BaseModel): id: int download_url: Optional[str] - price_msat: Optional[int] + price_msat: int = 0 name: str producer: int @@ -71,7 +70,7 @@ class Track(BaseModel): def success_action( self, payment_hash: str, request: Request - ) -> Optional[LnurlPaySuccessAction]: + ) -> Optional[UrlAction]: if not self.download_url: return None @@ -79,7 +78,8 @@ class Track(BaseModel): url_with_query = f"{url}?p={payment_hash}" return UrlAction( - url=url_with_query, description=f"Download the track {self.name}!" + url=ClearnetUrl(url_with_query, scheme="https"), + description=Max144Str(f"Download the track {self.name}!"), ) diff --git a/lnbits/extensions/livestream/views.py b/lnbits/extensions/livestream/views.py index 97f803a3..ca12f16b 100644 --- a/lnbits/extensions/livestream/views.py +++ b/lnbits/extensions/livestream/views.py @@ -1,20 +1,16 @@ from http import HTTPStatus -from fastapi.param_functions import Depends -from fastapi.params import Query -from starlette.exceptions import HTTPException -from starlette.requests import Request +from fastapi import Depends, HTTPException, Query, Request +from starlette.datastructures import URL from starlette.responses import HTMLResponse, RedirectResponse from lnbits.core.crud import get_wallet_payment -from lnbits.core.models import Payment, User +from lnbits.core.models import User from lnbits.decorators import check_user_exists from . import livestream_ext, livestream_renderer from .crud import get_livestream_by_track, get_track -# from mmap import MAP_DENYWRITE - @livestream_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): @@ -28,12 +24,18 @@ async def track_redirect_download(track_id, p: str = Query(...)): payment_hash = p track = await get_track(track_id) ls = await get_livestream_by_track(track_id) - payment: Payment = await get_wallet_payment(ls.wallet, payment_hash) + assert ls + payment = await get_wallet_payment(ls.wallet, payment_hash) if not payment: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, - detail=f"Couldn't find the payment {payment_hash} or track {track.id}.", + detail=f"Couldn't find the payment {payment_hash}.", + ) + if not track: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail=f"Couldn't find the track {track_id}.", ) if payment.pending: @@ -41,4 +43,6 @@ async def track_redirect_download(track_id, p: str = Query(...)): status_code=HTTPStatus.PAYMENT_REQUIRED, detail=f"Payment {payment_hash} wasn't received yet. Please try again in a minute.", ) - return RedirectResponse(url=track.download_url) + + assert track.download_url + return RedirectResponse(url=URL(track.download_url)) diff --git a/lnbits/extensions/livestream/views_api.py b/lnbits/extensions/livestream/views_api.py index 0c169a71..b346f353 100644 --- a/lnbits/extensions/livestream/views_api.py +++ b/lnbits/extensions/livestream/views_api.py @@ -1,12 +1,9 @@ from http import HTTPStatus -from fastapi.param_functions import Depends +from fastapi import Depends, HTTPException, Request from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl -from starlette.exceptions import HTTPException -from starlette.requests import Request # type: ignore from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.livestream.models import CreateTrack from . import livestream_ext from .crud import ( @@ -20,6 +17,7 @@ from .crud import ( update_livestream_fee, update_track, ) +from .models import CreateTrack @livestream_ext.get("/api/v1/livestream") @@ -27,6 +25,7 @@ async def api_livestream_from_wallet( req: Request, g: WalletTypeInfo = Depends(get_key_type) ): ls = await get_or_create_livestream_by_wallet(g.wallet.id) + assert ls tracks = await get_tracks(ls.id) producers = await get_producers(ls.id) @@ -55,17 +54,17 @@ async def api_update_track(track_id, g: WalletTypeInfo = Depends(get_key_type)): id = int(track_id) except ValueError: id = 0 - if id <= 0: - id = None ls = await get_or_create_livestream_by_wallet(g.wallet.id) - await update_current_track(ls.id, id) + assert ls + await update_current_track(ls.id, None if id <= 0 else id) return "", HTTPStatus.NO_CONTENT @livestream_ext.put("/api/v1/livestream/fee/{fee_pct}") async def api_update_fee(fee_pct, g: WalletTypeInfo = Depends(get_key_type)): ls = await get_or_create_livestream_by_wallet(g.wallet.id) + assert ls await update_livestream_fee(ls.id, int(fee_pct)) return "", HTTPStatus.NO_CONTENT @@ -76,9 +75,10 @@ async def api_add_track( data: CreateTrack, id=None, g: WalletTypeInfo = Depends(get_key_type) ): ls = await get_or_create_livestream_by_wallet(g.wallet.id) + assert ls if data.producer_id: - p_id = data.producer_id + p_id = int(data.producer_id) elif data.producer_name: p_id = await add_producer(ls.id, data.producer_name) else: @@ -96,5 +96,6 @@ async def api_add_track( @livestream_ext.delete("/api/v1/livestream/tracks/{track_id}") async def api_delete_track(track_id, g: WalletTypeInfo = Depends(get_key_type)): ls = await get_or_create_livestream_by_wallet(g.wallet.id) + assert ls await delete_track_from_livestream(ls.id, track_id) return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/lnaddress/cloudflare.py b/lnbits/extensions/lnaddress/cloudflare.py index 679cb515..cf8feaf0 100644 --- a/lnbits/extensions/lnaddress/cloudflare.py +++ b/lnbits/extensions/lnaddress/cloudflare.py @@ -2,7 +2,7 @@ import json import httpx -from lnbits.extensions.lnaddress.models import Domains +from .models import Domains async def cloudflare_create_record(domain: Domains, ip: str): diff --git a/lnbits/extensions/lnaddress/views_api.py b/lnbits/extensions/lnaddress/views_api.py index d9e50e9d..7d15a55f 100644 --- a/lnbits/extensions/lnaddress/views_api.py +++ b/lnbits/extensions/lnaddress/views_api.py @@ -6,7 +6,6 @@ from fastapi import Depends, HTTPException, Query, Request from lnbits.core.crud import get_user from lnbits.core.services import check_transaction_status, create_invoice from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.lnaddress.models import CreateAddress, CreateDomain from . import lnaddress_ext from .cloudflare import cloudflare_create_record @@ -23,6 +22,7 @@ from .crud import ( get_domains, update_domain, ) +from .models import CreateAddress, CreateDomain # DOMAINS diff --git a/lnbits/extensions/lndhub/decorators.py b/lnbits/extensions/lndhub/decorators.py index fcadce27..48118087 100644 --- a/lnbits/extensions/lndhub/decorators.py +++ b/lnbits/extensions/lndhub/decorators.py @@ -5,7 +5,7 @@ from fastapi.param_functions import Security from fastapi.security.api_key import APIKeyHeader from starlette.exceptions import HTTPException -from lnbits.decorators import WalletTypeInfo, get_key_type # type: ignore +from lnbits.decorators import WalletTypeInfo, get_key_type api_key_header_auth = APIKeyHeader( name="AUTHORIZATION", diff --git a/lnbits/extensions/lnticket/views_api.py b/lnbits/extensions/lnticket/views_api.py index 35d6eaff..4462688b 100644 --- a/lnbits/extensions/lnticket/views_api.py +++ b/lnbits/extensions/lnticket/views_api.py @@ -8,7 +8,6 @@ from lnbits.core.crud import get_user from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.lnticket.models import CreateFormData, CreateTicketData from . import lnticket_ext from .crud import ( @@ -23,6 +22,7 @@ from .crud import ( set_ticket_paid, update_form, ) +from .models import CreateFormData, CreateTicketData # FORMS diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index 182df743..0ab520da 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -7,8 +7,6 @@ from lnbits.helpers import urlsafe_short_hash from . import db from .models import createLnurldevice, lnurldevicepayment, lnurldevices -###############lnurldeviceS########################## - async def create_lnurldevice( data: createLnurldevice, @@ -69,10 +67,12 @@ async def create_lnurldevice( data.pin4, ), ) - return await get_lnurldevice(lnurldevice_id) + device = await get_lnurldevice(lnurldevice_id) + assert device + return device -async def update_lnurldevice(lnurldevice_id: str, **kwargs) -> Optional[lnurldevices]: +async def update_lnurldevice(lnurldevice_id: str, **kwargs) -> lnurldevices: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( f"UPDATE lnurldevice.lnurldevices SET {q} WHERE id = ?", @@ -81,19 +81,18 @@ async def update_lnurldevice(lnurldevice_id: str, **kwargs) -> Optional[lnurldev row = await db.fetchone( "SELECT * FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) ) - return lnurldevices(**row) if row else None + return lnurldevices(**row) -async def get_lnurldevice(lnurldevice_id: str) -> lnurldevices: +async def get_lnurldevice(lnurldevice_id: str) -> Optional[lnurldevices]: row = await db.fetchone( "SELECT * FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) ) return lnurldevices(**row) if row else None -async def get_lnurldevices(wallet_ids: Union[str, List[str]]) -> List[lnurldevices]: - wallet_ids = [wallet_ids] - q = ",".join(["?"] * len(wallet_ids[0])) +async def get_lnurldevices(wallet_ids: List[str]) -> List[lnurldevices]: + q = ",".join(["?"] * len(wallet_ids)) rows = await db.fetchall( f""" SELECT * FROM lnurldevice.lnurldevices WHERE wallet IN ({q}) @@ -102,7 +101,7 @@ async def get_lnurldevices(wallet_ids: Union[str, List[str]]) -> List[lnurldevic (*wallet_ids,), ) - return [lnurldevices(**row) if row else None for row in rows] + return [lnurldevices(**row) for row in rows] async def delete_lnurldevice(lnurldevice_id: str) -> None: @@ -110,8 +109,6 @@ async def delete_lnurldevice(lnurldevice_id: str) -> None: "DELETE FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) ) - ########################lnuldevice payments########################### - async def create_lnurldevicepayment( deviceid: str, @@ -121,6 +118,7 @@ async def create_lnurldevicepayment( sats: Optional[int] = 0, ) -> lnurldevicepayment: device = await get_lnurldevice(deviceid) + assert device if device.device == "atm": lnurldevicepayment_id = shortuuid.uuid(name=payload) else: @@ -139,7 +137,9 @@ async def create_lnurldevicepayment( """, (lnurldevicepayment_id, deviceid, payload, pin, payhash, sats), ) - return await get_lnurldevicepayment(lnurldevicepayment_id) + dpayment = await get_lnurldevicepayment(lnurldevicepayment_id) + assert dpayment + return dpayment async def update_lnurldevicepayment( @@ -157,7 +157,9 @@ async def update_lnurldevicepayment( return lnurldevicepayment(**row) if row else None -async def get_lnurldevicepayment(lnurldevicepayment_id: str) -> lnurldevicepayment: +async def get_lnurldevicepayment( + lnurldevicepayment_id: str, +) -> Optional[lnurldevicepayment]: row = await db.fetchone( "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", (lnurldevicepayment_id,), @@ -165,7 +167,9 @@ async def get_lnurldevicepayment(lnurldevicepayment_id: str) -> lnurldevicepayme return lnurldevicepayment(**row) if row else None -async def get_lnurlpayload(lnurldevicepayment_payload: str) -> lnurldevicepayment: +async def get_lnurlpayload( + lnurldevicepayment_payload: str, +) -> Optional[lnurldevicepayment]: row = await db.fetchone( "SELECT * FROM lnurldevice.lnurldevicepayment WHERE payload = ?", (lnurldevicepayment_payload,), diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index 34de20fa..eba2a693 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -1,16 +1,11 @@ import base64 -import hashlib import hmac from http import HTTPStatus from io import BytesIO -from typing import Optional import shortuuid from embit import bech32, compact -from fastapi import Request -from fastapi.param_functions import Query -from loguru import logger -from starlette.exceptions import HTTPException +from fastapi import HTTPException, Query, Request from lnbits import bolt11 from lnbits.core.services import create_invoice @@ -44,7 +39,9 @@ def bech32_decode(bech): encoding = bech32.bech32_verify_checksum(hrp, data) if encoding is None: return - return bytes(bech32.convertbits(data[:-6], 5, 8, False)) + bits = bech32.convertbits(data[:-6], 5, 8, False) + assert bits + return bytes(bits) def xor_decrypt(key, blob): @@ -105,6 +102,8 @@ async def lnurl_v1_params( "reason": f"lnurldevice {device_id} not found on this server", } if device.device == "switch": + # TODO: AMOUNT IN CENT was never reference here + amount_in_cent = 0 price_msat = ( await fiat_amount_as_satoshis(float(profit), device.currency) if device.currency != "sat" @@ -160,23 +159,18 @@ async def lnurl_v1_params( if device.device != "atm": return {"status": "ERROR", "reason": "Not ATM device."} price_msat = int(price_msat * (1 - (device.profit / 100)) / 1000) - lnurldevicepayment = await get_lnurldevicepayment(shortuuid.uuid(name=p)) - if lnurldevicepayment: - logger.debug("lnurldevicepayment") - logger.debug(lnurldevicepayment) - logger.debug("lnurldevicepayment") - if lnurldevicepayment.payload == lnurldevicepayment.payhash: - return {"status": "ERROR", "reason": f"Payment already claimed"} - else: + try: lnurldevicepayment = await create_lnurldevicepayment( deviceid=device.id, payload=p, sats=price_msat * 1000, - pin=pin, + pin=str(pin), payhash="payment_hash", ) + except: + return {"status": "ERROR", "reason": "Could not create ATM payment."} if not lnurldevicepayment: - return {"status": "ERROR", "reason": "Could not create payment."} + return {"status": "ERROR", "reason": "Could not create ATM payment."} return { "tag": "withdrawRequest", "callback": request.url_for( @@ -193,7 +187,7 @@ async def lnurl_v1_params( deviceid=device.id, payload=p, sats=price_msat * 1000, - pin=pin, + pin=str(pin), payhash="payment_hash", ) if not lnurldevicepayment: @@ -221,6 +215,10 @@ async def lnurl_callback( k1: str = Query(None), ): lnurldevicepayment = await get_lnurldevicepayment(paymentid) + if not lnurldevicepayment: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="lnurldevicepayment not found." + ) device = await get_lnurldevice(lnurldevicepayment.deviceid) if not device: raise HTTPException( @@ -241,13 +239,17 @@ async def lnurl_callback( else: if lnurldevicepayment.payload != k1: return {"status": "ERROR", "reason": "Bad K1"} - lnurldevicepayment = await update_lnurldevicepayment( + if lnurldevicepayment.payhash != "payment_hash": + return {"status": "ERROR", "reason": f"Payment already claimed"} + + lnurldevicepayment_updated = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload ) + assert lnurldevicepayment_updated await pay_invoice( wallet_id=device.wallet, payment_request=pr, - max_sat=lnurldevicepayment.sats / 1000, + max_sat=int(lnurldevicepayment_updated.sats / 1000), extra={"tag": "withdraw"}, ) return {"status": "OK"} diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index 66b215f2..f9640de1 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -3,13 +3,9 @@ from sqlite3 import Row from typing import List, Optional from fastapi import Request -from lnurl import Lnurl -from lnurl import encode as lnurl_encode # type: ignore -from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore -from lnurl.types import LnurlPayMetadata # type: ignore -from loguru import logger +from lnurl import encode as lnurl_encode +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel -from pydantic.main import BaseModel class createLnurldevice(BaseModel): @@ -58,6 +54,7 @@ class lnurldevices(BaseModel): pin4: int timestamp: str + @classmethod def from_row(cls, row: Row) -> "lnurldevices": return cls(**dict(row)) diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 8ad9772c..9aec173e 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -1,18 +1,11 @@ import asyncio -import json -from http import HTTPStatus -from urllib.parse import urlparse -import httpx -from fastapi import HTTPException - -from lnbits import bolt11 from lnbits.core.models import Payment -from lnbits.core.services import pay_invoice, websocketUpdater +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_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment +from .crud import get_lnurldevicepayment, update_lnurldevicepayment async def wait_for_paid_invoices(): @@ -27,14 +20,15 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: # (avoid loops) if "Switch" == payment.extra.get("tag"): - lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id")) + lnurldevicepayment = await get_lnurldevicepayment(payment.extra["id"]) if not lnurldevicepayment: return if lnurldevicepayment.payhash == "used": return lnurldevicepayment = await update_lnurldevicepayment( - lnurldevicepayment_id=payment.extra.get("id"), payhash="used" + lnurldevicepayment_id=payment.extra["id"], payhash="used" ) + assert lnurldevicepayment return await websocketUpdater( lnurldevicepayment.deviceid, str(lnurldevicepayment.pin) + "-" + str(lnurldevicepayment.payload), diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index f1be4f0d..a6256a41 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -1,12 +1,7 @@ from http import HTTPStatus -from io import BytesIO -import pyqrcode -from fastapi import Request -from fastapi.param_functions import Query -from fastapi.params import Depends +from fastapi import Depends, HTTPException, Query, Request from fastapi.templating import Jinja2Templates -from starlette.exceptions import HTTPException from starlette.responses import HTMLResponse, StreamingResponse from lnbits.core.crud import update_payment_status @@ -62,4 +57,6 @@ async def img(request: Request, lnurldevice_id): raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist." ) - return lnurldevice.lnurl(request) + # error: "lnurldevices" has no attribute "lnurl" + # return lnurldevice.lnurl(request) + return None diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index c6766423..2fd1bd12 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -1,13 +1,9 @@ from http import HTTPStatus -from fastapi import Request -from fastapi.param_functions import Query -from fastapi.params import Depends -from starlette.exceptions import HTTPException +from fastapi import Depends, HTTPException, Query, Request from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.extensions.lnurldevice import lnurldevice_ext from lnbits.utils.exchange_rates import currencies from . import lnurldevice_ext @@ -26,9 +22,6 @@ async def api_list_currencies_available(): return list(currencies.keys()) -#######################lnurldevice########################## - - @lnurldevice_ext.post("/api/v1/lnurlpos") @lnurldevice_ext.put("/api/v1/lnurlpos/{lnurldevice_id}") async def api_lnurldevice_create_or_update( @@ -41,7 +34,7 @@ async def api_lnurldevice_create_or_update( lnurldevice = await create_lnurldevice(data) return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} else: - lnurldevice = await update_lnurldevice(data, lnurldevice_id=lnurldevice_id) + lnurldevice = await update_lnurldevice(lnurldevice_id, **data.dict()) return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} @@ -49,7 +42,8 @@ async def api_lnurldevice_create_or_update( async def api_lnurldevices_retrieve( req: Request, wallet: WalletTypeInfo = Depends(get_key_type) ): - wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids + user = await get_user(wallet.wallet.user) + wallet_ids = user.wallet_ids if user else [] try: return [ {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} @@ -65,10 +59,11 @@ async def api_lnurldevices_retrieve( return "" -@lnurldevice_ext.get("/api/v1/lnurlpos/{lnurldevice_id}") +@lnurldevice_ext.get( + "/api/v1/lnurlpos/{lnurldevice_id}", dependencies=[Depends(get_key_type)] +) async def api_lnurldevice_retrieve( req: Request, - wallet: WalletTypeInfo = Depends(get_key_type), lnurldevice_id: str = Query(None), ): lnurldevice = await get_lnurldevice(lnurldevice_id) @@ -76,23 +71,18 @@ async def api_lnurldevice_retrieve( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="lnurldevice does not exist" ) - if not lnurldevice.lnurl_toggle: - return {**lnurldevice.dict()} return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} -@lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}") -async def api_lnurldevice_delete( - wallet: WalletTypeInfo = Depends(require_admin_key), - lnurldevice_id: str = Query(None), -): +@lnurldevice_ext.delete( + "/api/v1/lnurlpos/{lnurldevice_id}", dependencies=[Depends(require_admin_key)] +) +async def api_lnurldevice_delete(lnurldevice_id: str = Query(None)): lnurldevice = await get_lnurldevice(lnurldevice_id) - if not lnurldevice: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Wallet link does not exist." ) await delete_lnurldevice(lnurldevice_id) - return "", HTTPStatus.NO_CONTENT diff --git a/lnbits/extensions/lnurlp/lnurl.py b/lnbits/extensions/lnurlp/lnurl.py index 8f6aa623..99de459c 100644 --- a/lnbits/extensions/lnurlp/lnurl.py +++ b/lnbits/extensions/lnurlp/lnurl.py @@ -3,11 +3,7 @@ import math from http import HTTPStatus from fastapi import Request -from lnurl import ( # type: ignore - LnurlErrorResponse, - LnurlPayActionResponse, - LnurlPayResponse, -) +from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse from starlette.exceptions import HTTPException from lnbits.core.services import create_invoice diff --git a/lnbits/extensions/lnurlp/models.py b/lnbits/extensions/lnurlp/models.py index 42ea2926..1c6b6f71 100644 --- a/lnbits/extensions/lnurlp/models.py +++ b/lnbits/extensions/lnurlp/models.py @@ -4,11 +4,11 @@ from typing import Dict, Optional from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse from fastapi.param_functions import Query -from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel from starlette.requests import Request -from lnbits.lnurl import encode as lnurl_encode # type: ignore +from lnbits.lnurl import encode as lnurl_encode class CreatePayLinkData(BaseModel): diff --git a/lnbits/extensions/lnurlp/views_api.py b/lnbits/extensions/lnurlp/views_api.py index 0fa739b0..a7bf0761 100644 --- a/lnbits/extensions/lnurlp/views_api.py +++ b/lnbits/extensions/lnurlp/views_api.py @@ -2,7 +2,7 @@ import json from http import HTTPStatus from fastapi import Depends, Query, Request -from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore +from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl from starlette.exceptions import HTTPException from lnbits.core.crud import get_user diff --git a/lnbits/extensions/market/notifier.py b/lnbits/extensions/market/notifier.py index e2bf7c91..88a1a4a3 100644 --- a/lnbits/extensions/market/notifier.py +++ b/lnbits/extensions/market/notifier.py @@ -10,8 +10,8 @@ from collections import defaultdict from fastapi import WebSocket from loguru import logger -from lnbits.extensions.market.crud import create_chat_message -from lnbits.extensions.market.models import CreateChatMessage +from .crud import create_chat_message +from .models import CreateChatMessage class Notifier: diff --git a/lnbits/extensions/market/templates/market/_dialogs.html b/lnbits/extensions/market/templates/market/_dialogs.html index d2a8dd0a..a0ab84b3 100644 --- a/lnbits/extensions/market/templates/market/_dialogs.html +++ b/lnbits/extensions/market/templates/market/_dialogs.html @@ -55,8 +55,16 @@ > - + + - Link to pass to stall relay + Disabled: link to pass to stall relays when using + nostr {{ col.value }} diff --git a/lnbits/extensions/market/templates/market/index.html b/lnbits/extensions/market/templates/market/index.html index ffcb612b..77e3acce 100644 --- a/lnbits/extensions/market/templates/market/index.html +++ b/lnbits/extensions/market/templates/market/index.html @@ -498,6 +498,7 @@ }, productDialog: { show: false, + url: true, data: {} }, stallDialog: { @@ -536,6 +537,9 @@ methods: { resetDialog(dialog) { this[dialog].show = false + if (dialog == 'productDialog') { + this[dialog].url = true + } this[dialog].data = {} }, toggleDA(value, evt) { @@ -798,11 +802,17 @@ var link = _.findWhere(self.products, {id: linkId}) self.productDialog.data = _.clone(link._data) - self.productDialog.data.categories = self.productDialog.data.categories.split( - ',' - ) + if (self.productDialog.data.categories) { + self.productDialog.data.categories = self.productDialog.data.categories.split( + ',' + ) + } + if (self.productDialog.data.image.startsWith('data:')) { + self.productDialog.url = false + } self.productDialog.show = true + console.log(self.productDialog) }, sendProductFormData: function () { let _data = {...this.productDialog.data} @@ -831,14 +841,8 @@ let canvas = document.createElement('canvas') canvas.setAttribute('width', fit.width) canvas.setAttribute('height', fit.height) - await pica.resize(image, canvas, { - quality: 0, - alpha: true, - unsharpAmount: 95, - unsharpRadius: 0.9, - unsharpThreshold: 70 - }) - this.productDialog.data.image = canvas.toDataURL() + output = await pica.resize(image, canvas) + this.productDialog.data.image = output.toDataURL('image/jpeg', 0.4) this.productDialog = {...this.productDialog} } }, diff --git a/lnbits/extensions/market/views.py b/lnbits/extensions/market/views.py index 23bc5706..e6c8eeff 100644 --- a/lnbits/extensions/market/views.py +++ b/lnbits/extensions/market/views.py @@ -16,11 +16,9 @@ from starlette.exceptions import HTTPException from starlette.responses import HTMLResponse from lnbits.core.models import User -from lnbits.decorators import check_user_exists # type: ignore -from lnbits.extensions.market import market_ext, market_renderer -from lnbits.extensions.market.models import CreateChatMessage, SetSettings -from lnbits.extensions.market.notifier import Notifier +from lnbits.decorators import check_user_exists +from . import market_ext, market_renderer from .crud import ( create_chat_message, create_market_settings, @@ -35,6 +33,8 @@ from .crud import ( get_market_zones, update_market_product_stock, ) +from .models import CreateChatMessage, SetSettings +from .notifier import Notifier templates = Jinja2Templates(directory="templates") diff --git a/lnbits/extensions/market/views_api.py b/lnbits/extensions/market/views_api.py index 045bc0fc..31703e8d 100644 --- a/lnbits/extensions/market/views_api.py +++ b/lnbits/extensions/market/views_api.py @@ -113,6 +113,23 @@ async def api_market_product_create( if stall.currency != "sat": data.price *= settings.fiat_base_multiplier + if data.image: + image_is_url = data.image.startswith("https://") or data.image.startswith( + "http://" + ) + + if not image_is_url: + + def size(b64string): + return int((len(b64string) * 3) / 4 - b64string.count("=", -2)) + + image_size = size(data.image) / 1024 + if image_size > 100: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"Image size is too big, {int(image_size)}Kb. Max: 100kb, Compress the image at https://tinypng.com, or use an URL.", + ) + if product_id: product = await get_market_product(product_id) if not product: diff --git a/lnbits/extensions/ngrok/views.py b/lnbits/extensions/ngrok/views.py index 81c8b24e..d84ecd2d 100644 --- a/lnbits/extensions/ngrok/views.py +++ b/lnbits/extensions/ngrok/views.py @@ -1,4 +1,3 @@ -# type: ignore from os import getenv from fastapi import Depends, Request @@ -36,5 +35,5 @@ ngrok_tunnel = ngrok.connect(port) @ngrok_ext.get("/") async def index(request: Request, user: User = Depends(check_user_exists)): return ngrok_renderer().TemplateResponse( - "ngrok/index.html", {"request": request, "ngrok": string5, "user": user.dict()} + "ngrok/index.html", {"request": request, "ngrok": string5, "user": user.dict()} # type: ignore ) diff --git a/lnbits/extensions/nostrnip5/README.md b/lnbits/extensions/nostrnip5/README.md index b8912fa2..2bcbf054 100644 --- a/lnbits/extensions/nostrnip5/README.md +++ b/lnbits/extensions/nostrnip5/README.md @@ -41,4 +41,19 @@ location /.well-known/nostr.json { proxy_cache_valid 200 300s; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; } +``` + +Example Caddy configuration + +``` +my.lnbits.instance { + reverse_proxy {your_lnbits} +} + +nip.5.domain { + route /.well-known/nostr.json { + rewrite * /nostrnip5/api/v1/domain/{domain_id}/nostr.json + reverse_proxy {your_lnbits} + } +} ``` \ No newline at end of file diff --git a/lnbits/extensions/nostrnip5/crud.py b/lnbits/extensions/nostrnip5/crud.py index 12adc05a..fe71b981 100644 --- a/lnbits/extensions/nostrnip5/crud.py +++ b/lnbits/extensions/nostrnip5/crud.py @@ -173,12 +173,17 @@ async def create_address_internal(domain_id: str, data: CreateAddressData) -> Ad async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Domain: domain_id = urlsafe_short_hash() + if data.currency != "Satoshis": + amount = data.amount * 100 + else: + amount = data.amount + await db.execute( """ INSERT INTO nostrnip5.domains (id, wallet, currency, amount, domain) VALUES (?, ?, ?, ?, ?) """, - (domain_id, wallet_id, data.currency, int(data.amount * 100), data.domain), + (domain_id, wallet_id, data.currency, int(amount), data.domain), ) domain = await get_domain(domain_id) diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html index b1459ee3..6b805ccc 100644 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html +++ b/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html @@ -201,7 +201,7 @@ dense v-model.trim="formDialog.data.amount" label="Amount" - placeholder="10.00" + placeholder="How much do you want to charge?" >

- The current price is + The current price is {% if domain.currency != "Satoshis" %} {{ "{:0,.2f}".format(domain.amount / 100) }} {{ domain.currency }} - for an account (if you do not own the domain, the service provider can - disable at any time). + {% else %} + {{ "{}".format(domain.amount) }} {{ domain.currency }} + {% endif %} for an account (if you do not own the domain, the service + provider can disable at any time).

After submitting payment, your address will be

diff --git a/lnbits/extensions/nostrnip5/views_api.py b/lnbits/extensions/nostrnip5/views_api.py index 704a98c4..79768d62 100644 --- a/lnbits/extensions/nostrnip5/views_api.py +++ b/lnbits/extensions/nostrnip5/views_api.py @@ -196,7 +196,12 @@ async def api_address_create( ) address = await create_address_internal(domain_id=domain_id, data=post_data) - price_in_sats = await fiat_amount_as_satoshis(domain.amount / 100, domain.currency) + if domain.currency == "Satoshis": + price_in_sats = domain.amount + else: + price_in_sats = await fiat_amount_as_satoshis( + domain.amount / 100, domain.currency + ) try: payment_hash, payment_request = await create_invoice( diff --git a/lnbits/extensions/satsdice/lnurl.py b/lnbits/extensions/satsdice/lnurl.py index 1e9c6c09..f766d8cb 100644 --- a/lnbits/extensions/satsdice/lnurl.py +++ b/lnbits/extensions/satsdice/lnurl.py @@ -5,7 +5,7 @@ from http import HTTPStatus from fastapi import Request from fastapi.param_functions import Query from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse # type: ignore +from starlette.responses import HTMLResponse from lnbits.core.services import create_invoice, pay_invoice diff --git a/lnbits/extensions/satsdice/models.py b/lnbits/extensions/satsdice/models.py index 2537f8d7..b0a9a4cd 100644 --- a/lnbits/extensions/satsdice/models.py +++ b/lnbits/extensions/satsdice/models.py @@ -5,8 +5,8 @@ from typing import Dict, Optional from fastapi import Request from fastapi.param_functions import Query from lnurl import Lnurl -from lnurl import encode as lnurl_encode # type: ignore -from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl import encode as lnurl_encode +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel from pydantic.main import BaseModel diff --git a/lnbits/extensions/satsdice/views_api.py b/lnbits/extensions/satsdice/views_api.py index 77c2f1d4..57ab26b8 100644 --- a/lnbits/extensions/satsdice/views_api.py +++ b/lnbits/extensions/satsdice/views_api.py @@ -1,7 +1,7 @@ from http import HTTPStatus from fastapi import Depends, Query, Request -from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore +from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl from starlette.exceptions import HTTPException from lnbits.core.crud import get_user diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 4fb14695..01abe24e 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -7,7 +7,7 @@ from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.helpers import urlsafe_short_hash -from ..watchonly.crud import get_config, get_fresh_address +from ..watchonly.crud import get_config, get_fresh_address # type: ignore from . import db from .helpers import fetch_onchain_balance from .models import Charges, CreateCharge, SatsPayThemes diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index 992e5eb6..2c636351 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -4,11 +4,10 @@ import json 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 update_charge +from .crud import check_address_balance, get_charge, update_charge from .helpers import call_webhook diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 15a4403d..175b00bd 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -6,10 +6,10 @@ from starlette.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists -from lnbits.extensions.satspay.helpers import public_charge from . import satspay_ext, satspay_renderer from .crud import get_charge, get_theme +from .helpers import public_charge templates = Jinja2Templates(directory="templates") diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 98c338ed..200773fb 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -11,8 +11,8 @@ from lnbits.decorators import ( require_admin_key, require_invoice_key, ) -from lnbits.extensions.satspay import satspay_ext +from . import satspay_ext from .crud import ( check_address_balance, create_charge, diff --git a/lnbits/extensions/scrub/models.py b/lnbits/extensions/scrub/models.py index db05e4f1..8079f358 100644 --- a/lnbits/extensions/scrub/models.py +++ b/lnbits/extensions/scrub/models.py @@ -3,7 +3,7 @@ from sqlite3 import Row from pydantic import BaseModel from starlette.requests import Request -from lnbits.lnurl import encode as lnurl_encode # type: ignore +from lnbits.lnurl import encode as lnurl_encode class CreateScrubLink(BaseModel): diff --git a/lnbits/extensions/smtp/README.md b/lnbits/extensions/smtp/README.md new file mode 100644 index 00000000..5b7757e2 --- /dev/null +++ b/lnbits/extensions/smtp/README.md @@ -0,0 +1,14 @@ +

SMTP Extension

+ +This extension allows you to setup a smtp, to offer sending emails with it for a small fee. + +## Requirements + +- SMTP Server + +## Usage + +1. Create new emailaddress +2. Verify if email goes to your testemail. Testmail is sent on create and update +3. Share the link with the email form. + diff --git a/lnbits/extensions/smtp/__init__.py b/lnbits/extensions/smtp/__init__.py new file mode 100644 index 00000000..e7419852 --- /dev/null +++ b/lnbits/extensions/smtp/__init__.py @@ -0,0 +1,34 @@ +import asyncio + +from fastapi import APIRouter +from fastapi.staticfiles import StaticFiles + +from lnbits.db import Database +from lnbits.helpers import template_renderer +from lnbits.tasks import catch_everything_and_restart + +db = Database("ext_smtp") + +smtp_static_files = [ + { + "path": "/smtp/static", + "app": StaticFiles(directory="lnbits/extensions/smtp/static"), + "name": "smtp_static", + } +] + +smtp_ext: APIRouter = APIRouter(prefix="/smtp", tags=["smtp"]) + + +def smtp_renderer(): + return template_renderer(["lnbits/extensions/smtp/templates"]) + + +from .tasks import wait_for_paid_invoices +from .views import * # noqa +from .views_api import * # noqa + + +def smtp_start(): + loop = asyncio.get_event_loop() + loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/smtp/config.json b/lnbits/extensions/smtp/config.json new file mode 100644 index 00000000..325ebfa7 --- /dev/null +++ b/lnbits/extensions/smtp/config.json @@ -0,0 +1,6 @@ +{ + "name": "SMTP", + "short_description": "Charge sats for sending emails", + "tile": "/smtp/static/smtp-bitcoin-email.png", + "contributors": ["dni"] +} diff --git a/lnbits/extensions/smtp/crud.py b/lnbits/extensions/smtp/crud.py new file mode 100644 index 00000000..2eee4c3d --- /dev/null +++ b/lnbits/extensions/smtp/crud.py @@ -0,0 +1,168 @@ +from http import HTTPStatus +from typing import List, Optional, Union + +from lnbits.helpers import urlsafe_short_hash + +from . import db +from .models import CreateEmail, CreateEmailaddress, Emailaddresses, Emails +from .smtp import send_mail + + +def get_test_mail(email, testemail): + return CreateEmail( + emailaddress_id=email, + subject="LNBits SMTP - Test Email", + message="This is a test email from the LNBits SMTP extension! email is working!", + receiver=testemail, + ) + + +async def create_emailaddress(data: CreateEmailaddress) -> Emailaddresses: + + emailaddress_id = urlsafe_short_hash() + + # send test mail for checking connection + email = get_test_mail(data.email, data.testemail) + await send_mail(data, email) + + await db.execute( + """ + INSERT INTO smtp.emailaddress (id, wallet, email, testemail, smtp_server, smtp_user, smtp_password, smtp_port, anonymize, description, cost) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + emailaddress_id, + data.wallet, + data.email, + data.testemail, + data.smtp_server, + data.smtp_user, + data.smtp_password, + data.smtp_port, + data.anonymize, + data.description, + data.cost, + ), + ) + + new_emailaddress = await get_emailaddress(emailaddress_id) + assert new_emailaddress, "Newly created emailaddress couldn't be retrieved" + return new_emailaddress + + +async def update_emailaddress(emailaddress_id: str, **kwargs) -> Emailaddresses: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute( + f"UPDATE smtp.emailaddress SET {q} WHERE id = ?", + (*kwargs.values(), emailaddress_id), + ) + row = await db.fetchone( + "SELECT * FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,) + ) + + # send test mail for checking connection + email = get_test_mail(row.email, row.testemail) + await send_mail(row, email) + + assert row, "Newly updated emailaddress couldn't be retrieved" + return Emailaddresses(**row) + + +async def get_emailaddress(emailaddress_id: str) -> Optional[Emailaddresses]: + row = await db.fetchone( + "SELECT * FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,) + ) + return Emailaddresses(**row) if row else None + + +async def get_emailaddress_by_email(email: str) -> Optional[Emailaddresses]: + row = await db.fetchone("SELECT * FROM smtp.emailaddress WHERE email = ?", (email,)) + return Emailaddresses(**row) if row else None + + +# async def get_emailAddressByEmail(email: str) -> Optional[Emails]: +# row = await db.fetchone( +# "SELECT s.*, d.emailaddress as emailaddress FROM smtp.email s INNER JOIN smtp.emailaddress d ON (s.emailaddress_id = d.id) WHERE s.emailaddress = ?", +# (email,), +# ) +# return Subdomains(**row) if row else None + + +async def get_emailaddresses(wallet_ids: Union[str, List[str]]) -> List[Emailaddresses]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT * FROM smtp.emailaddress WHERE wallet IN ({q})", (*wallet_ids,) + ) + + return [Emailaddresses(**row) for row in rows] + + +async def delete_emailaddress(emailaddress_id: str) -> None: + await db.execute("DELETE FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,)) + + +## create emails +async def create_email(payment_hash, wallet, data: CreateEmail) -> Emails: + await db.execute( + """ + INSERT INTO smtp.email (id, wallet, emailaddress_id, subject, receiver, message, paid) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + payment_hash, + wallet, + data.emailaddress_id, + data.subject, + data.receiver, + data.message, + False, + ), + ) + + new_email = await get_email(payment_hash) + assert new_email, "Newly created email couldn't be retrieved" + return new_email + + +async def set_email_paid(payment_hash: str) -> Emails: + email = await get_email(payment_hash) + if email and email.paid == False: + await db.execute( + """ + UPDATE smtp.email + SET paid = true + WHERE id = ? + """, + (payment_hash,), + ) + new_email = await get_email(payment_hash) + assert new_email, "Newly paid email couldn't be retrieved" + return new_email + + +async def get_email(email_id: str) -> Optional[Emails]: + row = await db.fetchone( + "SELECT s.*, d.email as emailaddress FROM smtp.email s INNER JOIN smtp.emailaddress d ON (s.emailaddress_id = d.id) WHERE s.id = ?", + (email_id,), + ) + return Emails(**row) if row else None + + +async def get_emails(wallet_ids: Union[str, List[str]]) -> List[Emails]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT s.*, d.email as emailaddress FROM smtp.email s INNER JOIN smtp.emailaddress d ON (s.emailaddress_id = d.id) WHERE s.wallet IN ({q})", + (*wallet_ids,), + ) + + return [Emails(**row) for row in rows] + + +async def delete_email(email_id: str) -> None: + await db.execute("DELETE FROM smtp.email WHERE id = ?", (email_id,)) diff --git a/lnbits/extensions/smtp/migrations.py b/lnbits/extensions/smtp/migrations.py new file mode 100644 index 00000000..16d50166 --- /dev/null +++ b/lnbits/extensions/smtp/migrations.py @@ -0,0 +1,35 @@ +async def m001_initial(db): + + await db.execute( + f""" + CREATE TABLE smtp.emailaddress ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + email TEXT NOT NULL, + testemail TEXT NOT NULL, + smtp_server TEXT NOT NULL, + smtp_user TEXT NOT NULL, + smtp_password TEXT NOT NULL, + smtp_port TEXT NOT NULL, + anonymize BOOLEAN NOT NULL, + description TEXT NOT NULL, + cost INTEGER NOT NULL, + time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} + ); + """ + ) + + await db.execute( + f""" + CREATE TABLE smtp.email ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + emailaddress_id TEXT NOT NULL, + subject TEXT NOT NULL, + receiver TEXT NOT NULL, + message TEXT NOT NULL, + paid BOOLEAN NOT NULL, + time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} + ); + """ + ) diff --git a/lnbits/extensions/smtp/models.py b/lnbits/extensions/smtp/models.py new file mode 100644 index 00000000..e2f3fc13 --- /dev/null +++ b/lnbits/extensions/smtp/models.py @@ -0,0 +1,47 @@ +from fastapi import Query +from pydantic import BaseModel + + +class CreateEmailaddress(BaseModel): + wallet: str = Query(...) + email: str = Query(...) + testemail: str = Query(...) + smtp_server: str = Query(...) + smtp_user: str = Query(...) + smtp_password: str = Query(...) + smtp_port: str = Query(...) + description: str = Query(...) + anonymize: bool + cost: int = Query(..., ge=0) + + +class Emailaddresses(BaseModel): + id: str + wallet: str + email: str + testemail: str + smtp_server: str + smtp_user: str + smtp_password: str + smtp_port: str + anonymize: bool + description: str + cost: int + + +class CreateEmail(BaseModel): + emailaddress_id: str = Query(...) + subject: str = Query(...) + receiver: str = Query(...) + message: str = Query(...) + + +class Emails(BaseModel): + id: str + wallet: str + emailaddress_id: str + subject: str + receiver: str + message: str + paid: bool + time: int diff --git a/lnbits/extensions/smtp/smtp.py b/lnbits/extensions/smtp/smtp.py new file mode 100644 index 00000000..e77bc0fa --- /dev/null +++ b/lnbits/extensions/smtp/smtp.py @@ -0,0 +1,86 @@ +import re +import socket +import time +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.utils import formatdate +from http import HTTPStatus +from smtplib import SMTP_SSL as SMTP + +from loguru import logger +from starlette.exceptions import HTTPException + + +def valid_email(s): + # https://regexr.com/2rhq7 + pat = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" + if re.match(pat, s): + return True + msg = f"SMTP - invalid email: {s}." + logger.error(msg) + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) + + +async def send_mail(emailaddress, email): + valid_email(emailaddress.email) + valid_email(email.receiver) + + ts = time.time() + date = formatdate(ts, True) + + msg = MIMEMultipart("alternative") + msg = MIMEMultipart("alternative") + msg["Date"] = date + msg["Subject"] = email.subject + msg["From"] = emailaddress.email + msg["To"] = email.receiver + + signature = "Email sent anonymiously by LNbits Sendmail extension." + text = f""" +{email.message} + +{signature} +""" + + html = f""" + + + +

{email.message}

+
+

{signature}

+ + +""" + + part1 = MIMEText(text, "plain") + part2 = MIMEText(html, "html") + msg.attach(part1) + msg.attach(part2) + + try: + conn = SMTP( + host=emailaddress.smtp_server, port=emailaddress.smtp_port, timeout=10 + ) + logger.debug("SMTP - connected to smtp server.") + # conn.set_debuglevel(True) + except: + msg = f"SMTP - error connecting to smtp server: {emailaddress.smtp_server}:{emailaddress.smtp_port}." + logger.error(msg) + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) + try: + conn.login(emailaddress.smtp_user, emailaddress.smtp_password) + logger.debug("SMTP - successful login to smtp server.") + except: + msg = f"SMTP - error login into smtp {emailaddress.smtp_user}." + logger.error(msg) + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) + try: + conn.sendmail(emailaddress.email, email.receiver, msg.as_string()) + logger.debug("SMTP - successfully send email.") + except socket.error as e: + msg = f"SMTP - error sending email: {str(e)}." + logger.error(msg) + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) + finally: + conn.quit() diff --git a/lnbits/extensions/smtp/static/smtp-bitcoin-email.png b/lnbits/extensions/smtp/static/smtp-bitcoin-email.png new file mode 100644 index 00000000..e80b6c9a Binary files /dev/null and b/lnbits/extensions/smtp/static/smtp-bitcoin-email.png differ diff --git a/lnbits/extensions/smtp/tasks.py b/lnbits/extensions/smtp/tasks.py new file mode 100644 index 00000000..9c544473 --- /dev/null +++ b/lnbits/extensions/smtp/tasks.py @@ -0,0 +1,36 @@ +import asyncio + +from loguru import logger + +from lnbits.core.models import Payment +from lnbits.tasks import register_invoice_listener + +from .crud import get_email, get_emailaddress, set_email_paid +from .smtp import send_mail + + +async def wait_for_paid_invoices(): + invoice_queue = asyncio.Queue() + register_invoice_listener(invoice_queue) + while True: + payment = await invoice_queue.get() + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment) -> None: + if payment.extra.get("tag") != "smtp": + return + + email = await get_email(payment.checking_id) + if not email: + logger.error("SMTP: email can not by fetched") + return + + emailaddress = await get_emailaddress(email.emailaddress_id) + if not emailaddress: + logger.error("SMTP: emailaddress can not by fetched") + return + + await payment.set_pending(False) + await send_mail(emailaddress, email) + await set_email_paid(payment_hash=payment.payment_hash) diff --git a/lnbits/extensions/smtp/templates/smtp/_api_docs.html b/lnbits/extensions/smtp/templates/smtp/_api_docs.html new file mode 100644 index 00000000..cfb811d1 --- /dev/null +++ b/lnbits/extensions/smtp/templates/smtp/_api_docs.html @@ -0,0 +1,23 @@ + + + +
+ LNBits SMTP: Get paid sats to send emails +
+

+ Charge people for using sending an email via your smtp server
+ More details +
+ Created by, dni +

+
+
+
diff --git a/lnbits/extensions/smtp/templates/smtp/display.html b/lnbits/extensions/smtp/templates/smtp/display.html new file mode 100644 index 00000000..f60d22c1 --- /dev/null +++ b/lnbits/extensions/smtp/templates/smtp/display.html @@ -0,0 +1,175 @@ +{% extends "public.html" %} {% block page %} +
+
+ + +

{{ email }}

+
+
{{ desc }}
+
+ + + + +

Total cost: {{ cost }} sats

+
+ Submit +
+
+
+
+
+ + + + + + +
+ Copy invoice + Close +
+
+
+
+ +{% endblock %} {% block scripts %} + +{% endblock %} diff --git a/lnbits/extensions/smtp/templates/smtp/index.html b/lnbits/extensions/smtp/templates/smtp/index.html new file mode 100644 index 00000000..bf43ad7f --- /dev/null +++ b/lnbits/extensions/smtp/templates/smtp/index.html @@ -0,0 +1,528 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} + +
+
+ + + New Emailaddress + + + + + +
+
+
Emailaddresses
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+ + + +
+
+
Emails
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+
+
+ + +
+ {{SITE_TITLE}} Sendmail extension +
+
+ + + {% include "smtp/_api_docs.html" %} + +
+
+ + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ Update Form + Create Emailaddress + Cancel +
+
+
+
+
+ +{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/smtp/views.py b/lnbits/extensions/smtp/views.py new file mode 100644 index 00000000..df208a77 --- /dev/null +++ b/lnbits/extensions/smtp/views.py @@ -0,0 +1,40 @@ +from http import HTTPStatus + +from fastapi import Depends, HTTPException, Request +from fastapi.templating import Jinja2Templates +from starlette.responses import HTMLResponse + +from lnbits.core.models import User +from lnbits.decorators import check_user_exists + +from . import smtp_ext, smtp_renderer +from .crud import get_emailaddress + +templates = Jinja2Templates(directory="templates") + + +@smtp_ext.get("/", response_class=HTMLResponse) +async def index(request: Request, user: User = Depends(check_user_exists)): + return smtp_renderer().TemplateResponse( + "smtp/index.html", {"request": request, "user": user.dict()} + ) + + +@smtp_ext.get("/{emailaddress_id}") +async def display(request: Request, emailaddress_id): + emailaddress = await get_emailaddress(emailaddress_id) + if not emailaddress: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Emailaddress does not exist." + ) + + return smtp_renderer().TemplateResponse( + "smtp/display.html", + { + "request": request, + "emailaddress_id": emailaddress.id, + "email": emailaddress.email, + "desc": emailaddress.description, + "cost": emailaddress.cost, + }, + ) diff --git a/lnbits/extensions/smtp/views_api.py b/lnbits/extensions/smtp/views_api.py new file mode 100644 index 00000000..4ae1f966 --- /dev/null +++ b/lnbits/extensions/smtp/views_api.py @@ -0,0 +1,170 @@ +from http import HTTPStatus + +from fastapi import Depends, HTTPException, Query + +from lnbits.core.crud import get_user +from lnbits.core.services import check_transaction_status, create_invoice +from lnbits.decorators import WalletTypeInfo, get_key_type + +from . import smtp_ext +from .crud import ( + create_email, + create_emailaddress, + delete_email, + delete_emailaddress, + get_email, + get_emailaddress, + get_emailaddresses, + get_emails, + update_emailaddress, +) +from .models import CreateEmail, CreateEmailaddress +from .smtp import valid_email + + +## EMAILS +@smtp_ext.get("/api/v1/email") +async def api_email( + g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) +): + wallet_ids = [g.wallet.id] + if all_wallets: + user = await get_user(g.wallet.user) + if user: + wallet_ids = user.wallet_ids + return [email.dict() for email in await get_emails(wallet_ids)] + + +@smtp_ext.get("/api/v1/email/{payment_hash}") +async def api_smtp_send_email(payment_hash): + email = await get_email(payment_hash) + if not email: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail="paymenthash is wrong" + ) + + emailaddress = await get_emailaddress(email.emailaddress_id) + + try: + status = await check_transaction_status(email.wallet, payment_hash) + is_paid = not status.pending + except Exception: + return {"paid": False} + if is_paid: + if emailaddress.anonymize: + await delete_email(email.id) + return {"paid": True} + return {"paid": False} + + +@smtp_ext.post("/api/v1/email/{emailaddress_id}") +async def api_smtp_make_email(emailaddress_id, data: CreateEmail): + + valid_email(data.receiver) + + emailaddress = await get_emailaddress(emailaddress_id) + # If the request is coming for the non-existant emailaddress + if not emailaddress: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail="Emailaddress address does not exist.", + ) + try: + memo = f"sent email from {emailaddress.email} to {data.receiver}" + if emailaddress.anonymize: + memo = "sent email" + + payment_hash, payment_request = await create_invoice( + wallet_id=emailaddress.wallet, + amount=emailaddress.cost, + memo=memo, + extra={"tag": "smtp"}, + ) + except Exception as e: + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) + + email = await create_email( + payment_hash=payment_hash, wallet=emailaddress.wallet, data=data + ) + + if not email: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Email could not be fetched." + ) + return {"payment_hash": payment_hash, "payment_request": payment_request} + + +@smtp_ext.delete("/api/v1/email/{email_id}") +async def api_email_delete(email_id, g: WalletTypeInfo = Depends(get_key_type)): + email = await get_email(email_id) + + if not email: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="LNsubdomain does not exist." + ) + + if email.wallet != g.wallet.id: + raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your email.") + + await delete_email(email_id) + raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + + +## EMAILADDRESSES +@smtp_ext.get("/api/v1/emailaddress") +async def api_emailaddresses( + g: WalletTypeInfo = Depends(get_key_type), + all_wallets: bool = Query(False), +): + wallet_ids = [g.wallet.id] + if all_wallets: + user = await get_user(g.wallet.user) + if user: + wallet_ids = user.wallet_ids + return [ + emailaddress.dict() for emailaddress in await get_emailaddresses(wallet_ids) + ] + + +@smtp_ext.post("/api/v1/emailaddress") +@smtp_ext.put("/api/v1/emailaddress/{emailaddress_id}") +async def api_emailaddress_create( + data: CreateEmailaddress, + emailaddress_id=None, + g: WalletTypeInfo = Depends(get_key_type), +): + if emailaddress_id: + emailaddress = await get_emailaddress(emailaddress_id) + + if not emailaddress: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Emailadress does not exist." + ) + if emailaddress.wallet != g.wallet.id: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not your emailaddress." + ) + + emailaddress = await update_emailaddress(emailaddress_id, **data.dict()) + else: + emailaddress = await create_emailaddress(data=data) + return emailaddress.dict() + + +@smtp_ext.delete("/api/v1/emailaddress/{emailaddress_id}") +async def api_emailaddress_delete( + emailaddress_id, g: WalletTypeInfo = Depends(get_key_type) +): + emailaddress = await get_emailaddress(emailaddress_id) + + if not emailaddress: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Emailaddress does not exist." + ) + if emailaddress.wallet != g.wallet.id: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not your Emailaddress." + ) + + await delete_emailaddress(emailaddress_id) + raise HTTPException(status_code=HTTPStatus.NO_CONTENT) diff --git a/lnbits/extensions/streamalerts/crud.py b/lnbits/extensions/streamalerts/crud.py index 37583117..94113447 100644 --- a/lnbits/extensions/streamalerts/crud.py +++ b/lnbits/extensions/streamalerts/crud.py @@ -7,6 +7,7 @@ from lnbits.core.crud import get_wallet from lnbits.db import SQLITE from lnbits.helpers import urlsafe_short_hash +# todo: use the API, not direct import from ..satspay.crud import delete_charge # type: ignore from . import db from .models import CreateService, Donation, Service diff --git a/lnbits/extensions/streamalerts/views_api.py b/lnbits/extensions/streamalerts/views_api.py index 0134fe82..7bf952c7 100644 --- a/lnbits/extensions/streamalerts/views_api.py +++ b/lnbits/extensions/streamalerts/views_api.py @@ -7,15 +7,13 @@ from starlette.responses import RedirectResponse from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.satspay.models import CreateCharge -from lnbits.extensions.streamalerts.models import ( - CreateDonation, - CreateService, - ValidateDonation, -) + +# todo: use the API, not direct import +from lnbits.extensions.satspay.models import CreateCharge # type: ignore from lnbits.utils.exchange_rates import btc_price -from ..satspay.crud import create_charge, get_charge +# todo: use the API, not direct import +from ..satspay.crud import create_charge, get_charge # type: ignore from . import streamalerts_ext from .crud import ( authenticate_service, @@ -33,6 +31,7 @@ from .crud import ( update_donation, update_service, ) +from .models import CreateDonation, CreateService, ValidateDonation @streamalerts_ext.post("/api/v1/services") diff --git a/lnbits/extensions/subdomains/cloudflare.py b/lnbits/extensions/subdomains/cloudflare.py index 679ca843..5b951b21 100644 --- a/lnbits/extensions/subdomains/cloudflare.py +++ b/lnbits/extensions/subdomains/cloudflare.py @@ -2,7 +2,7 @@ import json import httpx -from lnbits.extensions.subdomains.models import Domains +from .models import Domains async def cloudflare_create_subdomain( diff --git a/lnbits/extensions/subdomains/tasks.py b/lnbits/extensions/subdomains/tasks.py index f9e0c8ee..ca57950b 100644 --- a/lnbits/extensions/subdomains/tasks.py +++ b/lnbits/extensions/subdomains/tasks.py @@ -30,7 +30,7 @@ async def on_invoice_paid(payment: Payment) -> None: ### Create subdomain cf_response = await cloudflare_create_subdomain( - domain=domain, + domain=domain, # type: ignore subdomain=subdomain.subdomain, record_type=subdomain.record_type, ip=subdomain.ip, diff --git a/lnbits/extensions/subdomains/views_api.py b/lnbits/extensions/subdomains/views_api.py index 2b20bd1f..9fbae4f3 100644 --- a/lnbits/extensions/subdomains/views_api.py +++ b/lnbits/extensions/subdomains/views_api.py @@ -6,7 +6,6 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_user from lnbits.core.services import check_transaction_status, create_invoice from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.subdomains.models import CreateDomain, CreateSubdomain from . import subdomains_ext from .cloudflare import cloudflare_create_subdomain, cloudflare_deletesubdomain @@ -22,6 +21,7 @@ from .crud import ( get_subdomains, update_domain, ) +from .models import CreateDomain, CreateSubdomain # domainS diff --git a/lnbits/extensions/tipjar/crud.py b/lnbits/extensions/tipjar/crud.py index 1b58a43d..3ea45d0d 100644 --- a/lnbits/extensions/tipjar/crud.py +++ b/lnbits/extensions/tipjar/crud.py @@ -2,6 +2,7 @@ from typing import Optional from lnbits.db import SQLITE +# todo: use the API, not direct import from ..satspay.crud import delete_charge # type: ignore from . import db from .models import Tip, TipJar, createTipJar @@ -33,7 +34,11 @@ async def create_tip( async def create_tipjar(data: createTipJar) -> TipJar: """Create a new TipJar""" - await db.execute( + + returning = "" if db.type == SQLITE else "RETURNING ID" + method = db.execute if db.type == SQLITE else db.fetchone + + result = await (method)( f""" INSERT INTO tipjar.TipJars ( name, @@ -42,11 +47,16 @@ async def create_tipjar(data: createTipJar) -> TipJar: onchain ) VALUES (?, ?, ?, ?) + {returning} """, (data.name, data.wallet, data.webhook, data.onchain), ) - row = await db.fetchone("SELECT * FROM tipjar.TipJars LIMIT 1") - tipjar = TipJar(**row) + if db.type == SQLITE: + tipjar_id = result._result_proxy.lastrowid + else: + tipjar_id = result[0] + + tipjar = await get_tipjar(tipjar_id) assert tipjar return tipjar diff --git a/lnbits/extensions/tipjar/views_api.py b/lnbits/extensions/tipjar/views_api.py index d0c7ac7d..7d3df920 100644 --- a/lnbits/extensions/tipjar/views_api.py +++ b/lnbits/extensions/tipjar/views_api.py @@ -6,8 +6,9 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type -from ..satspay.crud import create_charge -from ..satspay.models import CreateCharge +# todo: use the API, not direct import +from ..satspay.crud import create_charge # type: ignore +from ..satspay.models import CreateCharge # type: ignore from . import tipjar_ext from .crud import ( create_tip, diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index 80ee1085..4b7bd9f9 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -20,9 +20,6 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra: - return - if payment.extra.get("tag") != "tpos": return diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py index 61e47cfe..1d9abcec 100644 --- a/lnbits/extensions/watchonly/crud.py +++ b/lnbits/extensions/watchonly/crud.py @@ -41,8 +41,9 @@ async def create_watch_wallet(user: str, w: WalletAccount) -> WalletAccount: w.meta, ), ) - - return await get_watch_wallet(wallet_id) + wallet = await get_watch_wallet(wallet_id) + assert wallet + return wallet async def get_watch_wallet(wallet_id: str) -> Optional[WalletAccount]: @@ -121,11 +122,11 @@ async def create_fresh_addresses( change_address=False, ) -> List[Address]: if start_address_index > end_address_index: - return None + return [] wallet = await get_watch_wallet(wallet_id) if not wallet: - return None + return [] branch_index = 1 if change_address else 0 @@ -150,7 +151,7 @@ async def create_fresh_addresses( # return fresh addresses rows = await db.fetchall( """ - SELECT * FROM watchonly.addresses + SELECT * FROM watchonly.addresses WHERE wallet = ? AND branch_index = ? AND address_index >= ? AND address_index < ? ORDER BY branch_index, address_index """, @@ -172,7 +173,7 @@ async def get_address_at_index( ) -> Optional[Address]: row = await db.fetchone( """ - SELECT * FROM watchonly.addresses + SELECT * FROM watchonly.addresses WHERE wallet = ? AND branch_index = ? AND address_index = ? """, ( diff --git a/lnbits/extensions/watchonly/helpers.py b/lnbits/extensions/watchonly/helpers.py index 74125dde..8db9ff57 100644 --- a/lnbits/extensions/watchonly/helpers.py +++ b/lnbits/extensions/watchonly/helpers.py @@ -1,6 +1,6 @@ -from embit.descriptor import Descriptor, Key # type: ignore -from embit.descriptor.arguments import AllowedDerivation # type: ignore -from embit.networks import NETWORKS # type: ignore +from embit.descriptor import Descriptor, Key +from embit.descriptor.arguments import AllowedDerivation +from embit.networks import NETWORKS def detect_network(k): diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index c6265d6c..24d63bfd 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -1,7 +1,7 @@ from sqlite3 import Row from typing import List, Optional -from fastapi.param_functions import Query +from fastapi import Query from pydantic import BaseModel @@ -35,7 +35,7 @@ class Address(BaseModel): amount: int = 0 branch_index: int = 0 address_index: int - note: str = None + note: Optional[str] = None has_activity: bool = False @classmethod @@ -57,9 +57,9 @@ class TransactionInput(BaseModel): class TransactionOutput(BaseModel): amount: int address: str - branch_index: int = None - address_index: int = None - wallet: str = None + branch_index: Optional[int] = None + address_index: Optional[int] = None + wallet: Optional[str] = None class MasterPublicKey(BaseModel): diff --git a/lnbits/extensions/watchonly/views.py b/lnbits/extensions/watchonly/views.py index 819d1248..8cebc6cc 100644 --- a/lnbits/extensions/watchonly/views.py +++ b/lnbits/extensions/watchonly/views.py @@ -1,6 +1,5 @@ -from fastapi.params import Depends +from fastapi import Depends, Request from fastapi.templating import Jinja2Templates -from starlette.requests import Request from starlette.responses import HTMLResponse from lnbits.core.models import User diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index c6e15ea6..2e3fc45d 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -1,5 +1,6 @@ import json from http import HTTPStatus +from typing import List import httpx from embit import finalizer, script @@ -7,13 +8,11 @@ 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 -from fastapi.params import Depends -from starlette.exceptions import HTTPException +from fastapi import Depends, HTTPException, Query, Request from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.extensions.watchonly import watchonly_ext +from . import watchonly_ext from .crud import ( create_config, create_fresh_addresses, @@ -57,10 +56,8 @@ async def api_wallets_retrieve( return [] -@watchonly_ext.get("/api/v1/wallet/{wallet_id}") -async def api_wallet_retrieve( - wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) -): +@watchonly_ext.get("/api/v1/wallet/{wallet_id}", dependencies=[Depends(get_key_type)]) +async def api_wallet_retrieve(wallet_id: str): w_wallet = await get_watch_wallet(wallet_id) if not w_wallet: @@ -126,8 +123,10 @@ async def api_wallet_create_or_update( return wallet.dict() -@watchonly_ext.delete("/api/v1/wallet/{wallet_id}") -async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(require_admin_key)): +@watchonly_ext.delete( + "/api/v1/wallet/{wallet_id}", dependencies=[Depends(require_admin_key)] +) +async def api_wallet_delete(wallet_id: str): wallet = await get_watch_wallet(wallet_id) if not wallet: @@ -144,16 +143,15 @@ async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(require_admin #############################ADDRESSES########################## -@watchonly_ext.get("/api/v1/address/{wallet_id}") -async def api_fresh_address(wallet_id, w: WalletTypeInfo = Depends(get_key_type)): +@watchonly_ext.get("/api/v1/address/{wallet_id}", dependencies=[Depends(get_key_type)]) +async def api_fresh_address(wallet_id: str): address = await get_fresh_address(wallet_id) + assert address return address.dict() -@watchonly_ext.put("/api/v1/address/{id}") -async def api_update_address( - id: str, req: Request, w: WalletTypeInfo = Depends(require_admin_key) -): +@watchonly_ext.put("/api/v1/address/{id}", dependencies=[Depends(require_admin_key)]) +async def api_update_address(id: str, req: Request): body = await req.json() params = {} # amout is only updated if the address has history @@ -162,9 +160,10 @@ async def api_update_address( params["has_activity"] = True if "note" in body: - params["note"] = str(body["note"]) + params["note"] = body["note"] address = await update_address(**params, id=id) + assert address wallet = ( await get_watch_wallet(address.wallet) @@ -189,6 +188,7 @@ async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type) addresses = await get_addresses(wallet_id) config = await get_config(w.wallet.user) + assert config if not addresses: await create_fresh_addresses(wallet_id, 0, config.receive_gap_limit) @@ -229,10 +229,8 @@ async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type) #############################PSBT########################## -@watchonly_ext.post("/api/v1/psbt") -async def api_psbt_create( - data: CreatePsbt, w: WalletTypeInfo = Depends(require_admin_key) -): +@watchonly_ext.post("/api/v1/psbt", dependencies=[Depends(require_admin_key)]) +async def api_psbt_create(data: CreatePsbt): try: vin = [ TransactionInput(bytes.fromhex(inp.tx_id), inp.vout) for inp in data.inputs @@ -246,7 +244,7 @@ async def api_psbt_create( for _, masterpub in enumerate(data.masterpubs): descriptors[masterpub.id] = parse_key(masterpub.public_key) - inputs_extra = [] + inputs_extra: List[dict] = [] for i, inp in enumerate(data.inputs): bip32_derivations = {} @@ -266,14 +264,15 @@ async def api_psbt_create( tx = Transaction(vin=vin, vout=vout) psbt = PSBT(tx) - for i, inp in enumerate(inputs_extra): - psbt.inputs[i].bip32_derivations = inp["bip32_derivations"] - psbt.inputs[i].non_witness_utxo = inp.get("non_witness_utxo", None) + for i, inp_extra in enumerate(inputs_extra): + psbt.inputs[i].bip32_derivations = inp_extra["bip32_derivations"] + psbt.inputs[i].non_witness_utxo = inp_extra.get("non_witness_utxo", None) outputs_extra = [] bip32_derivations = {} for i, out in enumerate(data.outputs): if out.branch_index == 1: + assert out.wallet descriptor = descriptors[out.wallet][0] d = descriptor.derive(out.address_index, out.branch_index) for k in d.keys: @@ -282,8 +281,8 @@ async def api_psbt_create( ) outputs_extra.append({"bip32_derivations": bip32_derivations}) - for i, out in enumerate(outputs_extra): - psbt.outputs[i].bip32_derivations = out["bip32_derivations"] + for i, out_extra in enumerate(outputs_extra): + psbt.outputs[i].bip32_derivations = out_extra["bip32_derivations"] return psbt.to_string() @@ -360,7 +359,8 @@ async def api_tx_broadcast( else config.mempool_endpoint + "/testnet" ) async with httpx.AsyncClient() as client: - r = await client.post(endpoint + "/api/tx", data=data.tx_hex) + r = await client.post(endpoint + "/api/tx", content=data.tx_hex) + r.raise_for_status() tx_id = r.text return tx_id except Exception as e: @@ -375,6 +375,7 @@ async def api_update_config( data: Config, w: WalletTypeInfo = Depends(require_admin_key) ): config = await update_config(data, user=w.wallet.user) + assert config return config.dict() diff --git a/lnbits/helpers.py b/lnbits/helpers.py index d3a4e6ea..4804bdea 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -4,7 +4,7 @@ import os from typing import Any, List, NamedTuple, Optional import jinja2 -import shortuuid # type: ignore +import shortuuid from lnbits.jinja2_templating import Jinja2Templates from lnbits.requestvars import g diff --git a/lnbits/settings.py b/lnbits/settings.py index 59eb376b..6ec4db0c 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -98,6 +98,8 @@ class LndRestFundingSource(LNbitsSettings): lnd_cert: Optional[str] = Field(default=None) lnd_admin_macaroon: Optional[str] = Field(default=None) lnd_invoice_macaroon: Optional[str] = Field(default=None) + lnd_rest_admin_macaroon: Optional[str] = Field(default=None) + lnd_rest_invoice_macaroon: Optional[str] = Field(default=None) class LndGrpcFundingSource(LNbitsSettings): diff --git a/poetry.lock b/poetry.lock index ce70fb81..698d700d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + [[package]] name = "aiofiles" version = "0.8.0" @@ -5,6 +7,10 @@ description = "File support for asyncio." category = "main" optional = false python-versions = ">=3.6,<4.0" +files = [ + {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"}, + {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"}, +] [[package]] name = "anyio" @@ -13,6 +19,10 @@ description = "High level compatibility layer for multiple asynchronous event lo category = "main" optional = false python-versions = ">=3.6.2" +files = [ + {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, + {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, +] [package.dependencies] idna = ">=2.8" @@ -31,6 +41,10 @@ description = "ASGI specs, helper code, and adapters" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, + {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, +] [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.8\""} @@ -45,6 +59,10 @@ description = "Fast ASN.1 parser and serializer with definitions for private key category = "main" optional = false python-versions = "*" +files = [ + {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, + {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, +] [[package]] name = "async-timeout" @@ -53,6 +71,10 @@ description = "Timeout context manager for asyncio programs" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} @@ -64,6 +86,10 @@ description = "Classes Without Boilerplate" category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] @@ -78,6 +104,10 @@ description = "Base58 and Base58Check implementation." category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, + {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, +] [package.extras] tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"] @@ -89,6 +119,10 @@ description = "Reference implementation for Bech32 and segwit addresses." category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "bech32-1.2.0-py3-none-any.whl", hash = "sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"}, + {file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"}, +] [[package]] name = "bitstring" @@ -97,14 +131,33 @@ description = "Simple construction, analysis and modification of binary data." category = "main" optional = false python-versions = "*" +files = [ + {file = "bitstring-3.1.9-py2-none-any.whl", hash = "sha256:e3e340e58900a948787a05e8c08772f1ccbe133f6f41fe3f0fa19a18a22bbf4f"}, + {file = "bitstring-3.1.9-py3-none-any.whl", hash = "sha256:0de167daa6a00c9386255a7cac931b45e6e24e0ad7ea64f1f92a64ac23ad4578"}, + {file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"}, +] [[package]] name = "black" -version = "22.10.0" +version = "22.12.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] [package.dependencies] click = ">=8.0.0" @@ -128,6 +181,10 @@ description = "Ecash wallet and mint with Bitcoin Lightning support" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "cashu-0.6.0-py3-none-any.whl", hash = "sha256:54096af145643aab45943b235f95a3357b0ec697835c1411e66523049ffb81f6"}, + {file = "cashu-0.6.0.tar.gz", hash = "sha256:503a90c4ca8d25d0b2c3f78a11b163c32902a726ea5b58e5337dc00eca8e96ad"}, +] [package.dependencies] anyio = {version = "3.6.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} @@ -182,6 +239,9 @@ description = "Lightweight, extensible schema and data validation tool for Pytho category = "main" optional = false python-versions = ">=2.7" +files = [ + {file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"}, +] [package.dependencies] setuptools = "*" @@ -193,6 +253,10 @@ description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] [[package]] name = "cffi" @@ -201,1022 +265,7 @@ description = "Foreign Function Interface for Python calling C code." category = "main" optional = false python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "2.0.12" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.5.0" - -[package.extras] -unicode-backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.0.4" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "coincurve" -version = "17.0.0" -description = "Cross-platform Python CFFI bindings for libsecp256k1" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -asn1crypto = "*" -cffi = ">=1.3.0" - -[[package]] -name = "colorama" -version = "0.4.5" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "coverage" -version = "6.5.0" -description = "Code coverage measurement for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "cryptography" -version = "36.0.2" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -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)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] - -[[package]] -name = "ecdsa" -version = "0.18.0" -description = "ECDSA cryptographic signature library (pure python)" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[package.dependencies] -six = ">=1.9.0" - -[package.extras] -gmpy = ["gmpy"] -gmpy2 = ["gmpy2"] - -[[package]] -name = "embit" -version = "0.4.9" -description = "yet another bitcoin library" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "enum34" -version = "1.1.10" -description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "environs" -version = "9.5.0" -description = "simplified environment variable parsing" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -marshmallow = ">=3.0.0" -python-dotenv = "*" - -[package.extras] -dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] -django = ["dj-database-url", "dj-email-url", "django-cache-url"] -lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] -tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] - -[[package]] -name = "fastapi" -version = "0.83.0" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "main" -optional = false -python-versions = ">=3.6.1" - -[package.dependencies] -pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" -starlette = "0.19.1" - -[package.extras] -all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] -dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] -doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"] -test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] - -[[package]] -name = "grpcio" -version = "1.51.1" -description = "HTTP/2-based RPC framework" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -protobuf = ["grpcio-tools (>=1.51.1)"] - -[[package]] -name = "h11" -version = "0.12.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "httpcore" -version = "0.15.0" -description = "A minimal low-level HTTP client." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -anyio = ">=3.0.0,<4.0.0" -certifi = "*" -h11 = ">=0.11,<0.13" -sniffio = ">=1.0.0,<2.0.0" - -[package.extras] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - -[[package]] -name = "httptools" -version = "0.4.0" -description = "A collection of framework independent HTTP protocol utils." -category = "main" -optional = false -python-versions = ">=3.5.0" - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - -[[package]] -name = "httpx" -version = "0.23.0" -description = "The next generation HTTP client." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -certifi = "*" -httpcore = ">=0.15.0,<0.16.0" -rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "importlib-metadata" -version = "5.0.0" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "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 (>=0.9.2)"] - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "isort" -version = "5.10.1" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.6.1,<4.0" - -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "jinja2" -version = "3.0.1" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "lnurl" -version = "0.3.6" -description = "LNURL implementation for Python." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -bech32 = "*" -pydantic = "*" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "loguru" -version = "0.6.0" -description = "Python logging made (stupidly) simple" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} -win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} - -[package.extras] -dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils (==0.16)", "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)"] - -[[package]] -name = "markupsafe" -version = "2.0.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "marshmallow" -version = "3.18.0" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -packaging = ">=17.0" - -[package.extras] -dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.1.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)"] -tests = ["pytest", "pytz", "simplejson"] - -[[package]] -name = "mock" -version = "4.0.3" -description = "Rolling backport of unittest.mock for all Pythons" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -build = ["blurb", "twine", "wheel"] -docs = ["sphinx"] -test = ["pytest (<5.4)", "pytest-cov"] - -[[package]] -name = "mypy" -version = "0.971" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -mypy-extensions = ">=0.4.3" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "outcome" -version = "1.2.0" -description = "Capture the outcome of Python function calls." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "pathlib2" -version = "2.3.7.post1" -description = "Object-oriented filesystem paths" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - -[[package]] -name = "pathspec" -version = "0.10.2" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "platformdirs" -version = "2.5.4" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "protobuf" -version = "4.21.10" -description = "" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "psycopg2-binary" -version = "2.9.1" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pycryptodomex" -version = "3.14.1" -description = "Cryptographic library for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pydantic" -version = "1.10.2" -description = "Data validation and settings management using python type hints" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = ">=4.1.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pyln-bolt7" -version = "1.0.246" -description = "BOLT7" -category = "main" -optional = false -python-versions = ">=3.7,<4.0" - -[[package]] -name = "pyln-client" -version = "0.11.1" -description = "Client library and plugin library for Core Lightning" -category = "main" -optional = false -python-versions = ">=3.7,<4.0" - -[package.dependencies] -pyln-bolt7 = ">=1.0,<2.0" -pyln-proto = ">=0.11,<0.12" - -[[package]] -name = "pyln-proto" -version = "0.11.1" -description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)." -category = "main" -optional = false -python-versions = ">=3.7,<4.0" - -[package.dependencies] -base58 = ">=2.1.1,<3.0.0" -bitstring = ">=3.1.9,<4.0.0" -coincurve = ">=17.0.0,<18.0.0" -cryptography = ">=36.0.1,<37.0.0" -PySocks = ">=1.7.1,<2.0.0" - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pypng" -version = "0.0.21" -description = "Pure Python library for saving and loading PNG images" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyqrcode" -version = "1.2.1" -description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output." -category = "main" -optional = false -python-versions = "*" - -[package.extras] -png = ["pypng (>=0.0.13)"] - -[[package]] -name = "pyscss" -version = "1.4.0" -description = "pyScss, a Scss compiler for Python" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -enum34 = "*" -pathlib2 = "*" -six = "*" - -[[package]] -name = "pysocks" -version = "1.7.1" -description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pytest" -version = "7.1.3" -description = "pytest: simple powerful testing with Python" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -tomli = ">=1.0.0" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.19.0" -description = "Pytest support for asyncio" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -pytest = ">=6.1.0" -typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} - -[package.extras] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] - -[[package]] -name = "pytest-cov" -version = "3.0.0" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "python-bitcoinlib" -version = "0.11.2" -description = "The Swiss Army Knife of the Bitcoin protocol." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "python-dotenv" -version = "0.21.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "pyyaml" -version = "5.4.1" -description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[[package]] -name = "represent" -version = "1.6.0.post0" -description = "Create __repr__ automatically or declaratively." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -six = ">=1.8.0" - -[package.extras] -test = ["ipython", "mock", "pytest (>=3.0.5)"] - -[[package]] -name = "requests" -version = "2.27.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] - -[[package]] -name = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} - -[package.extras] -idna2008 = ["idna"] - -[[package]] -name = "secp256k1" -version = "0.14.0" -description = "FFI bindings to libsecp256k1" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -cffi = ">=1.3.0" - -[[package]] -name = "setuptools" -version = "65.6.3" -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)", "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-timeout", "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" -description = "A generator library for concise, unambiguous and URL-safe UUIDs." -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "sqlalchemy" -version = "1.3.24" -description = "Database Abstraction Library" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.extras] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mysql = ["mysqlclient"] -oracle = ["cx_oracle"] -postgresql = ["psycopg2"] -postgresql-pg8000 = ["pg8000 (<1.16.6)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql", "pymysql (<1)"] - -[[package]] -name = "sqlalchemy-aio" -version = "0.17.0" -description = "Async support for SQLAlchemy." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -outcome = "*" -represent = ">=1.4" -sqlalchemy = "<1.4" - -[package.extras] -test = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)", "pytest-trio (>=0.6)"] -test-noextras = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)"] -trio = ["trio (>=0.15)"] - -[[package]] -name = "sse-starlette" -version = "0.6.2" -description = "SSE plugin for Starlette" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "starlette" -version = "0.19.1" -description = "The little ASGI library that shines." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "typed-ast" -version = "1.5.4" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "types-protobuf" -version = "3.20.4.6" -description = "Typing stubs for protobuf" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "urllib3" -version = "1.26.12" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "uvicorn" -version = "0.18.3" -description = "The lightning-fast ASGI server." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -click = ">=7.0" -h11 = ">=0.8" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] - -[[package]] -name = "uvloop" -version = "0.16.0" -description = "Fast implementation of asyncio event loop on top of libuv" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] - -[[package]] -name = "watchgod" -version = "0.7" -description = "Simple, modern file watching and code reload in python." -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "websocket-client" -version = "1.3.3" -description = "WebSocket client for Python with low level API options" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - -[[package]] -name = "websockets" -version = "10.0" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "win32-setctime" -version = "1.1.0" -description = "A small Python utility to set file creation time on Windows" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.extras] -dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] - -[[package]] -name = "zipp" -version = "3.9.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" -content-hash = "7f75ca0b067a11f19520dc2121f0789e16738b573a8da84ba3838ed8a466a6e1" - - -[metadata.files] -aiofiles = [ - {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"}, - {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"}, -] -anyio = [ - {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, - {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, -] -asgiref = [ - {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, - {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, -] -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-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -base58 = [ - {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, - {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, -] -bech32 = [ - {file = "bech32-1.2.0-py3-none-any.whl", hash = "sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"}, - {file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"}, -] -bitstring = [ - {file = "bitstring-3.1.9-py2-none-any.whl", hash = "sha256:e3e340e58900a948787a05e8c08772f1ccbe133f6f41fe3f0fa19a18a22bbf4f"}, - {file = "bitstring-3.1.9-py3-none-any.whl", hash = "sha256:0de167daa6a00c9386255a7cac931b45e6e24e0ad7ea64f1f92a64ac23ad4578"}, - {file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"}, -] -black = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, -] -cashu = [ - {file = "cashu-0.6.0-py3-none-any.whl", hash = "sha256:54096af145643aab45943b235f95a3357b0ec697835c1411e66523049ffb81f6"}, - {file = "cashu-0.6.0.tar.gz", hash = "sha256:503a90c4ca8d25d0b2c3f78a11b163c32902a726ea5b58e5337dc00eca8e96ad"}, -] -cerberus = [ - {file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"}, -] -certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] -cffi = [ +files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, @@ -1282,15 +331,49 @@ cffi = [ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] -charset-normalizer = [ + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" +files = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] -click = [ + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.4" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] -coincurve = [ + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "coincurve" +version = "17.0.0" +description = "Cross-platform Python CFFI bindings for libsecp256k1" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"}, {file = "coincurve-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924"}, {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb"}, @@ -1326,63 +409,98 @@ coincurve = [ {file = "coincurve-17.0.0-py3-none-win_amd64.whl", hash = "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e"}, {file = "coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"}, ] -colorama = [ + +[package.dependencies] +asn1crypto = "*" +cffi = ">=1.3.0" + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] -coverage = [ - {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"}, + +[[package]] +name = "coverage" +version = "7.0.5" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b"}, + {file = "coverage-7.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89"}, + {file = "coverage-7.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40"}, + {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2"}, + {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289"}, + {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0"}, + {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8"}, + {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809"}, + {file = "coverage-7.0.5-cp310-cp310-win32.whl", hash = "sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21"}, + {file = "coverage-7.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad"}, + {file = "coverage-7.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca"}, + {file = "coverage-7.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0"}, + {file = "coverage-7.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196"}, + {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0"}, + {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc"}, + {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45"}, + {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757"}, + {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095"}, + {file = "coverage-7.0.5-cp311-cp311-win32.whl", hash = "sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831"}, + {file = "coverage-7.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea"}, + {file = "coverage-7.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c"}, + {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7"}, + {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96"}, + {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029"}, + {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3"}, + {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2"}, + {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47"}, + {file = "coverage-7.0.5-cp37-cp37m-win32.whl", hash = "sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882"}, + {file = "coverage-7.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d"}, + {file = "coverage-7.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb"}, + {file = "coverage-7.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6"}, + {file = "coverage-7.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd"}, + {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a"}, + {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2"}, + {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7"}, + {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0"}, + {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499"}, + {file = "coverage-7.0.5-cp38-cp38-win32.whl", hash = "sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16"}, + {file = "coverage-7.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af"}, + {file = "coverage-7.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab"}, + {file = "coverage-7.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637"}, + {file = "coverage-7.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4"}, + {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded"}, + {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b"}, + {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209"}, + {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78"}, + {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1"}, + {file = "coverage-7.0.5-cp39-cp39-win32.whl", hash = "sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904"}, + {file = "coverage-7.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f"}, + {file = "coverage-7.0.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0"}, + {file = "coverage-7.0.5.tar.gz", hash = "sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45"}, ] -cryptography = [ + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cryptography" +version = "36.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"}, {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"}, {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"}, @@ -1404,27 +522,113 @@ cryptography = [ {file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"}, {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"}, ] -ecdsa = [ + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +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)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] + +[[package]] +name = "ecdsa" +version = "0.18.0" +description = "ECDSA cryptographic signature library (pure python)" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, ] -embit = [ + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[[package]] +name = "embit" +version = "0.4.9" +description = "yet another bitcoin library" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"}, ] -enum34 = [ + +[[package]] +name = "enum34" +version = "1.1.10" +description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "enum34-1.1.10-py2-none-any.whl", hash = "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53"}, {file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"}, {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, ] -environs = [ + +[[package]] +name = "environs" +version = "9.5.0" +description = "simplified environment variable parsing" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "environs-9.5.0-py2.py3-none-any.whl", hash = "sha256:1e549569a3de49c05f856f40bce86979e7d5ffbbc4398e7f338574c220189124"}, {file = "environs-9.5.0.tar.gz", hash = "sha256:a76307b36fbe856bdca7ee9161e6c466fd7fcffc297109a118c59b54e27e30c9"}, ] -fastapi = [ + +[package.dependencies] +marshmallow = ">=3.0.0" +python-dotenv = "*" + +[package.extras] +dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] +django = ["dj-database-url", "dj-email-url", "django-cache-url"] +lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] +tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] + +[[package]] +name = "fastapi" +version = "0.83.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.6.1" +files = [ {file = "fastapi-0.83.0-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"}, {file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"}, ] -grpcio = [ + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = "0.19.1" + +[package.extras] +all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] +dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] +doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"] +test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] + +[[package]] +name = "grpcio" +version = "1.51.1" +description = "HTTP/2-based RPC framework" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "grpcio-1.51.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:cc2bece1737b44d878cc1510ea04469a8073dbbcdd762175168937ae4742dfb3"}, {file = "grpcio-1.51.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:e223a9793522680beae44671b9ed8f6d25bbe5ddf8887e66aebad5e0686049ef"}, {file = "grpcio-1.51.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:24ac1154c4b2ab4a0c5326a76161547e70664cd2c39ba75f00fc8a2170964ea2"}, @@ -1471,15 +675,52 @@ grpcio = [ {file = "grpcio-1.51.1-cp39-cp39-win_amd64.whl", hash = "sha256:2b170eaf51518275c9b6b22ccb59450537c5a8555326fd96ff7391b5dd75303c"}, {file = "grpcio-1.51.1.tar.gz", hash = "sha256:e6dfc2b6567b1c261739b43d9c59d201c1b89e017afd9e684d85aa7a186c9f7a"}, ] -h11 = [ + +[package.extras] +protobuf = ["grpcio-tools (>=1.51.1)"] + +[[package]] +name = "h11" +version = "0.12.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, ] -httpcore = [ + +[[package]] +name = "httpcore" +version = "0.15.0" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"}, {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"}, ] -httptools = [ + +[package.dependencies] +anyio = ">=3.0.0,<4.0.0" +certifi = "*" +h11 = ">=0.11,<0.13" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "httptools" +version = "0.4.0" +description = "A collection of framework independent HTTP protocol utils." +category = "main" +optional = false +python-versions = ">=3.5.0" +files = [ {file = "httptools-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcddfe70553be717d9745990dfdb194e22ee0f60eb8f48c0794e7bfeda30d2d5"}, {file = "httptools-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1ee0b459257e222b878a6c09ccf233957d3a4dcb883b0847640af98d2d9aac23"}, {file = "httptools-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceafd5e960b39c7e0d160a1936b68eb87c5e79b3979d66e774f0c77d4d8faaed"}, @@ -1515,39 +756,159 @@ httptools = [ {file = "httptools-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:34d2903dd2a3dd85d33705b6fde40bf91fc44411661283763fd0746723963c83"}, {file = "httptools-0.4.0.tar.gz", hash = "sha256:2c9a930c378b3d15d6b695fb95ebcff81a7395b4f9775c4f10a076beb0b2c1ff"}, ] -httpx = [ + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "httpx" +version = "0.23.0" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"}, {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, ] -idna = [ + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.16.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -importlib-metadata = [ + +[[package]] +name = "importlib-metadata" +version = "5.0.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, ] -iniconfig = [ + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "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 (>=0.9.2)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, + +[[package]] +name = "isort" +version = "5.11.4" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"}, + {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"}, ] -jinja2 = [ + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jinja2" +version = "3.0.1" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, ] -lnurl = [ + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "lnurl" +version = "0.3.6" +description = "LNURL implementation for Python." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "lnurl-0.3.6-py3-none-any.whl", hash = "sha256:579982fd8c4d25bc84c61c74ec45cb7999fa1fa2426f5d5aeb0160ba333b9c92"}, {file = "lnurl-0.3.6.tar.gz", hash = "sha256:8af07460115a48f3122a5a9c9a6062bee3897d5f6ab4c9a60f6561a83a8234f6"}, ] -loguru = [ + +[package.dependencies] +bech32 = "*" +pydantic = "*" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "loguru" +version = "0.6.0" +description = "Python logging made (stupidly) simple" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"}, {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"}, ] -markupsafe = [ + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils (==0.16)", "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)"] + +[[package]] +name = "markupsafe" +version = "2.0.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {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"}, @@ -1618,15 +979,53 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] -marshmallow = [ + +[[package]] +name = "marshmallow" +version = "3.18.0" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "marshmallow-3.18.0-py3-none-any.whl", hash = "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104"}, {file = "marshmallow-3.18.0.tar.gz", hash = "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7"}, ] -mock = [ + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.1.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "mock" +version = "4.0.3" +description = "Rolling backport of unittest.mock for all Pythons" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"}, {file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"}, ] -mypy = [ + +[package.extras] +build = ["blurb", "twine", "wheel"] +docs = ["sphinx"] +test = ["pytest (<5.4)", "pytest-cov"] + +[[package]] +name = "mypy" +version = "0.971" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"}, {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"}, {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"}, @@ -1651,51 +1050,157 @@ mypy = [ {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"}, {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, ] -mypy-extensions = [ + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -outcome = [ + +[[package]] +name = "outcome" +version = "1.2.0" +description = "Capture the outcome of Python function calls." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"}, {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"}, ] -packaging = [ + +[package.dependencies] +attrs = ">=19.2.0" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -pathlib2 = [ + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathlib2" +version = "2.3.7.post1" +description = "Object-oriented filesystem paths" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] -pathspec = [ - {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, - {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, + +[package.dependencies] +six = "*" + +[[package]] +name = "pathspec" +version = "0.10.3" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, + {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, ] -platformdirs = [ - {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, - {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, + +[[package]] +name = "platformdirs" +version = "2.6.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, ] -pluggy = [ + +[package.dependencies] +typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -protobuf = [ - {file = "protobuf-4.21.10-cp310-abi3-win32.whl", hash = "sha256:e92768d17473657c87e98b79a4c7724b0ddfa23211b05ce137bfdc55e734e36f"}, - {file = "protobuf-4.21.10-cp310-abi3-win_amd64.whl", hash = "sha256:0c968753028cb14b1d24cc839723f7e9505b305fc588a37a9e0f7d270cb59d89"}, - {file = "protobuf-4.21.10-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:e53165dd14d19abc7f50733f365de431e51d1d262db40c0ee22e271a074fac59"}, - {file = "protobuf-4.21.10-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:5efa8a8162ada7e10847140308fbf84fdc5b89dc21655d12ec04aed87284fe07"}, - {file = "protobuf-4.21.10-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:2a172741b5b041a896b621cef4277077afd571e0d3a6e524e7171f1c70e33200"}, - {file = "protobuf-4.21.10-cp37-cp37m-win32.whl", hash = "sha256:05cbcb9a25cd781fd949f93f6f98a911883868c0360c6d2264fc99a903c8f0d7"}, - {file = "protobuf-4.21.10-cp37-cp37m-win_amd64.whl", hash = "sha256:3f08f04b4f101dd469efbcc1731fbf48068eccd8a42f4e2ea530aa012a5f56f8"}, - {file = "protobuf-4.21.10-cp38-cp38-win32.whl", hash = "sha256:6b809f20923b6ef49dc1755cb50bdb21be179b4a3c7ffcab1fe5d3f139b58a51"}, - {file = "protobuf-4.21.10-cp38-cp38-win_amd64.whl", hash = "sha256:81b233a06c62387ea5c9be2cd9aedd2ba09940e91da53b920e9ff5bd98e48e7f"}, - {file = "protobuf-4.21.10-cp39-cp39-win32.whl", hash = "sha256:b78d7c2c36b51c0041b9bf000be4adb09f4112bfc40bc7a9d48ac0b0dfad139e"}, - {file = "protobuf-4.21.10-cp39-cp39-win_amd64.whl", hash = "sha256:0413addc126c40a5440ee59be098de1007183d68e9f5f20ed5fbc44848f417ca"}, - {file = "protobuf-4.21.10-py2.py3-none-any.whl", hash = "sha256:a5e89eabaa0ca72ce1b7c8104a740d44cdb67942cbbed00c69a4c0541de17107"}, - {file = "protobuf-4.21.10-py3-none-any.whl", hash = "sha256:5096b3922b45e4b7a04d3d3cb855d13bb5ccd4d5e44b129e706232ebf0ffb870"}, - {file = "protobuf-4.21.10.tar.gz", hash = "sha256:4d97c16c0d11155b3714a29245461f0eb60cace294455077f3a3b8a629afa383"}, + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "protobuf" +version = "4.21.12" +description = "" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.21.12-cp310-abi3-win32.whl", hash = "sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1"}, + {file = "protobuf-4.21.12-cp310-abi3-win_amd64.whl", hash = "sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2"}, + {file = "protobuf-4.21.12-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791"}, + {file = "protobuf-4.21.12-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97"}, + {file = "protobuf-4.21.12-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7"}, + {file = "protobuf-4.21.12-cp37-cp37m-win32.whl", hash = "sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717"}, + {file = "protobuf-4.21.12-cp37-cp37m-win_amd64.whl", hash = "sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"}, + {file = "protobuf-4.21.12-cp38-cp38-win32.whl", hash = "sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec"}, + {file = "protobuf-4.21.12-cp38-cp38-win_amd64.whl", hash = "sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30"}, + {file = "protobuf-4.21.12-cp39-cp39-win32.whl", hash = "sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc"}, + {file = "protobuf-4.21.12-cp39-cp39-win_amd64.whl", hash = "sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b"}, + {file = "protobuf-4.21.12-py2.py3-none-any.whl", hash = "sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5"}, + {file = "protobuf-4.21.12-py3-none-any.whl", hash = "sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462"}, + {file = "protobuf-4.21.12.tar.gz", hash = "sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab"}, ] -psycopg2-binary = [ + +[[package]] +name = "psycopg2-binary" +version = "2.9.1" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, {file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"}, {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:542875f62bc56e91c6eac05a0deadeae20e1730be4c6334d8f04c944fcd99759"}, @@ -1733,15 +1238,39 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.1-cp39-cp39-win32.whl", hash = "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975"}, {file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"}, ] -py = [ + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] -pycparser = [ + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -pycryptodomex = [ + +[[package]] +name = "pycryptodomex" +version = "3.14.1" +description = "Cryptographic library for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "pycryptodomex-3.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca88f2f7020002638276439a01ffbb0355634907d1aa5ca91f3dc0c2e44e8f3b"}, {file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:8536bc08d130cae6dcba1ea689f2913dfd332d06113904d171f2f56da6228e89"}, {file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:406ec8cfe0c098fadb18d597dc2ee6de4428d640c0ccafa453f3d9b2e58d29e2"}, @@ -1770,7 +1299,15 @@ pycryptodomex = [ {file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:530756d2faa40af4c1f74123e1d889bd07feae45bac2fd32f259a35f7aa74151"}, {file = "pycryptodomex-3.14.1.tar.gz", hash = "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"}, ] -pydantic = [ + +[[package]] +name = "pydantic" +version = "1.10.2" +description = "Data validation and settings management using python type hints" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, @@ -1808,58 +1345,229 @@ pydantic = [ {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, ] -pyln-bolt7 = [ + +[package.dependencies] +typing-extensions = ">=4.1.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyln-bolt7" +version = "1.0.246" +description = "BOLT7" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ {file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"}, {file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"}, ] -pyln-client = [ + +[[package]] +name = "pyln-client" +version = "0.11.1" +description = "Client library and plugin library for Core Lightning" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ {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 = [ + +[package.dependencies] +pyln-bolt7 = ">=1.0,<2.0" +pyln-proto = ">=0.11,<0.12" + +[[package]] +name = "pyln-proto" +version = "0.11.1" +description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ {file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"}, {file = "pyln_proto-0.11.1-py3-none-any.whl", hash = "sha256:27b2e04a81b894f69018279c0ce4aa2e7ccd03b86dd9783f96b9d8d1498c8393"}, ] -pyparsing = [ + +[package.dependencies] +base58 = ">=2.1.1,<3.0.0" +bitstring = ">=3.1.9,<4.0.0" +coincurve = ">=17.0.0,<18.0.0" +cryptography = ">=36.0.1,<37.0.0" +PySocks = ">=1.7.1,<2.0.0" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" +files = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] -pypng = [ + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pypng" +version = "0.0.21" +description = "Pure Python library for saving and loading PNG images" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"}, ] -pyqrcode = [ + +[[package]] +name = "pyqrcode" +version = "1.2.1" +description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"}, {file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"}, ] -pyscss = [ + +[package.extras] +png = ["pypng (>=0.0.13)"] + +[[package]] +name = "pyscss" +version = "1.4.0" +description = "pyScss, a Scss compiler for Python" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"}, ] -pysocks = [ + +[package.dependencies] +enum34 = "*" +pathlib2 = "*" +six = "*" + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {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"}, ] -pytest = [ + +[[package]] +name = "pytest" +version = "7.1.3" +description = "pytest: simple powerful testing with Python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, ] -pytest-asyncio = [ + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.19.0" +description = "Pytest support for asyncio" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, {file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"}, ] -pytest-cov = [ + +[package.dependencies] +pytest = ">=6.1.0" +typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} + +[package.extras] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] -python-bitcoinlib = [ + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "python-bitcoinlib" +version = "0.11.2" +description = "The Swiss Army Knife of the Bitcoin protocol." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "python-bitcoinlib-0.11.2.tar.gz", hash = "sha256:61ba514e0d232cc84741e49862dcedaf37199b40bba252a17edc654f63d13f39"}, {file = "python_bitcoinlib-0.11.2-py3-none-any.whl", hash = "sha256:78bd4ee717fe805cd760dfdd08765e77b7c7dbef4627f8596285e84953756508"}, ] -python-dotenv = [ + +[[package]] +name = "python-dotenv" +version = "0.21.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"}, {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"}, ] -pyyaml = [ + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ {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"}, @@ -1890,19 +1598,73 @@ 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 = [ + +[[package]] +name = "represent" +version = "1.6.0.post0" +description = "Create __repr__ automatically or declaratively." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {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"}, ] -requests = [ + +[package.dependencies] +six = ">=1.8.0" + +[package.extras] +test = ["ipython", "mock", "pytest (>=3.0.5)"] + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] -rfc3986 = [ + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] -secp256k1 = [ + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "secp256k1" +version = "0.14.0" +description = "FFI bindings to libsecp256k1" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "secp256k1-0.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f666c67dcf1dc69e1448b2ede5e12aaf382b600204a61dbc65e4f82cea444405"}, {file = "secp256k1-0.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fcabb3c3497a902fb61eec72d1b69bf72747d7bcc2a732d56d9319a1e8322262"}, {file = "secp256k1-0.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a27c479ab60571502516a1506a562d0a9df062de8ad645313fabfcc97252816"}, @@ -1927,23 +1689,71 @@ 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 = [ + +[package.dependencies] +cffi = ">=1.3.0" + +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, ] -shortuuid = [ + +[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)", "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-timeout", "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" +description = "A generator library for concise, unambiguous and URL-safe UUIDs." +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"}, {file = "shortuuid-1.0.1.tar.gz", hash = "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f"}, ] -six = [ + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -sniffio = [ + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] -sqlalchemy = [ + +[[package]] +name = "sqlalchemy" +version = "1.3.24" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, @@ -1979,22 +1789,91 @@ sqlalchemy = [ {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, ] -sqlalchemy-aio = [ + +[package.extras] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mysql = ["mysqlclient"] +oracle = ["cx_oracle"] +postgresql = ["psycopg2"] +postgresql-pg8000 = ["pg8000 (<1.16.6)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql", "pymysql (<1)"] + +[[package]] +name = "sqlalchemy-aio" +version = "0.17.0" +description = "Async support for SQLAlchemy." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "sqlalchemy_aio-0.17.0-py3-none-any.whl", hash = "sha256:3f4aa392c38f032d6734826a4138a0f02ed3122d442ed142be1e5964f2a33b60"}, {file = "sqlalchemy_aio-0.17.0.tar.gz", hash = "sha256:f531c7982662d71dfc0b117e77bb2ed544e25cd5361e76cf9f5208edcfb71f7b"}, ] -sse-starlette = [ + +[package.dependencies] +outcome = "*" +represent = ">=1.4" +sqlalchemy = "<1.4" + +[package.extras] +test = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)", "pytest-trio (>=0.6)"] +test-noextras = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)"] +trio = ["trio (>=0.15)"] + +[[package]] +name = "sse-starlette" +version = "0.6.2" +description = "SSE plugin for Starlette" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "sse-starlette-0.6.2.tar.gz", hash = "sha256:1c0cc62cc7d021a386dc06a16a9ddc3e2861d19da6bc2e654e65cc111e820456"}, ] -starlette = [ + +[[package]] +name = "starlette" +version = "0.19.1" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"}, {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"}, ] -tomli = [ + +[package.dependencies] +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -typed-ast = [ + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, @@ -2020,23 +1899,76 @@ typed-ast = [ {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] -types-protobuf = [ + +[[package]] +name = "types-protobuf" +version = "3.20.4.6" +description = "Typing stubs for protobuf" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "types-protobuf-3.20.4.6.tar.gz", hash = "sha256:ba27443c592bbec1629dd69494a24c84461c63f0d3b7d648ce258aaae9680965"}, {file = "types_protobuf-3.20.4.6-py3-none-any.whl", hash = "sha256:ab2d315ba82246b83d28f8797c98dc0fe1dd5cfd187909e56faf87239aedaae3"}, ] -typing-extensions = [ + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -urllib3 = [ + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +files = [ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] -uvicorn = [ + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "uvicorn" +version = "0.18.3" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"}, {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"}, ] -uvloop = [ + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] + +[[package]] +name = "uvloop" +version = "0.16.0" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"}, {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"}, @@ -2054,15 +1986,49 @@ uvloop = [ {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"}, {file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"}, ] -watchgod = [ + +[package.extras] +dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] + +[[package]] +name = "watchgod" +version = "0.7" +description = "Simple, modern file watching and code reload in python." +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"}, {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, ] -websocket-client = [ + +[[package]] +name = "websocket-client" +version = "1.3.3" +description = "WebSocket client for Python with low level API options" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "websocket-client-1.3.3.tar.gz", hash = "sha256:d58c5f284d6a9bf8379dab423259fe8f85b70d5fa5d2916d5791a84594b122b1"}, {file = "websocket_client-1.3.3-py3-none-any.whl", hash = "sha256:5d55652dc1d0b3c734f044337d929aaf83f4f9138816ec680c1aefefb4dc4877"}, ] -websockets = [ + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "websockets" +version = "10.0" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "websockets-10.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cd8c6f2ec24aedace251017bc7a414525171d4e6578f914acab9349362def4da"}, {file = "websockets-10.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f6b814cff6aadc4288297cb3a248614829c6e4ff5556593c44a115e9dd49939"}, {file = "websockets-10.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:01db0ecd1a0ca6702d02a5ed40413e18b7d22f94afb3bbe0d323bac86c42c1c8"}, @@ -2089,11 +2055,39 @@ websockets = [ {file = "websockets-10.0-cp39-cp39-win_amd64.whl", hash = "sha256:c5880442f5fc268f1ef6d37b2c152c114deccca73f48e3a8c48004d2f16f4567"}, {file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"}, ] -win32-setctime = [ + +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, ] -zipp = [ + +[package.extras] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] + +[[package]] +name = "zipp" +version = "3.9.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"}, {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"}, ] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" +content-hash = "73e1443abc1eed24639a5297a66b2eb16e80491f8849b8008e065f153de215c7" diff --git a/pyproject.toml b/pyproject.toml index 22725ebb..46808433 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,16 +86,34 @@ lnbits = "lnbits.server:main" profile = "black" [tool.mypy] -ignore_missing_imports = "True" files = "lnbits" exclude = """(?x)( - ^lnbits/extensions/boltz. - | ^lnbits/extensions/livestream. - | ^lnbits/extensions/lnurldevice. - | ^lnbits/extensions/watchonly. + ^lnbits/extensions/bleskomat. + | ^lnbits/extensions/boltz. | ^lnbits/wallets/lnd_grpc_files. )""" +[[tool.mypy.overrides]] +module = [ + "embit.*", + "secp256k1.*", + "uvicorn.*", + "sqlalchemy.*", + "sqlalchemy_aio.*", + "websocket.*", + "websockets.*", + "pyqrcode.*", + "cashu.*", + "shortuuid.*", + "grpc.*", + "lnurl.*", + "bitstring.*", + "ecdsa.*", + "psycopg2.*", + "pyngrok.*", +] +ignore_missing_imports = "True" + [tool.pytest.ini_options] addopts = "--durations=1 -s --cov=lnbits --cov-report=xml" testpaths = [ diff --git a/tests/data/mock_data.zip b/tests/data/mock_data.zip index 4070bee7..8356cd54 100644 Binary files a/tests/data/mock_data.zip and b/tests/data/mock_data.zip differ diff --git a/tools/conv.py b/tools/conv.py index 2ba723f2..f01295fc 100644 --- a/tools/conv.py +++ b/tools/conv.py @@ -1,3 +1,8 @@ +# Python script to migrate an LNbits SQLite DB to Postgres +# All credits to @Fritz446 for the awesome work + +# pip install psycopg2 OR psycopg2-binary + import argparse import os import sqlite3 @@ -8,13 +13,6 @@ import psycopg2 from lnbits.settings import settings -# Python script to migrate an LNbits SQLite DB to Postgres -# All credits to @Fritz446 for the awesome work - -# pip install psycopg2 OR psycopg2-binary - -# Change these values as needed - sqfolder = settings.lnbits_data_folder db_url = settings.lnbits_database_url @@ -31,7 +29,7 @@ else: pgschema = "" -def get_sqlite_cursor(sqdb) -> sqlite3: +def get_sqlite_cursor(sqdb): consq = sqlite3.connect(sqdb) return consq.cursor() @@ -112,12 +110,15 @@ def migrate_core(file: str, exclude_tables: List[str] = []): def migrate_ext(file: str): filename = os.path.basename(file) schema = filename.replace("ext_", "").split(".")[0] - print(f"Migrating ext: {file}.{schema}") + print(f"Migrating ext: {schema} from file {file}") migrate_db(file, schema) print(f"✅ Migrated ext: {schema}") def migrate_db(file: str, schema: str, exclude_tables: List[str] = []): + # first we check if this file exists: + assert os.path.isfile(file), f"{file} does not exist!" + sq = get_sqlite_cursor(file) tables = sq.execute( """ @@ -139,6 +140,10 @@ def migrate_db(file: str, schema: str, exclude_tables: List[str] = []): q = build_insert_query(schema, tableName, columns) data = sq.execute(f"SELECT * FROM {tableName};").fetchall() + + if len(data) == 0: + print(f"🛑 You sneaky dev! Table {tableName} is empty!") + insert_to_pg(q, data) sq.close()