Merge branch 'main' into draggablecopilot

This commit is contained in:
ben 2023-01-16 10:01:40 +00:00
commit 615734e4a9
34 changed files with 751 additions and 516 deletions

View file

@ -54,8 +54,9 @@ LNBITS_SITE_DESCRIPTION="Some description about your service, will display if ti
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, 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, LnTipsWallet # Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet,
# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet # LndWallet, LndRestWallet, CoreLightningWallet, EclairWallet,
# LnTipsWallet, LNbitsWallet, SparkWallet, FakeWallet,
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,
# just so you can see the UI before dealing with this file. # just so you can see the UI before dealing with this file.
@ -76,6 +77,14 @@ CORELIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc"
LNBITS_ENDPOINT=https://legend.lnbits.com LNBITS_ENDPOINT=https://legend.lnbits.com
LNBITS_KEY=LNBITS_ADMIN_KEY LNBITS_KEY=LNBITS_ADMIN_KEY
# LndWallet
LND_GRPC_ENDPOINT=127.0.0.1
LND_GRPC_PORT=10009
LND_GRPC_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_GRPC_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
# To use an AES-encrypted macaroon, set
# LND_GRPC_MACAROON="eNcRyPtEdMaCaRoOn"
# LndRestWallet # LndRestWallet
LND_REST_ENDPOINT=https://127.0.0.1:8080/ LND_REST_ENDPOINT=https://127.0.0.1:8080/
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"

View file

