Merge branch 'main' into cashu
This commit is contained in:
commit
5984538de0
93 changed files with 4163 additions and 536 deletions
24
.env.example
24
.env.example
|
|
@ -9,8 +9,11 @@ LNBITS_ADMIN_USERS=""
|
||||||
LNBITS_ADMIN_EXTENSIONS="ngrok"
|
LNBITS_ADMIN_EXTENSIONS="ngrok"
|
||||||
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
|
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
|
||||||
|
|
||||||
LNBITS_AD_SPACE="" # csv ad image filepaths or urls, extensions can choose to honor
|
# csv ad image filepaths or urls, extensions can choose to honor
|
||||||
LNBITS_HIDE_API=false # Hides wallet api, extensions can choose to honor
|
LNBITS_AD_SPACE=""
|
||||||
|
|
||||||
|
# Hides wallet api, extensions can choose to honor
|
||||||
|
LNBITS_HIDE_API=false
|
||||||
|
|
||||||
# Disable extensions for all users, use "all" to disable all extensions
|
# Disable extensions for all users, use "all" to disable all extensions
|
||||||
LNBITS_DISABLED_EXTENSIONS="amilk"
|
LNBITS_DISABLED_EXTENSIONS="amilk"
|
||||||
|
|
@ -25,18 +28,20 @@ LNBITS_DATA_FOLDER="./data"
|
||||||
|
|
||||||
LNBITS_FORCE_HTTPS=true
|
LNBITS_FORCE_HTTPS=true
|
||||||
LNBITS_SERVICE_FEE="0.0"
|
LNBITS_SERVICE_FEE="0.0"
|
||||||
LNBITS_RESERVE_FEE_MIN=2000 # value in millisats
|
# value in millisats
|
||||||
LNBITS_RESERVE_FEE_PERCENT=1.0 # value in percent
|
LNBITS_RESERVE_FEE_MIN=2000
|
||||||
|
# value in percent
|
||||||
|
LNBITS_RESERVE_FEE_PERCENT=1.0
|
||||||
|
|
||||||
# Change theme
|
# Change theme
|
||||||
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,
|
||||||
|
|
@ -87,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.8]
|
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
|
||||||
|
|
|
||||||
15
Dockerfile
15
Dockerfile
|
|
@ -1,12 +1,23 @@
|
||||||
FROM python:3.9-slim
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
RUN apt-get clean
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y curl
|
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"]
|
||||||
|
|
|
||||||
8
Makefile
8
Makefile
|
|
@ -28,6 +28,10 @@ checkisort:
|
||||||
poetry run isort --check-only .
|
poetry run isort --check-only .
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
BOLTZ_NETWORK="regtest" \
|
||||||
|
BOLTZ_URL="http://127.0.0.1:9001" \
|
||||||
|
BOLTZ_MEMPOOL_SPACE_URL="http://127.0.0.1:8080" \
|
||||||
|
BOLTZ_MEMPOOL_SPACE_URL_WS="ws://127.0.0.1:8080" \
|
||||||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||||
FAKE_WALLET_SECRET="ToTheMoon1" \
|
FAKE_WALLET_SECRET="ToTheMoon1" \
|
||||||
LNBITS_DATA_FOLDER="./tests/data" \
|
LNBITS_DATA_FOLDER="./tests/data" \
|
||||||
|
|
@ -46,6 +50,10 @@ test-real-wallet:
|
||||||
poetry run pytest
|
poetry run pytest
|
||||||
|
|
||||||
test-venv:
|
test-venv:
|
||||||
|
BOLTZ_NETWORK="regtest" \
|
||||||
|
BOLTZ_URL="http://127.0.0.1:9001" \
|
||||||
|
BOLTZ_MEMPOOL_SPACE_URL="http://127.0.0.1:8080" \
|
||||||
|
BOLTZ_MEMPOOL_SPACE_URL_WS="ws://127.0.0.1:8080" \
|
||||||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||||
FAKE_WALLET_SECRET="ToTheMoon1" \
|
FAKE_WALLET_SECRET="ToTheMoon1" \
|
||||||
LNBITS_DATA_FOLDER="./tests/data" \
|
LNBITS_DATA_FOLDER="./tests/data" \
|
||||||
|
|
|
||||||
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)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
@ -12,26 +12,31 @@ By default, LNbits will use SQLite as its database. You can also use PostgreSQL
|
||||||
|
|
||||||
## Option 1 (recommended): poetry
|
## Option 1 (recommended): poetry
|
||||||
|
|
||||||
|
If you have problems installing LNbits using these instructions, please have a look at the [Troubleshooting](#troubleshooting) section.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
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
|
||||||
|
|
||||||
mkdir data
|
mkdir data
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
sudo nano .env # set funding source
|
# set funding source amongst other options
|
||||||
|
nano .env
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Running the server
|
#### Running the server
|
||||||
|
|
@ -39,6 +44,8 @@ sudo 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
|
||||||
|
|
@ -176,13 +183,15 @@ Problems installing? These commands have helped us install LNbits.
|
||||||
```sh
|
```sh
|
||||||
sudo apt install pkg-config libffi-dev libpq-dev
|
sudo apt install pkg-config libffi-dev libpq-dev
|
||||||
|
|
||||||
|
# build essentials for debian/ubuntu
|
||||||
|
sudo apt install python3.9-dev gcc build-essential
|
||||||
|
|
||||||
# if the secp256k1 build fails:
|
# if the secp256k1 build fails:
|
||||||
# if you used venv
|
|
||||||
./venv/bin/pip install setuptools wheel
|
|
||||||
# if you used poetry
|
# if you used poetry
|
||||||
poetry add setuptools wheel
|
poetry add setuptools wheel
|
||||||
# build essentials for debian/ubuntu
|
|
||||||
sudo apt install python3-dev gcc build-essential
|
# if you used venv
|
||||||
|
./venv/bin/pip install setuptools wheel
|
||||||
```
|
```
|
||||||
|
|
||||||
### Optional: PostgreSQL database
|
### Optional: PostgreSQL database
|
||||||
|
|
@ -289,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():
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,11 @@ async def get_wallet_for_key(
|
||||||
return Wallet(**row)
|
return Wallet(**row)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_total_balance(conn: Optional[Connection] = None):
|
||||||
|
row = await (conn or db).fetchone("SELECT SUM(balance) FROM balances")
|
||||||
|
return 0 if row[0] is None else row[0]
|
||||||
|
|
||||||
|
|
||||||
# wallet payments
|
# wallet payments
|
||||||
# ---------------
|
# ---------------
|
||||||
|
|
||||||
|
|
@ -328,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:
|
||||||
|
|
@ -338,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(
|
||||||
"""
|
"""
|
||||||
|
|
@ -452,6 +457,15 @@ async def delete_payment(checking_id: str, conn: Optional[Connection] = None) ->
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_wallet_payment(
|
||||||
|
checking_id: str, wallet_id: str, conn: Optional[Connection] = None
|
||||||
|
) -> None:
|
||||||
|
await (conn or db).execute(
|
||||||
|
"DELETE FROM apipayments WHERE checking_id = ? AND wallet = ?",
|
||||||
|
(checking_id, wallet_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def check_internal(
|
async def check_internal(
|
||||||
payment_hash: str, conn: Optional[Connection] = None
|
payment_hash: str, conn: Optional[Connection] = None
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ class Payment(BaseModel):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Deleting outgoing failed payment {self.checking_id}: {status}"
|
f"Deleting outgoing failed payment {self.checking_id}: {status}"
|
||||||
)
|
)
|
||||||
await self.delete()
|
await self.delete(conn)
|
||||||
elif not status.pending:
|
elif not status.pending:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Marking '{'in' if self.is_in else 'out'}' {self.checking_id} as not pending anymore: {status}"
|
f"Marking '{'in' if self.is_in else 'out'}' {self.checking_id} as not pending anymore: {status}"
|
||||||
|
|
@ -182,10 +182,10 @@ class Payment(BaseModel):
|
||||||
await self.update_status(status, conn=conn)
|
await self.update_status(status, conn=conn)
|
||||||
return status
|
return status
|
||||||
|
|
||||||
async def delete(self) -> None:
|
async def delete(self, conn: Optional[Connection] = None) -> None:
|
||||||
from .crud import delete_payment
|
from .crud import delete_payment
|
||||||
|
|
||||||
await delete_payment(self.checking_id)
|
await delete_payment(self.checking_id, conn=conn)
|
||||||
|
|
||||||
|
|
||||||
class BalanceCheck(BaseModel):
|
class BalanceCheck(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ from . import db
|
||||||
from .crud import (
|
from .crud import (
|
||||||
check_internal,
|
check_internal,
|
||||||
create_payment,
|
create_payment,
|
||||||
delete_payment,
|
delete_wallet_payment,
|
||||||
get_wallet,
|
get_wallet,
|
||||||
get_wallet_payment,
|
get_wallet_payment,
|
||||||
update_payment_details,
|
update_payment_details,
|
||||||
|
|
@ -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}")
|
||||||
|
|
@ -221,10 +221,10 @@ async def pay_invoice(
|
||||||
logger.warning(f"backend sent payment failure")
|
logger.warning(f"backend sent payment failure")
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
logger.debug(f"deleting temporary payment {temp_id}")
|
logger.debug(f"deleting temporary payment {temp_id}")
|
||||||
await delete_payment(temp_id, conn=conn)
|
await delete_wallet_payment(temp_id, wallet_id, conn=conn)
|
||||||
raise PaymentFailure(
|
raise PaymentFailure(
|
||||||
f"payment failed: {payment.error_message}"
|
f"Payment failed: {payment.error_message}"
|
||||||
or "payment failed, but backend didn't give us an error message"
|
or "Payment failed, but backend didn't give us an error message."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -675,7 +675,7 @@ new Vue({
|
||||||
// status is important for export but it is not in paymentsTable
|
// status is important for export but it is not in paymentsTable
|
||||||
// because it is manually added with payment detail link and icons
|
// because it is manually added with payment detail link and icons
|
||||||
// and would cause duplication in the list
|
// and would cause duplication in the list
|
||||||
let columns = this.paymentsTable.columns
|
let columns = structuredClone(this.paymentsTable.columns)
|
||||||
columns.unshift({
|
columns.unshift({
|
||||||
name: 'pending',
|
name: 'pending',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,14 @@ import asyncio
|
||||||
import binascii
|
import binascii
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
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
|
||||||
|
|
@ -15,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
|
||||||
|
|
@ -27,7 +30,7 @@ from lnbits.decorators import (
|
||||||
require_invoice_key,
|
require_invoice_key,
|
||||||
)
|
)
|
||||||
from lnbits.helpers import url_for, urlsafe_short_hash
|
from lnbits.helpers import url_for, urlsafe_short_hash
|
||||||
from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE
|
from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET
|
||||||
from lnbits.utils.exchange_rates import (
|
from lnbits.utils.exchange_rates import (
|
||||||
currencies,
|
currencies,
|
||||||
fiat_amount_as_satoshis,
|
fiat_amount_as_satoshis,
|
||||||
|
|
@ -39,6 +42,7 @@ from ..crud import (
|
||||||
create_payment,
|
create_payment,
|
||||||
get_payments,
|
get_payments,
|
||||||
get_standalone_payment,
|
get_standalone_payment,
|
||||||
|
get_total_balance,
|
||||||
get_wallet,
|
get_wallet,
|
||||||
get_wallet_for_key,
|
get_wallet_for_key,
|
||||||
save_balance_check,
|
save_balance_check,
|
||||||
|
|
@ -364,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:
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
payment: Payment = await payment_queue.get()
|
payment: Payment = await payment_queue.get()
|
||||||
if payment.wallet_id == this_wallet_id:
|
if payment.wallet_id == this_wallet_id:
|
||||||
logger.debug("payment received", payment)
|
logger.debug("sse listener: payment receieved", payment)
|
||||||
await send_queue.put(("payment-received", 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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -403,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",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -657,3 +674,26 @@ async def img(request: Request, data):
|
||||||
"Expires": "0",
|
"Expires": "0",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@core_app.get("/api/v1/audit/")
|
||||||
|
async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
|
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user"
|
||||||
|
)
|
||||||
|
|
||||||
|
total_balance = await get_total_balance()
|
||||||
|
error_message, node_balance = await WALLET.status()
|
||||||
|
|
||||||
|
if not error_message:
|
||||||
|
delta = node_balance - total_balance
|
||||||
|
else:
|
||||||
|
node_balance, delta = None, None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"node_balance_msats": node_balance,
|
||||||
|
"lnbits_balance_msats": total_balance,
|
||||||
|
"delta_msats": delta,
|
||||||
|
"timestamp": int(time.time()),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -153,14 +153,18 @@ async def get_key_type(
|
||||||
LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS
|
LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS
|
||||||
) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS):
|
) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
|
status_code=HTTPStatus.FORBIDDEN,
|
||||||
|
detail="User not authorized for this extension.",
|
||||||
)
|
)
|
||||||
return wallet
|
return wallet
|
||||||
except HTTPException as e:
|
except HTTPException as e:
|
||||||
if e.status_code == HTTPStatus.BAD_REQUEST:
|
if e.status_code == HTTPStatus.BAD_REQUEST:
|
||||||
raise
|
raise
|
||||||
if e.status_code == HTTPStatus.UNAUTHORIZED:
|
elif e.status_code == HTTPStatus.UNAUTHORIZED:
|
||||||
|
# we pass this in case it is not an invoice key, nor an admin key, and then return NOT_FOUND at the end of this block
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ 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.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.helpers import get_current_extension_name, urlsafe_short_hash
|
||||||
from lnbits.tasks import internal_invoice_listener, register_invoice_listener
|
from lnbits.tasks import internal_invoice_listener, 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
|
||||||
|
|
@ -14,7 +14,7 @@ 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()
|
||||||
|
|
|
||||||
|
|
@ -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,7 +209,12 @@ 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 not pr:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.FORBIDDEN, detail="No payment request"
|
||||||
|
)
|
||||||
|
else:
|
||||||
if lnurldevicepayment.id != k1:
|
if lnurldevicepayment.id != k1:
|
||||||
return {"status": "ERROR", "reason": "Bad K1"}
|
return {"status": "ERROR", "reason": "Bad K1"}
|
||||||
if lnurldevicepayment.payhash != "payment_hash":
|
if lnurldevicepayment.payhash != "payment_hash":
|
||||||
|
|
@ -200,6 +230,21 @@ async def lnurl_callback(
|
||||||
extra={"tag": "withdraw"},
|
extra={"tag": "withdraw"},
|
||||||
)
|
)
|
||||||
return {"status": "OK"}
|
return {"status": "OK"}
|
||||||
|
if device.device == "switch":
|
||||||
|
payment_hash, payment_request = await create_invoice(
|
||||||
|
wallet_id=device.wallet,
|
||||||
|
amount=lnurldevicepayment.sats / 1000,
|
||||||
|
memo=device.title + "-" + lnurldevicepayment.id,
|
||||||
|
unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"),
|
||||||
|
extra={"tag": "Switch", "id": paymentid, "time": device.amount},
|
||||||
|
)
|
||||||
|
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,7 +149,20 @@
|
||||||
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>
|
||||||
|
<center>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
v-if="settingsDialog.data.device == 'switch'"
|
||||||
|
dense
|
||||||
|
outline
|
||||||
|
unelevated
|
||||||
|
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
|
dense
|
||||||
outline
|
outline
|
||||||
unelevated
|
unelevated
|
||||||
|
|
@ -145,7 +175,7 @@
|
||||||
{{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,24 +32,34 @@ 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:
|
||||||
|
try:
|
||||||
|
return [
|
||||||
|
{**lnurldevice.dict()}
|
||||||
|
for lnurldevice in await get_lnurldevices(wallet_ids)
|
||||||
]
|
]
|
||||||
except:
|
except:
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -57,7 +67,7 @@ async def api_lnurldevices_retrieve(wallet: WalletTypeInfo = Depends(get_key_typ
|
||||||
|
|
||||||
@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()
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ animals = [
|
||||||
"duck",
|
"duck",
|
||||||
"eagle",
|
"eagle",
|
||||||
"flamingo",
|
"flamingo",
|
||||||
"gorila",
|
"gorilla",
|
||||||
"hamster",
|
"hamster",
|
||||||
"iguana",
|
"iguana",
|
||||||
"jaguar",
|
"jaguar",
|
||||||
"koala",
|
"koala",
|
||||||
"llama",
|
"llama",
|
||||||
"macaroni penguim",
|
"macaroni penguin",
|
||||||
"numbat",
|
"numbat",
|
||||||
"octopus",
|
"octopus",
|
||||||
"platypus",
|
"platypus",
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ 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.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.helpers import get_current_extension_name, urlsafe_short_hash
|
||||||
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
|
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_targets
|
from .crud import get_targets
|
||||||
|
|
@ -14,7 +14,7 @@ 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()
|
||||||
|
|
@ -28,6 +28,10 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
|
|
||||||
# now we make some special internal transfers (from no one to the receiver)
|
# 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)
|
||||||
|
|
||||||
|
if not targets:
|
||||||
|
return
|
||||||
|
|
||||||
transfers = [
|
transfers = [
|
||||||
(target.wallet, int(target.percent * payment.amount / 100))
|
(target.wallet, int(target.percent * payment.amount / 100))
|
||||||
for target in targets
|
for target in targets
|
||||||
|
|
@ -41,9 +45,6 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not targets:
|
|
||||||
return
|
|
||||||
|
|
||||||
# mark the original payment with one extra key, "splitted"
|
# mark the original payment with one extra key, "splitted"
|
||||||
# (this prevents us from doing this process again and it's informative)
|
# (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
|
# and reduce it by the amount we're going to send to the producer
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import json
|
||||||
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.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.helpers import get_current_extension_name, urlsafe_short_hash
|
||||||
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
|
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_tpos
|
from .crud import get_tpos
|
||||||
|
|
@ -12,7 +12,7 @@ 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()
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,10 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
|
||||||
type,
|
type,
|
||||||
address_no,
|
address_no,
|
||||||
balance,
|
balance,
|
||||||
network
|
network,
|
||||||
|
meta
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
wallet_id,
|
wallet_id,
|
||||||
|
|
@ -37,6 +38,7 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
|
||||||
w.address_no,
|
w.address_no,
|
||||||
w.balance,
|
w.balance,
|
||||||
w.network,
|
w.network,
|
||||||
|
w.meta,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,3 +93,10 @@ async def m006_drop_mempool_table(db):
|
||||||
Mempool data is now part of `config`
|
Mempool data is now part of `config`
|
||||||
"""
|
"""
|
||||||
await db.execute("DROP TABLE watchonly.mempool;")
|
await db.execute("DROP TABLE watchonly.mempool;")
|
||||||
|
|
||||||
|
|
||||||
|
async def m007_add_wallet_meta_data(db):
|
||||||
|
"""
|
||||||
|
Add 'meta' for storing various metadata about the wallet
|
||||||
|
"""
|
||||||
|
await db.execute("ALTER TABLE watchonly.wallets ADD COLUMN meta TEXT DEFAULT '{}';")
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class CreateWallet(BaseModel):
|
||||||
masterpub: str = Query("")
|
masterpub: str = Query("")
|
||||||
title: str = Query("")
|
title: str = Query("")
|
||||||
network: str = "Mainnet"
|
network: str = "Mainnet"
|
||||||
|
meta: str = "{}"
|
||||||
|
|
||||||
|
|
||||||
class WalletAccount(BaseModel):
|
class WalletAccount(BaseModel):
|
||||||
|
|
@ -21,6 +22,7 @@ class WalletAccount(BaseModel):
|
||||||
balance: int
|
balance: int
|
||||||
type: Optional[str] = ""
|
type: Optional[str] = ""
|
||||||
network: str = "Mainnet"
|
network: str = "Mainnet"
|
||||||
|
meta: str = "{}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row(cls, row: Row) -> "WalletAccount":
|
def from_row(cls, row: Row) -> "WalletAccount":
|
||||||
|
|
@ -78,6 +80,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,
|
||||||
|
|
@ -104,6 +105,18 @@ async function payment(path) {
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const p2trUtxo = this.utxos.find(
|
||||||
|
u => u.selected && u.accountType === 'p2tr'
|
||||||
|
)
|
||||||
|
if (p2trUtxo) {
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Taproot Signing not supported yet!',
|
||||||
|
caption: 'Please manually deselect the Taproot UTXOs',
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!this.serialSignerRef.isAuthenticated()) {
|
if (!this.serialSignerRef.isAuthenticated()) {
|
||||||
await this.serialSignerRef.hwwShowPasswordDialog()
|
await this.serialSignerRef.hwwShowPasswordDialog()
|
||||||
const authenticated = await this.serialSignerRef.isAuthenticating()
|
const authenticated = await this.serialSignerRef.isAuthenticating()
|
||||||
|
|
@ -267,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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
<div>
|
||||||
|
<div v-if="done">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">Seed Input Done</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 q-pt-sm">Word Count</div>
|
||||||
|
<div class="col-6 q-pr-lg">
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="wordCount"
|
||||||
|
type="number"
|
||||||
|
label="Word Count"
|
||||||
|
:options="wordCountOptions"
|
||||||
|
@input="initWords"
|
||||||
|
></q-select>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 q-pr-lg"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 q-pr-lg"></div>
|
||||||
|
<div class="col-6">Enter word at position: {{actualPosition}}</div>
|
||||||
|
<div class="col-3 q-pr-lg"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 q-pr-lg">
|
||||||
|
<q-btn
|
||||||
|
v-if="currentPosition > 0"
|
||||||
|
@click="previousPosition"
|
||||||
|
unelevated
|
||||||
|
class="btn-full"
|
||||||
|
color="secondary"
|
||||||
|
>Previous</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 q-pr-lg">
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
use-input
|
||||||
|
hide-selected
|
||||||
|
fill-input
|
||||||
|
input-debounce="0"
|
||||||
|
v-model="currentWord"
|
||||||
|
:options="options"
|
||||||
|
@filter="filterFn"
|
||||||
|
@input-value="setModel"
|
||||||
|
></q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-3 q-pr-lg">
|
||||||
|
<q-btn
|
||||||
|
v-if="currentPosition < wordCount - 1"
|
||||||
|
@click="nextPosition"
|
||||||
|
unelevated
|
||||||
|
class="btn-full"
|
||||||
|
color="secondary"
|
||||||
|
>Next</q-btn
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
v-else
|
||||||
|
@click="seedInputDone"
|
||||||
|
unelevated
|
||||||
|
class="btn-full"
|
||||||
|
color="primary"
|
||||||
|
>Done</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<q-linear-progress
|
||||||
|
:value="currentPosition / (wordCount -1)"
|
||||||
|
size="5px"
|
||||||
|
color="primary"
|
||||||
|
class="q-mt-sm"
|
||||||
|
></q-linear-progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
async function seedInput(path) {
|
||||||
|
const template = await loadTemplateAsync(path)
|
||||||
|
Vue.component('seed-input', {
|
||||||
|
name: 'seed-input',
|
||||||
|
template,
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
actualPosition: function () {
|
||||||
|
return this.words[this.currentPosition].position
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
wordCountOptions: ['12', '15', '18', '21', '24'],
|
||||||
|
wordCount: 24,
|
||||||
|
words: [],
|
||||||
|
currentPosition: 0,
|
||||||
|
stringOptions: [],
|
||||||
|
options: [],
|
||||||
|
currentWord: '',
|
||||||
|
done: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
filterFn(val, update, abort) {
|
||||||
|
update(() => {
|
||||||
|
const needle = val.toLocaleLowerCase()
|
||||||
|
this.options = this.stringOptions
|
||||||
|
.filter(v => v.toLocaleLowerCase().indexOf(needle) != -1)
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.startsWith(needle)) {
|
||||||
|
if (b.startsWith(needle)) {
|
||||||
|
return a - b
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
if (b.startsWith(needle)) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return a - b
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
initWords() {
|
||||||
|
const words = []
|
||||||
|
for (let i = 1; i <= this.wordCount; i++) {
|
||||||
|
words.push({
|
||||||
|
position: i,
|
||||||
|
value: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.currentPosition = 0
|
||||||
|
this.words = _.shuffle(words)
|
||||||
|
},
|
||||||
|
setModel(val) {
|
||||||
|
this.currentWord = val
|
||||||
|
this.words[this.currentPosition].value = this.currentWord
|
||||||
|
},
|
||||||
|
nextPosition() {
|
||||||
|
if (this.currentPosition < this.wordCount - 1) {
|
||||||
|
this.currentPosition++
|
||||||
|
}
|
||||||
|
this.currentWord = this.words[this.currentPosition].value
|
||||||
|
},
|
||||||
|
previousPosition() {
|
||||||
|
if (this.currentPosition > 0) {
|
||||||
|
this.currentPosition--
|
||||||
|
}
|
||||||
|
this.currentWord = this.words[this.currentPosition].value
|
||||||
|
},
|
||||||
|
seedInputDone() {
|
||||||
|
const badWordPositions = this.words
|
||||||
|
.filter(w => !w.value || !this.stringOptions.includes(w.value))
|
||||||
|
.map(w => w.position)
|
||||||
|
if (badWordPositions.length) {
|
||||||
|
this.$q.notify({
|
||||||
|
timeout: 10000,
|
||||||
|
type: 'warning',
|
||||||
|
message:
|
||||||
|
'The seed has incorrect words. Please check at these positions: ',
|
||||||
|
caption: 'Position: ' + badWordPositions.join(', ')
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const mnemonic = this.words
|
||||||
|
.sort((a, b) => a.position - b.position)
|
||||||
|
.map(w => w.value)
|
||||||
|
.join(' ')
|
||||||
|
this.$emit('on-seed-input-done', mnemonic)
|
||||||
|
this.done = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created: async function () {
|
||||||
|
this.stringOptions = bip39WordList
|
||||||
|
this.initWords()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
>
|
>
|
||||||
|
|
@ -170,6 +170,31 @@
|
||||||
type="password"
|
type="password"
|
||||||
label="Password"
|
label="Password"
|
||||||
></q-input>
|
></q-input>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<q-toggle
|
||||||
|
label="Passphrase (optional)"
|
||||||
|
color="secodary"
|
||||||
|
v-model="hww.hasPassphrase"
|
||||||
|
></q-toggle>
|
||||||
|
<q-input
|
||||||
|
v-if="hww.hasPassphrase"
|
||||||
|
v-model.trim="hww.passphrase"
|
||||||
|
filled
|
||||||
|
:type="hww.showPassphrase ? 'text' : 'password'"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
label="Passphrase"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
:name="hww.showPassphrase ? 'visibility' : 'visibility_off'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="hww.showPassphrase = !hww.showPassphrase"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
@ -351,6 +376,18 @@
|
||||||
|
|
||||||
<q-dialog v-model="showConsole" position="top">
|
<q-dialog v-model="showConsole" position="top">
|
||||||
<q-card class="q-pa-lg q-pt-xl">
|
<q-card class="q-pa-lg q-pt-xl">
|
||||||
|
<div class="row q-mt-lg q-mb-lg">
|
||||||
|
<div class="col">
|
||||||
|
<q-badge
|
||||||
|
class="text-subtitle2 float-right"
|
||||||
|
color="yellow"
|
||||||
|
text-color="black"
|
||||||
|
>
|
||||||
|
Open the browser Developer Console for more Details!
|
||||||
|
</q-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
|
|
@ -361,16 +398,29 @@
|
||||||
cols="200"
|
cols="200"
|
||||||
label="Console"
|
label="Console"
|
||||||
></q-input>
|
></q-input>
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
<q-dialog v-model="hww.showSeedDialog" position="top">
|
<q-dialog v-model="hww.showSeedDialog" @hide="closeSeedDialog" position="top">
|
||||||
<q-card class="q-pa-lg q-pt-xl">
|
<q-card class="q-pa-lg q-pt-xl">
|
||||||
<span>Check word at position {{hww.seedWordPosition}} on display</span>
|
<span>Check word at position {{hww.seedWordPosition}} on device</span>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-toggle
|
||||||
|
label="Show Seed Word"
|
||||||
|
color="secodary"
|
||||||
|
v-model="hww.showSeedWord"
|
||||||
|
></q-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="hww.showSeedWord" class="row q-mt-lg">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-input readonly v-model.trim="hww.seedWord"></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
|
|
@ -409,8 +459,15 @@
|
||||||
>
|
>
|
||||||
For test purposes only. Do not enter word list with real funds!!!
|
For test purposes only. Do not enter word list with real funds!!!
|
||||||
</q-badge>
|
</q-badge>
|
||||||
<br /><br /><br />
|
<br />
|
||||||
<span>Enter new word list separated by space</span>
|
<q-toggle
|
||||||
|
label="Enter word list separated by space"
|
||||||
|
color="secodary"
|
||||||
|
v-model="hww.quickMnemonicInput"
|
||||||
|
></q-toggle>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div v-if="hww.quickMnemonicInput">
|
||||||
<q-input
|
<q-input
|
||||||
v-model.trim="hww.mnemonic"
|
v-model.trim="hww.mnemonic"
|
||||||
filled
|
filled
|
||||||
|
|
@ -427,30 +484,10 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<seed-input v-else @on-seed-input-done="seedInputDone"></seed-input>
|
||||||
<br />
|
<br />
|
||||||
<q-toggle
|
|
||||||
label="Passphrase (optional)"
|
|
||||||
color="secodary"
|
|
||||||
v-model="hww.hasPassphrase"
|
|
||||||
></q-toggle>
|
|
||||||
<br />
|
|
||||||
<q-input
|
|
||||||
v-if="hww.hasPassphrase"
|
|
||||||
v-model.trim="hww.passphrase"
|
|
||||||
filled
|
|
||||||
:type="hww.showPassphrase ? 'text' : 'password'"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
label="Passphrase"
|
|
||||||
>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon
|
|
||||||
:name="hww.showPassphrase ? 'visibility' : 'visibility_off'"
|
|
||||||
class="cursor-pointer"
|
|
||||||
@click="hww.showPassphrase = !hww.showPassphrase"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
<br />
|
<br />
|
||||||
<span>Enter new password (8 numbers/letters)</span>
|
<span>Enter new password (8 numbers/letters)</span>
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,14 @@ 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,
|
||||||
showPassword: false,
|
showPassword: false,
|
||||||
mnemonic: null,
|
mnemonic: null,
|
||||||
showMnemonic: false,
|
showMnemonic: false,
|
||||||
|
quickMnemonicInput: false,
|
||||||
passphrase: null,
|
passphrase: null,
|
||||||
showPassphrase: false,
|
showPassphrase: false,
|
||||||
hasPassphrase: false,
|
hasPassphrase: false,
|
||||||
|
|
@ -38,6 +39,8 @@ async function serialSigner(path) {
|
||||||
psbtSentResolve: null,
|
psbtSentResolve: null,
|
||||||
xpubResolve: null,
|
xpubResolve: null,
|
||||||
seedWordPosition: 1,
|
seedWordPosition: 1,
|
||||||
|
seedWord: null,
|
||||||
|
showSeedWord: false,
|
||||||
showSeedDialog: false,
|
showSeedDialog: false,
|
||||||
// config: null,
|
// config: null,
|
||||||
|
|
||||||
|
|
@ -48,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')) ||
|
||||||
|
|
@ -106,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(
|
||||||
|
|
@ -172,6 +180,10 @@ async function serialSigner(path) {
|
||||||
isAuthenticated: function () {
|
isAuthenticated: function () {
|
||||||
return this.hww.authenticated
|
return this.hww.authenticated
|
||||||
},
|
},
|
||||||
|
|
||||||
|
seedInputDone: function (mnemonic) {
|
||||||
|
this.hww.mnemonic = mnemonic
|
||||||
|
},
|
||||||
isAuthenticating: function () {
|
isAuthenticating: function () {
|
||||||
if (this.isAuthenticated()) return false
|
if (this.isAuthenticated()) return false
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
|
@ -218,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
|
||||||
}
|
}
|
||||||
|
|
@ -233,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) {
|
||||||
|
|
@ -275,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) {
|
||||||
|
|
@ -300,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({
|
||||||
|
|
@ -374,6 +388,10 @@ async function serialSigner(path) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
closeSeedDialog: function () {
|
||||||
|
this.hww.seedWord = null
|
||||||
|
this.hww.showSeedWord = false
|
||||||
|
},
|
||||||
hwwConfirmNext: async function () {
|
hwwConfirmNext: async function () {
|
||||||
this.hww.confirm.outputIndex += 1
|
this.hww.confirm.outputIndex += 1
|
||||||
if (this.hww.confirm.outputIndex >= this.tx.outputs.length) {
|
if (this.hww.confirm.outputIndex >= this.tx.outputs.length) {
|
||||||
|
|
@ -403,7 +421,10 @@ async function serialSigner(path) {
|
||||||
},
|
},
|
||||||
hwwLogin: async function () {
|
hwwLogin: async function () {
|
||||||
try {
|
try {
|
||||||
await this.sendCommandSecure(COMMAND_PASSWORD, [this.hww.password])
|
await this.sendCommandSecure(COMMAND_PASSWORD, [
|
||||||
|
this.hww.password,
|
||||||
|
this.hww.passphrase
|
||||||
|
])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
|
@ -414,7 +435,9 @@ async function serialSigner(path) {
|
||||||
} finally {
|
} finally {
|
||||||
this.hww.showPasswordDialog = false
|
this.hww.showPasswordDialog = false
|
||||||
this.hww.password = null
|
this.hww.password = null
|
||||||
|
this.hww.passphrase = null
|
||||||
this.hww.showPassword = false
|
this.hww.showPassword = false
|
||||||
|
this.hww.showPassphrase = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleLoginResponse: function (res = '') {
|
handleLoginResponse: function (res = '') {
|
||||||
|
|
@ -449,6 +472,22 @@ async function serialSigner(path) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
hwwShowAddress: async function (path, address) {
|
||||||
|
try {
|
||||||
|
await this.sendCommandSecure(COMMAND_ADDRESS, [
|
||||||
|
this.network,
|
||||||
|
path,
|
||||||
|
address
|
||||||
|
])
|
||||||
|
} catch (error) {
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Failed to logout from Hardware Wallet!',
|
||||||
|
caption: `${error}`,
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
handleLogoutResponse: function (res = '') {
|
handleLogoutResponse: function (res = '') {
|
||||||
const authenticated = !(res.trim() === '1')
|
const authenticated = !(res.trim() === '1')
|
||||||
if (this.hww.authenticated && !authenticated) {
|
if (this.hww.authenticated && !authenticated) {
|
||||||
|
|
@ -550,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
|
||||||
)
|
)
|
||||||
|
|
@ -571,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',
|
||||||
|
|
@ -590,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
|
||||||
|
|
@ -714,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
|
||||||
})
|
})
|
||||||
|
|
@ -796,21 +845,15 @@ async function serialSigner(path) {
|
||||||
await this.sendCommandSecure(COMMAND_SEED, [this.hww.seedWordPosition])
|
await this.sendCommandSecure(COMMAND_SEED, [this.hww.seedWordPosition])
|
||||||
},
|
},
|
||||||
handleShowSeedResponse: function (res = '') {
|
handleShowSeedResponse: function (res = '') {
|
||||||
const args = res.trim().split(' ')
|
const [pos, word] = res.trim().split(' ')
|
||||||
|
this.hww.seedWord = `${pos}. ${word}`
|
||||||
|
this.hww.seedWordPosition = pos
|
||||||
},
|
},
|
||||||
hwwRestore: async function () {
|
hwwRestore: async function () {
|
||||||
try {
|
try {
|
||||||
let mnemonicWithPassphrase = this.hww.mnemonic
|
|
||||||
if (
|
|
||||||
this.hww.hasPassphrase &&
|
|
||||||
this.hww.passphrase &&
|
|
||||||
this.hww.passphrase.length
|
|
||||||
) {
|
|
||||||
mnemonicWithPassphrase += '/' + this.hww.passphrase
|
|
||||||
}
|
|
||||||
await this.sendCommandSecure(COMMAND_RESTORE, [
|
await this.sendCommandSecure(COMMAND_RESTORE, [
|
||||||
this.hww.password,
|
this.hww.password,
|
||||||
mnemonicWithPassphrase
|
this.hww.mnemonic
|
||||||
])
|
])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
|
|
@ -822,7 +865,6 @@ async function serialSigner(path) {
|
||||||
} finally {
|
} finally {
|
||||||
this.hww.showRestoreDialog = false
|
this.hww.showRestoreDialog = false
|
||||||
this.hww.mnemonic = null
|
this.hww.mnemonic = null
|
||||||
this.hww.passphrase = null
|
|
||||||
this.hww.showMnemonic = false
|
this.hww.showMnemonic = false
|
||||||
this.hww.password = null
|
this.hww.password = null
|
||||||
this.hww.confirmedPassword = null
|
this.hww.confirmedPassword = null
|
||||||
|
|
@ -837,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,
|
||||||
|
|
@ -876,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 {
|
||||||
|
|
@ -896,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!'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -924,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
|
||||||
|
|
@ -935,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)
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,13 @@
|
||||||
<q-badge v-if="props.row.isChange" color="orange" class="q-mr-md">
|
<q-badge v-if="props.row.isChange" color="orange" class="q-mr-md">
|
||||||
change
|
change
|
||||||
</q-badge>
|
</q-badge>
|
||||||
|
<q-badge
|
||||||
|
v-if="props.row.accountType === 'p2tr'"
|
||||||
|
color="yellow"
|
||||||
|
text-color="black"
|
||||||
|
>
|
||||||
|
taproot
|
||||||
|
</q-badge>
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@
|
||||||
>New Receive Address</q-btn
|
>New Receive Address</q-btn
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
{{getAccountDescription(props.row.type)}}
|
{{getAccountDescription(props.row.type)}}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -124,15 +125,56 @@
|
||||||
|
|
||||||
<div class="row items-center no-wrap q-mb-md">
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
<div class="col-2 q-pr-lg">Master Pubkey:</div>
|
<div class="col-2 q-pr-lg">Master Pubkey:</div>
|
||||||
<div class="col-8">
|
<div class="col-7 q-pr-lg">
|
||||||
<q-input
|
<q-input v-model="props.row.masterpub" filled readonly />
|
||||||
v-model="props.row.masterpub"
|
</div>
|
||||||
filled
|
<div class="col-1">
|
||||||
readonly
|
<q-btn
|
||||||
type="textarea"
|
unelevated
|
||||||
/>
|
dense
|
||||||
|
size="md"
|
||||||
|
icon="qr_code"
|
||||||
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||||
|
@click="openQrCodeDialog(props.row.masterpub)"
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 q-pr-lg">
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="grey"
|
||||||
|
icon="content_copy"
|
||||||
|
@click="copyText(props.row.masterpub)"
|
||||||
|
class="q-ml-sm"
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="props.row.meta?.xpub"
|
||||||
|
class="row items-center no-wrap q-mb-md"
|
||||||
|
>
|
||||||
|
<div class="col-2 q-pr-lg">XPub:</div>
|
||||||
|
<div class="col-7 q-pr-lg">
|
||||||
|
<q-input v-model="props.row.meta.xpub" filled readonly />
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
dense
|
||||||
|
size="md"
|
||||||
|
icon="qr_code"
|
||||||
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||||
|
@click="openQrCodeDialog(props.row.meta.xpub)"
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 q-pr-lg">
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="grey"
|
||||||
|
icon="content_copy"
|
||||||
|
@click="copyText(props.row.meta.xpub)"
|
||||||
|
class="q-ml-sm"
|
||||||
|
></q-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2 q-pr-lg"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row items-center no-wrap q-mb-md">
|
<div class="row items-center no-wrap q-mb-md">
|
||||||
<div class="col-2 q-pr-lg">Last Address Index:</div>
|
<div class="col-2 q-pr-lg">Last Address Index:</div>
|
||||||
|
|
@ -229,4 +271,15 @@
|
||||||
</q-form>
|
</q-form>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
<q-dialog v-model="showQrCodeDialog" position="top">
|
||||||
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
|
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
||||||
|
<qrcode
|
||||||
|
:value="qrCodeValue"
|
||||||
|
:options="{width: 800}"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode>
|
||||||
|
</q-responsive>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ async function walletList(path) {
|
||||||
return {
|
return {
|
||||||
walletAccounts: [],
|
walletAccounts: [],
|
||||||
address: {},
|
address: {},
|
||||||
|
showQrCodeDialog: false,
|
||||||
|
qrCodeValue: null,
|
||||||
formDialog: {
|
formDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
|
|
||||||
|
|
@ -118,9 +120,11 @@ async function walletList(path) {
|
||||||
},
|
},
|
||||||
createWalletAccount: async function (data) {
|
createWalletAccount: async function (data) {
|
||||||
try {
|
try {
|
||||||
|
const meta = {accountPath: this.accountPath}
|
||||||
if (this.formDialog.useSerialPort) {
|
if (this.formDialog.useSerialPort) {
|
||||||
const {xpub, fingerprint} = await this.fetchXpubFromHww()
|
const {xpub, fingerprint} = await this.fetchXpubFromHww()
|
||||||
if (!xpub) return
|
if (!xpub) return
|
||||||
|
meta.xpub = xpub
|
||||||
const path = this.accountPath.substring(2)
|
const path = this.accountPath.substring(2)
|
||||||
const outputType = this.formDialog.addressType.id
|
const outputType = this.formDialog.addressType.id
|
||||||
if (outputType === 'sh') {
|
if (outputType === 'sh') {
|
||||||
|
|
@ -129,6 +133,7 @@ async function walletList(path) {
|
||||||
data.masterpub = `${outputType}([${fingerprint}/${path}]${xpub}/{0,1}/*)`
|
data.masterpub = `${outputType}([${fingerprint}/${path}]${xpub}/{0,1}/*)`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
data.meta = JSON.stringify(meta)
|
||||||
const response = await LNbits.api.request(
|
const response = await LNbits.api.request(
|
||||||
'POST',
|
'POST',
|
||||||
'/watchonly/api/v1/wallet',
|
'/watchonly/api/v1/wallet',
|
||||||
|
|
@ -233,7 +238,7 @@ async function walletList(path) {
|
||||||
const addressData = mapAddressesData(data)
|
const addressData = mapAddressesData(data)
|
||||||
|
|
||||||
addressData.note = `Shared on ${currentDateTime()}`
|
addressData.note = `Shared on ${currentDateTime()}`
|
||||||
const lastAcctiveAddress =
|
const lastActiveAddress =
|
||||||
this.addresses
|
this.addresses
|
||||||
.filter(
|
.filter(
|
||||||
a =>
|
a =>
|
||||||
|
|
@ -243,11 +248,11 @@ async function walletList(path) {
|
||||||
addressData.gapLimitExceeded =
|
addressData.gapLimitExceeded =
|
||||||
!addressData.isChange &&
|
!addressData.isChange &&
|
||||||
addressData.addressIndex >
|
addressData.addressIndex >
|
||||||
lastAcctiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT
|
lastActiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT
|
||||||
|
|
||||||
const wallet = this.walletAccounts.find(w => w.id === walletId) || {}
|
const wallet = this.walletAccounts.find(w => w.id === walletId) || {}
|
||||||
wallet.address_no = addressData.addressIndex
|
wallet.address_no = addressData.addressIndex
|
||||||
this.$emit('new-receive-address', addressData)
|
this.$emit('new-receive-address', {addressData, wallet})
|
||||||
},
|
},
|
||||||
showAddAccountDialog: function () {
|
showAddAccountDialog: function () {
|
||||||
this.formDialog.show = true
|
this.formDialog.show = true
|
||||||
|
|
@ -283,6 +288,20 @@ async function walletList(path) {
|
||||||
const addressType =
|
const addressType =
|
||||||
this.addressTypeOptions.find(t => t.id === value.id) || {}
|
this.addressTypeOptions.find(t => t.id === value.id) || {}
|
||||||
this.accountPath = addressType[`path${this.network}`]
|
this.accountPath = addressType[`path${this.network}`]
|
||||||
|
},
|
||||||
|
// todo: bad. base.js not present in custom components
|
||||||
|
copyText: function (text, message, position) {
|
||||||
|
var notify = this.$q.notify
|
||||||
|
Quasar.utils.copyToClipboard(text).then(function () {
|
||||||
|
notify({
|
||||||
|
message: message || 'Copied to clipboard!',
|
||||||
|
position: position || 'bottom'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
openQrCodeDialog: function (qrCodeValue) {
|
||||||
|
this.qrCodeValue = qrCodeValue
|
||||||
|
this.showQrCodeDialog = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: async function () {
|
created: async function () {
|
||||||
|
|
|
||||||
2050
lnbits/extensions/watchonly/static/js/bip39-word-list.js
Normal file
2050
lnbits/extensions/watchonly/static/js/bip39-word-list.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -7,6 +7,7 @@ const watchOnly = async () => {
|
||||||
await history('static/components/history/history.html')
|
await history('static/components/history/history.html')
|
||||||
await utxoList('static/components/utxo-list/utxo-list.html')
|
await utxoList('static/components/utxo-list/utxo-list.html')
|
||||||
await feeRate('static/components/fee-rate/fee-rate.html')
|
await feeRate('static/components/fee-rate/fee-rate.html')
|
||||||
|
await seedInput('static/components/seed-input/seed-input.html')
|
||||||
await sendTo('static/components/send-to/send-to.html')
|
await sendTo('static/components/send-to/send-to.html')
|
||||||
await payment('static/components/payment/payment.html')
|
await payment('static/components/payment/payment.html')
|
||||||
await serialSigner('static/components/serial-signer/serial-signer.html')
|
await serialSigner('static/components/serial-signer/serial-signer.html')
|
||||||
|
|
@ -172,10 +173,6 @@ const watchOnly = async () => {
|
||||||
this.$refs.paymentRef.updateSignedPsbt(psbtBase64)
|
this.$refs.paymentRef.updateSignedPsbt(psbtBase64)
|
||||||
},
|
},
|
||||||
|
|
||||||
//################### SERIAL PORT ###################
|
|
||||||
|
|
||||||
//################### HARDWARE WALLET ###################
|
|
||||||
|
|
||||||
//################### UTXOs ###################
|
//################### UTXOs ###################
|
||||||
scanAllAddresses: async function () {
|
scanAllAddresses: async function () {
|
||||||
await this.refreshAddresses()
|
await this.refreshAddresses()
|
||||||
|
|
@ -227,7 +224,7 @@ const watchOnly = async () => {
|
||||||
newAddr => !this.addresses.find(a => a.address === newAddr.address)
|
newAddr => !this.addresses.find(a => a.address === newAddr.address)
|
||||||
)
|
)
|
||||||
|
|
||||||
const lastAcctiveAddress =
|
const lastActiveAddress =
|
||||||
uniqueAddresses.filter(a => !a.isChange && a.hasActivity).pop() ||
|
uniqueAddresses.filter(a => !a.isChange && a.hasActivity).pop() ||
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|
@ -237,7 +234,7 @@ const watchOnly = async () => {
|
||||||
a.gapLimitExceeded =
|
a.gapLimitExceeded =
|
||||||
!a.isChange &&
|
!a.isChange &&
|
||||||
a.addressIndex >
|
a.addressIndex >
|
||||||
lastAcctiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT
|
lastActiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT
|
||||||
})
|
})
|
||||||
this.addresses.push(...uniqueAddresses)
|
this.addresses.push(...uniqueAddresses)
|
||||||
}
|
}
|
||||||
|
|
@ -380,6 +377,26 @@ const watchOnly = async () => {
|
||||||
showAddressDetails: function (addressData) {
|
showAddressDetails: function (addressData) {
|
||||||
this.openQrCodeDialog(addressData)
|
this.openQrCodeDialog(addressData)
|
||||||
},
|
},
|
||||||
|
showAddressDetailsWithConfirmation: function ({addressData, wallet}) {
|
||||||
|
this.showAddressDetails(addressData)
|
||||||
|
if (this.$refs.serialSigner.isConnected()) {
|
||||||
|
if (this.$refs.serialSigner.isAuthenticated()) {
|
||||||
|
if (wallet.meta?.accountPath) {
|
||||||
|
const branchIndex = addressData.isChange ? 1 : 0
|
||||||
|
const path =
|
||||||
|
wallet.meta.accountPath +
|
||||||
|
`/${branchIndex}/${addressData.addressIndex}`
|
||||||
|
this.$refs.serialSigner.hwwShowAddress(path, addressData.address)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Please login in order to confirm address on device',
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
initUtxos: function (addresses) {
|
initUtxos: function (addresses) {
|
||||||
if (!this.fetchedUtxos && addresses.length) {
|
if (!this.fetchedUtxos && addresses.length) {
|
||||||
this.fetchedUtxos = true
|
this.fetchedUtxos = true
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ const mapWalletAccount = function (o) {
|
||||||
'YYYY-MM-DD HH:mm'
|
'YYYY-MM-DD HH:mm'
|
||||||
)
|
)
|
||||||
: '',
|
: '',
|
||||||
|
meta: o.meta ? JSON.parse(o.meta) : null,
|
||||||
label: o.title,
|
label: o.title,
|
||||||
expanded: false
|
expanded: false
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const PSBT_BASE64_PREFIX = 'cHNidP8'
|
||||||
const COMMAND_PING = '/ping'
|
const COMMAND_PING = '/ping'
|
||||||
const COMMAND_PASSWORD = '/password'
|
const COMMAND_PASSWORD = '/password'
|
||||||
const COMMAND_PASSWORD_CLEAR = '/password-clear'
|
const COMMAND_PASSWORD_CLEAR = '/password-clear'
|
||||||
|
const COMMAND_ADDRESS = '/address'
|
||||||
const COMMAND_SEND_PSBT = '/psbt'
|
const COMMAND_SEND_PSBT = '/psbt'
|
||||||
const COMMAND_SIGN_PSBT = '/sign'
|
const COMMAND_SIGN_PSBT = '/sign'
|
||||||
const COMMAND_HELP = '/help'
|
const COMMAND_HELP = '/help'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
:addresses="addresses"
|
:addresses="addresses"
|
||||||
:serial-signer-ref="$refs.serialSigner"
|
:serial-signer-ref="$refs.serialSigner"
|
||||||
@accounts-update="updateAccounts"
|
@accounts-update="updateAccounts"
|
||||||
@new-receive-address="showAddressDetails"
|
@new-receive-address="showAddressDetailsWithConfirmation"
|
||||||
>
|
>
|
||||||
</wallet-list>
|
</wallet-list>
|
||||||
|
|
||||||
|
|
@ -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,6 +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.3)</small>
|
||||||
</h6>
|
</h6>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
|
|
@ -238,6 +240,8 @@
|
||||||
<script src="{{ url_for('watchonly_static', path='js/tables.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/tables.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='js/map.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/map.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='js/utils.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/utils.js') }}"></script>
|
||||||
|
<script src="{{ url_for('watchonly_static', path='js/bip39-word-list.js') }}"></script>
|
||||||
|
|
||||||
<script src="{{ url_for('watchonly_static', path='components/my-checkbox/my-checkbox.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/my-checkbox/my-checkbox.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/wallet-config/wallet-config.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/wallet-config/wallet-config.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/wallet-list/wallet-list.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/wallet-list/wallet-list.js') }}"></script>
|
||||||
|
|
@ -245,10 +249,12 @@
|
||||||
<script src="{{ url_for('watchonly_static', path='components/history/history.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/history/history.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/utxo-list/utxo-list.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/utxo-list/utxo-list.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/fee-rate/fee-rate.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/fee-rate/fee-rate.js') }}"></script>
|
||||||
|
<script src="{{ url_for('watchonly_static', path='components/seed-input/seed-input.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/send-to/send-to.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/send-to/send-to.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/payment/payment.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/payment/payment.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/serial-signer/serial-signer.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/serial-signer/serial-signer.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='components/serial-port-config/serial-port-config.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='components/serial-port-config/serial-port-config.js') }}"></script>
|
||||||
|
|
||||||
<script src="{{ url_for('watchonly_static', path='js/crypto/noble-secp256k1.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/crypto/noble-secp256k1.js') }}"></script>
|
||||||
<script src="{{ url_for('watchonly_static', path='js/crypto/aes.js') }}"></script>
|
<script src="{{ url_for('watchonly_static', path='js/crypto/aes.js') }}"></script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -93,6 +94,7 @@ async def api_wallet_create_or_update(
|
||||||
address_no=-1, # so fresh address on empty wallet can get address with index 0
|
address_no=-1, # so fresh address on empty wallet can get address with index 0
|
||||||
balance=0,
|
balance=0,
|
||||||
network=network["name"],
|
network=network["name"],
|
||||||
|
meta=data.meta,
|
||||||
)
|
)
|
||||||
|
|
||||||
wallets = await get_watch_wallets(w.wallet.user, network["name"])
|
wallets = await get_watch_wallets(w.wallet.user, network["name"])
|
||||||
|
|
@ -137,7 +139,7 @@ async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(require_admin
|
||||||
await delete_watch_wallet(wallet_id)
|
await delete_watch_wallet(wallet_id)
|
||||||
await delete_addresses_for_wallet(wallet_id)
|
await delete_addresses_for_wallet(wallet_id)
|
||||||
|
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
return "", HTTPStatus.NO_CONTENT
|
||||||
|
|
||||||
|
|
||||||
#############################ADDRESSES##########################
|
#############################ADDRESSES##########################
|
||||||
|
|
@ -268,7 +270,6 @@ async def api_psbt_create(
|
||||||
for i, inp in enumerate(inputs_extra):
|
for i, inp in enumerate(inputs_extra):
|
||||||
psbt.inputs[i].bip32_derivations = inp["bip32_derivations"]
|
psbt.inputs[i].bip32_derivations = inp["bip32_derivations"]
|
||||||
psbt.inputs[i].non_witness_utxo = inp.get("non_witness_utxo", None)
|
psbt.inputs[i].non_witness_utxo = inp.get("non_witness_utxo", None)
|
||||||
print("### ", inp.get("non_witness_utxo", None))
|
|
||||||
|
|
||||||
outputs_extra = []
|
outputs_extra = []
|
||||||
bip32_derivations = {}
|
bip32_derivations = {}
|
||||||
|
|
@ -295,6 +296,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 +318,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:
|
||||||
|
|
@ -343,11 +345,8 @@ async def api_tx_broadcast(
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
r = await client.post(endpoint + "/api/tx", data=data.tx_hex)
|
r = await client.post(endpoint + "/api/tx", data=data.tx_hex)
|
||||||
tx_id = r.text
|
tx_id = r.text
|
||||||
print("### broadcast tx_id: ", tx_id)
|
|
||||||
return tx_id
|
return tx_id
|
||||||
# return "0f0f0f0f0f0f0f0f0f0f0f00f0f0f0f0f0f0f0f0f0f00f0f0f0f0f0f0.mock.transaction.id"
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("### broadcast error: ", str(e))
|
|
||||||
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
|
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(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
|
||||||
|
|
|
||||||
|
|
@ -24,18 +24,21 @@ LNBITS_DATA_FOLDER = env.str(
|
||||||
)
|
)
|
||||||
LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None)
|
LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None)
|
||||||
|
|
||||||
LNBITS_ALLOWED_USERS: List[str] = env.list(
|
LNBITS_ALLOWED_USERS: List[str] = [
|
||||||
"LNBITS_ALLOWED_USERS", default=[], subcast=str
|
x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str)
|
||||||
)
|
]
|
||||||
LNBITS_ADMIN_USERS: List[str] = env.list("LNBITS_ADMIN_USERS", default=[], subcast=str)
|
LNBITS_ADMIN_USERS: List[str] = [
|
||||||
LNBITS_ADMIN_EXTENSIONS: List[str] = env.list(
|
x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str)
|
||||||
"LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str
|
]
|
||||||
)
|
LNBITS_ADMIN_EXTENSIONS: List[str] = [
|
||||||
LNBITS_DISABLED_EXTENSIONS: List[str] = env.list(
|
x.strip(" ") for x in env.list("LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str)
|
||||||
"LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str
|
]
|
||||||
)
|
LNBITS_DISABLED_EXTENSIONS: List[str] = [
|
||||||
|
x.strip(" ")
|
||||||
|
for x in env.list("LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str)
|
||||||
|
]
|
||||||
|
|
||||||
LNBITS_AD_SPACE = env.list("LNBITS_AD_SPACE", default=[])
|
LNBITS_AD_SPACE = [x.strip(" ") for x in env.list("LNBITS_AD_SPACE", default=[])]
|
||||||
LNBITS_HIDE_API = env.bool("LNBITS_HIDE_API", default=False)
|
LNBITS_HIDE_API = env.bool("LNBITS_HIDE_API", default=False)
|
||||||
LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits")
|
LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits")
|
||||||
LNBITS_DENOMINATION = env.str("LNBITS_DENOMINATION", default="sats")
|
LNBITS_DENOMINATION = env.str("LNBITS_DENOMINATION", default="sats")
|
||||||
|
|
@ -43,11 +46,14 @@ LNBITS_SITE_TAGLINE = env.str(
|
||||||
"LNBITS_SITE_TAGLINE", default="free and open-source lightning wallet"
|
"LNBITS_SITE_TAGLINE", default="free and open-source lightning wallet"
|
||||||
)
|
)
|
||||||
LNBITS_SITE_DESCRIPTION = env.str("LNBITS_SITE_DESCRIPTION", default="")
|
LNBITS_SITE_DESCRIPTION = env.str("LNBITS_SITE_DESCRIPTION", default="")
|
||||||
LNBITS_THEME_OPTIONS: List[str] = env.list(
|
LNBITS_THEME_OPTIONS: List[str] = [
|
||||||
|
x.strip(" ")
|
||||||
|
for x in env.list(
|
||||||
"LNBITS_THEME_OPTIONS",
|
"LNBITS_THEME_OPTIONS",
|
||||||
default="classic, flamingo, mint, salvador, monochrome, autumn",
|
default="classic, flamingo, mint, salvador, monochrome, autumn",
|
||||||
subcast=str,
|
subcast=str,
|
||||||
)
|
)
|
||||||
|
]
|
||||||
LNBITS_CUSTOM_LOGO = env.str("LNBITS_CUSTOM_LOGO", default="")
|
LNBITS_CUSTOM_LOGO = env.str("LNBITS_CUSTOM_LOGO", default="")
|
||||||
|
|
||||||
WALLET = wallet_class()
|
WALLET = wallet_class()
|
||||||
|
|
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no"
|
||||||
/>
|
/>
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -198,16 +198,29 @@ class LndWallet(Wallet):
|
||||||
3: False, # FAILED
|
3: False, # FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
failure_reasons = {
|
||||||
|
0: "No error given.",
|
||||||
|
1: "Payment timed out.",
|
||||||
|
2: "No route to destination.",
|
||||||
|
3: "Error.",
|
||||||
|
4: "Incorrect payment details.",
|
||||||
|
5: "Insufficient balance.",
|
||||||
|
}
|
||||||
|
|
||||||
fee_msat = None
|
fee_msat = None
|
||||||
preimage = None
|
preimage = None
|
||||||
checking_id = resp.payment_hash
|
error_message = None
|
||||||
|
checking_id = None
|
||||||
|
|
||||||
if resp.status: # SUCCEEDED
|
if statuses[resp.status] == True: # SUCCEEDED
|
||||||
fee_msat = -resp.htlcs[-1].route.total_fees_msat
|
fee_msat = -resp.htlcs[-1].route.total_fees_msat
|
||||||
preimage = bytes_to_hex(resp.payment_preimage)
|
preimage = resp.payment_preimage
|
||||||
|
checking_id = resp.payment_hash
|
||||||
|
elif statuses[resp.status] == False:
|
||||||
|
error_message = failure_reasons[resp.failure_reason]
|
||||||
|
|
||||||
return PaymentResponse(
|
return PaymentResponse(
|
||||||
statuses[resp.status], checking_id, fee_msat, preimage, None
|
statuses[resp.status], checking_id, fee_msat, preimage, error_message
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||||
|
|
@ -245,23 +258,29 @@ class LndWallet(Wallet):
|
||||||
router.TrackPaymentRequest(payment_hash=r_hash)
|
router.TrackPaymentRequest(payment_hash=r_hash)
|
||||||
)
|
)
|
||||||
|
|
||||||
# HTLCAttempt.HTLCStatus:
|
# # HTLCAttempt.HTLCStatus:
|
||||||
# https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto#L3641
|
# # https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto#L3641
|
||||||
|
# htlc_statuses = {
|
||||||
|
# 0: None, # IN_FLIGHT
|
||||||
|
# 1: True, # "SUCCEEDED"
|
||||||
|
# 2: False, # "FAILED"
|
||||||
|
# }
|
||||||
statuses = {
|
statuses = {
|
||||||
0: None, # IN_FLIGHT
|
0: None, # NON_EXISTENT
|
||||||
1: True, # "SUCCEEDED"
|
1: None, # IN_FLIGHT
|
||||||
2: False, # "FAILED"
|
2: True, # SUCCEEDED
|
||||||
|
3: False, # FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async for payment in resp:
|
async for payment in resp:
|
||||||
if statuses[payment.htlcs[-1].status]:
|
if len(payment.htlcs) and statuses[payment.status]:
|
||||||
return PaymentStatus(
|
return PaymentStatus(
|
||||||
True,
|
True,
|
||||||
-payment.htlcs[-1].route.total_fees_msat,
|
-payment.htlcs[-1].route.total_fees_msat,
|
||||||
bytes_to_hex(payment.htlcs[-1].preimage),
|
bytes_to_hex(payment.htlcs[-1].preimage),
|
||||||
)
|
)
|
||||||
return PaymentStatus(statuses[payment.htlcs[-1].status])
|
return PaymentStatus(statuses[payment.status])
|
||||||
except: # most likely the payment wasn't found
|
except: # most likely the payment wasn't found
|
||||||
return PaymentStatus(None)
|
return PaymentStatus(None)
|
||||||
|
|
||||||
|
|
|
||||||
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)
|
||||||
|
|
|
||||||
611
poetry.lock
generated
611
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.2"
|
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"
|
||||||
|
|
@ -194,6 +250,14 @@ category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum34"
|
||||||
|
version = "1.1.10"
|
||||||
|
description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "environs"
|
name = "environs"
|
||||||
version = "9.3.3"
|
version = "9.3.3"
|
||||||
|
|
@ -207,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"
|
||||||
|
|
@ -225,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"
|
||||||
|
|
@ -282,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)"]
|
||||||
|
|
||||||
|
|
@ -308,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"
|
||||||
|
|
@ -329,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"
|
||||||
|
|
@ -374,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 = ["isort (>=5.1.1)", "black (>=19.10b0)", "sphinx-rtd-theme (>=0.4.3)", "sphinx-autobuild (>=0.7.1)", "Sphinx (>=2.2.1)", "pytest-cov (>=2.7.1)", "pytest (>=4.6.2)", "tox-travis (>=0.12)", "tox (>=3.9.0)", "flake8 (>=3.7.7)", "colorama (>=0.3.4)", "codecov (>=2.0.15)"]
|
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"
|
||||||
|
|
@ -396,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]]
|
||||||
|
|
@ -410,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"]
|
||||||
|
|
||||||
|
|
@ -463,13 +541,24 @@ python-versions = ">=3.6"
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathlib2"
|
||||||
|
version = "2.3.7.post1"
|
||||||
|
description = "Object-oriented filesystem paths"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = "*"
|
||||||
|
|
||||||
[[package]]
|
[[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"
|
||||||
|
|
@ -480,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"
|
||||||
|
|
@ -495,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"
|
||||||
|
|
@ -545,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"
|
||||||
|
|
@ -554,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"
|
||||||
|
|
@ -565,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"
|
||||||
|
|
@ -576,26 +708,35 @@ python-versions = "*"
|
||||||
PNG = ["pypng (>=0.0.13)"]
|
PNG = ["pypng (>=0.0.13)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyscss"
|
name = "pyScss"
|
||||||
version = "1.3.7"
|
version = "1.4.0"
|
||||||
description = "pyScss, a Scss compiler for Python"
|
description = "pyScss, a Scss compiler for Python"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
enum34 = "*"
|
||||||
|
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\""}
|
||||||
|
|
@ -621,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"
|
||||||
|
|
@ -636,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"
|
||||||
|
|
@ -650,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"
|
||||||
|
|
@ -658,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"
|
||||||
|
|
@ -669,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"
|
||||||
|
|
@ -696,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"
|
||||||
|
|
@ -721,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"
|
||||||
|
|
@ -733,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"
|
||||||
|
|
@ -799,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
|
||||||
|
|
@ -827,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"
|
||||||
|
|
@ -838,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"
|
||||||
|
|
@ -891,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 = "cadb8f2e46f0c083e91956f4f0f70b53b6c106f1c0b47972b57132dfee357367"
|
content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
aiofiles = [
|
aiofiles = [
|
||||||
|
|
@ -912,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"},
|
||||||
|
|
@ -929,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 = [
|
||||||
|
|
@ -1020,52 +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.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e"},
|
{file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
|
||||||
{file = "coverage-6.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c"},
|
{file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
|
||||||
{file = "coverage-6.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8"},
|
{file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
|
||||||
{file = "coverage-6.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39"},
|
{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.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0"},
|
{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.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee"},
|
{file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
|
||||||
{file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d"},
|
{file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
|
||||||
{file = "coverage-6.4.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc"},
|
{file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
|
||||||
{file = "coverage-6.4.2-cp310-cp310-win32.whl", hash = "sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386"},
|
{file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
|
||||||
{file = "coverage-6.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0"},
|
{file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
|
||||||
{file = "coverage-6.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46"},
|
{file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
|
||||||
{file = "coverage-6.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07"},
|
{file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
|
||||||
{file = "coverage-6.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039"},
|
{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.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996"},
|
{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.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f"},
|
{file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
|
||||||
{file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e"},
|
{file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
|
||||||
{file = "coverage-6.4.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083"},
|
{file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
|
||||||
{file = "coverage-6.4.2-cp37-cp37m-win32.whl", hash = "sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7"},
|
{file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
|
||||||
{file = "coverage-6.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120"},
|
{file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
|
||||||
{file = "coverage-6.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452"},
|
{file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
|
||||||
{file = "coverage-6.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32"},
|
{file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
|
||||||
{file = "coverage-6.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae"},
|
{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.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8"},
|
{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.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1"},
|
{file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
|
||||||
{file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63"},
|
{file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
|
||||||
{file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933"},
|
{file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
|
||||||
{file = "coverage-6.4.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de"},
|
{file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
|
||||||
{file = "coverage-6.4.2-cp38-cp38-win32.whl", hash = "sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783"},
|
{file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
|
||||||
{file = "coverage-6.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6"},
|
{file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
|
||||||
{file = "coverage-6.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f"},
|
{file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
|
||||||
{file = "coverage-6.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f"},
|
{file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
|
||||||
{file = "coverage-6.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe"},
|
{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.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29"},
|
{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.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55"},
|
{file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
|
||||||
{file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b"},
|
{file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
|
||||||
{file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978"},
|
{file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
|
||||||
{file = "coverage-6.4.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c"},
|
{file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
|
||||||
{file = "coverage-6.4.2-cp39-cp39-win32.whl", hash = "sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd"},
|
{file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
|
||||||
{file = "coverage-6.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf"},
|
{file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
|
||||||
{file = "coverage-6.4.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97"},
|
{file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
|
||||||
{file = "coverage-6.4.2.tar.gz", hash = "sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe"},
|
{file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
|
||||||
|
{file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
|
||||||
|
{file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
|
||||||
|
{file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
|
||||||
|
{file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
|
||||||
|
{file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
|
||||||
|
{file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
|
||||||
|
{file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
|
||||||
|
{file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
|
||||||
|
{file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
|
||||||
|
]
|
||||||
|
cryptography = [
|
||||||
|
{file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"},
|
||||||
|
{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"},
|
||||||
|
|
@ -1074,6 +1304,11 @@ ecdsa = [
|
||||||
embit = [
|
embit = [
|
||||||
{file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"},
|
{file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"},
|
||||||
]
|
]
|
||||||
|
enum34 = [
|
||||||
|
{file = "enum34-1.1.10-py2-none-any.whl", hash = "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53"},
|
||||||
|
{file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"},
|
||||||
|
{file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"},
|
||||||
|
]
|
||||||
environs = [
|
environs = [
|
||||||
{file = "environs-9.3.3-py2.py3-none-any.whl", hash = "sha256:ee5466156b50fe03aa9fec6e720feea577b5bf515d7f21b2c46608272557ba26"},
|
{file = "environs-9.3.3-py2.py3-none-any.whl", hash = "sha256:ee5466156b50fe03aa9fec6e720feea577b5bf515d7f21b2c46608272557ba26"},
|
||||||
{file = "environs-9.3.3.tar.gz", hash = "sha256:72b867ff7b553076cdd90f3ee01ecc1cf854987639c9c459f0ed0d3d44ae490c"},
|
{file = "environs-9.3.3.tar.gz", hash = "sha256:72b867ff7b553076cdd90f3ee01ecc1cf854987639c9c459f0ed0d3d44ae490c"},
|
||||||
|
|
@ -1082,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"},
|
||||||
|
|
@ -1146,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"},
|
||||||
]
|
]
|
||||||
|
|
@ -1158,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"},
|
||||||
|
|
@ -1274,9 +1556,13 @@ packaging = [
|
||||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||||
]
|
]
|
||||||
|
pathlib2 = [
|
||||||
|
{file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"},
|
||||||
|
{file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"},
|
||||||
|
]
|
||||||
pathspec = [
|
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"},
|
||||||
|
|
@ -1286,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"},
|
||||||
|
|
@ -1385,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"},
|
||||||
|
|
@ -1392,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.3.7.tar.gz", hash = "sha256:f1df571569021a23941a538eb154405dde80bed35dc1ea7c5f3e18e0144746bf"},
|
{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"},
|
||||||
|
|
@ -1415,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"},
|
||||||
|
|
@ -1446,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"},
|
||||||
]
|
]
|
||||||
|
|
@ -1479,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"},
|
||||||
|
|
@ -1491,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"},
|
||||||
|
|
@ -1573,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"},
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,12 @@ generate-setup-file = false
|
||||||
script = "build.py"
|
script = "build.py"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9 | ^3.8 | ^3.7"
|
python = "^3.10 | ^3.9 | ^3.8 | ^3.7"
|
||||||
aiofiles = "0.8.0"
|
aiofiles = "0.8.0"
|
||||||
asgiref = "3.4.1"
|
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"
|
||||||
|
|
@ -39,7 +38,7 @@ pycryptodomex = "3.14.1"
|
||||||
pydantic = "1.8.2"
|
pydantic = "1.8.2"
|
||||||
pypng = "0.0.21"
|
pypng = "0.0.21"
|
||||||
pyqrcode = "1.2.1"
|
pyqrcode = "1.2.1"
|
||||||
pyscss = "1.3.7"
|
pyScss = "1.4.0"
|
||||||
python-dotenv = "0.19.0"
|
python-dotenv = "0.19.0"
|
||||||
pyyaml = "5.4.1"
|
pyyaml = "5.4.1"
|
||||||
represent = "1.6.0.post0"
|
represent = "1.6.0.post0"
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -72,7 +76,7 @@ mypy = "^0.971"
|
||||||
types-protobuf = "^3.19.22"
|
types-protobuf = "^3.19.22"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0", "pyScss"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -5,18 +5,21 @@ from tests.helpers import is_fake, is_regtest
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
||||||
async def test_mempool_url(client):
|
async def test_mempool_url(client):
|
||||||
response = await client.get("/boltz/api/v1/swap/mempool")
|
response = await client.get("/boltz/api/v1/swap/mempool")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
||||||
async def test_boltz_config(client):
|
async def test_boltz_config(client):
|
||||||
response = await client.get("/boltz/api/v1/swap/boltz")
|
response = await client.get("/boltz/api/v1/swap/boltz")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
||||||
async def test_endpoints_unauthenticated(client):
|
async def test_endpoints_unauthenticated(client):
|
||||||
response = await client.get("/boltz/api/v1/swap?all_wallets=true")
|
response = await client.get("/boltz/api/v1/swap?all_wallets=true")
|
||||||
assert response.status_code == 401
|
assert response.status_code == 401
|
||||||
|
|
@ -33,6 +36,7 @@ async def test_endpoints_unauthenticated(client):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
||||||
async def test_endpoints_inkey(client, inkey_headers_to):
|
async def test_endpoints_inkey(client, inkey_headers_to):
|
||||||
response = await client.get(
|
response = await client.get(
|
||||||
"/boltz/api/v1/swap?all_wallets=true", headers=inkey_headers_to
|
"/boltz/api/v1/swap?all_wallets=true", headers=inkey_headers_to
|
||||||
|
|
@ -56,6 +60,7 @@ async def test_endpoints_inkey(client, inkey_headers_to):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
||||||
async def test_endpoints_adminkey_nocontent(client, adminkey_headers_to):
|
async def test_endpoints_adminkey_nocontent(client, adminkey_headers_to):
|
||||||
response = await client.post("/boltz/api/v1/swap", headers=adminkey_headers_to)
|
response = await client.post("/boltz/api/v1/swap", headers=adminkey_headers_to)
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
@ -73,54 +78,6 @@ async def test_endpoints_adminkey_nocontent(client, adminkey_headers_to):
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.skipif(is_regtest, reason="this test is only passes with fakewallet")
|
|
||||||
async def test_endpoints_adminkey_fakewallet(client, from_wallet, adminkey_headers_to):
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap/check", headers=adminkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
swap = {
|
|
||||||
"wallet": from_wallet.id,
|
|
||||||
"refund_address": "bcrt1q3cwq33y435h52gq3qqsdtczh38ltlnf69zvypm",
|
|
||||||
"amount": 50_000,
|
|
||||||
}
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap", json=swap, headers=adminkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 405
|
|
||||||
reverse_swap = {
|
|
||||||
"wallet": from_wallet.id,
|
|
||||||
"instant_settlement": True,
|
|
||||||
"onchain_address": "bcrt1q4vfyszl4p8cuvqh07fyhtxve5fxq8e2ux5gx43",
|
|
||||||
"amount": 50_000,
|
|
||||||
}
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap/reverse", json=reverse_swap, headers=adminkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 201
|
|
||||||
reverse_swap = response.json()
|
|
||||||
assert reverse_swap["id"] is not None
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap/status",
|
|
||||||
params={"swap_id": reverse_swap["id"]},
|
|
||||||
headers=adminkey_headers_to,
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap/status",
|
|
||||||
params={"swap_id": "wrong"},
|
|
||||||
headers=adminkey_headers_to,
|
|
||||||
)
|
|
||||||
assert response.status_code == 404
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap/refund",
|
|
||||||
params={"swap_id": "wrong"},
|
|
||||||
headers=adminkey_headers_to,
|
|
||||||
)
|
|
||||||
assert response.status_code == 404
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
||||||
async def test_endpoints_adminkey_regtest(client, from_wallet, adminkey_headers_to):
|
async def test_endpoints_adminkey_regtest(client, from_wallet, adminkey_headers_to):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue