Merge remote-tracking branch 'origin/main' into gerty
This commit is contained in:
commit
82fea2a4ea
81 changed files with 1581 additions and 526 deletions
11
.env.example
11
.env.example
|
|
@ -37,11 +37,11 @@ LNBITS_RESERVE_FEE_PERCENT=1.0
|
||||||
LNBITS_SITE_TITLE="LNbits"
|
LNBITS_SITE_TITLE="LNbits"
|
||||||
LNBITS_SITE_TAGLINE="free and open-source lightning wallet"
|
LNBITS_SITE_TAGLINE="free and open-source lightning wallet"
|
||||||
LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'"
|
LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'"
|
||||||
# Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic
|
# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic
|
||||||
LNBITS_THEME_OPTIONS="classic, bitcoin, freedom, mint, autumn, monochrome, salvador"
|
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador"
|
||||||
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
|
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
|
||||||
|
|
||||||
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet
|
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet
|
||||||
# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
|
# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
|
||||||
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
|
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
|
||||||
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
|
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
|
||||||
|
|
@ -92,3 +92,8 @@ LNBITS_DENOMINATION=sats
|
||||||
# EclairWallet
|
# EclairWallet
|
||||||
ECLAIR_URL=http://127.0.0.1:8283
|
ECLAIR_URL=http://127.0.0.1:8283
|
||||||
ECLAIR_PASS=eclairpw
|
ECLAIR_PASS=eclairpw
|
||||||
|
|
||||||
|
# LnTipsWallet
|
||||||
|
# Enter /api in LightningTipBot to get your key
|
||||||
|
LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
|
||||||
|
LNTIPS_API_ENDPOINT=https://ln.tips
|
||||||
|
|
|
||||||
13
.github/workflows/formatting.yml
vendored
13
.github/workflows/formatting.yml
vendored
|
|
@ -9,9 +9,20 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
checks:
|
checks:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.9"]
|
||||||
|
poetry-version: ["1.2.1"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: abatilo/actions-poetry@v2.1.3
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
with:
|
||||||
|
poetry-version: ${{ matrix.poetry-version }}
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: poetry install
|
run: poetry install
|
||||||
- name: Check black
|
- name: Check black
|
||||||
|
|
|
||||||
8
.github/workflows/migrations.yml
vendored
8
.github/workflows/migrations.yml
vendored
|
|
@ -22,14 +22,18 @@ jobs:
|
||||||
--health-retries 5
|
--health-retries 5
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9"]
|
||||||
|
poetry-version: ["1.2.1"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- uses: abatilo/actions-poetry@v2.1.3
|
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
with:
|
||||||
|
poetry-version: ${{ matrix.poetry-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
poetry install
|
poetry install
|
||||||
|
|
|
||||||
8
.github/workflows/mypy.yml
vendored
8
.github/workflows/mypy.yml
vendored
|
|
@ -7,14 +7,18 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9"]
|
||||||
|
poetry-version: ["1.2.1"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- uses: abatilo/actions-poetry@v2.1.3
|
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
with:
|
||||||
|
poetry-version: ${{ matrix.poetry-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
poetry install
|
poetry install
|
||||||
|
|
|
||||||
26
.github/workflows/regtest.yml
vendored
26
.github/workflows/regtest.yml
vendored
|
|
@ -7,14 +7,18 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9"]
|
||||||
|
poetry-version: ["1.2.1"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- uses: abatilo/actions-poetry@v2.1.3
|
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
with:
|
||||||
|
poetry-version: ${{ matrix.poetry-version }}
|
||||||
- name: Setup Regtest
|
- name: Setup Regtest
|
||||||
run: |
|
run: |
|
||||||
docker build -t lnbitsdocker/lnbits-legend .
|
docker build -t lnbitsdocker/lnbits-legend .
|
||||||
|
|
@ -46,14 +50,18 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9"]
|
||||||
|
poetry-version: ["1.2.1"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- uses: abatilo/actions-poetry@v2.1.3
|
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
with:
|
||||||
|
poetry-version: ${{ matrix.poetry-version }}
|
||||||
- name: Setup Regtest
|
- name: Setup Regtest
|
||||||
run: |
|
run: |
|
||||||
docker build -t lnbitsdocker/lnbits-legend .
|
docker build -t lnbitsdocker/lnbits-legend .
|
||||||
|
|
@ -65,7 +73,6 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
poetry install
|
poetry install
|
||||||
poetry add grpcio protobuf
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
env:
|
env:
|
||||||
PYTHONUNBUFFERED: 1
|
PYTHONUNBUFFERED: 1
|
||||||
|
|
@ -87,14 +94,18 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9"]
|
||||||
|
poetry-version: ["1.2.1"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- uses: abatilo/actions-poetry@v2.1.3
|
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
with:
|
||||||
|
poetry-version: ${{ matrix.poetry-version }}
|
||||||
- name: Setup Regtest
|
- name: Setup Regtest
|
||||||
run: |
|
run: |
|
||||||
docker build -t lnbitsdocker/lnbits-legend .
|
docker build -t lnbitsdocker/lnbits-legend .
|
||||||
|
|
@ -106,7 +117,6 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
poetry install
|
poetry install
|
||||||
poetry add pyln-client
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
env:
|
env:
|
||||||
PYTHONUNBUFFERED: 1
|
PYTHONUNBUFFERED: 1
|
||||||
|
|
|
||||||
19
.github/workflows/tests.yml
vendored
19
.github/workflows/tests.yml
vendored
|
|
@ -7,7 +7,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9"]
|
||||||
|
poetry-version: ["1.2.1"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
|
@ -29,14 +30,18 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9"]
|
||||||
|
poetry-version: ["1.2.1"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- uses: abatilo/actions-poetry@v2.1.3
|
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
with:
|
||||||
|
poetry-version: ${{ matrix.poetry-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
env:
|
env:
|
||||||
VIRTUAL_ENV: ./venv
|
VIRTUAL_ENV: ./venv
|
||||||
|
|
@ -64,14 +69,18 @@ jobs:
|
||||||
--health-retries 5
|
--health-retries 5
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.9]
|
python-version: ["3.9"]
|
||||||
|
poetry-version: ["1.2.1"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- uses: abatilo/actions-poetry@v2.1.3
|
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
with:
|
||||||
|
poetry-version: ${{ matrix.poetry-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
poetry install
|
poetry install
|
||||||
|
|
|
||||||
12
Dockerfile
12
Dockerfile
|
|
@ -1,13 +1,23 @@
|
||||||
FROM python:3.9-slim
|
FROM python:3.9-slim
|
||||||
|
|
||||||
RUN apt-get clean
|
RUN apt-get clean
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y curl pkg-config build-essential
|
RUN apt-get install -y curl pkg-config build-essential
|
||||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
|
||||||
ENV PATH="/root/.local/bin:$PATH"
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN poetry config virtualenvs.create false
|
RUN poetry config virtualenvs.create false
|
||||||
RUN poetry install --no-dev --no-root
|
RUN poetry install --no-dev --no-root
|
||||||
RUN poetry run python build.py
|
RUN poetry run python build.py
|
||||||
|
|
||||||
|
ENV LNBITS_PORT="5000"
|
||||||
|
ENV LNBITS_HOST="0.0.0.0"
|
||||||
|
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
CMD ["poetry", "run", "lnbits", "--port", "5000", "--host", "0.0.0.0"]
|
|
||||||
|
CMD ["sh", "-c", "poetry run lnbits --port $LNBITS_PORT --host $LNBITS_HOST"]
|
||||||
|
|
|
||||||
87
docs/devs/websockets.md
Normal file
87
docs/devs/websockets.md
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
---
|
||||||
|
layout: default
|
||||||
|
parent: For developers
|
||||||
|
title: Websockets
|
||||||
|
nav_order: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
Websockets
|
||||||
|
=================
|
||||||
|
|
||||||
|
`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from the `copilot` extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension):
|
||||||
|
|
||||||
|
|
||||||
|
```sh
|
||||||
|
from fastapi import Request, WebSocket, WebSocketDisconnect
|
||||||
|
|
||||||
|
class ConnectionManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.active_connections: List[WebSocket] = []
|
||||||
|
|
||||||
|
async def connect(self, websocket: WebSocket, extension_id: str):
|
||||||
|
await websocket.accept()
|
||||||
|
websocket.id = extension_id
|
||||||
|
self.active_connections.append(websocket)
|
||||||
|
|
||||||
|
def disconnect(self, websocket: WebSocket):
|
||||||
|
self.active_connections.remove(websocket)
|
||||||
|
|
||||||
|
async def send_personal_message(self, message: str, extension_id: str):
|
||||||
|
for connection in self.active_connections:
|
||||||
|
if connection.id == extension_id:
|
||||||
|
await connection.send_text(message)
|
||||||
|
|
||||||
|
async def broadcast(self, message: str):
|
||||||
|
for connection in self.active_connections:
|
||||||
|
await connection.send_text(message)
|
||||||
|
|
||||||
|
|
||||||
|
manager = ConnectionManager()
|
||||||
|
|
||||||
|
|
||||||
|
@extension_ext.websocket("/ws/{extension_id}", name="extension.websocket_by_id")
|
||||||
|
async def websocket_endpoint(websocket: WebSocket, extension_id: str):
|
||||||
|
await manager.connect(websocket, extension_id)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data = await websocket.receive_text()
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
manager.disconnect(websocket)
|
||||||
|
|
||||||
|
|
||||||
|
async def updater(extension_id, data):
|
||||||
|
extension = await get_extension(extension_id)
|
||||||
|
if not extension:
|
||||||
|
return
|
||||||
|
await manager.send_personal_message(f"{data}", extension_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
Example vue-js function for listening to the websocket:
|
||||||
|
|
||||||
|
```
|
||||||
|
initWs: async function () {
|
||||||
|
if (location.protocol !== 'http:') {
|
||||||
|
localUrl =
|
||||||
|
'wss://' +
|
||||||
|
document.domain +
|
||||||
|
':' +
|
||||||
|
location.port +
|
||||||
|
'/extension/ws/' +
|
||||||
|
self.extension.id
|
||||||
|
} else {
|
||||||
|
localUrl =
|
||||||
|
'ws://' +
|
||||||
|
document.domain +
|
||||||
|
':' +
|
||||||
|
location.port +
|
||||||
|
'/extension/ws/' +
|
||||||
|
self.extension.id
|
||||||
|
}
|
||||||
|
this.ws = new WebSocket(localUrl)
|
||||||
|
this.ws.addEventListener('message', async ({data}) => {
|
||||||
|
const res = JSON.parse(data.toString())
|
||||||
|
console.log(res)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
@ -18,21 +18,25 @@ If you have problems installing LNbits using these instructions, please have a l
|
||||||
git clone https://github.com/lnbits/lnbits-legend.git
|
git clone https://github.com/lnbits/lnbits-legend.git
|
||||||
cd lnbits-legend/
|
cd lnbits-legend/
|
||||||
|
|
||||||
# for making sure python 3.9 is installed, skip if installed
|
# for making sure python 3.9 is installed, skip if installed. To check your installed version: python3 --version
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install software-properties-common
|
sudo apt install software-properties-common
|
||||||
sudo add-apt-repository ppa:deadsnakes/ppa
|
sudo add-apt-repository ppa:deadsnakes/ppa
|
||||||
sudo apt install python3.9 python3.9-distutils
|
sudo apt install python3.9 python3.9-distutils
|
||||||
|
|
||||||
curl -sSL https://install.python-poetry.org | python3 -
|
curl -sSL https://install.python-poetry.org | python3 -
|
||||||
export PATH="/home/ubuntu/.local/bin:$PATH" # or whatever is suggested in the poetry install notes printed to terminal
|
# Once the above poetry install is completed, use the installation path printed to terminal and replace in the following command
|
||||||
|
export PATH="/home/user/.local/bin:$PATH"
|
||||||
|
# Next command, you can exchange with python3.10 or newer versions.
|
||||||
|
# Identify your version with python3 --version and specify in the next line
|
||||||
|
# command is only needed when your default python is not ^3.9 or ^3.10
|
||||||
poetry env use python3.9
|
poetry env use python3.9
|
||||||
poetry install --no-dev
|
poetry install --only main
|
||||||
poetry run python build.py
|
|
||||||
|
|
||||||
mkdir data
|
mkdir data
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
nano .env # set funding source
|
# set funding source amongst other options
|
||||||
|
nano .env
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Running the server
|
#### Running the server
|
||||||
|
|
@ -40,6 +44,8 @@ nano .env # set funding source
|
||||||
```sh
|
```sh
|
||||||
poetry run lnbits
|
poetry run lnbits
|
||||||
# To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0'
|
# To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0'
|
||||||
|
# adding --debug in the start-up command above to help your troubleshooting and generate a more verbose output
|
||||||
|
# Note that you have to add the line DEBUG=true in your .env file, too.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Option 2: Nix
|
## Option 2: Nix
|
||||||
|
|
@ -292,6 +298,43 @@ Save the file and run the following commands:
|
||||||
sudo systemctl enable lnbits.service
|
sudo systemctl enable lnbits.service
|
||||||
sudo systemctl start lnbits.service
|
sudo systemctl start lnbits.service
|
||||||
```
|
```
|
||||||
|
## Reverse proxy with automatic https using Caddy
|
||||||
|
|
||||||
|
Use Caddy to make your LNbits install accessible over clearnet with a domain and https cert.
|
||||||
|
|
||||||
|
Point your domain at the IP of the server you're running LNbits on, by making an `A` record.
|
||||||
|
|
||||||
|
Install Caddy on the server
|
||||||
|
https://caddyserver.com/docs/install#debian-ubuntu-raspbian
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo caddy stop
|
||||||
|
```
|
||||||
|
Create a Caddyfile
|
||||||
|
```
|
||||||
|
sudo nano Caddyfile
|
||||||
|
```
|
||||||
|
Assuming your LNbits is running on port `5000` add:
|
||||||
|
```
|
||||||
|
yourdomain.com {
|
||||||
|
handle /api/v1/payments/sse* {
|
||||||
|
reverse_proxy 0.0.0.0:5000 {
|
||||||
|
header_up X-Forwarded-Host yourdomain.com
|
||||||
|
transport http {
|
||||||
|
keepalive off
|
||||||
|
compression off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reverse_proxy 0.0.0.0:5000 {
|
||||||
|
header_up X-Forwarded-Host yourdomain.com
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Save and exit `CTRL + x`
|
||||||
|
```
|
||||||
|
sudo caddy start
|
||||||
|
```
|
||||||
|
|
||||||
## Running behind an apache2 reverse proxy over https
|
## Running behind an apache2 reverse proxy over https
|
||||||
Install apache2 and enable apache2 mods
|
Install apache2 and enable apache2 mods
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ A backend wallet can be configured using the following LNbits environment variab
|
||||||
|
|
||||||
### CoreLightning
|
### CoreLightning
|
||||||
|
|
||||||
Using this wallet requires the installation of the `pylightning` Python package.
|
|
||||||
|
|
||||||
- `LNBITS_BACKEND_WALLET_CLASS`: **CoreLightningWallet**
|
- `LNBITS_BACKEND_WALLET_CLASS`: **CoreLightningWallet**
|
||||||
- `CORELIGHTNING_RPC`: /file/path/lightning-rpc
|
- `CORELIGHTNING_RPC`: /file/path/lightning-rpc
|
||||||
|
|
||||||
|
|
@ -39,8 +37,6 @@ or
|
||||||
|
|
||||||
### LND (gRPC)
|
### LND (gRPC)
|
||||||
|
|
||||||
Using this wallet requires the installation of the `grpcio` and `protobuf` Python packages.
|
|
||||||
|
|
||||||
- `LNBITS_BACKEND_WALLET_CLASS`: **LndWallet**
|
- `LNBITS_BACKEND_WALLET_CLASS`: **LndWallet**
|
||||||
- `LND_GRPC_ENDPOINT`: ip_address
|
- `LND_GRPC_ENDPOINT`: ip_address
|
||||||
- `LND_GRPC_PORT`: port
|
- `LND_GRPC_PORT`: port
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ from .tasks import (
|
||||||
check_pending_payments,
|
check_pending_payments,
|
||||||
internal_invoice_listener,
|
internal_invoice_listener,
|
||||||
invoice_listener,
|
invoice_listener,
|
||||||
run_deferred_async,
|
|
||||||
webhook_handler,
|
webhook_handler,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -127,7 +126,7 @@ def check_funding_source(app: FastAPI) -> None:
|
||||||
logger.info("Retrying connection to backend in 5 seconds...")
|
logger.info("Retrying connection to backend in 5 seconds...")
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
signal.signal(signal.SIGINT, original_sigint_handler)
|
signal.signal(signal.SIGINT, original_sigint_handler)
|
||||||
logger.info(
|
logger.success(
|
||||||
f"✔️ Backend {WALLET.__class__.__name__} connected and with a balance of {balance} msat."
|
f"✔️ Backend {WALLET.__class__.__name__} connected and with a balance of {balance} msat."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -185,7 +184,7 @@ def register_async_tasks(app):
|
||||||
loop.create_task(catch_everything_and_restart(invoice_listener))
|
loop.create_task(catch_everything_and_restart(invoice_listener))
|
||||||
loop.create_task(catch_everything_and_restart(internal_invoice_listener))
|
loop.create_task(catch_everything_and_restart(internal_invoice_listener))
|
||||||
await register_task_listeners()
|
await register_task_listeners()
|
||||||
await run_deferred_async()
|
# await run_deferred_async() # calle: doesn't do anyting?
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
@app.on_event("shutdown")
|
||||||
async def stop_listeners():
|
async def stop_listeners():
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,7 @@ async def delete_expired_invoices(
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
logger.debug(f"Checking expiry of {len(rows)} invoices")
|
logger.debug(f"Checking expiry of {len(rows)} invoices")
|
||||||
for (payment_request,) in rows:
|
for i, (payment_request,) in enumerate(rows):
|
||||||
try:
|
try:
|
||||||
invoice = bolt11.decode(payment_request)
|
invoice = bolt11.decode(payment_request)
|
||||||
except:
|
except:
|
||||||
|
|
@ -343,7 +343,7 @@ async def delete_expired_invoices(
|
||||||
if expiration_date > datetime.datetime.utcnow():
|
if expiration_date > datetime.datetime.utcnow():
|
||||||
continue
|
continue
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Deleting expired invoice: {invoice.payment_hash} (expired: {expiration_date})"
|
f"Deleting expired invoice {i}/{len(rows)}: {invoice.payment_hash} (expired: {expiration_date})"
|
||||||
)
|
)
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -186,9 +186,9 @@ async def pay_invoice(
|
||||||
)
|
)
|
||||||
|
|
||||||
# notify receiver asynchronously
|
# notify receiver asynchronously
|
||||||
|
|
||||||
from lnbits.tasks import internal_invoice_queue
|
from lnbits.tasks import internal_invoice_queue
|
||||||
|
|
||||||
|
logger.debug(f"enqueuing internal invoice {internal_checking_id}")
|
||||||
await internal_invoice_queue.put(internal_checking_id)
|
await internal_invoice_queue.put(internal_checking_id)
|
||||||
else:
|
else:
|
||||||
logger.debug(f"backend: sending payment {temp_id}")
|
logger.debug(f"backend: sending payment {temp_id}")
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ const CACHE_VERSION = 1
|
||||||
const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-`
|
const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-`
|
||||||
|
|
||||||
const getApiKey = request => {
|
const getApiKey = request => {
|
||||||
return request.headers.get('X-Api-Key') || 'none'
|
let api_key = request.headers.get('X-Api-Key')
|
||||||
|
if (!api_key || api_key == 'undefined') {
|
||||||
|
api_key = 'no_api_key'
|
||||||
|
}
|
||||||
|
return api_key
|
||||||
}
|
}
|
||||||
|
|
||||||
// on activation we clean up the previously registered service workers
|
// on activation we clean up the previously registered service workers
|
||||||
|
|
@ -26,8 +30,10 @@ self.addEventListener('activate', evt =>
|
||||||
// If no response is found, it populates the runtime cache with the response
|
// If no response is found, it populates the runtime cache with the response
|
||||||
// from the network before returning it to the page.
|
// from the network before returning it to the page.
|
||||||
self.addEventListener('fetch', event => {
|
self.addEventListener('fetch', event => {
|
||||||
// Skip cross-origin requests, like those for Google Analytics.
|
|
||||||
if (
|
if (
|
||||||
|
!event.request.url.startsWith(
|
||||||
|
self.location.origin + '/api/v1/payments/sse'
|
||||||
|
) &&
|
||||||
event.request.url.startsWith(self.location.origin) &&
|
event.request.url.startsWith(self.location.origin) &&
|
||||||
event.request.method == 'GET'
|
event.request.method == 'GET'
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,43 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import List
|
from typing import Dict
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.helpers import get_current_extension_name
|
||||||
|
from lnbits.tasks import SseListenersDict, register_invoice_listener
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
from .crud import get_balance_notify
|
from .crud import get_balance_notify
|
||||||
from .models import Payment
|
from .models import Payment
|
||||||
|
|
||||||
api_invoice_listeners: List[asyncio.Queue] = []
|
api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict(
|
||||||
|
"api_invoice_listeners"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def register_task_listeners():
|
async def register_task_listeners():
|
||||||
|
"""
|
||||||
|
Registers an invoice listener queue for the core tasks.
|
||||||
|
Incoming payaments in this queue will eventually trigger the signals sent to all other extensions
|
||||||
|
and fulfill other core tasks such as dispatching webhooks.
|
||||||
|
"""
|
||||||
invoice_paid_queue = asyncio.Queue(5)
|
invoice_paid_queue = asyncio.Queue(5)
|
||||||
register_invoice_listener(invoice_paid_queue)
|
# we register invoice_paid_queue to receive all incoming invoices
|
||||||
|
register_invoice_listener(invoice_paid_queue, "core/tasks.py")
|
||||||
|
# register a worker that will react to invoices
|
||||||
asyncio.create_task(wait_for_paid_invoices(invoice_paid_queue))
|
asyncio.create_task(wait_for_paid_invoices(invoice_paid_queue))
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
|
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
|
||||||
|
"""
|
||||||
|
This worker dispatches events to all extensions, dispatches webhooks and balance notifys.
|
||||||
|
"""
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_paid_queue.get()
|
payment = await invoice_paid_queue.get()
|
||||||
logger.debug("received invoice paid event")
|
logger.trace("received invoice paid event")
|
||||||
# send information to sse channel
|
# send information to sse channel
|
||||||
await dispatch_invoice_listener(payment)
|
await dispatch_api_invoice_listeners(payment)
|
||||||
|
|
||||||
# dispatch webhook
|
# dispatch webhook
|
||||||
if payment.webhook and not payment.webhook_status:
|
if payment.webhook and not payment.webhook_status:
|
||||||
|
|
@ -41,16 +54,23 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def dispatch_invoice_listener(payment: Payment):
|
async def dispatch_api_invoice_listeners(payment: Payment):
|
||||||
for send_channel in api_invoice_listeners:
|
"""
|
||||||
|
Emits events to invoice listener subscribed from the API.
|
||||||
|
"""
|
||||||
|
for chan_name, send_channel in api_invoice_listeners.items():
|
||||||
try:
|
try:
|
||||||
|
logger.debug(f"sending invoice paid event to {chan_name}")
|
||||||
send_channel.put_nowait(payment)
|
send_channel.put_nowait(payment)
|
||||||
except asyncio.QueueFull:
|
except asyncio.QueueFull:
|
||||||
logger.debug("removing sse listener", send_channel)
|
logger.error(f"removing sse listener {send_channel}:{chan_name}")
|
||||||
api_invoice_listeners.remove(send_channel)
|
api_invoice_listeners.pop(chan_name)
|
||||||
|
|
||||||
|
|
||||||
async def dispatch_webhook(payment: Payment):
|
async def dispatch_webhook(payment: Payment):
|
||||||
|
"""
|
||||||
|
Dispatches the webhook to the webhook url.
|
||||||
|
"""
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
data = payment.dict()
|
data = payment.dict()
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,17 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<a href="https://mynodebtc.com">
|
||||||
|
<q-img
|
||||||
|
contain
|
||||||
|
:src="($q.dark.isActive) ? '/static/images/mynode.png' : '/static/images/mynodel.png'"
|
||||||
|
></q-img>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col q-pl-md"> </div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ import binascii
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Dict, List, Optional, Tuple, Union
|
from typing import Dict, List, Optional, Tuple, Union
|
||||||
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
|
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
import httpx
|
import httpx
|
||||||
import pyqrcode
|
import pyqrcode
|
||||||
from fastapi import Depends, Header, Query, Request
|
from fastapi import Depends, Header, Query, Request
|
||||||
|
|
@ -16,7 +18,7 @@ from fastapi.params import Body
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pydantic.fields import Field
|
from pydantic.fields import Field
|
||||||
from sse_starlette.sse import EventSourceResponse
|
from sse_starlette.sse import EventSourceResponse, ServerSentEvent
|
||||||
from starlette.responses import HTMLResponse, StreamingResponse
|
from starlette.responses import HTMLResponse, StreamingResponse
|
||||||
|
|
||||||
from lnbits import bolt11, lnurl
|
from lnbits import bolt11, lnurl
|
||||||
|
|
@ -366,37 +368,48 @@ async def api_payments_pay_lnurl(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def subscribe(request: Request, wallet: Wallet):
|
async def subscribe_wallet_invoices(request: Request, wallet: Wallet):
|
||||||
|
"""
|
||||||
|
Subscribe to new invoices for a wallet. Can be wrapped in EventSourceResponse.
|
||||||
|
Listenes invoming payments for a wallet and yields jsons with payment details.
|
||||||
|
"""
|
||||||
this_wallet_id = wallet.id
|
this_wallet_id = wallet.id
|
||||||
|
|
||||||
payment_queue: asyncio.Queue[Payment] = asyncio.Queue(0)
|
payment_queue: asyncio.Queue[Payment] = asyncio.Queue(0)
|
||||||
|
|
||||||
logger.debug("adding sse listener", payment_queue)
|
uid = f"{this_wallet_id}_{str(uuid.uuid4())[:8]}"
|
||||||
api_invoice_listeners.append(payment_queue)
|
logger.debug(f"adding sse listener for wallet: {uid}")
|
||||||
|
api_invoice_listeners[uid] = payment_queue
|
||||||
|
|
||||||
send_queue: asyncio.Queue[Tuple[str, Payment]] = asyncio.Queue(0)
|
send_queue: asyncio.Queue[Tuple[str, Payment]] = asyncio.Queue(0)
|
||||||
|
|
||||||
async def payment_received() -> None:
|
async def payment_received() -> None:
|
||||||
while True:
|
while True:
|
||||||
payment: Payment = await payment_queue.get()
|
try:
|
||||||
if payment.wallet_id == this_wallet_id:
|
async with async_timeout.timeout(1):
|
||||||
logger.debug("payment received", payment)
|
payment: Payment = await payment_queue.get()
|
||||||
await send_queue.put(("payment-received", payment))
|
if payment.wallet_id == this_wallet_id:
|
||||||
|
logger.debug("sse listener: payment receieved", payment)
|
||||||
|
await send_queue.put(("payment-received", payment))
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
asyncio.create_task(payment_received())
|
task = asyncio.create_task(payment_received())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
|
if await request.is_disconnected():
|
||||||
|
await request.close()
|
||||||
|
break
|
||||||
typ, data = await send_queue.get()
|
typ, data = await send_queue.get()
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
jdata = json.dumps(dict(data.dict(), pending=False))
|
jdata = json.dumps(dict(data.dict(), pending=False))
|
||||||
|
|
||||||
# yield dict(id=1, event="this", data="1234")
|
|
||||||
# await asyncio.sleep(2)
|
|
||||||
yield dict(data=jdata, event=typ)
|
yield dict(data=jdata, event=typ)
|
||||||
# yield dict(data=jdata.encode("utf-8"), event=typ.encode("utf-8"))
|
except asyncio.CancelledError as e:
|
||||||
except asyncio.CancelledError:
|
logger.debug(f"CancelledError on listener {uid}: {e}")
|
||||||
|
api_invoice_listeners.pop(uid)
|
||||||
|
task.cancel()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -405,7 +418,9 @@ async def api_payments_sse(
|
||||||
request: Request, wallet: WalletTypeInfo = Depends(get_key_type)
|
request: Request, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||||
):
|
):
|
||||||
return EventSourceResponse(
|
return EventSourceResponse(
|
||||||
subscribe(request, wallet.wallet), ping=20, media_type="text/event-stream"
|
subscribe_wallet_invoices(request, wallet.wallet),
|
||||||
|
ping=20,
|
||||||
|
media_type="text/event-stream",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ async def api_public_payment_longpolling(payment_hash):
|
||||||
|
|
||||||
payment_queue = asyncio.Queue(0)
|
payment_queue = asyncio.Queue(0)
|
||||||
|
|
||||||
logger.debug("adding standalone invoice listener", payment_hash, payment_queue)
|
logger.debug(f"adding standalone invoice listener for hash: {payment_hash}")
|
||||||
api_invoice_listeners.append(payment_queue)
|
api_invoice_listeners[payment_hash] = payment_queue
|
||||||
|
|
||||||
response = None
|
response = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,12 @@ class Compat:
|
||||||
return ""
|
return ""
|
||||||
return "<nothing>"
|
return "<nothing>"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def big_int(self) -> str:
|
||||||
|
if self.type in {POSTGRES}:
|
||||||
|
return "BIGINT"
|
||||||
|
return "INT"
|
||||||
|
|
||||||
|
|
||||||
class Connection(Compat):
|
class Connection(Compat):
|
||||||
def __init__(self, conn: AsyncConnection, txn, typ, name, schema):
|
def __init__(self, conn: AsyncConnection, txn, typ, name, schema):
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ async def m001_initial(db):
|
||||||
)
|
)
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE boltcards.hits (
|
CREATE TABLE boltcards.hits (
|
||||||
id TEXT PRIMARY KEY UNIQUE,
|
id TEXT PRIMARY KEY UNIQUE,
|
||||||
card_id TEXT NOT NULL,
|
card_id TEXT NOT NULL,
|
||||||
|
|
@ -38,7 +38,7 @@ async def m001_initial(db):
|
||||||
useragent TEXT,
|
useragent TEXT,
|
||||||
old_ctr INT NOT NULL DEFAULT 0,
|
old_ctr INT NOT NULL DEFAULT 0,
|
||||||
new_ctr INT NOT NULL DEFAULT 0,
|
new_ctr INT NOT NULL DEFAULT 0,
|
||||||
amount INT NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
time TIMESTAMP NOT NULL DEFAULT """
|
time TIMESTAMP NOT NULL DEFAULT """
|
||||||
+ db.timestamp_now
|
+ db.timestamp_now
|
||||||
+ """
|
+ """
|
||||||
|
|
@ -47,11 +47,11 @@ async def m001_initial(db):
|
||||||
)
|
)
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE boltcards.refunds (
|
CREATE TABLE boltcards.refunds (
|
||||||
id TEXT PRIMARY KEY UNIQUE,
|
id TEXT PRIMARY KEY UNIQUE,
|
||||||
hit_id TEXT NOT NULL,
|
hit_id TEXT NOT NULL,
|
||||||
refund_amount INT NOT NULL,
|
refund_amount {db.big_int} NOT NULL,
|
||||||
time TIMESTAMP NOT NULL DEFAULT """
|
time TIMESTAMP NOT NULL DEFAULT """
|
||||||
+ db.timestamp_now
|
+ db.timestamp_now
|
||||||
+ """
|
+ """
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import httpx
|
||||||
|
|
||||||
from lnbits.core import db as core_db
|
from lnbits.core import db as core_db
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import create_refund, get_hit
|
from .crud import create_refund, get_hit
|
||||||
|
|
@ -12,7 +13,7 @@ from .crud import create_refund, get_hit
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ from .models import (
|
||||||
from .utils import check_balance, get_timestamp, req_wrap
|
from .utils import check_balance, get_timestamp, req_wrap
|
||||||
|
|
||||||
net = NETWORKS[BOLTZ_NETWORK]
|
net = NETWORKS[BOLTZ_NETWORK]
|
||||||
logger.debug(f"BOLTZ_URL: {BOLTZ_URL}")
|
logger.trace(f"BOLTZ_URL: {BOLTZ_URL}")
|
||||||
logger.debug(f"Bitcoin Network: {net['name']}")
|
logger.trace(f"Bitcoin Network: {net['name']}")
|
||||||
|
|
||||||
|
|
||||||
async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
|
async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS
|
||||||
|
|
||||||
from .utils import req_wrap
|
from .utils import req_wrap
|
||||||
|
|
||||||
logger.debug(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}")
|
logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}")
|
||||||
logger.debug(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}")
|
logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}")
|
||||||
|
|
||||||
websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws"
|
websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
async def m001_initial(db):
|
async def m001_initial(db):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE boltz.submarineswap (
|
CREATE TABLE boltz.submarineswap (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
payment_hash TEXT NOT NULL,
|
payment_hash TEXT NOT NULL,
|
||||||
amount INT NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
status TEXT NOT NULL,
|
status TEXT NOT NULL,
|
||||||
boltz_id TEXT NOT NULL,
|
boltz_id TEXT NOT NULL,
|
||||||
refund_address TEXT NOT NULL,
|
refund_address TEXT NOT NULL,
|
||||||
refund_privkey TEXT NOT NULL,
|
refund_privkey TEXT NOT NULL,
|
||||||
expected_amount INT NOT NULL,
|
expected_amount {db.big_int} NOT NULL,
|
||||||
timeout_block_height INT NOT NULL,
|
timeout_block_height INT NOT NULL,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
bip21 TEXT NOT NULL,
|
bip21 TEXT NOT NULL,
|
||||||
|
|
@ -22,12 +22,12 @@ async def m001_initial(db):
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE boltz.reverse_submarineswap (
|
CREATE TABLE boltz.reverse_submarineswap (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
onchain_address TEXT NOT NULL,
|
onchain_address TEXT NOT NULL,
|
||||||
amount INT NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
instant_settlement BOOLEAN NOT NULL,
|
instant_settlement BOOLEAN NOT NULL,
|
||||||
status TEXT NOT NULL,
|
status TEXT NOT NULL,
|
||||||
boltz_id TEXT NOT NULL,
|
boltz_id TEXT NOT NULL,
|
||||||
|
|
@ -37,7 +37,7 @@ async def m001_initial(db):
|
||||||
claim_privkey TEXT NOT NULL,
|
claim_privkey TEXT NOT NULL,
|
||||||
lockup_address TEXT NOT NULL,
|
lockup_address TEXT NOT NULL,
|
||||||
invoice TEXT NOT NULL,
|
invoice TEXT NOT NULL,
|
||||||
onchain_amount INT NOT NULL,
|
onchain_amount {db.big_int} NOT NULL,
|
||||||
time TIMESTAMP NOT NULL DEFAULT """
|
time TIMESTAMP NOT NULL DEFAULT """
|
||||||
+ db.timestamp_now
|
+ db.timestamp_now
|
||||||
+ """
|
+ """
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.core.services import check_transaction_status
|
from lnbits.core.services import check_transaction_status
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .boltz import (
|
from .boltz import (
|
||||||
|
|
@ -56,7 +57,7 @@ async def check_for_pending_swaps():
|
||||||
swap_status = get_swap_status(swap)
|
swap_status = get_swap_status(swap)
|
||||||
# should only happen while development when regtest is reset
|
# should only happen while development when regtest is reset
|
||||||
if swap_status.exists is False:
|
if swap_status.exists is False:
|
||||||
logger.warning(f"Boltz - swap: {swap.boltz_id} does not exist.")
|
logger.debug(f"Boltz - swap: {swap.boltz_id} does not exist.")
|
||||||
await update_swap_status(swap.id, "failed")
|
await update_swap_status(swap.id, "failed")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -72,7 +73,7 @@ async def check_for_pending_swaps():
|
||||||
else:
|
else:
|
||||||
if swap_status.hit_timeout:
|
if swap_status.hit_timeout:
|
||||||
if not swap_status.has_lockup:
|
if not swap_status.has_lockup:
|
||||||
logger.warning(
|
logger.debug(
|
||||||
f"Boltz - swap: {swap.id} hit timeout, but no lockup tx..."
|
f"Boltz - swap: {swap.id} hit timeout, but no lockup tx..."
|
||||||
)
|
)
|
||||||
await update_swap_status(swap.id, "timeout")
|
await update_swap_status(swap.id, "timeout")
|
||||||
|
|
@ -127,7 +128,7 @@ async def check_for_pending_swaps():
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.core import db as core_db
|
from lnbits.core import db as core_db
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_copilot
|
from .crud import get_copilot
|
||||||
|
|
@ -15,7 +16,7 @@ from .views import updater
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ async def m001_initial_invoices(db):
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
invoice_id TEXT NOT NULL,
|
invoice_id TEXT NOT NULL,
|
||||||
|
|
||||||
amount INT NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
|
|
||||||
time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import update_jukebox_payment
|
from .crud import update_jukebox_payment
|
||||||
|
|
@ -8,7 +9,7 @@ from .crud import update_jukebox_payment
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@ import json
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core import db as core_db
|
from lnbits.core import db as core_db
|
||||||
from lnbits.core.crud import create_payment
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from lnbits.core.services import create_invoice, pay_invoice
|
||||||
from lnbits.tasks import internal_invoice_listener, register_invoice_listener
|
from lnbits.helpers import get_current_extension_name
|
||||||
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_livestream_by_track, get_producer, get_track
|
from .crud import get_livestream_by_track, get_producer, get_track
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
@ -44,44 +44,20 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
# now we make a special kind of internal transfer
|
# now we make a special kind of internal transfer
|
||||||
amount = int(payment.amount * (100 - ls.fee_pct) / 100)
|
amount = int(payment.amount * (100 - ls.fee_pct) / 100)
|
||||||
|
|
||||||
# mark the original payment with two extra keys, "shared_with" and "received"
|
payment_hash, payment_request = await create_invoice(
|
||||||
# (this prevents us from doing this process again and it's informative)
|
wallet_id=tpos.tip_wallet,
|
||||||
# and reduce it by the amount we're going to send to the producer
|
amount=amount, # sats
|
||||||
await core_db.execute(
|
internal=True,
|
||||||
"""
|
|
||||||
UPDATE apipayments
|
|
||||||
SET extra = ?, amount = ?
|
|
||||||
WHERE hash = ?
|
|
||||||
AND checking_id NOT LIKE 'internal_%'
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
json.dumps(
|
|
||||||
dict(
|
|
||||||
**payment.extra,
|
|
||||||
shared_with=[producer.name, producer.id],
|
|
||||||
received=payment.amount,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
payment.amount - amount,
|
|
||||||
payment.payment_hash,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# perform an internal transfer using the same payment_hash to the producer wallet
|
|
||||||
internal_checking_id = f"internal_{urlsafe_short_hash()}"
|
|
||||||
await create_payment(
|
|
||||||
wallet_id=producer.wallet,
|
|
||||||
checking_id=internal_checking_id,
|
|
||||||
payment_request="",
|
|
||||||
payment_hash=payment.payment_hash,
|
|
||||||
amount=amount,
|
|
||||||
memo=f"Revenue from '{track.name}'.",
|
memo=f"Revenue from '{track.name}'.",
|
||||||
pending=False,
|
|
||||||
)
|
)
|
||||||
|
logger.debug(f"livestream: producer invoice created: {payment_hash}")
|
||||||
|
|
||||||
# manually send this for now
|
checking_id = await pay_invoice(
|
||||||
# await internal_invoice_paid.send(internal_checking_id)
|
payment_request=payment_request,
|
||||||
await internal_invoice_listener.put(internal_checking_id)
|
wallet_id=payment.wallet_id,
|
||||||
|
extra={"tag": "livestream"},
|
||||||
|
)
|
||||||
|
logger.debug(f"livestream: producer invoice paid: {checking_id}")
|
||||||
|
|
||||||
# so the flow is the following:
|
# so the flow is the following:
|
||||||
# - we receive, say, 1000 satoshis
|
# - we receive, say, 1000 satoshis
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import asyncio
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_address, get_domain, set_address_paid, set_address_renewed
|
from .crud import get_address, get_domain, set_address_paid, set_address_renewed
|
||||||
|
|
@ -10,7 +11,7 @@ from .crud import get_address, get_domain, set_address_paid, set_address_renewed
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import asyncio
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_ticket, set_ticket_paid
|
from .crud import get_ticket, set_ticket_paid
|
||||||
|
|
@ -10,7 +11,7 @@ from .crud import get_ticket, set_ticket_paid
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from lnbits.db import Database
|
from lnbits.db import Database
|
||||||
from lnbits.helpers import template_renderer
|
from lnbits.helpers import template_renderer
|
||||||
|
from lnbits.tasks import catch_everything_and_restart
|
||||||
|
|
||||||
db = Database("ext_lnurldevice")
|
db = Database("ext_lnurldevice")
|
||||||
|
|
||||||
|
|
@ -13,5 +16,11 @@ def lnurldevice_renderer():
|
||||||
|
|
||||||
|
|
||||||
from .lnurl import * # noqa
|
from .lnurl import * # noqa
|
||||||
|
from .tasks import wait_for_paid_invoices
|
||||||
from .views import * # noqa
|
from .views import * # noqa
|
||||||
from .views_api import * # noqa
|
from .views_api import * # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def lnurldevice_start():
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ async def create_lnurldevice(
|
||||||
wallet,
|
wallet,
|
||||||
currency,
|
currency,
|
||||||
device,
|
device,
|
||||||
profit
|
profit,
|
||||||
|
amount
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
lnurldevice_id,
|
lnurldevice_id,
|
||||||
|
|
@ -34,6 +35,7 @@ async def create_lnurldevice(
|
||||||
data.currency,
|
data.currency,
|
||||||
data.device,
|
data.device,
|
||||||
data.profit,
|
data.profit,
|
||||||
|
data.amount,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return await get_lnurldevice(lnurldevice_id)
|
return await get_lnurldevice(lnurldevice_id)
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,32 @@ async def lnurl_v1_params(
|
||||||
if device.device == "atm":
|
if device.device == "atm":
|
||||||
if paymentcheck:
|
if paymentcheck:
|
||||||
return {"status": "ERROR", "reason": f"Payment already claimed"}
|
return {"status": "ERROR", "reason": f"Payment already claimed"}
|
||||||
|
if device.device == "switch":
|
||||||
|
|
||||||
|
price_msat = (
|
||||||
|
await fiat_amount_as_satoshis(float(device.profit), device.currency)
|
||||||
|
if device.currency != "sat"
|
||||||
|
else amount_in_cent
|
||||||
|
) * 1000
|
||||||
|
|
||||||
|
lnurldevicepayment = await create_lnurldevicepayment(
|
||||||
|
deviceid=device.id,
|
||||||
|
payload="bla",
|
||||||
|
sats=price_msat,
|
||||||
|
pin=1,
|
||||||
|
payhash="bla",
|
||||||
|
)
|
||||||
|
if not lnurldevicepayment:
|
||||||
|
return {"status": "ERROR", "reason": "Could not create payment."}
|
||||||
|
return {
|
||||||
|
"tag": "payRequest",
|
||||||
|
"callback": request.url_for(
|
||||||
|
"lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id
|
||||||
|
),
|
||||||
|
"minSendable": price_msat,
|
||||||
|
"maxSendable": price_msat,
|
||||||
|
"metadata": await device.lnurlpay_metadata(),
|
||||||
|
}
|
||||||
if len(p) % 4 > 0:
|
if len(p) % 4 > 0:
|
||||||
p += "=" * (4 - (len(p) % 4))
|
p += "=" * (4 - (len(p) % 4))
|
||||||
|
|
||||||
|
|
@ -184,22 +209,42 @@ async def lnurl_callback(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found."
|
status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found."
|
||||||
)
|
)
|
||||||
if pr:
|
if device.device == "atm":
|
||||||
if lnurldevicepayment.id != k1:
|
if not pr:
|
||||||
return {"status": "ERROR", "reason": "Bad K1"}
|
raise HTTPException(
|
||||||
if lnurldevicepayment.payhash != "payment_hash":
|
status_code=HTTPStatus.FORBIDDEN, detail="No payment request"
|
||||||
return {"status": "ERROR", "reason": f"Payment already claimed"}
|
)
|
||||||
|
else:
|
||||||
|
if lnurldevicepayment.id != k1:
|
||||||
|
return {"status": "ERROR", "reason": "Bad K1"}
|
||||||
|
if lnurldevicepayment.payhash != "payment_hash":
|
||||||
|
return {"status": "ERROR", "reason": f"Payment already claimed"}
|
||||||
lnurldevicepayment = await update_lnurldevicepayment(
|
lnurldevicepayment = await update_lnurldevicepayment(
|
||||||
lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload
|
lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload
|
||||||
)
|
)
|
||||||
|
|
||||||
await pay_invoice(
|
await pay_invoice(
|
||||||
|
wallet_id=device.wallet,
|
||||||
|
payment_request=pr,
|
||||||
|
max_sat=lnurldevicepayment.sats / 1000,
|
||||||
|
extra={"tag": "withdraw"},
|
||||||
|
)
|
||||||
|
return {"status": "OK"}
|
||||||
|
if device.device == "switch":
|
||||||
|
payment_hash, payment_request = await create_invoice(
|
||||||
wallet_id=device.wallet,
|
wallet_id=device.wallet,
|
||||||
payment_request=pr,
|
amount=lnurldevicepayment.sats / 1000,
|
||||||
max_sat=lnurldevicepayment.sats / 1000,
|
memo=device.title + "-" + lnurldevicepayment.id,
|
||||||
extra={"tag": "withdraw"},
|
unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"),
|
||||||
|
extra={"tag": "Switch", "id": paymentid, "time": device.amount},
|
||||||
)
|
)
|
||||||
return {"status": "OK"}
|
lnurldevicepayment = await update_lnurldevicepayment(
|
||||||
|
lnurldevicepayment_id=paymentid, payhash=payment_hash
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"pr": payment_request,
|
||||||
|
"routes": [],
|
||||||
|
}
|
||||||
|
|
||||||
payment_hash, payment_request = await create_invoice(
|
payment_hash, payment_request = await create_invoice(
|
||||||
wallet_id=device.wallet,
|
wallet_id=device.wallet,
|
||||||
|
|
@ -221,5 +266,3 @@ async def lnurl_callback(
|
||||||
},
|
},
|
||||||
"routes": [],
|
"routes": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.dict()
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ async def m001_initial(db):
|
||||||
payhash TEXT,
|
payhash TEXT,
|
||||||
payload TEXT NOT NULL,
|
payload TEXT NOT NULL,
|
||||||
pin INT,
|
pin INT,
|
||||||
sats INT,
|
sats {db.big_int},
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
|
@ -79,3 +79,12 @@ async def m002_redux(db):
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
async def m003_redux(db):
|
||||||
|
"""
|
||||||
|
Add 'meta' for storing various metadata about the wallet
|
||||||
|
"""
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ class createLnurldevice(BaseModel):
|
||||||
currency: str
|
currency: str
|
||||||
device: str
|
device: str
|
||||||
profit: float
|
profit: float
|
||||||
|
amount: int
|
||||||
|
|
||||||
|
|
||||||
class lnurldevices(BaseModel):
|
class lnurldevices(BaseModel):
|
||||||
|
|
@ -27,15 +28,14 @@ class lnurldevices(BaseModel):
|
||||||
currency: str
|
currency: str
|
||||||
device: str
|
device: str
|
||||||
profit: float
|
profit: float
|
||||||
|
amount: int
|
||||||
timestamp: str
|
timestamp: str
|
||||||
|
|
||||||
def from_row(cls, row: Row) -> "lnurldevices":
|
def from_row(cls, row: Row) -> "lnurldevices":
|
||||||
return cls(**dict(row))
|
return cls(**dict(row))
|
||||||
|
|
||||||
def lnurl(self, req: Request) -> Lnurl:
|
def lnurl(self, req: Request) -> Lnurl:
|
||||||
url = req.url_for(
|
url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id)
|
||||||
"lnurldevice.lnurl_response", device_id=self.id, _external=True
|
|
||||||
)
|
|
||||||
return lnurl_encode(url)
|
return lnurl_encode(url)
|
||||||
|
|
||||||
async def lnurlpay_metadata(self) -> LnurlPayMetadata:
|
async def lnurlpay_metadata(self) -> LnurlPayMetadata:
|
||||||
|
|
|
||||||
40
lnbits/extensions/lnurldevice/tasks.py
Normal file
40
lnbits/extensions/lnurldevice/tasks.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from http import HTTPStatus
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
from lnbits import bolt11
|
||||||
|
from lnbits.core.models import Payment
|
||||||
|
from lnbits.core.services import pay_invoice
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
|
from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment
|
||||||
|
from .views import updater
|
||||||
|
|
||||||
|
|
||||||
|
async def wait_for_paid_invoices():
|
||||||
|
invoice_queue = asyncio.Queue()
|
||||||
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
|
while True:
|
||||||
|
payment = await invoice_queue.get()
|
||||||
|
await on_invoice_paid(payment)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_invoice_paid(payment: Payment) -> None:
|
||||||
|
# (avoid loops)
|
||||||
|
if "Switch" == payment.extra.get("tag"):
|
||||||
|
lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id"))
|
||||||
|
if not lnurldevicepayment:
|
||||||
|
return
|
||||||
|
if lnurldevicepayment.payhash == "used":
|
||||||
|
return
|
||||||
|
lnurldevicepayment = await update_lnurldevicepayment(
|
||||||
|
lnurldevicepayment_id=payment.extra.get("id"), payhash="used"
|
||||||
|
)
|
||||||
|
return await updater(lnurldevicepayment.deviceid)
|
||||||
|
return
|
||||||
|
|
@ -1,13 +1,24 @@
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<p>
|
<p>
|
||||||
Register LNURLDevice devices to receive payments in your LNbits wallet.<br />
|
For LNURL based Points of Sale, ATMs, and relay devices<br />
|
||||||
Build your own here
|
Use with: <br />
|
||||||
<a href="https://github.com/arcbtc/bitcoinpos"
|
LNPoS
|
||||||
>https://github.com/arcbtc/bitcoinpos</a
|
<a href="https://lnbits.github.io/lnpos">
|
||||||
|
https://lnbits.github.io/lnpos</a
|
||||||
|
><br />
|
||||||
|
bitcoinSwitch
|
||||||
|
<a href="https://github.com/lnbits/bitcoinSwitch">
|
||||||
|
https://github.com/lnbits/bitcoinSwitch</a
|
||||||
|
><br />
|
||||||
|
FOSSA
|
||||||
|
<a href="https://github.com/lnbits/fossa">
|
||||||
|
https://github.com/lnbits/fossa</a
|
||||||
><br />
|
><br />
|
||||||
<small>
|
<small>
|
||||||
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
|
Created by, <a href="https://github.com/benarc">Ben Arc</a>,
|
||||||
|
<a href="https://github.com/blackcoffeexbt">BC</a>,
|
||||||
|
<a href="https://github.com/motorina0">Vlad Stan</a></small
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
<q-th style="width: 5%"></q-th>
|
<q-th style="width: 5%"></q-th>
|
||||||
<q-th style="width: 5%"></q-th>
|
<q-th style="width: 5%"></q-th>
|
||||||
|
<q-th style="width: 5%"></q-th>
|
||||||
|
|
||||||
<q-th
|
<q-th
|
||||||
v-for="col in props.cols"
|
v-for="col in props.cols"
|
||||||
|
|
@ -91,6 +92,22 @@
|
||||||
<q-tooltip> LNURLDevice Settings </q-tooltip>
|
<q-tooltip> LNURLDevice Settings </q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
<q-td>
|
||||||
|
<q-btn
|
||||||
|
v-if="props.row.device == 'switch'"
|
||||||
|
:disable="protocol == 'http:'"
|
||||||
|
flat
|
||||||
|
unelevated
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
icon="visibility"
|
||||||
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||||
|
@click="openQrCodeDialog(props.row.id)"
|
||||||
|
><q-tooltip v-if="protocol == 'http:'">
|
||||||
|
LNURLs only work over HTTPS </q-tooltip
|
||||||
|
><q-tooltip v-else> view LNURL </q-tooltip></q-btn
|
||||||
|
>
|
||||||
|
</q-td>
|
||||||
<q-td
|
<q-td
|
||||||
v-for="col in props.cols"
|
v-for="col in props.cols"
|
||||||
:key="col.name"
|
:key="col.name"
|
||||||
|
|
@ -132,20 +149,33 @@
|
||||||
class="q-pa-lg q-pt-xl lnbits__dialog-card"
|
class="q-pa-lg q-pt-xl lnbits__dialog-card"
|
||||||
>
|
>
|
||||||
<div class="text-h6">LNURLDevice device string</div>
|
<div class="text-h6">LNURLDevice device string</div>
|
||||||
<q-btn
|
<center>
|
||||||
dense
|
<q-btn
|
||||||
outline
|
v-if="settingsDialog.data.device == 'switch'"
|
||||||
unelevated
|
dense
|
||||||
color="primary"
|
outline
|
||||||
size="md"
|
unelevated
|
||||||
@click="copyText(location + '/lnurldevice/api/v1/lnurl/' + settingsDialog.data.id + ',' +
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
@click="copyText(wslocation + '/lnurldevice/ws/' + settingsDialog.data.id, 'Link copied to clipboard!')"
|
||||||
|
>{% raw %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{%
|
||||||
|
endraw %}<q-tooltip> Click to copy URL </q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-else
|
||||||
|
dense
|
||||||
|
outline
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
@click="copyText(location + '/lnurldevice/api/v1/lnurl/' + settingsDialog.data.id + ',' +
|
||||||
settingsDialog.data.key + ',' + settingsDialog.data.currency, 'Link copied to clipboard!')"
|
settingsDialog.data.key + ',' + settingsDialog.data.currency, 'Link copied to clipboard!')"
|
||||||
>{% raw
|
>{% raw
|
||||||
%}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
|
%}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
|
||||||
{{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
|
{{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
|
||||||
%}<q-tooltip> Click to copy URL </q-tooltip>
|
%}<q-tooltip> Click to copy URL </q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
</center>
|
||||||
<div class="text-subtitle2">
|
<div class="text-subtitle2">
|
||||||
<small> </small>
|
<small> </small>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -191,6 +221,7 @@
|
||||||
label="Type of device"
|
label="Type of device"
|
||||||
></q-option-group>
|
></q-option-group>
|
||||||
<q-input
|
<q-input
|
||||||
|
v-if="formDialoglnurldevice.data.device != 'switch'"
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model.trim="formDialoglnurldevice.data.profit"
|
v-model.trim="formDialoglnurldevice.data.profit"
|
||||||
|
|
@ -198,6 +229,29 @@
|
||||||
max="90"
|
max="90"
|
||||||
label="Profit margin (% added to invoices/deducted from faucets)"
|
label="Profit margin (% added to invoices/deducted from faucets)"
|
||||||
></q-input>
|
></q-input>
|
||||||
|
<div v-else>
|
||||||
|
<q-input
|
||||||
|
ref="setAmount"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.profit"
|
||||||
|
class="q-pb-md"
|
||||||
|
:label="'Amount (' + formDialoglnurldevice.data.currency + ') *'"
|
||||||
|
:mask="'#.##'"
|
||||||
|
fill-mask="0"
|
||||||
|
reverse-fill-mask
|
||||||
|
:step="'0.01'"
|
||||||
|
value="0.00"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.amount"
|
||||||
|
type="number"
|
||||||
|
value="1000"
|
||||||
|
label="milesecs to turn Switch on for (1sec = 1000ms)"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
@ -225,6 +279,33 @@
|
||||||
</q-form>
|
</q-form>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
|
<q-dialog v-model="qrCodeDialog.show" position="top">
|
||||||
|
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
|
||||||
|
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
||||||
|
<qrcode
|
||||||
|
:value="qrCodeDialog.data.url + '/?lightning=' + qrCodeDialog.data.lnurl"
|
||||||
|
:options="{width: 800}"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode>
|
||||||
|
{% raw %}
|
||||||
|
</q-responsive>
|
||||||
|
<p style="word-break: break-all">
|
||||||
|
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br />
|
||||||
|
</p>
|
||||||
|
{% endraw %}
|
||||||
|
<div class="row q-mt-lg q-gutter-sm">
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="grey"
|
||||||
|
@click="copyText(qrCodeDialog.data.lnurl, 'LNURL copied to clipboard!')"
|
||||||
|
class="q-ml-sm"
|
||||||
|
>Copy LNURL</q-btn
|
||||||
|
>
|
||||||
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
|
|
||||||
|
|
@ -252,7 +333,9 @@
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
protocol: window.location.protocol,
|
||||||
location: window.location.hostname,
|
location: window.location.hostname,
|
||||||
|
wslocation: window.location.hostname,
|
||||||
filter: '',
|
filter: '',
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
lnurldeviceLinks: [],
|
lnurldeviceLinks: [],
|
||||||
|
|
@ -265,6 +348,10 @@
|
||||||
{
|
{
|
||||||
label: 'ATM',
|
label: 'ATM',
|
||||||
value: 'atm'
|
value: 'atm'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Switch',
|
||||||
|
value: 'switch'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
lnurldevicesTable: {
|
lnurldevicesTable: {
|
||||||
|
|
@ -333,7 +420,8 @@
|
||||||
show_ack: false,
|
show_ack: false,
|
||||||
show_price: 'None',
|
show_price: 'None',
|
||||||
device: 'pos',
|
device: 'pos',
|
||||||
profit: 2,
|
profit: 0,
|
||||||
|
amount: 1,
|
||||||
title: ''
|
title: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -344,6 +432,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
openQrCodeDialog: function (lnurldevice_id) {
|
||||||
|
var lnurldevice = _.findWhere(this.lnurldeviceLinks, {
|
||||||
|
id: lnurldevice_id
|
||||||
|
})
|
||||||
|
console.log(lnurldevice)
|
||||||
|
this.qrCodeDialog.data = _.clone(lnurldevice)
|
||||||
|
this.qrCodeDialog.data.url =
|
||||||
|
window.location.protocol + '//' + window.location.host
|
||||||
|
this.qrCodeDialog.show = true
|
||||||
|
},
|
||||||
cancellnurldevice: function (data) {
|
cancellnurldevice: function (data) {
|
||||||
var self = this
|
var self = this
|
||||||
self.formDialoglnurldevice.show = false
|
self.formDialoglnurldevice.show = false
|
||||||
|
|
@ -400,6 +498,7 @@
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
self.lnurldeviceLinks = response.data.map(maplnurldevice)
|
self.lnurldeviceLinks = response.data.map(maplnurldevice)
|
||||||
|
console.log(response.data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
|
|
@ -519,6 +618,7 @@
|
||||||
'//',
|
'//',
|
||||||
window.location.host
|
window.location.host
|
||||||
].join('')
|
].join('')
|
||||||
|
self.wslocation = ['ws://', window.location.host].join('')
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request('GET', '/api/v1/currencies')
|
.request('GET', '/api/v1/currencies')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from fastapi import Request
|
import pyqrcode
|
||||||
|
from fastapi import Request, WebSocket, WebSocketDisconnect
|
||||||
from fastapi.param_functions import Query
|
from fastapi.param_functions import Query
|
||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from starlette.responses import HTMLResponse
|
from starlette.responses import HTMLResponse, StreamingResponse
|
||||||
|
|
||||||
from lnbits.core.crud import update_payment_status
|
from lnbits.core.crud import update_payment_status
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
|
|
@ -51,3 +53,58 @@ async def displaypin(request: Request, paymentid: str = Query(None)):
|
||||||
"lnurldevice/error.html",
|
"lnurldevice/error.html",
|
||||||
{"request": request, "pin": "filler", "not_paid": True},
|
{"request": request, "pin": "filler", "not_paid": True},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@lnurldevice_ext.get("/img/{lnurldevice_id}", response_class=StreamingResponse)
|
||||||
|
async def img(request: Request, lnurldevice_id):
|
||||||
|
lnurldevice = await get_lnurldevice(lnurldevice_id)
|
||||||
|
if not lnurldevice:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist."
|
||||||
|
)
|
||||||
|
return lnurldevice.lnurl(request)
|
||||||
|
|
||||||
|
|
||||||
|
##################WEBSOCKET ROUTES########################
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.active_connections: List[WebSocket] = []
|
||||||
|
|
||||||
|
async def connect(self, websocket: WebSocket, lnurldevice_id: str):
|
||||||
|
await websocket.accept()
|
||||||
|
websocket.id = lnurldevice_id
|
||||||
|
self.active_connections.append(websocket)
|
||||||
|
|
||||||
|
def disconnect(self, websocket: WebSocket):
|
||||||
|
self.active_connections.remove(websocket)
|
||||||
|
|
||||||
|
async def send_personal_message(self, message: str, lnurldevice_id: str):
|
||||||
|
for connection in self.active_connections:
|
||||||
|
if connection.id == lnurldevice_id:
|
||||||
|
await connection.send_text(message)
|
||||||
|
|
||||||
|
async def broadcast(self, message: str):
|
||||||
|
for connection in self.active_connections:
|
||||||
|
await connection.send_text(message)
|
||||||
|
|
||||||
|
|
||||||
|
manager = ConnectionManager()
|
||||||
|
|
||||||
|
|
||||||
|
@lnurldevice_ext.websocket("/ws/{lnurldevice_id}", name="lnurldevice.lnurldevice_by_id")
|
||||||
|
async def websocket_endpoint(websocket: WebSocket, lnurldevice_id: str):
|
||||||
|
await manager.connect(websocket, lnurldevice_id)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data = await websocket.receive_text()
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
manager.disconnect(websocket)
|
||||||
|
|
||||||
|
|
||||||
|
async def updater(lnurldevice_id):
|
||||||
|
lnurldevice = await get_lnurldevice(lnurldevice_id)
|
||||||
|
if not lnurldevice:
|
||||||
|
return
|
||||||
|
await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id)
|
||||||
|
|
|
||||||
|
|
@ -32,32 +32,42 @@ async def api_list_currencies_available():
|
||||||
@lnurldevice_ext.post("/api/v1/lnurlpos")
|
@lnurldevice_ext.post("/api/v1/lnurlpos")
|
||||||
@lnurldevice_ext.put("/api/v1/lnurlpos/{lnurldevice_id}")
|
@lnurldevice_ext.put("/api/v1/lnurlpos/{lnurldevice_id}")
|
||||||
async def api_lnurldevice_create_or_update(
|
async def api_lnurldevice_create_or_update(
|
||||||
|
req: Request,
|
||||||
data: createLnurldevice,
|
data: createLnurldevice,
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
lnurldevice_id: str = Query(None),
|
lnurldevice_id: str = Query(None),
|
||||||
):
|
):
|
||||||
if not lnurldevice_id:
|
if not lnurldevice_id:
|
||||||
lnurldevice = await create_lnurldevice(data)
|
lnurldevice = await create_lnurldevice(data)
|
||||||
return lnurldevice.dict()
|
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
||||||
else:
|
else:
|
||||||
lnurldevice = await update_lnurldevice(data, lnurldevice_id=lnurldevice_id)
|
lnurldevice = await update_lnurldevice(data, lnurldevice_id=lnurldevice_id)
|
||||||
return lnurldevice.dict()
|
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
||||||
|
|
||||||
|
|
||||||
@lnurldevice_ext.get("/api/v1/lnurlpos")
|
@lnurldevice_ext.get("/api/v1/lnurlpos")
|
||||||
async def api_lnurldevices_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
|
async def api_lnurldevices_retrieve(
|
||||||
|
req: Request, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||||
|
):
|
||||||
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
|
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
|
||||||
try:
|
try:
|
||||||
return [
|
return [
|
||||||
{**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)
|
{**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
||||||
|
for lnurldevice in await get_lnurldevices(wallet_ids)
|
||||||
]
|
]
|
||||||
except:
|
except:
|
||||||
return ""
|
try:
|
||||||
|
return [
|
||||||
|
{**lnurldevice.dict()}
|
||||||
|
for lnurldevice in await get_lnurldevices(wallet_ids)
|
||||||
|
]
|
||||||
|
except:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@lnurldevice_ext.get("/api/v1/lnurlpos/{lnurldevice_id}")
|
@lnurldevice_ext.get("/api/v1/lnurlpos/{lnurldevice_id}")
|
||||||
async def api_lnurldevice_retrieve(
|
async def api_lnurldevice_retrieve(
|
||||||
request: Request,
|
req: Request,
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
wallet: WalletTypeInfo = Depends(get_key_type),
|
||||||
lnurldevice_id: str = Query(None),
|
lnurldevice_id: str = Query(None),
|
||||||
):
|
):
|
||||||
|
|
@ -68,7 +78,7 @@ async def api_lnurldevice_retrieve(
|
||||||
)
|
)
|
||||||
if not lnurldevice.lnurl_toggle:
|
if not lnurldevice.lnurl_toggle:
|
||||||
return {**lnurldevice.dict()}
|
return {**lnurldevice.dict()}
|
||||||
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(request=request)}}
|
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
||||||
|
|
||||||
|
|
||||||
@lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
|
@lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import httpx
|
||||||
|
|
||||||
from lnbits.core import db as core_db
|
from lnbits.core import db as core_db
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_pay_link
|
from .crud import get_pay_link
|
||||||
|
|
@ -12,7 +13,7 @@ from .crud import get_pay_link
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@ async def m001_initial(db):
|
||||||
Initial lnurlpayouts table.
|
Initial lnurlpayouts table.
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
CREATE TABLE lnurlpayout.lnurlpayouts (
|
CREATE TABLE lnurlpayout.lnurlpayouts (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
admin_key TEXT NOT NULL,
|
admin_key TEXT NOT NULL,
|
||||||
lnurlpay TEXT NOT NULL,
|
lnurlpay TEXT NOT NULL,
|
||||||
threshold INT NOT NULL
|
threshold {db.big_int} NOT NULL
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from lnbits.core.crud import get_wallet
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.core.services import pay_invoice
|
from lnbits.core.services import pay_invoice
|
||||||
from lnbits.core.views.api import api_payments_decode
|
from lnbits.core.views.api import api_payments_decode
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_lnurlpayout_from_wallet
|
from .crud import get_lnurlpayout_from_wallet
|
||||||
|
|
@ -17,7 +18,7 @@ from .crud import get_lnurlpayout_from_wallet
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ async def check_address_balance(charge_id: str) -> List[Charges]:
|
||||||
charge = await get_charge(charge_id)
|
charge = await get_charge(charge_id)
|
||||||
if not charge.paid:
|
if not charge.paid:
|
||||||
if charge.onchainaddress:
|
if charge.onchainaddress:
|
||||||
config = await get_config(charge.user)
|
config = await get_charge_config(charge_id)
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
r = await client.get(
|
r = await client.get(
|
||||||
|
|
@ -122,3 +122,10 @@ async def check_address_balance(charge_id: str) -> List[Charges]:
|
||||||
return await update_charge(charge_id=charge_id, balance=charge.amount)
|
return await update_charge(charge_id=charge_id, balance=charge.amount)
|
||||||
row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,))
|
row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,))
|
||||||
return Charges.from_row(row) if row else None
|
return Charges.from_row(row) if row else None
|
||||||
|
|
||||||
|
|
||||||
|
async def get_charge_config(charge_id: str):
|
||||||
|
row = await db.fetchone(
|
||||||
|
"""SELECT "user" FROM satspay.charges WHERE id = ?""", (charge_id,)
|
||||||
|
)
|
||||||
|
return await get_config(row.user)
|
||||||
|
|
|
||||||
17
lnbits/extensions/satspay/helpers.py
Normal file
17
lnbits/extensions/satspay/helpers.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from .models import Charges
|
||||||
|
|
||||||
|
|
||||||
|
def compact_charge(charge: Charges):
|
||||||
|
return {
|
||||||
|
"id": charge.id,
|
||||||
|
"description": charge.description,
|
||||||
|
"onchainaddress": charge.onchainaddress,
|
||||||
|
"payment_request": charge.payment_request,
|
||||||
|
"payment_hash": charge.payment_hash,
|
||||||
|
"time": charge.time,
|
||||||
|
"amount": charge.amount,
|
||||||
|
"balance": charge.balance,
|
||||||
|
"paid": charge.paid,
|
||||||
|
"timestamp": charge.timestamp,
|
||||||
|
"completelink": charge.completelink, # should be secret?
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,6 @@ class CreateCharge(BaseModel):
|
||||||
|
|
||||||
class Charges(BaseModel):
|
class Charges(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
user: str
|
|
||||||
description: Optional[str]
|
description: Optional[str]
|
||||||
onchainwallet: Optional[str]
|
onchainwallet: Optional[str]
|
||||||
onchainaddress: Optional[str]
|
onchainaddress: Optional[str]
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.extensions.satspay.crud import check_address_balance, get_charge
|
from lnbits.extensions.satspay.crud import check_address_balance, get_charge
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
# from .crud import get_ticket, set_ticket_paid
|
# from .crud import get_ticket, set_ticket_paid
|
||||||
|
|
@ -11,7 +12,7 @@ from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,7 @@
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
checkBalances: async function () {
|
checkBalances: async function () {
|
||||||
if (!this.charge.hasStaleBalance) await this.refreshCharge()
|
if (this.charge.hasStaleBalance) return
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'GET',
|
'GET',
|
||||||
|
|
@ -339,18 +339,9 @@
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refreshCharge: async function () {
|
|
||||||
try {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'GET',
|
|
||||||
`/satspay/api/v1/charge/${this.charge.id}`
|
|
||||||
)
|
|
||||||
this.charge = mapCharge(data, this.charge)
|
|
||||||
} catch (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkPendingOnchain: async function () {
|
checkPendingOnchain: async function () {
|
||||||
|
if (!this.charge.onchainaddress) return
|
||||||
|
|
||||||
const {
|
const {
|
||||||
bitcoin: {addresses: addressesAPI}
|
bitcoin: {addresses: addressesAPI}
|
||||||
} = mempoolJS({
|
} = mempoolJS({
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,9 @@ from starlette.responses import HTMLResponse
|
||||||
from lnbits.core.crud import get_wallet
|
from lnbits.core.crud import get_wallet
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
from lnbits.decorators import check_user_exists
|
from lnbits.decorators import check_user_exists
|
||||||
from lnbits.extensions.watchonly.crud import get_config
|
|
||||||
|
|
||||||
from . import satspay_ext, satspay_renderer
|
from . import satspay_ext, satspay_renderer
|
||||||
from .crud import get_charge
|
from .crud import get_charge, get_charge_config
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
@ -32,7 +31,7 @@ async def display(request: Request, charge_id: str):
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
|
||||||
)
|
)
|
||||||
wallet = await get_wallet(charge.lnbitswallet)
|
wallet = await get_wallet(charge.lnbitswallet)
|
||||||
onchainwallet_config = await get_config(charge.user)
|
onchainwallet_config = await get_charge_config(charge_id)
|
||||||
inkey = wallet.inkey if wallet else None
|
inkey = wallet.inkey if wallet else None
|
||||||
mempool_endpoint = (
|
mempool_endpoint = (
|
||||||
onchainwallet_config.mempool_endpoint if onchainwallet_config else None
|
onchainwallet_config.mempool_endpoint if onchainwallet_config else None
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ from .crud import (
|
||||||
get_charges,
|
get_charges,
|
||||||
update_charge,
|
update_charge,
|
||||||
)
|
)
|
||||||
|
from .helpers import compact_charge
|
||||||
from .models import CreateCharge
|
from .models import CreateCharge
|
||||||
|
|
||||||
#############################CHARGES##########################
|
#############################CHARGES##########################
|
||||||
|
|
@ -123,25 +124,13 @@ async def api_charge_balance(charge_id):
|
||||||
try:
|
try:
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
charge.webhook,
|
charge.webhook,
|
||||||
json={
|
json=compact_charge(charge),
|
||||||
"id": charge.id,
|
|
||||||
"description": charge.description,
|
|
||||||
"onchainaddress": charge.onchainaddress,
|
|
||||||
"payment_request": charge.payment_request,
|
|
||||||
"payment_hash": charge.payment_hash,
|
|
||||||
"time": charge.time,
|
|
||||||
"amount": charge.amount,
|
|
||||||
"balance": charge.balance,
|
|
||||||
"paid": charge.paid,
|
|
||||||
"timestamp": charge.timestamp,
|
|
||||||
"completelink": charge.completelink,
|
|
||||||
},
|
|
||||||
timeout=40,
|
timeout=40,
|
||||||
)
|
)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
charge.webhook = None
|
charge.webhook = None
|
||||||
return {
|
return {
|
||||||
**charge.dict(),
|
**compact_charge(charge),
|
||||||
**{"time_elapsed": charge.time_elapsed},
|
**{"time_elapsed": charge.time_elapsed},
|
||||||
**{"time_left": charge.time_left},
|
**{"time_left": charge.time_left},
|
||||||
**{"paid": charge.paid},
|
**{"paid": charge.paid},
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
SCRUB is a small but handy extension that allows a user to take advantage of all the functionalities inside **LNbits** and upon a payment received to your LNbits wallet, automatically forward it to your desired wallet via LNURL or LNAddress!
|
SCRUB is a small but handy extension that allows a user to take advantage of all the functionalities inside **LNbits** and upon a payment received to your LNbits wallet, automatically forward it to your desired wallet via LNURL or LNAddress!
|
||||||
|
|
||||||
|
<small>Only whole values, integers, are Scrubbed, amounts will be rounded down (example: 6.3 will be 6)! The decimals, if existing, will be kept in your wallet!</small>
|
||||||
|
|
||||||
[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets)
|
[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from math import floor
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -9,6 +10,7 @@ from fastapi import HTTPException
|
||||||
from lnbits import bolt11
|
from lnbits import bolt11
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.core.services import pay_invoice
|
from lnbits.core.services import pay_invoice
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_scrub_by_wallet
|
from .crud import get_scrub_by_wallet
|
||||||
|
|
@ -16,7 +18,7 @@ from .crud import get_scrub_by_wallet
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
@ -25,7 +27,7 @@ async def wait_for_paid_invoices():
|
||||||
|
|
||||||
async def on_invoice_paid(payment: Payment) -> None:
|
async def on_invoice_paid(payment: Payment) -> None:
|
||||||
# (avoid loops)
|
# (avoid loops)
|
||||||
if "scrubed" == payment.extra.get("tag"):
|
if payment.extra.get("tag") == "scrubed":
|
||||||
# already scrubbed
|
# already scrubbed
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -41,12 +43,13 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
|
|
||||||
# I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267
|
# I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267
|
||||||
domain = urlparse(data["callback"]).netloc
|
domain = urlparse(data["callback"]).netloc
|
||||||
|
rounded_amount = floor(payment.amount / 1000) * 1000
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
try:
|
try:
|
||||||
r = await client.get(
|
r = await client.get(
|
||||||
data["callback"],
|
data["callback"],
|
||||||
params={"amount": payment.amount},
|
params={"amount": rounded_amount},
|
||||||
timeout=40,
|
timeout=40,
|
||||||
)
|
)
|
||||||
if r.is_error:
|
if r.is_error:
|
||||||
|
|
@ -65,7 +68,8 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
invoice = bolt11.decode(params["pr"])
|
invoice = bolt11.decode(params["pr"])
|
||||||
if invoice.amount_msat != payment.amount:
|
|
||||||
|
if invoice.amount_msat != rounded_amount:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
detail=f"{domain} returned an invalid invoice. Expected {payment.amount} msat, got {invoice.amount_msat}.",
|
detail=f"{domain} returned an invalid invoice. Expected {payment.amount} msat, got {invoice.amount_msat}.",
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,21 @@
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<h6 class="text-subtitle1 q-my-none">{{SITE_TITLE}} Scrub extension</h6>
|
<h6 class="text-subtitle1 q-my-none">{{SITE_TITLE}} Scrub extension</h6>
|
||||||
|
<p>
|
||||||
|
Automatically forward funds (Scrub) that get paid to the LNbits
|
||||||
|
wallet, to an LNURLpay or Lightning Address.
|
||||||
|
<br />
|
||||||
|
More info in Scrub's
|
||||||
|
<a
|
||||||
|
href="https://github.com/lnbits/lnbits/blob/main/lnbits/extensions/scrub/README.md#scrub"
|
||||||
|
target="_blank"
|
||||||
|
>readme</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 90%">
|
||||||
|
<strong>Important: </strong>wallet will need a float to account for
|
||||||
|
any fees, before being able to push a payment
|
||||||
|
</p>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class Target(BaseModel):
|
||||||
class TargetPutList(BaseModel):
|
class TargetPutList(BaseModel):
|
||||||
wallet: str = Query(...)
|
wallet: str = Query(...)
|
||||||
alias: str = Query("")
|
alias: str = Query("")
|
||||||
percent: float = Query(..., ge=0.01)
|
percent: float = Query(..., ge=0.01, lt=100)
|
||||||
|
|
||||||
|
|
||||||
class TargetPut(BaseModel):
|
class TargetPut(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core import db as core_db
|
|
||||||
from lnbits.core.crud import create_payment
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from lnbits.core.services import create_invoice, pay_invoice
|
||||||
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
|
from lnbits.helpers import get_current_extension_name
|
||||||
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_targets
|
from .crud import get_targets
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
@ -22,59 +20,36 @@ async def wait_for_paid_invoices():
|
||||||
|
|
||||||
|
|
||||||
async def on_invoice_paid(payment: Payment) -> None:
|
async def on_invoice_paid(payment: Payment) -> None:
|
||||||
if payment.extra.get("tag") == "splitpayments" or payment.extra.get("splitted"):
|
if payment.extra.get("tag") == "splitpayments":
|
||||||
# already splitted, ignore
|
# already a splitted payment, ignore
|
||||||
return
|
return
|
||||||
|
|
||||||
# now we make some special internal transfers (from no one to the receiver)
|
|
||||||
targets = await get_targets(payment.wallet_id)
|
targets = await get_targets(payment.wallet_id)
|
||||||
transfers = [
|
|
||||||
(target.wallet, int(target.percent * payment.amount / 100))
|
|
||||||
for target in targets
|
|
||||||
]
|
|
||||||
transfers = [(wallet, amount) for wallet, amount in transfers if amount > 0]
|
|
||||||
amount_left = payment.amount - sum([amount for _, amount in transfers])
|
|
||||||
|
|
||||||
if amount_left < 0:
|
|
||||||
logger.error(
|
|
||||||
"splitpayments failure: amount_left is negative.", payment.payment_hash
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not targets:
|
if not targets:
|
||||||
return
|
return
|
||||||
|
|
||||||
# mark the original payment with one extra key, "splitted"
|
total_percent = sum([target.percent for target in targets])
|
||||||
# (this prevents us from doing this process again and it's informative)
|
|
||||||
# and reduce it by the amount we're going to send to the producer
|
|
||||||
await core_db.execute(
|
|
||||||
"""
|
|
||||||
UPDATE apipayments
|
|
||||||
SET extra = ?, amount = ?
|
|
||||||
WHERE hash = ?
|
|
||||||
AND checking_id NOT LIKE 'internal_%'
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
json.dumps(dict(**payment.extra, splitted=True)),
|
|
||||||
amount_left,
|
|
||||||
payment.payment_hash,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# perform the internal transfer using the same payment_hash
|
if total_percent > 100:
|
||||||
for wallet, amount in transfers:
|
logger.error("splitpayment failure: total percent adds up to more than 100%")
|
||||||
internal_checking_id = f"internal_{urlsafe_short_hash()}"
|
return
|
||||||
await create_payment(
|
|
||||||
wallet_id=wallet,
|
logger.debug(f"performing split payments to {len(targets)} targets")
|
||||||
checking_id=internal_checking_id,
|
for target in targets:
|
||||||
payment_request="",
|
amount = int(payment.amount * target.percent / 100) # msats
|
||||||
payment_hash=payment.payment_hash,
|
payment_hash, payment_request = await create_invoice(
|
||||||
amount=amount,
|
wallet_id=target.wallet,
|
||||||
memo=payment.memo,
|
amount=int(amount / 1000), # sats
|
||||||
pending=False,
|
internal=True,
|
||||||
|
memo=f"split payment: {target.percent}% for {target.alias or target.wallet}",
|
||||||
extra={"tag": "splitpayments"},
|
extra={"tag": "splitpayments"},
|
||||||
)
|
)
|
||||||
|
logger.debug(f"created split invoice: {payment_hash}")
|
||||||
|
|
||||||
# manually send this for now
|
checking_id = await pay_invoice(
|
||||||
await internal_invoice_queue.put(internal_checking_id)
|
payment_request=payment_request,
|
||||||
return
|
wallet_id=payment.wallet_id,
|
||||||
|
extra={"tag": "splitpayments"},
|
||||||
|
)
|
||||||
|
logger.debug(f"paid split invoice: {checking_id}")
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ async def m001_initial(db):
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
message TEXT NOT NULL,
|
message TEXT NOT NULL,
|
||||||
cur_code TEXT NOT NULL,
|
cur_code TEXT NOT NULL,
|
||||||
sats INT NOT NULL,
|
sats {db.big_int} NOT NULL,
|
||||||
amount FLOAT NOT NULL,
|
amount FLOAT NOT NULL,
|
||||||
service INTEGER NOT NULL,
|
service INTEGER NOT NULL,
|
||||||
posted BOOLEAN NOT NULL,
|
posted BOOLEAN NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import asyncio
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
|
from lnbits.helpers import get_current_extension_name
|
||||||
from lnbits.tasks import register_invoice_listener
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .cloudflare import cloudflare_create_subdomain
|
from .cloudflare import cloudflare_create_subdomain
|
||||||
|
|
@ -11,7 +12,7 @@ from .crud import get_domain, set_subdomain_paid
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ async def m001_initial(db):
|
||||||
wallet TEXT NOT NULL,
|
wallet TEXT NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
message TEXT NOT NULL,
|
message TEXT NOT NULL,
|
||||||
sats INT NOT NULL,
|
sats {db.big_int} NOT NULL,
|
||||||
tipjar INT NOT NULL,
|
tipjar {db.big_int} NOT NULL,
|
||||||
FOREIGN KEY(tipjar) REFERENCES {db.references_schema}TipJars(id)
|
FOREIGN KEY(tipjar) REFERENCES {db.references_schema}TipJars(id)
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
|
||||||
|
|
||||||
from lnbits.core import db as core_db
|
from loguru import logger
|
||||||
from lnbits.core.crud import create_payment
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.models import Payment
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from lnbits.core.services import create_invoice, pay_invoice
|
||||||
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
|
from lnbits.helpers import get_current_extension_name
|
||||||
|
from lnbits.tasks import register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_tpos
|
from .crud import get_tpos
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
register_invoice_listener(invoice_queue)
|
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_queue.get()
|
payment = await invoice_queue.get()
|
||||||
|
|
@ -20,11 +20,9 @@ async def wait_for_paid_invoices():
|
||||||
|
|
||||||
|
|
||||||
async def on_invoice_paid(payment: Payment) -> None:
|
async def on_invoice_paid(payment: Payment) -> None:
|
||||||
if payment.extra.get("tag") == "tpos" and payment.extra.get("tipSplitted"):
|
if payment.extra.get("tag") != "tpos":
|
||||||
# already splitted, ignore
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# now we make some special internal transfers (from no one to the receiver)
|
|
||||||
tpos = await get_tpos(payment.extra.get("tposId"))
|
tpos = await get_tpos(payment.extra.get("tposId"))
|
||||||
tipAmount = payment.extra.get("tipAmount")
|
tipAmount = payment.extra.get("tipAmount")
|
||||||
|
|
||||||
|
|
@ -32,39 +30,17 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
# no tip amount
|
# no tip amount
|
||||||
return
|
return
|
||||||
|
|
||||||
tipAmount = tipAmount * 1000
|
payment_hash, payment_request = await create_invoice(
|
||||||
amount = payment.amount - tipAmount
|
|
||||||
|
|
||||||
# mark the original payment with one extra key, "splitted"
|
|
||||||
# (this prevents us from doing this process again and it's informative)
|
|
||||||
# and reduce it by the amount we're going to send to the producer
|
|
||||||
await core_db.execute(
|
|
||||||
"""
|
|
||||||
UPDATE apipayments
|
|
||||||
SET extra = ?, amount = ?
|
|
||||||
WHERE hash = ?
|
|
||||||
AND checking_id NOT LIKE 'internal_%'
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
json.dumps(dict(**payment.extra, tipSplitted=True)),
|
|
||||||
amount,
|
|
||||||
payment.payment_hash,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# perform the internal transfer using the same payment_hash
|
|
||||||
internal_checking_id = f"internal_{urlsafe_short_hash()}"
|
|
||||||
await create_payment(
|
|
||||||
wallet_id=tpos.tip_wallet,
|
wallet_id=tpos.tip_wallet,
|
||||||
checking_id=internal_checking_id,
|
amount=int(tipAmount), # sats
|
||||||
payment_request="",
|
internal=True,
|
||||||
payment_hash=payment.payment_hash,
|
memo=f"tpos tip",
|
||||||
amount=tipAmount,
|
|
||||||
memo=f"Tip for {payment.memo}",
|
|
||||||
pending=False,
|
|
||||||
extra={"tipSplitted": True},
|
|
||||||
)
|
)
|
||||||
|
logger.debug(f"tpos: tip invoice created: {payment_hash}")
|
||||||
|
|
||||||
# manually send this for now
|
checking_id = await pay_invoice(
|
||||||
await internal_invoice_queue.put(internal_checking_id)
|
payment_request=payment_request,
|
||||||
return
|
wallet_id=payment.wallet_id,
|
||||||
|
extra={"tag": "tpos"},
|
||||||
|
)
|
||||||
|
logger.debug(f"tpos: tip invoice paid: {checking_id}")
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from .models import Address, Config, WalletAccount
|
||||||
##########################WALLETS####################
|
##########################WALLETS####################
|
||||||
|
|
||||||
|
|
||||||
async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
|
async def create_watch_wallet(user: str, w: WalletAccount) -> WalletAccount:
|
||||||
wallet_id = urlsafe_short_hash()
|
wallet_id = urlsafe_short_hash()
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
|
|
@ -30,7 +30,7 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
wallet_id,
|
wallet_id,
|
||||||
w.user,
|
user,
|
||||||
w.masterpub,
|
w.masterpub,
|
||||||
w.fingerprint,
|
w.fingerprint,
|
||||||
w.title,
|
w.title,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ class CreateWallet(BaseModel):
|
||||||
|
|
||||||
class WalletAccount(BaseModel):
|
class WalletAccount(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
user: str
|
|
||||||
masterpub: str
|
masterpub: str
|
||||||
fingerprint: str
|
fingerprint: str
|
||||||
title: str
|
title: str
|
||||||
|
|
@ -80,6 +79,7 @@ class CreatePsbt(BaseModel):
|
||||||
class ExtractPsbt(BaseModel):
|
class ExtractPsbt(BaseModel):
|
||||||
psbtBase64 = "" # // todo snake case
|
psbtBase64 = "" # // todo snake case
|
||||||
inputs: List[TransactionInput]
|
inputs: List[TransactionInput]
|
||||||
|
network = "Mainnet"
|
||||||
|
|
||||||
|
|
||||||
class SignedTransaction(BaseModel):
|
class SignedTransaction(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<send-to
|
<send-to
|
||||||
:data.sync="sendToList"
|
:data.sync="sendToList"
|
||||||
:fee-rate="feeRate"
|
:fee-rate="feeRate"
|
||||||
:tx-size="txSizeNoChange"
|
:tx-size="txSize"
|
||||||
:selected-amount="selectedAmount"
|
:selected-amount="selectedAmount"
|
||||||
:sats-denominated="satsDenominated"
|
:sats-denominated="satsDenominated"
|
||||||
@update:outputs="handleOutputsChange"
|
@update:outputs="handleOutputsChange"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ async function payment(path) {
|
||||||
'mempool-endpoint',
|
'mempool-endpoint',
|
||||||
'sats-denominated',
|
'sats-denominated',
|
||||||
'serial-signer-ref',
|
'serial-signer-ref',
|
||||||
'adminkey'
|
'adminkey',
|
||||||
|
'network'
|
||||||
],
|
],
|
||||||
watch: {
|
watch: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
|
@ -279,7 +280,8 @@ async function payment(path) {
|
||||||
this.adminkey,
|
this.adminkey,
|
||||||
{
|
{
|
||||||
psbtBase64,
|
psbtBase64,
|
||||||
inputs: this.tx.inputs
|
inputs: this.tx.inputs,
|
||||||
|
network: this.network
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
<q-item
|
<q-item
|
||||||
v-for="device in pairedDevices"
|
v-for="device in pairedDevices"
|
||||||
:key="device.id"
|
:key="device.id"
|
||||||
v-if="!selectedPort"
|
v-if="!selectedPort && showPairedDevices"
|
||||||
clickable
|
clickable
|
||||||
v-close-popup
|
v-close-popup
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ async function serialSigner(path) {
|
||||||
receivedData: '',
|
receivedData: '',
|
||||||
config: {},
|
config: {},
|
||||||
decryptionKey: null,
|
decryptionKey: null,
|
||||||
sharedSecret: null, // todo: store in secure local storage
|
sharedSecret: null,
|
||||||
|
|
||||||
hww: {
|
hww: {
|
||||||
password: null,
|
password: null,
|
||||||
|
|
@ -51,12 +51,14 @@ async function serialSigner(path) {
|
||||||
},
|
},
|
||||||
tx: null, // todo: move to hww
|
tx: null, // todo: move to hww
|
||||||
|
|
||||||
showConsole: false
|
showConsole: false,
|
||||||
|
showPairedDevices: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
pairedDevices: {
|
pairedDevices: {
|
||||||
|
cache: false,
|
||||||
get: function () {
|
get: function () {
|
||||||
return (
|
return (
|
||||||
JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) ||
|
JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) ||
|
||||||
|
|
@ -109,7 +111,10 @@ async function serialSigner(path) {
|
||||||
|
|
||||||
// Wait for the serial port to open.
|
// Wait for the serial port to open.
|
||||||
await this.selectedPort.open(config)
|
await this.selectedPort.open(config)
|
||||||
|
// do not await
|
||||||
this.startSerialPortReading()
|
this.startSerialPortReading()
|
||||||
|
// wait to init
|
||||||
|
sleep(1000)
|
||||||
|
|
||||||
const textEncoder = new TextEncoderStream()
|
const textEncoder = new TextEncoderStream()
|
||||||
this.writableStreamClosed = textEncoder.readable.pipeTo(
|
this.writableStreamClosed = textEncoder.readable.pipeTo(
|
||||||
|
|
@ -225,8 +230,9 @@ async function serialSigner(path) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const {value, done} = await readStringUntil('\n')
|
const {value, done} = await readStringUntil('\n')
|
||||||
if (value) {
|
if (value) {
|
||||||
this.handleSerialPortResponse(value)
|
const {command, commandData} = await this.extractCommand(value)
|
||||||
this.updateSerialPortConsole(value)
|
this.handleSerialPortResponse(command, commandData)
|
||||||
|
this.updateSerialPortConsole(command)
|
||||||
}
|
}
|
||||||
if (done) return
|
if (done) return
|
||||||
}
|
}
|
||||||
|
|
@ -240,8 +246,7 @@ async function serialSigner(path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleSerialPortResponse: async function (value) {
|
handleSerialPortResponse: async function (command, commandData) {
|
||||||
const {command, commandData} = await this.extractCommand(value)
|
|
||||||
this.logPublicCommandsResponse(command, commandData)
|
this.logPublicCommandsResponse(command, commandData)
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
|
|
@ -282,7 +287,7 @@ async function serialSigner(path) {
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
console.log(` %c${value}`, 'background: #222; color: red')
|
console.log(` %c${command}`, 'background: #222; color: red')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
logPublicCommandsResponse: function (command, commandData) {
|
logPublicCommandsResponse: function (command, commandData) {
|
||||||
|
|
@ -307,6 +312,8 @@ async function serialSigner(path) {
|
||||||
},
|
},
|
||||||
hwwPing: async function () {
|
hwwPing: async function () {
|
||||||
try {
|
try {
|
||||||
|
// Send an empty ping. The serial port buffer might have some jubk data. Flush it.
|
||||||
|
await this.sendCommandClearText(COMMAND_PING)
|
||||||
await this.sendCommandClearText(COMMAND_PING, [window.location.host])
|
await this.sendCommandClearText(COMMAND_PING, [window.location.host])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
|
|
@ -582,7 +589,7 @@ async function serialSigner(path) {
|
||||||
hwwCheckPairing: async function () {
|
hwwCheckPairing: async function () {
|
||||||
const iv = window.crypto.getRandomValues(new Uint8Array(16))
|
const iv = window.crypto.getRandomValues(new Uint8Array(16))
|
||||||
const encrypted = await this.encryptMessage(
|
const encrypted = await this.encryptMessage(
|
||||||
this.sharedSecret,
|
this.sharedSecret, // todo: revisit
|
||||||
iv,
|
iv,
|
||||||
PAIRING_CONTROL_TEXT.length + ' ' + PAIRING_CONTROL_TEXT
|
PAIRING_CONTROL_TEXT.length + ' ' + PAIRING_CONTROL_TEXT
|
||||||
)
|
)
|
||||||
|
|
@ -603,10 +610,10 @@ async function serialSigner(path) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleCheckPairingResponse: async function (res = '') {
|
handleCheckPairingResponse: async function (res = '') {
|
||||||
const [statusCode, encryptedMessage] = res.split(' ')
|
const [statusCode, message] = res.split(' ')
|
||||||
switch (statusCode) {
|
switch (statusCode) {
|
||||||
case '0':
|
case '0':
|
||||||
const controlText = await this.decryptData(encryptedMessage)
|
const controlText = await this.decryptData(message)
|
||||||
if (controlText == PAIRING_CONTROL_TEXT) {
|
if (controlText == PAIRING_CONTROL_TEXT) {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
|
|
@ -622,6 +629,16 @@ async function serialSigner(path) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case '1':
|
||||||
|
this.closeSerialPort()
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'warning',
|
||||||
|
message:
|
||||||
|
'Re-pairing failed. Remove (forget) device and try again!',
|
||||||
|
caption: `Error: ${message}`,
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
// noting to do here yet
|
// noting to do here yet
|
||||||
break
|
break
|
||||||
|
|
@ -746,7 +763,7 @@ async function serialSigner(path) {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Failed to ask for help!',
|
message: 'Failed to wipe!',
|
||||||
caption: `${error}`,
|
caption: `${error}`,
|
||||||
timeout: 10000
|
timeout: 10000
|
||||||
})
|
})
|
||||||
|
|
@ -862,6 +879,11 @@ async function serialSigner(path) {
|
||||||
sendCommandSecure: async function (command, attrs = []) {
|
sendCommandSecure: async function (command, attrs = []) {
|
||||||
const message = [command].concat(attrs).join(' ')
|
const message = [command].concat(attrs).join(' ')
|
||||||
const iv = window.crypto.getRandomValues(new Uint8Array(16))
|
const iv = window.crypto.getRandomValues(new Uint8Array(16))
|
||||||
|
if (!this.sharedSecret || !this.sharedSecret.length) {
|
||||||
|
throw new Error(
|
||||||
|
`Secure connection not estabileshed. Tried to run command: ${command}`
|
||||||
|
)
|
||||||
|
}
|
||||||
const encrypted = await this.encryptMessage(
|
const encrypted = await this.encryptMessage(
|
||||||
this.sharedSecret,
|
this.sharedSecret,
|
||||||
iv,
|
iv,
|
||||||
|
|
@ -901,6 +923,7 @@ async function serialSigner(path) {
|
||||||
},
|
},
|
||||||
decryptData: async function (value) {
|
decryptData: async function (value) {
|
||||||
if (!this.sharedSecret) {
|
if (!this.sharedSecret) {
|
||||||
|
console.log('/error Secure session not established!')
|
||||||
return '/error Secure session not established!'
|
return '/error Secure session not established!'
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
@ -921,6 +944,7 @@ async function serialSigner(path) {
|
||||||
.trim()
|
.trim()
|
||||||
return command
|
return command
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log('/error Failed to decrypt message from device!')
|
||||||
return '/error Failed to decrypt message from device!'
|
return '/error Failed to decrypt message from device!'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -949,6 +973,11 @@ async function serialSigner(path) {
|
||||||
devices.splice(deviceIndex, 1)
|
devices.splice(deviceIndex, 1)
|
||||||
}
|
}
|
||||||
this.pairedDevices = devices
|
this.pairedDevices = devices
|
||||||
|
this.showPairedDevices = false
|
||||||
|
setTimeout(() => {
|
||||||
|
// force UI refresh
|
||||||
|
this.showPairedDevices = true
|
||||||
|
})
|
||||||
},
|
},
|
||||||
addPairedDevice: function (deviceId, sharedSecretHex, config) {
|
addPairedDevice: function (deviceId, sharedSecretHex, config) {
|
||||||
const devices = this.pairedDevices
|
const devices = this.pairedDevices
|
||||||
|
|
@ -960,6 +989,11 @@ async function serialSigner(path) {
|
||||||
config
|
config
|
||||||
})
|
})
|
||||||
this.pairedDevices = devices
|
this.pairedDevices = devices
|
||||||
|
this.showPairedDevices = false
|
||||||
|
setTimeout(() => {
|
||||||
|
// force UI refresh
|
||||||
|
this.showPairedDevices = true
|
||||||
|
})
|
||||||
},
|
},
|
||||||
updatePairedDeviceConfig(deviceId, config) {
|
updatePairedDeviceConfig(deviceId, config) {
|
||||||
const device = this.getPairedDevice(deviceId)
|
const device = this.getPairedDevice(deviceId)
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,21 @@
|
||||||
>directly from browser</a
|
>directly from browser</a
|
||||||
>
|
>
|
||||||
<small>
|
<small>
|
||||||
<br />Created by,
|
<br />Created by
|
||||||
<a target="_blank" style="color: unset" href="https://github.com/arcbtc"
|
<a target="_blank" style="color: unset" href="https://github.com/arcbtc"
|
||||||
>Ben Arc</a
|
>Ben Arc</a
|
||||||
|
>,
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
style="color: unset"
|
||||||
|
href="https://github.com/talvasconcelos"
|
||||||
|
>Tiago Vasconcelos</a
|
||||||
|
>,
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
style="color: unset"
|
||||||
|
href="https://github.com/motorina0"
|
||||||
|
>motorina0</a
|
||||||
>
|
>
|
||||||
(using,
|
(using,
|
||||||
<a
|
<a
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@
|
||||||
:adminkey="g.user.wallets[0].adminkey"
|
:adminkey="g.user.wallets[0].adminkey"
|
||||||
:serial-signer-ref="$refs.serialSigner"
|
:serial-signer-ref="$refs.serialSigner"
|
||||||
:sats-denominated="config.sats_denominated"
|
:sats-denominated="config.sats_denominated"
|
||||||
|
:network="config.network"
|
||||||
@broadcast-done="handleBroadcastSuccess"
|
@broadcast-done="handleBroadcastSuccess"
|
||||||
></payment>
|
></payment>
|
||||||
<!-- todo: no more utxos.data -->
|
<!-- todo: no more utxos.data -->
|
||||||
|
|
@ -149,7 +150,7 @@
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<h6 class="text-subtitle1 q-my-none">
|
<h6 class="text-subtitle1 q-my-none">
|
||||||
{{SITE_TITLE}} Onchain Wallet (watch-only) Extension
|
{{SITE_TITLE}} Onchain Wallet (watch-only) Extension
|
||||||
<small>(v0.2)</small>
|
<small>(v0.3)</small>
|
||||||
</h6>
|
</h6>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from http import HTTPStatus
|
||||||
import httpx
|
import httpx
|
||||||
from embit import finalizer, script
|
from embit import finalizer, script
|
||||||
from embit.ec import PublicKey
|
from embit.ec import PublicKey
|
||||||
|
from embit.networks import NETWORKS
|
||||||
from embit.psbt import PSBT, DerivationPath
|
from embit.psbt import PSBT, DerivationPath
|
||||||
from embit.transaction import Transaction, TransactionInput, TransactionOutput
|
from embit.transaction import Transaction, TransactionInput, TransactionOutput
|
||||||
from fastapi import Query, Request
|
from fastapi import Query, Request
|
||||||
|
|
@ -85,7 +86,6 @@ async def api_wallet_create_or_update(
|
||||||
|
|
||||||
new_wallet = WalletAccount(
|
new_wallet = WalletAccount(
|
||||||
id="none",
|
id="none",
|
||||||
user=w.wallet.user,
|
|
||||||
masterpub=data.masterpub,
|
masterpub=data.masterpub,
|
||||||
fingerprint=descriptor.keys[0].fingerprint.hex(),
|
fingerprint=descriptor.keys[0].fingerprint.hex(),
|
||||||
type=descriptor.scriptpubkey_type(),
|
type=descriptor.scriptpubkey_type(),
|
||||||
|
|
@ -114,7 +114,7 @@ async def api_wallet_create_or_update(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
wallet = await create_watch_wallet(new_wallet)
|
wallet = await create_watch_wallet(w.wallet.user, new_wallet)
|
||||||
|
|
||||||
await api_get_addresses(wallet.id, w)
|
await api_get_addresses(wallet.id, w)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -295,6 +295,7 @@ async def api_psbt_create(
|
||||||
async def api_psbt_extract_tx(
|
async def api_psbt_extract_tx(
|
||||||
data: ExtractPsbt, w: WalletTypeInfo = Depends(require_admin_key)
|
data: ExtractPsbt, w: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
|
network = NETWORKS["main"] if data.network == "Mainnet" else NETWORKS["test"]
|
||||||
res = SignedTransaction()
|
res = SignedTransaction()
|
||||||
try:
|
try:
|
||||||
psbt = PSBT.from_base64(data.psbtBase64)
|
psbt = PSBT.from_base64(data.psbtBase64)
|
||||||
|
|
@ -316,7 +317,7 @@ async def api_psbt_extract_tx(
|
||||||
|
|
||||||
for out in transaction.vout:
|
for out in transaction.vout:
|
||||||
tx["outputs"].append(
|
tx["outputs"].append(
|
||||||
{"amount": out.value, "address": out.script_pubkey.address()}
|
{"amount": out.value, "address": out.script_pubkey.address(network)}
|
||||||
)
|
)
|
||||||
res.tx_json = json.dumps(tx)
|
res.tx_json = json.dumps(tx)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -183,3 +183,26 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates:
|
||||||
t.env.globals["VENDORED_CSS"] = ["/static/bundle.css"]
|
t.env.globals["VENDORED_CSS"] = ["/static/bundle.css"]
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_extension_name() -> str:
|
||||||
|
"""
|
||||||
|
Returns the name of the extension that calls this method.
|
||||||
|
"""
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
callee_filepath = inspect.stack()[1].filename
|
||||||
|
callee_dirname, callee_filename = os.path.split(callee_filepath)
|
||||||
|
|
||||||
|
path = os.path.normpath(callee_dirname)
|
||||||
|
extension_director_name = path.split(os.sep)[-1]
|
||||||
|
try:
|
||||||
|
config_path = os.path.join(callee_dirname, "config.json")
|
||||||
|
with open(config_path) as json_file:
|
||||||
|
config = json.load(json_file)
|
||||||
|
ext_name = config["name"]
|
||||||
|
except:
|
||||||
|
ext_name = extension_director_name
|
||||||
|
return ext_name
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.1 KiB |
|
|
@ -1,8 +1,9 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import uuid
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Callable, List
|
from typing import Callable, Dict, List
|
||||||
|
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -18,20 +19,6 @@ from lnbits.settings import WALLET
|
||||||
|
|
||||||
from .core import db
|
from .core import db
|
||||||
|
|
||||||
deferred_async: List[Callable] = []
|
|
||||||
|
|
||||||
|
|
||||||
def record_async(func: Callable) -> Callable:
|
|
||||||
def recorder(state):
|
|
||||||
deferred_async.append(func)
|
|
||||||
|
|
||||||
return recorder
|
|
||||||
|
|
||||||
|
|
||||||
async def run_deferred_async():
|
|
||||||
for func in deferred_async:
|
|
||||||
asyncio.create_task(catch_everything_and_restart(func))
|
|
||||||
|
|
||||||
|
|
||||||
async def catch_everything_and_restart(func):
|
async def catch_everything_and_restart(func):
|
||||||
try:
|
try:
|
||||||
|
|
@ -50,18 +37,48 @@ async def send_push_promise(a, b) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
invoice_listeners: List[asyncio.Queue] = []
|
class SseListenersDict(dict):
|
||||||
|
"""
|
||||||
|
A dict of sse listeners.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str = None):
|
||||||
|
self.name = name or f"sse_listener_{str(uuid.uuid4())[:8]}"
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
assert type(key) == str, f"{key} is not a string"
|
||||||
|
assert type(value) == asyncio.Queue, f"{value} is not an asyncio.Queue"
|
||||||
|
logger.trace(f"sse: adding listener {key} to {self.name}. len = {len(self)+1}")
|
||||||
|
return super().__setitem__(key, value)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
logger.trace(f"sse: removing listener from {self.name}. len = {len(self)-1}")
|
||||||
|
return super().__delitem__(key)
|
||||||
|
|
||||||
|
_RaiseKeyError = object() # singleton for no-default behavior
|
||||||
|
|
||||||
|
def pop(self, key, v=_RaiseKeyError) -> None:
|
||||||
|
logger.trace(f"sse: removing listener from {self.name}. len = {len(self)-1}")
|
||||||
|
return super().pop(key)
|
||||||
|
|
||||||
|
|
||||||
def register_invoice_listener(send_chan: asyncio.Queue):
|
invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict("invoice_listeners")
|
||||||
|
|
||||||
|
|
||||||
|
def register_invoice_listener(send_chan: asyncio.Queue, name: str = None):
|
||||||
"""
|
"""
|
||||||
A method intended for extensions to call when they want to be notified about
|
A method intended for extensions (and core/tasks.py) to call when they want to be notified about
|
||||||
new invoice payments incoming.
|
new invoice payments incoming. Will emit all incoming payments.
|
||||||
"""
|
"""
|
||||||
invoice_listeners.append(send_chan)
|
name_unique = f"{name or 'no_name'}_{str(uuid.uuid4())[:8]}"
|
||||||
|
logger.trace(f"sse: registering invoice listener {name_unique}")
|
||||||
|
invoice_listeners[name_unique] = send_chan
|
||||||
|
|
||||||
|
|
||||||
async def webhook_handler():
|
async def webhook_handler():
|
||||||
|
"""
|
||||||
|
Returns the webhook_handler for the selected wallet if present. Used by API.
|
||||||
|
"""
|
||||||
handler = getattr(WALLET, "webhook_listener", None)
|
handler = getattr(WALLET, "webhook_listener", None)
|
||||||
if handler:
|
if handler:
|
||||||
return await handler()
|
return await handler()
|
||||||
|
|
@ -72,18 +89,36 @@ internal_invoice_queue: asyncio.Queue = asyncio.Queue(0)
|
||||||
|
|
||||||
|
|
||||||
async def internal_invoice_listener():
|
async def internal_invoice_listener():
|
||||||
|
"""
|
||||||
|
internal_invoice_queue will be filled directly in core/services.py
|
||||||
|
after the payment was deemed to be settled internally.
|
||||||
|
|
||||||
|
Called by the app startup sequence.
|
||||||
|
"""
|
||||||
while True:
|
while True:
|
||||||
checking_id = await internal_invoice_queue.get()
|
checking_id = await internal_invoice_queue.get()
|
||||||
|
logger.info("> got internal payment notification", checking_id)
|
||||||
asyncio.create_task(invoice_callback_dispatcher(checking_id))
|
asyncio.create_task(invoice_callback_dispatcher(checking_id))
|
||||||
|
|
||||||
|
|
||||||
async def invoice_listener():
|
async def invoice_listener():
|
||||||
|
"""
|
||||||
|
invoice_listener will collect all invoices that come directly
|
||||||
|
from the backend wallet.
|
||||||
|
|
||||||
|
Called by the app startup sequence.
|
||||||
|
"""
|
||||||
async for checking_id in WALLET.paid_invoices_stream():
|
async for checking_id in WALLET.paid_invoices_stream():
|
||||||
logger.info("> got a payment notification", checking_id)
|
logger.info("> got a payment notification", checking_id)
|
||||||
asyncio.create_task(invoice_callback_dispatcher(checking_id))
|
asyncio.create_task(invoice_callback_dispatcher(checking_id))
|
||||||
|
|
||||||
|
|
||||||
async def check_pending_payments():
|
async def check_pending_payments():
|
||||||
|
"""
|
||||||
|
check_pending_payments is called during startup to check for pending payments with
|
||||||
|
the backend and also to delete expired invoices. Incoming payments will be
|
||||||
|
checked only once, outgoing pending payments will be checked regularly.
|
||||||
|
"""
|
||||||
outgoing = True
|
outgoing = True
|
||||||
incoming = True
|
incoming = True
|
||||||
|
|
||||||
|
|
@ -133,9 +168,14 @@ async def perform_balance_checks():
|
||||||
|
|
||||||
|
|
||||||
async def invoice_callback_dispatcher(checking_id: str):
|
async def invoice_callback_dispatcher(checking_id: str):
|
||||||
|
"""
|
||||||
|
Takes incoming payments, sets pending=False, and dispatches them to
|
||||||
|
invoice_listeners from core and extensions.
|
||||||
|
"""
|
||||||
payment = await get_standalone_payment(checking_id, incoming=True)
|
payment = await get_standalone_payment(checking_id, incoming=True)
|
||||||
if payment and payment.is_in:
|
if payment and payment.is_in:
|
||||||
logger.trace("sending invoice callback for payment", checking_id)
|
logger.trace(f"sse sending invoice callback for payment {checking_id}")
|
||||||
await payment.set_pending(False)
|
await payment.set_pending(False)
|
||||||
for send_chan in invoice_listeners:
|
for chan_name, send_chan in invoice_listeners.items():
|
||||||
|
logger.trace(f"sse sending to chan: {chan_name}")
|
||||||
await send_chan.put(payment)
|
await send_chan.put(payment)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
|
|
||||||
|
|
||||||
from .cliche import ClicheWallet
|
from .cliche import ClicheWallet
|
||||||
from .cln import CoreLightningWallet # legacy .env support
|
from .cln import CoreLightningWallet # legacy .env support
|
||||||
from .cln import CoreLightningWallet as CLightningWallet
|
from .cln import CoreLightningWallet as CLightningWallet
|
||||||
|
|
@ -9,6 +10,7 @@ from .lnbits import LNbitsWallet
|
||||||
from .lndgrpc import LndWallet
|
from .lndgrpc import LndWallet
|
||||||
from .lndrest import LndRestWallet
|
from .lndrest import LndRestWallet
|
||||||
from .lnpay import LNPayWallet
|
from .lnpay import LNPayWallet
|
||||||
|
from .lntips import LnTipsWallet
|
||||||
from .lntxbot import LntxbotWallet
|
from .lntxbot import LntxbotWallet
|
||||||
from .opennode import OpenNodeWallet
|
from .opennode import OpenNodeWallet
|
||||||
from .spark import SparkWallet
|
from .spark import SparkWallet
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ from typing import AsyncGenerator, Dict, Optional
|
||||||
from environs import Env # type: ignore
|
from environs import Env # type: ignore
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from ..bolt11 import Invoice, decode, encode
|
||||||
|
|
||||||
from ..bolt11 import decode, encode
|
|
||||||
from .base import (
|
from .base import (
|
||||||
InvoiceResponse,
|
InvoiceResponse,
|
||||||
PaymentResponse,
|
PaymentResponse,
|
||||||
|
|
@ -24,6 +22,16 @@ env.read_env()
|
||||||
|
|
||||||
|
|
||||||
class FakeWallet(Wallet):
|
class FakeWallet(Wallet):
|
||||||
|
queue: asyncio.Queue = asyncio.Queue(0)
|
||||||
|
secret: str = env.str("FAKE_WALLET_SECTRET", default="ToTheMoon1")
|
||||||
|
privkey: str = hashlib.pbkdf2_hmac(
|
||||||
|
"sha256",
|
||||||
|
secret.encode("utf-8"),
|
||||||
|
("FakeWallet").encode("utf-8"),
|
||||||
|
2048,
|
||||||
|
32,
|
||||||
|
).hex()
|
||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
logger.info(
|
logger.info(
|
||||||
"FakeWallet funding source is for using LNbits as a centralised, stand-alone payment system with brrrrrr."
|
"FakeWallet funding source is for using LNbits as a centralised, stand-alone payment system with brrrrrr."
|
||||||
|
|
@ -39,18 +47,12 @@ class FakeWallet(Wallet):
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
# we set a default secret since FakeWallet is used for internal=True invoices
|
# we set a default secret since FakeWallet is used for internal=True invoices
|
||||||
# and the user might not have configured a secret yet
|
# and the user might not have configured a secret yet
|
||||||
secret = env.str("FAKE_WALLET_SECTRET", default="ToTheMoon1")
|
|
||||||
data: Dict = {
|
data: Dict = {
|
||||||
"out": False,
|
"out": False,
|
||||||
"amount": amount,
|
"amount": amount,
|
||||||
"currency": "bc",
|
"currency": "bc",
|
||||||
"privkey": hashlib.pbkdf2_hmac(
|
"privkey": self.privkey,
|
||||||
"sha256",
|
|
||||||
secret.encode("utf-8"),
|
|
||||||
("FakeWallet").encode("utf-8"),
|
|
||||||
2048,
|
|
||||||
32,
|
|
||||||
).hex(),
|
|
||||||
"memo": None,
|
"memo": None,
|
||||||
"description_hash": None,
|
"description_hash": None,
|
||||||
"description": "",
|
"description": "",
|
||||||
|
|
@ -86,8 +88,9 @@ class FakeWallet(Wallet):
|
||||||
invoice = decode(bolt11)
|
invoice = decode(bolt11)
|
||||||
if (
|
if (
|
||||||
hasattr(invoice, "checking_id")
|
hasattr(invoice, "checking_id")
|
||||||
and invoice.checking_id[6:] == data["privkey"][:6] # type: ignore
|
and invoice.checking_id[:6] == self.privkey[:6] # type: ignore
|
||||||
):
|
):
|
||||||
|
await self.queue.put(invoice)
|
||||||
return PaymentResponse(True, invoice.payment_hash, 0)
|
return PaymentResponse(True, invoice.payment_hash, 0)
|
||||||
else:
|
else:
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
|
|
@ -101,7 +104,6 @@ class FakeWallet(Wallet):
|
||||||
return PaymentStatus(None)
|
return PaymentStatus(None)
|
||||||
|
|
||||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||||
self.queue: asyncio.Queue = asyncio.Queue(0)
|
|
||||||
while True:
|
while True:
|
||||||
value = await self.queue.get()
|
value: Invoice = await self.queue.get()
|
||||||
yield value
|
yield value.payment_hash
|
||||||
|
|
|
||||||
170
lnbits/wallets/lntips.py
Normal file
170
lnbits/wallets/lntips.py
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
import asyncio
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from os import getenv
|
||||||
|
from typing import AsyncGenerator, Dict, Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from .base import (
|
||||||
|
InvoiceResponse,
|
||||||
|
PaymentResponse,
|
||||||
|
PaymentStatus,
|
||||||
|
StatusResponse,
|
||||||
|
Wallet,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LnTipsWallet(Wallet):
|
||||||
|
def __init__(self):
|
||||||
|
endpoint = getenv("LNTIPS_API_ENDPOINT")
|
||||||
|
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||||
|
|
||||||
|
key = (
|
||||||
|
getenv("LNTIPS_API_KEY")
|
||||||
|
or getenv("LNTIPS_ADMIN_KEY")
|
||||||
|
or getenv("LNTIPS_INVOICE_KEY")
|
||||||
|
)
|
||||||
|
self.auth = {"Authorization": f"Basic {key}"}
|
||||||
|
|
||||||
|
async def status(self) -> StatusResponse:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.get(
|
||||||
|
f"{self.endpoint}/api/v1/balance", headers=self.auth, timeout=40
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
data = r.json()
|
||||||
|
except:
|
||||||
|
return StatusResponse(
|
||||||
|
f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if data.get("error"):
|
||||||
|
return StatusResponse(data["error"], 0)
|
||||||
|
|
||||||
|
return StatusResponse(None, data["balance"] * 1000)
|
||||||
|
|
||||||
|
async def create_invoice(
|
||||||
|
self,
|
||||||
|
amount: int,
|
||||||
|
memo: Optional[str] = None,
|
||||||
|
description_hash: Optional[bytes] = None,
|
||||||
|
unhashed_description: Optional[bytes] = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> InvoiceResponse:
|
||||||
|
data: Dict = {"amount": amount}
|
||||||
|
if description_hash:
|
||||||
|
data["description_hash"] = description_hash.hex()
|
||||||
|
elif unhashed_description:
|
||||||
|
data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest()
|
||||||
|
else:
|
||||||
|
data["memo"] = memo or ""
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.post(
|
||||||
|
f"{self.endpoint}/api/v1/createinvoice",
|
||||||
|
headers=self.auth,
|
||||||
|
json=data,
|
||||||
|
timeout=40,
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.is_error:
|
||||||
|
try:
|
||||||
|
data = r.json()
|
||||||
|
error_message = data["message"]
|
||||||
|
except:
|
||||||
|
error_message = r.text
|
||||||
|
pass
|
||||||
|
|
||||||
|
return InvoiceResponse(False, None, None, error_message)
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
return InvoiceResponse(
|
||||||
|
True, data["payment_hash"], data["payment_request"], None
|
||||||
|
)
|
||||||
|
|
||||||
|
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.post(
|
||||||
|
f"{self.endpoint}/api/v1/payinvoice",
|
||||||
|
headers=self.auth,
|
||||||
|
json={"pay_req": bolt11},
|
||||||
|
timeout=None,
|
||||||
|
)
|
||||||
|
if r.is_error:
|
||||||
|
return PaymentResponse(False, None, 0, None, r.text)
|
||||||
|
|
||||||
|
if "error" in r.json():
|
||||||
|
try:
|
||||||
|
data = r.json()
|
||||||
|
error_message = data["error"]
|
||||||
|
except:
|
||||||
|
error_message = r.text
|
||||||
|
pass
|
||||||
|
return PaymentResponse(False, None, 0, None, error_message)
|
||||||
|
|
||||||
|
data = r.json()["details"]
|
||||||
|
checking_id = data["payment_hash"]
|
||||||
|
fee_msat = -data["fee"]
|
||||||
|
preimage = data["preimage"]
|
||||||
|
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
|
||||||
|
|
||||||
|
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.post(
|
||||||
|
f"{self.endpoint}/api/v1/invoicestatus/{checking_id}",
|
||||||
|
headers=self.auth,
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.is_error or len(r.text) == 0:
|
||||||
|
return PaymentStatus(None)
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
return PaymentStatus(data["paid"])
|
||||||
|
|
||||||
|
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.post(
|
||||||
|
url=f"{self.endpoint}/api/v1/paymentstatus/{checking_id}",
|
||||||
|
headers=self.auth,
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.is_error:
|
||||||
|
return PaymentStatus(None)
|
||||||
|
data = r.json()
|
||||||
|
|
||||||
|
paid_to_status = {False: None, True: True}
|
||||||
|
return PaymentStatus(paid_to_status[data.get("paid")])
|
||||||
|
|
||||||
|
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||||
|
last_connected = None
|
||||||
|
while True:
|
||||||
|
url = f"{self.endpoint}/api/v1/invoicestream"
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=None, headers=self.auth) as client:
|
||||||
|
last_connected = time.time()
|
||||||
|
async with client.stream("GET", url) as r:
|
||||||
|
async for line in r.aiter_lines():
|
||||||
|
try:
|
||||||
|
prefix = "data: "
|
||||||
|
if not line.startswith(prefix):
|
||||||
|
continue
|
||||||
|
data = line[len(prefix) :] # sse parsing
|
||||||
|
inv = json.loads(data)
|
||||||
|
if not inv.get("payment_hash"):
|
||||||
|
continue
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
yield inv["payment_hash"]
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# do not sleep if the connection was active for more than 10s
|
||||||
|
# since the backend is expected to drop the connection after 90s
|
||||||
|
if last_connected is None or time.time() - last_connected < 10:
|
||||||
|
logger.error(
|
||||||
|
f"lost connection to {self.endpoint}/api/v1/invoicestream, retrying in 5 seconds"
|
||||||
|
)
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
@ -23,7 +23,7 @@ class VoidWallet(Wallet):
|
||||||
raise Unsupported("")
|
raise Unsupported("")
|
||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
logger.info(
|
logger.warning(
|
||||||
"This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits."
|
"This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits."
|
||||||
)
|
)
|
||||||
return StatusResponse(None, 0)
|
return StatusResponse(None, 0)
|
||||||
|
|
|
||||||
586
poetry.lock
generated
586
poetry.lock
generated
|
|
@ -20,8 +20,8 @@ sniffio = ">=1.1"
|
||||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||||
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
|
test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
|
||||||
trio = ["trio (>=0.16)"]
|
trio = ["trio (>=0.16)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -36,15 +36,26 @@ python-versions = ">=3.6"
|
||||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
|
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomicwrites"
|
name = "asn1crypto"
|
||||||
version = "1.4.1"
|
version = "1.5.1"
|
||||||
description = "Atomic file writes."
|
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"
|
||||||
category = "dev"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-timeout"
|
||||||
|
version = "4.0.2"
|
||||||
|
description = "Timeout context manager for asyncio programs"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
|
|
@ -55,10 +66,21 @@ optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
|
dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||||
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
|
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"]
|
||||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
|
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base58"
|
||||||
|
version = "2.1.1"
|
||||||
|
description = "Base58 and Base58Check implementation."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bech32"
|
name = "bech32"
|
||||||
|
|
@ -78,7 +100,7 @@ python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
version = "22.6.0"
|
version = "22.8.0"
|
||||||
description = "The uncompromising code formatter."
|
description = "The uncompromising code formatter."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
|
@ -100,13 +122,16 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||||
uvloop = ["uvloop (>=0.15.2)"]
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cerberus"
|
name = "Cerberus"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
description = "Lightweight, extensible schema and data validation tool for Python dictionaries."
|
description = "Lightweight, extensible schema and data validation tool for Python dictionaries."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7"
|
python-versions = ">=2.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
setuptools = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2021.5.30"
|
version = "2021.5.30"
|
||||||
|
|
@ -149,6 +174,18 @@ python-versions = ">=3.6"
|
||||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coincurve"
|
||||||
|
version = "17.0.0"
|
||||||
|
description = "Cross-platform Python CFFI bindings for libsecp256k1"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
asn1crypto = "*"
|
||||||
|
cffi = ">=1.3.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
|
@ -159,7 +196,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "6.4.4"
|
version = "6.5.0"
|
||||||
description = "Code coverage measurement for Python"
|
description = "Code coverage measurement for Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
|
@ -171,6 +208,25 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1
|
||||||
[package.extras]
|
[package.extras]
|
||||||
toml = ["tomli"]
|
toml = ["tomli"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cryptography"
|
||||||
|
version = "36.0.2"
|
||||||
|
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cffi = ">=1.12"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"]
|
||||||
|
docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
|
||||||
|
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
|
||||||
|
sdist = ["setuptools_rust (>=0.11.4)"]
|
||||||
|
ssh = ["bcrypt (>=3.1.5)"]
|
||||||
|
test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.17.0"
|
version = "0.17.0"
|
||||||
|
|
@ -215,10 +271,10 @@ marshmallow = ">=3.0.0"
|
||||||
python-dotenv = "*"
|
python-dotenv = "*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "tox"]
|
dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"]
|
||||||
django = ["dj-database-url", "dj-email-url", "django-cache-url"]
|
django = ["dj-database-url", "dj-email-url", "django-cache-url"]
|
||||||
lint = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"]
|
lint = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"]
|
||||||
tests = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url"]
|
tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
|
|
@ -233,10 +289,24 @@ pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.
|
||||||
starlette = "0.19.1"
|
starlette = "0.19.1"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
|
all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
|
||||||
dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)", "pre-commit (>=2.17.0,<3.0.0)"]
|
dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
|
||||||
doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer (>=0.4.1,<0.5.0)", "pyyaml (>=5.3.1,<7.0.0)"]
|
doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"]
|
||||||
test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==22.3.0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==4.2.1)", "types-orjson (==3.6.2)", "types-dataclasses (==0.6.5)"]
|
test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grpcio"
|
||||||
|
version = "1.49.1"
|
||||||
|
description = "HTTP/2-based RPC framework"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = ">=1.5.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
protobuf = ["grpcio-tools (>=1.49.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
|
|
@ -290,8 +360,8 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
|
||||||
sniffio = "*"
|
sniffio = "*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
brotli = ["brotlicffi", "brotli"]
|
brotli = ["brotli", "brotlicffi"]
|
||||||
cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
|
cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
|
||||||
http2 = ["h2 (>=3,<5)"]
|
http2 = ["h2 (>=3,<5)"]
|
||||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||||
|
|
||||||
|
|
@ -316,9 +386,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
|
||||||
zipp = ">=0.5"
|
zipp = ">=0.5"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
|
||||||
perf = ["ipython"]
|
perf = ["ipython"]
|
||||||
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
|
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy", "pytest-perf (>=0.9.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
|
|
@ -337,13 +407,13 @@ optional = false
|
||||||
python-versions = ">=3.6.1,<4.0"
|
python-versions = ">=3.6.1,<4.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
|
|
||||||
requirements_deprecated_finder = ["pipreqs", "pip-api"]
|
|
||||||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||||
|
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
|
||||||
plugins = ["setuptools"]
|
plugins = ["setuptools"]
|
||||||
|
requirements_deprecated_finder = ["pip-api", "pipreqs"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "Jinja2"
|
||||||
version = "3.0.1"
|
version = "3.0.1"
|
||||||
description = "A very fast and expressive template engine."
|
description = "A very fast and expressive template engine."
|
||||||
category = "main"
|
category = "main"
|
||||||
|
|
@ -382,10 +452,10 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
|
||||||
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"]
|
dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "MarkupSafe"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
description = "Safely add untrusted strings to HTML/XML markup."
|
description = "Safely add untrusted strings to HTML/XML markup."
|
||||||
category = "main"
|
category = "main"
|
||||||
|
|
@ -404,9 +474,9 @@ python-versions = ">=3.7"
|
||||||
packaging = ">=17.0"
|
packaging = ">=17.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["pytest", "pytz", "simplejson", "mypy (==0.961)", "flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "pre-commit (>=2.4,<3.0)", "tox"]
|
dev = ["flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "mypy (==0.961)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"]
|
||||||
docs = ["sphinx (==4.5.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.8)"]
|
docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.8)", "sphinx (==4.5.0)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"]
|
||||||
lint = ["mypy (==0.961)", "flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "pre-commit (>=2.4,<3.0)"]
|
lint = ["flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "mypy (==0.961)", "pre-commit (>=2.4,<3.0)"]
|
||||||
tests = ["pytest", "pytz", "simplejson"]
|
tests = ["pytest", "pytz", "simplejson"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -418,7 +488,7 @@ optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
build = ["twine", "wheel", "blurb"]
|
build = ["blurb", "twine", "wheel"]
|
||||||
docs = ["sphinx"]
|
docs = ["sphinx"]
|
||||||
test = ["pytest (<5.4)", "pytest-cov"]
|
test = ["pytest (<5.4)", "pytest-cov"]
|
||||||
|
|
||||||
|
|
@ -484,11 +554,11 @@ six = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
version = "0.9.0"
|
version = "0.10.1"
|
||||||
description = "Utility library for gitignore style pattern matching of file paths."
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
|
|
@ -499,8 +569,8 @@ optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
|
|
@ -514,8 +584,16 @@ python-versions = ">=3.6"
|
||||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["pytest-benchmark", "pytest"]
|
dev = ["pre-commit", "tox"]
|
||||||
dev = ["tox", "pre-commit"]
|
testing = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protobuf"
|
||||||
|
version = "4.21.7"
|
||||||
|
description = ""
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg2-binary"
|
name = "psycopg2-binary"
|
||||||
|
|
@ -564,6 +642,41 @@ typing-extensions = ">=3.7.4.3"
|
||||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||||
email = ["email-validator (>=1.0.3)"]
|
email = ["email-validator (>=1.0.3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyln-bolt7"
|
||||||
|
version = "1.0.246"
|
||||||
|
description = "BOLT7"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7,<4.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyln-client"
|
||||||
|
version = "0.11.1"
|
||||||
|
description = "Client library and plugin library for Core Lightning"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7,<4.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pyln-bolt7 = ">=1.0,<2.0"
|
||||||
|
pyln-proto = ">=0.11,<0.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyln-proto"
|
||||||
|
version = "0.11.1"
|
||||||
|
description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7,<4.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
base58 = ">=2.1.1,<3.0.0"
|
||||||
|
bitstring = ">=3.1.9,<4.0.0"
|
||||||
|
coincurve = ">=17.0.0,<18.0.0"
|
||||||
|
cryptography = ">=36.0.1,<37.0.0"
|
||||||
|
PySocks = ">=1.7.1,<2.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyparsing"
|
name = "pyparsing"
|
||||||
version = "3.0.9"
|
version = "3.0.9"
|
||||||
|
|
@ -573,7 +686,7 @@ optional = false
|
||||||
python-versions = ">=3.6.8"
|
python-versions = ">=3.6.8"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
diagrams = ["railroad-diagrams", "jinja2"]
|
diagrams = ["jinja2", "railroad-diagrams"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pypng"
|
name = "pypng"
|
||||||
|
|
@ -584,7 +697,7 @@ optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyqrcode"
|
name = "PyQRCode"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output."
|
description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output."
|
||||||
category = "main"
|
category = "main"
|
||||||
|
|
@ -595,7 +708,7 @@ python-versions = "*"
|
||||||
PNG = ["pypng (>=0.0.13)"]
|
PNG = ["pypng (>=0.0.13)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyscss"
|
name = "pyScss"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
description = "pyScss, a Scss compiler for Python"
|
description = "pyScss, a Scss compiler for Python"
|
||||||
category = "main"
|
category = "main"
|
||||||
|
|
@ -607,16 +720,23 @@ enum34 = "*"
|
||||||
pathlib2 = "*"
|
pathlib2 = "*"
|
||||||
six = "*"
|
six = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "PySocks"
|
||||||
|
version = "1.7.1"
|
||||||
|
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "7.1.2"
|
version = "7.1.3"
|
||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
|
||||||
attrs = ">=19.2.0"
|
attrs = ">=19.2.0"
|
||||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||||
|
|
@ -642,7 +762,7 @@ pytest = ">=6.1.0"
|
||||||
typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
|
typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
|
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest-cov"
|
name = "pytest-cov"
|
||||||
|
|
@ -657,7 +777,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||||
pytest = ">=4.6"
|
pytest = ">=4.6"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"]
|
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
|
|
@ -671,7 +791,7 @@ python-versions = ">=3.5"
|
||||||
cli = ["click (>=5.0)"]
|
cli = ["click (>=5.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "PyYAML"
|
||||||
version = "5.4.1"
|
version = "5.4.1"
|
||||||
description = "YAML parser and emitter for Python"
|
description = "YAML parser and emitter for Python"
|
||||||
category = "main"
|
category = "main"
|
||||||
|
|
@ -679,7 +799,7 @@ optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "represent"
|
name = "Represent"
|
||||||
version = "1.6.0.post0"
|
version = "1.6.0.post0"
|
||||||
description = "Create __repr__ automatically or declaratively."
|
description = "Create __repr__ automatically or declaratively."
|
||||||
category = "main"
|
category = "main"
|
||||||
|
|
@ -690,7 +810,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
six = ">=1.8.0"
|
six = ">=1.8.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["ipython", "pytest (>=3.0.5)", "mock"]
|
test = ["ipython", "mock", "pytest (>=3.0.5)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfc3986"
|
name = "rfc3986"
|
||||||
|
|
@ -717,6 +837,19 @@ python-versions = "*"
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
cffi = ">=1.3.0"
|
cffi = ">=1.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "setuptools"
|
||||||
|
version = "65.4.1"
|
||||||
|
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||||
|
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
|
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shortuuid"
|
name = "shortuuid"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
@ -742,7 +875,7 @@ optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlalchemy"
|
name = "SQLAlchemy"
|
||||||
version = "1.3.23"
|
version = "1.3.23"
|
||||||
description = "Database Abstraction Library"
|
description = "Database Abstraction Library"
|
||||||
category = "main"
|
category = "main"
|
||||||
|
|
@ -754,12 +887,12 @@ mssql = ["pyodbc"]
|
||||||
mssql_pymssql = ["pymssql"]
|
mssql_pymssql = ["pymssql"]
|
||||||
mssql_pyodbc = ["pyodbc"]
|
mssql_pyodbc = ["pyodbc"]
|
||||||
mysql = ["mysqlclient"]
|
mysql = ["mysqlclient"]
|
||||||
oracle = ["cx-oracle"]
|
oracle = ["cx_oracle"]
|
||||||
postgresql = ["psycopg2"]
|
postgresql = ["psycopg2"]
|
||||||
postgresql_pg8000 = ["pg8000 (<1.16.6)"]
|
postgresql_pg8000 = ["pg8000 (<1.16.6)"]
|
||||||
postgresql_psycopg2binary = ["psycopg2-binary"]
|
postgresql_psycopg2binary = ["psycopg2-binary"]
|
||||||
postgresql_psycopg2cffi = ["psycopg2cffi"]
|
postgresql_psycopg2cffi = ["psycopg2cffi"]
|
||||||
pymysql = ["pymysql (<1)", "pymysql"]
|
pymysql = ["pymysql", "pymysql (<1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlalchemy-aio"
|
name = "sqlalchemy-aio"
|
||||||
|
|
@ -820,7 +953,7 @@ python-versions = ">=3.6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-protobuf"
|
name = "types-protobuf"
|
||||||
version = "3.19.22"
|
version = "3.20.4"
|
||||||
description = "Typing stubs for protobuf"
|
description = "Typing stubs for protobuf"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
|
@ -848,7 +981,7 @@ h11 = ">=0.8"
|
||||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchfiles (>=0.13)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
|
standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uvloop"
|
name = "uvloop"
|
||||||
|
|
@ -859,9 +992,9 @@ optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
|
dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
||||||
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
|
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
||||||
test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
|
test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "watchgod"
|
name = "watchgod"
|
||||||
|
|
@ -912,13 +1045,13 @@ optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
|
||||||
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
|
testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.9 | ^3.8 | ^3.7"
|
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
|
||||||
content-hash = "ac8c4117d537aaf8853d35038f2821ea4bc04b29c7971b91fd46329365008b95"
|
content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
aiofiles = [
|
aiofiles = [
|
||||||
|
|
@ -933,13 +1066,22 @@ asgiref = [
|
||||||
{file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"},
|
{file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"},
|
||||||
{file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"},
|
{file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"},
|
||||||
]
|
]
|
||||||
atomicwrites = [
|
asn1crypto = [
|
||||||
{file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
|
{file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"},
|
||||||
|
{file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"},
|
||||||
|
]
|
||||||
|
async-timeout = [
|
||||||
|
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
|
||||||
|
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
|
||||||
]
|
]
|
||||||
attrs = [
|
attrs = [
|
||||||
{file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
|
{file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
|
||||||
{file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
|
{file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
|
||||||
]
|
]
|
||||||
|
base58 = [
|
||||||
|
{file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"},
|
||||||
|
{file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"},
|
||||||
|
]
|
||||||
bech32 = [
|
bech32 = [
|
||||||
{file = "bech32-1.2.0-py3-none-any.whl", hash = "sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"},
|
{file = "bech32-1.2.0-py3-none-any.whl", hash = "sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"},
|
||||||
{file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"},
|
{file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"},
|
||||||
|
|
@ -950,31 +1092,31 @@ bitstring = [
|
||||||
{file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"},
|
{file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"},
|
||||||
]
|
]
|
||||||
black = [
|
black = [
|
||||||
{file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"},
|
{file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"},
|
||||||
{file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
|
{file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"},
|
||||||
{file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"},
|
{file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"},
|
||||||
{file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"},
|
{file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"},
|
||||||
{file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"},
|
{file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"},
|
||||||
{file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"},
|
{file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"},
|
||||||
{file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"},
|
{file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"},
|
||||||
{file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"},
|
{file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"},
|
||||||
{file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"},
|
{file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"},
|
||||||
{file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"},
|
{file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"},
|
||||||
{file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"},
|
{file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"},
|
||||||
{file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"},
|
{file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"},
|
||||||
{file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"},
|
{file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"},
|
||||||
{file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"},
|
{file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"},
|
||||||
{file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"},
|
{file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"},
|
||||||
{file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"},
|
{file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"},
|
||||||
{file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"},
|
{file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"},
|
||||||
{file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"},
|
{file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"},
|
||||||
{file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"},
|
{file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"},
|
||||||
{file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"},
|
{file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"},
|
||||||
{file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"},
|
{file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"},
|
||||||
{file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"},
|
{file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"},
|
||||||
{file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"},
|
{file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
|
||||||
]
|
]
|
||||||
cerberus = [
|
Cerberus = [
|
||||||
{file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"},
|
{file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"},
|
||||||
]
|
]
|
||||||
certifi = [
|
certifi = [
|
||||||
|
|
@ -1041,61 +1183,119 @@ click = [
|
||||||
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
|
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
|
||||||
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
|
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
|
||||||
]
|
]
|
||||||
|
coincurve = [
|
||||||
|
{file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"},
|
||||||
|
{file = "coincurve-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924"},
|
||||||
|
{file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb"},
|
||||||
|
{file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30dd44d1039f1d237aaa2da6d14a455ca88df3bcb00610b41f3253fdca1be97b"},
|
||||||
|
{file = "coincurve-17.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154e2eb5711db8c5ef52fcd80935b5a0e751c057bc6ffb215a7bb409aedef03"},
|
||||||
|
{file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c71caffb97dd3d0c243beb62352669b1e5dafa3a4bccdbb27d36bd82f5e65d20"},
|
||||||
|
{file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:747215254e51dd4dfbe6dded9235491263da5d88fe372d66541ca16b51ea078f"},
|
||||||
|
{file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad2f6df39ba1e2b7b14bb984505ffa7d0a0ecdd697e8d7dbd19e04bc245c87ed"},
|
||||||
|
{file = "coincurve-17.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0503326963916c85b61d16f611ea0545f03c9e418fa8007c233c815429e381e8"},
|
||||||
|
{file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1013c1597b65684ae1c3e42497f9ef5a04527fa6136a84a16b34602606428c74"},
|
||||||
|
{file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4beef321fd6434448aab03a0c245f31c4e77f43b54b82108c0948d29852ac7e"},
|
||||||
|
{file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f47806527d3184da3e8b146fac92a8ed567bbd225194f4517943d8cdc85f9542"},
|
||||||
|
{file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51e56373ac79f4ec1cfc5da53d72c55f5e5ac28d848b0849ef5e687ace857888"},
|
||||||
|
{file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d694ad194bee9e8792e2e75879dc5238d8a184010cde36c5ad518fcfe2cd8f2"},
|
||||||
|
{file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74cedb3d3a1dc5abe0c9c2396e1b82cc64496babc5b42e007e72e185cb1edad8"},
|
||||||
|
{file = "coincurve-17.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:db874c5c1dcb1f3a19379773b5e8cffc777625a7a7a60dd9a67206e31e62e2e9"},
|
||||||
|
{file = "coincurve-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:896b01941254f0a218cf331a9bddfe2d43892f7f1ba10d6e372e2eb744a744c2"},
|
||||||
|
{file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6aec70238dbe7a5d66b5f9438ff45b08eb5e0990d49c32ebb65247c5d5b89d7a"},
|
||||||
|
{file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24284d17162569df917a640f19d9654ba3b43cf560ced8864f270da903f73a5"},
|
||||||
|
{file = "coincurve-17.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ea057f777842396d387103c606babeb3a1b4c6126769cc0a12044312fc6c465"},
|
||||||
|
{file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b88642edf7f281649b0c0b6ffade051945ccceae4b885e40445634877d0b3049"},
|
||||||
|
{file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a80a207131813b038351c5bdae8f20f5f774bbf53622081f208d040dd2b7528f"},
|
||||||
|
{file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1ef72574aa423bc33665ef4be859164a478bad24d48442da874ef3dc39a474d"},
|
||||||
|
{file = "coincurve-17.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfd4fab857bcd975edc39111cb5f5c104f138dac2e9ace35ea8434d37bcea3be"},
|
||||||
|
{file = "coincurve-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73f39579dd651a9fc29da5a8fc0d8153d872bcbc166f876457baced1a1c01501"},
|
||||||
|
{file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8852dc01af4f0fe941ffd04069f7e4fecdce9b867a016f823a02286a1a1f07b5"},
|
||||||
|
{file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1bef812da1da202cdd601a256825abcf26d86e8634fac3ec3e615e3bb3ff08c"},
|
||||||
|
{file = "coincurve-17.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abbefc9ccb170cb255a31df32457c2e43084b9f37589d0694dacc2dea6ddaf7c"},
|
||||||
|
{file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:abbd9d017a7638dc38a3b9bb4851f8801b7818d4e5ac22e0c75e373b3c1dbff0"},
|
||||||
|
{file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e2c2e8a1f0b1f8e48049c891af4ae3cad65d115d358bde72f6b8abdbb8a23170"},
|
||||||
|
{file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c571445b166c714af4f8155e38a894376c16c0431e88963f2fff474a9985d87"},
|
||||||
|
{file = "coincurve-17.0.0-py3-none-win32.whl", hash = "sha256:b956b0b2c85e25a7d00099970ff5d8338254b45e46f0a940f4a2379438ce0dde"},
|
||||||
|
{file = "coincurve-17.0.0-py3-none-win_amd64.whl", hash = "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e"},
|
||||||
|
{file = "coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"},
|
||||||
|
]
|
||||||
colorama = [
|
colorama = [
|
||||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||||
]
|
]
|
||||||
coverage = [
|
coverage = [
|
||||||
{file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"},
|
{file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
|
||||||
{file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"},
|
{file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
|
||||||
{file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"},
|
{file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
|
||||||
{file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"},
|
{file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"},
|
||||||
{file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"},
|
{file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"},
|
||||||
{file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"},
|
{file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
|
||||||
{file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"},
|
{file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
|
||||||
{file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"},
|
{file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
|
||||||
{file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"},
|
{file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
|
||||||
{file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"},
|
{file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
|
||||||
{file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"},
|
{file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
|
||||||
{file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"},
|
{file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
|
||||||
{file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"},
|
{file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"},
|
||||||
{file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"},
|
{file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"},
|
||||||
{file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"},
|
{file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
|
||||||
{file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"},
|
{file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
|
||||||
{file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"},
|
{file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
|
||||||
{file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"},
|
{file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
|
||||||
{file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"},
|
{file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
|
||||||
{file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"},
|
{file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
|
||||||
{file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"},
|
{file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
|
||||||
{file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"},
|
{file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"},
|
||||||
{file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"},
|
{file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"},
|
||||||
{file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"},
|
{file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
|
||||||
{file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"},
|
{file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
|
||||||
{file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"},
|
{file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
|
||||||
{file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"},
|
{file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
|
||||||
{file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"},
|
{file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"},
|
{file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"},
|
{file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"},
|
{file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"},
|
{file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"},
|
{file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"},
|
{file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"},
|
{file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"},
|
{file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"},
|
{file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
|
||||||
{file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"},
|
{file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"},
|
{file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"},
|
{file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"},
|
{file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"},
|
{file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"},
|
{file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"},
|
{file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"},
|
{file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"},
|
{file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"},
|
{file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
|
||||||
{file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"},
|
{file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
|
||||||
{file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"},
|
{file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
|
||||||
{file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"},
|
{file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
|
||||||
|
]
|
||||||
|
cryptography = [
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-win32.whl", hash = "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"},
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29"},
|
||||||
|
{file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7"},
|
||||||
|
{file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e"},
|
||||||
|
{file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0"},
|
||||||
|
{file = "cryptography-36.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b"},
|
||||||
|
{file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77"},
|
||||||
|
{file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85"},
|
||||||
|
{file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51"},
|
||||||
|
{file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"},
|
||||||
|
{file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"},
|
||||||
]
|
]
|
||||||
ecdsa = [
|
ecdsa = [
|
||||||
{file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"},
|
{file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"},
|
||||||
|
|
@ -1117,6 +1317,53 @@ fastapi = [
|
||||||
{file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
|
{file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
|
||||||
{file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
|
{file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
|
||||||
]
|
]
|
||||||
|
grpcio = [
|
||||||
|
{file = "grpcio-1.49.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:fd86040232e805b8e6378b2348c928490ee595b058ce9aaa27ed8e4b0f172b20"},
|
||||||
|
{file = "grpcio-1.49.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6fd0c9cede9552bf00f8c5791d257d5bf3790d7057b26c59df08be5e7a1e021d"},
|
||||||
|
{file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d0d402e158d4e84e49c158cb5204119d55e1baf363ee98d6cb5dce321c3a065d"},
|
||||||
|
{file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ceec743d42a627e64ea266059a62d214c5a3cdfcd0d7fe2b7a8e4e82527c7"},
|
||||||
|
{file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2106d9c16527f0a85e2eea6e6b91a74fc99579c60dd810d8690843ea02bc0f5f"},
|
||||||
|
{file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:52dd02b7e7868233c571b49bc38ebd347c3bb1ff8907bb0cb74cb5f00c790afc"},
|
||||||
|
{file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:120fecba2ec5d14b5a15d11063b39783fda8dc8d24addd83196acb6582cabd9b"},
|
||||||
|
{file = "grpcio-1.49.1-cp310-cp310-win32.whl", hash = "sha256:f1a3b88e3c53c1a6e6bed635ec1bbb92201bb6a1f2db186179f7f3f244829788"},
|
||||||
|
{file = "grpcio-1.49.1-cp310-cp310-win_amd64.whl", hash = "sha256:a7d0017b92d3850abea87c1bdec6ea41104e71c77bca44c3e17f175c6700af62"},
|
||||||
|
{file = "grpcio-1.49.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:9fb17ff8c0d56099ac6ebfa84f670c5a62228d6b5c695cf21c02160c2ac1446b"},
|
||||||
|
{file = "grpcio-1.49.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:075f2d06e3db6b48a2157a1bcd52d6cbdca980dd18988fe6afdb41795d51625f"},
|
||||||
|
{file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46d93a1b4572b461a227f1db6b8d35a88952db1c47e5fadcf8b8a2f0e1dd9201"},
|
||||||
|
{file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc79b2b37d779ac42341ddef40ad5bf0966a64af412c89fc2b062e3ddabb093f"},
|
||||||
|
{file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5f8b3a971c7820ea9878f3fd70086240a36aeee15d1b7e9ecbc2743b0e785568"},
|
||||||
|
{file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49b301740cf5bc8fed4fee4c877570189ae3951432d79fa8e524b09353659811"},
|
||||||
|
{file = "grpcio-1.49.1-cp311-cp311-win32.whl", hash = "sha256:1c66a25afc6c71d357867b341da594a5587db5849b48f4b7d5908d236bb62ede"},
|
||||||
|
{file = "grpcio-1.49.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b6c3a95d27846f4145d6967899b3ab25fffc6ae99544415e1adcacef84842d2"},
|
||||||
|
{file = "grpcio-1.49.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:1cc400c8a2173d1c042997d98a9563e12d9bb3fb6ad36b7f355bc77c7663b8af"},
|
||||||
|
{file = "grpcio-1.49.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:34f736bd4d0deae90015c0e383885b431444fe6b6c591dea288173df20603146"},
|
||||||
|
{file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:196082b9c89ebf0961dcd77cb114bed8171964c8e3063b9da2fb33536a6938ed"},
|
||||||
|
{file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c9f89c42749890618cd3c2464e1fbf88446e3d2f67f1e334c8e5db2f3272bbd"},
|
||||||
|
{file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64419cb8a5b612cdb1550c2fd4acbb7d4fb263556cf4625f25522337e461509e"},
|
||||||
|
{file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8a5272061826e6164f96e3255405ef6f73b88fd3e8bef464c7d061af8585ac62"},
|
||||||
|
{file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ea9d0172445241ad7cb49577314e39d0af2c5267395b3561d7ced5d70458a9f3"},
|
||||||
|
{file = "grpcio-1.49.1-cp37-cp37m-win32.whl", hash = "sha256:2070e87d95991473244c72d96d13596c751cb35558e11f5df5414981e7ed2492"},
|
||||||
|
{file = "grpcio-1.49.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fcedcab49baaa9db4a2d240ac81f2d57eb0052b1c6a9501b46b8ae912720fbf"},
|
||||||
|
{file = "grpcio-1.49.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:afbb3475cf7f4f7d380c2ca37ee826e51974f3e2665613996a91d6a58583a534"},
|
||||||
|
{file = "grpcio-1.49.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a4f9ba141380abde6c3adc1727f21529137a2552002243fa87c41a07e528245c"},
|
||||||
|
{file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:cf0a1fb18a7204b9c44623dfbd1465b363236ce70c7a4ed30402f9f60d8b743b"},
|
||||||
|
{file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17bb6fe72784b630728c6cff9c9d10ccc3b6d04e85da6e0a7b27fb1d135fac62"},
|
||||||
|
{file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18305d5a082d1593b005a895c10041f833b16788e88b02bb81061f5ebcc465df"},
|
||||||
|
{file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b6a1b39e59ac5a3067794a0e498911cf2e37e4b19ee9e9977dc5e7051714f13f"},
|
||||||
|
{file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e20d59aafc086b1cc68400463bddda6e41d3e5ed30851d1e2e0f6a2e7e342d3"},
|
||||||
|
{file = "grpcio-1.49.1-cp38-cp38-win32.whl", hash = "sha256:e1e83233d4680863a421f3ee4a7a9b80d33cd27ee9ed7593bc93f6128302d3f2"},
|
||||||
|
{file = "grpcio-1.49.1-cp38-cp38-win_amd64.whl", hash = "sha256:221d42c654d2a41fa31323216279c73ed17d92f533bc140a3390cc1bd78bf63c"},
|
||||||
|
{file = "grpcio-1.49.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:fa9e6e61391e99708ac87fc3436f6b7b9c6b845dc4639b406e5e61901e1aacde"},
|
||||||
|
{file = "grpcio-1.49.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9b449e966ef518ce9c860d21f8afe0b0f055220d95bc710301752ac1db96dd6a"},
|
||||||
|
{file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aa34d2ad9f24e47fa9a3172801c676e4037d862247e39030165fe83821a7aafd"},
|
||||||
|
{file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5207f4eed1b775d264fcfe379d8541e1c43b878f2b63c0698f8f5c56c40f3d68"},
|
||||||
|
{file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b24a74651438d45619ac67004638856f76cc13d78b7478f2457754cbcb1c8ad"},
|
||||||
|
{file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fe763781669790dc8b9618e7e677c839c87eae6cf28b655ee1fa69ae04eea03f"},
|
||||||
|
{file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2f2ff7ba0f8f431f32d4b4bc3a3713426949d3533b08466c4ff1b2b475932ca8"},
|
||||||
|
{file = "grpcio-1.49.1-cp39-cp39-win32.whl", hash = "sha256:08ff74aec8ff457a89b97152d36cb811dcc1d17cd5a92a65933524e363327394"},
|
||||||
|
{file = "grpcio-1.49.1-cp39-cp39-win_amd64.whl", hash = "sha256:274ffbb39717918c514b35176510ae9be06e1d93121e84d50b350861dcb9a705"},
|
||||||
|
{file = "grpcio-1.49.1.tar.gz", hash = "sha256:d4725fc9ec8e8822906ae26bb26f5546891aa7fbc3443de970cc556d43a5c99f"},
|
||||||
|
]
|
||||||
h11 = [
|
h11 = [
|
||||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||||
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
|
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
|
||||||
|
|
@ -1181,7 +1428,7 @@ isort = [
|
||||||
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
||||||
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
||||||
]
|
]
|
||||||
jinja2 = [
|
Jinja2 = [
|
||||||
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
|
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
|
||||||
{file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
|
{file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
|
||||||
]
|
]
|
||||||
|
|
@ -1193,7 +1440,7 @@ loguru = [
|
||||||
{file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"},
|
{file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"},
|
||||||
{file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"},
|
{file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"},
|
||||||
]
|
]
|
||||||
markupsafe = [
|
MarkupSafe = [
|
||||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
|
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
|
||||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
|
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
|
||||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
|
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
|
||||||
|
|
@ -1314,8 +1561,8 @@ pathlib2 = [
|
||||||
{file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"},
|
{file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"},
|
||||||
]
|
]
|
||||||
pathspec = [
|
pathspec = [
|
||||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
{file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"},
|
||||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
{file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
|
||||||
]
|
]
|
||||||
platformdirs = [
|
platformdirs = [
|
||||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||||
|
|
@ -1325,6 +1572,22 @@ pluggy = [
|
||||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||||
]
|
]
|
||||||
|
protobuf = [
|
||||||
|
{file = "protobuf-4.21.7-cp310-abi3-win32.whl", hash = "sha256:c7cb105d69a87416bd9023e64324e1c089593e6dae64d2536f06bcbe49cd97d8"},
|
||||||
|
{file = "protobuf-4.21.7-cp310-abi3-win_amd64.whl", hash = "sha256:3ec85328a35a16463c6f419dbce3c0fc42b3e904d966f17f48bae39597c7a543"},
|
||||||
|
{file = "protobuf-4.21.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:db9056b6a11cb5131036d734bcbf91ef3ef9235d6b681b2fc431cbfe5a7f2e56"},
|
||||||
|
{file = "protobuf-4.21.7-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ca200645d6235ce0df3ccfdff1567acbab35c4db222a97357806e015f85b5744"},
|
||||||
|
{file = "protobuf-4.21.7-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b019c79e23a80735cc8a71b95f76a49a262f579d6b84fd20a0b82279f40e2cc1"},
|
||||||
|
{file = "protobuf-4.21.7-cp37-cp37m-win32.whl", hash = "sha256:d3f89ccf7182293feba2de2739c8bf34fed1ed7c65a5cf987be00311acac57c1"},
|
||||||
|
{file = "protobuf-4.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:a74d96cd960b87b4b712797c741bb3ea3a913f5c2dc4b6cbe9c0f8360b75297d"},
|
||||||
|
{file = "protobuf-4.21.7-cp38-cp38-win32.whl", hash = "sha256:8e09d1916386eca1ef1353767b6efcebc0a6859ed7f73cb7fb974feba3184830"},
|
||||||
|
{file = "protobuf-4.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:9e355f2a839d9930d83971b9f562395e13493f0e9211520f8913bd11efa53c02"},
|
||||||
|
{file = "protobuf-4.21.7-cp39-cp39-win32.whl", hash = "sha256:f370c0a71712f8965023dd5b13277444d3cdfecc96b2c778b0e19acbfd60df6e"},
|
||||||
|
{file = "protobuf-4.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:9643684232b6b340b5e63bb69c9b4904cdd39e4303d498d1a92abddc7e895b7f"},
|
||||||
|
{file = "protobuf-4.21.7-py2.py3-none-any.whl", hash = "sha256:8066322588d4b499869bf9f665ebe448e793036b552f68c585a9b28f1e393f66"},
|
||||||
|
{file = "protobuf-4.21.7-py3-none-any.whl", hash = "sha256:58b81358ec6c0b5d50df761460ae2db58405c063fd415e1101209221a0a810e1"},
|
||||||
|
{file = "protobuf-4.21.7.tar.gz", hash = "sha256:71d9dba03ed3432c878a801e2ea51e034b0ea01cf3a4344fb60166cb5f6c8757"},
|
||||||
|
]
|
||||||
psycopg2-binary = [
|
psycopg2-binary = [
|
||||||
{file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
|
{file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
|
||||||
{file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"},
|
{file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"},
|
||||||
|
|
@ -1424,6 +1687,18 @@ pydantic = [
|
||||||
{file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
|
{file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
|
||||||
{file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
|
{file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
|
||||||
]
|
]
|
||||||
|
pyln-bolt7 = [
|
||||||
|
{file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"},
|
||||||
|
{file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"},
|
||||||
|
]
|
||||||
|
pyln-client = [
|
||||||
|
{file = "pyln-client-0.11.1.tar.gz", hash = "sha256:f5ea648840b030e2bbcf8c66ee72d25a5817f89854434a28d30e887547138c8e"},
|
||||||
|
{file = "pyln_client-0.11.1-py3-none-any.whl", hash = "sha256:497db443406b80c98c0434e2938eb1b2a17e88fd9aa63b018124068198df6141"},
|
||||||
|
]
|
||||||
|
pyln-proto = [
|
||||||
|
{file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"},
|
||||||
|
{file = "pyln_proto-0.11.1-py3-none-any.whl", hash = "sha256:27b2e04a81b894f69018279c0ce4aa2e7ccd03b86dd9783f96b9d8d1498c8393"},
|
||||||
|
]
|
||||||
pyparsing = [
|
pyparsing = [
|
||||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||||
|
|
@ -1431,16 +1706,21 @@ pyparsing = [
|
||||||
pypng = [
|
pypng = [
|
||||||
{file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"},
|
{file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"},
|
||||||
]
|
]
|
||||||
pyqrcode = [
|
PyQRCode = [
|
||||||
{file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"},
|
{file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"},
|
||||||
{file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"},
|
{file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"},
|
||||||
]
|
]
|
||||||
pyscss = [
|
pyScss = [
|
||||||
{file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"},
|
{file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"},
|
||||||
]
|
]
|
||||||
|
PySocks = [
|
||||||
|
{file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"},
|
||||||
|
{file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"},
|
||||||
|
{file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
|
||||||
|
]
|
||||||
pytest = [
|
pytest = [
|
||||||
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
|
{file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"},
|
||||||
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
|
{file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"},
|
||||||
]
|
]
|
||||||
pytest-asyncio = [
|
pytest-asyncio = [
|
||||||
{file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"},
|
{file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"},
|
||||||
|
|
@ -1454,7 +1734,7 @@ python-dotenv = [
|
||||||
{file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
|
{file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
|
||||||
{file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
|
{file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
|
||||||
]
|
]
|
||||||
pyyaml = [
|
PyYAML = [
|
||||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||||
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
|
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
|
||||||
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
|
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
|
||||||
|
|
@ -1485,7 +1765,7 @@ pyyaml = [
|
||||||
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
|
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
|
||||||
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
|
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
|
||||||
]
|
]
|
||||||
represent = [
|
Represent = [
|
||||||
{file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"},
|
{file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"},
|
||||||
{file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"},
|
{file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"},
|
||||||
]
|
]
|
||||||
|
|
@ -1518,6 +1798,10 @@ secp256k1 = [
|
||||||
{file = "secp256k1-0.14.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c9e7c024ff17e9b9d7c392bb2a917da231d6cb40ab119389ff1f51dca10339a4"},
|
{file = "secp256k1-0.14.0-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"},
|
{file = "secp256k1-0.14.0.tar.gz", hash = "sha256:82c06712d69ef945220c8b53c1a0d424c2ff6a1f64aee609030df79ad8383397"},
|
||||||
]
|
]
|
||||||
|
setuptools = [
|
||||||
|
{file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"},
|
||||||
|
{file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"},
|
||||||
|
]
|
||||||
shortuuid = [
|
shortuuid = [
|
||||||
{file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"},
|
{file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"},
|
||||||
{file = "shortuuid-1.0.1.tar.gz", hash = "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f"},
|
{file = "shortuuid-1.0.1.tar.gz", hash = "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f"},
|
||||||
|
|
@ -1530,7 +1814,7 @@ sniffio = [
|
||||||
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
|
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
|
||||||
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
|
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
|
||||||
]
|
]
|
||||||
sqlalchemy = [
|
SQLAlchemy = [
|
||||||
{file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"},
|
{file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"},
|
||||||
{file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1"},
|
{file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1"},
|
||||||
{file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5"},
|
{file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5"},
|
||||||
|
|
@ -1612,8 +1896,8 @@ typed-ast = [
|
||||||
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
|
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
|
||||||
]
|
]
|
||||||
types-protobuf = [
|
types-protobuf = [
|
||||||
{file = "types-protobuf-3.19.22.tar.gz", hash = "sha256:d2b26861b0cb46a3c8669b0df507b7ef72e487da66d61f9f3576aa76ce028a83"},
|
{file = "types-protobuf-3.20.4.tar.gz", hash = "sha256:0dad3a5009895c985a56e2837f61902bad9594151265ac0ee907bb16d0b01eb7"},
|
||||||
{file = "types_protobuf-3.19.22-py3-none-any.whl", hash = "sha256:d291388678af91bb045fafa864f142dc4ac22f5d4cdca097c7d8d8a32fa9b3ab"},
|
{file = "types_protobuf-3.20.4-py3-none-any.whl", hash = "sha256:5082437afe64ce3b31c8db109eae86e02fda11e4d5f9ac59cb8578a8a138aa70"},
|
||||||
]
|
]
|
||||||
typing-extensions = [
|
typing-extensions = [
|
||||||
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ asgiref = "3.4.1"
|
||||||
attrs = "21.2.0"
|
attrs = "21.2.0"
|
||||||
bech32 = "1.2.0"
|
bech32 = "1.2.0"
|
||||||
bitstring = "3.1.9"
|
bitstring = "3.1.9"
|
||||||
cerberus = "1.3.4"
|
|
||||||
certifi = "2021.5.30"
|
certifi = "2021.5.30"
|
||||||
charset-normalizer = "2.0.6"
|
charset-normalizer = "2.0.6"
|
||||||
click = "8.0.1"
|
click = "8.0.1"
|
||||||
|
|
@ -60,6 +59,11 @@ zipp = "3.5.0"
|
||||||
loguru = "0.5.3"
|
loguru = "0.5.3"
|
||||||
cffi = "1.15.0"
|
cffi = "1.15.0"
|
||||||
websocket-client = "1.3.3"
|
websocket-client = "1.3.3"
|
||||||
|
grpcio = "^1.49.1"
|
||||||
|
protobuf = "^4.21.6"
|
||||||
|
Cerberus = "^1.3.4"
|
||||||
|
async-timeout = "^4.0.2"
|
||||||
|
pyln-client = "0.11.1"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
isort = "^5.10.1"
|
isort = "^5.10.1"
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,5 @@ uvloop==0.16.0
|
||||||
watchfiles==0.16.0
|
watchfiles==0.16.0
|
||||||
websockets==10.3
|
websockets==10.3
|
||||||
websocket-client==1.3.3
|
websocket-client==1.3.3
|
||||||
|
async-timeout==4.0.2
|
||||||
|
setuptools==65.4.0
|
||||||
Loading…
Add table
Add a link
Reference in a new issue