@ -15,8 +15,8 @@ By default, LNbits will use SQLite as its database. You can also use PostgreSQL
If you have problems installing LNbits using these instructions, please have a look at the [Troubleshooting](#troubleshooting) section. 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.git
cd lnbits-legend/ cd lnbits
# for making sure python 3.9 is installed, skip if installed. To check your installed version: python3 --version # for making sure python 3.9 is installed, skip if installed. To check your installed version: python3 --version
sudo apt update sudo apt update
@ -50,9 +50,10 @@ poetry run lnbits
#### Updating the server #### Updating the server
``` ```
cd lnbits-legend/ cd lnbits
# Stop LNbits with `ctrl + x` # Stop LNbits with `ctrl + x`
git pull git pull
# Keep your poetry install up to date, this can be done with `poetry self update`
poetry install --only main poetry install --only main
# Start LNbits with `poetry run lnbits` # Start LNbits with `poetry run lnbits`
``` ```
@ -62,8 +63,8 @@ poetry install --only main
> note: currently not supported while we make some architectural changes on the path to leave beta > note: currently not supported while we make some architectural changes on the path to leave beta
```sh ```sh
git clone https://github.com/lnbits/lnbits-legend.git git clone https://github.com/lnbits/lnbits.git
cd lnbits-legend/ cd lnbits
# Modern debian distros usually include Nix, however you can install with: # Modern debian distros usually include Nix, however you can install with:
# 'sh <(curl -L https://nixos.org/nix/install) --daemon', or use setup here https://nixos.org/download.html#nix-verify-installation # 'sh <(curl -L https://nixos.org/nix/install) --daemon', or use setup here https://nixos.org/download.html#nix-verify-installation
@ -82,8 +83,8 @@ LNBITS_DATA_FOLDER=data LNBITS_BACKEND_WALLET_CLASS=LNbitsWallet LNBITS_ENDPOINT
## Option 3: venv ## Option 3: venv
```sh ```sh
git clone https://github.com/lnbits/lnbits-legend.git git clone https://github.com/lnbits/lnbits.git
cd lnbits-legend/ cd lnbits
# ensure you have virtualenv installed, on debian/ubuntu 'apt install python3.9-venv' # ensure you have virtualenv installed, on debian/ubuntu 'apt install python3.9-venv'
python3.9 -m venv venv python3.9 -m venv venv
# If you have problems here, try `sudo apt install -y pkg-config libpq-dev` # If you have problems here, try `sudo apt install -y pkg-config libpq-dev`
@ -105,9 +106,9 @@ If you want to host LNbits on the internet, run with the option `--host 0.0.0.0`
## Option 4: Docker ## Option 4: Docker
```sh ```sh
git clone https://github.com/lnbits/lnbits-legend.git git clone https://github.com/lnbits/lnbits.git
cd lnbits-legend cd lnbits
docker build -t lnbits-legend . docker build -t lnbits .
cp .env.example .env cp .env.example .env
mkdir data mkdir data
docker run --detach --publish 5000:5000 --name lnbits-legend --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits-legend docker run --detach --publish 5000:5000 --name lnbits-legend --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits-legend
@ -135,8 +136,8 @@ You can either run those commands, then `source ~/.bash_profile` or, if you don'
Once installed, run the following commands. Once installed, run the following commands.
``` ```
git clone https://github.com/lnbits/lnbits-legend.git git clone https://github.com/lnbits/lnbits.git
cd lnbits-legend cd lnbits
fly auth login fly auth login
[complete login process] [complete login process]
fly launch fly launch
@ -437,8 +438,8 @@ If you want to run LNbits on your Umbrel but want it to be reached through clear
To install using docker you first need to build the docker image as: To install using docker you first need to build the docker image as:
``` ```
git clone https://github.com/lnbits/lnbits-legend.git git clone https://github.com/lnbits/lnbits.git
cd lnbits-legend cd lnbits
docker build -t lnbits-legend . docker build -t lnbits-legend .
``` ```

View file

@ -66,7 +66,7 @@
outline outline
color="grey" color="grey"
type="a" type="a"
href="https://github.com/lnbits/lnbits-legend" href="https://github.com/lnbits/lnbits"
target="_blank" target="_blank"
rel="noopener" rel="noopener"
>View project in GitHub</q-btn >View project in GitHub</q-btn

View file

@ -1,5 +1,6 @@
import json import json
import os import os
from typing import Callable, Dict, Union
import httpx import httpx
@ -12,7 +13,9 @@ fiat_currencies = json.load(
) )
) )
exchange_rate_providers = { exchange_rate_providers: dict[
str, dict[str, Union[str, Callable[[dict, dict], str]]]
] = {
"bitfinex": { "bitfinex": {
"name": "Bitfinex", "name": "Bitfinex",
"domain": "bitfinex.com", "domain": "bitfinex.com",
@ -65,17 +68,19 @@ async def fetch_fiat_exchange_rate(currency: str, provider: str):
"to": currency.lower(), "to": currency.lower(),
} }
url = exchange_rate_providers[provider]["api_url"] api_url_or_none = exchange_rate_providers[provider]["api_url"]
if url: if api_url_or_none is not None:
api_url = str(api_url_or_none)
for key in replacements.keys(): for key in replacements.keys():
url = url.replace("{" + key + "}", replacements[key]) api_url = api_url.replace("{" + key + "}", replacements[key])
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.get(url) r = await client.get(api_url)
r.raise_for_status() r.raise_for_status()
data = r.json() data = r.json()
else: else:
data = {} data = {}
getter = exchange_rate_providers[provider]["getter"] getter = exchange_rate_providers[provider]["getter"]
print(getter)
if callable(getter):
rate = float(getter(data, replacements)) rate = float(getter(data, replacements))
return rate return rate

View file

@ -1,11 +1,11 @@
import base64 import base64
import hashlib import hashlib
import hmac import hmac
import urllib
from http import HTTPStatus from http import HTTPStatus
from typing import Dict from typing import Dict
from urllib import parse
from starlette.requests import Request from fastapi import Request
def generate_bleskomat_lnurl_hash(secret: str): def generate_bleskomat_lnurl_hash(secret: str):
@ -22,7 +22,7 @@ def generate_bleskomat_lnurl_signature(
elif api_key_encoding == "base64": elif api_key_encoding == "base64":
key = base64.b64decode(api_key_secret) key = base64.b64decode(api_key_secret)
else: else:
key = bytes(f"{api_key_secret}") key = bytes.fromhex(api_key_secret)
return hmac.new(key=key, msg=payload.encode(), digestmod=hashlib.sha256).hexdigest() return hmac.new(key=key, msg=payload.encode(), digestmod=hashlib.sha256).hexdigest()
@ -57,8 +57,8 @@ class LnurlValidationError(Exception):
pass pass
def prepare_lnurl_params(tag: str, query: Dict[str, str]): def prepare_lnurl_params(tag: str, query: dict) -> dict:
params = {} params: dict = {}
if not is_supported_lnurl_subprotocol(tag): if not is_supported_lnurl_subprotocol(tag):
raise LnurlValidationError(f'Unsupported subprotocol: "{tag}"') raise LnurlValidationError(f'Unsupported subprotocol: "{tag}"')
if tag == "withdrawRequest": if tag == "withdrawRequest":
@ -85,15 +85,15 @@ def query_to_signing_payload(query: Dict[str, str]) -> str:
payload = [] payload = []
for key in sorted_keys: for key in sorted_keys:
if not key == "signature": if not key == "signature":
encoded_key = urllib.parse.quote(key, safe=encode_uri_component_safe_chars) encoded_key = parse.quote(key, safe=encode_uri_component_safe_chars)
encoded_value = urllib.parse.quote( encoded_value = parse.quote(
query[key], safe=encode_uri_component_safe_chars query[key], safe=encode_uri_component_safe_chars
) )
payload.append(f"{encoded_key}={encoded_value}") payload.append(f"{encoded_key}={encoded_value}")
return "&".join(payload) return "&".join(payload)
unshorten_rules = { unshorten_rules: dict[str, dict] = {
"query": {"n": "nonce", "s": "signature", "t": "tag"}, "query": {"n": "nonce", "s": "signature", "t": "tag"},
"tags": { "tags": {
"c": "channelRequest", "c": "channelRequest",
@ -114,7 +114,7 @@ unshorten_rules = {
} }
def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]: def unshorten_lnurl_query(query: dict) -> Dict[str, str]:
new_query = {} new_query = {}
rules = unshorten_rules rules = unshorten_rules
if "tag" in query: if "tag" in query:
@ -131,9 +131,9 @@ def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]:
if not tag in rules["params"]: if not tag in rules["params"]:
raise LnurlValidationError(f'Unknown tag: "{tag}"') raise LnurlValidationError(f'Unknown tag: "{tag}"')
for key in query: for key in query:
if key in rules["params"][tag]: if key in rules["params"][str(tag)]:
short_param_key = key short_param_key = key
long_param_key = rules["params"][tag][short_param_key] long_param_key = rules["params"][str(tag)][short_param_key]
if short_param_key in query: if short_param_key in query:
new_query[long_param_key] = query[short_param_key] new_query[long_param_key] = query[short_param_key]
else: else:
@ -146,7 +146,7 @@ def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]:
if short_key in query: if short_key in query:
new_query[long_key] = query[short_key] new_query[long_key] = query[short_key]
else: else:
new_query[long_key] = query[long_key] new_query[long_key] = query[str(long_key)]
else: else:
# Keep unknown key/value pairs unchanged: # Keep unknown key/value pairs unchanged:
new_query[key] = query[key] new_query[key] = query[key]

View file

@ -1,6 +1,5 @@
import json import json
import math import math
import traceback
from http import HTTPStatus from http import HTTPStatus
from loguru import logger from loguru import logger
@ -28,7 +27,7 @@ from .helpers import (
@bleskomat_ext.get("/u", name="bleskomat.api_bleskomat_lnurl") @bleskomat_ext.get("/u", name="bleskomat.api_bleskomat_lnurl")
async def api_bleskomat_lnurl(req: Request): async def api_bleskomat_lnurl(req: Request):
try: try:
query = req.query_params query = dict(req.query_params)
# Unshorten query if "s" is used instead of "signature". # Unshorten query if "s" is used instead of "signature".
if "s" in query: if "s" in query:
@ -89,11 +88,15 @@ async def api_bleskomat_lnurl(req: Request):
# Convert to msats: # Convert to msats:
params[key] = int(amount_sats_less_fee * 1e3) params[key] = int(amount_sats_less_fee * 1e3)
except LnurlValidationError as e: except LnurlValidationError as e:
raise LnurlHttpError(e.message, HTTPStatus.BAD_REQUEST) raise LnurlHttpError(str(e), HTTPStatus.BAD_REQUEST)
# Create a new LNURL using the query parameters provided in the signed URL. # Create a new LNURL using the query parameters provided in the signed URL.
params = json.JSONEncoder().encode(params) json_params = json.JSONEncoder().encode(params)
lnurl = await create_bleskomat_lnurl( lnurl = await create_bleskomat_lnurl(
bleskomat=bleskomat, secret=secret, tag=tag, params=params, uses=1 bleskomat=bleskomat,
secret=secret,
tag=tag,
params=json_params,
uses=1,
) )
# Reply with LNURL response object. # Reply with LNURL response object.

View file

@ -2,10 +2,9 @@ import json
import time import time
from typing import Dict from typing import Dict
from fastapi.params import Query from fastapi import Query, Request
from loguru import logger from loguru import logger
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from starlette.requests import Request
from lnbits import bolt11 from lnbits import bolt11
from lnbits.core.services import PaymentFailure, pay_invoice from lnbits.core.services import PaymentFailure, pay_invoice
@ -80,7 +79,7 @@ class BleskomatLnurl(BaseModel):
response["k1"] = secret response["k1"] = secret
return response return response
def validate_action(self, query: Dict[str, str]) -> None: def validate_action(self, query) -> None:
tag = self.tag tag = self.tag
params = json.loads(self.params) params = json.loads(self.params)
# Perform tag-specific checks. # Perform tag-specific checks.
@ -109,7 +108,7 @@ class BleskomatLnurl(BaseModel):
else: else:
raise LnurlValidationError(f'Unknown subprotocol: "{tag}"') raise LnurlValidationError(f'Unknown subprotocol: "{tag}"')
async def execute_action(self, query: Dict[str, str]): async def execute_action(self, query):
self.validate_action(query) self.validate_action(query)
used = False used = False
async with db.connect() as conn: async with db.connect() as conn:

View file

@ -1,5 +1,4 @@
from fastapi import Request from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse

View file

@ -6,7 +6,6 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, require_admin_key from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.bleskomat.models import CreateBleskomat
from . import bleskomat_ext from . import bleskomat_ext
from .crud import ( from .crud import (
@ -17,6 +16,7 @@ from .crud import (
update_bleskomat, update_bleskomat,
) )
from .exchange_rates import fetch_fiat_exchange_rate from .exchange_rates import fetch_fiat_exchange_rate
from .models import CreateBleskomat
@bleskomat_ext.get("/api/v1/bleskomats") @bleskomat_ext.get("/api/v1/bleskomats")
@ -27,7 +27,8 @@ async def api_bleskomats(
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [bleskomat.dict() for bleskomat in await get_bleskomats(wallet_ids)] return [bleskomat.dict() for bleskomat in await get_bleskomats(wallet_ids)]
@ -54,9 +55,9 @@ async def api_bleskomat_create_or_update(
wallet: WalletTypeInfo = Depends(require_admin_key), wallet: WalletTypeInfo = Depends(require_admin_key),
bleskomat_id=None, bleskomat_id=None,
): ):
try:
fiat_currency = data.fiat_currency fiat_currency = data.fiat_currency
exchange_rate_provider = data.exchange_rate_provider exchange_rate_provider = data.exchange_rate_provider
try:
await fetch_fiat_exchange_rate( await fetch_fiat_exchange_rate(
currency=fiat_currency, provider=exchange_rate_provider currency=fiat_currency, provider=exchange_rate_provider
) )
@ -79,6 +80,7 @@ async def api_bleskomat_create_or_update(
else: else:
bleskomat = await create_bleskomat(wallet_id=wallet.wallet.id, data=data) bleskomat = await create_bleskomat(wallet_id=wallet.wallet.id, data=data)
assert bleskomat
return bleskomat.dict() return bleskomat.dict()

View file

@ -32,7 +32,7 @@
<a <a
class="text-secondary" class="text-secondary"
target="_blank" target="_blank"
href="https://github.com/lnbits/lnbits-legend/tree/main/lnbits/extensions/boltz" href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/boltz"
>More details</a >More details</a
> >
</p> </p>

View file

@ -4,18 +4,19 @@
<q-card class="q-pa-lg q-mb-xl"> <q-card class="q-pa-lg q-mb-xl">
<q-card-section class="q-pa-none"> <q-card-section class="q-pa-none">
<center> <center>
<q-icon <q-img
name="account_balance" src="/cashu/static/image/cashu.png"
class="text-grey" spinner-color="white"
style="font-size: 10rem" style="max-width: 20%"
></q-icon> ></q-img>
<h4 class="q-mt-none q-mb-md">{{ mint_name }}</h4> <h4 class="q-mt-sm q-mb-md">{{ mint_name }}</h4>
<!-- <a class="text-secondary">Mint URL: {{testfield}} </a> <br /> -->
<a <a
class="text-secondary" class="text-secondary"
class="q-my-xl text-white" class="q-my-xl text-white"
style="font-size: 1.5rem" style="font-size: 1.5rem"
href="../wallet?mint_id={{ mint_id }}" href="../wallet?mint_id={{ mint_id }}"
>Open wallet</a >click to open wallet</a
> >
</center> </center>
</q-card-section> </q-card-section>
@ -58,24 +59,34 @@
</p> </p>
<p> <p>
<strong>This service is in BETA</strong> <br /> <strong>This service is in BETA</strong> <br />
We hold no responsibility for people losing access to funds. Use at Cashu is still experimental and in active development. There are
your own risk! likely bugs in this implementation so please use this with caution. We
hold no responsibility for people losing access to funds. Use at your
own risk!
</p> </p>
</q-card-section> </q-card-section>
</q-card> </q-card>
</div> </div>
</div>
{% endblock %} {% block scripts %} {% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
<script>
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
data: function () { data: function () {
return {} return {
testfield: 'asd',
mintURL: {
location: window.location,
base_url: location.protocol + '//' + location.host + location.pathname
}
}
} }
}) })
</script> </script>
{% endblock %} {% endblock %}
</div>

View file

@ -1479,15 +1479,15 @@ page_container %}
}, },
constructOutputs: async function (amounts, secrets) { constructOutputs: async function (amounts, secrets) {
const blindedMessages = [] const outputs = []
const rs = [] const rs = []
for (let i = 0; i < amounts.length; i++) { for (let i = 0; i < amounts.length; i++) {
const {B_, r} = await step1Alice(secrets[i]) const {B_, r} = await step1Alice(secrets[i])
blindedMessages.push({amount: amounts[i], B_: B_}) outputs.push({amount: amounts[i], B_: B_})
rs.push(r) rs.push(r)
} }
return { return {
blindedMessages, outputs,
rs rs
} }
}, },
@ -1581,25 +1581,26 @@ page_container %}
mintApi: async function (amounts, payment_hash, verbose = true) { mintApi: async function (amounts, payment_hash, verbose = true) {
/* /*
asks the mint to check whether the invoice with payment_hash has been paid asks the mint to check whether the invoice with payment_hash has been paid
and requests signing of the attached outputs (blindedMessages) and requests signing of the attached outputs.
*/ */
console.log('### promises', payment_hash) console.log('### promises', payment_hash)
try { try {
let secrets = await this.generateSecrets(amounts) let secrets = await this.generateSecrets(amounts)
let {blindedMessages, rs} = await this.constructOutputs( let {outputs, rs} = await this.constructOutputs(amounts, secrets)
amounts,
secrets
)
const promises = await LNbits.api.request( const promises = await LNbits.api.request(
'POST', 'POST',
`/cashu/api/v1/${this.mintId}/mint?payment_hash=${payment_hash}`, `/cashu/api/v1/${this.mintId}/mint?payment_hash=${payment_hash}`,
'', '',
{ {
blinded_messages: blindedMessages outputs
} }
) )
console.log('### promises data', promises.data) console.log('### promises data', promises.data.promises)
let proofs = await this.constructProofs(promises.data, secrets, rs) let proofs = await this.constructProofs(
promises.data.promises,
secrets,
rs
)
return proofs return proofs
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -1682,16 +1683,11 @@ page_container %}
'number of secrets does not match number of outputs.' 'number of secrets does not match number of outputs.'
) )
} }
let {blindedMessages, rs} = await this.constructOutputs( let {outputs, rs} = await this.constructOutputs(amounts, secrets)
amounts,
secrets
)
const payload = { const payload = {
amount, amount,
proofs, proofs,
outputs: { outputs
blinded_messages: blindedMessages
}
} }
console.log('payload', JSON.stringify(payload)) console.log('payload', JSON.stringify(payload))
@ -1881,10 +1877,7 @@ page_container %}
'amount with fees', 'amount with fees',
amount amount
) )
// if (amount > balance()) {
// LNbits.utils.notifyApiError('Balance too low')
// return
// }
let {fristProofs, scndProofs} = await this.splitToSend( let {fristProofs, scndProofs} = await this.splitToSend(
this.proofs, this.proofs,
amount amount
@ -2132,6 +2125,19 @@ page_container %}
return paid return paid
}, },
findTokenForAmount: function (amount) {
for (const token of this.proofs) {
const index = token.promises?.findIndex(p => p.amount === amount)
if (index >= 0) {
return {
promise: token.promises[index],
secret: token.secrets[index],
r: token.rs[index]
}
}
}
},
////////////// WORKERS ////////////// ////////////// WORKERS //////////////
clearAllWorkers: function () { clearAllWorkers: function () {
@ -2220,76 +2226,6 @@ page_container %}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
findTokenForAmount: function (amount) {
for (const token of this.proofs) {
const index = token.promises?.findIndex(p => p.amount === amount)
if (index >= 0) {
return {
promise: token.promises[index],
secret: token.secrets[index],
r: token.rs[index]
}
}
}
},
// checkInvoice: function () {
// console.log('#### checkInvoice')
// try {
// const invoice = decode(this.payInvoiceData.data.request)
// const cleanInvoice = {
// msat: invoice.human_readable_part.amount,
// sat: invoice.human_readable_part.amount / 1000,
// fsat: LNbits.utils.formatSat(
// invoice.human_readable_part.amount / 1000
// )
// }
// _.each(invoice.data.tags, tag => {
// if (_.isObject(tag) && _.has(tag, 'description')) {
// if (tag.description === 'payment_hash') {
// cleanInvoice.hash = tag.value
// } else if (tag.description === 'description') {
// cleanInvoice.description = tag.value
// } else if (tag.description === 'expiry') {
// var expireDate = new Date(
// (invoice.data.time_stamp + tag.value) * 1000
// )
// cleanInvoice.expireDate = Quasar.utils.date.formatDate(
// expireDate,
// 'YYYY-MM-DDTHH:mm:ss.SSSZ'
// )
// cleanInvoice.expired = false // TODO
// }
// }
// this.payInvoiceData.invoice = cleanInvoice
// })
// console.log(
// '#### this.payInvoiceData.invoice',
// this.payInvoiceData.invoice
// )
// } catch (error) {
// this.$q.notify({
// timeout: 5000,
// type: 'warning',
// message: 'Could not decode invoice',
// caption: error + '',
// position: 'top',
// actions: [
// {
// icon: 'close',
// color: 'white',
// handler: () => {}
// }
// ]
// })
// throw error
// }
// },
////////////// STORAGE ///////////// ////////////// STORAGE /////////////
getLocalstorageToFile: async function () { getLocalstorageToFile: async function () {

View file

@ -12,7 +12,8 @@ from cashu.core.base import (
GetMintResponse, GetMintResponse,
Invoice, Invoice,
MeltRequest, MeltRequest,
MintRequest, PostMintRequest,
PostMintResponse,
PostSplitResponse, PostSplitResponse,
SplitRequest, SplitRequest,
) )
@ -204,10 +205,10 @@ async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintR
@cashu_ext.post("/api/v1/{cashu_id}/mint") @cashu_ext.post("/api/v1/{cashu_id}/mint")
async def mint( async def mint(
data: MintRequest, data: PostMintRequest,
cashu_id: str = Query(None), cashu_id: str = Query(None),
payment_hash: str = Query(None), payment_hash: str = Query(None),
) -> List[BlindedSignature]: ) -> PostMintResponse:
""" """
Requests the minting of tokens belonging to a paid payment request. Requests the minting of tokens belonging to a paid payment request.
Call this endpoint after `GET /mint`. Call this endpoint after `GET /mint`.
@ -245,7 +246,7 @@ async def mint(
) )
try: try:
total_requested = sum([bm.amount for bm in data.blinded_messages]) total_requested = sum([bm.amount for bm in data.outputs])
if total_requested > invoice.amount: if total_requested > invoice.amount:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED, status_code=HTTPStatus.PAYMENT_REQUIRED,
@ -257,10 +258,8 @@ async def mint(
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid." status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
) )
promises = await ledger._generate_promises( promises = await ledger._generate_promises(B_s=data.outputs, keyset=keyset)
B_s=data.blinded_messages, keyset=keyset return PostMintResponse(promises=promises)
)
return promises
except (Exception, HTTPException) as e: except (Exception, HTTPException) as e:
logger.debug(f"Cashu: /melt {str(e) or getattr(e, 'detail')}") logger.debug(f"Cashu: /melt {str(e) or getattr(e, 'detail')}")
# unset issued flag because something went wrong # unset issued flag because something went wrong
@ -274,10 +273,8 @@ async def mint(
) )
else: else:
# only used for testing when LIGHTNING=false # only used for testing when LIGHTNING=false
promises = await ledger._generate_promises( promises = await ledger._generate_promises(B_s=data.outputs, keyset=keyset)
B_s=data.blinded_messages, keyset=keyset return PostMintResponse(promises=promises)
)
return promises
@cashu_ext.post("/api/v1/{cashu_id}/melt") @cashu_ext.post("/api/v1/{cashu_id}/melt")
@ -421,7 +418,7 @@ async def split(
) )
amount = payload.amount amount = payload.amount
outputs = payload.outputs.blinded_messages outputs = payload.outputs
assert outputs, Exception("no outputs provided.") assert outputs, Exception("no outputs provided.")
split_return = None split_return = None
try: try:

