diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ba522e8..36cacff 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,33 +1,28 @@ - { - "name": "lnbits_nwc_provider", - "image": "mcr.microsoft.com/devcontainers/python:1-3.9-bullseye", - "features": { - "ghcr.io/devcontainers-contrib/features/poetry:2": { - } - - }, - "mounts": [ - "source=${localWorkspaceFolder}/.devcontainer/start.sh,target=/start-lnbits.sh,type=bind", - "source=${localWorkspaceFolder}/.devcontainer/setup.sh,target=/setup.sh,type=bind", - "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" - - ], - "containerEnv": { - "IS_DEV_CONTAINER": "true", - }, - "postCreateCommand": "/bin/bash /setup.sh ${containerWorkspaceFolder}", - "postStartCommand": "/bin/bash /start-lnbits.sh", - "forwardPorts": [ - 5000 - ], - "customizations": { - "vscode": { - "settings": { - // "python.pythonPath": "/opt/python/bin/python3.9" - } - } - }, - "runArgs": ["--network", "host"] - + "name": "lnbits_nwc_provider", + "image": "mcr.microsoft.com/devcontainers/python:1-3.9-bullseye", + "features": { + "ghcr.io/devcontainers-contrib/features/poetry:2": {} + }, + "mounts": [ + "source=${localWorkspaceFolder}/.devcontainer/start.sh,target=/start-lnbits.sh,type=bind", + "source=${localWorkspaceFolder}/.devcontainer/setup.sh,target=/setup.sh,type=bind", + "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" + ], + "containerEnv": { + "IS_DEV_CONTAINER": "true" + }, + "postCreateCommand": "/bin/bash /setup.sh ${containerWorkspaceFolder}", + "postStartCommand": "/bin/bash /start-lnbits.sh", + "forwardPorts": [5000], + "customizations": { + "vscode": { + "settings": { + "python.analysis.extraPaths": ["/home/vscode/lnbits"], + "python.pythonPath": "/home/vscode/python" + // "python.pythonPath": "/opt/python/bin/python3.9" + } + } + }, + "runArgs": ["--network", "host"] } diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 41e341c..6151741 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -1,4 +1,5 @@ #!/bin/bash +echo $PYTHONPATH CONTAINER_WORKSPACE_FOLDER=$1 cd $CONTAINER_WORKSPACE_FOLDER sudo apt update -y @@ -8,14 +9,18 @@ sudo apt-get install -y docker.io curl -fsSL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh sudo bash /tmp/nodesource_setup.sh sudo apt-get install -y nodejs -if [ ! -d lnbits ] ; then - git clone https://github.com/lnbits/lnbits.git; + +cd $HOME +echo $PWD +if [ ! -d ./lnbits ] ; then + git clone https://github.com/lnbits/lnbits.git lnbits fi cd lnbits +echo $PWD git checkout 0.12.8 poetry env use python3.9 -# export VENV_PATH=$(poetry env info -p) -# sudo ln -s $VENV_PATH /opt/python +POETRY_PYTHON_PATH=$(poetry env info -p)/bin/python +ln -sf $POETRY_PYTHON_PATH /home/vscode/python make bundle poetry install --no-interaction mkdir -p lnbits/extensions/ @@ -23,4 +28,7 @@ if [ ! -d lnbits/extensions/nwcprovider ] ; then ln -s $CONTAINER_WORKSPACE_FOLDER lnbits/extensions/nwcprovider fi - +cd $CONTAINER_WORKSPACE_FOLDER +poetry install --no-interaction +npm i prettier +npm i pyright \ No newline at end of file diff --git a/.devcontainer/start.sh b/.devcontainer/start.sh index 85d96ee..5650e5c 100644 --- a/.devcontainer/start.sh +++ b/.devcontainer/start.sh @@ -1,4 +1,4 @@ #!/bin/bash -ln -s $PWD/.devcontainer/.env ./lnbits/.env -cd ./lnbits +ln -s $PWD/.devcontainer/.env $HOME/lnbits/.env +cd $HOME/lnbits poetry run lnbits diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f82e72e..ed44344 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,7 +4,6 @@ on: - "v[0-9]+.[0-9]+.[0-9]+" jobs: - release: runs-on: ubuntu-latest steps: @@ -23,7 +22,7 @@ jobs: with: token: ${{ secrets.EXT_GITHUB }} repository: lnbits/lnbits-extensions - path: './lnbits-extensions' + path: "./lnbits-extensions" - name: setup git user run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f82e72e..ed44344 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,6 @@ on: - "v[0-9]+.[0-9]+.[0-9]+" jobs: - release: runs-on: ubuntu-latest steps: @@ -23,7 +22,7 @@ jobs: with: token: ${{ secrets.EXT_GITHUB }} repository: lnbits/lnbits-extensions - path: './lnbits-extensions' + path: "./lnbits-extensions" - name: setup git user run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 010a30b..4d337da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,45 +4,43 @@ on: push: pull_request: - jobs: test: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v2 + - name: Checkout code + uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.9' + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.9" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install poetry + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install poetry - - name: Run setup script - run: | - /bin/bash .devcontainer/setup.sh ${{ github.workspace }} + - name: Run setup script + run: | + /bin/bash .devcontainer/setup.sh ${{ github.workspace }} + - name: Run unit tests + run: | + cd lnbits + poetry_env_path=$(poetry env info --path) + source $poetry_env_path/bin/activate + cd .. + pytest tests/unit/*.py -s - - name: Run unit tests - run: | - cd lnbits - poetry_env_path=$(poetry env info --path) - source $poetry_env_path/bin/activate - cd .. - pytest tests/unit/*.py -s + - name: Setup integration tests + run: bash tests/integration/start.sh - - name: Setup integration tests - run: bash tests/integration/start.sh - - - name: Run integration tests - run: | - cd lnbits - poetry_env_path=$(poetry env info --path) - source $poetry_env_path/bin/activate - cd .. - pytest tests/unit/*.py -s \ No newline at end of file + - name: Run integration tests + run: | + cd lnbits + poetry_env_path=$(poetry env info --path) + source $poetry_env_path/bin/activate + cd .. + pytest tests/integration/*.py -s diff --git a/.gitignore b/.gitignore index 3f09e69..0f4a59a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ __pycache__ lnbits tests/integration/strfry-data -tests/integration/lnbits_itest_data \ No newline at end of file +tests/integration/lnbits_itest_data +node_modules +.pytest_cache +.ruff_cache +.mypy_cache \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fb619f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +all: format check + +format: prettier black ruff + +check: mypy pyright checkblack checkruff checkprettier + +prettier: + poetry run ./node_modules/.bin/prettier --write . +pyright: + poetry run ./node_modules/.bin/pyright + +mypy: + poetry run mypy . + +black: + poetry run black . + +ruff: + poetry run ruff check . --fix + +checkruff: + poetry run ruff check . + +checkprettier: + poetry run ./node_modules/.bin/prettier --check . + +checkblack: + poetry run black --check . + +checkeditorconfig: + editorconfig-checker + +test: + PYTHONUNBUFFERED=1 \ + DEBUG=true \ + poetry run pytest tests/unit/*.py -s +install-pre-commit-hook: + @echo "Installing pre-commit hook to git" + @echo "Uninstall the hook with poetry run pre-commit uninstall" + poetry run pre-commit install + +pre-commit: + poetry run pre-commit run --all-files + + +checkbundle: + @echo "skipping checkbundle" \ No newline at end of file diff --git a/README.md b/README.md index 1bf7653..605ec9d 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ Configure the extension from the "Settings" page in the top right menu when logg ### Configuration Options: -| Key | Description | Default | -|---------------|------------------------------------------------------------------------------------------------------------------------------------|--------------------------------| -| relay | URL of the nostr relay for dispatching and receiving NWC events. Use public relays or a custom one. Specify `nostrclient` to connect to the [nostrclient extension](https://github.com/lnbits/nostrclient). | nostrclient | -| provider_key | Nostr secret key of the NWC Service Provider. | Random key generated on install | -| relay_alias | Relay URL to display in pairing URLs. Set if different from `relay`. | Empty (uses the `relay` value) | +| Key | Description | Default | +| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| relay | URL of the nostr relay for dispatching and receiving NWC events. Use public relays or a custom one. Specify `nostrclient` to connect to the [nostrclient extension](https://github.com/lnbits/nostrclient). | nostrclient | +| provider_key | Nostr secret key of the NWC Service Provider. | Random key generated on install | +| relay_alias | Relay URL to display in pairing URLs. Set if different from `relay`. | Empty (uses the `relay` value) | ### Using Nostrclient diff --git a/__init__.py b/__init__.py index dfdc095..f0adf33 100644 --- a/__init__.py +++ b/__init__.py @@ -1,18 +1,22 @@ import asyncio + from fastapi import APIRouter -from lnbits.db import Database -from lnbits.helpers import template_renderer -from lnbits.tasks import create_permanent_unique_task from loguru import logger +from lnbits.tasks import create_permanent_unique_task + +from .crud import db +from .tasks import handle_execution_queue, handle_nwc +from .views import nwcprovider_router +from .views_api import nwcprovider_api_router -db = Database("ext_nwcprovider") -execution_queue = asyncio.Queue() scheduled_tasks: list[asyncio.Task] = [] nwcprovider_ext: APIRouter = APIRouter( prefix="/nwcprovider", tags=["NWC Service Provider"] ) +nwcprovider_ext.include_router(nwcprovider_router) +nwcprovider_ext.include_router(nwcprovider_api_router) nwcprovider_static_files = [ { @@ -22,25 +26,6 @@ nwcprovider_static_files = [ ] -def nwcprovider_renderer(): - return template_renderer(["nwcprovider/templates"]) - - -async def enqueue(action): - future = asyncio.Future() - execution_queue.put_nowait({ - "action": action, - "future": future - }) - return await future - -from .views import * -from .views_api import * -from .tasks import handle_nwc, handle_execution_queue - - - - def nwcprovider_stop(): for task in scheduled_tasks: try: @@ -53,6 +38,9 @@ def nwcprovider_start(): task = create_permanent_unique_task("ext_nwcprovider", handle_nwc) scheduled_tasks.append(task) task = create_permanent_unique_task( - "ext_nwcprovider_execution_queue", handle_execution_queue) + "ext_nwcprovider_execution_queue", handle_execution_queue + ) scheduled_tasks.append(task) + +__all__ = ["db"] diff --git a/crud.py b/crud.py index 9745211..806cbf2 100644 --- a/crud.py +++ b/crud.py @@ -1,56 +1,78 @@ -from typing import Optional, Any, List -from . import db -from .models import NWCKey, NWCBudget, NWCLog, NWCNewBudget import time -from . import enqueue -import json +from typing import List, Optional + +from lnbits.db import Database + +from .execution_queue import enqueue +from .models import NWCBudget, NWCKey, NWCNewBudget + +db = Database("ext_nwcprovider") + async def create_nwc( - pubkey: str, - wallet_id:str, - description: str, - expires_at: int , - permissions:List[str] , - budgets: Optional[List[NWCNewBudget]] = None + pubkey: str, + wallet_id: str, + description: str, + expires_at: int, + permissions: List[str], + budgets: Optional[List[NWCNewBudget]] = None, ) -> NWCKey: # Check if the key already exists if await get_nwc(pubkey, None, True): raise Exception("Public key already used") # If not, create it - now=int(time.time()) + now = int(time.time()) await db.execute( """ - INSERT INTO nwcprovider.keys (pubkey, wallet, description, permissions, created_at, expires_at, last_used) + INSERT INTO nwcprovider.keys ( + pubkey, + wallet, + description, + permissions, + created_at, + expires_at, + last_used + ) VALUES (?, ?, ?, ?, ?, ?, ?) """, - (pubkey, wallet_id, description, " ".join(permissions), now,int(expires_at) if expires_at else 0, now) + ( + pubkey, + wallet_id, + description, + " ".join(permissions), + now, + int(expires_at) if expires_at else 0, + now, + ), ) # Add budgets if budgets: for budget in budgets: await db.execute( """ - INSERT INTO nwcprovider.budgets (pubkey, budget_msats, refresh_window, created_at) + INSERT INTO nwcprovider.budgets ( + pubkey, + budget_msats, + refresh_window, + created_at + ) VALUES (?, ?, ?, ?) """, - (pubkey, budget.budget_msats, budget.refresh_window, budget.created_at) + (pubkey, budget.budget_msats, budget.refresh_window, budget.created_at), ) # Return the created key return NWCKey( - pubkey=pubkey, - wallet=wallet_id, - description=description, - expires_at=expires_at, + pubkey=pubkey, + wallet=wallet_id, + description=description, + expires_at=expires_at, permissions=" ".join(permissions), created_at=now, - last_used=now + last_used=now, ) -async def delete_nwc( - pubkey: str, - wallet_id:str -): +async def delete_nwc(pubkey: str, wallet_id: str): nwc = await get_nwc(pubkey, wallet_id) if not nwc: raise Exception("Public key does not exist") @@ -58,35 +80,45 @@ async def delete_nwc( """ DELETE FROM nwcprovider.keys WHERE pubkey = ? AND wallet = ? """, - (pubkey, wallet_id) + (pubkey, wallet_id), ) - async def get_wallet_nwcs( - wallet_id: str, - include_expired: Optional[bool] = False + wallet_id: str, include_expired: Optional[bool] = False ) -> List[NWCKey]: rows = await db.fetchall( - "SELECT * FROM nwcprovider.keys WHERE wallet = ? AND (expires_at = 0 OR expires_at > ?)", (wallet_id, int(time.time()) if not include_expired else -1 ) + """ + SELECT * FROM nwcprovider.keys + WHERE wallet = ? AND (expires_at = 0 OR expires_at > ?) + """, + (wallet_id, int(time.time()) if not include_expired else -1), ) return [NWCKey(**row) for row in rows] async def get_nwc( - pubkey: str, - wallet_id: Optional[str] = None, - include_expired: Optional[bool] = False, - refresh_last_used: Optional[bool] = False + pubkey: str, + wallet_id: Optional[str] = None, + include_expired: Optional[bool] = False, + refresh_last_used: Optional[bool] = False, ) -> Optional[NWCKey]: # expires_at = 0 means it never expires if wallet_id: row = await db.fetchone( - "SELECT * FROM nwcprovider.keys WHERE pubkey = ? AND wallet = ? AND (expires_at = 0 OR expires_at > ?)", (pubkey, wallet_id, int(time.time()) if not include_expired else -1 ) + """ + SELECT * FROM nwcprovider.keys + WHERE pubkey = ? AND wallet = ? AND (expires_at = 0 OR expires_at > ?) + """, + (pubkey, wallet_id, int(time.time()) if not include_expired else -1), ) else: row = await db.fetchone( - "SELECT * FROM nwcprovider.keys WHERE pubkey = ? AND (expires_at = 0 OR expires_at > ?)", (pubkey, int(time.time()) if not include_expired else -1 ) + """ + SELECT * FROM nwcprovider.keys + WHERE pubkey = ? AND (expires_at = 0 OR expires_at > ?) + """, + (pubkey, int(time.time()) if not include_expired else -1), ) if not row: return None @@ -95,10 +127,10 @@ async def get_nwc( """ UPDATE nwcprovider.keys SET last_used = ? WHERE pubkey = ? """, - (int(time.time()), pubkey) + (int(time.time()), pubkey), ) return NWCKey(**row) - + async def get_budgets_nwc(pubkey, calculate_spent=False): rows = await db.fetchall( @@ -110,47 +142,35 @@ async def get_budgets_nwc(pubkey, calculate_spent=False): last_cycle, next_cycle = budget.get_timestamp_range() tot_spent_in_range_msats = await db.fetchone( """ - SELECT SUM(amount_msats) FROM nwcprovider.spent WHERE pubkey = ? AND created_at >= ? AND created_at < ? + SELECT SUM(amount_msats) FROM nwcprovider.spent + WHERE pubkey = ? AND created_at >= ? AND created_at < ? """, - (pubkey, last_cycle, next_cycle) + (pubkey, last_cycle, next_cycle), ) tot_spent_in_range_msats = tot_spent_in_range_msats[0] or 0 - budget.used_budget_msats = tot_spent_in_range_msats + budget.used_budget_msats = tot_spent_in_range_msats return budgets -# async def log_nwc( -# pubkey: str, -# payload:Optional[Any] = None -# ): -# if not payload: payload="" -# payload = json.dumps(payload) -# await db.execute( -# """ -# INSERT INTO nwcprovider.logs (pubkey, payload, created_at) -# VALUES (?, ?, ?) -# """, -# (pubkey, payload, int(time.time())) -# ) - - -async def tracked_spend_nwc( - pubkey: str, - amount_msats: int, - action -): +async def tracked_spend_nwc(pubkey: str, amount_msats: int, action): async def r(): created_at = int(time.time()) budgets = await get_budgets_nwc(pubkey) in_budget = True for budget in budgets: last_cycle, next_cycle = budget.get_timestamp_range() - tot_spent_in_range_msats =(await db.fetchone( - """ - SELECT SUM(amount_msats) FROM nwcprovider.spent WHERE pubkey = ? AND created_at >= ? AND created_at < ? - """, - (pubkey, last_cycle, next_cycle) - ))[0] or 0 + tot_spent_in_range_msats = ( + ( + await db.fetchone( + """ + SELECT SUM(amount_msats) FROM nwcprovider.spent + WHERE pubkey = ? AND created_at >= ? AND created_at < ? + """, + (pubkey, last_cycle, next_cycle), + ) + )[0] + or 0 + ) if tot_spent_in_range_msats + amount_msats > budget.budget_msats: in_budget = False break @@ -162,40 +182,37 @@ async def tracked_spend_nwc( INSERT INTO nwcprovider.spent (pubkey, amount_msats, created_at) VALUES (?, ?, ?) """, - (pubkey, amount_msats, created_at) + (pubkey, amount_msats, created_at), ) return True, out + return await enqueue(r) - -async def get_config_nwc(key:str): - row = await db.fetchone( - "SELECT * FROM nwcprovider.config WHERE key = ?", (key) - ) +async def get_config_nwc(key: str): + row = await db.fetchone("SELECT * FROM nwcprovider.config WHERE key = ?", (key,)) if not row: return None return row["value"] -async def get_all_config_nwc(): - rows = await db.fetchall( - "SELECT * FROM nwcprovider.config" - ) - return {row["key"]:row["value"] for row in rows} -async def set_config_nwc(key:str, value:str): +async def get_all_config_nwc(): + rows = await db.fetchall("SELECT * FROM nwcprovider.config") + return {row["key"]: row["value"] for row in rows} + + +async def set_config_nwc(key: str, value: str): await db.execute( """ DELETE FROM nwcprovider.config WHERE key = ? """, - (key,) + (key,), ) await db.execute( """ INSERT INTO nwcprovider.config (key, value) VALUES (?, ?) """, - (key, value) + (key, value), ) - diff --git a/data/logs/debug.log b/data/logs/debug.log new file mode 100644 index 0000000..e69de29 diff --git a/data/logs/lnbits.log b/data/logs/lnbits.log new file mode 100644 index 0000000..e69de29 diff --git a/execution_queue.py b/execution_queue.py new file mode 100644 index 0000000..972e9a6 --- /dev/null +++ b/execution_queue.py @@ -0,0 +1,10 @@ +import asyncio +from typing import Any, Dict + +execution_queue: asyncio.Queue[Dict[str, Any]] = asyncio.Queue() + + +async def enqueue(action): + future = asyncio.Future() + execution_queue.put_nowait({"action": action, "future": future}) + return await future diff --git a/manifest.json b/manifest.json index 6a77c95..eea77ee 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "repos": [ { "id": "nwcprovider", - "organisation": "riccardobl", + "organisation": "lnbits", "repository": "nwcprovider" } ] diff --git a/migrations.py b/migrations.py index f7afcbb..61d79a4 100644 --- a/migrations.py +++ b/migrations.py @@ -1,5 +1,6 @@ import secp256k1 + async def m001_initial(db): """ Initial tables @@ -8,11 +9,11 @@ async def m001_initial(db): """ CREATE TABLE nwcprovider.keys ( pubkey TEXT PRIMARY KEY, - wallet TEXT NOT NULL, + wallet TEXT NOT NULL, description TEXT NOT NULL, - expires_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL, permissions TEXT NOT NULL, - created_at INTEGER NOT NULL + created_at INTEGER NOT NULL ); """ ) @@ -20,23 +21,27 @@ async def m001_initial(db): await db.execute( f""" CREATE TABLE nwcprovider.spent ( - id {db.serial_primary_key}, - pubkey TEXT NOT NULL, + id {db.serial_primary_key}, + pubkey TEXT NOT NULL, amount_msats INTEGER NOT NULL, - created_at INTEGER NOT NULL, - FOREIGN KEY(pubkey) REFERENCES {db.references_schema}keys(pubkey) ON DELETE CASCADE + created_at INTEGER NOT NULL, + FOREIGN KEY(pubkey) + REFERENCES {db.references_schema}keys(pubkey) + ON DELETE CASCADE ); """ ) - + await db.execute( - f""" + f""" CREATE TABLE nwcprovider.logs ( - id {db.serial_primary_key}, - pubkey TEXT NOT NULL, + id {db.serial_primary_key}, + pubkey TEXT NOT NULL, payload TEXT NOT NULL, - created_at INTEGER NOT NULL, - FOREIGN KEY(pubkey) REFERENCES {db.references_schema}keys(pubkey) ON DELETE CASCADE + created_at INTEGER NOT NULL, + FOREIGN KEY(pubkey) + REFERENCES {db.references_schema}keys(pubkey) + ON DELETE CASCADE ); """ ) @@ -48,14 +53,15 @@ async def m001_initial(db): pubkey TEXT NOT NULL, budget_msats INTEGER NOT NULL, refresh_window INTEGER NOT NULL, - created_at INTEGER NOT NULL, - FOREIGN KEY(pubkey) REFERENCES {db.references_schema}keys(pubkey) ON DELETE CASCADE + created_at INTEGER NOT NULL, + FOREIGN KEY(pubkey) + REFERENCES {db.references_schema}keys(pubkey) + ON DELETE CASCADE ); """ ) - async def m002_config(db): """ Config table @@ -69,6 +75,7 @@ async def m002_config(db): """ ) + async def m003_default_config(db): """ Default config @@ -83,21 +90,20 @@ async def m003_default_config(db): """ INSERT INTO nwcprovider.config (key, value) VALUES ('provider_key', ?); """, - (new_private_key,) + (new_private_key,), ) - async def m004_default_config2(db): """ Default config """ - + await db.execute( """ INSERT INTO nwcprovider.config (key, value) VALUES ('relay_alias', ?); """, - ('',) + ("",), ) diff --git a/models.py b/models.py index b2e0b4f..83f0b2a 100644 --- a/models.py +++ b/models.py @@ -1,54 +1,52 @@ # Data models for your extension -from sqlite3 import Row -from pydantic import BaseModel, Field import time -import json -from typing import List, Dict, Any +from sqlite3 import Row +from typing import Any, Dict, List + from pydantic import BaseModel - class NWCKey(BaseModel): - pubkey: str + pubkey: str wallet: str - description: str + description: str expires_at: int permissions: str - created_at: int - last_used: int - - def getPermissions(cls) -> List[str]: - try: - return cls.permissions.split(" ") - except: - return [] + created_at: int + last_used: int + def get_permissions(self) -> List[str]: + try: + return self.permissions.split(" ") + except Exception: + return [] @classmethod def from_row(cls, row: Dict[str, Any]) -> "NWCKey": return cls(**row) + class NWCBudget(BaseModel): - id: int - pubkey: str - budget_msats: int - refresh_window: int - created_at: int + id: int + pubkey: str + budget_msats: int + refresh_window: int + created_at: int used_budget_msats: int = 0 - def get_timestamp_range(cls): + def get_timestamp_range(self) -> tuple[int, int]: c = int(time.time()) - if cls.refresh_window <= 0: # never refresh + if self.refresh_window <= 0: # never refresh # return a timestamp in the future - return c + 21000000 - # calculate the next refresh timestamp - elapsed = c - cls.created_at - passed_cycles = elapsed // cls.refresh_window - last_cycle = cls.created_at + (passed_cycles * cls.refresh_window) - next_cycle = last_cycle + cls.refresh_window + return c, c + 21000000 + # calculate the next refresh timestamp + elapsed = c - self.created_at + passed_cycles = elapsed // self.refresh_window + last_cycle = self.created_at + (passed_cycles * self.refresh_window) + next_cycle = last_cycle + self.refresh_window return last_cycle, next_cycle - + @classmethod def from_row(cls, row: Row) -> "NWCBudget": return cls(**dict(row)) @@ -81,5 +79,3 @@ class NWCRegistrationRequest(BaseModel): class NWCGetResponse(BaseModel): data: NWCKey budgets: List[NWCBudget] - - diff --git a/NWCServiceProvider.py b/nwcp.py similarity index 54% rename from NWCServiceProvider.py rename to nwcp.py index de0168d..09dfcf2 100644 --- a/NWCServiceProvider.py +++ b/nwcp.py @@ -1,81 +1,90 @@ import asyncio +import base64 import hashlib import json -from typing import Dict -import secp256k1 -from loguru import logger -from lnbits.settings import settings +import random import time -import websockets +from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Union + +import secp256k1 +import websockets.client as websockets from Cryptodome import Random from Cryptodome.Cipher import AES -import base64 -import random -from typing import Union, List, Callable, Tuple -from lnbits.app import settings -from lnbits.helpers import encrypt_internal_message -from urllib.parse import quote from Cryptodome.Util.Padding import pad, unpad -from typing import List, Dict, Optional +from lnbits.helpers import encrypt_internal_message +from lnbits.settings import settings +from loguru import logger + + class MainSubscription: def __init__(self): - self.requests_sub_id = None - self.responses_sub_id = None + self.requests_sub_id: Optional[str] = None + self.responses_sub_id: Optional[str] = None self.requests_eose = False self.responses_eose = False - self.events:Dict[str, Dict] = {} - self.responses:List[str] = [] - - def getStale(self) -> List[Dict]: + self.events: Dict[str, Dict] = {} + self.responses: List[str] = [] + + def get_stale(self) -> List[Dict]: """ Get all the pending events that do not have a response yet. """ pending_events = [] - for [id, event] in self.events.items(): - if not id in self.responses: + for [event_id, event] in self.events.items(): + if event_id not in self.responses: pending_events.append(event) return pending_events - - def registerResponse(self, event_id:str): + + def register_response(self, event_id: str): """ Register a response for a request event (not stale anymore) """ - if not event_id in self.responses: + if event_id not in self.responses: self.responses.append(event_id) class NWCServiceProvider: - def __init__(self, private_key:str=None, relay:str=None): - if not relay: # Connect to nostrclient + def __init__(self, private_key: Optional[str] = None, relay: Optional[str] = None): + if not relay: # Connect to nostrclient relay = "nostrclient" if relay == "nostrclient": - relay=f"ws://localhost:{settings.port}/nostrclient/api/v1/relay" + relay = f"ws://localhost:{settings.port}/nostrclient/api/v1/relay" elif relay == "nostrclient:private": relay_endpoint = encrypt_internal_message("relay") - relay=f"ws://localhost:{settings.port}/nostrclient/api/v1/{relay_endpoint}" + relay = ( + f"ws://localhost:{settings.port}/nostrclient/api/v1/{relay_endpoint}" + ) self.relay = relay - if not private_key: # Create random key + if not private_key: # Create random key private_key = bytes.hex(secp256k1._gen_private_key()) - + self.private_key = secp256k1.PrivateKey(bytes.fromhex(private_key)) self.private_key_hex = private_key self.public_key = self.private_key.pubkey + if not self.public_key: + raise Exception("Invalid public key") self.public_key_hex = self.public_key.serialize().hex()[2:] # List of supported methods - self.supported_methods = [] - + self.supported_methods: List[str] = [] + # Keep track of the number of subscriptions (used for unique subid) - self.subscriptions_count = 0 + self.subscriptions_count: int = 0 # Request listeners, listen to specific methods - self.request_listeners = {} + self.request_listeners: Dict[ + str, + Callable[ + ["NWCServiceProvider", str, Dict], + Awaitable[List[Tuple[Optional[Dict], Optional[Dict], List]]], + ], + ] = {} # Reconnect task (if the connection is lost) self.reconnect_task = None - # Subscription + # Subscription self.sub = None # websocket connection @@ -83,30 +92,43 @@ class NWCServiceProvider: # if True the websocket is connected self.connected = False - + # if True the instance is shutting down self.shutdown = False - logger.info("NWC Service is ready. relay: "+str(self.relay)+" pubkey: " + - self.public_key_hex) - - def getSupportedMethods(self): + logger.info( + "NWC Service is ready. relay: " + + str(self.relay) + + " pubkey: " + + self.public_key_hex + ) + + def get_supported_methods(self): """ Returns the list of supported methods by this service provider. """ return self.supported_methods - def addRequestListener(self, method: str, l: Callable[["NWCServiceProvider", str, Dict], List[Tuple[Dict, Dict, List]]]): + def add_request_listener( + self, + method: str, + listener: Callable[ + ["NWCServiceProvider", str, Dict], + Awaitable[List[Tuple[Optional[Dict], Optional[Dict], List]]], + ], + ): """ Adds a request listener for a specific method. Args: method (str): The method name. - l (Callable[["NWCServiceProvider", str, Dict], List[Tuple[Dict, Dict]]]): The listener function + listener (Callable[ + ["NWCServiceProvider", str, Dict], List[Tuple[Dict, Dict]] + ]): The listener function """ - if not method in self.supported_methods: + if method not in self.supported_methods: self.supported_methods.append(method) - self.request_listeners[method] = l + self.request_listeners[method] = listener async def start(self): """ @@ -114,21 +136,19 @@ class NWCServiceProvider: """ self.reconnect_task = asyncio.create_task(self._connect_to_relay()) - def _json_dumps(self, data: Union[Dict, list]) -> str: """ Converts a Python dictionary to a JSON string with compact encoding. Args: data (Dict): The dictionary to be converted. - + Returns: str: The compact JSON string. """ if isinstance(data, Dict): data = {k: v for k, v in data.items() if v is not None} - return json.dumps(data, separators=(',', ':'), ensure_ascii=False) - + return json.dumps(data, separators=(",", ":"), ensure_ascii=False) def _is_shutting_down(self) -> bool: """ @@ -136,14 +156,15 @@ class NWCServiceProvider: """ return self.shutdown or not settings.lnbits_running - - async def _send(self, data: Dict): + async def _send(self, data: List[Any]): """ Sends data to the relay. Args: data (Dict): The data to be sent. """ + if not self.ws: + raise Exception("Websocket connection is not established") if self._is_shutting_down(): logger.warning("Trying to send data while shutting down") return @@ -151,7 +172,6 @@ class NWCServiceProvider: tx = self._json_dumps(data) await self.ws.send(tx) - def _get_new_subid(self) -> str: """ Generates a unique subscription id. @@ -159,17 +179,16 @@ class NWCServiceProvider: Returns: str: The generated 64 characters long subscription id (eg. lnbits0abc...) """ - subid = "lnbitsnwcs"+str(self.subscriptions_count) + subid = "lnbitsnwcs" + str(self.subscriptions_count) self.subscriptions_count += 1 - maxLength = 64 + max_length = 64 chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - n = maxLength - len(subid) + n = max_length - len(subid) if n > 0: - for i in range(n): + for _ in range(n): subid += chars[random.randint(0, len(chars) - 1)] return subid - async def _wait_for_connection(self): """ Waits until the connection is established. @@ -180,56 +199,52 @@ class NWCServiceProvider: logger.debug("Waiting for connection...") await asyncio.sleep(1) - async def _subscribe(self): """ [Re]Subscribe to receive nip 47 requests and responses from the relay """ - self.sub = MainSubscription() + self.sub = MainSubscription() # Create requests subscription req_filter = { "kinds": [23194], "#p": [self.public_key_hex], # Since the last 3 hours (handles reboots) - "since": int(time.time()) - 3*60*60 + "since": int(time.time()) - 3 * 60 * 60, } self.sub.requests_sub_id = self._get_new_subid() # Create responses subscription (needed to track previosly responded requests) res_filter = { "kinds": [23195], "authors": [self.public_key_hex], - "since": int(time.time()) - 3*60*60 + "since": int(time.time()) - 3 * 60 * 60, } self.sub.responses_sub_id = self._get_new_subid() # Subscribe await self._send(["REQ", self.sub.requests_sub_id, req_filter]) await self._send(["REQ", self.sub.responses_sub_id, res_filter]) - - async def _on_connection(self,ws): + async def _on_connection(self, ws): """ - On connection callback, announce the service provider methods and subscribe to nip67 events. + On connection callback, announce the service provider + methods and subscribe to nip67 events. """ # Send info event event = { "kind": 13194, "content": " ".join(self.supported_methods), "created_at": int(time.time()), - "tags": [ - ["p", self.public_key_hex] - ] + "tags": [["p", self.public_key_hex]], } self._sign_event(event) await self._send(["EVENT", event]) # Resubscribe to nwc events await self._subscribe() - async def _handle_request(self, event: Dict) -> List[Dict]: """ Handle a nwc request """ - nwc_pubkey = event["pubkey"] + nwc_pubkey = event["pubkey"] content = event["content"] # Decrypt the content content = self._decrypt_content(content, nwc_pubkey) @@ -237,34 +252,30 @@ class NWCServiceProvider: content = json.loads(content) # Handle request method = content["method"] - l = self.request_listeners.get(method, None) - outs = [] - if not l: - outs.append({ - "error": { - "code": "NOT_IMPLEMENTED", - "message": "Method "+method+" is not implemented by this service provider" + listener = self.request_listeners.get(method, None) + outs: List[Dict[str, Any]] = [] + if not listener: + outs.append( + { + "error": { + "code": "NOT_IMPLEMENTED", + "message": "Method " + + method + + " is not implemented by this service provider", + } } - }) + ) else: try: - results = await l(self, nwc_pubkey, content) + results = await listener(self, nwc_pubkey, content) for result in results: r = result[0] e = result[1] t = result[2] if len(result) > 2 else None - out = {} - if r: out["result"] = r - if e: out["error"] = e - if t: out["tags"] = t + out = {"result": r, "error": e, "tags": t} outs.append(out) except Exception as e: - outs.append({ - "error":{ - "code": "INTERNAL", - "message": str(e) - } - }) + outs.append({"error": {"code": "INTERNAL", "message": str(e)}}) sent_events = [] for out in outs: # Finalize output @@ -275,7 +286,7 @@ class NWCServiceProvider: if "error" in out: content["error"] = out["error"] # Prepare response event - res = { + res: Dict = { "kind": 23195, "created_at": int(time.time()), "tags": out.get("tags", []), @@ -286,16 +297,94 @@ class NWCServiceProvider: # Reference user res["tags"].append(["p", nwc_pubkey]) # Finalize response event + print(res) res["content"] = self._encrypt_content(res["content"], nwc_pubkey) self._sign_event(res) + # Register response for this request, so we knows it is not stale - if self.sub: self.sub.registerResponse(event["id"]) + if self.sub: + self.sub.register_response(event["id"]) # Send response event await self._send(["EVENT", res]) # Track sent events sent_events.append(res) return sent_events - + + async def _on_event_message(self, msg): + if not self.sub: + return + sub_id = msg[1] + event = msg[2] + # Ensure the event is valid (do not trust relays) + if not self._verify_event(event): + raise Exception("Invalid event signature") + tags = event["tags"] + expiration = int(next((tag for tag in tags if tag[0] == "expiration"), -1)) + # Handle event expiration if the relay doesn't support nip 40 + if expiration > 0 and expiration < int(time.time()): + logger.debug("Event expired") + return + if event["kind"] == 23194 and sub_id == self.sub.requests_sub_id: + # Ensure the request is for this service provider + valid_p = any( + tag[0] == "p" and tag[1] == self.public_key_hex for tag in tags + ) + if not valid_p: + raise Exception("Unexpected request from another service") + # Track request + self.sub.events[event["id"]] = event + # if eose was received for both subscriptions, we handle the request + # in realtime if not, we do nothing since the request may be + # already handled or stale, all stale requests will be handled + # later when eose is received + if self.sub.requests_eose and self.sub.responses_eose: + await self._handle_request(event) + elif event["kind"] == 23195 and sub_id == self.sub.responses_sub_id: + # Ensure the response is from this service provider + if event["pubkey"] != self.public_key_hex: + raise Exception("Unexpected response from another service") + # Register as response for each e tag (request event id) + # Note: usually we expect only one "e" tag, but we are handling + # multiple "e" tags just in case + etag = next((tag[1] for tag in tags if tag[0] == "e"), None) + if etag: + self.sub.register_response(etag) + + async def _on_eose_message(self, msg): + if not self.sub: + return + sub_id = msg[1] + # Track EOSE + if sub_id == self.sub.requests_sub_id: + self.sub.requests_eose = True + elif sub_id == self.sub.responses_sub_id: + self.sub.responses_eose = True + # When both EOSE are receives, handle all the stale requests + # Note: All the requests that were received prior to the + # service connection and do not have a response yet, + # are considered stale, we will process them now + if self.sub.requests_eose and self.sub.responses_eose: + stales = self.sub.get_stale() + for stale in stales: + await self._handle_request(stale) + + async def _on_closed_message(self, msg): + if not self.sub: + return + # Subscription was closed remotely. + sub_id = msg[1] + info = msg[2] or "" if len(msg) > 2 else "" + # Resubscribe if one of the main subscriptions was closed + if sub_id == self.sub.requests_sub_id or sub_id == self.sub.responses_sub_id: + logger.warning( + "Subscription " + + sub_id + + " was closed remotely: " + + info + + " ... resubscribing..." + ) + await self._subscribe() + async def _on_message(self, ws, message: str): """ Handle incoming messages from the relay. @@ -303,113 +392,63 @@ class NWCServiceProvider: try: msg = json.loads(message) if msg[0] == "EVENT": # Event message - sub_id = msg[1] - event = msg[2] - # Ensure the event is valid (do not trust relays) - if not self._verify_event(event): - raise Exception("Invalid event signature") - tags = event["tags"] - expiration = -1 - for tag in tags: - if tag[0] == "expiration": - expiration = int(tag[1]) - break - # Handle event expiration if the relay doesn't support nip 40 - if expiration > 0 and expiration < int(time.time()): - logger.debug("Event expired") - return - if event["kind"] == 23194 and sub_id == self.sub.requests_sub_id: - # Ensure the request is for this service provider - valid_p = False - for tag in tags: - if tag[0] == "p" and tag[1] == self.public_key_hex: - valid_p = True - break - if not valid_p: - raise Exception("Unexpected request from another service") - # Track request - self.sub.events[event["id"]] = event - # if eose was received for both subscriptions, we handle the request in realtime - # if not, we do nothing since the request may be already handled or stale, - # all stale requests will be handled later when eose is received - if self.sub.requests_eose and self.sub.responses_eose: - await self._handle_request(event) - elif event["kind"] == 23195 and sub_id == self.sub.responses_sub_id: - # Ensure the response is from this service provider - if event["pubkey"] != self.public_key_hex: - raise Exception("Unexpected response from another service") - # Register as response for each e tag (request event id) - # Note: usually we expect only one "e" tag, but we are handling multiple "e" tags just in case - for tag in tags: - if tag[0] == "e": - self.sub.registerResponse(tag[1]) - elif msg[0] == "EOSE": - sub_id = msg[1] - # Track EOSE - if sub_id == self.sub.requests_sub_id: - self.sub.requests_eose = True - elif sub_id == self.sub.responses_sub_id: - self.sub.responses_eose = True - # When both EOSE are receives, handle all the stale requests - # Note: All the requests that were received prior to the service connection - # and do not have a response yet, are considered stale, we will process them now - if self.sub.requests_eose and self.sub.responses_eose: - stales = self.sub.getStale() - for stale in stales: - await self._handle_request(stale) + await self._on_event_message(msg) + elif msg[0] == "EOSE": + await self._on_eose_message(msg) elif msg[0] == "CLOSED": - # Subscription was closed remotely. - sub_id = msg[1] - info = msg[2] or "" if len(msg) > 2 else "" - # Resubscribe if one of the main subscriptions was closed - if sub_id == self.sub.requests_sub_id or sub_id == self.sub.responses_sub_id: - logger.warning("Subscription "+sub_id+" was closed remotely: "+info+" ... resubscribing...") - self._subscribe() + await self._on_closed_message(msg) elif msg[0] == "NOTICE": # A message from the relay, mostly useless, but we log it anyway - logger.info("Notice from relay "+self.relay+": "+str(msg[1])) + logger.info("Notice from relay " + self.relay + ": " + str(msg[1])) elif msg[0] == "OK": pass else: raise Exception("Unknown message type") except Exception as e: - logger.error("Error parsing event: "+str(e)) - + logger.error("Error parsing event: " + str(e)) async def _connect_to_relay(self): """ Initiate websocket connection to the relay. """ - await asyncio.sleep(1) - logger.debug("Connecting to NWC relay "+self.relay) - while not self._is_shutting_down(): # Reconnect until the instance is shutting down - logger.debug('Creating new connection...') + await asyncio.sleep(1) + logger.debug("Connecting to NWC relay " + self.relay) + while ( + not self._is_shutting_down() + ): # Reconnect until the instance is shutting down + logger.debug("Creating new connection...") try: async with websockets.connect(self.relay) as ws: self.ws = ws self.connected = True await self._on_connection(ws) - while not self._is_shutting_down(): # receive messages until the instance is shutting down + while ( + not self._is_shutting_down() + ): # receive messages until the instance is shutting down try: reply = await ws.recv() + if isinstance(reply, bytes): + reply = reply.decode("utf-8") await self._on_message(ws, reply) except Exception as e: logger.debug("Error receiving message: " + str(e)) break logger.debug("Connection to NWC relay closed") except Exception as e: - logger.error("Error connecting to NWC relay: "+str(e)) + logger.error("Error connecting to NWC relay: " + str(e)) await asyncio.sleep(5) # the connection was closed, so we set the connected flag to False - # this will make the methods calling _wait_for_connection() to wait until the connection is re-established + # this will make the methods calling _wait_for_connection() to wait + # until the connection is re-established self.connected = False if not self._is_shutting_down(): # Wait some time before reconnecting logger.debug("Reconnecting to NWC relay in 5 seconds...") await asyncio.sleep(5) - - def _encrypt_content(self, content: str, pubkey_hex:str, iv_seed: Optional[int]=None) -> str: + def _encrypt_content( + self, content: str, pubkey_hex: str, iv_seed: Optional[int] = None + ) -> str: """ Encrypts the content for the given public key @@ -420,16 +459,14 @@ class NWCServiceProvider: Returns: str: The encrypted content. """ - pubkey = secp256k1.PublicKey( - bytes.fromhex("02" + pubkey_hex), True) - shared = pubkey.tweak_mul(bytes.fromhex( - self.private_key_hex)).serialize()[1:] + pubkey = secp256k1.PublicKey(bytes.fromhex("02" + pubkey_hex), True) + shared = pubkey.tweak_mul(bytes.fromhex(self.private_key_hex)).serialize()[1:] # random iv (16B) if not iv_seed: iv = Random.new().read(AES.block_size) else: - iv = hashlib.sha256(iv_seed.to_bytes(32, byteorder='big')).digest() - iv = iv[:AES.block_size] + iv = hashlib.sha256(iv_seed.to_bytes(32, byteorder="big")).digest() + iv = iv[: AES.block_size] aes = AES.new(shared, AES.MODE_CBC, iv) @@ -438,14 +475,12 @@ class NWCServiceProvider: # padding content_bytes = pad(content_bytes, AES.block_size) - encrypted_b64 = base64.b64encode( - aes.encrypt(content_bytes)).decode("ascii") - ivB64 = base64.b64encode(iv).decode("ascii") - encrypted_content = encrypted_b64 + "?iv=" + ivB64 + encrypted_b64 = base64.b64encode(aes.encrypt(content_bytes)).decode("ascii") + iv_b64 = base64.b64encode(iv).decode("ascii") + encrypted_content = encrypted_b64 + "?iv=" + iv_b64 return encrypted_content - - def _decrypt_content(self, content: str , pubkey_hex:str) -> str: + def _decrypt_content(self, content: str, pubkey_hex: str) -> str: """ Decrypts the content for the given public key @@ -456,15 +491,12 @@ class NWCServiceProvider: Returns: str: The decrypted content. """ - pubkey = secp256k1.PublicKey( - bytes.fromhex("02" + pubkey_hex), True) + pubkey = secp256k1.PublicKey(bytes.fromhex("02" + pubkey_hex), True) - shared = pubkey.tweak_mul(bytes.fromhex( - self.private_key_hex)).serialize()[1:] + shared = pubkey.tweak_mul(bytes.fromhex(self.private_key_hex)).serialize()[1:] # extract iv and content (encrypted_content_b64, iv_b64) = content.split("?iv=") - encrypted_content = base64.b64decode( - encrypted_content_b64.encode("ascii")) + encrypted_content = base64.b64decode(encrypted_content_b64.encode("ascii")) iv = base64.b64decode(iv_b64.encode("ascii")) # Decrypt aes = AES.new(shared, AES.MODE_CBC, iv) @@ -473,7 +505,6 @@ class NWCServiceProvider: decrypted = decrypted_bytes.decode("utf-8") return decrypted - def _verify_event(self, event: Dict) -> bool: """ Verify the event signature @@ -484,27 +515,30 @@ class NWCServiceProvider: Returns: bool: True if the event signature is valid, False otherwise. """ - signature_data = self._json_dumps([ - 0, - event["pubkey"], - event["created_at"], - event["kind"], - event["tags"], - event["content"] - ]) + signature_data = self._json_dumps( + [ + 0, + event["pubkey"], + event["created_at"], + event["kind"], + event["tags"], + event["content"], + ] + ) event_id = hashlib.sha256(signature_data.encode()).hexdigest() if event_id != event["id"]: # Invalid event id return False - pubkeyHex = event["pubkey"] - pubkey = secp256k1.PublicKey(bytes.fromhex("02" + pubkeyHex), True) - if not pubkey.schnorr_verify(bytes.fromhex(event_id), bytes.fromhex(event["sig"]), None, raw=True): + pubkey_hex = event["pubkey"] + pubkey = secp256k1.PublicKey(bytes.fromhex("02" + pubkey_hex), True) + if not pubkey.schnorr_verify( + bytes.fromhex(event_id), bytes.fromhex(event["sig"]), None, raw=True + ): return False return True - def _sign_event(self, event: Dict) -> Dict: """ - Signs the event (in place) + Signs the event (in place) Args: event (Dict): The event to be signed. @@ -512,24 +546,26 @@ class NWCServiceProvider: Returns: Dict: The input event with the signature added. """ - signature_data = self._json_dumps([ - 0, - self.public_key_hex, - event["created_at"], - event["kind"], - event["tags"], - event["content"] - ]) + signature_data = self._json_dumps( + [ + 0, + self.public_key_hex, + event["created_at"], + event["kind"], + event["tags"], + event["content"], + ] + ) event_id = hashlib.sha256(signature_data.encode()).hexdigest() event["id"] = event_id event["pubkey"] = self.public_key_hex - signature = (self.private_key.schnorr_sign( - bytes.fromhex(event_id), None, raw=True)).hex() + signature = ( + self.private_key.schnorr_sign(bytes.fromhex(event_id), None, raw=True) + ).hex() event["sig"] = signature return event - async def cleanup(self): logger.debug("Closing NWC Service Provider connection") @@ -539,10 +575,10 @@ class NWCServiceProvider: if self.reconnect_task: self.reconnect_task.cancel() except Exception as e: - logger.warning("Error closing reconnection task: "+str(e)) + logger.warning("Error closing reconnection task: " + str(e)) # close the websocket try: if self.ws: await self.ws.close() except Exception as e: - logger.warning("Error closing websocket connection: "+str(e)) + logger.warning("Error closing websocket connection: " + str(e)) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..067e381 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,61 @@ +{ + "name": "nwcprovider", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nwcprovider", + "version": "1.0.0", + "dependencies": { + "prettier": "^3.3.3", + "pyright": "^1.1.379" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pyright": { + "version": "1.1.379", + "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.379.tgz", + "integrity": "sha512-n0X+IMqot6zL5b54vfU9GattS8jM9IOh8TRFho1k/6VoyjrpzQ7TnU6PtZzwEZNJaZi5izoLIDeMnGmbin8n8Q==", + "license": "MIT", + "bin": { + "pyright": "index.js", + "pyright-langserver": "langserver.index.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9cdf2ce --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "nwcprovider", + "version": "1.0.0", + "dependencies": { + "prettier": "^3.3.3", + "pyright": "^1.1.379" + } +} diff --git a/permission.py b/permission.py index 4110bcb..9caf17b 100644 --- a/permission.py +++ b/permission.py @@ -1,49 +1,33 @@ - - nwc_permissions = { - "pay":{ - "name":"Send payments", - "methods":[ + "pay": { + "name": "Send payments", + "methods": [ "multi_pay_invoice", "pay_invoice", "pay_keysend", "multi_pay_keysend", ], - "default": True + "default": True, }, - "invoice":{ - "name":"Create invoices", - "methods":[ - "make_invoice" - ], - "default": True + "invoice": { + "name": "Create invoices", + "methods": ["make_invoice"], + "default": True, }, - "lookup":{ - "name":"Lookup status of invoice", - "methods":[ - "lookup_invoice" - ], - "default": True + "lookup": { + "name": "Lookup status of invoice", + "methods": ["lookup_invoice"], + "default": True, }, "history": { "name": "Read transaction history", - "methods": [ - "list_transactions" - ], - "default": True + "methods": ["list_transactions"], + "default": True, }, "balance": { "name": "Read wallet balance", - "methods": [ - "get_balance" - ], - "default": True + "methods": ["get_balance"], + "default": True, }, - "info": { - "name": "Read account info", - "methods": [ - "get_info" - ], - "default": True - } -} \ No newline at end of file + "info": {"name": "Read account info", "methods": ["get_info"], "default": True}, +} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..46b723f --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2512 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "asn1crypto" +version = "1.5.1" +description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" +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 = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "base58" +version = "2.1.1" +description = "Base58 and Base58Check implementation." +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"] + +[[package]] +name = "bcrypt" +version = "4.2.0" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"}, + {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"}, + {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"}, + {file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"}, + {file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"}, + {file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"}, + {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"}, + {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"}, + {file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"}, + {file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"}, + {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"}, + {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"}, + {file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "bech32" +version = "1.2.0" +description = "Reference implementation for Bech32 and segwit addresses." +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 = "bitarray" +version = "2.9.2" +description = "efficient arrays of booleans -- C extension" +optional = false +python-versions = "*" +files = [ + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:917905de565d9576eb20f53c797c15ba88b9f4f19728acabec8d01eee1d3756a"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b35bfcb08b7693ab4bf9059111a6e9f14e07d57ac93cd967c420db58ab9b71e1"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea1923d2e7880f9e1959e035da661767b5a2e16a45dfd57d6aa831e8b65ee1bf"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0b63a565e8a311cc8348ff1262d5784df0f79d64031d546411afd5dd7ef67d"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf0620da2b81946d28c0b16f3e3704d38e9837d85ee4f0652816e2609aaa4fed"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79a9b8b05f2876c7195a2b698c47528e86a73c61ea203394ff8e7a4434bda5c8"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:345c76b349ff145549652436235c5532e5bfe9db690db6f0a6ad301c62b9ef21"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e2936f090bf3f4d1771f44f9077ebccdbc0415d2b598d51a969afcb519df505"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f9346e98fc2abcef90b942973087e2462af6d3e3710e82938078d3493f7fef52"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6ec283d4741befb86e8c3ea2e9ac1d17416c956d392107e45263e736954b1f7"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:962892646599529917ef26266091e4cb3077c88b93c3833a909d68dcc971c4e3"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e8da5355d7d75a52df5b84750989e34e39919ec7e59fafc4c104cc1607ab2d31"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:603e7d640e54ad764d2b4da6b61e126259af84f253a20f512dd10689566e5478"}, + {file = "bitarray-2.9.2-cp310-cp310-win32.whl", hash = "sha256:f00079f8e69d75c2a417de7961a77612bb77ef46c09bc74607d86de4740771ef"}, + {file = "bitarray-2.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:1bb33673e7f7190a65f0a940c1ef63266abdb391f4a3e544a47542d40a81f536"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99"}, + {file = "bitarray-2.9.2-cp311-cp311-win32.whl", hash = "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095"}, + {file = "bitarray-2.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d"}, + {file = "bitarray-2.9.2-cp312-cp312-win32.whl", hash = "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188"}, + {file = "bitarray-2.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498"}, + {file = "bitarray-2.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2c6be1b651fad8f3adb7a5aa12c65b612cd9b89530969af941844ae680f7d981"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b399ae6ab975257ec359f03b48fc00b1c1cd109471e41903548469b8feae5c"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b3543c8a1cb286ad105f11c25d8d0f712f41c5c55f90be39f0e5a1376c7d0b0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03adaacb79e2fb8f483ab3a67665eec53bb3fd0cd5dbd7358741aef124688db3"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae5b0657380d2581e13e46864d147a52c1e2bbac9f59b59c576e42fa7d10cf0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1f4bf6ea8eb9d7f30808c2e9894237a96650adfecbf5f3643862dc5982f89e"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a8873089be2aa15494c0f81af1209f6e1237d762c5065bc4766c1b84321e1b50"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:677e67f50e2559efc677a4366707070933ad5418b8347a603a49a070890b19bc"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a620d8ce4ea2f1c73c6b6b1399e14cb68c6915e2be3fad5808c2998ed55b4acf"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:64115ccabbdbe279c24c367b629c6b1d3da9ed36c7420129e27c338a3971bfee"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d6fb422772e75385b76ad1c52f45a68bd4efafd8be8d0061c11877be74c4d43"}, + {file = "bitarray-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:852e202875dd6dfd6139ce7ec4e98dac2b17d8d25934dc99900831e81c3adaef"}, + {file = "bitarray-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7dfefdcb0dc6a3ba9936063cec65a74595571b375beabe18742b3d91d087eefd"}, + {file = "bitarray-2.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b306c4cf66912511422060f7f5e1149c8bdb404f8e00e600561b0749fdd45659"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a09c4f81635408e3387348f415521d4b94198c562c23330f560596a6aaa26eaf"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5361413fd2ecfdf44dc8f065177dc6aba97fa80a91b815586cb388763acf7f8d"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8a9475d415ef1eaae7942df6f780fa4dcd48fce32825eda591a17abba869299"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b87baa7bfff9a5878fcc1bffe49ecde6e647a72a64b39a69cd8a2992a43a34"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb6b86cfdfc503e92cb71c68766a24565359136961642504a7cc9faf936d9c88"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd56b8ae87ebc71bcacbd73615098e8a8de952ecbb5785b6b4e2b07da8a06e1f"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3fa909cfd675004aed8b4cc9df352415933656e0155a6209d878b7cb615c787e"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b069ca9bf728e0c5c5b60e00a89df9af34cc170c695c3bfa3b372d8f40288efb"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6067f2f07a7121749858c7daa93c8774325c91590b3e81a299621e347740c2ae"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:321841cdad1dd0f58fe62e80e9c9c7531f8ebf8be93f047401e930dc47425b1e"}, + {file = "bitarray-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:54e16e32e60973bb83c315de9975bc1bcfc9bd50bb13001c31da159bc49b0ca1"}, + {file = "bitarray-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4dcadb7b8034aa3491ee8f5a69b3d9ba9d7d1e55c3cc1fc45be313e708277f8"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c8919fdbd3bb596b104388b56ae4b266eb28da1f2f7dff2e1f9334a21840fe96"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eb7a9d8a2e400a1026de341ad48e21670a6261a75b06df162c5c39b0d0e7c8f4"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ec84668dd7b937874a2b2c293cd14ba84f37be0d196dead852e0ada9815d807"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2de9a31c34e543ae089fd2a5ced01292f725190e379921384f695e2d7184bd3"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9521f49ae121a17c0a41e5112249e6fa7f6a571245b1118de81fb86e7c1bc1ce"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6cc6545d6d76542aee3d18c1c9485fb7b9812b8df4ebe52c4535ec42081b48f"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:856bbe1616425f71c0df5ef2e8755e878d9504d5a531acba58ab4273c52c117a"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4bba8042ea6ab331ade91bc435d81ad72fddb098e49108610b0ce7780c14e68"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a035da89c959d98afc813e3c62f052690d67cfd55a36592f25d734b70de7d4b0"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d70b1579da7fb71be5a841a1f965d19aca0ef27f629cfc07d06b09aafd0a333"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:405b83bed28efaae6d86b6ab287c75712ead0adbfab2a1075a1b7ab47dad4d62"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7eb8be687c50da0b397d5e0ab7ca200b5ebb639e79a9f5e285851d1944c94be9"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eceb551dfeaf19c609003a69a0cf8264b0efd7abc3791a11dfabf4788daf0d19"}, + {file = "bitarray-2.9.2-cp38-cp38-win32.whl", hash = "sha256:bb198c6ed1edbcdaf3d1fa3c9c9d1cdb7e179a5134ef5ee660b53cdec43b34e7"}, + {file = "bitarray-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:648d2f2685590b0103c67a937c2fb9e09bcc8dfb166f0c7c77bd341902a6f5b3"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ea816dc8f8e65841a8bbdd30e921edffeeb6f76efe6a1eb0da147b60d539d1cf"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d0e32530f941c41eddfc77600ec89b65184cb909c549336463a738fab3ed285"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a22266fb416a3b6c258bf7f83c9fe531ba0b755a56986a81ad69dc0f3bcc070"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc6d3e80dd8239850f2604833ff3168b28909c8a9357abfed95632cccd17e3e7"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f135e804986b12bf14f2cd1eb86674c47dea86c4c5f0fa13c88978876b97ebe6"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87580c7f7d14f7ec401eda7adac1e2a25e95153e9c339872c8ae61b3208819a1"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b433e26993127732ac7b66a7821b2537c3044355798de7c5fcb0af34b8296f"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e497c535f2a9b68c69d36631bf2dba243e05eb343b00b9c7bbdc8c601c6802d"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e40b3cb9fa1edb4e0175d7c06345c49c7925fe93e39ef55ecb0bc40c906b0c09"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f2f8692f95c9e377eb19ca519d30d1f884b02feb7e115f798de47570a359e43f"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f0b84fc50b6dbeced4fa390688c07c10a73222810fb0e08392bd1a1b8259de36"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d656ad38c942e38a470ddbce26b5020e08e1a7ea86b8fd413bb9024b5189993a"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ab0f1dbfe5070db98771a56aa14797595acd45a1af9eadfb193851a270e7996"}, + {file = "bitarray-2.9.2-cp39-cp39-win32.whl", hash = "sha256:0a99b23ac845a9ea3157782c97465e6ae026fe0c7c4c1ed1d88f759fd6ea52d9"}, + {file = "bitarray-2.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:9bbcfc7c279e8d74b076e514e669b683f77b4a2a328585b3f16d4c5259c91222"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:43847799461d8ba71deb4d97b47250c2c2fb66d82cd3cb8b4caf52bb97c03034"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f44381b0a4bdf64416082f4f0e7140377ae962c0ced6f983c6d7bbfc034040"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a484061616fb4b158b80789bd3cb511f399d2116525a8b29b6334c68abc2310f"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ff9e38356cc803e06134cf8ae9758e836ccd1b793135ef3db53c7c5d71e93bc"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b44105792fbdcfbda3e26ee88786790fda409da4c71f6c2b73888108cf8f062f"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e913098de169c7fc890638ce5e171387363eb812579e637c44261460ac00aa2"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6fe315355cdfe3ed22ef355b8bdc81a805ca4d0949d921576560e5b227a1112"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f708e91fdbe443f3bec2df394ed42328fb9b0446dff5cb4199023ac6499e09fd"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7b09489b71f9f1f64c0fa0977e250ec24500767dab7383ba9912495849cadf"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:128cc3488176145b9b137fdcf54c1c201809bbb8dd30b260ee40afe915843b43"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21f21e7f56206be346bdbda2a6bdb2165a5e6a11821f88fd4911c5a6bbbdc7e2"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f4dd3af86dd8a617eb6464622fb64ca86e61ce99b59b5c35d8cd33f9c30603d"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6465de861aff7a2559f226b37982007417eab8c3557543879987f58b453519bd"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbaf2bb71d6027152d603f1d5f31e0dfd5e50173d06f877bec484e5396d4594b"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f32948c86e0d230a296686db28191b67ed229756f84728847daa0c7ab7406e3"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be94e5a685e60f9d24532af8fe5c268002e9016fa80272a94727f435de3d1003"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5cc9381fd54f3c23ae1039f977bfd6d041a5c3c1518104f616643c3a5a73b15"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd926e8ae4d1ed1ac4a8f37212a62886292f692bc1739fde98013bf210c2d175"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:461a3dafb9d5fda0bb3385dc507d78b1984b49da3fe4c6d56c869a54373b7008"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:393cb27fd859af5fd9c16eb26b1c59b17b390ff66b3ae5d0dd258270191baf13"}, + {file = "bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e"}, +] + +[[package]] +name = "bitstring" +version = "4.2.3" +description = "Simple construction, analysis and modification of binary data." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bitstring-4.2.3-py3-none-any.whl", hash = "sha256:20ed0036e2fcf0323acb0f92f0b7b178516a080f3e91061470aa019ac4ede404"}, + {file = "bitstring-4.2.3.tar.gz", hash = "sha256:e0c447af3fda0d114f77b88c2d199f02f97ee7e957e6d719f40f41cf15fbb897"}, +] + +[package.dependencies] +bitarray = ">=2.9.0,<3.0.0" + +[[package]] +name = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "bolt11" +version = "2.1.0" +description = "A library for encoding and decoding BOLT11 payment requests." +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "bolt11-2.1.0-py3-none-any.whl", hash = "sha256:1535e441233414a9d8031a99fd3be07de4674bffda948033579404d44a42f614"}, + {file = "bolt11-2.1.0.tar.gz", hash = "sha256:177c63cd88d1eaba669eddb5937364676226253f2e9e5b77e8fe317ef32e62dd"}, +] + +[package.dependencies] +base58 = "*" +bech32 = "*" +bitstring = "*" +click = "*" +coincurve = "*" + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "coincurve" +version = "18.0.0" +description = "Cross-platform Python CFFI bindings for libsecp256k1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coincurve-18.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b1a42eba91b9e4f833309e94bc6a270b1700cb4567d4809ef91f00968b57925"}, + {file = "coincurve-18.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:116bf1b60a6e72e23c6b153d7c79f0e565d82973d917a3cecf655ffb29263163"}, + {file = "coincurve-18.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d53e2a268142924c24e9b786b3e6c3603fae54bb8211560036b0e9ce6a9f2dbc"}, + {file = "coincurve-18.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b31ab366fadff16ecfdde96ffc07e70fee83850f88bd1f985a8b4977a68bbfb"}, + {file = "coincurve-18.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e3c37cfadac6896668a130ea46296a3dfdeea0160fd66a51e377ad00795269"}, + {file = "coincurve-18.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f3e5f2a2d774050b3ea8bf2167f2d598fde58d7690779931516714d98b65d884"}, + {file = "coincurve-18.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83379dd70291480df2052554851bfd17444c003aef7c4bb02d96d73eec69fe28"}, + {file = "coincurve-18.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:33678f6b43edbeab6605584c725305f4f814239780c53eba0f8e4bc4a52b1d1a"}, + {file = "coincurve-18.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f40646d5f29ac9026f8cc1b368bc9ab68710fad055b64fbec020f9bbfc99b242"}, + {file = "coincurve-18.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:779da694dea1b1d09e16b00e079f6a1195290ce9568f39c95cddf35f1f49ec49"}, + {file = "coincurve-18.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7844f01904e32317a00696a27fd771860e53a2fa62e5c66eace9337d2742c9e6"}, + {file = "coincurve-18.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257c6171cd0301c119ef41360f0d0c2fb5cc288717b33d3bd5482a4c9ae04551"}, + {file = "coincurve-18.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8bcb9c40fd730cf377fa448f1304355d6497fb3d00b7b0a69a10dfcc14a6d28"}, + {file = "coincurve-18.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3abb7f65e2b5fb66a15e374faeaafe6700fdb83fb66d1873ddff91c395a3b74"}, + {file = "coincurve-18.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f44b9ba588b34795d1b4074f9a9fa372adef3fde58300bf32f40a69e8cd72a23"}, + {file = "coincurve-18.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:908467330cd3047c71105a08394c4f3e7dce76e4371b030ba8b0ef863013e3ca"}, + {file = "coincurve-18.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:599b1b3cf097cae920d97f31a5b8e8aff185ca8fa5d8a785b2edf7b199fb9731"}, + {file = "coincurve-18.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d2c20d108580bce5efedb980688031462168f4de2446de95898b48a249127a2"}, + {file = "coincurve-18.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eba563f7f70c10323227d1890072172bd84df6f814c9a6b012033b214426b6cf"}, + {file = "coincurve-18.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:412a06b7d1b8229f25318f05e76310298da5ad55d73851eabac7ddfdcdc5bff4"}, + {file = "coincurve-18.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:286969b6f789bbd9d744d28350a3630c1cb3ee045263469a28892f70a4a6654a"}, + {file = "coincurve-18.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:14700463009c7d799a746929728223aa53ff1ece394ea408516d98d637434883"}, + {file = "coincurve-18.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7f1142252e870f091b2c2c21cc1fadfdd29af23d02e99f29add0f14d1ba94b4c"}, + {file = "coincurve-18.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cd11d2ca5b7e989c5ce1af217a2ad78c19c21afca786f198d1b1a408d6f408dc"}, + {file = "coincurve-18.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1bce17d7475cee9db2c2fa7af07eaab582732b378acf6dcaee417de1df2d8661"}, + {file = "coincurve-18.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab662b67454fea7f0a5ae855ba6ad9410bcaebe68b97f4dade7b5944dec3a11"}, + {file = "coincurve-18.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23b9ced9cce32dabb4bc15fa6449252fa51efddf0268481973e4c3772a5a68c6"}, + {file = "coincurve-18.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d05641cf31d68514c47cb54105d20acbae79fc3ee3942454eaaf411babb3f880"}, + {file = "coincurve-18.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a7b31efe56b3f6434828ad5f6ecde4a95747bb69b59032746482eebb8f3456a4"}, + {file = "coincurve-18.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d95103ed43df855121cd925869ae2589360a8d94fcd61b236958deacfb9a359"}, + {file = "coincurve-18.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:abeb4c1d78e1a81a3f1c99a406cd858669582ada2d976e876ef694f57dec95ca"}, + {file = "coincurve-18.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fceca9d6ecaa1e8f891675e4f4ff530d54e41c648fc6e8a816835ffa640fa899"}, + {file = "coincurve-18.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e009f06287507158f16c82cc313c0f3bfd0e9ec1e82d1a4d5fa1c5b6c0060f69"}, + {file = "coincurve-18.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a0c0c1e492ef08efe99d25a23d535e2bff667bbef43d71a6f8893ae811b3d81"}, + {file = "coincurve-18.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3caf58877bcf41eb4c1be7a2d54317f0b31541d99ba248dae28821b19c52a0db"}, + {file = "coincurve-18.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8964e680c622a2b5eea940abdf51c77c1bd3d4fde2a04cec2420bf91981b198a"}, + {file = "coincurve-18.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:73e464e0ace77c686fdc54590e5592905b6802f9fc20a0c023f0b1585669d6a3"}, + {file = "coincurve-18.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ba9eaddd50a2ce0d891af7cee11c2e048d1f0f44bf87db00a5c4b1eee7e3391b"}, + {file = "coincurve-18.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8290903d4629f27f9f3cdeec72ffa97536c5a6ed5ba7e3413b2707991c650fbe"}, + {file = "coincurve-18.0.0-py3-none-win32.whl", hash = "sha256:c60690bd7704d8563968d2dded33eb514875a52b5964f085409965ad041b2555"}, + {file = "coincurve-18.0.0-py3-none-win_amd64.whl", hash = "sha256:704d1abf2e78def33988368592233a8ec9b98bfc45dfa2ec9e898adfad46e5ad"}, + {file = "coincurve-18.0.0.tar.gz", hash = "sha256:c86626afe417a09d8e80e56780efcae3ae516203b23b5ade84813916e1c94fc1"}, +] + +[package.dependencies] +asn1crypto = "*" +cffi = ">=1.3.0" + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cryptography" +version = "41.0.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "dnspython" +version = "2.6.1" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "ecdsa" +version = "0.19.0" +description = "ECDSA cryptographic signature library (pure python)" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" +files = [ + {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, + {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, +] + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[[package]] +name = "email-validator" +version = "2.2.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + +[[package]] +name = "embit" +version = "0.8.0" +description = "A minimal bitcoin library for MicroPython and Python3 with a focus on embedded systems." +optional = false +python-versions = "*" +files = [ + {file = "embit-0.8.0.tar.gz", hash = "sha256:8bf4b10073c67400370ce523fb16f035fe759f6fdd987c579bdcc268d75ed770"}, +] + +[package.extras] +dev = ["black", "mkdocs", "mkdocs-material", "mkdocstrings[python]", "mypy", "pre-commit", "pytest", "pytest-cov", "requests"] + +[[package]] +name = "environs" +version = "9.5.0" +description = "simplified environment variable parsing" +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"}, +] + +[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 = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastapi" +version = "0.112.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.112.0-py3-none-any.whl", hash = "sha256:3487ded9778006a45834b8c816ec4a48d522e2631ca9e75ec5a774f1b052f821"}, + {file = "fastapi-0.112.0.tar.gz", hash = "sha256:d262bc56b7d101d1f4e8fc0ad2ac75bb9935fec504d2b7117686cec50710cf05"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.37.2,<0.38.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "fastapi-sso" +version = "0.15.0" +description = "FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "fastapi_sso-0.15.0-py3-none-any.whl", hash = "sha256:071e0c204181bdb6a8afad2714f9f6abad32bd8984b9e57500ff0d2b680fdb37"}, + {file = "fastapi_sso-0.15.0.tar.gz", hash = "sha256:9717c3e0f88ae7cccb43fa1827e6b85fa2621a4c41b21347ddebf719b40abc15"}, +] + +[package.dependencies] +fastapi = ">=0.80" +httpx = ">=0.23.0" +oauthlib = ">=3.1.0" +pydantic = {version = ">=1.8.0", extras = ["email"]} + +[[package]] +name = "filelock" +version = "3.15.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "grpcio" +version = "1.65.5" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.8" +files = [ + {file = "grpcio-1.65.5-cp310-cp310-linux_armv7l.whl", hash = "sha256:b67d450f1e008fedcd81e097a3a400a711d8be1a8b20f852a7b8a73fead50fe3"}, + {file = "grpcio-1.65.5-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a70a20eed87bba647a38bedd93b3ce7db64b3f0e8e0952315237f7f5ca97b02d"}, + {file = "grpcio-1.65.5-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f79c87c114bf37adf408026b9e2e333fe9ff31dfc9648f6f80776c513145c813"}, + {file = "grpcio-1.65.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17f9fa2d947dbfaca01b3ab2c62eefa8240131fdc67b924eb42ce6032e3e5c1"}, + {file = "grpcio-1.65.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32d60e18ff7c34fe3f6db3d35ad5c6dc99f5b43ff3982cb26fad4174462d10b1"}, + {file = "grpcio-1.65.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fe6505376f5b00bb008e4e1418152e3ad3d954b629da286c7913ff3cfc0ff740"}, + {file = "grpcio-1.65.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:33158e56c6378063923c417e9fbdb28660b6e0e2835af42e67f5a7793f587af7"}, + {file = "grpcio-1.65.5-cp310-cp310-win32.whl", hash = "sha256:1cbc208edb9acf1cc339396a1a36b83796939be52f34e591c90292045b579fbf"}, + {file = "grpcio-1.65.5-cp310-cp310-win_amd64.whl", hash = "sha256:bc74f3f745c37e2c5685c9d2a2d5a94de00f286963f5213f763ae137bf4f2358"}, + {file = "grpcio-1.65.5-cp311-cp311-linux_armv7l.whl", hash = "sha256:3207ae60d07e5282c134b6e02f9271a2cb523c6d7a346c6315211fe2bf8d61ed"}, + {file = "grpcio-1.65.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2f80510f99f82d4eb825849c486df703f50652cea21c189eacc2b84f2bde764"}, + {file = "grpcio-1.65.5-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a80e9a5e3f93c54f5eb82a3825ea1fc4965b2fa0026db2abfecb139a5c4ecdf1"}, + {file = "grpcio-1.65.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b2944390a496567de9e70418f3742b477d85d8ca065afa90432edc91b4bb8ad"}, + {file = "grpcio-1.65.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3655139d7be213c32c79ef6fb2367cae28e56ef68e39b1961c43214b457f257"}, + {file = "grpcio-1.65.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05f02d68fc720e085f061b704ee653b181e6d5abfe315daef085719728d3d1fd"}, + {file = "grpcio-1.65.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1c4caafe71aef4dabf53274bbf4affd6df651e9f80beedd6b8e08ff438ed3260"}, + {file = "grpcio-1.65.5-cp311-cp311-win32.whl", hash = "sha256:84c901cdec16a092099f251ef3360d15e29ef59772150fa261d94573612539b5"}, + {file = "grpcio-1.65.5-cp311-cp311-win_amd64.whl", hash = "sha256:11f8b16121768c1cb99d7dcb84e01510e60e6a206bf9123e134118802486f035"}, + {file = "grpcio-1.65.5-cp312-cp312-linux_armv7l.whl", hash = "sha256:ee6ed64a27588a2c94e8fa84fe8f3b5c89427d4d69c37690903d428ec61ca7e4"}, + {file = "grpcio-1.65.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:76991b7a6fb98630a3328839755181ce7c1aa2b1842aa085fd4198f0e5198960"}, + {file = "grpcio-1.65.5-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:89c00a18801b1ed9cc441e29b521c354725d4af38c127981f2c950c796a09b6e"}, + {file = "grpcio-1.65.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:078038e150a897e5e402ed3d57f1d31ebf604cbed80f595bd281b5da40762a92"}, + {file = "grpcio-1.65.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97962720489ef31b5ad8a916e22bc31bba3664e063fb9f6702dce056d4aa61b"}, + {file = "grpcio-1.65.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b8270b15b99781461b244f5c81d5c2bc9696ab9189fb5ff86c841417fb3b39fe"}, + {file = "grpcio-1.65.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8e5c4c15ac3fe1eb68e46bc51e66ad29be887479f231f8237cf8416058bf0cc1"}, + {file = "grpcio-1.65.5-cp312-cp312-win32.whl", hash = "sha256:f5b5970341359341d0e4c789da7568264b2a89cd976c05ea476036852b5950cd"}, + {file = "grpcio-1.65.5-cp312-cp312-win_amd64.whl", hash = "sha256:238a625f391a1b9f5f069bdc5930f4fd71b74426bea52196fc7b83f51fa97d34"}, + {file = "grpcio-1.65.5-cp38-cp38-linux_armv7l.whl", hash = "sha256:6c4e62bcf297a1568f627f39576dbfc27f1e5338a691c6dd5dd6b3979da51d1c"}, + {file = "grpcio-1.65.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d7df567b67d16d4177835a68d3f767bbcbad04da9dfb52cbd19171f430c898bd"}, + {file = "grpcio-1.65.5-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:b7ca419f1462390851eec395b2089aad1e49546b52d4e2c972ceb76da69b10f8"}, + {file = "grpcio-1.65.5-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa36dd8496d3af0d40165252a669fa4f6fd2db4b4026b9a9411cbf060b9d6a15"}, + {file = "grpcio-1.65.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a101696f9ece90a0829988ff72f1b1ea2358f3df035bdf6d675dd8b60c2c0894"}, + {file = "grpcio-1.65.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2a6d8169812932feac514b420daffae8ab8e36f90f3122b94ae767e633296b17"}, + {file = "grpcio-1.65.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:47d0aaaab82823f0aa6adea5184350b46e2252e13a42a942db84da5b733f2e05"}, + {file = "grpcio-1.65.5-cp38-cp38-win32.whl", hash = "sha256:85ae8f8517d5bcc21fb07dbf791e94ed84cc28f84c903cdc2bd7eaeb437c8f45"}, + {file = "grpcio-1.65.5-cp38-cp38-win_amd64.whl", hash = "sha256:770bd4bd721961f6dd8049bc27338564ba8739913f77c0f381a9815e465ff965"}, + {file = "grpcio-1.65.5-cp39-cp39-linux_armv7l.whl", hash = "sha256:ab5ec837d8cee8dbce9ef6386125f119b231e4333cc6b6d57b6c5c7c82a72331"}, + {file = "grpcio-1.65.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cabd706183ee08d8026a015af5819a0b3a8959bdc9d1f6fdacd1810f09200f2a"}, + {file = "grpcio-1.65.5-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:ec71fc5b39821ad7d80db7473c8f8c2910f3382f0ddadfbcfc2c6c437107eb67"}, + {file = "grpcio-1.65.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a9e35bcb045e39d7cac30464c285389b9a816ac2067e4884ad2c02e709ef8e"}, + {file = "grpcio-1.65.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d750e9330eb14236ca11b78d0c494eed13d6a95eb55472298f0e547c165ee324"}, + {file = "grpcio-1.65.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2b91ce647b6307f25650872454a4d02a2801f26a475f90d0b91ed8110baae589"}, + {file = "grpcio-1.65.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8da58ff80bc4556cf29bc03f5fff1f03b8387d6aaa7b852af9eb65b2cf833be4"}, + {file = "grpcio-1.65.5-cp39-cp39-win32.whl", hash = "sha256:7a412959aa5f08c5ac04aa7b7c3c041f5e4298cadd4fcc2acff195b56d185ebc"}, + {file = "grpcio-1.65.5-cp39-cp39-win_amd64.whl", hash = "sha256:55714ea852396ec9568f45f487639945ab674de83c12bea19d5ddbc3ae41ada3"}, + {file = "grpcio-1.65.5.tar.gz", hash = "sha256:ec6f219fb5d677a522b0deaf43cea6697b16f338cb68d009e30930c4aa0d2209"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.65.5)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "http-ece" +version = "1.2.0" +description = "Encrypted Content Encoding for HTTP" +optional = false +python-versions = "*" +files = [ + {file = "http_ece-1.2.0.tar.gz", hash = "sha256:b5920f8efb8e1b5fb025713e3b36fda54336262010634b26dc1f98f85d1eb3de"}, +] + +[package.dependencies] +cryptography = ">=2.5" + +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "identify" +version = "2.6.0" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "importlib-resources" +version = "6.4.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, + {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "limits" +version = "3.13.0" +description = "Rate limiting utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "limits-3.13.0-py3-none-any.whl", hash = "sha256:9767f7233da4255e9904b79908a728e8ec0984c0b086058b4cbbd309aea553f6"}, + {file = "limits-3.13.0.tar.gz", hash = "sha256:6571b0c567bfa175a35fed9f8a954c0c92f1c3200804282f1b8f1de4ad98a953"}, +] + +[package.dependencies] +deprecated = ">=1.2" +importlib-resources = ">=1.3" +packaging = ">=21,<25" +typing-extensions = "*" + +[package.extras] +all = ["aetcd", "coredis (>=3.4.0,<5)", "emcache (>=0.6.1)", "emcache (>=1)", "etcd3", "motor (>=3,<4)", "pymemcache (>3,<5.0.0)", "pymongo (>4.1,<5)", "redis (>3,!=4.5.2,!=4.5.3,<6.0.0)", "redis (>=4.2.0,!=4.5.2,!=4.5.3)"] +async-etcd = ["aetcd"] +async-memcached = ["emcache (>=0.6.1)", "emcache (>=1)"] +async-mongodb = ["motor (>=3,<4)"] +async-redis = ["coredis (>=3.4.0,<5)"] +etcd = ["etcd3"] +memcached = ["pymemcache (>3,<5.0.0)"] +mongodb = ["pymongo (>4.1,<5)"] +redis = ["redis (>3,!=4.5.2,!=4.5.3,<6.0.0)"] +rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] + +[[package]] +name = "lnbits" +version = "0.12.11" +description = "LNbits, free and open-source Lightning wallet and accounts system." +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "lnbits-0.12.11-py3-none-any.whl", hash = "sha256:269c38aa1f88085cf3d00c4427dab7f2ced0e08692a2b2ea77d5a181fa9d170a"}, + {file = "lnbits-0.12.11.tar.gz", hash = "sha256:5137d3d8ef4d4a3d1f961b8fdd8644f69ec2c440b06adac6a0c0e7161b2e4e01"}, +] + +[package.dependencies] +bcrypt = "4.2.0" +bech32 = "1.2.0" +bolt11 = "2.1.0" +click = "8.1.7" +ecdsa = "0.19.0" +embit = "0.8.0" +environs = "9.5.0" +fastapi = "0.112.0" +fastapi-sso = "0.15.0" +grpcio = "1.65.5" +httpx = "0.27.0" +itsdangerous = "2.2.0" +jinja2 = "3.1.4" +lnurl = "0.5.3" +loguru = "0.7.2" +packaging = "24.0" +passlib = "1.7.4" +protobuf = "5.27.3" +psycopg2-binary = "2.9.9" +pycryptodomex = "3.20.0" +pydantic = "1.10.17" +pyjwt = "2.9.0" +pyln-client = "24.5" +pyqrcode = "1.2.1" +python-crontab = "3.2.0" +pywebpush = "1.14.1" +secp256k1 = "0.14.0" +shortuuid = "1.0.13" +slowapi = "0.1.9" +sqlalchemy = "1.3.24" +sqlalchemy-aio = "0.17.0" +sse-starlette = "1.8.2" +typing-extensions = "4.12.2" +uvicorn = "0.30.5" +uvloop = "0.19.0" +websocket-client = "1.8.0" +websockets = "11.0.3" + +[package.extras] +breez = ["breez-sdk (==0.5.2)"] +liquid = ["wallycore (==1.3.0)"] + +[[package]] +name = "lnurl" +version = "0.5.3" +description = "LNURL implementation for Python." +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "lnurl-0.5.3-py3-none-any.whl", hash = "sha256:feaf6c60b0b7f104894ef3accbd30d23d52e038c2797c58432baea7f4a8aa952"}, + {file = "lnurl-0.5.3.tar.gz", hash = "sha256:60154bcfdbb98fb143eeca970a16d73a582f28e057a826b5f222259411c497fe"}, +] + +[package.dependencies] +bech32 = ">=1.2.0,<2.0.0" +bolt11 = ">=2.0.5,<3.0.0" +ecdsa = ">=0.19.0,<0.20.0" +httpx = ">=0.27.0,<0.28.0" +pydantic = ">=1,<2" + +[[package]] +name = "loguru" +version = "0.7.2" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = ">=3.5" +files = [ + {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, + {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, +] + +[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 (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "marshmallow" +version = "3.21.3" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, + {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "mypy" +version = "1.10.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, + {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, + {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, + {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, + {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, + {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, + {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, + {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, + {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, + {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, + {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, + {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, + {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, + {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, + {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, + {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, + {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, + {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +description = "Capture the outcome of Python function calls." +optional = false +python-versions = ">=3.7" +files = [ + {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, + {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, +] + +[package.dependencies] +attrs = ">=19.2.0" + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "passlib" +version = "1.7.4" +description = "comprehensive password hashing framework supporting over 30 schemes" +optional = false +python-versions = "*" +files = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] + +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] +totp = ["cryptography"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "3.7.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "protobuf" +version = "5.27.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-5.27.3-cp310-abi3-win32.whl", hash = "sha256:dcb307cd4ef8fec0cf52cb9105a03d06fbb5275ce6d84a6ae33bc6cf84e0a07b"}, + {file = "protobuf-5.27.3-cp310-abi3-win_amd64.whl", hash = "sha256:16ddf3f8c6c41e1e803da7abea17b1793a97ef079a912e42351eabb19b2cffe7"}, + {file = "protobuf-5.27.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:68248c60d53f6168f565a8c76dc58ba4fa2ade31c2d1ebdae6d80f969cdc2d4f"}, + {file = "protobuf-5.27.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:b8a994fb3d1c11156e7d1e427186662b64694a62b55936b2b9348f0a7c6625ce"}, + {file = "protobuf-5.27.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:a55c48f2a2092d8e213bd143474df33a6ae751b781dd1d1f4d953c128a415b25"}, + {file = "protobuf-5.27.3-cp38-cp38-win32.whl", hash = "sha256:043853dcb55cc262bf2e116215ad43fa0859caab79bb0b2d31b708f128ece035"}, + {file = "protobuf-5.27.3-cp38-cp38-win_amd64.whl", hash = "sha256:c2a105c24f08b1e53d6c7ffe69cb09d0031512f0b72f812dd4005b8112dbe91e"}, + {file = "protobuf-5.27.3-cp39-cp39-win32.whl", hash = "sha256:c84eee2c71ed83704f1afbf1a85c3171eab0fd1ade3b399b3fad0884cbcca8bf"}, + {file = "protobuf-5.27.3-cp39-cp39-win_amd64.whl", hash = "sha256:af7c0b7cfbbb649ad26132e53faa348580f844d9ca46fd3ec7ca48a1ea5db8a1"}, + {file = "protobuf-5.27.3-py3-none-any.whl", hash = "sha256:8572c6533e544ebf6899c360e91d6bcbbee2549251643d32c52cf8a5de295ba5"}, + {file = "protobuf-5.27.3.tar.gz", hash = "sha256:82460903e640f2b7e34ee81a947fdaad89de796d324bcbc38ff5430bcdead82c"}, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.9" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, +] + +[[package]] +name = "py-vapid" +version = "1.9.1" +description = "Simple VAPID header generation library" +optional = false +python-versions = "*" +files = [ + {file = "py_vapid-1.9.1.tar.gz", hash = "sha256:fe2b5461bf45c7baff1039df6981f03b87faa87cde0482addfa35b3fe636ac1b"}, +] + +[package.dependencies] +cryptography = ">=2.5" + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pycryptodomex" +version = "3.20.0" +description = "Cryptographic library for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, + {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, + {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, + {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, + {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, + {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, + {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, + {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, + {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, +] + +[[package]] +name = "pydantic" +version = "1.10.17" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b"}, + {file = "pydantic-1.10.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a"}, + {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e"}, + {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7"}, + {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6"}, + {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681"}, + {file = "pydantic-1.10.17-cp310-cp310-win_amd64.whl", hash = "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3"}, + {file = "pydantic-1.10.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a"}, + {file = "pydantic-1.10.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b"}, + {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63"}, + {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741"}, + {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c"}, + {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d"}, + {file = "pydantic-1.10.17-cp311-cp311-win_amd64.whl", hash = "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b"}, + {file = "pydantic-1.10.17-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb"}, + {file = "pydantic-1.10.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815"}, + {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab"}, + {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc"}, + {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f"}, + {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f"}, + {file = "pydantic-1.10.17-cp312-cp312-win_amd64.whl", hash = "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad"}, + {file = "pydantic-1.10.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655"}, + {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b"}, + {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7"}, + {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3"}, + {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076"}, + {file = "pydantic-1.10.17-cp37-cp37m-win_amd64.whl", hash = "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f"}, + {file = "pydantic-1.10.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33"}, + {file = "pydantic-1.10.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e"}, + {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768"}, + {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7"}, + {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7"}, + {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75"}, + {file = "pydantic-1.10.17-cp38-cp38-win_amd64.whl", hash = "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0"}, + {file = "pydantic-1.10.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54"}, + {file = "pydantic-1.10.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773"}, + {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe"}, + {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab"}, + {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d"}, + {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd"}, + {file = "pydantic-1.10.17-cp39-cp39-win_amd64.whl", hash = "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227"}, + {file = "pydantic-1.10.17-py3-none-any.whl", hash = "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688"}, + {file = "pydantic-1.10.17.tar.gz", hash = "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991"}, +] + +[package.dependencies] +email-validator = {version = ">=1.0.3", optional = true, markers = "extra == \"email\""} +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyjwt" +version = "2.9.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, + {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pyln-bolt7" +version = "1.0.246" +description = "BOLT7" +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"}, +] + +[[package]] +name = "pyln-client" +version = "24.5" +description = "Client library and plugin library for Core Lightning" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "pyln_client-24.5-py3-none-any.whl", hash = "sha256:b1c7fc8644c0668472ba9cc5d45b6fe88f44bae396f624f0b1a09695011fce0d"}, + {file = "pyln_client-24.5.tar.gz", hash = "sha256:642af007f4b0ea75f7c1771b7a4563acf858e0e761023d84b7f61fef13cf3888"}, +] + +[package.dependencies] +pyln-bolt7 = ">=1.0" +pyln-proto = ">=23" + +[[package]] +name = "pyln-proto" +version = "24.5" +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!)." +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "pyln_proto-24.5-py3-none-any.whl", hash = "sha256:78b9982412cfea8172dfafe217001bdf3d6ee8d585f138b8868925e47a8c54b2"}, + {file = "pyln_proto-24.5.tar.gz", hash = "sha256:b6e7a0cd5bd3905d743ec2c3ef93233271062e0abf0b7647a449ff71c0f20074"}, +] + +[package.dependencies] +base58 = ">=2.1.1,<3.0.0" +bitstring = ">=4.1.0,<5.0.0" +coincurve = ">=18,<19" +cryptography = ">=41,<42" +PySocks = ">=1,<2" + +[[package]] +name = "pyqrcode" +version = "1.2.1" +description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output." +optional = false +python-versions = "*" +files = [ + {file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"}, + {file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"}, +] + +[package.extras] +png = ["pypng (>=0.0.13)"] + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +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"}, +] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.21.2" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, + {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "python-crontab" +version = "3.2.0" +description = "Python Crontab API" +optional = false +python-versions = "*" +files = [ + {file = "python_crontab-3.2.0-py3-none-any.whl", hash = "sha256:82cb9b6a312d41ff66fd3caf3eed7115c28c195bfb50711bc2b4b9592feb9fe5"}, + {file = "python_crontab-3.2.0.tar.gz", hash = "sha256:40067d1dd39ade3460b2ad8557c7651514cd3851deffff61c5c60e1227c5c36b"}, +] + +[package.dependencies] +python-dateutil = "*" + +[package.extras] +cron-description = ["cron-descriptor"] +cron-schedule = ["croniter"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pywebpush" +version = "1.14.1" +description = "WebPush publication library" +optional = false +python-versions = "*" +files = [ + {file = "pywebpush-1.14.1-py3-none-any.whl", hash = "sha256:2865ee65cf44375f7cbdcfd5ba915a9d84c239900c6fba2245efd8d8314a3e84"}, + {file = "pywebpush-1.14.1.tar.gz", hash = "sha256:f88d7e2bf5e87c616dfb04b8c95c119238c511659b02f735ee77cc16842855ee"}, +] + +[package.dependencies] +cryptography = ">=2.6.1" +http-ece = ">=1.1.0" +py-vapid = ">=1.7.0" +requests = ">=2.21.0" +six = ">=1.15.0" + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "represent" +version = "2.1" +description = "Create __repr__ automatically or declaratively." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Represent-2.1-py3-none-any.whl", hash = "sha256:94fd22d7fec378240c598b20b233f80545ec7eb1131076e2d3d759cee9be2588"}, + {file = "Represent-2.1.tar.gz", hash = "sha256:0b2d015c14e7ba6b3b5e6a7ba131a952013fe944339ac538764ce728a75dbcac"}, +] + +[package.extras] +docstest = ["furo", "parver", "sphinx"] +test = ["ipython", "pytest", "rich"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "ruff" +version = "0.3.7" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, + {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, + {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, + {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, + {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, +] + +[[package]] +name = "secp256k1" +version = "0.14.0" +description = "FFI bindings to libsecp256k1" +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"}, + {file = "secp256k1-0.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4b9306bff6dde020444dfee9ca9b9f5b20ca53a2c0b04898361a3f43d5daf2e"}, + {file = "secp256k1-0.14.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:72735da6cb28273e924431cd40aa607e7f80ef09608c8c9300be2e0e1d2417b4"}, + {file = "secp256k1-0.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:87f4ad42a370f768910585989a301d1d65de17dcd86f6e8def9b021364b34d5c"}, + {file = "secp256k1-0.14.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:130f119b06142e597c10eb4470b5a38eae865362d01aaef06b113478d77f728d"}, + {file = "secp256k1-0.14.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:3aedcfe6eb1c5fa7c6be25b7cc91c76d8eb984271920ba0f7a934ae41ed56f51"}, + {file = "secp256k1-0.14.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c91dd3154f6c46ac798d9a41166120e1751222587f54516cc3f378f56ce4ac82"}, + {file = "secp256k1-0.14.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fec790cb6d0d37129ca0ce5b3f8e85692d5fb618d1c440f189453d18694035df"}, + {file = "secp256k1-0.14.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:63eb148196b8f646922d4be6739b17fbbf50ebb3a020078c823e2445d88b7a81"}, + {file = "secp256k1-0.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:adc23a4c5d24c95191638eb2ca313097827f07db102e77b59faed15d50c98cae"}, + {file = "secp256k1-0.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ce0314788d3248b275426501228969fd32f6501c9d1837902ee0e7bd8264a36f"}, + {file = "secp256k1-0.14.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bc761894b3634021686714278fc62b73395fa3eded33453eadfd8a00a6c44ef3"}, + {file = "secp256k1-0.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:373dc8bca735f3c2d73259aa2711a9ecea2f3c7edbb663555fe3422e3dd76102"}, + {file = "secp256k1-0.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe3f503c9dfdf663b500d3e0688ad842e116c2907ad3f1e1d685812df3f56290"}, + {file = "secp256k1-0.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4b1bf09953cde181132cf5e9033065615e5c2694e803165e2db763efa47695e5"}, + {file = "secp256k1-0.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6af07be5f8612628c3638dc7b208f6cc78d0abae3e25797eadb13890c7d5da81"}, + {file = "secp256k1-0.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a8dbd75a9fb6f42de307f3c5e24573fe59c3374637cbf39136edc66c200a4029"}, + {file = "secp256k1-0.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97a30c8dae633cb18135c76b6517ae99dc59106818e8985be70dbc05dcc06c0d"}, + {file = "secp256k1-0.14.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f4062d8c101aa63b9ecb3709f1f075ad9c01b6672869bbaa1bd77271816936a7"}, + {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"}, +] + +[package.dependencies] +cffi = ">=1.3.0" + +[[package]] +name = "shortuuid" +version = "1.0.13" +description = "A generator library for concise, unambiguous and URL-safe UUIDs." +optional = false +python-versions = ">=3.6" +files = [ + {file = "shortuuid-1.0.13-py3-none-any.whl", hash = "sha256:a482a497300b49b4953e15108a7913244e1bb0d41f9d332f5e9925dba33a3c5a"}, + {file = "shortuuid-1.0.13.tar.gz", hash = "sha256:3bb9cf07f606260584b1df46399c0b87dd84773e7b25912b7e391e30797c5e72"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +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"}, +] + +[[package]] +name = "slowapi" +version = "0.1.9" +description = "A rate limiting extension for Starlette and Fastapi" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "slowapi-0.1.9-py3-none-any.whl", hash = "sha256:cfad116cfb84ad9d763ee155c1e5c5cbf00b0d47399a769b227865f5df576e36"}, + {file = "slowapi-0.1.9.tar.gz", hash = "sha256:639192d0f1ca01b1c6d95bf6c71d794c3a9ee189855337b4821f7f457dddad77"}, +] + +[package.dependencies] +limits = ">=2.3" + +[package.extras] +redis = ["redis (>=3.4.1,<4.0.0)"] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sqlalchemy" +version = "1.3.24" +description = "Database Abstraction Library" +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"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, + {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, +] + +[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." +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"}, +] + +[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 = "1.8.2" +description = "SSE plugin for Starlette" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sse_starlette-1.8.2-py3-none-any.whl", hash = "sha256:70cc7ef5aca4abe8a25dec1284cce4fe644dd7bf0c406d3e852e516092b7f849"}, + {file = "sse_starlette-1.8.2.tar.gz", hash = "sha256:e0f9b8dec41adc092a0a6e0694334bd3cfd3084c44c497a6ebc1fb4bdd919acd"}, +] + +[package.dependencies] +anyio = "*" +fastapi = "*" +starlette = "*" +uvicorn = "*" + +[[package]] +name = "starlette" +version = "0.37.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +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"}, +] + +[[package]] +name = "types-cffi" +version = "1.16.0.20240331" +description = "Typing stubs for cffi" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-cffi-1.16.0.20240331.tar.gz", hash = "sha256:b8b20d23a2b89cfed5f8c5bc53b0cb8677c3aac6d970dbc771e28b9c698f5dee"}, + {file = "types_cffi-1.16.0.20240331-py3-none-any.whl", hash = "sha256:a363e5ea54a4eb6a4a105d800685fde596bc318089b025b27dee09849fe41ff0"}, +] + +[package.dependencies] +types-setuptools = "*" + +[[package]] +name = "types-setuptools" +version = "71.1.0.20240726" +description = "Typing stubs for setuptools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-setuptools-71.1.0.20240726.tar.gz", hash = "sha256:85ba28e9461bb1be86ebba4db0f1c2408f2b11115b1966334ea9dc464e29303e"}, + {file = "types_setuptools-71.1.0.20240726-py3-none-any.whl", hash = "sha256:a7775376f36e0ff09bcad236bf265777590a66b11623e48c20bfc30f1444ea36"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.30.5" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.30.5-py3-none-any.whl", hash = "sha256:b2d86de274726e9878188fa07576c9ceeff90a839e2b6e25c917fe05f5a6c835"}, + {file = "uvicorn-0.30.5.tar.gz", hash = "sha256:ac6fdbd4425c5fd17a9fe39daf4d4d075da6fdc80f653e5894cdc2fd98752bee"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.19.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, + {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, +] + +[package.extras] +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 = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] + +[[package]] +name = "virtualenv" +version = "20.26.3" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "websocket-client" +version = "1.8.0" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, +] + +[package.extras] +docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "websockets" +version = "11.0.3" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, +] + +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +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"}, +] + +[package.extras] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "zipp" +version = "3.19.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, +] + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10 | ^3.9" +content-hash = "6b44707a3b75a9adc6fea66a2434468588b3cdc4993ae84e77bfbe01e68fb27a" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b1e326a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,99 @@ +[tool.poetry] +name = "nwcprovider" +version = "0.0.0" +description = "A NWC service provider for LNbits." +authors = ["Riccardo Balbo "] + +[tool.poetry.dependencies] +python = "^3.10 | ^3.9" +lnbits = "*" +psycopg2-binary = "^2.9.9" + +[tool.poetry.group.dev.dependencies] +black = "^24.3.0" +pytest-asyncio = "^0.21.0" +pytest = "^7.3.2" +mypy = "^1.5.1" +pre-commit = "^3.2.2" +ruff = "^0.3.2" +types-cffi = "^1.16.0.20240331" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.mypy] +exclude = "(tests/*)" + +[[tool.mypy.overrides]] +module = [ + "lnbits.*", + "lnurl.*", + "loguru.*", + "fastapi.*", + "pydantic.*", + "pyqrcode.*", + "shortuuid.*", + "httpx.*", + "websocket.*", + "secp256k1.*", +] +ignore_missing_imports = "True" + +[tool.pytest.ini_options] +log_cli = false +testpaths = [ + "tests" +] + +[tool.black] +line-length = 88 + +[tool.ruff] +# Same as Black. + 10% rule of black +line-length = 88 +# exclude = [ +# "nostr", +# ] + +[tool.ruff.lint] +# Enable: +# F - pyflakes +# E - pycodestyle errors +# W - pycodestyle warnings +# I - isort +# A - flake8-builtins +# C - mccabe +# N - naming +# UP - pyupgrade +# RUF - ruff +# B - bugbear +select = ["F", "E", "W", "I", "A", "C", "N", "UP", "RUF", "B"] +ignore = ["C901"] + +# Allow autofix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +# needed for pydantic +[tool.ruff.lint.pep8-naming] +classmethod-decorators = [ + "root_validator", +] + +# Ignore unused imports in __init__.py files. +# [tool.ruff.lint.extend-per-file-ignores] +# "__init__.py" = ["F401", "F403"] + +# [tool.ruff.lint.mccabe] +# max-complexity = 10 + +[tool.ruff.lint.flake8-bugbear] +# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. +extend-immutable-calls = [ + "fastapi.Depends", + "fastapi.Query", +] diff --git a/static/js/noble-secp256k1.min.js b/static/js/noble-secp256k1.min.js index b4210cb..af328c8 100644 --- a/static/js/noble-secp256k1.min.js +++ b/static/js/noble-secp256k1.min.js @@ -1,2 +1,520 @@ /*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ -const B256=2n**256n,P=B256-0x1000003d1n,N=B256-0x14551231950b75fc4402da1732fc9bebfn,Gx=0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n,Gy=0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n,CURVE={p:P,n:N,a:0n,b:7n,Gx,Gy},fLen=32,crv=t=>mod(mod(t*t)*t+CURVE.b),err=(t="")=>{throw new Error(t)},big=t=>"bigint"==typeof t,str=t=>"string"==typeof t,fe=t=>big(t)&&0nbig(t)&&0nt instanceof Uint8Array||null!=t&&"object"==typeof t&&"Uint8Array"===t.constructor.name,au8=(t,n)=>!isu8(t)||"number"==typeof n&&n>0&&t.length!==n?err("Uint8Array expected"):t,u8n=t=>new Uint8Array(t),toU8=(t,n)=>au8(str(t)?h2b(t):u8n(au8(t)),n),mod=(t,n=P)=>{let e=t%n;return e>=0n?e:n+e},isPoint=t=>t instanceof Point?t:err("Point expected");class Point{constructor(t,n,e){this.px=t,this.py=n,this.pz=e}static fromAffine(t){return 0n===t.x&&0n===t.y?Point.ZERO:new Point(t.x,t.y,1n)}static fromHex(t){let n;const e=(t=toU8(t))[0],r=t.subarray(1),o=slcNum(r,0,32),i=t.length;if(33===i&&[2,3].includes(e)){fe(o)||err("Point hex invalid: x not FE");let t=sqrt(crv(o));!(1&~e)!==(1n===(1n&t))&&(t=mod(-t)),n=new Point(o,t,1n)}return 65===i&&4===e&&(n=new Point(o,slcNum(r,32,64),1n)),n?n.ok():err("Point is not on curve")}static fromPrivateKey(t){return G.mul(toPriv(t))}get x(){return this.aff().x}get y(){return this.aff().y}equals(t){const{px:n,py:e,pz:r}=this,{px:o,py:i,pz:s}=isPoint(t),a=mod(n*s),c=mod(o*r),d=mod(e*s),u=mod(i*r);return a===c&&d===u}negate(){return new Point(this.px,mod(-this.py),this.pz)}double(){return this.add(this)}add(t){const{px:n,py:e,pz:r}=this,{px:o,py:i,pz:s}=isPoint(t),{a,b:c}=CURVE;let d=0n,u=0n,m=0n;const h=mod(3n*c);let l=mod(n*o),y=mod(e*i),f=mod(r*s),p=mod(n+e),b=mod(o+i);p=mod(p*b),b=mod(l+y),p=mod(p-b),b=mod(n+r);let g=mod(o+s);return b=mod(b*g),g=mod(l+f),b=mod(b-g),g=mod(e+r),d=mod(i+s),g=mod(g*d),d=mod(y+f),g=mod(g-d),m=mod(a*b),d=mod(h*f),m=mod(d+m),d=mod(y-m),m=mod(y+m),u=mod(d*m),y=mod(l+l),y=mod(y+l),f=mod(a*f),b=mod(h*b),y=mod(y+f),f=mod(l-f),f=mod(a*f),b=mod(b+f),l=mod(y*b),u=mod(u+l),l=mod(g*b),d=mod(p*d),d=mod(d-l),l=mod(p*y),m=mod(g*m),m=mod(m+l),new Point(d,u,m)}mul(t,n=!0){if(!n&&0n===t)return I;if(ge(t)||err("invalid scalar"),this.equals(G))return wNAF(t).p;let e=I,r=G;for(let o=this;t>0n;o=o.double(),t>>=1n)1n&t?e=e.add(o):n&&(r=r.add(o));return e}mulAddQUns(t,n,e){return this.mul(n,!1).add(t.mul(e,!1)).ok()}toAffine(){const{px:t,py:n,pz:e}=this;if(this.equals(I))return{x:0n,y:0n};if(1n===e)return{x:t,y:n};const r=inv(e);return 1n!==mod(e*r)&&err("invalid inverse"),{x:mod(t*r),y:mod(n*r)}}assertValidity(){const{x:t,y:n}=this.aff();return fe(t)&&fe(n)||err("Point invalid: x or y"),mod(n*n)===crv(t)?this:err("Point invalid: not on curve")}multiply(t){return this.mul(t)}aff(){return this.toAffine()}ok(){return this.assertValidity()}toHex(t=!0){const{x:n,y:e}=this.aff();return(t?0n===(1n&e)?"02":"03":"04")+n2h(n)+(t?"":n2h(e))}toRawBytes(t=!0){return h2b(this.toHex(t))}}Point.BASE=new Point(Gx,Gy,1n),Point.ZERO=new Point(0n,1n,0n);const{BASE:G,ZERO:I}=Point,padh=(t,n)=>t.toString(16).padStart(n,"0"),b2h=t=>Array.from(t).map((t=>padh(t,2))).join(""),h2b=t=>{const n=t.length;(!str(t)||n%2)&&err("hex invalid 1");const e=u8n(n/2);for(let n=0;nBigInt("0x"+(b2h(t)||"0")),slcNum=(t,n,e)=>b2n(t.slice(n,e)),n2b=t=>big(t)&&t>=0n&&tb2h(n2b(t)),concatB=(...t)=>{const n=u8n(t.reduce(((t,n)=>t+au8(n).length),0));let e=0;return t.forEach((t=>{n.set(t,e),e+=t.length})),n},inv=(t,n=P)=>{(0n===t||n<=0n)&&err("no inverse n="+t+" mod="+n);let e=mod(t,n),r=n,o=0n,i=1n,s=1n,a=0n;for(;0n!==e;){const t=r/e,n=r%e,c=o-s*t,d=i-a*t;r=e,e=n,o=s,i=a,s=c,a=d}return 1n===r?mod(o,n):err("no inverse")},sqrt=t=>{let n=1n;for(let e=t,r=(P+1n)/4n;r>0n;r>>=1n)1n&r&&(n=n*e%P),e=e*e%P;return mod(n*n)===t?n:err("sqrt invalid")},toPriv=t=>(big(t)||(t=b2n(toU8(t,32))),ge(t)?t:err("private key out of range")),moreThanHalfN=t=>t>N>>1n,getPublicKey=(t,n=!0)=>Point.fromPrivateKey(t).toRawBytes(n);class Signature{constructor(t,n,e){this.r=t,this.s=n,this.recovery=e,this.assertValidity()}static fromCompact(t){return t=toU8(t,64),new Signature(slcNum(t,0,32),slcNum(t,32,64))}assertValidity(){return ge(this.r)&&ge(this.s)?this:err()}addRecoveryBit(t){return new Signature(this.r,this.s,t)}hasHighS(){return moreThanHalfN(this.s)}normalizeS(){return this.hasHighS()?new Signature(this.r,mod(this.s,N),this.recovery):this}recoverPublicKey(t){const{r:n,s:e,recovery:r}=this;[0,1,2,3].includes(r)||err("recovery id invalid");const o=bits2int_modN(toU8(t,32)),i=2===r||3===r?n+N:n;i>=P&&err("q.x invalid");const s=1&r?"03":"02",a=Point.fromHex(s+n2h(i)),c=inv(i,N),d=mod(-o*c,N),u=mod(e*c,N);return G.mulAddQUns(a,d,u)}toCompactRawBytes(){return h2b(this.toCompactHex())}toCompactHex(){return n2h(this.r)+n2h(this.s)}}const bits2int=t=>{const n=8*t.length-256,e=b2n(t);return n>0?e>>BigInt(n):e},bits2int_modN=t=>mod(bits2int(t),N),i2o=t=>n2b(t),cr=()=>"object"==typeof globalThis&&"crypto"in globalThis?globalThis.crypto:void 0;let _hmacSync;const optS={lowS:!0},optV={lowS:!0},prepSig=(t,n,e=optS)=>{["der","recovered","canonical"].some((t=>t in e))&&err("sign() legacy options not supported");let{lowS:r}=e;null==r&&(r=!0);const o=bits2int_modN(toU8(t)),i=i2o(o),s=toPriv(n),a=[i2o(s),i];let c=e.extraEntropy;if(c){!0===c&&(c=etc.randomBytes(32));const t=toU8(c);32!==t.length&&err(),a.push(t)}const d=o;return{seed:concatB(...a),k2sig:t=>{const n=bits2int(t);if(!ge(n))return;const e=inv(n,N),o=G.mul(n).aff(),i=mod(o.x,N);if(0n===i)return;const a=mod(e*mod(d+mod(s*i,N),N),N);if(0n===a)return;let c=a,u=(o.x===i?0:2)|Number(1n&o.y);return r&&moreThanHalfN(a)&&(c=mod(-a,N),u^=1),new Signature(i,c,u)}}};function hmacDrbg(t){let n=u8n(32),e=u8n(32),r=0;const o=()=>{n.fill(1),e.fill(0),r=0},i="drbg: tried 1000 values";if(t){const t=(...t)=>etc.hmacSha256Async(e,n,...t),s=async(r=u8n())=>{e=await t(u8n([0]),r),n=await t(),0!==r.length&&(e=await t(u8n([1]),r),n=await t())},a=async()=>(r++>=1e3&&err(i),n=await t(),n);return async(t,n)=>{let e;for(o(),await s(t);!(e=n(await a()));)await s();return o(),e}}{const t=(...t)=>{const r=_hmacSync;return r||err("etc.hmacSha256Sync not set"),r(e,n,...t)},s=(r=u8n())=>{e=t(u8n([0]),r),n=t(),0!==r.length&&(e=t(u8n([1]),r),n=t())},a=()=>(r++>=1e3&&err(i),n=t(),n);return(t,n)=>{let e;for(o(),s(t);!(e=n(a()));)s();return o(),e}}}const signAsync=async(t,n,e=optS)=>{const{seed:r,k2sig:o}=prepSig(t,n,e);return hmacDrbg(!0)(r,o)},sign=(t,n,e=optS)=>{const{seed:r,k2sig:o}=prepSig(t,n,e);return hmacDrbg(!1)(r,o)},verify=(t,n,e,r=optV)=>{let o,i,s,{lowS:a}=r;null==a&&(a=!0),"strict"in r&&err("verify() legacy options not supported");const c=t&&"object"==typeof t&&"r"in t;c||64===toU8(t).length||err("signature must be 64 bytes");try{o=c?new Signature(t.r,t.s).assertValidity():Signature.fromCompact(t),i=bits2int_modN(toU8(n)),s=e instanceof Point?e.ok():Point.fromHex(e)}catch(t){return!1}if(!o)return!1;const{r:d,s:u}=o;if(a&&moreThanHalfN(u))return!1;let m;try{const t=inv(u,N),n=mod(i*t,N),e=mod(d*t,N);m=G.mulAddQUns(s,n,e).aff()}catch(t){return!1}if(!m)return!1;return mod(m.x,N)===d},getSharedSecret=(t,n,e=!0)=>Point.fromHex(n).mul(toPriv(t)).toRawBytes(e),hashToPrivateKey=t=>{((t=toU8(t)).length<40||t.length>1024)&&err("expected proper params");const n=mod(b2n(t),N-1n)+1n;return n2b(n)},etc={hexToBytes:h2b,bytesToHex:b2h,concatBytes:concatB,bytesToNumberBE:b2n,numberToBytesBE:n2b,mod,invert:inv,hmacSha256Async:async(t,...n)=>{const e=cr(),r=e&&e.subtle;if(!r)return err("etc.hmacSha256Async not set");const o=await r.importKey("raw",t,{name:"HMAC",hash:{name:"SHA-256"}},!1,["sign"]);return u8n(await r.sign("HMAC",o,concatB(...n)))},hmacSha256Sync:_hmacSync,hashToPrivateKey,randomBytes:(t=32)=>{const n=cr();return n&&n.getRandomValues||err("crypto.getRandomValues must be defined"),n.getRandomValues(u8n(t))}},utils={normPrivateKeyToScalar:toPriv,isValidPrivateKey:t=>{try{return!!toPriv(t)}catch(t){return!1}},randomPrivateKey:()=>hashToPrivateKey(etc.randomBytes(48)),precompute:(t=8,n=G)=>(n.multiply(3n),n)};Object.defineProperties(etc,{hmacSha256Sync:{configurable:!1,get:()=>_hmacSync,set(t){_hmacSync||(_hmacSync=t)}}});const W=8,precompute=()=>{const t=[];let n=G,e=n;for(let r=0;r<33;r++){e=n,t.push(e);for(let r=1;r<128;r++)e=e.add(n),t.push(e);n=e.double()}return t};let Gpows;const wNAF=t=>{const n=Gpows||(Gpows=precompute()),e=(t,n)=>{let e=n.negate();return t?e:n};let r=I,o=G;const i=BigInt(255),s=BigInt(8);for(let a=0;a<33;a++){const c=128*a;let d=Number(t&i);t>>=s,d>128&&(d-=256,t+=1n);const u=c,m=c+Math.abs(d)-1,h=a%2!=0,l=d<0;0===d?o=o.add(e(h,n[u])):r=r.add(e(l,n[m]))}return{p:r,f:o}};export{getPublicKey,sign,signAsync,verify,CURVE,getSharedSecret,etc,utils,Point as ProjectivePoint,Signature}; +const B256 = 2n ** 256n, + P = B256 - 0x1000003d1n, + N = B256 - 0x14551231950b75fc4402da1732fc9bebfn, + Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n, + Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n, + CURVE = { p: P, n: N, a: 0n, b: 7n, Gx, Gy }, + fLen = 32, + crv = (t) => mod(mod(t * t) * t + CURVE.b), + err = (t = "") => { + throw new Error(t); + }, + big = (t) => "bigint" == typeof t, + str = (t) => "string" == typeof t, + fe = (t) => big(t) && 0n < t && t < P, + ge = (t) => big(t) && 0n < t && t < N, + isu8 = (t) => + t instanceof Uint8Array || + (null != t && "object" == typeof t && "Uint8Array" === t.constructor.name), + au8 = (t, n) => + !isu8(t) || ("number" == typeof n && n > 0 && t.length !== n) + ? err("Uint8Array expected") + : t, + u8n = (t) => new Uint8Array(t), + toU8 = (t, n) => au8(str(t) ? h2b(t) : u8n(au8(t)), n), + mod = (t, n = P) => { + let e = t % n; + return e >= 0n ? e : n + e; + }, + isPoint = (t) => (t instanceof Point ? t : err("Point expected")); +class Point { + constructor(t, n, e) { + (this.px = t), (this.py = n), (this.pz = e); + } + static fromAffine(t) { + return 0n === t.x && 0n === t.y ? Point.ZERO : new Point(t.x, t.y, 1n); + } + static fromHex(t) { + let n; + const e = (t = toU8(t))[0], + r = t.subarray(1), + o = slcNum(r, 0, 32), + i = t.length; + if (33 === i && [2, 3].includes(e)) { + fe(o) || err("Point hex invalid: x not FE"); + let t = sqrt(crv(o)); + !(1 & ~e) !== (1n === (1n & t)) && (t = mod(-t)), + (n = new Point(o, t, 1n)); + } + return ( + 65 === i && 4 === e && (n = new Point(o, slcNum(r, 32, 64), 1n)), + n ? n.ok() : err("Point is not on curve") + ); + } + static fromPrivateKey(t) { + return G.mul(toPriv(t)); + } + get x() { + return this.aff().x; + } + get y() { + return this.aff().y; + } + equals(t) { + const { px: n, py: e, pz: r } = this, + { px: o, py: i, pz: s } = isPoint(t), + a = mod(n * s), + c = mod(o * r), + d = mod(e * s), + u = mod(i * r); + return a === c && d === u; + } + negate() { + return new Point(this.px, mod(-this.py), this.pz); + } + double() { + return this.add(this); + } + add(t) { + const { px: n, py: e, pz: r } = this, + { px: o, py: i, pz: s } = isPoint(t), + { a, b: c } = CURVE; + let d = 0n, + u = 0n, + m = 0n; + const h = mod(3n * c); + let l = mod(n * o), + y = mod(e * i), + f = mod(r * s), + p = mod(n + e), + b = mod(o + i); + (p = mod(p * b)), (b = mod(l + y)), (p = mod(p - b)), (b = mod(n + r)); + let g = mod(o + s); + return ( + (b = mod(b * g)), + (g = mod(l + f)), + (b = mod(b - g)), + (g = mod(e + r)), + (d = mod(i + s)), + (g = mod(g * d)), + (d = mod(y + f)), + (g = mod(g - d)), + (m = mod(a * b)), + (d = mod(h * f)), + (m = mod(d + m)), + (d = mod(y - m)), + (m = mod(y + m)), + (u = mod(d * m)), + (y = mod(l + l)), + (y = mod(y + l)), + (f = mod(a * f)), + (b = mod(h * b)), + (y = mod(y + f)), + (f = mod(l - f)), + (f = mod(a * f)), + (b = mod(b + f)), + (l = mod(y * b)), + (u = mod(u + l)), + (l = mod(g * b)), + (d = mod(p * d)), + (d = mod(d - l)), + (l = mod(p * y)), + (m = mod(g * m)), + (m = mod(m + l)), + new Point(d, u, m) + ); + } + mul(t, n = !0) { + if (!n && 0n === t) return I; + if ((ge(t) || err("invalid scalar"), this.equals(G))) return wNAF(t).p; + let e = I, + r = G; + for (let o = this; t > 0n; o = o.double(), t >>= 1n) + 1n & t ? (e = e.add(o)) : n && (r = r.add(o)); + return e; + } + mulAddQUns(t, n, e) { + return this.mul(n, !1).add(t.mul(e, !1)).ok(); + } + toAffine() { + const { px: t, py: n, pz: e } = this; + if (this.equals(I)) return { x: 0n, y: 0n }; + if (1n === e) return { x: t, y: n }; + const r = inv(e); + return ( + 1n !== mod(e * r) && err("invalid inverse"), + { x: mod(t * r), y: mod(n * r) } + ); + } + assertValidity() { + const { x: t, y: n } = this.aff(); + return ( + (fe(t) && fe(n)) || err("Point invalid: x or y"), + mod(n * n) === crv(t) ? this : err("Point invalid: not on curve") + ); + } + multiply(t) { + return this.mul(t); + } + aff() { + return this.toAffine(); + } + ok() { + return this.assertValidity(); + } + toHex(t = !0) { + const { x: n, y: e } = this.aff(); + return ( + (t ? (0n === (1n & e) ? "02" : "03") : "04") + n2h(n) + (t ? "" : n2h(e)) + ); + } + toRawBytes(t = !0) { + return h2b(this.toHex(t)); + } +} +(Point.BASE = new Point(Gx, Gy, 1n)), (Point.ZERO = new Point(0n, 1n, 0n)); +const { BASE: G, ZERO: I } = Point, + padh = (t, n) => t.toString(16).padStart(n, "0"), + b2h = (t) => + Array.from(t) + .map((t) => padh(t, 2)) + .join(""), + h2b = (t) => { + const n = t.length; + (!str(t) || n % 2) && err("hex invalid 1"); + const e = u8n(n / 2); + for (let n = 0; n < e.length; n++) { + const r = 2 * n, + o = t.slice(r, r + 2), + i = Number.parseInt(o, 16); + (Number.isNaN(i) || i < 0) && err("hex invalid 2"), (e[n] = i); + } + return e; + }, + b2n = (t) => BigInt("0x" + (b2h(t) || "0")), + slcNum = (t, n, e) => b2n(t.slice(n, e)), + n2b = (t) => + big(t) && t >= 0n && t < B256 ? h2b(padh(t, 64)) : err("bigint expected"), + n2h = (t) => b2h(n2b(t)), + concatB = (...t) => { + const n = u8n(t.reduce((t, n) => t + au8(n).length, 0)); + let e = 0; + return ( + t.forEach((t) => { + n.set(t, e), (e += t.length); + }), + n + ); + }, + inv = (t, n = P) => { + (0n === t || n <= 0n) && err("no inverse n=" + t + " mod=" + n); + let e = mod(t, n), + r = n, + o = 0n, + i = 1n, + s = 1n, + a = 0n; + for (; 0n !== e; ) { + const t = r / e, + n = r % e, + c = o - s * t, + d = i - a * t; + (r = e), (e = n), (o = s), (i = a), (s = c), (a = d); + } + return 1n === r ? mod(o, n) : err("no inverse"); + }, + sqrt = (t) => { + let n = 1n; + for (let e = t, r = (P + 1n) / 4n; r > 0n; r >>= 1n) + 1n & r && (n = (n * e) % P), (e = (e * e) % P); + return mod(n * n) === t ? n : err("sqrt invalid"); + }, + toPriv = (t) => ( + big(t) || (t = b2n(toU8(t, 32))), + ge(t) ? t : err("private key out of range") + ), + moreThanHalfN = (t) => t > N >> 1n, + getPublicKey = (t, n = !0) => Point.fromPrivateKey(t).toRawBytes(n); +class Signature { + constructor(t, n, e) { + (this.r = t), (this.s = n), (this.recovery = e), this.assertValidity(); + } + static fromCompact(t) { + return ( + (t = toU8(t, 64)), new Signature(slcNum(t, 0, 32), slcNum(t, 32, 64)) + ); + } + assertValidity() { + return ge(this.r) && ge(this.s) ? this : err(); + } + addRecoveryBit(t) { + return new Signature(this.r, this.s, t); + } + hasHighS() { + return moreThanHalfN(this.s); + } + normalizeS() { + return this.hasHighS() + ? new Signature(this.r, mod(this.s, N), this.recovery) + : this; + } + recoverPublicKey(t) { + const { r: n, s: e, recovery: r } = this; + [0, 1, 2, 3].includes(r) || err("recovery id invalid"); + const o = bits2int_modN(toU8(t, 32)), + i = 2 === r || 3 === r ? n + N : n; + i >= P && err("q.x invalid"); + const s = 1 & r ? "03" : "02", + a = Point.fromHex(s + n2h(i)), + c = inv(i, N), + d = mod(-o * c, N), + u = mod(e * c, N); + return G.mulAddQUns(a, d, u); + } + toCompactRawBytes() { + return h2b(this.toCompactHex()); + } + toCompactHex() { + return n2h(this.r) + n2h(this.s); + } +} +const bits2int = (t) => { + const n = 8 * t.length - 256, + e = b2n(t); + return n > 0 ? e >> BigInt(n) : e; + }, + bits2int_modN = (t) => mod(bits2int(t), N), + i2o = (t) => n2b(t), + cr = () => + "object" == typeof globalThis && "crypto" in globalThis + ? globalThis.crypto + : void 0; +let _hmacSync; +const optS = { lowS: !0 }, + optV = { lowS: !0 }, + prepSig = (t, n, e = optS) => { + ["der", "recovered", "canonical"].some((t) => t in e) && + err("sign() legacy options not supported"); + let { lowS: r } = e; + null == r && (r = !0); + const o = bits2int_modN(toU8(t)), + i = i2o(o), + s = toPriv(n), + a = [i2o(s), i]; + let c = e.extraEntropy; + if (c) { + !0 === c && (c = etc.randomBytes(32)); + const t = toU8(c); + 32 !== t.length && err(), a.push(t); + } + const d = o; + return { + seed: concatB(...a), + k2sig: (t) => { + const n = bits2int(t); + if (!ge(n)) return; + const e = inv(n, N), + o = G.mul(n).aff(), + i = mod(o.x, N); + if (0n === i) return; + const a = mod(e * mod(d + mod(s * i, N), N), N); + if (0n === a) return; + let c = a, + u = (o.x === i ? 0 : 2) | Number(1n & o.y); + return ( + r && moreThanHalfN(a) && ((c = mod(-a, N)), (u ^= 1)), + new Signature(i, c, u) + ); + }, + }; + }; +function hmacDrbg(t) { + let n = u8n(32), + e = u8n(32), + r = 0; + const o = () => { + n.fill(1), e.fill(0), (r = 0); + }, + i = "drbg: tried 1000 values"; + if (t) { + const t = (...t) => etc.hmacSha256Async(e, n, ...t), + s = async (r = u8n()) => { + (e = await t(u8n([0]), r)), + (n = await t()), + 0 !== r.length && ((e = await t(u8n([1]), r)), (n = await t())); + }, + a = async () => (r++ >= 1e3 && err(i), (n = await t()), n); + return async (t, n) => { + let e; + for (o(), await s(t); !(e = n(await a())); ) await s(); + return o(), e; + }; + } + { + const t = (...t) => { + const r = _hmacSync; + return r || err("etc.hmacSha256Sync not set"), r(e, n, ...t); + }, + s = (r = u8n()) => { + (e = t(u8n([0]), r)), + (n = t()), + 0 !== r.length && ((e = t(u8n([1]), r)), (n = t())); + }, + a = () => (r++ >= 1e3 && err(i), (n = t()), n); + return (t, n) => { + let e; + for (o(), s(t); !(e = n(a())); ) s(); + return o(), e; + }; + } +} +const signAsync = async (t, n, e = optS) => { + const { seed: r, k2sig: o } = prepSig(t, n, e); + return hmacDrbg(!0)(r, o); + }, + sign = (t, n, e = optS) => { + const { seed: r, k2sig: o } = prepSig(t, n, e); + return hmacDrbg(!1)(r, o); + }, + verify = (t, n, e, r = optV) => { + let o, + i, + s, + { lowS: a } = r; + null == a && (a = !0), + "strict" in r && err("verify() legacy options not supported"); + const c = t && "object" == typeof t && "r" in t; + c || 64 === toU8(t).length || err("signature must be 64 bytes"); + try { + (o = c + ? new Signature(t.r, t.s).assertValidity() + : Signature.fromCompact(t)), + (i = bits2int_modN(toU8(n))), + (s = e instanceof Point ? e.ok() : Point.fromHex(e)); + } catch (t) { + return !1; + } + if (!o) return !1; + const { r: d, s: u } = o; + if (a && moreThanHalfN(u)) return !1; + let m; + try { + const t = inv(u, N), + n = mod(i * t, N), + e = mod(d * t, N); + m = G.mulAddQUns(s, n, e).aff(); + } catch (t) { + return !1; + } + if (!m) return !1; + return mod(m.x, N) === d; + }, + getSharedSecret = (t, n, e = !0) => + Point.fromHex(n).mul(toPriv(t)).toRawBytes(e), + hashToPrivateKey = (t) => { + ((t = toU8(t)).length < 40 || t.length > 1024) && + err("expected proper params"); + const n = mod(b2n(t), N - 1n) + 1n; + return n2b(n); + }, + etc = { + hexToBytes: h2b, + bytesToHex: b2h, + concatBytes: concatB, + bytesToNumberBE: b2n, + numberToBytesBE: n2b, + mod, + invert: inv, + hmacSha256Async: async (t, ...n) => { + const e = cr(), + r = e && e.subtle; + if (!r) return err("etc.hmacSha256Async not set"); + const o = await r.importKey( + "raw", + t, + { name: "HMAC", hash: { name: "SHA-256" } }, + !1, + ["sign"], + ); + return u8n(await r.sign("HMAC", o, concatB(...n))); + }, + hmacSha256Sync: _hmacSync, + hashToPrivateKey, + randomBytes: (t = 32) => { + const n = cr(); + return ( + (n && n.getRandomValues) || + err("crypto.getRandomValues must be defined"), + n.getRandomValues(u8n(t)) + ); + }, + }, + utils = { + normPrivateKeyToScalar: toPriv, + isValidPrivateKey: (t) => { + try { + return !!toPriv(t); + } catch (t) { + return !1; + } + }, + randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(48)), + precompute: (t = 8, n = G) => (n.multiply(3n), n), + }; +Object.defineProperties(etc, { + hmacSha256Sync: { + configurable: !1, + get: () => _hmacSync, + set(t) { + _hmacSync || (_hmacSync = t); + }, + }, +}); +const W = 8, + precompute = () => { + const t = []; + let n = G, + e = n; + for (let r = 0; r < 33; r++) { + (e = n), t.push(e); + for (let r = 1; r < 128; r++) (e = e.add(n)), t.push(e); + n = e.double(); + } + return t; + }; +let Gpows; +const wNAF = (t) => { + const n = Gpows || (Gpows = precompute()), + e = (t, n) => { + let e = n.negate(); + return t ? e : n; + }; + let r = I, + o = G; + const i = BigInt(255), + s = BigInt(8); + for (let a = 0; a < 33; a++) { + const c = 128 * a; + let d = Number(t & i); + (t >>= s), d > 128 && ((d -= 256), (t += 1n)); + const u = c, + m = c + Math.abs(d) - 1, + h = a % 2 != 0, + l = d < 0; + 0 === d ? (o = o.add(e(h, n[u]))) : (r = r.add(e(l, n[m]))); + } + return { p: r, f: o }; +}; +export { + getPublicKey, + sign, + signAsync, + verify, + CURVE, + getSharedSecret, + etc, + utils, + Point as ProjectivePoint, + Signature, +}; diff --git a/tasks.py b/tasks.py index a5a241d..46f9765 100644 --- a/tasks.py +++ b/tasks.py @@ -1,112 +1,131 @@ import asyncio +import time +from math import ceil +from typing import Any, Dict, List, Optional, Tuple +from bolt11 import decode as bolt11_decode +from lnbits.core.crud import get_payments, get_wallet, get_wallet_payment +from lnbits.core.models import Payment +from lnbits.core.services import ( + check_transaction_status, + create_invoice, + pay_invoice, +) +from lnbits.db import Filters +from lnbits.exceptions import PaymentError +from lnbits.settings import settings +from lnbits.wallets.base import PaymentStatus from loguru import logger -from lnbits.core.models import Payment -from lnbits.core.services import create_invoice, pay_invoice, check_transaction_status, PaymentError -from bolt11 import decode as bolt11_decode -from lnbits.core.crud import get_wallet_payment, get_payments, get_wallet -from .crud import get_nwc, tracked_spend_nwc, get_config_nwc -#from .crud import log_nwc -from . import execution_queue -from .NWCServiceProvider import NWCServiceProvider -from typing import Dict, List, Tuple -from math import ceil -import time -from lnbits.db import Filters -from .models import NWCKey -from typing import Optional -from .permission import nwc_permissions -from lnbits.settings import settings +from .crud import get_config_nwc, get_nwc, tracked_spend_nwc -async def _check(nwc: Optional[NWCKey], method: str, payload: Dict): +# from .crud import log_nwc +from .execution_queue import execution_queue +from .models import NWCKey +from .nwcp import NWCServiceProvider +from .permission import nwc_permissions + + +async def _check(nwc: Optional[NWCKey], method: str, payload: Dict) -> Optional[Dict]: # check if not nwc: - return { + return { "code": "UNAUTHORIZED", - "message": "This public key has no wallet connected." + "message": "This public key has no wallet connected.", } # check permissions allowed = False - permissions = nwc.getPermissions() + permissions = nwc.get_permissions() for p in permissions: - allowed_methods = nwc_permissions.get(p, {}).get("methods", []) + permissions_data: Dict[str, Any] = nwc_permissions.get(p, {}) + allowed_methods: List[str] = permissions_data.get("methods", []) if method in allowed_methods: allowed = True break if not allowed: return { "code": "RESTRICTED", - "message": "This public key is not allowed to do this operation." + "message": "This public key is not allowed to do this operation.", } return None - -async def _process_invoice(wallet_id:str, pubkey:str, invoice:str, amount_msats:int, description:Optional[str]=None): +async def _process_invoice( + wallet_id: str, + pubkey: str, + invoice: str, + amount_msats: int, + description: Optional[str] = None, +): async def execute_payment(): - return await pay_invoice(wallet_id=wallet_id, payment_request=invoice, - max_sat=int(ceil(amount_msats/1000)), - description=description or "" - ) + return await pay_invoice( + wallet_id=wallet_id, + payment_request=invoice, + max_sat=int(ceil(amount_msats / 1000)), + description=description or "", + ) + payment_hash = None try: - in_budget, payment_hash = await tracked_spend_nwc(pubkey, amount_msats, execute_payment) + in_budget, payment_hash = await tracked_spend_nwc( + pubkey, amount_msats, execute_payment + ) if not in_budget: error = { - "code":"QUOTA_EXCEEDED", - "message": "The wallet has exceeded its spending quota." - } - return { - "error": error, - "in_budget": False + "code": "QUOTA_EXCEEDED", + "message": "The wallet has exceeded its spending quota.", } + return {"error": error, "in_budget": False} except PaymentError as e: - status = e.status + status = e.status message = e.message if status == "failed": - error = { - "code": "PAYMENT_FAILED", - "message": message - } - return { - "error": error, - "in_budget": False - } + error = {"code": "PAYMENT_FAILED", "message": message} + return {"error": error, "in_budget": False} else: raise e if not payment_hash: raise Exception("Payment hash not found") - wait_for_preimage = True # currently required by nip 47 specs, might change in future - payment_status = None + wait_for_preimage = ( + True # currently required by nip 47 specs, might change in future + ) + payment_status: Optional[PaymentStatus] = None while wait_for_preimage: payment_status = await check_transaction_status(wallet_id, payment_hash) if payment_status.success: break await asyncio.sleep(0.05) + if not payment_status: + raise Exception("Payment status not found") return { - "preimage": payment_status.preimage or "0000000000000000000000000000000000000000000000000000000000000000", + "preimage": payment_status.preimage + or "0000000000000000000000000000000000000000000000000000000000000000", "fee_msats": payment_status.fee_msat, "paid": payment_status.paid, "payment_hash": payment_hash, - "in_budget": in_budget - + "in_budget": in_budget, } -async def _on_pay_invoice(sp: NWCServiceProvider, pubkey: str, payload: Dict) -> List[Tuple[Dict, Dict, List]]: +async def _on_pay_invoice( + sp: NWCServiceProvider, pubkey: str, payload: Dict +) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: nwc = await get_nwc(pubkey, None, False, True) error = await _check(nwc, "pay_invoice", payload) - if error: - return [(None,error, [])] + if error: + return [(None, error, [])] + if not nwc: + raise Exception("Pubkey has no associated wallet") params = payload.get("params", {}) invoice = params.get("invoice", None) # Ensures invoice is provided - if not invoice: + if not invoice: raise Exception("Missing invoice") invoice_data = bolt11_decode(invoice) - amount_msats = invoice_data.amount_msat - res = await _process_invoice(nwc.wallet, pubkey, invoice, amount_msats, invoice_data.description) + amount_msats = int(invoice_data.amount_msat or 0) + res = await _process_invoice( + nwc.wallet, pubkey, invoice, amount_msats, invoice_data.description + ) error = res.get("error") if error: return [(None, error, [])] @@ -114,103 +133,117 @@ async def _on_pay_invoice(sp: NWCServiceProvider, pubkey: str, payload: Dict) -> out = { "preimage": preimage, } - #await log_nwc(pubkey, payload) - return [(out,None, [])] + # await log_nwc(pubkey, payload) + return [(out, None, [])] -async def _on_multi_pay_invoice(sp: NWCServiceProvider, pubkey: str, payload: Dict) -> List[Tuple[Dict, Dict, List]]: +async def _on_multi_pay_invoice( + sp: NWCServiceProvider, pubkey: str, payload: Dict +) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: nwc = await get_nwc(pubkey, None, False, True) error = await _check(nwc, "multi_pay_invoice", payload) if error: return [(None, error, [])] + if not nwc: + raise Exception("Pubkey has no associated wallet") params = payload.get("params", {}) invoices = params.get("invoices", []) - results = [] - + results: List[Tuple[Optional[Dict], Optional[Dict], List]] = [] + # Ensures all invoices are provided for i in invoices: - invoice = i.get("invoice",None) + invoice = i.get("invoice", None) if not invoice: raise Exception("Missing invoice") for i in invoices: try: - id = i.get("id", None) + invoice_id = i.get("id", None) invoice = i.get("invoice", None) invoice_data = bolt11_decode(invoice) - amount_msats = invoice_data.amount_msat - res = await _process_invoice(nwc.wallet, pubkey, invoice, amount_msats, invoice_data.description) + amount_msats = int(invoice_data.amount_msat or 0) + res = await _process_invoice( + nwc.wallet, pubkey, invoice, amount_msats, invoice_data.description + ) error = res.get("error") if error: results.append((None, error, [])) else: r = ( - { - "preimage": res.get("preimage"), - }, - None, - [ - ["d", id if id else res.get("payment_hash")] - ] + { + "preimage": res.get("preimage"), + }, + None, + [["d", invoice_id if invoice_id else res.get("payment_hash")]], ) results.append(r) except Exception as e: - results.append((None, { - "code": "INTERNAL", - "message": str(e) - })) - #await log_nwc(pubkey, payload) + results.append((None, {"code": "INTERNAL", "message": str(e)}, [])) + # await log_nwc(pubkey, payload) return results -async def _on_make_invoice(sp: NWCServiceProvider, pubkey: str, payload: Dict) -> List[Tuple[Dict, Dict, List]]: +async def _on_make_invoice( + sp: NWCServiceProvider, pubkey: str, payload: Dict +) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: nwc = await get_nwc(pubkey, None, False, True) error = await _check(nwc, "make_invoice", payload) if error: return [(None, error, [])] + if not nwc: + raise Exception("Pubkey has no associated wallet") params = payload.get("params", {}) amount_msats = params.get("amount", None) # Ensures amount is provided - if not amount_msats: + if not amount_msats: raise Exception("Missing amount") description = params.get("description", "") description_hash = params.get("description_hash", None) - expiry = params.get("expiry" , None) + expiry = params.get("expiry", None) payment_hash, payment_request = await create_invoice( - wallet_id=nwc.wallet, - amount=int(amount_msats/1000), - currency="sat", + wallet_id=nwc.wallet, + amount=int(amount_msats / 1000), + currency="sat", memo=description, description_hash=bytes.fromhex(description_hash) if description_hash else None, unhashed_description=description.encode("utf-8"), - expiry=expiry) - payment_status = await check_transaction_status(wallet_id=nwc.wallet, payment_hash=payment_hash) - preimage = payment_status.preimage - if not preimage: # Some backend do not return a preimage (eg. FakeWallet), so we fake it + expiry=expiry, + ) + payment_status = await check_transaction_status( + wallet_id=nwc.wallet, payment_hash=payment_hash + ) + preimage = payment_status.preimage + if ( + not preimage + ): # Some backend do not return a preimage (eg. FakeWallet), so we fake it preimage = "0000000000000000000000000000000000000000000000000000000000000000" res = { "type": "incoming", "invoice": payment_request, "description": description, - "description_hash": description_hash, + "description_hash": description_hash, "preimage": preimage, "payment_hash": payment_hash, "amount": amount_msats, - #"fees_paid":None, + # "fees_paid":None, "created_at": int(time.time()), - "metadata": {} + "metadata": {}, } if expiry: res["expires_at"] = int(time.time()) + int(expiry) - #await log_nwc(pubkey, payload) + # await log_nwc(pubkey, payload) return [(res, None, [])] -async def _on_lookup_invoice(sp: NWCServiceProvider, pubkey: str, payload: Dict) -> List[Tuple[Dict, Dict, List]]: +async def _on_lookup_invoice( + sp: NWCServiceProvider, pubkey: str, payload: Dict +) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: nwc = await get_nwc(pubkey, None, False, True) error = await _check(nwc, "lookup_invoice", payload) if error: return [(None, error, [])] + if not nwc: + raise Exception("Pubkey has no associated wallet") params = payload.get("params", {}) payment_hash = params.get("payment_hash", None) invoice = params.get("invoice", None) @@ -228,131 +261,149 @@ async def _on_lookup_invoice(sp: NWCServiceProvider, pubkey: str, payload: Dict) invoice_data = bolt11_decode(payment.bolt11) is_settled = not payment.pending timestamp = payment.time or invoice_data.date - res = { + res: Dict = { "type": "outgoing" if payment.is_out else "incoming", "invoice": payment.bolt11, - "description": invoice_data.description if invoice_data.description else payment.memo, + "description": ( + invoice_data.description if invoice_data.description else payment.memo + ), "preimage": payment.preimage if is_settled or payment.is_in else None, "payment_hash": payment.payment_hash, "amount": abs(payment.msat), "fees_paid": abs(payment.fee), "created_at": timestamp, - "expires_at": payment.expiry if payment.expiry else timestamp+3600, + "expires_at": payment.expiry if payment.expiry else timestamp + 3600, "settled_at": timestamp if is_settled else None, - "metadata": {} + "metadata": {}, } if invoice_data.description_hash: res["description_hash"] = invoice_data.description_hash - #await log_nwc(pubkey, payload) + # await log_nwc(pubkey, payload) return [(res, None, [])] -async def _on_list_transactions(sp: NWCServiceProvider, pubkey: str, payload: Dict) -> List[Tuple[Dict, Dict, List]]: +async def _on_list_transactions( + sp: NWCServiceProvider, pubkey: str, payload: Dict +) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: nwc = await get_nwc(pubkey, None, False, True) error = await _check(nwc, "list_transactions", payload) if error: return [(None, error, [])] + if not nwc: + raise Exception("Pubkey has no associated wallet") tfrom = payload.get("from", 0) tto = payload.get("to", int(time.time())) limit = payload.get("limit", 10) offset = payload.get("offset", 0) unpaid = payload.get("unpaid", False) - type = payload.get("type", None) + tx_type = payload.get("type", None) values = [] - filters = Filters() - filters.where("time <= ?") + filters: Filters = Filters() + filters.where(["time <= ?"]) values.append(tto) filters.values(values) history = await get_payments( wallet_id=nwc.wallet, - complete=True, - pending = unpaid, - outgoing=not type or type == "outgoing", - incoming =not type or type=="incoming", + complete=True, + pending=unpaid, + outgoing=not tx_type or tx_type == "outgoing", + incoming=not tx_type or tx_type == "incoming", since=tfrom, exclude_uncheckable=False, filters=filters, limit=limit, - offset=offset + offset=offset, ) - transactions = [] + transactions: List[Dict] = [] + p: Payment for p in history: - p: Payment invoice_data = bolt11_decode(p.bolt11) is_settled = not p.pending - transactions.append({ - "type": "outgoing" if p.is_out else "incoming", - "invoice": p.bolt11, - "description": invoice_data.description, - "description_hash": invoice_data.description_hash, - "preimage": p.preimage if is_settled or p.is_in else None, - "payment_hash": p.payment_hash, - "amount": abs(p.msat), - "fees_paid": p.fee, - "created_at": p.time, - "settled_at": p.time if is_settled else None, - "metadata": {} - }) - #await log_nwc(pubkey, payload) - return [({ - "transactions": transactions - }, None, [])] + transactions.append( + { + "type": "outgoing" if p.is_out else "incoming", + "invoice": p.bolt11, + "description": invoice_data.description, + "description_hash": invoice_data.description_hash, + "preimage": p.preimage if is_settled or p.is_in else None, + "payment_hash": p.payment_hash, + "amount": abs(p.msat), + "fees_paid": p.fee, + "created_at": p.time, + "settled_at": p.time if is_settled else None, + "metadata": {}, + } + ) + # await log_nwc(pubkey, payload) + return [({"transactions": transactions}, None, [])] -async def _on_get_balance(sp: NWCServiceProvider, pubkey: str, payload: Dict) -> List[Tuple[Dict, Dict, List]]: +async def _on_get_balance( + sp: NWCServiceProvider, pubkey: str, payload: Dict +) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: nwc = await get_nwc(pubkey, None, False, True) error = await _check(nwc, "get_balance", payload) if error: return [(None, error, [])] + if not nwc: + raise Exception("Pubkey has no associated wallet") balance = 0 wallet = await get_wallet(nwc.wallet) if not wallet: raise Exception("Wallet not found") balance = wallet.balance_msat - #await log_nwc(pubkey, payload) - return [({ - "balance": balance - }, None, [])] + # await log_nwc(pubkey, payload) + return [({"balance": balance}, None, [])] -async def _on_get_info(sp: NWCServiceProvider, pubkey: str, payload: Dict) -> List[Tuple[Dict, Dict, List]]: +async def _on_get_info( + sp: NWCServiceProvider, pubkey: str, payload: Dict +) -> List[Tuple[Optional[Dict], Optional[Dict], List]]: nwc = await get_nwc(pubkey, None, False, True) error = await _check(nwc, "get_info", payload) if error: return [(None, error, [])] - sp_methods = sp.getSupportedMethods() - permissions = nwc.getPermissions() + if not nwc: + raise Exception("Pubkey has no associated wallet") + sp_methods = sp.get_supported_methods() + permissions = nwc.get_permissions() # Filter only methods supported by the extension and allowed by the permissions account_methods = [] for spm in sp_methods: for p in permissions: - allowed_methods = nwc_permissions.get(p, {}).get("methods", []) + permissions_data: Dict[str, Any] = nwc_permissions.get(p, {}) + allowed_methods: List[str] = permissions_data.get("methods", []) if spm in allowed_methods: account_methods.append(spm) - break - #await log_nwc(pubkey, payload) - return [({ - "alias": settings.lnbits_site_title, - "color": "", - "network": "mainnet", - "block_height": 0, - "block_hash": "", - "methods": account_methods - }, None, [])] - + break + # await log_nwc(pubkey, payload) + return [ + ( + { + "alias": settings.lnbits_site_title, + "color": "", + "network": "mainnet", + "block_height": 0, + "block_hash": "", + "methods": account_methods, + }, + None, + [], + ) + ] async def handle_nwc(): priv_key = await get_config_nwc("provider_key") - relay = await get_config_nwc("relay") + relay = await get_config_nwc("relay") nwcsp = NWCServiceProvider(priv_key, relay) - nwcsp.addRequestListener("pay_invoice", _on_pay_invoice) - nwcsp.addRequestListener("multi_pay_invoice", _on_multi_pay_invoice) - nwcsp.addRequestListener("make_invoice", _on_make_invoice) - nwcsp.addRequestListener("lookup_invoice", _on_lookup_invoice) - nwcsp.addRequestListener("list_transactions", _on_list_transactions) - nwcsp.addRequestListener("get_balance", _on_get_balance) - nwcsp.addRequestListener("get_info", _on_get_info) + nwcsp.add_request_listener("pay_invoice", _on_pay_invoice) + nwcsp.add_request_listener("multi_pay_invoice", _on_multi_pay_invoice) + nwcsp.add_request_listener("make_invoice", _on_make_invoice) + nwcsp.add_request_listener("lookup_invoice", _on_lookup_invoice) + nwcsp.add_request_listener("list_transactions", _on_list_transactions) + nwcsp.add_request_listener("get_balance", _on_get_balance) + nwcsp.add_request_listener("get_info", _on_get_info) # currently not supported by lnbits # nwcsp.addRequestListener("pay_keysend", _on_pay_keysend) # nwcsp.addRequestListener("multi_pay_keysend", _on_multi_pay_keysend) @@ -363,7 +414,7 @@ async def handle_nwc(): await asyncio.sleep(3600) except asyncio.CancelledError: await nwcsp.cleanup() - raise + raise async def handle_execution_queue(): @@ -373,9 +424,13 @@ async def handle_execution_queue(): action = task.get("action") future = task.get("future") try: + if not action: + raise Exception("Invalid action") res = await action() - future.set_result(res) + if future: + future.set_result(res) except Exception as e: - future.set_exception(e) + if future: + future.set_exception(e) except Exception as e: logger.error(str(e)) diff --git a/templates/nwcprovider/admin.html b/templates/nwcprovider/admin.html index 940388f..dbd87ba 100644 --- a/templates/nwcprovider/admin.html +++ b/templates/nwcprovider/admin.html @@ -1,101 +1,123 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %}
-
- - -
-
-
NWC Service Provider - Config
-
-
- - -
-
-
+
+ + +
+
+
+ NWC Service Provider - Config +
+
+
+ + +
+
+
{% endblock %} {% block scripts %} {{ window_vars(user) }} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/nwcprovider/index.html b/templates/nwcprovider/index.html index bb7c2d0..7b63e21 100644 --- a/templates/nwcprovider/index.html +++ b/templates/nwcprovider/index.html @@ -8,8 +8,14 @@
- + @@ -19,16 +25,28 @@
Connected Apps
- +
- + ${ col.label } - @@ -38,20 +56,30 @@
${ col.value }
- - - + Info - + Delete - -
@@ -62,80 +90,101 @@
NWC Service provider

- Nostr Wallet Connect (NWC) are a way for applications to connect to a remote lightning wallet. + Nostr Wallet Connect (NWC) are a way for applications to connect to a + remote lightning wallet.

- - + Swagger API - + Settings - - - +

Info

- Description - ${connectionInfoDialog.data.description} + ${connectionInfoDialog.data.description} Last used - ${connectionInfoDialog.data.last_used} + ${connectionInfoDialog.data.last_used} Expires - ${connectionInfoDialog.data.expires_at} + ${connectionInfoDialog.data.expires_at} Created - ${connectionInfoDialog.data.created_at} + ${connectionInfoDialog.data.created_at} Permissions - ${connectionInfoDialog.data.permissions} - - Limits + Limits Budget (sats) -
used/max
+
used/max
Refresh
- + No limits - - ${budget.used_budget_sats} / ${budget.budget_sats} + + ${budget.used_budget_sats} / ${budget.budget_sats} ${budget.refresh_window} @@ -144,27 +193,44 @@
- Close + Close - +
-
- - + -

Please scan this QR code with a supported app

-

Connect only with app you trust!

+

+ Please scan this QR code with a supported app +

+

+ Connect only with app you trust! +

- +
@@ -173,33 +239,58 @@

Pairing

-

Complete the last step of the setup by pasting or scanning your connection's - pairing secret in the desired - app to - finalise the connection.

-

Connect only with app you trust!

+

+ Complete the last step of the setup by pasting or scanning your + connection's pairing secret in the desired app to finalise the + connection. +

+

+ Connect only with app you trust! +

- +
Advanced
-
- + - - + - Close + Close
@@ -209,42 +300,78 @@

Add connection

- +
- - + +
Authorize the app to - + Limit the spendable amount - - No limit + + No limit - + -
- Connect + Connect
- Cancel + Cancel
@@ -269,16 +405,14 @@ {% endblock %} {% block scripts %} {{ window_vars(user) }} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/tests/integration/test_all.py b/tests/integration/test_all.py index d07d88a..0d4d38c 100644 --- a/tests/integration/test_all.py +++ b/tests/integration/test_all.py @@ -1,22 +1,21 @@ -import pytest import asyncio -from loguru import logger -import httpx -import asyncio -import bolt11 -import secp256k1 -import time -from typing import List, Dict -import websockets -import random -import json -from typing import List, Dict, Optional -from Cryptodome.Util.Padding import pad, unpad +import base64 import hashlib +import json +import random +import time +from typing import Dict, List, Optional, Union + +import bolt11 +import httpx +import pytest +import secp256k1 +import websockets.client as websockets from Cryptodome import Random from Cryptodome.Cipher import AES -import base64 -from typing import Union, Dict +from Cryptodome.Util.Padding import pad, unpad +from loguru import logger + wallets = { "wallet1": { "name": "wallet1", @@ -41,96 +40,118 @@ wallets = { "id": "2faa91184177414ab14712cadafbc78f", "admin_key": "0ffd65580a664e0aae85687f99dac7ad", "balance_msats": 1000000, - } + }, } + async def check_services(): # wait for http server in localhost:7777 while True: try: async with httpx.AsyncClient() as client: - resp = await client.get('http://localhost:7777') + resp = await client.get("http://localhost:7777") assert resp.status_code == 200 break - except: + except Exception: logger.info("Waiting for nostr relay @ http://localhost:7777") - logger.info("Please start the required services by running `bash start.sh` if you haven't already") + logger.info( + """Please start the required services by running\ + `bash start.sh` if you haven't already""" + ) await asyncio.sleep(1) - + # wait lnbits @ localhost:5000 while True: try: async with httpx.AsyncClient() as client: - resp = await client.get('http://localhost:5002') + resp = await client.get("http://localhost:5002") assert resp.status_code == 200 break - except: + except Exception: logger.info("Waiting for lnbits @ http://localhost:5002") - logger.info("Please start the required services by running `bash start.sh` if you haven't already") + logger.info( + """Please start the required services by running\ + `bash start.sh` if you haven't already""" + ) await asyncio.sleep(1) -async def get_wallet_balance(w:str): +async def get_wallet_balance(w: str): api_key = wallets[w]["admin_key"] async with httpx.AsyncClient() as client: - resp = await client.get(f'http://localhost:5002/api/v1/wallet?api-key={api_key}') + resp = await client.get( + f"http://localhost:5002/api/v1/wallet?api-key={api_key}" + ) assert resp.status_code == 200 v = resp.json() balance = v["balance"] return balance - + + async def refresh_wallet_balances(): for w in wallets: wallets[w]["balance_msats"] = await get_wallet_balance(w) logger.info(f"{w} balance: {wallets[w]['balance_msats']}") - + def gen_keypair(): - private_key_hex = bytes.hex(secp256k1._gen_private_key()) + private_key_hex = bytes.hex(secp256k1._gen_private_key()) private_key = secp256k1.PrivateKey(bytes.fromhex(private_key_hex)) public_key = private_key.pubkey + if not public_key: + raise Exception("Error generating pubkey") public_key_hex = public_key.serialize().hex()[2:] - return { - "priv": private_key_hex, - "pub": public_key_hex - } + return {"priv": private_key_hex, "pub": public_key_hex} -async def create_nwc(w:str, desc:str, permissions:List[str], budgets:List[Dict[str, int]], expiration: 0): + +async def create_nwc( + w: str, + desc: str, + permissions: List[str], + budgets: List[Dict[str, int]], + expiration: int = 0, +): keypair = gen_keypair() api_key = wallets[w]["admin_key"] async with httpx.AsyncClient() as client: - resp = await client.put(f'http://localhost:5002/nwcprovider/api/v1/nwc/{keypair["pub"]}?api-key={api_key}', json={ - "permissions": permissions, - "description": desc, - "expires_at": time.time()+expiration if expiration > 0 else 0, - "budgets": budgets - }) + resp = await client.put( + f'http://localhost:5002/nwcprovider/api/v1/nwc/{keypair["pub"]}?api-key={api_key}', + json={ + "permissions": permissions, + "description": desc, + "expires_at": time.time() + expiration if expiration > 0 else 0, + "budgets": budgets, + }, + ) assert resp.status_code == 201 nwc = resp.json() async with httpx.AsyncClient() as client: - resp = await client.get(f'http://localhost:5002/nwcprovider/api/v1/pairing/{keypair["priv"]}') + resp = await client.get( + f'http://localhost:5002/nwcprovider/api/v1/pairing/{keypair["priv"]}' + ) assert resp.status_code == 200 pairing = resp.json() return { "pubkey": keypair["pub"], "privkey": keypair["priv"], "pairing": pairing, - "nwc": nwc + "nwc": nwc, } - - -async def delete_nwc(w:str, pubkey:str): + +async def delete_nwc(w: str, pubkey: str): api_key = wallets[w]["admin_key"] async with httpx.AsyncClient() as client: - resp = await client.delete(f'http://localhost:5002/nwcprovider/api/v1/nwc/{pubkey}?api-key={api_key}') + resp = await client.delete( + f"http://localhost:5002/nwcprovider/api/v1/nwc/{pubkey}?api-key={api_key}" + ) assert resp.status_code == 200 return resp.json() - -class NWCWallet : + +class NWCWallet: def __init__(self, pairing_url): # Extract from Pairing url nostr+walletconnect://provider_pub?relay=relay&secret=secret self.pairing_url = pairing_url @@ -142,16 +163,22 @@ class NWCWallet : self.shutdown = False self.event_queue = [] self.subscriptions_count = 0 - self.sub_id="" + self.sub_id = "" self.private_key = secp256k1.PrivateKey(bytes.fromhex(self.secret)) self.private_key_hex = self.secret self.public_key = self.private_key.pubkey + if not self.public_key: + raise Exception("Error generating pubkey") self.public_key_hex = self.public_key.serialize().hex()[2:] + self.task = None - async def close(self): self.shutdown = True + if not self.ws: + raise Exception("Websocket not connected") await self.ws.close() + if self.task: + self.task.cancel() self.connected = False async def _wait_for_connection(self): @@ -159,25 +186,23 @@ class NWCWallet : await asyncio.sleep(0.2) async def start(self): - asyncio.create_task(self._run()) + self.task = asyncio.create_task(self._run()) await self._wait_for_connection() - - def _is_shutting_down(self): return self.shutdown - def _get_new_subid(self) -> str: - subid = "lnbitsnwcstest"+str(self.subscriptions_count) + def _get_new_subid(self) -> str: + subid = "lnbitsnwcstest" + str(self.subscriptions_count) self.subscriptions_count += 1 - maxLength = 64 + max_length = 64 chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - n = maxLength - len(subid) + n = max_length - len(subid) if n > 0: - for i in range(n): + for _ in range(n): subid += chars[random.randint(0, len(chars) - 1)] return subid - + async def _run(self): while True: try: @@ -188,15 +213,19 @@ class NWCWallet : res_filter = { "kinds": [23195], "authors": [self.provider_pub_hex], - "since": int(time.time()) + "since": int(time.time()), } - await self.ws.send(self._json_dumps(["REQ", self.sub_id, res_filter])) + await self.ws.send( + self._json_dumps(["REQ", self.sub_id, res_filter]) + ) while not self._is_shutting_down() and not ws.closed: try: reply = await ws.recv() + if isinstance(reply, bytes): + reply = reply.decode("utf-8") try: await self._on_message(ws, reply) - except Exception as e: + except Exception: pass except Exception as e: logger.debug("Error receiving message: " + str(e)) @@ -209,104 +238,105 @@ class NWCWallet : await asyncio.sleep(0.2) else: break - - def _encrypt_content(self, content: str, pubkey_hex: str, iv_seed: Optional[int] = None) -> str: - pubkey = secp256k1.PublicKey( - bytes.fromhex("02" + pubkey_hex), True) - shared = pubkey.tweak_mul(bytes.fromhex( - self.private_key_hex)).serialize()[1:] + + def _encrypt_content( + self, content: str, pubkey_hex: str, iv_seed: Optional[int] = None + ) -> str: + pubkey = secp256k1.PublicKey(bytes.fromhex("02" + pubkey_hex), True) + shared = pubkey.tweak_mul(bytes.fromhex(self.private_key_hex)).serialize()[1:] if not iv_seed: iv = Random.new().read(AES.block_size) else: - iv = hashlib.sha256(iv_seed.to_bytes(32, byteorder='big')).digest() - iv = iv[:AES.block_size] + iv = hashlib.sha256(iv_seed.to_bytes(32, byteorder="big")).digest() + iv = iv[: AES.block_size] aes = AES.new(shared, AES.MODE_CBC, iv) content_bytes = content.encode("utf-8") content_bytes = pad(content_bytes, AES.block_size) - encrypted_b64 = base64.b64encode( - aes.encrypt(content_bytes)).decode("ascii") - ivB64 = base64.b64encode(iv).decode("ascii") - encrypted_content = encrypted_b64 + "?iv=" + ivB64 + encrypted_b64 = base64.b64encode(aes.encrypt(content_bytes)).decode("ascii") + iv_b64 = base64.b64encode(iv).decode("ascii") + encrypted_content = encrypted_b64 + "?iv=" + iv_b64 return encrypted_content def _decrypt_content(self, content: str, pubkey_hex: str) -> str: - pubkey = secp256k1.PublicKey( - bytes.fromhex("02" + pubkey_hex), True) - shared = pubkey.tweak_mul(bytes.fromhex( - self.private_key_hex)).serialize()[1:] + pubkey = secp256k1.PublicKey(bytes.fromhex("02" + pubkey_hex), True) + shared = pubkey.tweak_mul(bytes.fromhex(self.private_key_hex)).serialize()[1:] (encrypted_content_b64, iv_b64) = content.split("?iv=") - encrypted_content = base64.b64decode( - encrypted_content_b64.encode("ascii")) + encrypted_content = base64.b64decode(encrypted_content_b64.encode("ascii")) iv = base64.b64decode(iv_b64.encode("ascii")) aes = AES.new(shared, AES.MODE_CBC, iv) decrypted_bytes = aes.decrypt(encrypted_content) decrypted_bytes = unpad(decrypted_bytes, AES.block_size) decrypted = decrypted_bytes.decode("utf-8") return decrypted - + async def _on_message(self, ws, message: str): logger.debug("Received message: " + message) msg = json.loads(message) if msg[0] == "EVENT": # Event message - sub_id = msg[1] event = msg[2] - nwc_pubkey = event["pubkey"] + nwc_pubkey = event["pubkey"] content = self._decrypt_content(event["content"], nwc_pubkey) content = json.loads(content) - self.event_queue.append({ - "created_at": event["created_at"], - "content": content, - "result": content["result"] if "result" in content else None, - "error": content["error"] if "error" in content else None, - "method": content["result_type"], - "tags": event["tags"] - }) + self.event_queue.append( + { + "created_at": event["created_at"], + "content": content, + "result": content["result"] if "result" in content else None, + "error": content["error"] if "error" in content else None, + "method": content["result_type"], + "tags": event["tags"], + } + ) - def _json_dumps(self, data: Union[Dict, list]) -> str: + def _json_dumps(self, data: Union[Dict, list]) -> str: if isinstance(data, Dict): data = {k: v for k, v in data.items() if v is not None} - return json.dumps(data, separators=(',', ':'), ensure_ascii=False) + return json.dumps(data, separators=(",", ":"), ensure_ascii=False) def _sign_event(self, event: Dict) -> Dict: - signature_data = self._json_dumps([ - 0, - self.public_key_hex, - event["created_at"], - event["kind"], - event["tags"], - event["content"] - ]) + signature_data = self._json_dumps( + [ + 0, + self.public_key_hex, + event["created_at"], + event["kind"], + event["tags"], + event["content"], + ] + ) event_id = hashlib.sha256(signature_data.encode()).hexdigest() event["id"] = event_id event["pubkey"] = self.public_key_hex - signature = (self.private_key.schnorr_sign( - bytes.fromhex(event_id), None, raw=True)).hex() + signature = ( + self.private_key.schnorr_sign(bytes.fromhex(event_id), None, raw=True) + ).hex() event["sig"] = signature return event - - async def sendEvent(self, method,params): + async def send_event(self, method, params): + if not self.ws: + raise Exception("Websocket not connected") await self._wait_for_connection() event = { "created_at": int(time.time()), "kind": 23194, - "tags":[ + "tags": [ ["p", self.provider_pub_hex], ], - "content": json.dumps({ - "method": method, - "params": params - - }) + "content": json.dumps({"method": method, "params": params}), } logger.debug("Sending event: " + str(event)) - event["content"] = self._encrypt_content(event["content"], self.provider_pub_hex) + event["content"] = self._encrypt_content( + event["content"], self.provider_pub_hex + ) self._sign_event(event) logger.debug("Sending event (encrypted): " + str(event)) await self.ws.send(self._json_dumps(["EVENT", event])) - async def waitFor(self, result_type, callback=None, on_error_callback=None, timeout=10): + async def wait_for( + self, result_type, callback=None, on_error_callback=None, timeout=10 + ): now = time.time() while True: for i in range(len(self.event_queue)): @@ -319,15 +349,14 @@ class NWCWallet : if e["error"]: if on_error_callback: on_error_callback(e["error"], e["tags"]) - + return e["result"], e["tags"], e["error"] else: return e["result"], e["tags"], None await asyncio.sleep(1) if timeout > 0 and time.time() > now + timeout: raise Exception("Timeout") - - + @pytest.mark.asyncio async def test_create(): @@ -342,7 +371,7 @@ async def test_create(): assert nwc["nwc"]["data"]["created_at"] > time.time() - 10 assert nwc["nwc"]["data"]["created_at"] < time.time() + 10 assert len(nwc["nwc"]["budgets"]) == 0 - + @pytest.mark.asyncio async def test_make_invoice(): @@ -350,13 +379,17 @@ async def test_make_invoice(): nwc = await create_nwc("wallet1", "test_make_invoice", ["invoice"], [], 0) wallet1 = NWCWallet(nwc["pairing"]) await wallet1.start() - await wallet1.sendEvent("make_invoice", {"amount": 1, "description": "test 123", "expiry": 1000}) - result, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event( + "make_invoice", {"amount": 1, "description": "test 123", "expiry": 1000} + ) + result, tags, error = await wallet1.wait_for("make_invoice") logger.info(error) - assert error , "Expected internal error, because amount is too low" + assert error, "Expected internal error, because amount is too low" - await wallet1.sendEvent("make_invoice", {"amount": 123000, "description":"test 123", "expiry": 1000}) - result, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event( + "make_invoice", {"amount": 123000, "description": "test 123", "expiry": 1000} + ) + result, tags, error = await wallet1.wait_for("make_invoice") assert not error assert result["type"] == "incoming" assert result["description"] == "test 123" @@ -364,16 +397,16 @@ async def test_make_invoice(): assert result["preimage"] assert result["created_at"] < time.time() + 10 assert result["created_at"] > time.time() - 10 - assert result["expires_at"] < time.time() + 1000 + 10 - assert result["expires_at"] > time.time() + assert result["expires_at"] < time.time() + 1000 + 10 + assert result["expires_at"] > time.time() assert result["invoice"] invoice = result["invoice"] decoded_invoice = bolt11.decode(invoice) assert decoded_invoice.amount_msat == 123000 - + await wallet1.close() - + @pytest.mark.asyncio async def test_lookup_invoice(): @@ -383,10 +416,11 @@ async def test_lookup_invoice(): wallet1 = NWCWallet(nwc["pairing"]) await wallet1.start() - - await wallet1.sendEvent("make_invoice", {"amount": 123000, "description": "test 123", "expiry": 1000}) - result, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event( + "make_invoice", {"amount": 123000, "description": "test 123", "expiry": 1000} + ) + result, tags, error = await wallet1.wait_for("make_invoice") assert not error assert result["type"] == "incoming" assert result["description"] == "test 123" @@ -401,8 +435,8 @@ async def test_lookup_invoice(): wallet2 = NWCWallet(nwc2["pairing"]) await wallet2.start() - await wallet2.sendEvent("lookup_invoice", {"invoice": result["invoice"]}) - result, tags, error = await wallet2.waitFor("lookup_invoice") + await wallet2.send_event("lookup_invoice", {"invoice": result["invoice"]}) + result, tags, error = await wallet2.wait_for("lookup_invoice") assert not error assert result["type"] == "incoming" assert result["description"] == "test 123" @@ -422,21 +456,19 @@ async def test_lookup_invoice(): async def test_get_info(): await check_services() nwc = await create_nwc("wallet1", "test_get_info", ["info"], [], 0) - wallet1 = NWCWallet(nwc["pairing"]) await wallet1.start() - - await wallet1.sendEvent("get_info", {}) - result, tags, error = await wallet1.waitFor("get_info") + await wallet1.send_event("get_info", {}) + result, tags, error = await wallet1.wait_for("get_info") assert not error assert result["alias"] == "LNBits_NWC_SP" assert result["color"] == "" assert result["network"] == "mainnet" assert result["block_height"] == 0 assert result["block_hash"] == "" - assert result["methods"] == ["get_info"] + assert result["methods"] == ["get_info"] await wallet1.close() @@ -445,49 +477,44 @@ async def test_get_info(): async def test_permisions(): await check_services() nwc = await create_nwc("wallet1", "test_permisions1", ["info"], [], 0) - nwc2 = await create_nwc("wallet1", "test_permisions2", [ "pay", "invoice"], [], 0) - nwc3 = await create_nwc("wallet1", "test_permisions3", ["info" , "pay", "invoice"], [], 0) - + nwc2 = await create_nwc("wallet1", "test_permisions2", ["pay", "invoice"], [], 0) + nwc3 = await create_nwc( + "wallet1", "test_permisions3", ["info", "pay", "invoice"], [], 0 + ) wallet1 = NWCWallet(nwc["pairing"]) wallet2 = NWCWallet(nwc2["pairing"]) wallet3 = NWCWallet(nwc3["pairing"]) await wallet1.start() - - await wallet1.sendEvent("get_info", {}) - result, tags, error = await wallet1.waitFor("get_info") + await wallet1.send_event("get_info", {}) + result, tags, error = await wallet1.wait_for("get_info") assert not error - await wallet1.sendEvent("make_invoice", { - "amount": 123000, - "description": "test 123", - "expiry": 1000 - }) - result, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event( + "make_invoice", {"amount": 123000, "description": "test 123", "expiry": 1000} + ) + result, tags, error = await wallet1.wait_for("make_invoice") assert error await wallet1.close() await wallet2.start() - await wallet2.sendEvent("get_info", {}) - result, tags, error = await wallet2.waitFor("get_info") + await wallet2.send_event("get_info", {}) + result, tags, error = await wallet2.wait_for("get_info") assert error - - await wallet2.sendEvent("make_invoice", { - "amount": 123000, - "description": "test 123", - "expiry": 1000 - }) - result, tags, error = await wallet2.waitFor("make_invoice") + await wallet2.send_event( + "make_invoice", {"amount": 123000, "description": "test 123", "expiry": 1000} + ) + result, tags, error = await wallet2.wait_for("make_invoice") assert not error await wallet2.close() await wallet3.start() - await wallet3.sendEvent("get_info", {}) - result, tags, error = await wallet3.waitFor("get_info") + await wallet3.send_event("get_info", {}) + result, tags, error = await wallet3.wait_for("get_info") assert not error assert "make_invoice" in result["methods"] assert "pay_invoice" in result["methods"] @@ -499,25 +526,25 @@ async def test_permisions(): @pytest.mark.asyncio async def test_pay_invoice_and_balance(): await check_services() - nwc = await create_nwc("wallet1", "test_pay_invoice_and_balance", ["invoice", "balance"], [], 0) - nwc2 = await create_nwc("wallet2", "test_pay_invoice_and_balance", ["pay", "balance"], [], 0) - + nwc = await create_nwc( + "wallet1", "test_pay_invoice_and_balance", ["invoice", "balance"], [], 0 + ) + nwc2 = await create_nwc( + "wallet2", "test_pay_invoice_and_balance", ["pay", "balance"], [], 0 + ) wallet1 = NWCWallet(nwc["pairing"]) await wallet1.start() - await refresh_wallet_balances() wallet1_balance = wallets["wallet1"]["balance_msats"] wallet2_balance = wallets["wallet2"]["balance_msats"] + await wallet1.send_event( + "make_invoice", {"amount": 123000, "description": "test 123"} + ) - await wallet1.sendEvent("make_invoice", { - "amount": 123000, - "description": "test 123" - }) - - result, tags, error = await wallet1.waitFor("make_invoice") + result, tags, error = await wallet1.wait_for("make_invoice") assert not error assert result["invoice"] @@ -525,10 +552,8 @@ async def test_pay_invoice_and_balance(): wallet2 = NWCWallet(nwc2["pairing"]) await wallet2.start() - await wallet2.sendEvent("pay_invoice", { - "invoice": invoice - }) - result, tags, error = await wallet2.waitFor("pay_invoice") + await wallet2.send_event("pay_invoice", {"invoice": invoice}) + result, tags, error = await wallet2.wait_for("pay_invoice") assert not error assert result["preimage"] @@ -539,13 +564,13 @@ async def test_pay_invoice_and_balance(): assert wallet1_balance_new == wallet1_balance + 123000 assert wallet2_balance_new == wallet2_balance - 123000 - await wallet1.sendEvent("get_balance", {}) - result, tags, error = await wallet1.waitFor("get_balance") + await wallet1.send_event("get_balance", {}) + result, tags, error = await wallet1.wait_for("get_balance") assert not error assert result["balance"] == wallet1_balance_new - await wallet2.sendEvent("get_balance", {}) - result, tags, error = await wallet2.waitFor("get_balance") + await wallet2.send_event("get_balance", {}) + result, tags, error = await wallet2.wait_for("get_balance") assert not error assert result["balance"] == wallet2_balance_new @@ -555,10 +580,16 @@ async def test_pay_invoice_and_balance(): @pytest.mark.asyncio async def test_multi_pay_invoices(): - nwc1 = await create_nwc("wallet1", "test_multi_pay_invoices", ["invoice", "pay", "balance"], [], 0) - nwc2 = await create_nwc("wallet2", "test_multi_pay_invoices", ["invoice", "pay", "balance"], [], 0) - nwc3 = await create_nwc("wallet3", "test_multi_pay_invoices", ["invoice", "pay", "balance"], [], 0) - + nwc1 = await create_nwc( + "wallet1", "test_multi_pay_invoices", ["invoice", "pay", "balance"], [], 0 + ) + nwc2 = await create_nwc( + "wallet2", "test_multi_pay_invoices", ["invoice", "pay", "balance"], [], 0 + ) + nwc3 = await create_nwc( + "wallet3", "test_multi_pay_invoices", ["invoice", "pay", "balance"], [], 0 + ) + wallet1 = NWCWallet(nwc1["pairing"]) wallet2 = NWCWallet(nwc2["pairing"]) wallet3 = NWCWallet(nwc3["pairing"]) @@ -572,53 +603,52 @@ async def test_multi_pay_invoices(): wallet2_balance = wallets["wallet2"]["balance_msats"] wallet3_balance = wallets["wallet3"]["balance_msats"] - await wallet1.sendEvent("make_invoice", { - "amount": 123000, - "description": "test 123" - }) + await wallet1.send_event( + "make_invoice", {"amount": 123000, "description": "test 123"} + ) - result, tags, error = await wallet1.waitFor("make_invoice") + result, tags, error = await wallet1.wait_for("make_invoice") assert not error assert result["invoice"] invoice1 = result["invoice"] - await wallet1.sendEvent("make_invoice", { - "amount": 123000, - "description": "test 123" - }) - result, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event( + "make_invoice", {"amount": 123000, "description": "test 123"} + ) + result, tags, error = await wallet1.wait_for("make_invoice") assert not error assert result["invoice"] invoice2 = result["invoice"] - await wallet2.sendEvent("make_invoice", { - "amount": 123000, - "description": "test 123" - }) - result, tags, error = await wallet2.waitFor("make_invoice") + await wallet2.send_event( + "make_invoice", {"amount": 123000, "description": "test 123"} + ) + result, tags, error = await wallet2.wait_for("make_invoice") assert not error assert result["invoice"] invoice3 = result["invoice"] - await wallet3.sendEvent("multi_pay_invoice", { - "invoices":[ - {"id":"invoice1", "invoice": invoice1, "amount": 123000}, - {"id":"invoice2", "invoice": invoice2, "amount": 123000}, - { "invoice": invoice3} - ] - }) - result, tags, error = await wallet3.waitFor("multi_pay_invoice") + await wallet3.send_event( + "multi_pay_invoice", + { + "invoices": [ + {"id": "invoice1", "invoice": invoice1, "amount": 123000}, + {"id": "invoice2", "invoice": invoice2, "amount": 123000}, + {"invoice": invoice3}, + ] + }, + ) + result, tags, error = await wallet3.wait_for("multi_pay_invoice") assert not error - d_tag = [t[1] for t in tags if t[0] == "d"][0] - if d_tag=="invoice1": + d_tag = next((t[1] for t in tags if t[0] == "d"), None) + if d_tag == "invoice1": assert result["preimage"] - elif d_tag=="invoice2": + elif d_tag == "invoice2": assert result["preimage"] - elif d_tag==invoice3: + elif d_tag == invoice3: assert result["preimage"] else: - assert False - + raise AssertionError("Unexpected d tag") await refresh_wallet_balances() wallet1_balance_new = wallets["wallet1"]["balance_msats"] @@ -627,38 +657,36 @@ async def test_multi_pay_invoices(): assert wallet1_balance_new == wallet1_balance + 123000 + 123000 assert wallet2_balance_new == wallet2_balance + 123000 - assert wallet3_balance_new == wallet3_balance - 123000 - 123000 - 123000 + assert wallet3_balance_new == wallet3_balance - 123000 - 123000 - 123000 - - - await wallet1.sendEvent("get_balance", {}) - result, tags, error = await wallet1.waitFor("get_balance") + await wallet1.send_event("get_balance", {}) + result, tags, error = await wallet1.wait_for("get_balance") assert not error assert result["balance"] == wallet1_balance_new - await wallet2.sendEvent("get_balance", {}) - result, tags, error = await wallet2.waitFor("get_balance") + await wallet2.send_event("get_balance", {}) + result, tags, error = await wallet2.wait_for("get_balance") assert not error assert result["balance"] == wallet2_balance_new - await wallet3.sendEvent("get_balance", {}) - result, tags, error = await wallet3.waitFor("get_balance") + await wallet3.send_event("get_balance", {}) + result, tags, error = await wallet3.wait_for("get_balance") assert not error assert result["balance"] == wallet3_balance_new await wallet1.close() await wallet2.close() await wallet3.close() - - - - @pytest.mark.asyncio async def test_insufficient_balance(): - nwc1 = await create_nwc("wallet1", "test_insufficient_balance", ["invoice", "pay", "balance"], [], 0) - nwc2 = await create_nwc("wallet2", "test_insufficient_balance", ["invoice", "pay", "balance"], [], 0) + nwc1 = await create_nwc( + "wallet1", "test_insufficient_balance", ["invoice", "pay", "balance"], [], 0 + ) + nwc2 = await create_nwc( + "wallet2", "test_insufficient_balance", ["invoice", "pay", "balance"], [], 0 + ) await refresh_wallet_balances() wallet1_balance = wallets["wallet1"]["balance_msats"] amount_to_spend = wallet1_balance + 1000 @@ -667,19 +695,16 @@ async def test_insufficient_balance(): await wallet1.start() await wallet2.start() - await wallet2.sendEvent("make_invoice", { - "amount": amount_to_spend, - "description": "test 123" - }) - result, tags, error = await wallet2.waitFor("make_invoice") + await wallet2.send_event( + "make_invoice", {"amount": amount_to_spend, "description": "test 123"} + ) + result, tags, error = await wallet2.wait_for("make_invoice") assert not error assert result["invoice"] invoice = result["invoice"] - await wallet1.sendEvent("pay_invoice", { - "invoice": invoice - }) - result, tags, error = await wallet1.waitFor("pay_invoice") + await wallet1.send_event("pay_invoice", {"invoice": invoice}) + result, tags, error = await wallet1.wait_for("pay_invoice") logger.info(error) logger.info(result) logger.info(amount_to_spend) @@ -687,88 +712,89 @@ async def test_insufficient_balance(): assert error # The proper error code should be INSUFFICIENT_BALANCE # but we use the more generic PAYMENT_FAILED in our implementation for simplicity - #assert error["code"] == "INSUFFICIENT_BALANCE" + # assert error["code"] == "INSUFFICIENT_BALANCE" assert error["code"] == "PAYMENT_FAILED" - + await wallet1.close() await wallet2.close() - @pytest.mark.asyncio async def test_expiry(): - nwc = await create_nwc("wallet3", "test_expiry", ["invoice", "pay", "balance"], [], 1) + nwc = await create_nwc( + "wallet3", "test_expiry", ["invoice", "pay", "balance"], [], 1 + ) await asyncio.sleep(2) wallet3 = NWCWallet(nwc["pairing"]) await wallet3.start() - await wallet3.sendEvent("make_invoice", { - "amount": 123000, - "description": "test 123" - }) - result, tags, error = await wallet3.waitFor("make_invoice") + await wallet3.send_event( + "make_invoice", {"amount": 123000, "description": "test 123"} + ) + result, tags, error = await wallet3.wait_for("make_invoice") assert error - assert error["code"] == "UNAUTHORIZED" , "Expected UNAUTHORIZED error, because the NWC expired" + assert ( + error["code"] == "UNAUTHORIZED" + ), "Expected UNAUTHORIZED error, because the NWC expired" await wallet3.close() - - @pytest.mark.asyncio async def test_budget(): - nwc1 = await create_nwc("wallet1", "test_expiry", ["invoice", "pay", "balance"], [], 0) - nwc3 = await create_nwc("wallet3", "test_expiry", ["invoice", "pay", "balance"], [ - { - "budget_msats": 100000, - "refresh_window": 3600, - "created_at": time.time() - } - ], 0) + nwc1 = await create_nwc( + "wallet1", "test_expiry", ["invoice", "pay", "balance"], [], 0 + ) + nwc3 = await create_nwc( + "wallet3", + "test_expiry", + ["invoice", "pay", "balance"], + [ + { + "budget_msats": 100000, + "refresh_window": 3600, + "created_at": int(time.time()), + } + ], + 0, + ) wallet1 = NWCWallet(nwc1["pairing"]) wallet3 = NWCWallet(nwc3["pairing"]) await wallet3.start() await wallet1.start() - await wallet1.sendEvent("make_invoice", { - "amount": 101000, - "description": "Invalid" - }) - result, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event( + "make_invoice", {"amount": 101000, "description": "Invalid"} + ) + result, tags, error = await wallet1.wait_for("make_invoice") assert not error - await wallet3.sendEvent("pay_invoice", { - "invoice": result["invoice"] - }) - result, tags, error = await wallet3.waitFor("pay_invoice") + await wallet3.send_event("pay_invoice", {"invoice": result["invoice"]}) + result, tags, error = await wallet3.wait_for("pay_invoice") assert error - assert error["code"] == "QUOTA_EXCEEDED" , "Expected QUOTA_EXCEEDED error, because the budget was exceeded" + assert ( + error["code"] == "QUOTA_EXCEEDED" + ), "Expected QUOTA_EXCEEDED error, because the budget was exceeded" - await wallet1.sendEvent("make_invoice", { - "amount": 99000, - "description": "Valid" - }) - result, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event("make_invoice", {"amount": 99000, "description": "Valid"}) + result, tags, error = await wallet1.wait_for("make_invoice") assert not error - await wallet3.sendEvent("pay_invoice", { - "invoice": result["invoice"] - }) - result, tags, error = await wallet3.waitFor("pay_invoice") + await wallet3.send_event("pay_invoice", {"invoice": result["invoice"]}) + result, tags, error = await wallet3.wait_for("pay_invoice") assert not error, "Expected successful payment, because the budget was not exceeded" assert result["preimage"] - await wallet1.sendEvent("make_invoice", { - "amount": 100000-99000+1000, - "description": "Invalid" - }) + await wallet1.send_event( + "make_invoice", {"amount": 100000 - 99000 + 1000, "description": "Invalid"} + ) - result, tags, error = await wallet1.waitFor("make_invoice") + result, tags, error = await wallet1.wait_for("make_invoice") assert not error - await wallet3.sendEvent("pay_invoice", { - "invoice": result["invoice"] - }) - result, tags, error = await wallet3.waitFor("pay_invoice") + await wallet3.send_event("pay_invoice", {"invoice": result["invoice"]}) + result, tags, error = await wallet3.wait_for("pay_invoice") assert error - assert error["code"] == "QUOTA_EXCEEDED" , "Expected QUOTA_EXCEEDED error, because the budget was exceeded" + assert ( + error["code"] == "QUOTA_EXCEEDED" + ), "Expected QUOTA_EXCEEDED error, because the budget was exceeded" await wallet3.close() await wallet1.close() @@ -776,63 +802,51 @@ async def test_budget(): @pytest.mark.asyncio async def test_budget_refresh(): - nwc1 = await create_nwc("wallet1", "test_expiry", ["invoice", "pay", "balance"], [], 0) - nwc3 = await create_nwc("wallet3", "test_expiry", ["invoice", "pay", "balance"], [ { - "budget_msats": 100000, - "refresh_window": 5, - "created_at": time.time() - }], 0) + nwc1 = await create_nwc( + "wallet1", "test_expiry", ["invoice", "pay", "balance"], [], 0 + ) + nwc3 = await create_nwc( + "wallet3", + "test_expiry", + ["invoice", "pay", "balance"], + [{"budget_msats": 100000, "refresh_window": 5, "created_at": int(time.time())}], + 0, + ) wallet1 = NWCWallet(nwc1["pairing"]) wallet3 = NWCWallet(nwc3["pairing"]) await wallet3.start() await wallet1.start() - await wallet1.sendEvent("make_invoice", { - "amount": 100000, - "description": "Invalid" - }) - result, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event( + "make_invoice", {"amount": 100000, "description": "Invalid"} + ) + result, tags, error = await wallet1.wait_for("make_invoice") assert not error - await wallet1.sendEvent("make_invoice", { - "amount": 100000, - "description": "Invalid" - }) - result2, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event( + "make_invoice", {"amount": 100000, "description": "Invalid"} + ) + result2, tags, error = await wallet1.wait_for("make_invoice") assert not error - await wallet3.sendEvent("pay_invoice", { - "invoice": result["invoice"] - }) - result, tags, error = await wallet3.waitFor("pay_invoice") + await wallet3.send_event("pay_invoice", {"invoice": result["invoice"]}) + result, tags, error = await wallet3.wait_for("pay_invoice") assert not error, "Expected successful payment, because the budget was not exceeded" - - await wallet3.sendEvent("pay_invoice", { - "invoice": result2["invoice"] - }) - result, tags, error = await wallet3.waitFor("pay_invoice") + await wallet3.send_event("pay_invoice", {"invoice": result2["invoice"]}) + result, tags, error = await wallet3.wait_for("pay_invoice") assert error - assert error["code"] == "QUOTA_EXCEEDED", "Expected QUOTA_EXCEEDED error, because the budget was exceeded" + assert ( + error["code"] == "QUOTA_EXCEEDED" + ), "Expected QUOTA_EXCEEDED error, because the budget was exceeded" await asyncio.sleep(5) - await wallet1.sendEvent("make_invoice", { - "amount": 100000, - "description": "Valid" - }) - result, tags, error = await wallet1.waitFor("make_invoice") + await wallet1.send_event("make_invoice", {"amount": 100000, "description": "Valid"}) + result, tags, error = await wallet1.wait_for("make_invoice") assert not error - - await wallet3.sendEvent("pay_invoice", { - "invoice": result["invoice"] - }) - result, tags, error = await wallet3.waitFor("pay_invoice") + await wallet3.send_event("pay_invoice", {"invoice": result["invoice"]}) + result, tags, error = await wallet3.wait_for("pay_invoice") assert not error, "Expected successful payment, because the budget was refreshed" await wallet3.close() await wallet1.close() - - - - - diff --git a/tests/unit/test_NWCServiceProvider.py b/tests/unit/test_nwcp.py similarity index 61% rename from tests/unit/test_NWCServiceProvider.py rename to tests/unit/test_nwcp.py index 71f7975..8ee0b75 100644 --- a/tests/unit/test_NWCServiceProvider.py +++ b/tests/unit/test_nwcp.py @@ -1,35 +1,41 @@ import json -from loguru import logger -import sys import os -sys.path.insert(0, os.path.abspath( - os.path.join(os.path.dirname(__file__), '..', '..'))) +import sys + +from loguru import logger + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) #### -import string import random -from NWCServiceProvider import NWCServiceProvider -from unittest.mock import AsyncMock, patch -import asyncio +import string + import pytest +from nwcp import NWCServiceProvider + @pytest.fixture def nwc_service_provider(): - return NWCServiceProvider("d7b5232fba0e02e32cfe26f20cdf2c803b27ecd81052c2dd5d17e5e1a333fe58", "") + return NWCServiceProvider( + "d7b5232fba0e02e32cfe26f20cdf2c803b27ecd81052c2dd5d17e5e1a333fe58", "" + ) @pytest.fixture def nwc_service_provider2(): - return NWCServiceProvider("ce40821040275f72f3074a89770db3e2744b189f204807c867840eb58565de51", "") + return NWCServiceProvider( + "ce40821040275f72f3074a89770db3e2744b189f204807c867840eb58565de51", "" + ) @pytest.mark.asyncio async def test_supported_methods(nwc_service_provider): def make_invoice(provider, pubkey, content): return "invoice" - nwc_service_provider.addRequestListener("make_invoice", make_invoice) - s = nwc_service_provider.getSupportedMethods() + + nwc_service_provider.add_request_listener("make_invoice", make_invoice) + s = nwc_service_provider.get_supported_methods() assert s == ["make_invoice"] @@ -38,14 +44,18 @@ async def test_encrytdecrypt(nwc_service_provider, nwc_service_provider2): content = "Hello World" expected_enc = "qVurNVISSl/9CfREIhk5Lg==?iv=QpCo5dI9gUcoLsSMLA7o7Q==" enc_a = nwc_service_provider._encrypt_content( - content, nwc_service_provider2.public_key_hex, 21) + content, nwc_service_provider2.public_key_hex, 21 + ) enc_b = nwc_service_provider2._encrypt_content( - content, nwc_service_provider.public_key_hex, 21) + content, nwc_service_provider.public_key_hex, 21 + ) dec_a = nwc_service_provider2._decrypt_content( - enc_a, nwc_service_provider.public_key_hex) + enc_a, nwc_service_provider.public_key_hex + ) dec_b = nwc_service_provider._decrypt_content( - enc_b, nwc_service_provider2.public_key_hex) + enc_b, nwc_service_provider2.public_key_hex + ) assert dec_a == content assert dec_b == content @@ -62,15 +72,16 @@ async def test_signverify(nwc_service_provider, nwc_service_provider2): tags = [] for _ in range(random.choice([1, 2, 3, 4])): - tags.append([random.choice(string.ascii_letters)+"_" + - random.choice(string.ascii_letters), random.choice(string.ascii_letters)]) + tags.append( + [ + random.choice(string.ascii_letters) + + "_" + + random.choice(string.ascii_letters), + random.choice(string.ascii_letters), + ] + ) - event = { - "kind": 1, - "content": content, - "tags": tags, - "created_at": 1234567890 - } + event = {"kind": 1, "content": content, "tags": tags, "created_at": 1234567890} signed = nwc_service_provider._sign_event(event) assert nwc_service_provider2._verify_event(signed) @@ -78,22 +89,17 @@ async def test_signverify(nwc_service_provider, nwc_service_provider2): @pytest.mark.asyncio async def test_handle(nwc_service_provider, nwc_service_provider2): - content = nwc_service_provider._json_dumps({ - "method": "pay_invoice", - "params": { - "invoice": "abc" - } - }) + content = nwc_service_provider._json_dumps( + {"method": "pay_invoice", "params": {"invoice": "abc"}} + ) content = nwc_service_provider._encrypt_content( - content, nwc_service_provider2.public_key_hex, 21) + content, nwc_service_provider2.public_key_hex, 21 + ) event = { "kind": 23194, "content": content, - "tags": [ - ["p", nwc_service_provider2.public_key_hex] - ], - "created_at": 1234567890 - + "tags": [["p", nwc_service_provider2.public_key_hex]], + "created_at": 1234567890, } signed = nwc_service_provider._sign_event(event) @@ -101,30 +107,20 @@ async def test_handle(nwc_service_provider, nwc_service_provider2): assert pubkey == nwc_service_provider.public_key_hex assert content["method"] == "pay_invoice" assert content["params"]["invoice"] == "abc" - return [ - ( - { - "preimage": "00000" + return [({"preimage": "00000"}, None, [["r1", "v1"]])] - }, - None, - [ - ["r1", "v1"] - ] - ) - ] - async def _send_pass(obj): pass - nwc_service_provider2._send=_send_pass - nwc_service_provider2.addRequestListener( - "pay_invoice", _handle_pay_invoice) + + nwc_service_provider2._send = _send_pass + nwc_service_provider2.add_request_listener("pay_invoice", _handle_pay_invoice) sent_events = await nwc_service_provider2._handle_request(signed) assert len(sent_events) == 1 for revent in sent_events: assert nwc_service_provider2._verify_event(revent) content = nwc_service_provider2._decrypt_content( - revent["content"], nwc_service_provider.public_key_hex) + revent["content"], nwc_service_provider.public_key_hex + ) logger.debug(event) logger.debug(revent) content = json.loads(content) @@ -138,7 +134,7 @@ async def test_handle(nwc_service_provider, nwc_service_provider2): e_tag = [tag for tag in tags if tag[0] == "e"] assert len(e_tag) == 1 assert e_tag[0][1] == event["id"] - + p_tag = [tag for tag in tags if tag[0] == "p"] assert len(p_tag) == 1 - assert p_tag[0][1] == nwc_service_provider.public_key_hex + assert p_tag[0][1] == nwc_service_provider.public_key_hex diff --git a/views.py b/views.py index df5e63f..35c80e4 100644 --- a/views.py +++ b/views.py @@ -1,20 +1,26 @@ - -from fastapi import Depends, Request +from fastapi import APIRouter, Depends, Request from starlette.responses import HTMLResponse + from lnbits.core.models import User -from lnbits.decorators import check_user_exists -from lnbits.decorators import check_admin - -from . import nwcprovider_ext,nwcprovider_renderer +from lnbits.decorators import check_admin, check_user_exists +from lnbits.helpers import template_renderer -@nwcprovider_ext.get("/", response_class=HTMLResponse) +def nwcprovider_renderer(): + return template_renderer(["nwcprovider/templates"]) + + +nwcprovider_router = APIRouter() + + +@nwcprovider_router.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): return nwcprovider_renderer().TemplateResponse( "nwcprovider/index.html", {"request": request, "user": user.dict()} ) -@nwcprovider_ext.get("/admin", response_class=HTMLResponse) + +@nwcprovider_router.get("/admin", response_class=HTMLResponse) async def admin(request: Request, user: User = Depends(check_admin)): return nwcprovider_renderer().TemplateResponse( "nwcprovider/admin.html", {"request": request, "user": user.dict()} diff --git a/views_api.py b/views_api.py index 5ca0a0a..3db81a6 100644 --- a/views_api.py +++ b/views_api.py @@ -1,162 +1,178 @@ from http import HTTPStatus -import json -from typing import List, Optional, Dict -from .models import NWCRegistrationRequest, NWCGetResponse -from fastapi import Depends, Request -from lnbits.decorators import ( - WalletTypeInfo, - require_admin_key -) -from fastapi.responses import JSONResponse +from typing import Dict, List, Optional -from . import nwcprovider_ext - -from .crud import get_nwc, get_wallet_nwcs, get_all_config_nwc, create_nwc, delete_nwc, get_budgets_nwc, get_config_nwc,set_config_nwc - - -from lnbits.decorators import check_admin -from fastapi import Depends import secp256k1 +from fastapi import APIRouter, Depends, Request +from fastapi.responses import JSONResponse +from lnbits.core.models import WalletTypeInfo +from lnbits.decorators import check_admin, require_admin_key + +from .crud import ( + create_nwc, + delete_nwc, + get_all_config_nwc, + get_budgets_nwc, + get_config_nwc, + get_nwc, + get_wallet_nwcs, + set_config_nwc, +) +from .models import NWCGetResponse, NWCRegistrationRequest from .permission import nwc_permissions +nwcprovider_api_router = APIRouter() + + # Get supported permissions -@nwcprovider_ext.get("/api/v1/permissions", status_code=HTTPStatus.OK) +@nwcprovider_api_router.get("/api/v1/permissions", status_code=HTTPStatus.OK) async def api_get_permissions( req: Request, wallet: WalletTypeInfo = Depends(require_admin_key), ) -> Dict: - return nwc_permissions - + return nwc_permissions + ## Get nwc keys associated with the wallet -@nwcprovider_ext.get("/api/v1/nwc", status_code=HTTPStatus.OK, response_model=List[NWCGetResponse]) +@nwcprovider_api_router.get( + "/api/v1/nwc", status_code=HTTPStatus.OK, response_model=List[NWCGetResponse] +) async def api_get_nwcs( req: Request, - includeExpired: Optional[bool] = False, - calculateSpendBudget: Optional[bool] = False, + include_expired: bool = False, + calculate_spent_budget: bool = False, wallet: WalletTypeInfo = Depends(require_admin_key), ): - + wallet_id = wallet.wallet.id - nwcs = await get_wallet_nwcs(wallet_id, includeExpired) + nwcs = await get_wallet_nwcs(wallet_id, include_expired) out = [] for nwc in nwcs: - budgets = await get_budgets_nwc(nwc.pubkey, calculateSpendBudget) - res = NWCGetResponse( - data=nwc, - budgets=budgets - ) + budgets = await get_budgets_nwc(nwc.pubkey, calculate_spent_budget) + res = NWCGetResponse(data=nwc, budgets=budgets) out.append(res) - return out - + return out # Get a nwc key -@nwcprovider_ext.get("/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK, response_model=NWCGetResponse) +@nwcprovider_api_router.get( + "/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK, response_model=NWCGetResponse +) async def api_get_nwc( req: Request, pubkey: str, - includeExpired: Optional[bool] = False, - wallet: WalletTypeInfo = Depends(require_admin_key) + include_expired: Optional[bool] = False, + wallet: WalletTypeInfo = Depends(require_admin_key), ) -> NWCGetResponse: wallet_id = wallet.wallet.id - nwc = await get_nwc(pubkey, wallet_id, includeExpired) - res = NWCGetResponse( - data=nwc, - budgets=await get_budgets_nwc(pubkey) - ) + nwc = await get_nwc(pubkey, wallet_id, include_expired) + if not nwc: + raise Exception("Pubkey has no associated wallet") + res = NWCGetResponse(data=nwc, budgets=await get_budgets_nwc(pubkey)) return res - + + # Get pairing url for given secret -@nwcprovider_ext.get("/api/v1/pairing/{secret}", status_code=HTTPStatus.OK, response_model=str) -async def api_get_pairing_url( - req: Request, - secret: str -) -> str: - pprivkey = await get_config_nwc("provider_key") +@nwcprovider_api_router.get( + "/api/v1/pairing/{secret}", status_code=HTTPStatus.OK, response_model=str +) +async def api_get_pairing_url(req: Request, secret: str) -> str: + pprivkey: Optional[str] = await get_config_nwc("provider_key") + if not pprivkey: + raise Exception("Extension is not configured") relay = await get_config_nwc("relay") - relay_alias = await get_config_nwc("relay_alias") + if not relay: + raise Exception("Extension is not configured") + relay_alias: Optional[str] = await get_config_nwc("relay_alias") if relay_alias: relay = relay_alias else: if relay == "nostrclient": scheme = req.url.scheme # http or https netloc = req.url.netloc # hostname and port - if scheme=="http": + if scheme == "http": scheme = "ws" else: scheme = "wss" netloc += "/nostrclient/api/v1/relay" - relay = f"{scheme}://{netloc}" + relay = f"{scheme}://{netloc}" psk = secp256k1.PrivateKey(bytes.fromhex(pprivkey)) ppk = psk.pubkey + if not ppk: + raise Exception("Error generating pubkey") ppubkey = ppk.serialize().hex()[2:] url = "nostr+walletconnect://" url += ppubkey - url += "?relay="+relay - url += "&secret="+secret - #lud16=? + url += "?relay=" + relay + url += "&secret=" + secret + # lud16=? return url + ## Register a new nwc key -@nwcprovider_ext.put("/api/v1/nwc/{pubkey}", status_code=HTTPStatus.CREATED, response_model=NWCGetResponse) +@nwcprovider_api_router.put( + "/api/v1/nwc/{pubkey}", + status_code=HTTPStatus.CREATED, + response_model=NWCGetResponse, +) async def api_register_nwc( - req: Request, - pubkey: str, + req: Request, + pubkey: str, registration_data: NWCRegistrationRequest, # Use the Pydantic model here - wallet: WalletTypeInfo = Depends(require_admin_key) + wallet: WalletTypeInfo = Depends(require_admin_key), ): wallet_id = wallet.wallet.id - nwc = await create_nwc(pubkey, wallet_id, registration_data.description, registration_data.expires_at, registration_data.permissions, registration_data.budgets) - budgets = await get_budgets_nwc(pubkey) - res = NWCGetResponse( - data=nwc, - budgets=budgets + nwc = await create_nwc( + pubkey, + wallet_id, + registration_data.description, + registration_data.expires_at, + registration_data.permissions, + registration_data.budgets, ) + budgets = await get_budgets_nwc(pubkey) + res = NWCGetResponse(data=nwc, budgets=budgets) return res # Delete a nwc key -@nwcprovider_ext.delete("/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK) +@nwcprovider_api_router.delete("/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK) async def api_delete_nwc( - req: Request, - pubkey: str, - wallet: WalletTypeInfo=Depends(require_admin_key) + req: Request, pubkey: str, wallet: WalletTypeInfo = Depends(require_admin_key) ): wallet_id = wallet.wallet.id await delete_nwc(pubkey, wallet_id) return JSONResponse(content={"message": f"NWC key {pubkey} deleted successfully."}) - - # Get config -@nwcprovider_ext.get("/api/v1/config", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]) +@nwcprovider_api_router.get( + "/api/v1/config", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) async def api_get_all_config_nwc( - req: Request, + req: Request, ): config = await get_all_config_nwc() return config # Get config -@nwcprovider_ext.get("/api/v1/config/{key}", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]) -async def api_get_config_nwc( - req: Request, - key:str -): +@nwcprovider_api_router.get( + "/api/v1/config/{key}", + status_code=HTTPStatus.OK, + dependencies=[Depends(check_admin)], +) +async def api_get_config_nwc(req: Request, key: str): config = await get_config_nwc(key) out = {} out[key] = config return out - # Set config -@nwcprovider_ext.post("/api/v1/config", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]) -async def api_set_config_nwc( - req: Request -): +@nwcprovider_api_router.post( + "/api/v1/config", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] +) +async def api_set_config_nwc(req: Request): data = await req.json() for key, value in data.items(): await set_config_nwc(key, value)