View file

@ -14,7 +14,7 @@
<a <a
class="text-secondary" class="text-secondary"
href="https://github.com/lnbits/lnbits-legend/tree/main/lnbits/extensions/lnaddress" href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/lnaddress"
>More details</a >More details</a
> >
<br /> <br />

View file

@ -195,7 +195,7 @@
Check extension Check extension
<a <a
class="text-secondary" class="text-secondary"
href="https://github.com/lnbits/lnbits-legend/blob/main/lnbits/extensions/lnaddress/README.md" href="https://github.com/lnbits/lnbits/blob/main/lnbits/extensions/lnaddress/README.md"
>documentation!</a >documentation!</a
> >
</template> </template>

View file

@ -3,7 +3,7 @@ from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from . import db from . import db
from .models import Address, CreateAddressData, CreateDomainData, Domain from .models import Address, CreateAddressData, CreateDomainData, Domain, EditDomainData
async def get_domain(domain_id: str) -> Optional[Domain]: async def get_domain(domain_id: str) -> Optional[Domain]:
@ -170,6 +170,26 @@ async def create_address_internal(domain_id: str, data: CreateAddressData) -> Ad
return address return address
async def update_domain_internal(wallet_id: str, data: EditDomainData) -> Domain:
if data.currency != "Satoshis":
amount = data.amount * 100
else:
amount = data.amount
print(data)
await db.execute(
"""
UPDATE nostrnip5.domains
SET amount = ?, currency = ?
WHERE id = ?
""",
(int(amount), data.currency, data.id),
)
domain = await get_domain(data.id)
assert domain, "Domain couldn't be updated"
return domain
async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Domain: async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Domain:
domain_id = urlsafe_short_hash() domain_id = urlsafe_short_hash()

View file

@ -24,6 +24,16 @@ class CreateDomainData(BaseModel):
domain: str domain: str
class EditDomainData(BaseModel):
id: str
currency: str
amount: float = Query(..., ge=0.01)
@classmethod
def from_row(cls, row: Row) -> "EditDomainData":
return cls(**dict(row))
class Domain(BaseModel): class Domain(BaseModel):
id: str id: str
wallet: str wallet: str

View file

@ -73,6 +73,14 @@
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="deleteDomain(props.row.id)" @click="deleteDomain(props.row.id)"
></q-btn> ></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="edit"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="editDomain(props.row.id)"
></q-btn>
</q-td> </q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props"> <q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }} {{ col.value }}
@ -226,6 +234,39 @@
</q-form> </q-form>
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-dialog
v-model="editFormDialog.show"
position="top"
@hide="closeFormDialog"
>
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-form @submit="saveEditedDomain" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="editFormDialog.data.currency"
:options="currencyOptions"
label="Currency *"
></q-select>
<q-input
filled
dense
v-model.trim="editFormDialog.data.amount"
label="Amount"
placeholder="How much do you want to charge?"
></q-input>
<div class="row q-mt-lg">
<q-btn unelevated color="primary" type="submit">Update Amount</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog <q-dialog
v-model="addressFormDialog.show" v-model="addressFormDialog.show"
position="top" position="top"
@ -513,6 +554,10 @@
show: false, show: false,
data: {} data: {}
}, },
editFormDialog: {
show: false,
data: {}
},
addressFormDialog: { addressFormDialog: {
show: false, show: false,
data: {} data: {}
@ -578,6 +623,34 @@
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
saveEditedDomain: function () {
var data = this.editFormDialog.data
var self = this
LNbits.api
.request(
'PUT',
'/nostrnip5/api/v1/domain',
_.findWhere(this.g.user.wallets, {
id: this.editFormDialog.data.wallet
}).inkey,
data
)
.then(function (response) {
self.editFormDialog.show = false
self.editFormDialog.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
editDomain: function (domain_id) {
var self = this
var data = _.findWhere(this.domains, {id: domain_id})
self.editFormDialog.show = true
self.editFormDialog.data = data
},
deleteDomain: function (domain_id) { deleteDomain: function (domain_id) {
var self = this var self = this
var domain = _.findWhere(this.domains, {id: domain_id}) var domain = _.findWhere(this.domains, {id: domain_id})

View file

@ -26,8 +26,14 @@ from .crud import (
get_domain_by_name, get_domain_by_name,
get_domains, get_domains,
rotate_address, rotate_address,
update_domain_internal,
)
from .models import (
CreateAddressData,
CreateDomainData,
EditDomainData,
RotateAddressData,
) )
from .models import CreateAddressData, CreateDomainData, RotateAddressData
@nostrnip5_ext.get("/api/v1/domains", status_code=HTTPStatus.OK) @nostrnip5_ext.get("/api/v1/domains", status_code=HTTPStatus.OK)
@ -89,6 +95,16 @@ async def api_domain_create(
return domain return domain
@nostrnip5_ext.put("/api/v1/domain", status_code=HTTPStatus.OK)
async def api_domain_update(
data: EditDomainData, wallet: WalletTypeInfo = Depends(get_key_type)
):
domain = await update_domain_internal(wallet_id=wallet.wallet.id, data=data)
return domain
@nostrnip5_ext.delete("/api/v1/domain/{domain_id}", status_code=HTTPStatus.CREATED) @nostrnip5_ext.delete("/api/v1/domain/{domain_id}", status_code=HTTPStatus.CREATED)
async def api_domain_delete( async def api_domain_delete(
domain_id: str, domain_id: str,

View file

@ -4,6 +4,15 @@ Vue.component(VueQrcode.name, VueQrcode)
const pica = window.pica() const pica = window.pica()
function imgSizeFit(img, maxWidth = 1024, maxHeight = 768) {
let ratio = Math.min(
1,
maxWidth / img.naturalWidth,
maxHeight / img.naturalHeight
)
return {width: img.naturalWidth * ratio, height: img.naturalHeight * ratio}
}
const defaultItemData = { const defaultItemData = {
unit: 'sat' unit: 'sat'
} }
@ -23,6 +32,7 @@ new Vue({
}, },
itemDialog: { itemDialog: {
show: false, show: false,
urlImg: true,
data: {...defaultItemData}, data: {...defaultItemData},
units: ['sat'] units: ['sat']
} }
@ -41,6 +51,9 @@ new Vue({
openUpdateDialog(itemId) { openUpdateDialog(itemId) {
this.itemDialog.show = true this.itemDialog.show = true
let item = this.offlineshop.items.find(item => item.id === itemId) let item = this.offlineshop.items.find(item => item.id === itemId)
if (item.image.startsWith('data:')) {
this.itemDialog.urlImg = false
}
this.itemDialog.data = item this.itemDialog.data = item
}, },
imageAdded(file) { imageAdded(file) {
@ -48,17 +61,12 @@ new Vue({
let image = new Image() let image = new Image()
image.src = blobURL image.src = blobURL
image.onload = async () => { image.onload = async () => {
let fit = imgSizeFit(image, 100, 100)
let canvas = document.createElement('canvas') let canvas = document.createElement('canvas')
canvas.setAttribute('width', 100) canvas.setAttribute('width', fit.width)
canvas.setAttribute('height', 100) canvas.setAttribute('height', fit.height)
await pica.resize(image, canvas, { output = await pica.resize(image, canvas)
quality: 0, this.itemDialog.data.image = output.toDataURL('image/jpeg', 0.4)
alpha: true,
unsharpAmount: 95,
unsharpRadius: 0.9,
unsharpThreshold: 70
})
this.itemDialog.data.image = canvas.toDataURL()
this.itemDialog = {...this.itemDialog} this.itemDialog = {...this.itemDialog}
} }
}, },
@ -155,6 +163,7 @@ new Vue({
this.loadShop() this.loadShop()
this.itemDialog.show = false this.itemDialog.show = false
this.itemDialog.urlImg = true
this.itemDialog.data = {...defaultItemData} this.itemDialog.data = {...defaultItemData}
}, },
toggleItem(itemId) { toggleItem(itemId) {

View file

@ -237,7 +237,7 @@
<q-responsive v-if="itemDialog.data.id" :ratio="1"> <q-responsive v-if="itemDialog.data.id" :ratio="1">
<qrcode <qrcode
:value="'lightning:' + itemDialog.data.lnurl" :value="'lightning:' + itemDialog.data.lnurl"
:options="{width: 800}" :options="{width: 300}"
class="rounded-borders" class="rounded-borders"
></qrcode> ></qrcode>
</q-responsive> </q-responsive>
@ -266,7 +266,16 @@
type="text" type="text"
label="Brief description" label="Brief description"
></q-input> ></q-input>
<q-input
v-if="itemDialog.urlImg"
filled
dense
v-model.trim="itemDialog.data.image"
type="url"
label="Image URL"
></q-input>
<q-file <q-file
v-else
filled filled
dense dense
capture="environment" capture="environment"
@ -288,6 +297,10 @@
/> />
</template> </template>
</q-file> </q-file>
<q-toggle
:label="`${itemDialog.urlImg ? 'Insert image URL' : 'Upload image file'}`"
v-model="itemDialog.urlImg"
></q-toggle>
<q-input <q-input
filled filled
dense dense

View file

@ -60,6 +60,22 @@ async def api_add_or_update_item(
): ):
shop = await get_or_create_shop_by_wallet(wallet.wallet.id) shop = await get_or_create_shop_by_wallet(wallet.wallet.id)
assert shop assert shop
if data.image:
image_is_url = data.image.startswith("https://") or data.image.startswith(
"http://"
)
if not image_is_url:
def size(b64string):
return int((len(b64string) * 3) / 4 - b64string.count("=", -2))
image_size = size(data.image) / 1024
if image_size > 100:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Image size is too big, {int(image_size)}Kb. Max: 100kb, Compress the image at https://tinypng.com, or use an URL.",
)
if data.unit != "sat": if data.unit != "sat":
data.price = data.price * 100 data.price = data.price * 100
if item_id == None: if item_id == None:

View file

@ -1,10 +1,9 @@
from http import HTTPStatus
from typing import List, Optional, Union from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from . import db from . import db
from .models import CreateEmail, CreateEmailaddress, Emailaddresses, Emails from .models import CreateEmail, CreateEmailaddress, Email, Emailaddress
from .smtp import send_mail from .smtp import send_mail
@ -17,7 +16,7 @@ def get_test_mail(email, testemail):
) )
async def create_emailaddress(data: CreateEmailaddress) -> Emailaddresses: async def create_emailaddress(data: CreateEmailaddress) -> Emailaddress:
emailaddress_id = urlsafe_short_hash() emailaddress_id = urlsafe_short_hash()
@ -50,7 +49,7 @@ async def create_emailaddress(data: CreateEmailaddress) -> Emailaddresses:
return new_emailaddress return new_emailaddress
async def update_emailaddress(emailaddress_id: str, **kwargs) -> Emailaddresses: async def update_emailaddress(emailaddress_id: str, **kwargs) -> Emailaddress:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute( await db.execute(
f"UPDATE smtp.emailaddress SET {q} WHERE id = ?", f"UPDATE smtp.emailaddress SET {q} WHERE id = ?",
@ -65,30 +64,22 @@ async def update_emailaddress(emailaddress_id: str, **kwargs) -> Emailaddresses:
await send_mail(row, email) await send_mail(row, email)
assert row, "Newly updated emailaddress couldn't be retrieved" assert row, "Newly updated emailaddress couldn't be retrieved"
return Emailaddresses(**row) return Emailaddress(**row)
async def get_emailaddress(emailaddress_id: str) -> Optional[Emailaddresses]: async def get_emailaddress(emailaddress_id: str) -> Optional[Emailaddress]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,) "SELECT * FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,)
) )
return Emailaddresses(**row) if row else None return Emailaddress(**row) if row else None
async def get_emailaddress_by_email(email: str) -> Optional[Emailaddresses]: async def get_emailaddress_by_email(email: str) -> Optional[Emailaddress]:
row = await db.fetchone("SELECT * FROM smtp.emailaddress WHERE email = ?", (email,)) row = await db.fetchone("SELECT * FROM smtp.emailaddress WHERE email = ?", (email,))
return Emailaddresses(**row) if row else None return Emailaddress(**row) if row else None
# async def get_emailAddressByEmail(email: str) -> Optional[Emails]: async def get_emailaddresses(wallet_ids: Union[str, List[str]]) -> List[Emailaddress]:
# row = await db.fetchone(
# "SELECT s.*, d.emailaddress as emailaddress FROM smtp.email s INNER JOIN smtp.emailaddress d ON (s.emailaddress_id = d.id) WHERE s.emailaddress = ?",
# (email,),
# )
# return Subdomains(**row) if row else None
async def get_emailaddresses(wallet_ids: Union[str, List[str]]) -> List[Emailaddresses]:
if isinstance(wallet_ids, str): if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids] wallet_ids = [wallet_ids]
@ -97,21 +88,22 @@ async def get_emailaddresses(wallet_ids: Union[str, List[str]]) -> List[Emailadd
f"SELECT * FROM smtp.emailaddress WHERE wallet IN ({q})", (*wallet_ids,) f"SELECT * FROM smtp.emailaddress WHERE wallet IN ({q})", (*wallet_ids,)
) )
return [Emailaddresses(**row) for row in rows] return [Emailaddress(**row) for row in rows]
async def delete_emailaddress(emailaddress_id: str) -> None: async def delete_emailaddress(emailaddress_id: str) -> None:
await db.execute("DELETE FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,)) await db.execute("DELETE FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,))
## create emails async def create_email(wallet: str, data: CreateEmail, payment_hash: str = "") -> Email:
async def create_email(payment_hash, wallet, data: CreateEmail) -> Emails: id = urlsafe_short_hash()
await db.execute( await db.execute(
""" """
INSERT INTO smtp.email (id, wallet, emailaddress_id, subject, receiver, message, paid) INSERT INTO smtp.email (id, payment_hash, wallet, emailaddress_id, subject, receiver, message, paid)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", """,
( (
id,
payment_hash, payment_hash,
wallet, wallet,
data.emailaddress_id, data.emailaddress_id,
@ -122,36 +114,34 @@ async def create_email(payment_hash, wallet, data: CreateEmail) -> Emails:
), ),
) )
new_email = await get_email(payment_hash) new_email = await get_email(id)
assert new_email, "Newly created email couldn't be retrieved" assert new_email, "Newly created email couldn't be retrieved"
return new_email return new_email
async def set_email_paid(payment_hash: str) -> Emails: async def set_email_paid(payment_hash: str) -> bool:
email = await get_email(payment_hash) email = await get_email_by_payment_hash(payment_hash)
if email and email.paid == False: if email and email.paid == False:
await db.execute( await db.execute(
""" f"UPDATE smtp.email SET paid = true WHERE payment_hash = ?", (payment_hash,)
UPDATE smtp.email
SET paid = true
WHERE id = ?
""",
(payment_hash,),
) )
new_email = await get_email(payment_hash) return True
assert new_email, "Newly paid email couldn't be retrieved" return False
return new_email
async def get_email(email_id: str) -> Optional[Emails]: async def get_email_by_payment_hash(payment_hash: str) -> Optional[Email]:
row = await db.fetchone( row = await db.fetchone(
"SELECT s.*, d.email as emailaddress FROM smtp.email s INNER JOIN smtp.emailaddress d ON (s.emailaddress_id = d.id) WHERE s.id = ?", f"SELECT * FROM smtp.email WHERE payment_hash = ?", (payment_hash,)
(email_id,),
) )
return Emails(**row) if row else None return Email(**row) if row else None
async def get_emails(wallet_ids: Union[str, List[str]]) -> List[Emails]: async def get_email(id: str) -> Optional[Email]:
row = await db.fetchone(f"SELECT * FROM smtp.email WHERE id = ?", (id,))
return Email(**row) if row else None
async def get_emails(wallet_ids: Union[str, List[str]]) -> List[Email]:
if isinstance(wallet_ids, str): if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids] wallet_ids = [wallet_ids]
@ -161,7 +151,7 @@ async def get_emails(wallet_ids: Union[str, List[str]]) -> List[Emails]:
(*wallet_ids,), (*wallet_ids,),
) )
return [Emails(**row) for row in rows] return [Email(**row) for row in rows]
async def delete_email(email_id: str) -> None: async def delete_email(email_id: str) -> None:

View file

@ -33,3 +33,7 @@ async def m001_initial(db):
); );
""" """
) )
async def m002_add_payment_hash(db):
await db.execute(f"ALTER TABLE smtp.email ADD COLUMN payment_hash TEXT;")

View file

@ -15,7 +15,7 @@ class CreateEmailaddress(BaseModel):
cost: int = Query(..., ge=0) cost: int = Query(..., ge=0)
class Emailaddresses(BaseModel): class Emailaddress(BaseModel):
id: str id: str
wallet: str wallet: str
email: str email: str
@ -36,7 +36,7 @@ class CreateEmail(BaseModel):
message: str = Query(...) message: str = Query(...)
class Emails(BaseModel): class Email(BaseModel):
id: str id: str
wallet: str wallet: str
emailaddress_id: str emailaddress_id: str

View file

@ -4,83 +4,107 @@ import time
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.utils import formatdate from email.utils import formatdate
from http import HTTPStatus
from smtplib import SMTP_SSL as SMTP from smtplib import SMTP_SSL as SMTP
from typing import Union
from loguru import logger from loguru import logger
from starlette.exceptions import HTTPException
from .models import CreateEmail, CreateEmailaddress, Email, Emailaddress
async def send_mail(
emailaddress: Union[Emailaddress, CreateEmailaddress],
email: Union[Email, CreateEmail],
):
smtp_client = SmtpService(emailaddress)
message = smtp_client.create_message(email)
await smtp_client.send_mail(email.receiver, message)
def valid_email(s): def valid_email(s):
# https://regexr.com/2rhq7 # https://regexr.com/2rhq7
pat = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" pat = r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
if re.match(pat, s): if re.match(pat, s):
return True return True
msg = f"SMTP - invalid email: {s}." log = f"SMTP - invalid email: {s}."
logger.error(msg) logger.error(log)
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) raise Exception(log)
async def send_mail(emailaddress, email): class SmtpService:
valid_email(emailaddress.email) def __init__(self, emailaddress: Union[Emailaddress, CreateEmailaddress]) -> None:
valid_email(email.receiver) self.sender = emailaddress.email
self.smtp_server = emailaddress.smtp_server
self.smtp_port = emailaddress.smtp_port
self.smtp_user = emailaddress.smtp_user
self.smtp_password = emailaddress.smtp_password
def render_email(self, email: Union[Email, CreateEmail]):
signature: str = "Email sent by LNbits SMTP extension."
text = f"{email.message}\n\n{signature}"
html = (
"""
<html>
<head></head>
<body>
<p>"""
+ email.message
+ """</p>
<p>"""
+ signature
+ """</p>
</body>
</html>
"""
)
return text, html
def create_message(self, email: Union[Email, CreateEmail]):
ts = time.time() ts = time.time()
date = formatdate(ts, True) date = formatdate(ts, True)
msg = MIMEMultipart("alternative")
msg = MIMEMultipart("alternative") msg = MIMEMultipart("alternative")
msg["Date"] = date msg["Date"] = date
msg["Subject"] = email.subject msg["Subject"] = email.subject
msg["From"] = emailaddress.email msg["From"] = self.sender
msg["To"] = email.receiver msg["To"] = email.receiver
signature = "Email sent anonymiously by LNbits Sendmail extension." text, html = self.render_email(email)
text = f"""
{email.message}
{signature}
"""
html = f"""
<html>
<head></head>
<body>
<p>{email.message}<p>
<br>
<p>{signature}</p>
</body>
</html>
"""
part1 = MIMEText(text, "plain") part1 = MIMEText(text, "plain")
part2 = MIMEText(html, "html") part2 = MIMEText(html, "html")
msg.attach(part1) msg.attach(part1)
msg.attach(part2) msg.attach(part2)
return msg
async def send_mail(self, receiver, msg: MIMEMultipart):
valid_email(self.sender)
valid_email(receiver)
try: try:
conn = SMTP( conn = SMTP(host=self.smtp_server, port=int(self.smtp_port), timeout=10)
host=emailaddress.smtp_server, port=emailaddress.smtp_port, timeout=10
)
logger.debug("SMTP - connected to smtp server.") logger.debug("SMTP - connected to smtp server.")
# conn.set_debuglevel(True) # conn.set_debuglevel(True)
except: except:
msg = f"SMTP - error connecting to smtp server: {emailaddress.smtp_server}:{emailaddress.smtp_port}." log = f"SMTP - error connecting to smtp server: {self.smtp_server}:{self.smtp_port}."
logger.error(msg) logger.debug(log)
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) raise Exception(log)
try: try:
conn.login(emailaddress.smtp_user, emailaddress.smtp_password) conn.login(self.smtp_user, self.smtp_password)
logger.debug("SMTP - successful login to smtp server.") logger.debug("SMTP - successful login to smtp server.")
except: except:
msg = f"SMTP - error login into smtp {emailaddress.smtp_user}." log = f"SMTP - error login into smtp {self.smtp_user}."
logger.error(msg) logger.error(log)
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) raise Exception(log)
try: try:
conn.sendmail(emailaddress.email, email.receiver, msg.as_string()) conn.sendmail(self.sender, receiver, msg.as_string())
logger.debug("SMTP - successfully send email.") logger.debug("SMTP - successfully send email.")
except socket.error as e: except socket.error as e:
msg = f"SMTP - error sending email: {str(e)}." log = f"SMTP - error sending email: {str(e)}."
logger.error(msg) logger.error(log)
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) raise Exception(log)
finally: finally:
conn.quit() conn.quit()

View file

@ -5,7 +5,7 @@ from loguru import logger
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
from .crud import get_email, get_emailaddress, set_email_paid from .crud import get_email_by_payment_hash, get_emailaddress, set_email_paid
from .smtp import send_mail from .smtp import send_mail
@ -21,7 +21,7 @@ async def on_invoice_paid(payment: Payment) -> None:
if payment.extra.get("tag") != "smtp": if payment.extra.get("tag") != "smtp":
return return
email = await get_email(payment.checking_id) email = await get_email_by_payment_hash(payment.checking_id)
if not email: if not email:
logger.error("SMTP: email can not by fetched") logger.error("SMTP: email can not by fetched")
return return

View file

@ -57,6 +57,14 @@
:href="props.row.displayUrl" :href="props.row.displayUrl"
target="_blank" target="_blank"
></q-btn> ></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="email"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="showEmailDialog(props.row.id)"
></q-btn>
</q-td> </q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props"> <q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }} {{ col.value }}
@ -154,6 +162,42 @@
</q-card> </q-card>
</div> </div>
<q-dialog v-model="emailDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendEmail()" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="emailDialog.data.receiver"
type="text"
label="Receiver"
></q-input>
<q-input
filled
dense
v-model.trim="emailDialog.data.subject"
type="text"
label="Subject"
></q-input>
<q-input
filled
dense
v-model.trim="emailDialog.data.message"
type="textarea"
label="Message "
></q-input>
<div class="row q-mt-lg">
<q-btn
unelevated
color="primary"
:disable="emailDialog.data.receiver == '' || emailDialog.data.subject == '' || emailDialog.data.message == ''"
type="submit"
>Submit</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog v-model="emailaddressDialog.show" position="top"> <q-dialog v-model="emailaddressDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> <q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md"> <q-form @submit="sendFormData" class="q-gutter-md">
@ -316,10 +360,10 @@
emailsTable: { emailsTable: {
columns: [ columns: [
{ {
name: 'emailaddress', name: 'emailaddress_id',
align: 'left', align: 'left',
label: 'From', label: 'From',
field: 'emailaddress' field: 'emailaddress_id'
}, },
{ {
name: 'receiver', name: 'receiver',
@ -350,6 +394,10 @@
rowsPerPage: 10 rowsPerPage: 10
} }
}, },
emailDialog: {
show: false,
data: {}
},
emailaddressDialog: { emailaddressDialog: {
show: false, show: false,
data: {} data: {}
@ -453,6 +501,33 @@
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
sendEmail: function () {
var self = this
var emailaddress = _.findWhere(this.emailaddresses, {
id: self.emailDialog.data.emailaddress_id
})
var wallet = _.findWhere(this.g.user.wallets, {
id: emailaddress.wallet
})
LNbits.api
.request(
'POST',
'/smtp/api/v1/email/' + emailaddress.id + '/send',
wallet.adminkey,
self.emailDialog.data
)
.then(function (response) {
self.emailDialog.show = false
self.emailDialog.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
showEmailDialog: function (emailaddress_id) {
this.emailDialog.data.emailaddress_id = emailaddress_id
this.emailDialog.show = true
},
updateEmailaddressDialog: function (formId) { updateEmailaddressDialog: function (formId) {
var link = _.findWhere(this.emailaddresses, {id: formId}) var link = _.findWhere(this.emailaddresses, {id: formId})
this.emailaddressDialog.data = _.clone(link) this.emailaddressDialog.data = _.clone(link)

View file

@ -4,7 +4,7 @@ from fastapi import Depends, HTTPException, Query
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.core.services import check_transaction_status, create_invoice from lnbits.core.services import check_transaction_status, create_invoice
from lnbits.decorators import WalletTypeInfo, get_key_type from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import smtp_ext from . import smtp_ext
from .crud import ( from .crud import (
@ -13,13 +13,14 @@ from .crud import (
delete_email, delete_email,
delete_emailaddress, delete_emailaddress,
get_email, get_email,
get_email_by_payment_hash,
get_emailaddress, get_emailaddress,
get_emailaddresses, get_emailaddresses,
get_emails, get_emails,
update_emailaddress, update_emailaddress,
) )
from .models import CreateEmail, CreateEmailaddress from .models import CreateEmail, CreateEmailaddress
from .smtp import valid_email from .smtp import send_mail, valid_email
## EMAILS ## EMAILS
@ -37,13 +38,14 @@ async def api_email(
@smtp_ext.get("/api/v1/email/{payment_hash}") @smtp_ext.get("/api/v1/email/{payment_hash}")
async def api_smtp_send_email(payment_hash): async def api_smtp_send_email(payment_hash):
email = await get_email(payment_hash) email = await get_email_by_payment_hash(payment_hash)
if not email: if not email:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="paymenthash is wrong" status_code=HTTPStatus.BAD_REQUEST, detail="paymenthash is wrong"
) )
emailaddress = await get_emailaddress(email.emailaddress_id) emailaddress = await get_emailaddress(email.emailaddress_id)
assert emailaddress
try: try:
status = await check_transaction_status(email.wallet, payment_hash) status = await check_transaction_status(email.wallet, payment_hash)
@ -59,11 +61,9 @@ async def api_smtp_send_email(payment_hash):
@smtp_ext.post("/api/v1/email/{emailaddress_id}") @smtp_ext.post("/api/v1/email/{emailaddress_id}")
async def api_smtp_make_email(emailaddress_id, data: CreateEmail): async def api_smtp_make_email(emailaddress_id, data: CreateEmail):
valid_email(data.receiver) valid_email(data.receiver)
emailaddress = await get_emailaddress(emailaddress_id) emailaddress = await get_emailaddress(emailaddress_id)
# If the request is coming for the non-existant emailaddress
if not emailaddress: if not emailaddress:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
@ -94,6 +94,26 @@ async def api_smtp_make_email(emailaddress_id, data: CreateEmail):
return {"payment_hash": payment_hash, "payment_request": payment_request} return {"payment_hash": payment_hash, "payment_request": payment_request}
@smtp_ext.post(
"/api/v1/email/{emailaddress_id}/send", dependencies=[Depends(require_admin_key)]
)
async def api_smtp_make_email_send(emailaddress_id, data: CreateEmail):
valid_email(data.receiver)
emailaddress = await get_emailaddress(emailaddress_id)
if not emailaddress:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="Emailaddress address does not exist.",
)
email = await create_email(wallet=emailaddress.wallet, data=data)
if not email:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Email could not be fetched."
)
await send_mail(emailaddress, email)
return {"sent": True}
@smtp_ext.delete("/api/v1/email/{email_id}") @smtp_ext.delete("/api/v1/email/{email_id}")
async def api_email_delete(email_id, g: WalletTypeInfo = Depends(get_key_type)): async def api_email_delete(email_id, g: WalletTypeInfo = Depends(get_key_type)):
email = await get_email(email_id) email = await get_email(email_id)

View file

@ -14,7 +14,7 @@
<a <a
class="text-secondary" class="text-secondary"
href="https://github.com/lnbits/lnbits-legend/tree/main/lnbits/extensions/subdomains" href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/subdomains"
>More details</a >More details</a
> >
<br /> <br />

View file

@ -8,7 +8,7 @@ from typing import AsyncGenerator, Dict, Optional
import httpx import httpx
from loguru import logger from loguru import logger
# TODO: https://github.com/lnbits/lnbits-legend/issues/764 # TODO: https://github.com/lnbits/lnbits/issues/764
# mypy https://github.com/aaugustin/websockets/issues/940 # mypy https://github.com/aaugustin/websockets/issues/940
from websockets import connect # type: ignore from websockets import connect # type: ignore
from websockets.exceptions import ( from websockets.exceptions import (

340
poetry.lock generated
View file

@ -81,21 +81,22 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "22.1.0" version = "22.2.0"
description = "Classes Without Boilerplate" description = "Classes Without Boilerplate"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.6"
files = [ files = [
{file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
{file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
] ]
[package.extras] [package.extras]
dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] dev = ["attrs[docs,tests]"]
tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] tests = ["attrs[tests-no-zope]", "zope.interface"]
tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
[[package]] [[package]]
name = "base58" name = "base58"
@ -176,61 +177,65 @@ uvloop = ["uvloop (>=0.15.2)"]
[[package]] [[package]]
name = "cashu" name = "cashu"
version = "0.6.0" version = "0.8.2"
description = "Ecash wallet and mint with Bitcoin Lightning support" description = "Ecash wallet and mint for Bitcoin Lightning"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "cashu-0.6.0-py3-none-any.whl", hash = "sha256:54096af145643aab45943b235f95a3357b0ec697835c1411e66523049ffb81f6"}, {file = "cashu-0.8.2-py3-none-any.whl", hash = "sha256:53893911763c424255bc7112aaba356e0a265e850d770338c70b2d282f67bf99"},
{file = "cashu-0.6.0.tar.gz", hash = "sha256:503a90c4ca8d25d0b2c3f78a11b163c32902a726ea5b58e5337dc00eca8e96ad"}, {file = "cashu-0.8.2.tar.gz", hash = "sha256:4cdd34a50d14960d2dcc6f5b6120f462688090dd084387860f7a59d16aa102ff"},
] ]
[package.dependencies] [package.dependencies]
anyio = {version = "3.6.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} anyio = {version = "3.6.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
attrs = {version = "22.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} attrs = {version = "22.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
bech32 = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} bech32 = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
bitstring = {version = "3.1.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} bitstring = {version = "3.1.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
certifi = {version = "2022.9.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} certifi = {version = "2022.12.7", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
cffi = {version = "1.15.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} cffi = {version = "1.15.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
charset-normalizer = {version = "2.0.12", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} charset-normalizer = {version = "2.0.12", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
click = {version = "8.0.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} click = {version = "8.0.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
colorama = {version = "0.4.5", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and platform_system == \"Windows\" or python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""} colorama = {version = "0.4.6", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and platform_system == \"Windows\" or python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
cryptography = {version = "36.0.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
ecdsa = {version = "0.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} ecdsa = {version = "0.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
environs = {version = "9.5.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} environs = {version = "9.5.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
exceptiongroup = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}
fastapi = {version = "0.83.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} fastapi = {version = "0.83.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
h11 = {version = "0.12.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} h11 = {version = "0.12.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
idna = {version = "3.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} idna = {version = "3.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
importlib-metadata = {version = "5.0.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""} importlib-metadata = {version = "5.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
iniconfig = {version = "1.1.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} iniconfig = {version = "2.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
loguru = {version = "0.6.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} loguru = {version = "0.6.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
marshmallow = {version = "3.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} marshmallow = {version = "3.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
outcome = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} outcome = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
packaging = {version = "21.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} packaging = {version = "23.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pluggy = {version = "1.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} pluggy = {version = "1.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
py = {version = "1.11.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pycparser = {version = "2.21", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} pycparser = {version = "2.21", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pydantic = {version = "1.10.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} pycryptodomex = {version = "3.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pyparsing = {version = "3.0.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} pydantic = {version = "1.10.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pysocks = {version = "1.7.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} pysocks = {version = "1.7.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pytest = {version = "7.1.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} pytest = {version = "7.2.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pytest-asyncio = {version = "0.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} pytest-asyncio = {version = "0.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
python-bitcoinlib = {version = "0.11.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} python-bitcoinlib = {version = "0.11.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
python-dotenv = {version = "0.21.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} python-dotenv = {version = "0.21.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
represent = {version = "1.6.0.post0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} represent = {version = "1.6.0.post0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
requests = {version = "2.27.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} requests = {version = "2.27.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
secp256k1 = {version = "0.14.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} secp256k1 = {version = "0.14.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
setuptools = {version = "65.7.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
six = {version = "1.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} six = {version = "1.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
sniffio = {version = "1.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} sniffio = {version = "1.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
sqlalchemy = {version = "1.3.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} sqlalchemy = {version = "1.3.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
sqlalchemy-aio = {version = "0.17.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} sqlalchemy-aio = {version = "0.17.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
starlette = {version = "0.19.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} starlette = {version = "0.19.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
tomli = {version = "2.0.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} tomli = {version = "2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}
typing-extensions = {version = "4.4.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} typing-extensions = {version = "4.4.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
urllib3 = {version = "1.26.12", markers = "python_version >= \"3.7\" and python_version < \"4\""} urllib3 = {version = "1.26.14", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
uvicorn = {version = "0.18.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} uvicorn = {version = "0.18.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
websocket-client = {version = "1.3.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
wheel = {version = "0.38.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
win32-setctime = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""} win32-setctime = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
zipp = {version = "3.9.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""} zipp = {version = "3.11.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
[[package]] [[package]]
name = "cerberus" name = "cerberus"
@ -248,14 +253,14 @@ setuptools = "*"
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2022.9.24" version = "2022.12.7"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
{file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
] ]
[[package]] [[package]]
@ -416,14 +421,14 @@ cffi = ">=1.3.0"
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.5" version = "0.4.6"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [ files = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
] ]
[[package]] [[package]]
@ -527,10 +532,10 @@ files = [
cffi = ">=1.12" cffi = ">=1.12"
[package.extras] [package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools_rust (>=0.11.4)"] sdist = ["setuptools-rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"] 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"] test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
@ -599,6 +604,21 @@ django = ["dj-database-url", "dj-email-url", "django-cache-url"]
lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"]
tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"]
[[package]]
name = "exceptiongroup"
version = "1.1.0"
description = "Backport of PEP 654 (exception groups)"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
{file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.83.0" version = "0.83.0"
@ -798,14 +818,14 @@ files = [
[[package]] [[package]]
name = "importlib-metadata" name = "importlib-metadata"
version = "5.0.0" version = "5.2.0"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"},
{file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"},
] ]
[package.dependencies] [package.dependencies]
@ -813,20 +833,20 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5" zipp = ">=0.5"
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"] perf = ["ipython"]
testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
[[package]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "1.1.1" version = "2.0.0"
description = "iniconfig: brain-dead simple config-ini parsing" description = "brain-dead simple config-ini parsing"
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = ">=3.7"
files = [ files = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
] ]
[[package]] [[package]]
@ -982,23 +1002,23 @@ files = [
[[package]] [[package]]
name = "marshmallow" name = "marshmallow"
version = "3.18.0" version = "3.19.0"
description = "A lightweight library for converting complex datatypes to and from native Python datatypes." description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "marshmallow-3.18.0-py3-none-any.whl", hash = "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104"}, {file = "marshmallow-3.19.0-py3-none-any.whl", hash = "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b"},
{file = "marshmallow-3.18.0.tar.gz", hash = "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7"}, {file = "marshmallow-3.19.0.tar.gz", hash = "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78"},
] ]
[package.dependencies] [package.dependencies]
packaging = ">=17.0" packaging = ">=17.0"
[package.extras] [package.extras]
dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.10.25)", "mypy (==0.990)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"]
docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.1.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.3.0)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"]
lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)"] lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.10.25)", "mypy (==0.990)", "pre-commit (>=2.4,<3.0)"]
tests = ["pytest", "pytz", "simplejson"] tests = ["pytest", "pytz", "simplejson"]
[[package]] [[package]]
@ -1091,19 +1111,16 @@ attrs = ">=19.2.0"
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "21.3" version = "23.0"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.7"
files = [ files = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
] ]
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]] [[package]]
name = "pathlib2" name = "pathlib2"
version = "2.3.7.post1" version = "2.3.7.post1"
@ -1239,18 +1256,6 @@ files = [
{file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"}, {file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"},
] ]
[[package]]
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
[[package]] [[package]]
name = "pycparser" name = "pycparser"
version = "2.21" version = "2.21"
@ -1265,89 +1270,88 @@ files = [
[[package]] [[package]]
name = "pycryptodomex" name = "pycryptodomex"
version = "3.14.1" version = "3.16.0"
description = "Cryptographic library for Python" description = "Cryptographic library for Python"
category = "main" category = "main"
optional = false 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.*"
files = [ files = [
{file = "pycryptodomex-3.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca88f2f7020002638276439a01ffbb0355634907d1aa5ca91f3dc0c2e44e8f3b"}, {file = "pycryptodomex-3.16.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b3d04c00d777c36972b539fb79958790126847d84ec0129fce1efef250bfe3ce"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:8536bc08d130cae6dcba1ea689f2913dfd332d06113904d171f2f56da6228e89"}, {file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e5a670919076b71522c7d567a9043f66f14b202414a63c3a078b5831ae342c03"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:406ec8cfe0c098fadb18d597dc2ee6de4428d640c0ccafa453f3d9b2e58d29e2"}, {file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ce338a9703f54b2305a408fc9890eb966b727ce72b69f225898bb4e9d9ed3f1f"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:da8db8374295fb532b4b0c467e66800ef17d100e4d5faa2bbbd6df35502da125"}, {file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:a1c0ae7123448ecb034c75c713189cb00ebe2d415b11682865b6c54d200d9c93"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:d709572d64825d8d59ea112e11cc7faf6007f294e9951324b7574af4251e4de8"}, {file = "pycryptodomex-3.16.0-cp27-cp27m-win32.whl", hash = "sha256:8851585ff19871e5d69e1790f4ca5f6fd1699d6b8b14413b472a4c0dbc7ea780"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-win32.whl", hash = "sha256:3da13c2535b7aea94cc2a6d1b1b37746814c74b6e80790daddd55ca5c120a489"}, {file = "pycryptodomex-3.16.0-cp27-cp27m-win_amd64.whl", hash = "sha256:8dd2d9e3c617d0712ed781a77efd84ea579e76c5f9b2a4bc0b684ebeddf868b2"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:298c00ea41a81a491d5b244d295d18369e5aac4b61b77b2de5b249ca61cd6659"}, {file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2ad9bb86b355b6104796567dd44c215b3dc953ef2fae5e0bdfb8516731df92cf"},
{file = "pycryptodomex-3.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:77931df40bb5ce5e13f4de2bfc982b2ddc0198971fbd947776c8bb5050896eb2"}, {file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e25a2f5667d91795f9417cb856f6df724ccdb0cdd5cbadb212ee9bf43946e9f8"},
{file = "pycryptodomex-3.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:c5dd3ffa663c982d7f1be9eb494a8924f6d40e2e2f7d1d27384cfab1b2ac0662"}, {file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b0789a8490114a2936ed77c87792cfe77582c829cb43a6d86ede0f9624ba8aa3"},
{file = "pycryptodomex-3.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2aa887683eee493e015545bd69d3d21ac8d5ad582674ec98f4af84511e353e45"}, {file = "pycryptodomex-3.16.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0da835af786fdd1c9930994c78b23e88d816dc3f99aa977284a21bbc26d19735"},
{file = "pycryptodomex-3.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8085bd0ad2034352eee4d4f3e2da985c2749cb7344b939f4d95ead38c2520859"}, {file = "pycryptodomex-3.16.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:22aed0868622d95179217c298e37ed7410025c7b29dac236d3230617d1e4ed56"},
{file = "pycryptodomex-3.14.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e95a4a6c54d27a84a4624d2af8bb9ee178111604653194ca6880c98dcad92f48"}, {file = "pycryptodomex-3.16.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1619087fb5b31510b0b0b058a54f001a5ffd91e6ffee220d9913064519c6a69d"},
{file = "pycryptodomex-3.14.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:a4d412eba5679ede84b41dbe48b1bed8f33131ab9db06c238a235334733acc5e"}, {file = "pycryptodomex-3.16.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:70288d9bfe16b2fd0d20b6c365db614428f1bcde7b20d56e74cf88ade905d9eb"},
{file = "pycryptodomex-3.14.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:d2cce1c82a7845d7e2e8a0956c6b7ed3f1661c9acf18eb120fc71e098ab5c6fe"}, {file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7993d26dae4d83b8f4ce605bb0aecb8bee330bb3c95475ef06f3694403621e71"},
{file = "pycryptodomex-3.14.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:f75009715dcf4a3d680c2338ab19dac5498f8121173a929872950f4fb3a48fbf"}, {file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1cda60207be8c1cf0b84b9138f9e3ca29335013d2b690774a5e94678ff29659a"},
{file = "pycryptodomex-3.14.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:1ca8e1b4c62038bb2da55451385246f51f412c5f5eabd64812c01766a5989b4a"}, {file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:04610536921c1ec7adba158ef570348550c9f3a40bc24be9f8da2ef7ab387981"},
{file = "pycryptodomex-3.14.1-cp35-abi3-win32.whl", hash = "sha256:ee835def05622e0c8b1435a906491760a43d0c462f065ec9143ec4b8d79f8bff"}, {file = "pycryptodomex-3.16.0-cp35-abi3-win32.whl", hash = "sha256:daa67f5ebb6fbf1ee9c90decaa06ca7fc88a548864e5e484d52b0920a57fe8a5"},
{file = "pycryptodomex-3.14.1-cp35-abi3-win_amd64.whl", hash = "sha256:b5a185ae79f899b01ca49f365bdf15a45d78d9856f09b0de1a41b92afce1a07f"}, {file = "pycryptodomex-3.16.0-cp35-abi3-win_amd64.whl", hash = "sha256:231dc8008cbdd1ae0e34645d4523da2dbc7a88c325f0d4a59635a86ee25b41dd"},
{file = "pycryptodomex-3.14.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:797a36bd1f69df9e2798e33edb4bd04e5a30478efc08f9428c087f17f65a7045"}, {file = "pycryptodomex-3.16.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:4dbbe18cc232b5980c7633972ae5417d0df76fe89e7db246eefd17ef4d8e6d7a"},
{file = "pycryptodomex-3.14.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:aebecde2adc4a6847094d3bd6a8a9538ef3438a5ea84ac1983fcb167db614461"}, {file = "pycryptodomex-3.16.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:893f8a97d533c66cc3a56e60dd3ed40a3494ddb4aafa7e026429a08772f8a849"},
{file = "pycryptodomex-3.14.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:f8524b8bc89470cec7ac51734907818d3620fb1637f8f8b542d650ebec42a126"}, {file = "pycryptodomex-3.16.0-pp27-pypy_73-win32.whl", hash = "sha256:6a465e4f856d2a4f2a311807030c89166529ccf7ccc65bef398de045d49144b6"},
{file = "pycryptodomex-3.14.1-pp27-pypy_73-win32.whl", hash = "sha256:4d0db8df9ffae36f416897ad184608d9d7a8c2b46c4612c6bc759b26c073f750"}, {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba57ac7861fd2c837cdb33daf822f2a052ff57dd769a2107807f52a36d0e8d38"},
{file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b276cc4deb4a80f9dfd47a41ebb464b1fe91efd8b1b8620cf5ccf8b824b850d6"}, {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f2b971a7b877348a27dcfd0e772a0343fb818df00b74078e91c008632284137d"},
{file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:e36c7e3b5382cd5669cf199c4a04a0279a43b2a3bdd77627e9b89778ac9ec08c"}, {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e2453162f473c1eae4826eb10cd7bce19b5facac86d17fb5f29a570fde145abd"},
{file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c4d8977ccda886d88dc3ca789de2f1adc714df912ff3934b3d0a3f3d777deafb"}, {file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0ba28aa97cdd3ff5ed1a4f2b7f5cd04e721166bd75bd2b929e2734433882b583"},
{file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:530756d2faa40af4c1f74123e1d889bd07feae45bac2fd32f259a35f7aa74151"}, {file = "pycryptodomex-3.16.0.tar.gz", hash = "sha256:e9ba9d8ed638733c9e95664470b71d624a6def149e2db6cc52c1aca5a6a2df1d"},
{file = "pycryptodomex-3.14.1.tar.gz", hash = "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"},
] ]
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "1.10.2" version = "1.10.4"
description = "Data validation and settings management using python type hints" description = "Data validation and settings management using python type hints"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, {file = "pydantic-1.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5635de53e6686fe7a44b5cf25fcc419a0d5e5c1a1efe73d49d48fe7586db854"},
{file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, {file = "pydantic-1.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6dc1cc241440ed7ca9ab59d9929075445da6b7c94ced281b3dd4cfe6c8cff817"},
{file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bdeb10d2db0f288e71d49c9cefa609bca271720ecd0c58009bd7504a0c464c"},
{file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cec42b95dbb500a1f7120bdf95c401f6abb616bbe8785ef09887306792e66e"},
{file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8775d4ef5e7299a2f4699501077a0defdaac5b6c4321173bcb0f3c496fbadf85"},
{file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:572066051eeac73d23f95ba9a71349c42a3e05999d0ee1572b7860235b850cc6"},
{file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, {file = "pydantic-1.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:7feb6a2d401f4d6863050f58325b8d99c1e56f4512d98b11ac64ad1751dc647d"},
{file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, {file = "pydantic-1.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39f4a73e5342b25c2959529f07f026ef58147249f9b7431e1ba8414a36761f53"},
{file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, {file = "pydantic-1.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:983e720704431a6573d626b00662eb78a07148c9115129f9b4351091ec95ecc3"},
{file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d52162fe6b2b55964fbb0af2ee58e99791a3138588c482572bb6087953113a"},
{file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdf8d759ef326962b4678d89e275ffc55b7ce59d917d9f72233762061fd04a2d"},
{file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05a81b006be15655b2a1bae5faa4280cf7c81d0e09fcb49b342ebf826abe5a72"},
{file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d88c4c0e5c5dfd05092a4b271282ef0588e5f4aaf345778056fc5259ba098857"},
{file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, {file = "pydantic-1.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:6a05a9db1ef5be0fe63e988f9617ca2551013f55000289c671f71ec16f4985e3"},
{file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, {file = "pydantic-1.10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:887ca463c3bc47103c123bc06919c86720e80e1214aab79e9b779cda0ff92a00"},
{file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdf88ab63c3ee282c76d652fc86518aacb737ff35796023fae56a65ced1a5978"},
{file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48f1953c4a1d9bd0b5167ac50da9a79f6072c63c4cef4cf2a3736994903583e"},
{file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a9f2de23bec87ff306aef658384b02aa7c32389766af3c5dee9ce33e80222dfa"},
{file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd8702c5142afda03dc2b1ee6bc358b62b3735b2cce53fc77b31ca9f728e4bc8"},
{file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, {file = "pydantic-1.10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6e7124d6855b2780611d9f5e1e145e86667eaa3bd9459192c8dc1a097f5e9903"},
{file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, {file = "pydantic-1.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b53e1d41e97063d51a02821b80538053ee4608b9a181c1005441f1673c55423"},
{file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, {file = "pydantic-1.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55b1625899acd33229c4352ce0ae54038529b412bd51c4915349b49ca575258f"},
{file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301d626a59edbe5dfb48fcae245896379a450d04baeed50ef40d8199f2733b06"},
{file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6f9d649892a6f54a39ed56b8dfd5e08b5f3be5f893da430bed76975f3735d15"},
{file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7b5a3821225f5c43496c324b0d6875fde910a1c2933d726a743ce328fbb2a8c"},
{file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f2f7eb6273dd12472d7f218e1fef6f7c7c2f00ac2e1ecde4db8824c457300416"},
{file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, {file = "pydantic-1.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:4b05697738e7d2040696b0a66d9f0a10bec0efa1883ca75ee9e55baf511909d6"},
{file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, {file = "pydantic-1.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9a6747cac06c2beb466064dda999a13176b23535e4c496c9d48e6406f92d42d"},
{file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, {file = "pydantic-1.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb992a1ef739cc7b543576337bebfc62c0e6567434e522e97291b251a41dad7f"},
{file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:990406d226dea0e8f25f643b370224771878142155b879784ce89f633541a024"},
{file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e82a6d37a95e0b1b42b82ab340ada3963aea1317fd7f888bb6b9dfbf4fff57c"},
{file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9193d4f4ee8feca58bc56c8306bcb820f5c7905fd919e0750acdeeeef0615b28"},
{file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b3ce5f16deb45c472dde1a0ee05619298c864a20cded09c4edd820e1454129f"},
{file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, {file = "pydantic-1.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9cbdc268a62d9a98c56e2452d6c41c0263d64a2009aac69246486f01b4f594c4"},
{file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, {file = "pydantic-1.10.4-py3-none-any.whl", hash = "sha256:4948f264678c703f3877d1c8877c4e3b2e12e549c57795107f08cf70c6ec7774"},
{file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, {file = "pydantic-1.10.4.tar.gz", hash = "sha256:b9a3859f24eb4e097502a3be1fb4b2abb79b6103dd9e2e0edb70613a4459a648"},
] ]
[package.dependencies] [package.dependencies]
typing-extensions = ">=4.1.0" typing-extensions = ">=4.2.0"
[package.extras] [package.extras]
dotenv = ["python-dotenv (>=0.10.4)"] dotenv = ["python-dotenv (>=0.10.4)"]
@ -1400,21 +1404,6 @@ coincurve = ">=17.0.0,<18.0.0"
cryptography = ">=36.0.1,<37.0.0" cryptography = ">=36.0.1,<37.0.0"
PySocks = ">=1.7.1,<2.0.0" PySocks = ">=1.7.1,<2.0.0"
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "main"
optional = false
python-versions = ">=3.6.8"
files = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]] [[package]]
name = "pypng" name = "pypng"
version = "0.0.21" version = "0.0.21"
@ -1472,25 +1461,25 @@ files = [
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "7.1.3" version = "7.2.1"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"},
{file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"},
] ]
[package.dependencies] [package.dependencies]
attrs = ">=19.2.0" attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""} colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*" iniconfig = "*"
packaging = "*" packaging = "*"
pluggy = ">=0.12,<2.0" pluggy = ">=0.12,<2.0"
py = ">=1.8.2" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
tomli = ">=1.0.0"
[package.extras] [package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
@ -1695,18 +1684,18 @@ cffi = ">=1.3.0"
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "65.6.3" version = "65.7.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, {file = "setuptools-65.7.0-py3-none-any.whl", hash = "sha256:8ab4f1dbf2b4a65f7eec5ad0c620e84c34111a68d3349833494b9088212214dd"},
{file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, {file = "setuptools-65.7.0.tar.gz", hash = "sha256:4d3c92fac8f1118bb77a22181355e29c239cabfe2b9effdaa665c66b711136d7"},
] ]
[package.extras] [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"] 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-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 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"]
@ -1795,7 +1784,7 @@ 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"]
@ -1926,14 +1915,14 @@ files = [
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "1.26.12" version = "1.26.14"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
files = [ files = [
{file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"},
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"},
] ]
[package.extras] [package.extras]
@ -2056,6 +2045,21 @@ files = [
{file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"}, {file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"},
] ]
[[package]]
name = "wheel"
version = "0.38.4"
description = "A built-package format for Python"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"},
{file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"},
]
[package.extras]
test = ["pytest (>=3.0.0)"]
[[package]] [[package]]
name = "win32-setctime" name = "win32-setctime"
version = "1.1.0" version = "1.1.0"
@ -2073,14 +2077,14 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]] [[package]]
name = "zipp" name = "zipp"
version = "3.9.0" version = "3.11.0"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"}, {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"},
{file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"}, {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"},
] ]
[package.extras] [package.extras]
@ -2090,4 +2094,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
content-hash = "73e1443abc1eed24639a5297a66b2eb16e80491f8849b8008e065f153de215c7" content-hash = "9daf94dd600a7e23dcefcc8752fae1694e0084e56553dc578a63272776a8fe53"

View file

@ -1,8 +1,8 @@
[tool.poetry] [tool.poetry]
name = "lnbits" name = "lnbits"
version = "0.1.0" version = "0.9.5.3"
description = "" description = "LNbits, free and open-source Lightning wallet and accounts system."
authors = ["matthewcroughan <matt@croughan.sh>"] authors = ["Alan Bits <alan@lnbits.com>"]
[tool.poetry.build] [tool.poetry.build]
generate-setup-file = false generate-setup-file = false
@ -12,10 +12,10 @@ script = "build.py"
python = "^3.10 | ^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 = "22.1.0" attrs = "22.2.0"
bech32 = "1.2.0" bech32 = "1.2.0"
bitstring = "3.1.9" bitstring = "3.1.9"
certifi = "2022.9.24" certifi = "2022.12.7"
charset-normalizer = "2.0.12" charset-normalizer = "2.0.12"
click = "8.0.4" click = "8.0.4"
ecdsa = "0.18.0" ecdsa = "0.18.0"
@ -26,15 +26,15 @@ httpcore = "0.15.0"
httptools = "0.4.0" httptools = "0.4.0"
httpx = "0.23.0" httpx = "0.23.0"
idna = "3.4" idna = "3.4"
importlib-metadata = "5.0.0" importlib-metadata = "5.2.0"
jinja2 = "3.0.1" jinja2 = "3.0.1"
lnurl = "0.3.6" lnurl = "0.3.6"
markupsafe = "2.0.1" markupsafe = "2.0.1"
marshmallow = "3.18.0" marshmallow = "3.19.0"
outcome = "1.2.0" outcome = "1.2.0"
psycopg2-binary = "2.9.1" psycopg2-binary = "2.9.1"
pycryptodomex = "3.14.1" pycryptodomex = "3.16.0"
pydantic = "1.10.2" pydantic = "1.10.4"
pypng = "0.0.21" pypng = "0.0.21"
pyqrcode = "1.2.1" pyqrcode = "1.2.1"
pyScss = "1.4.0" pyScss = "1.4.0"
@ -53,7 +53,7 @@ uvicorn = "0.18.3"
uvloop = "0.16.0" uvloop = "0.16.0"
watchgod = "0.7" watchgod = "0.7"
websockets = "10.0" websockets = "10.0"
zipp = "3.9.0" zipp = "3.11.0"
loguru = "0.6.0" loguru = "0.6.0"
cffi = "1.15.1" cffi = "1.15.1"
websocket-client = "1.3.3" websocket-client = "1.3.3"
@ -62,7 +62,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4" Cerberus = "^1.3.4"
async-timeout = "^4.0.2" async-timeout = "^4.0.2"
pyln-client = "0.11.1" pyln-client = "0.11.1"
cashu = "^0.6.0" cashu = "0.8.2"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
@ -88,8 +88,7 @@ profile = "black"
[tool.mypy] [tool.mypy]
files = "lnbits" files = "lnbits"
exclude = """(?x)( exclude = """(?x)(
^lnbits/extensions/bleskomat. ^lnbits/extensions/boltz.
| ^lnbits/extensions/boltz.
| ^lnbits/wallets/lnd_grpc_files. | ^lnbits/wallets/lnd_grpc_files.
)""" )"""

View file

@ -3,57 +3,56 @@ anyio==3.6.2 ; python_version >= "3.7" and python_version < "4.0"
asgiref==3.4.1 ; python_version >= "3.7" and python_version < "4.0" asgiref==3.4.1 ; python_version >= "3.7" and python_version < "4.0"
asn1crypto==1.5.1 ; python_version >= "3.7" and python_version < "4.0" asn1crypto==1.5.1 ; python_version >= "3.7" and python_version < "4.0"
async-timeout==4.0.2 ; python_version >= "3.7" and python_version < "4.0" async-timeout==4.0.2 ; python_version >= "3.7" and python_version < "4.0"
attrs==22.1.0 ; python_version >= "3.7" and python_version < "4.0" attrs==22.2.0 ; python_version >= "3.7" and python_version < "4.0"
base58==2.1.1 ; python_version >= "3.7" and python_version < "4.0" base58==2.1.1 ; python_version >= "3.7" and python_version < "4.0"
bech32==1.2.0 ; python_version >= "3.7" and python_version < "4.0" bech32==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
bitstring==3.1.9 ; python_version >= "3.7" and python_version < "4.0" bitstring==3.1.9 ; python_version >= "3.7" and python_version < "4.0"
cashu==0.6.0 ; python_version >= "3.7" and python_version < "4.0" cashu==0.8.2 ; python_version >= "3.7" and python_version < "4.0"
cerberus==1.3.4 ; python_version >= "3.7" and python_version < "4.0" cerberus==1.3.4 ; python_version >= "3.7" and python_version < "4.0"
certifi==2022.9.24 ; python_version >= "3.7" and python_version < "4.0" certifi==2022.12.7 ; python_version >= "3.7" and python_version < "4.0"
cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0" cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0"
charset-normalizer==2.0.12 ; python_version >= "3.7" and python_version < "4.0" charset-normalizer==2.0.12 ; python_version >= "3.7" and python_version < "4.0"
click==8.0.4 ; python_version >= "3.7" and python_version < "4.0" click==8.0.4 ; python_version >= "3.7" and python_version < "4.0"
coincurve==17.0.0 ; python_version >= "3.7" and python_version < "4.0" coincurve==17.0.0 ; python_version >= "3.7" and python_version < "4.0"
colorama==0.4.5 ; python_version >= "3.7" and python_version < "4.0" and platform_system == "Windows" or python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" colorama==0.4.6 ; python_version >= "3.7" and python_version < "4.0" and platform_system == "Windows" or python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32"
cryptography==36.0.2 ; python_version >= "3.7" and python_version < "4.0" cryptography==36.0.2 ; python_version >= "3.7" and python_version < "4.0"
ecdsa==0.18.0 ; python_version >= "3.7" and python_version < "4.0" ecdsa==0.18.0 ; python_version >= "3.7" and python_version < "4.0"
embit==0.4.9 ; python_version >= "3.7" and python_version < "4.0" embit==0.4.9 ; python_version >= "3.7" and python_version < "4.0"
enum34==1.1.10 ; python_version >= "3.7" and python_version < "4.0" enum34==1.1.10 ; python_version >= "3.7" and python_version < "4.0"
environs==9.5.0 ; python_version >= "3.7" and python_version < "4.0" environs==9.5.0 ; python_version >= "3.7" and python_version < "4.0"
exceptiongroup==1.1.0 ; python_version >= "3.7" and python_version < "3.11"
fastapi==0.83.0 ; python_version >= "3.7" and python_version < "4.0" fastapi==0.83.0 ; python_version >= "3.7" and python_version < "4.0"
grpcio==1.50.0 ; python_version >= "3.7" and python_version < "4.0" grpcio==1.51.1 ; python_version >= "3.7" and python_version < "4.0"
h11==0.12.0 ; python_version >= "3.7" and python_version < "4.0" h11==0.12.0 ; python_version >= "3.7" and python_version < "4.0"
httpcore==0.15.0 ; python_version >= "3.7" and python_version < "4.0" httpcore==0.15.0 ; python_version >= "3.7" and python_version < "4.0"
httptools==0.4.0 ; python_version >= "3.7" and python_version < "4.0" httptools==0.4.0 ; python_version >= "3.7" and python_version < "4.0"
httpx==0.23.0 ; python_version >= "3.7" and python_version < "4.0" httpx==0.23.0 ; python_version >= "3.7" and python_version < "4.0"
idna==3.4 ; python_version >= "3.7" and python_version < "4.0" idna==3.4 ; python_version >= "3.7" and python_version < "4.0"
importlib-metadata==5.0.0 ; python_version >= "3.7" and python_version < "4.0" importlib-metadata==5.2.0 ; python_version >= "3.7" and python_version < "4.0"
iniconfig==1.1.1 ; python_version >= "3.7" and python_version < "4.0" iniconfig==2.0.0 ; python_version >= "3.7" and python_version < "4.0"
jinja2==3.0.1 ; python_version >= "3.7" and python_version < "4.0" jinja2==3.0.1 ; python_version >= "3.7" and python_version < "4.0"
lnurl==0.3.6 ; python_version >= "3.7" and python_version < "4.0" lnurl==0.3.6 ; python_version >= "3.7" and python_version < "4.0"
loguru==0.6.0 ; python_version >= "3.7" and python_version < "4.0" loguru==0.6.0 ; python_version >= "3.7" and python_version < "4.0"
markupsafe==2.0.1 ; python_version >= "3.7" and python_version < "4.0" markupsafe==2.0.1 ; python_version >= "3.7" and python_version < "4.0"
marshmallow==3.18.0 ; python_version >= "3.7" and python_version < "4.0" marshmallow==3.19.0 ; python_version >= "3.7" and python_version < "4.0"
outcome==1.2.0 ; python_version >= "3.7" and python_version < "4.0" outcome==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
packaging==21.3 ; python_version >= "3.7" and python_version < "4.0" packaging==23.0 ; python_version >= "3.7" and python_version < "4.0"
pathlib2==2.3.7.post1 ; python_version >= "3.7" and python_version < "4.0" pathlib2==2.3.7.post1 ; python_version >= "3.7" and python_version < "4.0"
pluggy==1.0.0 ; python_version >= "3.7" and python_version < "4.0" pluggy==1.0.0 ; python_version >= "3.7" and python_version < "4.0"
protobuf==4.21.9 ; python_version >= "3.7" and python_version < "4.0" protobuf==4.21.12 ; python_version >= "3.7" and python_version < "4.0"
psycopg2-binary==2.9.1 ; python_version >= "3.7" and python_version < "4.0" psycopg2-binary==2.9.1 ; python_version >= "3.7" and python_version < "4.0"
py==1.11.0 ; python_version >= "3.7" and python_version < "4.0"
pycparser==2.21 ; python_version >= "3.7" and python_version < "4.0" pycparser==2.21 ; python_version >= "3.7" and python_version < "4.0"
pycryptodomex==3.14.1 ; python_version >= "3.7" and python_version < "4.0" pycryptodomex==3.16.0 ; python_version >= "3.7" and python_version < "4.0"
pydantic==1.10.2 ; python_version >= "3.7" and python_version < "4.0" pydantic==1.10.4 ; python_version >= "3.7" and python_version < "4.0"
pyln-bolt7==1.0.246 ; python_version >= "3.7" and python_version < "4.0" pyln-bolt7==1.0.246 ; python_version >= "3.7" and python_version < "4.0"
pyln-client==0.11.1 ; python_version >= "3.7" and python_version < "4.0" pyln-client==0.11.1 ; python_version >= "3.7" and python_version < "4.0"
pyln-proto==0.11.1 ; python_version >= "3.7" and python_version < "4.0" pyln-proto==0.11.1 ; python_version >= "3.7" and python_version < "4.0"
pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0"
pypng==0.0.21 ; python_version >= "3.7" and python_version < "4.0" pypng==0.0.21 ; python_version >= "3.7" and python_version < "4.0"
pyqrcode==1.2.1 ; python_version >= "3.7" and python_version < "4.0" pyqrcode==1.2.1 ; python_version >= "3.7" and python_version < "4.0"
pyscss==1.4.0 ; python_version >= "3.7" and python_version < "4.0" pyscss==1.4.0 ; python_version >= "3.7" and python_version < "4.0"
pysocks==1.7.1 ; python_version >= "3.7" and python_version < "4.0" pysocks==1.7.1 ; python_version >= "3.7" and python_version < "4.0"
pytest-asyncio==0.19.0 ; python_version >= "3.7" and python_version < "4.0" pytest-asyncio==0.19.0 ; python_version >= "3.7" and python_version < "4.0"
pytest==7.1.3 ; python_version >= "3.7" and python_version < "4.0" pytest==7.2.1 ; python_version >= "3.7" and python_version < "4.0"
python-bitcoinlib==0.11.2 ; python_version >= "3.7" and python_version < "4.0" python-bitcoinlib==0.11.2 ; python_version >= "3.7" and python_version < "4.0"
python-dotenv==0.21.0 ; python_version >= "3.7" and python_version < "4.0" python-dotenv==0.21.0 ; python_version >= "3.7" and python_version < "4.0"
pyyaml==5.4.1 ; python_version >= "3.7" and python_version < "4.0" pyyaml==5.4.1 ; python_version >= "3.7" and python_version < "4.0"
@ -62,7 +61,7 @@ requests==2.27.1 ; python_version >= "3.7" and python_version < "4.0"
rfc3986==1.5.0 ; python_version >= "3.7" and python_version < "4.0" rfc3986==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
rfc3986[idna2008]==1.5.0 ; python_version >= "3.7" and python_version < "4.0" rfc3986[idna2008]==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
secp256k1==0.14.0 ; python_version >= "3.7" and python_version < "4.0" secp256k1==0.14.0 ; python_version >= "3.7" and python_version < "4.0"
setuptools==65.6.3 ; python_version >= "3.7" and python_version < "4.0" setuptools==65.7.0 ; python_version >= "3.7" and python_version < "4.0"
shortuuid==1.0.1 ; python_version >= "3.7" and python_version < "4.0" shortuuid==1.0.1 ; python_version >= "3.7" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.7" and python_version < "4.0" six==1.16.0 ; python_version >= "3.7" and python_version < "4.0"
sniffio==1.3.0 ; python_version >= "3.7" and python_version < "4.0" sniffio==1.3.0 ; python_version >= "3.7" and python_version < "4.0"
@ -70,13 +69,14 @@ sqlalchemy-aio==0.17.0 ; python_version >= "3.7" and python_version < "4.0"
sqlalchemy==1.3.24 ; python_version >= "3.7" and python_version < "4.0" sqlalchemy==1.3.24 ; python_version >= "3.7" and python_version < "4.0"
sse-starlette==0.6.2 ; python_version >= "3.7" and python_version < "4.0" sse-starlette==0.6.2 ; python_version >= "3.7" and python_version < "4.0"
starlette==0.19.1 ; python_version >= "3.7" and python_version < "4.0" starlette==0.19.1 ; python_version >= "3.7" and python_version < "4.0"
tomli==2.0.1 ; python_version >= "3.7" and python_version < "4.0" tomli==2.0.1 ; python_version >= "3.7" and python_version < "3.11"
typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0" typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0"
urllib3==1.26.12 ; python_version >= "3.7" and python_version < "4" urllib3==1.26.14 ; python_version >= "3.7" and python_version < "4.0"
uvicorn==0.18.3 ; python_version >= "3.7" and python_version < "4.0" uvicorn==0.18.3 ; python_version >= "3.7" and python_version < "4.0"
uvloop==0.16.0 ; python_version >= "3.7" and python_version < "4.0" uvloop==0.16.0 ; python_version >= "3.7" and python_version < "4.0"
watchgod==0.7 ; python_version >= "3.7" and python_version < "4.0" watchgod==0.7 ; python_version >= "3.7" and python_version < "4.0"
websocket-client==1.3.3 ; python_version >= "3.7" and python_version < "4.0" websocket-client==1.3.3 ; python_version >= "3.7" and python_version < "4.0"
websockets==10.0 ; python_version >= "3.7" and python_version < "4.0" websockets==10.0 ; python_version >= "3.7" and python_version < "4.0"
wheel==0.38.4 ; python_version >= "3.7" and python_version < "4.0"
win32-setctime==1.1.0 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" win32-setctime==1.1.0 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32"
zipp==3.9.0 ; python_version >= "3.7" and python_version < "4.0" zipp==3.11.0 ; python_version >= "3.7" and python_version < "4.0"