Compare commits

..
36 Commits
Author SHA1 Message Date
dni ⚡andGitHub 83334d67c8 HOTFIX: websocket with old balance was sent. (#3758) 2026-02-03 12:12:33 +01:00
dni ⚡andGitHub ef9adc077b chore: update to v1.4.1 (#3731) 2026-01-20 13:01:16 +01:00
Vlad StanandGitHub ffad5d79c4 fix: undelete wallet (#3730) 2026-01-20 13:51:20 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
09c35eaca0 chore(deps-dev): bump filelock from 3.20.1 to 3.20.3 (#3721)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-15 09:17:26 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
52adc62bc4 chore(deps): bump filelock from 3.20.0 to 3.20.3 (#3720)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-15 08:34:40 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0efa173084 chore(deps): bump virtualenv from 20.35.4 to 20.36.1 (#3719)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-15 08:34:21 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
724f8c9f63 chore(deps-dev): bump virtualenv from 20.31.2 to 20.36.1 (#3718)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-15 08:34:05 +01:00
Vlad StanandGitHub c6981089e5 fix: handle pre-populated data (#3717) 2026-01-13 08:05:47 +01:00
Vlad Stan 13480027dd chore: bump release candidate 2026-01-12 13:33:53 +02:00
93f1006d3a feat: wire up paidreviews (#3656)
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2026-01-12 13:17:39 +02:00
Tiago VasconcelosandGitHub 5490367179 fix: search input on extension page (#3705) 2026-01-12 12:39:48 +02:00
a185197e34 Fix: Update apipayments updated_at field when a payment is updated. (#3699)
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2026-01-09 09:52:10 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
aa1bddcdd5 chore(deps-dev): bump werkzeug from 3.1.4 to 3.1.5 (#3714)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-09 06:26:32 +01:00
dni ⚡andGitHub 6d24ba55aa fix: /user/{user_id}/wallet should return deleted wallet (#3713) 2026-01-09 06:25:40 +01:00
Vlad StanandGitHub 5b2937672c fix: delete user with wallets (#3711) 2026-01-08 16:39:37 +01:00
Vlad StanandGitHub 957d9ce419 fix: password reset from UI (#3712) 2026-01-08 16:38:48 +01:00
PatMulliganandGitHub 0e21b77b74 fix(lndrest): use boolean for allow_self_payment instead of integer (#3700) 2026-01-08 09:54:05 +01:00
24e28911ab fix: greenlet import issue mac osx (#3698)
Co-authored-by: Pratik Patel <pratikpatelpp802@gmail.com>
2026-01-08 09:16:50 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1e252d0485 chore(deps): bump urllib3 from 2.6.0 to 2.6.3 (#3706)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-08 08:50:24 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e98550a125 chore(deps): bump aiohttp from 3.12.15 to 3.13.3 (#3703)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-08 08:50:10 +01:00
dni ⚡andGitHub bc55474859 fix: export qrcode was undefined (#3689) 2025-12-29 19:45:15 +01:00
dni ⚡andGitHub d985bdaadb chore: update breez liquid sdk (#3685) 2025-12-27 12:07:08 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>dni ⚡
8e0d1cc100 chore(deps): bump fastapi-sso from 0.18.0 to 0.19.0 (#3681)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dni  <office@dnilabs.com>
2025-12-23 09:18:33 +01:00
dni ⚡andGitHub cb3fd56647 chore: update to version v1.4.0 (#3684) 2025-12-22 14:34:03 +01:00
Vlad StanandGitHub 9f383bfee6 fix: cached user check (#3682) 2025-12-22 10:24:07 +01:00
dni ⚡andGitHub 3b2e28dd60 chore: update to v1.4.0-rc4 (#3675) 2025-12-22 09:27:16 +01:00
132192bc94 test: add boltz fundingsource to regtest (#3677)
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2025-12-22 09:23:46 +01:00
281c3df826 fix: unlimited admin upload (#3679)
Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
2025-12-19 20:11:34 +00:00
Vlad StanandGitHub 08591f34c2 fix: extension spinner not stopping after install (#3680) 2025-12-19 20:01:04 +00:00
ArcandGitHub f0e8ae0f5c Adds support for stripe readers (#3678) 2025-12-19 08:23:56 +01:00
168cb726b1 Make funding source fields more explicit (#3676)
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2025-12-18 13:44:22 +00:00
dni ⚡andGitHub 91ac245307 fix: release lnbits-boltz with proper tag (#3674) 2025-12-17 14:36:29 +01:00
dni ⚡andGitHub d098e2f710 chore: update to v1.4.0-rc3 (#3672) 2025-12-17 13:57:22 +01:00
dni ⚡andGitHub a0f65f4cda fix: Boltzclient fundingsource. use a hashed wallet_name (#3673) 2025-12-17 13:57:05 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
606ee215b4 chore(deps-dev): bump filelock from 3.18.0 to 3.20.1 (#3669)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 09:13:18 +01:00
dni ⚡andGitHub 7716f819f6 CI: create github prerelease and appimage (#3668) 2025-12-17 07:21:47 +01:00
47 changed files with 1506 additions and 466 deletions
+7
View File
@@ -23,3 +23,10 @@ mypy.ini
package-lock.json package-lock.json
package.json package.json
pytest.ini pytest.ini
.mypy_cache
.github
.pytest_cache
.vscode
bin
dist
-3
View File
@@ -18,7 +18,6 @@ ENABLE_LOG_TO_FILE=true
# https://loguru.readthedocs.io/en/stable/api/logger.html#file # https://loguru.readthedocs.io/en/stable/api/logger.html#file
LOG_ROTATION="100 MB" LOG_ROTATION="100 MB"
LOG_RETENTION="3 months" LOG_RETENTION="3 months"
# for database cleanup commands # for database cleanup commands
# CLEANUP_WALLETS_DAYS=90 # CLEANUP_WALLETS_DAYS=90
@@ -187,7 +186,6 @@ BOLTZ_CLIENT_ENDPOINT=127.0.0.1:9002
BOLTZ_CLIENT_MACAROON="/home/bob/.boltz/macaroons/admin.macaroon" BOLTZ_CLIENT_MACAROON="/home/bob/.boltz/macaroons/admin.macaroon"
# HEXSTRING instead of path also possible # HEXSTRING instead of path also possible
BOLTZ_CLIENT_CERT="/home/bob/.boltz/tls.cert" BOLTZ_CLIENT_CERT="/home/bob/.boltz/tls.cert"
BOLTZ_CLIENT_WALLET="lnbits"
# StrikeWallet # StrikeWallet
STRIKE_API_ENDPOINT=https://api.strike.me/v1 STRIKE_API_ENDPOINT=https://api.strike.me/v1
@@ -333,4 +331,3 @@ LNBITS_RESERVE_FEE_PERCENT=1.0
###################################### ######################################
###### Logging and Development ####### ###### Logging and Development #######
###################################### ######################################
+8 -1
View File
@@ -75,7 +75,14 @@ jobs:
strategy: strategy:
matrix: matrix:
python-version: ["3.10"] python-version: ["3.10"]
backend-wallet-class: ["LndRestWallet", "LndWallet", "CoreLightningWallet", "CoreLightningRestWallet", "LNbitsWallet", "EclairWallet"] backend-wallet-class:
- BoltzWallet
- LndRestWallet
- LndWallet
- CoreLightningWallet
- CoreLightningRestWallet
- LNbitsWallet
- EclairWallet
with: with:
custom-pytest: "uv run pytest tests/regtest" custom-pytest: "uv run pytest tests/regtest"
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
+1
View File
@@ -62,3 +62,4 @@ jobs:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
cache-from: type=local,src=/tmp/.buildx-cache cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache
build-args: LNBITS_TAG=${{ inputs.tag }}
+4 -2
View File
@@ -40,8 +40,8 @@ jobs:
run: | run: |
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker cd docker
chmod +x ./tests chmod +x ./start-regtest
./tests ./start-regtest
sudo chmod -R a+rwx . sudo chmod -R a+rwx .
- name: Run pytest - name: Run pytest
@@ -63,6 +63,8 @@ jobs:
LNBITS_ENDPOINT: http://localhost:5001 LNBITS_ENDPOINT: http://localhost:5001
LNBITS_KEY: "d08a3313322a4514af75d488bcc27eee" LNBITS_KEY: "d08a3313322a4514af75d488bcc27eee"
ECLAIR_URL: http://127.0.0.1:8082 ECLAIR_URL: http://127.0.0.1:8082
BOLTZ_CLIENT_ENDPOINT: 127.0.0.1:9002
BOLTZ_MNEMONIC: abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
LNBITS_MAX_OUTGOING_PAYMENT_AMOUNT_SATS: 1000000000 LNBITS_MAX_OUTGOING_PAYMENT_AMOUNT_SATS: 1000000000
LNBITS_MAX_INCOMING_PAYMENT_AMOUNT_SATS: 1000000000 LNBITS_MAX_INCOMING_PAYMENT_AMOUNT_SATS: 1000000000
ECLAIR_PASS: lnbits ECLAIR_PASS: lnbits
+30
View File
@@ -10,7 +10,29 @@ permissions:
jobs: jobs:
release:
runs-on: ubuntu-24.04
outputs:
upload_url: ${{ steps.get_upload_url.outputs.upload_url }}
steps:
- uses: actions/checkout@v4
- name: Create github pre-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
run: |
gh release create "$tag" --prerelease --generate-notes --draft
- id: get_upload_url
name: Get upload url of Github release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
run: |
upload_url=$(gh release view "$tag" --json uploadUrl -q ".uploadUrl")
echo "upload_url=$upload_url" >> "$GITHUB_OUTPUT"
docker: docker:
if: github.repository == 'lnbits/lnbits'
uses: ./.github/workflows/docker.yml uses: ./.github/workflows/docker.yml
with: with:
tag: ${{ github.ref_name }} tag: ${{ github.ref_name }}
@@ -19,6 +41,7 @@ jobs:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
pypi: pypi:
if: github.repository == 'lnbits/lnbits'
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Install dependencies for building secp256k1 - name: Install dependencies for building secp256k1
@@ -30,3 +53,10 @@ jobs:
uses: JRubics/poetry-publish@v1.15 uses: JRubics/poetry-publish@v1.15
with: with:
pypi_token: ${{ secrets.PYPI_API_KEY }} pypi_token: ${{ secrets.PYPI_API_KEY }}
appimage:
needs: [ release ]
uses: ./.github/workflows/appimage.yml
with:
tag_name: ${{ github.ref_name }}
upload_url: ${{ needs.release.outputs.upload_url }}
+8 -2
View File
@@ -14,7 +14,7 @@ jobs:
release: release:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
outputs: outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.get_upload_url.outputs.upload_url }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Create github release - name: Create github release
@@ -23,7 +23,13 @@ jobs:
tag: ${{ github.ref_name }} tag: ${{ github.ref_name }}
run: | run: |
gh release create "$tag" --generate-notes --draft gh release create "$tag" --generate-notes --draft
upload_url=$(gh release view "$tag" --repo "${{ github.repository }}" --json uploadUrl -q ".uploadUrl") - id: get_upload_url
name: Get upload url of Github release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
run: |
upload_url=$(gh release view "$tag" --json uploadUrl -q ".uploadUrl")
echo "upload_url=$upload_url" >> "$GITHUB_OUTPUT" echo "upload_url=$upload_url" >> "$GITHUB_OUTPUT"
docker: docker:
+3 -2
View File
@@ -1,6 +1,7 @@
FROM boltz/boltz-client:latest AS boltz ARG LNBITS_TAG=latest
FROM lnbits/lnbits:latest FROM boltz/boltz-client:latest AS boltz
FROM lnbits/lnbits:${LNBITS_TAG}
COPY --from=boltz /bin/boltzd /bin/boltzcli /usr/local/bin/ COPY --from=boltz /bin/boltzd /bin/boltzcli /usr/local/bin/
RUN ls -l /usr/local/bin/boltzd RUN ls -l /usr/local/bin/boltzd
+12 -2
View File
@@ -1,3 +1,4 @@
from datetime import datetime, timezone
from time import time from time import time
from typing import Any from typing import Any
@@ -304,8 +305,16 @@ async def update_payment_checking_id(
checking_id: str, new_checking_id: str, conn: Connection | None = None checking_id: str, new_checking_id: str, conn: Connection | None = None
) -> None: ) -> None:
await (conn or db).execute( await (conn or db).execute(
"UPDATE apipayments SET checking_id = :new_id WHERE checking_id = :old_id", f"""
{"new_id": new_checking_id, "old_id": checking_id}, UPDATE apipayments
SET checking_id = :new_id, updated_at = {db.timestamp_placeholder('now')}
WHERE checking_id = :old_id
""", # noqa: S608
{
"new_id": new_checking_id,
"old_id": checking_id,
"now": int(time()),
},
) )
@@ -314,6 +323,7 @@ async def update_payment(
new_checking_id: str | None = None, new_checking_id: str | None = None,
conn: Connection | None = None, conn: Connection | None = None,
) -> None: ) -> None:
payment.updated_at = datetime.now(timezone.utc)
await (conn or db).update( await (conn or db).update(
"apipayments", payment, "WHERE checking_id = :checking_id" "apipayments", payment, "WHERE checking_id = :checking_id"
) )
+10 -5
View File
@@ -45,17 +45,13 @@ async def update_wallet(
async def delete_wallet( async def delete_wallet(
*,
user_id: str, user_id: str,
wallet_id: str, wallet_id: str,
deleted: bool = True, deleted: bool = True,
conn: Connection | None = None, conn: Connection | None = None,
) -> None: ) -> None:
_clear_wallet_cache(wallet_id)
now = int(time()) now = int(time())
cached_wallet: BaseWallet | None = cache.pop(f"auth:wallet:{wallet_id}")
if cached_wallet:
cache.pop(f"auth:x-api-key:{cached_wallet.adminkey}")
cache.pop(f"auth:x-api-key:{cached_wallet.inkey}")
await (conn or db).execute( await (conn or db).execute(
# Timestamp placeholder is safe from SQL injection (not user input) # Timestamp placeholder is safe from SQL injection (not user input)
@@ -69,6 +65,7 @@ async def delete_wallet(
async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) -> None: async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) -> None:
_clear_wallet_cache(wallet_id)
await (conn or db).execute( await (conn or db).execute(
"DELETE FROM wallets WHERE id = :wallet", "DELETE FROM wallets WHERE id = :wallet",
{"wallet": wallet_id}, {"wallet": wallet_id},
@@ -78,6 +75,7 @@ async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) ->
async def delete_wallet_by_id( async def delete_wallet_by_id(
wallet_id: str, conn: Connection | None = None wallet_id: str, conn: Connection | None = None
) -> int | None: ) -> int | None:
_clear_wallet_cache(wallet_id)
now = int(time()) now = int(time())
result = await (conn or db).execute( result = await (conn or db).execute(
# Timestamp placeholder is safe from SQL injection (not user input) # Timestamp placeholder is safe from SQL injection (not user input)
@@ -294,3 +292,10 @@ async def get_total_balance(conn: Connection | None = None):
result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances") result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances")
row = result.mappings().first() row = result.mappings().first()
return row.get("balance", 0) or 0 return row.get("balance", 0) or 0
def _clear_wallet_cache(wallet_id):
cached_wallet: BaseWallet | None = cache.pop(f"auth:wallet:{wallet_id}")
if cached_wallet:
cache.pop(f"auth:x-api-key:{cached_wallet.adminkey}")
cache.pop(f"auth:x-api-key:{cached_wallet.inkey}")
+27 -1
View File
@@ -12,7 +12,7 @@ from typing import Any
import httpx import httpx
from loguru import logger from loguru import logger
from pydantic import BaseModel from pydantic import BaseModel, Field
from lnbits.helpers import ( from lnbits.helpers import (
download_url, download_url,
@@ -777,6 +777,32 @@ class ExtensionDetailsRequest(BaseModel):
version: str version: str
class ExtensionReviewsStatus(BaseModel):
tag: str
avg_rating: float
review_count: int
class CreateExtensionReview(BaseModel):
tag: str
name: str | None = Field(None)
rating: int = Field(..., ge=0, le=1000)
comment: str | None = Field(None)
class ExtensionReviewPaymentRequest(BaseModel):
payment_hash: str
payment_request: str
class ExtensionReview(BaseModel):
id: str
name: str | None = Field(default=None)
tag: str | None = Field(default=None)
rating: int = Field(default=0, ge=0, le=1000)
comment: str | None = Field(default=None)
async def github_api_get(url: str, error_msg: str | None) -> Any: async def github_api_get(url: str, error_msg: str | None) -> Any:
headers = {"User-Agent": settings.user_agent} headers = {"User-Agent": settings.user_agent}
if settings.lnbits_ext_github_token: if settings.lnbits_ext_github_token:
+1 -1
View File
@@ -16,7 +16,7 @@ async def create_user_asset(user_id: str, file: UploadFile, is_public: bool) ->
if file.content_type.lower() not in settings.lnbits_assets_allowed_mime_types: if file.content_type.lower() not in settings.lnbits_assets_allowed_mime_types:
raise ValueError(f"File type '{file.content_type}' not allowed.") raise ValueError(f"File type '{file.content_type}' not allowed.")
if user_id not in settings.lnbits_assets_no_limit_users: if not settings.is_unlimited_assets_user(user_id):
user_assets_count = await get_user_assets_count(user_id) user_assets_count = await get_user_assets_count(user_id)
if user_assets_count >= settings.lnbits_max_assets_per_user: if user_assets_count >= settings.lnbits_max_assets_per_user:
raise ValueError( raise ValueError(
+13 -2
View File
@@ -752,7 +752,7 @@ async def _pay_internal_invoice(
await update_payment(internal_payment, conn=conn) await update_payment(internal_payment, conn=conn)
logger.success(f"internal payment successful {internal_payment.checking_id}") logger.success(f"internal payment successful {internal_payment.checking_id}")
send_payment_notification_in_background(wallet, payment) await _send_payment_notification_in_background(wallet.id, payment, conn=conn)
# notify receiver asynchronously # notify receiver asynchronously
from lnbits.tasks import internal_invoice_queue from lnbits.tasks import internal_invoice_queue
@@ -825,7 +825,8 @@ async def _pay_external_invoice(
payment = await update_payment_success_status( payment = await update_payment_success_status(
payment, payment_response, conn=conn payment, payment_response, conn=conn
) )
send_payment_notification_in_background(wallet, payment)
await _send_payment_notification_in_background(wallet.id, payment, conn=conn)
logger.success(f"payment successful {payment_response.checking_id}") logger.success(f"payment successful {payment_response.checking_id}")
payment.checking_id = payment_response.checking_id payment.checking_id = payment_response.checking_id
@@ -1033,3 +1034,13 @@ async def cancel_hold_invoice(payment: Payment) -> InvoiceResponse:
await update_payment(payment) await update_payment(payment)
return response return response
async def _send_payment_notification_in_background(
wallet_id: str, payment: Payment, conn: Connection | None = None
):
# fetch balance again
wallet = await get_wallet(wallet_id, conn=conn)
if not wallet:
raise PaymentError(f"Could not fetch wallet '{wallet_id}'.", status="failed")
send_payment_notification_in_background(wallet, payment)
+53
View File
@@ -2,8 +2,10 @@ import sys
import traceback import traceback
from http import HTTPStatus from http import HTTPStatus
import httpx
from bolt11 import decode as bolt11_decode from bolt11 import decode as bolt11_decode
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastapi.requests import Request
from loguru import logger from loguru import logger
from lnbits.core.crud.extensions import get_user_extensions from lnbits.core.crud.extensions import get_user_extensions
@@ -14,10 +16,14 @@ from lnbits.core.models import (
) )
from lnbits.core.models.extensions import ( from lnbits.core.models.extensions import (
CreateExtension, CreateExtension,
CreateExtensionReview,
Extension, Extension,
ExtensionConfig, ExtensionConfig,
ExtensionMeta, ExtensionMeta,
ExtensionRelease, ExtensionRelease,
ExtensionReview,
ExtensionReviewPaymentRequest,
ExtensionReviewsStatus,
InstallableExtension, InstallableExtension,
PayToEnableInfo, PayToEnableInfo,
ReleasePaymentInfo, ReleasePaymentInfo,
@@ -34,6 +40,7 @@ from lnbits.core.services.extensions import (
install_extension, install_extension,
uninstall_extension, uninstall_extension,
) )
from lnbits.db import Page
from lnbits.decorators import ( from lnbits.decorators import (
check_account_exists, check_account_exists,
check_account_id_exists, check_account_id_exists,
@@ -589,3 +596,49 @@ async def extensions(account_id: AccountId = Depends(check_account_id_exists)):
for ext in installable_exts for ext in installable_exts
] ]
return extension_data return extension_data
@extension_router.get(
"/reviews/tags",
dependencies=[Depends(check_account_exists)],
)
async def get_extension_reviews_tags() -> list[ExtensionReviewsStatus]:
async with httpx.AsyncClient() as client:
resp = await client.get(settings.lnbits_extensions_reviews_url + "/tags")
resp.raise_for_status()
data = resp.json()
return [ExtensionReviewsStatus(**item) for item in data]
@extension_router.get(
"/reviews/{ext_id}",
dependencies=[Depends(check_account_exists)],
)
async def get_extension_reviews(ext_id: str, request: Request) -> Page[ExtensionReview]:
async with httpx.AsyncClient() as client:
query_string = str(request.query_params)
resp = await client.get(
settings.lnbits_extensions_reviews_url + f"/reviews/{ext_id}?{query_string}"
)
resp.raise_for_status()
reviews = resp.json()
return Page(
data=[ExtensionReview(**item) for item in reviews["data"]],
total=reviews["total"],
)
@extension_router.put(
"/reviews",
dependencies=[Depends(check_account_exists)],
)
async def create_extension_review(
data: CreateExtensionReview,
) -> ExtensionReviewPaymentRequest:
async with httpx.AsyncClient() as client:
resp = await client.post(
settings.lnbits_extensions_reviews_url + "/reviews", json=data.dict()
)
resp.raise_for_status()
payment_request = resp.json()
return ExtensionReviewPaymentRequest(**payment_request)
+2 -2
View File
@@ -337,7 +337,7 @@ async def api_payment(payment_hash, x_api_key: str | None = Header(None)):
return {"paid": False, "status": "failed"} return {"paid": False, "status": "failed"}
try: try:
status = await payment.check_status() payment = await update_pending_payment(payment)
except Exception: except Exception:
if wallet and wallet.id == payment.wallet_id: if wallet and wallet.id == payment.wallet_id:
return {"paid": False, "details": payment} return {"paid": False, "details": payment}
@@ -346,7 +346,7 @@ async def api_payment(payment_hash, x_api_key: str | None = Header(None)):
if wallet and wallet.id == payment.wallet_id: if wallet and wallet.id == payment.wallet_id:
return { return {
"paid": payment.success, "paid": payment.success,
"status": f"{status!s}", "status": f"{payment.status!s}",
"preimage": payment.preimage, "preimage": payment.preimage,
"details": payment, "details": payment,
} }
+5 -7
View File
@@ -20,6 +20,7 @@ from lnbits.core.crud import (
update_admin_settings, update_admin_settings,
update_wallet, update_wallet,
) )
from lnbits.core.crud.wallets import delete_wallet_by_id
from lnbits.core.models import ( from lnbits.core.models import (
AccountFilters, AccountFilters,
AccountOverview, AccountOverview,
@@ -157,11 +158,8 @@ async def api_users_delete_user(
user_id: str, account: Account = Depends(check_admin) user_id: str, account: Account = Depends(check_admin)
) -> SimpleStatus: ) -> SimpleStatus:
wallets = await get_wallets(user_id, deleted=False) wallets = await get_wallets(user_id, deleted=False)
if len(wallets) > 0: for wallet in wallets:
raise HTTPException( await delete_wallet_by_id(wallet.id)
status_code=HTTPStatus.BAD_REQUEST,
detail="Cannot delete user with wallets.",
)
if user_id == settings.super_user: if user_id == settings.super_user:
raise HTTPException( raise HTTPException(
@@ -224,7 +222,7 @@ async def api_users_toggle_admin(user_id: str) -> SimpleStatus:
@users_router.get("/user/{user_id}/wallet", name="Get wallets for user") @users_router.get("/user/{user_id}/wallet", name="Get wallets for user")
async def api_users_get_user_wallet(user_id: str) -> list[Wallet]: async def api_users_get_user_wallet(user_id: str) -> list[Wallet]:
return await get_wallets(user_id) return await get_wallets(user_id, deleted=None)
@users_router.post("/user/{user_id}/wallet", name="Create a new wallet for user") @users_router.post("/user/{user_id}/wallet", name="Create a new wallet for user")
@@ -247,7 +245,7 @@ async def api_users_create_user_wallet(
"/user/{user_id}/wallet/{wallet}/undelete", name="Reactivate deleted wallet" "/user/{user_id}/wallet/{wallet}/undelete", name="Reactivate deleted wallet"
) )
async def api_users_undelete_user_wallet(user_id: str, wallet: str) -> SimpleStatus: async def api_users_undelete_user_wallet(user_id: str, wallet: str) -> SimpleStatus:
wal = await get_wallet(wallet) wal = await get_wallet(wallet, deleted=True)
if not wal: if not wal:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND,
+1 -1
View File
@@ -249,7 +249,7 @@ async def check_account_id_exists(
account_id = cache.get(cache_key) account_id = cache.get(cache_key)
if account_id: if account_id:
r.scope["user_id"] = account_id.id r.scope["user_id"] = account_id.id
await _check_user_access(r, account_id, conn=conn) await _check_user_access(r, account_id.id, conn=conn)
return account_id return account_id
account = await _check_account_exists(r, access_token, usr, conn=conn) account = await _check_account_exists(r, access_token, usr, conn=conn)
+21
View File
@@ -35,6 +35,7 @@ class StripeTerminalOptions(BaseModel):
capture_method: Literal["automatic", "manual"] = "automatic" capture_method: Literal["automatic", "manual"] = "automatic"
metadata: dict[str, str] = Field(default_factory=dict) metadata: dict[str, str] = Field(default_factory=dict)
reader_id: str | None = None
class StripeCheckoutOptions(BaseModel): class StripeCheckoutOptions(BaseModel):
@@ -295,6 +296,15 @@ class StripeWallet(FiatProvider):
r.raise_for_status() r.raise_for_status()
return r.json() return r.json()
async def _process_terminal_payment_intent(
self, reader_id: str, payment_intent_id: str
) -> None:
data = {"payment_intent": payment_intent_id}
r = await self.client.post(
f"/v1/terminal/readers/{reader_id}/process_payment_intent", data=data
)
r.raise_for_status()
async def _create_checkout_invoice( async def _create_checkout_invoice(
self, self,
amount_cents: int, amount_cents: int,
@@ -378,6 +388,17 @@ class StripeWallet(FiatProvider):
ok=False, ok=False,
error_message="Error: missing PaymentIntent or client_secret", error_message="Error: missing PaymentIntent or client_secret",
) )
if term.reader_id:
try:
await self._process_terminal_payment_intent(term.reader_id, pi_id)
except Exception as exc:
logger.warning(exc)
return FiatInvoiceResponse(
ok=False,
error_message=(
"Error: unable to process PaymentIntent on reader"
),
)
return FiatInvoiceResponse( return FiatInvoiceResponse(
ok=True, checking_id=pi_id, payment_request=client_secret ok=True, checking_id=pi_id, payment_request=client_secret
) )
+1
View File
@@ -107,6 +107,7 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates
"LNBITS_DEFAULT_BGIMAGE": settings.lnbits_default_bgimage, "LNBITS_DEFAULT_BGIMAGE": settings.lnbits_default_bgimage,
"VOIDWALLET": settings.lnbits_backend_wallet_class == "VoidWallet", "VOIDWALLET": settings.lnbits_backend_wallet_class == "VoidWallet",
"WEBPUSH_PUBKEY": settings.lnbits_webpush_pubkey, "WEBPUSH_PUBKEY": settings.lnbits_webpush_pubkey,
"LNBITS_EXTENSIONS_REVIEWS_URL": settings.lnbits_extensions_reviews_url,
"LNBITS_DENOMINATION": settings.lnbits_denomination, "LNBITS_DENOMINATION": settings.lnbits_denomination,
"has_holdinvoice": settings.has_holdinvoice, "has_holdinvoice": settings.has_holdinvoice,
"LNBITS_NOSTR_CONFIGURED": settings.is_nostr_notifications_configured(), "LNBITS_NOSTR_CONFIGURED": settings.is_nostr_notifications_configured(),
+14 -20
View File
@@ -6,7 +6,6 @@ import inspect
import json import json
import os import os
import re import re
import secrets
from datetime import datetime, timezone from datetime import datetime, timezone
from enum import Enum from enum import Enum
from os import path from os import path
@@ -19,21 +18,6 @@ from loguru import logger
from pydantic import BaseModel, BaseSettings, Extra, Field, validator from pydantic import BaseModel, BaseSettings, Extra, Field, validator
def generate_default_boltz_mnemonic() -> str | None:
"""
Generate a BIP39 mnemonic using the bundled embit dependency.
If embit is unavailable at runtime, return None to avoid blocking startup.
"""
try:
from embit import bip39
except Exception as exc: # pragma: no cover - dependency missing at runtime
logger.warning(f"Unable to generate Boltz mnemonic (embit missing): {exc}")
return None
entropy = secrets.token_bytes(16) # 128 bits -> 12-word mnemonic
return bip39.mnemonic_from_bytes(entropy)
def list_parse_fallback(v: str): def list_parse_fallback(v: str):
v = v.replace(" ", "") v = v.replace(" ", "")
if len(v) > 0: if len(v) > 0:
@@ -68,6 +52,13 @@ class ExtensionsSettings(LNbitsSettings):
lnbits_user_default_extensions: list[str] = Field(default=[]) lnbits_user_default_extensions: list[str] = Field(default=[])
lnbits_extensions_deactivate_all: bool = Field(default=False) lnbits_extensions_deactivate_all: bool = Field(default=False)
lnbits_extensions_builder_activate_non_admins: bool = Field(default=False) lnbits_extensions_builder_activate_non_admins: bool = Field(default=False)
lnbits_extensions_reviews_url: str = Field(
default="https://demo.lnbits.com/paidreviews/api/v1/AdFzLjzuKFLsdk4Bcnff6r",
description="""
URL for the paid reviews.
Regular users can view this URL (not secret).
""",
)
lnbits_extensions_manifests: list[str] = Field( lnbits_extensions_manifests: list[str] = Field(
default=[ default=[
"https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json" "https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json"
@@ -324,6 +315,12 @@ class AssetSettings(LNbitsSettings):
lnbits_max_assets_per_user: int = Field(default=1, ge=0) lnbits_max_assets_per_user: int = Field(default=1, ge=0)
lnbits_assets_no_limit_users: list[str] = Field(default=[]) lnbits_assets_no_limit_users: list[str] = Field(default=[])
def is_unlimited_assets_user(self, user_id: str) -> bool:
return (
settings.is_admin_user(user_id)
or user_id in self.lnbits_assets_no_limit_users
)
class FeeSettings(LNbitsSettings): class FeeSettings(LNbitsSettings):
lnbits_reserve_fee_min: int = Field(default=2000, ge=0) lnbits_reserve_fee_min: int = Field(default=2000, ge=0)
@@ -617,12 +614,9 @@ class BreezLiquidSdkFundingSource(LNbitsSettings):
class BoltzFundingSource(LNbitsSettings): class BoltzFundingSource(LNbitsSettings):
boltz_client_endpoint: str | None = Field(default="127.0.0.1:9002") boltz_client_endpoint: str | None = Field(default="127.0.0.1:9002")
boltz_client_macaroon: str | None = Field(default=None) boltz_client_macaroon: str | None = Field(default=None)
boltz_client_wallet: str | None = Field(default="lnbits")
boltz_client_password: str = Field(default="") boltz_client_password: str = Field(default="")
boltz_client_cert: str | None = Field(default=None) boltz_client_cert: str | None = Field(default=None)
boltz_mnemonic: str | None = Field( boltz_mnemonic: str | None = Field(default=None)
default_factory=generate_default_boltz_mnemonic
)
class StrikeFundingSource(LNbitsSettings): class StrikeFundingSource(LNbitsSettings):
File diff suppressed because one or more lines are too long
+10 -10
View File
File diff suppressed because one or more lines are too long
+19
View File
@@ -187,6 +187,25 @@ window.localisation.en = {
'Only admin accounts can create extensions', 'Only admin accounts can create extensions',
admin_only: 'Admin Only', admin_only: 'Admin Only',
new_version: 'New Version', new_version: 'New Version',
reviews_url: 'Reviews URL',
reviews_url_label: 'Reviews server URL',
reviews_url_hint:
'Full PaidReviews URL including the settings id (e.g. https://example.com/paidreviews/SETTINGS_ID)',
reviews_open: 'View reviews',
reviews_leave: 'Leave a review',
reviews_name: 'Your name',
reviews_comment: 'Your review',
reviews_rating: 'Rating',
reviews_submit: 'Submit review',
reviews_loading: 'Loading reviews...',
reviews_refresh: 'Refresh reviews',
reviews_error_load: 'Could not load reviews',
reviews_url_not_configured: 'Reviews URL not configured',
reviews_pay_invoice: 'Pay invoice',
reviews_invoice_paid: 'Invoice paid',
reviews_invoice_title: 'Pay this invoice to submit your review',
reviews_count: 'Reviews',
no_reviews: 'No reviews yet',
extension_has_free_release: 'Has free releases', extension_has_free_release: 'Has free releases',
extension_has_paid_release: 'Has paid releases', extension_has_paid_release: 'Has paid releases',
extension_depends_on: 'Depends on:', extension_depends_on: 'Depends on:',
+2 -1
View File
@@ -13,7 +13,8 @@ window.LNbits = {
wallets: data.wallets, wallets: data.wallets,
fiat_providers: data.fiat_providers || [], fiat_providers: data.fiat_providers || [],
super_user: data.super_user, super_user: data.super_user,
extra: data.extra ?? {} extra: data.extra ?? {},
hasPassword: data.has_password ?? false
} }
const mapWallet = this.wallet const mapWallet = this.wallet
obj.wallets = obj.wallets.map(mapWallet).sort((a, b) => { obj.wallets = obj.wallets.map(mapWallet).sort((a, b) => {
+28 -1
View File
@@ -3,7 +3,34 @@ window.app.component(QrcodeVue)
window.app.component('lnbits-extension-rating', { window.app.component('lnbits-extension-rating', {
template: '#lnbits-extension-rating', template: '#lnbits-extension-rating',
name: 'lnbits-extension-rating', name: 'lnbits-extension-rating',
props: ['rating'] props: {
rating: {
type: Number,
default: 0
},
count: {
type: Number,
default: null
},
clickable: {
type: Boolean,
default: false
}
},
computed: {
displayRating() {
return Math.round((this.rating || 0) * 2) / 2
},
hasData() {
return this.count !== null && this.count !== undefined
}
},
methods: {
handleClick() {
if (!this.clickable) return
this.$emit('click')
}
}
}) })
window.app.component('lnbits-fsat', { window.app.component('lnbits-fsat', {
@@ -89,8 +89,14 @@ window.app.component('lnbits-admin-funding-sources', {
lnd_rest_cert: 'Certificate', lnd_rest_cert: 'Certificate',
lnd_rest_macaroon: 'Macaroon', lnd_rest_macaroon: 'Macaroon',
lnd_rest_macaroon_encrypted: 'Encrypted Macaroon', lnd_rest_macaroon_encrypted: 'Encrypted Macaroon',
lnd_rest_route_hints: 'Enable Route Hints', lnd_rest_route_hints: {
lnd_rest_allow_self_payment: 'Allow Self Payment' advanced: true,
label: 'Enable Route Hints'
},
lnd_rest_allow_self_payment: {
advanced: true,
label: 'Allow Self Payment'
}
} }
], ],
[ [
@@ -160,22 +166,27 @@ window.app.component('lnbits-admin-funding-sources', {
'BoltzWallet', 'BoltzWallet',
'Boltz', 'Boltz',
{ {
boltz_client_endpoint: 'Endpoint', boltz_client_endpoint: {
label: 'Boltz client endpoint',
value: '127.0.0.1:9002'
},
boltz_client_macaroon: { boltz_client_macaroon: {
label: 'Admin Macaroon path or hex', label: 'Admin Macaroon path or hex',
default: '/home/ubuntu/.boltz/macaroons/admin.macaroon' value: '/home/ubuntu/.boltz/macaroons/admin.macaroon'
}, },
boltz_client_cert: { boltz_client_cert: {
label: 'Certificate path or hex', label: 'Certificate path or hex',
default: '/home/ben/.boltz/tls.cert' value: '/home/ubuntu/.boltz/tls.cert'
}, },
boltz_client_wallet: 'Wallet Name',
boltz_client_password: 'Wallet Password (can be empty)',
boltz_mnemonic: { boltz_mnemonic: {
label: 'Liquid mnemonic (copy into greenwallet)', label: 'Liquid seed phrase',
hint: 'Boltz will fetch once connected, but you can change later (can be opened in a liquid wallet) ',
copy: true, copy: true,
qrcode: true, qrcode: true
hint: 'Paste or type your liquid mnemonic' },
boltz_client_password: {
label: 'Wallet Password (optional)',
advanced: true
} }
} }
], ],
@@ -1,9 +1,10 @@
window.app.component('lnbits-wallet-extra', { window.app.component('lnbits-wallet-extra', {
template: '#lnbits-wallet-extra', template: '#lnbits-wallet-extra',
mixins: [window.windowMixin],
props: ['chartConfig'], props: ['chartConfig'],
data() { computed: {
return {} exportUrl() {
return `${window.location.origin}/wallet?usr=${this.g.user.id}&wal=${this.g.wallet.id}`
}
}, },
methods: { methods: {
handleSendLnurl(lnurl) { handleSendLnurl(lnurl) {
+234 -23
View File
@@ -26,7 +26,63 @@ window.PageExtensions = {
selectedRelease: null, selectedRelease: null,
uninstallAndDropDb: false, uninstallAndDropDb: false,
maxStars: 5, maxStars: 5,
paylinkWebsocket: null paylinkWebsocket: null,
searchToggle: false,
reviewsUrl: null,
reviewsDialog: {
show: false,
extension: null,
loading: false,
submitting: false,
form: {
name: '',
rating: 0,
comment: ''
},
error: null
},
reviews: [],
reviewsTable: {
loading: false,
columns: [
{
name: 'name',
align: 'left',
label: this.$t('Name'),
field: 'name',
sortable: true
},
{
name: 'comment',
align: 'left',
label: this.$t('Comment'),
field: 'comment'
},
{
name: 'created_at',
align: 'left',
label: this.$t('Date'),
field: 'created_at'
},
{
name: 'rating',
align: 'right',
label: 'Rating',
field: 'rating'
}
],
pagination: {
rowsPerPage: 5,
sortBy: 'created_at',
descending: true,
page: 1
}
},
paymentDialog: {
show: false,
invoice: '',
hash: ''
}
} }
}, },
watch: { watch: {
@@ -71,7 +127,6 @@ window.PageExtensions = {
// the install logic has been triggered one way or another // the install logic has been triggered one way or another
this.unsubscribeFromPaylinkWs() this.unsubscribeFromPaylinkWs()
const extension = this.selectedExtension
this.selectedExtension.inProgress = true this.selectedExtension.inProgress = true
this.showManageExtensionDialog = false this.showManageExtensionDialog = false
release.payment_hash = release.payment_hash =
@@ -86,6 +141,7 @@ window.PageExtensions = {
version: release.version version: release.version
}) })
.then(response => { .then(response => {
this.selectedExtension.inProgress = false
const extension = this.extensions.find( const extension = this.extensions.find(
ext => ext.id === this.selectedExtension.id ext => ext.id === this.selectedExtension.id
) )
@@ -100,7 +156,7 @@ window.PageExtensions = {
}) })
.catch(err => { .catch(err => {
console.warn(err) console.warn(err)
extension.inProgress = false this.selectedExtension.inProgress = false
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
}) })
}, },
@@ -284,8 +340,7 @@ window.PageExtensions = {
try { try {
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(
'GET', 'GET',
`/api/v1/extension/${extension.id}/releases`, `/api/v1/extension/${extension.id}/releases`
this.g.user.wallets[0].adminkey
) )
this.selectedExtensionRepos = data.reduce((repos, release) => { this.selectedExtensionRepos = data.reduce((repos, release) => {
@@ -323,6 +378,9 @@ window.PageExtensions = {
if (!detailsLink) { if (!detailsLink) {
return return
} }
// keep a reference to the extension so we can show rating stats
this.selectedExtension =
this.extensions.find(ext => ext.id === extId) || this.selectedExtension
this.selectedExtensionDetails = null this.selectedExtensionDetails = null
this.showExtensionDetailsDialog = true this.showExtensionDetailsDialog = true
this.slide = 0 this.slide = 0
@@ -331,8 +389,7 @@ window.PageExtensions = {
try { try {
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(
'GET', 'GET',
`/api/v1/extension/${extId}/details?details_link=${detailsLink}`, `/api/v1/extension/${extId}/details?details_link=${detailsLink}`
this.g.user.wallets[0].inkey
) )
this.selectedExtensionDetails = data this.selectedExtensionDetails = data
@@ -453,7 +510,7 @@ window.PageExtensions = {
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(
'PUT', 'PUT',
`/api/v1/extension/${extId}/invoice/install`, `/api/v1/extension/${extId}/invoice/install`,
this.g.user.wallets[0].adminkey, null,
{ {
ext_id: extId, ext_id: extId,
archive: release.archive, archive: release.archive,
@@ -469,7 +526,7 @@ window.PageExtensions = {
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(
'PUT', 'PUT',
`/api/v1/extension/${extId}/invoice/enable`, `/api/v1/extension/${extId}/invoice/enable`,
this.g.user.wallets[0].adminkey, null,
{ {
amount amount
} }
@@ -560,8 +617,7 @@ window.PageExtensions = {
try { try {
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(
'GET', 'GET',
`/api/v1/extension/release/${org}/${repo}/${release.version}`, `/api/v1/extension/release/${org}/${repo}/${release.version}`
this.g.user.wallets[0].adminkey
) )
release.loaded = true release.loaded = true
release.is_version_compatible = data.is_version_compatible release.is_version_compatible = data.is_version_compatible
@@ -586,18 +642,13 @@ window.PageExtensions = {
continue continue
} }
ext.inProgress = true ext.inProgress = true
await LNbits.api.request( await LNbits.api.request('POST', `/api/v1/extension`, null, {
'POST', ext_id: ext.id,
`/api/v1/extension`, archive: ext.latestRelease.archive,
this.g.user.wallets[0].adminkey, source_repo: ext.latestRelease.source_repo,
{ payment_hash: ext.latestRelease.payment_hash,
ext_id: ext.id, version: ext.latestRelease.version
archive: ext.latestRelease.archive, })
source_repo: ext.latestRelease.source_repo,
payment_hash: ext.latestRelease.payment_hash,
version: ext.latestRelease.version
}
)
count++ count++
ext.isAvailable = true ext.isAvailable = true
ext.isInstalled = true ext.isInstalled = true
@@ -622,6 +673,163 @@ window.PageExtensions = {
}) })
this.showUpdateAllDialog = false this.showUpdateAllDialog = false
}, },
formatAvg(raw) {
const val = Number(raw || 0)
return Math.round((val / 2 / 100) * 2) / 2
},
async loadReviewStats() {
if (!this.reviewsUrl) {
console.info('Extension reviews are not configured')
return
}
try {
const {data} = await LNbits.api.request(
'GET',
`/api/v1/extension/reviews/tags`
)
const map = {}
data.forEach(stat => {
map[stat.tag] = stat
})
this.extensions.forEach(ext => {
ext.reviewStats = map[ext.id] || null
})
this.filterExtensions(this.searchTerm, this.tab)
} catch (error) {
console.warn(error)
}
},
async openReviews(extension) {
const targetExtension =
extension ||
(this.selectedExtensionDetails
? this.extensions.find(e => e.id === this.selectedExtensionDetails.id)
: null)
if (!targetExtension) {
return
}
if (!this.reviewsUrl) {
Quasar.Notify.create({
type: 'warning',
message: this.$t('reviews_url_not_configured')
})
return
}
this.reviewsDialog.extension = targetExtension
this.selectedExtension = extension
this.reviewsDialog.show = true
await this.getTagReviews()
},
async getTagReviews(props) {
if (!this.reviewsUrl) {
Quasar.Notify.create({
type: 'warning',
message: this.$t('reviews_url_not_configured')
})
return
}
try {
this.reviewsTable.loading = true
const params = LNbits.utils.prepareFilterQuery(this.reviewsTable, props)
const {data} = await LNbits.api.request(
'GET',
`/api/v1/extension/reviews/${this.selectedExtension.id}?${params}`
)
this.reviews = data.data
this.reviewsTable.pagination.rowsNumber = data.total
} catch (e) {
LNbits.utils.notifyApiError(e)
} finally {
this.reviewsTable.loading = false
}
},
formatReviewDate(val) {
if (!val) return ''
const numeric = Number(val)
if (!Number.isNaN(numeric)) {
return this.utils.formatTimestamp(numeric)
}
return this.utils.formatDate(val)
},
async submitReview() {
if (!this.reviewsDialog.extension || !this.reviewsUrl) {
return
}
this.reviewsDialog.submitting = true
try {
const payload = {
tag: this.reviewsDialog.extension.id,
name: this.reviewsDialog.form.name,
rating: this.reviewsDialog.form.rating * 100,
comment: this.reviewsDialog.form.comment
}
const {data} = await LNbits.api.request(
'PUT',
`/api/v1/extension/reviews`,
null,
payload
)
if (data.payment_request) {
this.openInvoiceDialog(data.payment_request, data.payment_hash)
} else {
Quasar.Notify.create({
type: 'positive',
message: 'Review submitted'
})
this.resetReviewForm()
await this.getTagReviews()
await this.loadReviewStats()
}
} catch (error) {
LNbits.utils.notifyApiError(error)
} finally {
this.reviewsDialog.submitting = false
}
},
openInvoiceDialog(invoice, hash) {
this.paymentDialog.invoice = invoice
this.paymentDialog.hash = hash
this.paymentDialog.show = true
this.listenForPayment(hash)
},
resetReviewForm() {
this.reviewsDialog.form = {
name: '',
rating: 0,
comment: ''
}
this.paymentDialog = {
show: false,
invoice: '',
hash: ''
}
},
listenForPayment(hash) {
try {
const base = new URL(this.reviewsUrl)
base.protocol = base.protocol === 'https:' ? 'wss:' : 'ws:'
base.pathname = `/api/v1/ws/${hash}`
const ws = new WebSocket(base)
ws.addEventListener('message', async () => {
Quasar.Notify.create({
type: 'positive',
message: this.$t('reviews_invoice_paid')
})
this.paymentDialog.show = false
this.resetReviewForm()
setTimeout(async () => {
await this.getTagReviews()
}, 1000)
await this.loadReviewStats()
ws.close()
})
} catch (error) {
console.warn(error)
}
},
async fetchAllExtensions() { async fetchAllExtensions() {
try { try {
const {data} = await LNbits.api.request('GET', `/api/v1/extension/all`) const {data} = await LNbits.api.request('GET', `/api/v1/extension/all`)
@@ -636,6 +844,7 @@ window.PageExtensions = {
async created() { async created() {
this.extensions = await this.fetchAllExtensions() this.extensions = await this.fetchAllExtensions()
this.extbuilderEnabled = this.g.user.admin || this.LNBITS_EXT_BUILDER this.extbuilderEnabled = this.g.user.admin || this.LNBITS_EXT_BUILDER
this.reviewsUrl = this.LNBITS_EXTENSIONS_REVIEWS_URL
if (this.g.user.extensions.length === 0) { if (this.g.user.extensions.length === 0) {
this.tab = 'all' this.tab = 'all'
@@ -652,6 +861,8 @@ window.PageExtensions = {
this.hasNewVersion(ext) this.hasNewVersion(ext)
) )
await this.loadReviewStats()
this.filterExtensions(this.searchTerm, this.tab) this.filterExtensions(this.searchTerm, this.tab)
} }
} }
+26 -5
View File
@@ -561,11 +561,32 @@ include('components/lnbits-error.vue') %}
</template> </template>
<template id="lnbits-extension-rating"> <template id="lnbits-extension-rating">
<div style="margin-bottom: 3px"> <div class="row items-center q-gutter-xs" style="margin-bottom: 3px">
<q-rating v-model="rating" size="1.5em" :max="5" color="primary" <q-rating
><q-tooltip> :model-value="displayRating"
<span v-text="$t('extension_rating_soon')"></span> </q-tooltip size="1.4em"
></q-rating> :max="5"
color="primary"
icon="star_border"
icon-selected="star"
icon-half="star_half"
readonly
:class="clickable ? 'cursor-pointer' : ''"
@click="handleClick"
>
<q-tooltip v-if="!hasData">
<span v-text="$t('extension_rating_soon')"></span>
</q-tooltip>
<q-tooltip v-else-if="clickable">
<span v-text="$t('reviews_open')"></span>
</q-tooltip>
</q-rating>
<div
class="text-caption text-grey"
v-if="count !== null && count !== undefined"
>
<span v-text="`(${count})`"></span>
</div>
</div> </div>
</template> </template>
@@ -137,6 +137,19 @@
:hint="$t('extension_builder_manifest_url_hint')" :hint="$t('extension_builder_manifest_url_hint')"
></q-input> ></q-input>
</div> </div>
<div class="col-12 col-md-6">
<p>
<span v-text="$t('reviews_url')"></span>
</p>
<q-input
filled
v-model="formData.lnbits_extensions_reviews_url"
:label="$t('reviews_url_label')"
:hint="$t('reviews_url_hint')"
type="url"
autocomplete="off"
></q-input>
</div>
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
@@ -35,7 +35,9 @@
> >
<div <div
class="row" class="row"
v-for="([key, prop], i) in Object.entries(fundingSources.get(fund))" v-for="([key, prop], i) in Object.entries(
fundingSources.get(fund)
).filter(([, p]) => !p.advanced)"
:key="i" :key="i"
> >
<div class="col-12"> <div class="col-12">
@@ -46,7 +48,7 @@
:type="hideInput ? 'password' : 'text'" :type="hideInput ? 'password' : 'text'"
:label="prop.label" :label="prop.label"
:hint="prop.hint" :hint="prop.hint"
:readonly="prop.readonly || false" :value="prop.value"
> >
<q-btn <q-btn
v-if="prop.copy" v-if="prop.copy"
@@ -69,6 +71,55 @@
</q-input> </q-input>
</div> </div>
</div> </div>
<q-expansion-item
v-if="
Object.entries(fundingSources.get(fund)).some(([, p]) => p.advanced)
"
dense
expand-separator
icon="tune"
label="Advanced"
class="q-mt-sm"
>
<div
class="row"
v-for="([key, prop], i) in Object.entries(
fundingSources.get(fund)
).filter(([, p]) => p.advanced)"
:key="`adv-${i}`"
>
<div class="col-12">
<q-input
v-model="formData[key]"
filled
class="q-mt-sm"
:type="hideInput ? 'password' : 'text'"
:label="prop.label"
:hint="prop.hint"
:readonly="prop.readonly || false"
>
<q-btn
v-if="prop.copy"
@click="utils.copyText(formData[key])"
icon="content_copy"
class="cursor-pointer"
color="grey"
flat
dense
></q-btn>
<q-btn
v-if="prop.qrcode"
@click="showQRValue(formData[key])"
icon="qr_code"
class="cursor-pointer"
color="grey"
flat
dense
></q-btn>
</q-input>
</div>
</div>
</q-expansion-item>
</div> </div>
</q-list> </q-list>
<q-dialog v-model="showQRDialog"> <q-dialog v-model="showQRDialog">
@@ -83,9 +83,7 @@
class="text-center" class="text-center"
v-text="$t('export_to_phone_desc')" v-text="$t('export_to_phone_desc')"
></p> ></p>
<lnbits-qrcode <lnbits-qrcode :value="exportUrl"></lnbits-qrcode>
:value="`${baseUrl}wallet?usr=${g.user.id}&wal=${g.wallet.id}`"
></lnbits-qrcode>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
+1 -1
View File
@@ -120,7 +120,7 @@
class="q-mb-md" class="q-mb-md"
></q-input> ></q-input>
<q-input <q-input
v-if="g.user.has_password" v-if="g.user.hasPassword"
v-model="credentialsData.oldPassword" v-model="credentialsData.oldPassword"
type="password" type="password"
autocomplete="off" autocomplete="off"
+234 -3
View File
@@ -15,6 +15,7 @@
<q-space></q-space> <q-space></q-space>
<q-input <q-input
v-if="$q.screen.gt.sm"
:label="$t('search_extensions')" :label="$t('search_extensions')"
:dense="dense" :dense="dense"
class="float-right q-pr-xl" class="float-right q-pr-xl"
@@ -33,6 +34,14 @@
</q-icon> </q-icon>
</template> </template>
</q-input> </q-input>
<q-btn
v-else
flat
icon="search"
@click="searchToggle = !searchToggle"
class="q-mr-md"
>
</q-btn>
<q-badge <q-badge
v-if="g.user.admin && updatableExtensions?.length" v-if="g.user.admin && updatableExtensions?.length"
@click="showUpdateAllDialog = true" @click="showUpdateAllDialog = true"
@@ -68,6 +77,25 @@
><q-tooltip v-text="$t('admin_settings')"></q-tooltip ><q-tooltip v-text="$t('admin_settings')"></q-tooltip
></q-btn> ></q-btn>
</q-tabs> </q-tabs>
<div v-if="$q.screen.lt.sm && searchToggle" class="q-pa-sm">
<q-input
:label="$t('search_extensions')"
dense
class=""
v-model="searchTerm"
autofocus
>
<template v-slot:append>
<q-icon
v-if="searchTerm !== ''"
name="close"
@click="searchTerm = ''"
class="cursor-pointer"
>
</q-icon>
</template>
</q-input>
</div>
</div> </div>
</div> </div>
</q-card> </q-card>
@@ -135,7 +163,22 @@
v-text="extension.name" v-text="extension.name"
></div> ></div>
<div style="justify-content: space-between; display: flex"> <div style="justify-content: space-between; display: flex">
<lnbits-extension-rating :rating="0" /> <lnbits-extension-rating
:rating="
formatAvg(
extension.reviewStats
? extension.reviewStats.avg_rating
: 0
)
"
:count="
extension.reviewStats
? extension.reviewStats.review_count
: null
"
:clickable="!!reviewsUrl"
@click="openReviews(extension)"
/>
<q-btn-group size="xs" style="margin: 5px 0"> <q-btn-group size="xs" style="margin: 5px 0">
<q-btn <q-btn
v-if="extension.hasFreeRelease" v-if="extension.hasFreeRelease"
@@ -970,8 +1013,20 @@
</div> </div>
<div class="col-sm-12 col-md-4 q-pl-sm"> <div class="col-sm-12 col-md-4 q-pl-sm">
<lnbits-extension-rating <lnbits-extension-rating
:rating="0" :rating="
size="2.5em" formatAvg(
selectedExtension && selectedExtension.reviewStats
? selectedExtension.reviewStats.avg_rating
: 0
)
"
:count="
selectedExtension && selectedExtension.reviewStats
? selectedExtension.reviewStats.review_count
: null
"
:clickable="!!reviewsUrl"
@click="openReviews(selectedExtension)"
></lnbits-extension-rating> ></lnbits-extension-rating>
<div class="q-mt-md"> <div class="q-mt-md">
<b> <b>
@@ -1029,6 +1084,182 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-dialog v-model="reviewsDialog.show" position="top">
<q-card
class="q-pa-md lnbits__dialog-card"
style="width: 900px; max-width: 95vw"
>
<q-card-section class="q-gutter-y-sm">
<div class="row items-center">
<div class="col">
<div
class="text-h6"
v-text="
(reviewsDialog.extension && reviewsDialog.extension.name) ||
(reviewsDialog.extension && reviewsDialog.extension.id)
"
></div>
<div
class="text-caption text-grey"
v-text="reviewsDialog.extension && reviewsDialog.extension.id"
></div>
</div>
<div class="col-auto">
<lnbits-extension-rating
:rating="
formatAvg(
reviewsDialog.extension && reviewsDialog.extension.reviewStats
? reviewsDialog.extension.reviewStats.avg_rating
: 0
)
"
:count="
reviewsDialog.extension && reviewsDialog.extension.reviewStats
? reviewsDialog.extension.reviewStats.review_count
: null
"
></lnbits-extension-rating>
</div>
</div>
</q-card-section>
<q-separator></q-separator>
<q-card-section v-if="!reviewsUrl">
<div
class="text-negative"
v-text="$t('reviews_url_not_configured')"
></div>
</q-card-section>
<q-card-section v-else>
<div v-if="reviewsDialog.loading" class="row justify-center q-pa-lg">
<q-spinner-bars color="primary" size="2.55em"></q-spinner-bars>
</div>
<div v-else>
<div v-if="reviewsDialog.error" class="text-negative q-mb-md">
<span v-text="reviewsDialog.error"></span>
</div>
<q-form @submit="submitReview" class="q-gutter-md">
<div class="row q-col-gutter-lg">
<div class="col-12 col-md-6">
<q-input
dense
filled
v-model="reviewsDialog.form.name"
:label="$t('reviews_name')"
></q-input>
</div>
<div class="col-12 col-md-6">
<q-rating
v-model="reviewsDialog.form.rating"
max="10"
icon="star_border"
icon-selected="star"
icon-half="star_half"
color="primary"
class="float-right"
></q-rating>
</div>
<div class="col-12 col-md-6">
<q-input
dense
filled
type="textarea"
autogrow
v-model="reviewsDialog.form.comment"
:label="$t('reviews_comment')"
></q-input>
</div>
<div class="col-12 col-md-6">
<q-btn
type="submit"
color="primary"
class="float-right"
unelevated
:loading="reviewsDialog.submitting"
:disable="!reviewsDialog.form.rating"
:label="$t('reviews_submit')"
></q-btn>
</div>
</div>
</q-form>
<q-separator class="q-my-md"></q-separator>
<div class="row q-col-gutter-lg">
<div class="col-12">
<div v-if="reviews.length === 0" class="text-grey q-mb-md">
<span v-text="$t('no_reviews')"></span>
</div>
<div v-else>
<q-table
:rows="reviews"
:columns="reviewsTable.columns"
v-model:pagination="reviewsTable.pagination"
@request="getTagReviews"
:loading="reviewsTable.loading"
row-key="name"
:filter="reviewsTable.search"
>
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="name" :props="props">
<span v-text="props.row.name"></span>
</q-td>
<q-td key="comment" :props="props">
<span v-text="props.row.comment"></span>
</q-td>
<q-td key="created_at" :props="props">
<span
v-text="formatReviewDate(props.row.created_at)"
></span>
</q-td>
<q-td key="rating" :props="props">
<q-rating
class="float-right q-pt-xs"
readonly
icon="star_border"
icon-selected="star"
icon-half="star_half"
:model-value="formatAvg(props.row.rating)"
size="sm"
color="secondary"
></q-rating>
</q-td>
</q-tr>
</template>
</q-table>
</div>
</div>
</div>
</div>
</q-card-section>
<q-separator v-if="reviews.length"></q-separator>
<q-card-section>
<q-btn
@click="reviewsDialog.show = false"
color="grey"
outline
class="float-right"
:label="$t('close')"
></q-btn>
</q-card-section>
</q-card>
</q-dialog>
<q-dialog v-model="paymentDialog.show" position="top">
<q-card class="q-pa-md lnbits__dialog-card">
<q-card-section>
<q-responsive :ratio="1" class="q-mx-xl q-mb-xl">
<lnbits-qrcode
:value="paymentDialog.invoice"
:options="{width: 800}"
class="rounded-borders"
></lnbits-qrcode>
</q-responsive>
</q-card-section>
<q-card-actions align="between">
<q-btn v-close-popup flat color="grey" :label="$t('close')"></q-btn>
</q-card-actions>
</q-card>
</q-dialog>
<q-dialog v-model="showUpdateAllDialog" position="top"> <q-dialog v-model="showUpdateAllDialog" position="top">
<q-card class="q-pa-md q-pt-md lnbits__dialog-card"> <q-card class="q-pa-md q-pt-md lnbits__dialog-card">
<div class="row"> <div class="row">
+137 -59
View File
@@ -5,7 +5,7 @@ from bolt11.decode import decode
from grpc.aio import AioRpcError from grpc.aio import AioRpcError
from loguru import logger from loguru import logger
from lnbits.helpers import normalize_endpoint from lnbits.helpers import normalize_endpoint, sha256s
from lnbits.settings import settings from lnbits.settings import settings
from lnbits.wallets.boltz_grpc_files import boltzrpc_pb2, boltzrpc_pb2_grpc from lnbits.wallets.boltz_grpc_files import boltzrpc_pb2, boltzrpc_pb2_grpc
from lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc import grpc from lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc import grpc
@@ -38,21 +38,18 @@ class BoltzWallet(Wallet):
raise ValueError( raise ValueError(
"cannot initialize BoltzWallet: missing boltz_client_endpoint" "cannot initialize BoltzWallet: missing boltz_client_endpoint"
) )
if not settings.boltz_client_wallet:
raise ValueError(
"cannot initialize BoltzWallet: missing boltz_client_wallet"
)
self.endpoint = normalize_endpoint( self.endpoint = normalize_endpoint(
settings.boltz_client_endpoint, add_proto=True settings.boltz_client_endpoint, add_proto=True
) )
self.metadata = None
if settings.boltz_client_macaroon: if settings.boltz_client_macaroon:
self.metadata = [ try:
("macaroon", load_macaroon(settings.boltz_client_macaroon)) macaroon = load_macaroon(settings.boltz_client_macaroon)
] self.metadata = [("macaroon", macaroon)]
else: except Exception as e:
self.metadata = None logger.error(f"BoltzWallet failed to load macaroon: {e}")
if settings.boltz_client_cert: if settings.boltz_client_cert:
cert = open(settings.boltz_client_cert, "rb").read() cert = open(settings.boltz_client_cert, "rb").read()
@@ -63,31 +60,21 @@ class BoltzWallet(Wallet):
self.rpc = boltzrpc_pb2_grpc.BoltzStub(channel) self.rpc = boltzrpc_pb2_grpc.BoltzStub(channel)
self.wallet_id = 0 self.wallet_id = 0
self.wallet_name = "lnbits"
# Auto-create wallet if running in Docker mode self.wallet_ready = False
async def _init_boltz_wallet():
try:
wallet_name = settings.boltz_client_wallet or "lnbits"
mnemonic = await self._fetch_wallet(wallet_name)
if mnemonic:
logger.info(
"✅ Mnemonic found for Boltz wallet, saving to settings"
)
settings.boltz_mnemonic = mnemonic
from lnbits.core.crud.settings import set_settings_field
await set_settings_field("boltz_mnemonic", mnemonic)
else:
logger.warning("⚠️ No mnemonic returned from Boltz")
except Exception as e:
logger.error(f"❌ Failed to auto-create Boltz wallet: {e}")
self._init_wallet_task = asyncio.create_task(_init_boltz_wallet())
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
if self.wallet_ready is False:
self.wallet_ready = True
if settings.boltz_mnemonic: # restore wallet from mnemonic
await self._restore_boltz_wallet(
settings.boltz_mnemonic,
settings.boltz_client_password,
)
else: # create new wallet
await self._create_boltz_wallet()
try: try:
request = boltzrpc_pb2.GetWalletRequest(name=settings.boltz_client_wallet) request = boltzrpc_pb2.GetWalletRequest(name=self.wallet_name)
response: boltzrpc_pb2.Wallet = await self.rpc.GetWallet( response: boltzrpc_pb2.Wallet = await self.rpc.GetWallet(
request, metadata=self.metadata request, metadata=self.metadata
) )
@@ -126,12 +113,18 @@ class BoltzWallet(Wallet):
try: try:
response = await self.rpc.CreateReverseSwap(request, metadata=self.metadata) response = await self.rpc.CreateReverseSwap(request, metadata=self.metadata)
except AioRpcError as exc: except AioRpcError as exc:
logger.warning(exc)
return InvoiceResponse(ok=False, error_message=exc.details()) return InvoiceResponse(ok=False, error_message=exc.details())
fee_msat = response.routing_fee_milli_sat
return InvoiceResponse( return InvoiceResponse(
ok=True, checking_id=response.id, payment_request=response.invoice ok=True,
checking_id=response.id,
payment_request=response.invoice,
fee_msat=fee_msat,
) )
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
pair = boltzrpc_pb2.Pair(**{"from": boltzrpc_pb2.LBTC}) pair = boltzrpc_pb2.Pair(**{"from": boltzrpc_pb2.LBTC})
try: try:
pair_info: boltzrpc_pb2.PairInfo pair_info: boltzrpc_pb2.PairInfo
@@ -143,8 +136,9 @@ class BoltzWallet(Wallet):
if not invoice.amount_msat: if not invoice.amount_msat:
raise ValueError("amountless invoice") raise ValueError("amountless invoice")
service_fee: float = invoice.amount_msat * pair_info.fees.percentage / 100 service_fee: float = invoice.amount_msat * pair_info.fees.percentage / 100
estimate = service_fee + pair_info.fees.miner_fees * 1000 estimate = int(service_fee + pair_info.fees.miner_fees * 1000)
if estimate > fee_limit_msat: if estimate > fee_limit_msat:
error = f"fee of {estimate} msat exceeds limit of {fee_limit_msat} msat" error = f"fee of {estimate} msat exceeds limit of {fee_limit_msat} msat"
@@ -166,11 +160,12 @@ class BoltzWallet(Wallet):
if response.id == "": if response.id == "":
# note that there is no way to provide a checking id here, # note that there is no way to provide a checking id here,
# but there is no need since it immediately is considered as successfull # but there is no need since it immediately is considered as successfull
return PaymentResponse( logger.warning(
ok=True, "Boltz invoice paid directly on liquid network using magic routing"
checking_id=response.id,
) )
return PaymentResponse(ok=True, checking_id=invoice.payment_hash)
except AioRpcError as exc: except AioRpcError as exc:
logger.warning(exc)
return PaymentResponse(ok=False, error_message=exc.details()) return PaymentResponse(ok=False, error_message=exc.details())
try: try:
@@ -180,10 +175,15 @@ class BoltzWallet(Wallet):
info_request, metadata=self.metadata info_request, metadata=self.metadata
): ):
if info.swap.state == boltzrpc_pb2.SUCCESSFUL: if info.swap.state == boltzrpc_pb2.SUCCESSFUL:
fee_msat = (info.swap.onchain_fee + info.swap.service_fee) * 1000
logger.debug(
f"Boltz swap successful, status: {info.swap.status}"
f"fee_msat: {fee_msat}"
)
return PaymentResponse( return PaymentResponse(
ok=True, ok=True,
checking_id=response.id, checking_id=invoice.payment_hash,
fee_msat=(info.swap.onchain_fee + info.swap.service_fee) * 1000, fee_msat=fee_msat,
preimage=info.swap.preimage, preimage=info.swap.preimage,
) )
elif info.swap.error != "": elif info.swap.error != "":
@@ -192,21 +192,29 @@ class BoltzWallet(Wallet):
ok=False, error_message="stream stopped unexpectedly" ok=False, error_message="stream stopped unexpectedly"
) )
except AioRpcError as exc: except AioRpcError as exc:
logger.warning(exc)
return PaymentResponse(ok=False, error_message=exc.details()) return PaymentResponse(ok=False, error_message=exc.details())
async def get_invoice_status(self, checking_id: str) -> PaymentStatus: async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
try: try:
request = boltzrpc_pb2.GetSwapInfoRequest(id=checking_id)
response: boltzrpc_pb2.GetSwapInfoResponse = await self.rpc.GetSwapInfo( response: boltzrpc_pb2.GetSwapInfoResponse = await self.rpc.GetSwapInfo(
boltzrpc_pb2.GetSwapInfoRequest(id=checking_id), metadata=self.metadata request,
metadata=self.metadata,
) )
swap = response.reverse_swap swap = response.reverse_swap
except AioRpcError: except AioRpcError as exc:
logger.warning(exc)
return PaymentPendingStatus() return PaymentPendingStatus()
if swap.state == boltzrpc_pb2.SwapState.SUCCESSFUL: if swap.state == boltzrpc_pb2.SwapState.SUCCESSFUL:
fee_msat = (
swap.service_fee + swap.onchain_fee
) * 1000 + swap.routing_fee_msat
logger.debug(
f"Boltz swap successful, status: {swap.status}, fee_msat: {fee_msat}"
)
return PaymentSuccessStatus( return PaymentSuccessStatus(
fee_msat=( fee_msat=fee_msat,
(swap.service_fee + swap.onchain_fee) * 1000 + swap.routing_fee_msat
),
preimage=swap.preimage, preimage=swap.preimage,
) )
elif swap.state == boltzrpc_pb2.SwapState.PENDING: elif swap.state == boltzrpc_pb2.SwapState.PENDING:
@@ -216,18 +224,23 @@ class BoltzWallet(Wallet):
async def get_payment_status(self, checking_id: str) -> PaymentStatus: async def get_payment_status(self, checking_id: str) -> PaymentStatus:
try: try:
checking_id_bytes = bytes.fromhex(checking_id)
request = boltzrpc_pb2.GetSwapInfoRequest(payment_hash=checking_id_bytes)
response: boltzrpc_pb2.GetSwapInfoResponse = await self.rpc.GetSwapInfo( response: boltzrpc_pb2.GetSwapInfoResponse = await self.rpc.GetSwapInfo(
boltzrpc_pb2.GetSwapInfoRequest( request,
payment_hash=bytes.fromhex(checking_id)
),
metadata=self.metadata, metadata=self.metadata,
) )
swap = response.swap swap = response.swap
except AioRpcError: except AioRpcError as exc:
logger.warning(exc)
return PaymentPendingStatus() return PaymentPendingStatus()
if swap.state == boltzrpc_pb2.SwapState.SUCCESSFUL: if swap.state == boltzrpc_pb2.SwapState.SUCCESSFUL:
fee_msat = (swap.service_fee + swap.onchain_fee) * 1000
logger.debug(
f"Boltz swap successful, status: {swap.status}, fee_msat: {fee_msat}"
)
return PaymentSuccessStatus( return PaymentSuccessStatus(
fee_msat=(swap.service_fee + swap.onchain_fee) * 1000, fee_msat=fee_msat,
preimage=swap.preimage, preimage=swap.preimage,
) )
elif swap.state == boltzrpc_pb2.SwapState.PENDING: elif swap.state == boltzrpc_pb2.SwapState.PENDING:
@@ -244,7 +257,16 @@ class BoltzWallet(Wallet):
request, metadata=self.metadata request, metadata=self.metadata
): ):
reverse = info.reverse_swap reverse = info.reverse_swap
if reverse and reverse.state == boltzrpc_pb2.SUCCESSFUL: if (
reverse
and reverse.state == boltzrpc_pb2.SUCCESSFUL
and reverse.status == "invoice.settled"
):
fee_msat = ((reverse.service_fee + reverse.onchain_fee) * 1000,)
logger.debug(
f"Boltz reverse swap settled: {reverse.id}, "
f"fee_msat: {fee_msat}"
)
yield reverse.id yield reverse.id
except Exception as exc: except Exception as exc:
logger.error( logger.error(
@@ -253,24 +275,80 @@ class BoltzWallet(Wallet):
) )
await asyncio.sleep(5) await asyncio.sleep(5)
async def _fetch_wallet(self, wallet_name: str) -> str | None: async def _check_wallet_exists(
self, wallet_name: str
) -> boltzrpc_pb2.Wallet | None:
try: try:
request = boltzrpc_pb2.GetWalletRequest(name=wallet_name) request = boltzrpc_pb2.GetWalletRequest(name=wallet_name)
response = await self.rpc.GetWallet(request, metadata=self.metadata) response = await self.rpc.GetWallet(request, metadata=self.metadata)
logger.info(f"Wallet '{wallet_name}' already exists with ID {response.id}") return response
return settings.boltz_mnemonic except AioRpcError:
except AioRpcError as exc: return None
details = exc.details() or "unknown error"
if exc.code() != grpc.StatusCode.NOT_FOUND:
logger.error(f"Error checking wallet existence: {details}")
raise
async def _delete_wallet(self, wallet_id: int) -> None:
logger.info(f"Deleting wallet '{wallet_id}'")
delete_request = boltzrpc_pb2.RemoveWalletRequest(id=wallet_id)
await self.rpc.RemoveWallet(delete_request, metadata=self.metadata)
async def _restore_wallet(self, wallet_name: str, mnemonic: str, password: str):
logger.info(f"Restoring wallet '{wallet_name}' from mnemonic")
credentials = boltzrpc_pb2.WalletCredentials(mnemonic=mnemonic)
params = boltzrpc_pb2.WalletParams(
name=wallet_name,
currency=boltzrpc_pb2.LBTC,
password=password,
)
restore_request = boltzrpc_pb2.ImportWalletRequest(
credentials=credentials, params=params
)
response = await self.rpc.ImportWallet(restore_request, metadata=self.metadata)
return response
async def _create_wallet(self, wallet_name: str, password: str) -> str:
logger.info(f"Creating new wallet '{wallet_name}'") logger.info(f"Creating new wallet '{wallet_name}'")
params = boltzrpc_pb2.WalletParams( params = boltzrpc_pb2.WalletParams(
name=wallet_name, name=wallet_name,
currency=boltzrpc_pb2.LBTC, currency=boltzrpc_pb2.LBTC,
password=settings.boltz_client_password, password=password,
) )
create_request = boltzrpc_pb2.CreateWalletRequest(params=params) create_request = boltzrpc_pb2.CreateWalletRequest(params=params)
response = await self.rpc.CreateWallet(create_request, metadata=self.metadata) response = await self.rpc.CreateWallet(create_request, metadata=self.metadata)
return response.mnemonic return response.mnemonic
async def _restore_boltz_wallet(self, mnemonic: str, password: str):
try:
# delete the wallet with to name without hashing first if it exists
wallet = await self._check_wallet_exists(self.wallet_name)
if wallet:
await self._delete_wallet(wallet.id)
# recreate the wallet with the hashed name
self.wallet_name = sha256s(mnemonic + password)
wallet = await self._check_wallet_exists(self.wallet_name)
if wallet:
logger.success("✅ Boltz wallet exists.")
return
await self._restore_wallet(self.wallet_name, mnemonic, password)
logger.success("✅ Boltz wallet restored from existing mnemonic")
except Exception as e:
logger.error(f"❌ Failed to restore Boltz wallet: {e}")
async def _create_boltz_wallet(self):
try:
# delete the wallet with to name without hashing first if it exists
wallet = await self._check_wallet_exists(self.wallet_name)
if wallet:
await self._delete_wallet(wallet.id)
logger.info("No Mnemonic found for Boltz wallet, creating wallet...")
mnemonic = await self._create_wallet(
self.wallet_name, settings.boltz_client_password
)
logger.success("✅ Boltz wallet created successfully")
settings.boltz_mnemonic = mnemonic
from lnbits.core.crud.settings import set_settings_field
await set_settings_field("boltz_mnemonic", mnemonic)
except Exception as e:
logger.error(f"❌ Failed to create Boltz wallet: {e}")
+1 -1
View File
@@ -178,7 +178,7 @@ class LndRestWallet(Wallet):
"no_inflight_updates": True, "no_inflight_updates": True,
} }
if settings.lnd_rest_allow_self_payment: if settings.lnd_rest_allow_self_payment:
req["allow_self_payment"] = 1 req["allow_self_payment"] = True
try: try:
r = await self.client.post( r = await self.client.post(
Generated
+222 -199
View File
@@ -14,98 +14,132 @@ files = [
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
version = "3.12.15" version = "3.13.3"
description = "Async http client/server framework (asyncio)" description = "Async http client/server framework (asyncio)"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"] groups = ["main"]
files = [ files = [
{file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc"}, {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7"},
{file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af"}, {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821"},
{file = "aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421"}, {file = "aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845"},
{file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79"}, {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af"},
{file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77"}, {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940"},
{file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c"}, {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160"},
{file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4"}, {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7"},
{file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6"}, {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455"},
{file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2"}, {file = "aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279"},
{file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d"}, {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e"},
{file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb"}, {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d"},
{file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5"}, {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808"},
{file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b"}, {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40"},
{file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065"}, {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29"},
{file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1"}, {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11"},
{file = "aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a"}, {file = "aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd"},
{file = "aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830"}, {file = "aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c"},
{file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117"}, {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b"},
{file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe"}, {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64"},
{file = "aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9"}, {file = "aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea"},
{file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5"}, {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a"},
{file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728"}, {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540"},
{file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16"}, {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b"},
{file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0"}, {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3"},
{file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b"}, {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1"},
{file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd"}, {file = "aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3"},
{file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8"}, {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440"},
{file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50"}, {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7"},
{file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676"}, {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c"},
{file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7"}, {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51"},
{file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7"}, {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4"},
{file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685"}, {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29"},
{file = "aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b"}, {file = "aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239"},
{file = "aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d"}, {file = "aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f"},
{file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"}, {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c"},
{file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"}, {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168"},
{file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"}, {file = "aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d"},
{file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"}, {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29"},
{file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"}, {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3"},
{file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"}, {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d"},
{file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"}, {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463"},
{file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"}, {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc"},
{file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"}, {file = "aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf"},
{file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"}, {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033"},
{file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"}, {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f"},
{file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"}, {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679"},
{file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"}, {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423"},
{file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"}, {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce"},
{file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"}, {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a"},
{file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"}, {file = "aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046"},
{file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"}, {file = "aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57"},
{file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"}, {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c"},
{file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"}, {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9"},
{file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"}, {file = "aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3"},
{file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"}, {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf"},
{file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"}, {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6"},
{file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"}, {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d"},
{file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"}, {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261"},
{file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"}, {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0"},
{file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"}, {file = "aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730"},
{file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"}, {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91"},
{file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"}, {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3"},
{file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"}, {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4"},
{file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"}, {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998"},
{file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"}, {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0"},
{file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"}, {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591"},
{file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"}, {file = "aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf"},
{file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"}, {file = "aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e"},
{file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:691d203c2bdf4f4637792efbbcdcd157ae11e55eaeb5e9c360c1206fb03d4d98"}, {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808"},
{file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e995e1abc4ed2a454c731385bf4082be06f875822adc4c6d9eaadf96e20d406"}, {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415"},
{file = "aiohttp-3.12.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd44d5936ab3193c617bfd6c9a7d8d1085a8dc8c3f44d5f1dcf554d17d04cf7d"}, {file = "aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f"},
{file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46749be6e89cd78d6068cdf7da51dbcfa4321147ab8e4116ee6678d9a056a0cf"}, {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6"},
{file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c643f4d75adea39e92c0f01b3fb83d57abdec8c9279b3078b68a3a52b3933b6"}, {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687"},
{file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a23918fedc05806966a2438489dcffccbdf83e921a1170773b6178d04ade142"}, {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26"},
{file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74bdd8c864b36c3673741023343565d95bfbd778ffe1eb4d412c135a28a8dc89"}, {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a"},
{file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a146708808c9b7a988a4af3821379e379e0f0e5e466ca31a73dbdd0325b0263"}, {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1"},
{file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7011a70b56facde58d6d26da4fec3280cc8e2a78c714c96b7a01a87930a9530"}, {file = "aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25"},
{file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3bdd6e17e16e1dbd3db74d7f989e8af29c4d2e025f9828e6ef45fbdee158ec75"}, {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603"},
{file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57d16590a351dfc914670bd72530fd78344b885a00b250e992faea565b7fdc05"}, {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a"},
{file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc9a0f6569ff990e0bbd75506c8d8fe7214c8f6579cca32f0546e54372a3bb54"}, {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926"},
{file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:536ad7234747a37e50e7b6794ea868833d5220b49c92806ae2d7e8a9d6b5de02"}, {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba"},
{file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f0adb4177fa748072546fb650d9bd7398caaf0e15b370ed3317280b13f4083b0"}, {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c"},
{file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14954a2988feae3987f1eb49c706bff39947605f4b6fa4027c1d75743723eb09"}, {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43"},
{file = "aiohttp-3.12.15-cp39-cp39-win32.whl", hash = "sha256:b784d6ed757f27574dca1c336f968f4e81130b27595e458e69457e6878251f5d"}, {file = "aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1"},
{file = "aiohttp-3.12.15-cp39-cp39-win_amd64.whl", hash = "sha256:86ceded4e78a992f835209e236617bffae649371c4a50d5e5a3987f237db84b8"}, {file = "aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984"},
{file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"}, {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c"},
{file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592"},
{file = "aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f"},
{file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29"},
{file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc"},
{file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2"},
{file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587"},
{file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8"},
{file = "aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632"},
{file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64"},
{file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0"},
{file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56"},
{file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72"},
{file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df"},
{file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa"},
{file = "aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767"},
{file = "aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344"},
{file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31a83ea4aead760dfcb6962efb1d861db48c34379f2ff72db9ddddd4cda9ea2e"},
{file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:988a8c5e317544fdf0d39871559e67b6341065b87fceac641108c2096d5506b7"},
{file = "aiohttp-3.13.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b174f267b5cfb9a7dba9ee6859cecd234e9a681841eb85068059bc867fb8f02"},
{file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:947c26539750deeaee933b000fb6517cc770bbd064bad6033f1cff4803881e43"},
{file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9ebf57d09e131f5323464bd347135a88622d1c0976e88ce15b670e7ad57e4bd6"},
{file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4ae5b5a0e1926e504c81c5b84353e7a5516d8778fbbff00429fe7b05bb25cbce"},
{file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ba0eea45eb5cc3172dbfc497c066f19c41bac70963ea1a67d51fc92e4cf9a80"},
{file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bae5c2ed2eae26cc382020edad80d01f36cb8e746da40b292e68fec40421dc6a"},
{file = "aiohttp-3.13.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a60e60746623925eab7d25823329941aee7242d559baa119ca2b253c88a7bd6"},
{file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e50a2e1404f063427c9d027378472316201a2290959a295169bcf25992d04558"},
{file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9a9dc347e5a3dc7dfdbc1f82da0ef29e388ddb2ed281bfce9dd8248a313e62b7"},
{file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b46020d11d23fe16551466c77823df9cc2f2c1e63cc965daf67fa5eec6ca1877"},
{file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:69c56fbc1993fa17043e24a546959c0178fe2b5782405ad4559e6c13975c15e3"},
{file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b99281b0704c103d4e11e72a76f1b543d4946fea7dd10767e7e1b5f00d4e5704"},
{file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:40c5e40ecc29ba010656c18052b877a1c28f84344825efa106705e835c28530f"},
{file = "aiohttp-3.13.3-cp39-cp39-win32.whl", hash = "sha256:56339a36b9f1fc708260c76c87e593e2afb30d26de9ae1eb445b5e051b98a7a1"},
{file = "aiohttp-3.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:c6b8568a3bb5819a0ad087f16d40e5a3fb6099f39ea1d5625a3edc1e923fc538"},
{file = "aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88"},
] ]
[package.dependencies] [package.dependencies]
@@ -119,7 +153,7 @@ propcache = ">=0.2.0"
yarl = ">=1.17.0,<2.0" yarl = ">=1.17.0,<2.0"
[package.extras] [package.extras]
speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""]
[[package]] [[package]]
name = "aiosignal" name = "aiosignal"
@@ -681,43 +715,43 @@ files = [
[[package]] [[package]]
name = "breez-sdk-liquid" name = "breez-sdk-liquid"
version = "0.9.1" version = "0.11.11"
description = "Python language bindings for the Breez Liquid SDK" description = "Python language bindings for the Breez Liquid SDK"
optional = true optional = true
python-versions = "*" python-versions = "*"
groups = ["main"] groups = ["main"]
markers = "extra == \"breez\"" markers = "extra == \"breez\""
files = [ files = [
{file = "breez_sdk_liquid-0.9.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:414a7c04dbf869435fe234b3b9987699c15dc2f8d4660d9ca1a51af608331b45"}, {file = "breez_sdk_liquid-0.11.11-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0c1da93d69112cee65c07f1bf3efbcfbb9182c5e3d8933ca27adc659eae064e0"},
{file = "breez_sdk_liquid-0.9.1-cp310-cp310-manylinux_2_31_aarch64.whl", hash = "sha256:7f461b6b10ddfe9cfb58119e590e5f67da4d7c93fc580a84a0116ce9fd21dffa"}, {file = "breez_sdk_liquid-0.11.11-cp310-cp310-manylinux_2_31_aarch64.whl", hash = "sha256:c2b2d9a04022e05eb56699b6e8a628e7b53676d6f90b1c461c0b1b38b781d952"},
{file = "breez_sdk_liquid-0.9.1-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:c27efe35539e4092dddbe5315f5cefffccbdd699017ed8733611d11eb52f4ce0"}, {file = "breez_sdk_liquid-0.11.11-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:6fb44fb16bd55a53fcbd22c7a1982bc8b83de655d0b3e677fe6debd366b41890"},
{file = "breez_sdk_liquid-0.9.1-cp310-cp310-win32.whl", hash = "sha256:375f6322c2552d15d6ba68302a85fdc345a7671eeda8fea92c013a16c5e96505"}, {file = "breez_sdk_liquid-0.11.11-cp310-cp310-win32.whl", hash = "sha256:04719223f8b5a44401c02aef6202076e0e557f28476b7cbe787ed9e7947e641a"},
{file = "breez_sdk_liquid-0.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:f28d107099fb7caaac4051d5950a4f11bf823e11b56e797976c5fa2a7280d597"}, {file = "breez_sdk_liquid-0.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0d36272277e8d5b0287b45cacce5444934579ae9f6726de377c9de7d4535dc6f"},
{file = "breez_sdk_liquid-0.9.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:acefa49896bdb4ca06d351faacbf075646d574dda5539033df11310ec5a94446"}, {file = "breez_sdk_liquid-0.11.11-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4ab4029773e5d2f946872ced5b3c76c5686e00cf6786b9515b707e16691e40dd"},
{file = "breez_sdk_liquid-0.9.1-cp311-cp311-manylinux_2_31_aarch64.whl", hash = "sha256:d0e54fd5c8ce542dc2061d8a1ef46e6a1d430dbaea5eab369bb91ae2c4a1be7b"}, {file = "breez_sdk_liquid-0.11.11-cp311-cp311-manylinux_2_31_aarch64.whl", hash = "sha256:b3ab95f85f7454710c312443d4c0bc96fd1f06a4d3453305eb2fc9c510e9441e"},
{file = "breez_sdk_liquid-0.9.1-cp311-cp311-manylinux_2_31_x86_64.whl", hash = "sha256:a5d1bfd4d3286d57c110d925332416b0903dda076dadede02c4b7db67576f0bf"}, {file = "breez_sdk_liquid-0.11.11-cp311-cp311-manylinux_2_31_x86_64.whl", hash = "sha256:bf85cecaad3d433ee9af75814a921d5ae582a74c9f4009c8bc865c820b9f8cd9"},
{file = "breez_sdk_liquid-0.9.1-cp311-cp311-win32.whl", hash = "sha256:3ce2152f0a2ba65f4425fd22efb8e1dafb419e5fcf3b93dfe06fc061b1d1c0c6"}, {file = "breez_sdk_liquid-0.11.11-cp311-cp311-win32.whl", hash = "sha256:f9b0c5393111f7de3511c061f680f94cfd867f6f2e114904c3f5e31fe1fa2824"},
{file = "breez_sdk_liquid-0.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:8cdb4f247bccc0f4b9bbfff4451faa78d7b17e76a9dc918804880b600f1d4249"}, {file = "breez_sdk_liquid-0.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:7d2862108da64e2b73de46f61ff7d2df5607cbb6bb78a1bc437d19d06caa9f93"},
{file = "breez_sdk_liquid-0.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:c463191a89a829afcae19c94f7683dac415667755d68c212afc60d8f2b21ba63"}, {file = "breez_sdk_liquid-0.11.11-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:93faa55da3b4c850b1655b66b8822bd36b40a0491990d31d5d6f9000e0e61521"},
{file = "breez_sdk_liquid-0.9.1-cp312-cp312-manylinux_2_31_aarch64.whl", hash = "sha256:52efaeae2c278b498b37de1090e80f73987ee943743e69a363877ed93cd7a31e"}, {file = "breez_sdk_liquid-0.11.11-cp312-cp312-manylinux_2_31_aarch64.whl", hash = "sha256:337be3e6c59bba6890629fc82e7b8f342bbf19fdb0f9c4a2f0d2aa5e93dfa690"},
{file = "breez_sdk_liquid-0.9.1-cp312-cp312-manylinux_2_31_x86_64.whl", hash = "sha256:68aebea98990319c677bcbd7d1de258627b85063a29720879986694b9d393adb"}, {file = "breez_sdk_liquid-0.11.11-cp312-cp312-manylinux_2_31_x86_64.whl", hash = "sha256:129baf243381bf5ce4d2927f52dabc68f61f9e173d498ee53d9aef55f4e44fb2"},
{file = "breez_sdk_liquid-0.9.1-cp312-cp312-win32.whl", hash = "sha256:f53e4a1973dfd41df3ac3ecb42cf72acb71f49d5727a8acab25668b51a953bb2"}, {file = "breez_sdk_liquid-0.11.11-cp312-cp312-win32.whl", hash = "sha256:9acd3eea87c861ed5cde91e188d38e893090d5de925c9ed3c9b21f9be15abe0d"},
{file = "breez_sdk_liquid-0.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:6adb1f3f3ccd4b743a11dcc0b307db604c3424f42e2547f94fa939f0afb9bb85"}, {file = "breez_sdk_liquid-0.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:466824ad3124ef22262ccc5ce76f5cedefd4a2540bb0cd0408e9b2f4e67ac58a"},
{file = "breez_sdk_liquid-0.9.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f94432a75bc848e597fb9859a77bab6c703d5e423d470519f37c402fc3c5d79e"}, {file = "breez_sdk_liquid-0.11.11-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3708379531b0273da5da7797d857caf2ee8b3837e003e70d36d2592a98f62ee0"},
{file = "breez_sdk_liquid-0.9.1-cp313-cp313-manylinux_2_31_aarch64.whl", hash = "sha256:8d6838e69620162c038d513f95ffade870e112ae5c6a0d713eaa2a3a31d6f212"}, {file = "breez_sdk_liquid-0.11.11-cp313-cp313-manylinux_2_31_aarch64.whl", hash = "sha256:026d33a1fda6d2fc80bc3c6eae7df2924d7bf101050fb71dddfe79a24c6e641a"},
{file = "breez_sdk_liquid-0.9.1-cp313-cp313-manylinux_2_31_x86_64.whl", hash = "sha256:d6b9a8601cef5875447e10c75ca3e1579dd1e881ec57cabe9787616b8a6de263"}, {file = "breez_sdk_liquid-0.11.11-cp313-cp313-manylinux_2_31_x86_64.whl", hash = "sha256:adea3697e464257bc306de8680f82436ae46a6dd3af9390b6a3c55202bba37df"},
{file = "breez_sdk_liquid-0.9.1-cp313-cp313-win32.whl", hash = "sha256:d76fc905003ac5a0cfd2132df90ba6343b40b9948164a0be06a68f15b28789a2"}, {file = "breez_sdk_liquid-0.11.11-cp313-cp313-win32.whl", hash = "sha256:653b8b06a4f891c4a08901dff49a08c8f9f3624089441c6d3f1e558ee7e137fa"},
{file = "breez_sdk_liquid-0.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6b0fc1d77b7855d9a72799d65cdaaa0bd07a3999d6d4d27050d8ef174ce0fd3"}, {file = "breez_sdk_liquid-0.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:6becd498285eb7c9a17c3ccf2a2ae02780919f89e968eadab46003ed39c54099"},
{file = "breez_sdk_liquid-0.9.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d0d560657c29d7e3b054ad4be07955fc41b63d4b93a2b51b29bb85d1d52341af"}, {file = "breez_sdk_liquid-0.11.11-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:978faa63b72f272672dfb77f19617dd80a98f739a40af8748bfa3739e6ff783f"},
{file = "breez_sdk_liquid-0.9.1-cp38-cp38-manylinux_2_31_aarch64.whl", hash = "sha256:2531f788eb15bb00f4b3b505ac5b8ee3de23161dd4f65768a4aa3255ea04d68e"}, {file = "breez_sdk_liquid-0.11.11-cp38-cp38-manylinux_2_31_aarch64.whl", hash = "sha256:78cd9f069e277274cf708f4bbc1d84b6cc152222b2b56ce0019e3daf4750bc51"},
{file = "breez_sdk_liquid-0.9.1-cp38-cp38-manylinux_2_31_x86_64.whl", hash = "sha256:f9c9f6255999cf403bc07a9deed99f8c1625a0405ae9231e3dbecb3b6af0fdc7"}, {file = "breez_sdk_liquid-0.11.11-cp38-cp38-manylinux_2_31_x86_64.whl", hash = "sha256:315074affc1ffe86440fa380367719d623e469205d14b28e42c4f9bc0277836a"},
{file = "breez_sdk_liquid-0.9.1-cp38-cp38-win32.whl", hash = "sha256:5232b25b7e8583100ec54423e7568a305cac7778736adc9f8c735798110350c7"}, {file = "breez_sdk_liquid-0.11.11-cp38-cp38-win32.whl", hash = "sha256:01600323bf8e273a50baac42d431bbb4779ae5108e225c6e4dbee7c535b6d8d1"},
{file = "breez_sdk_liquid-0.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:67d2b894b70ba9011fc98e04ed1ade2ff18f0413b6f2a0994747e71418d004e4"}, {file = "breez_sdk_liquid-0.11.11-cp38-cp38-win_amd64.whl", hash = "sha256:ba21c8bb1f674baad81c077abb046c77ec6994bcb3e4c67f48581173c49f8dc2"},
{file = "breez_sdk_liquid-0.9.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:bd77d2ce43a2c0ad77a7f22226e373034ff57677338b38e4ebe3b77eb34fa224"}, {file = "breez_sdk_liquid-0.11.11-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:ffa2303c39fb5aa872e88e8d42d1e9e0aeafd97dd3c132d3149d97496cc9e037"},
{file = "breez_sdk_liquid-0.9.1-cp39-cp39-manylinux_2_31_aarch64.whl", hash = "sha256:14092d9d65f1b0a9009e64630614e7b8b936e9d382d0f6415f023f0b192f6b45"}, {file = "breez_sdk_liquid-0.11.11-cp39-cp39-manylinux_2_31_aarch64.whl", hash = "sha256:0fd15395866c70cffcfbc1faaf2ca0d1e9f602542aefa02dd9f90478dc43ed8d"},
{file = "breez_sdk_liquid-0.9.1-cp39-cp39-manylinux_2_31_x86_64.whl", hash = "sha256:5faa4d5e9a2b47899783aa0c44ac2274d32f74a7f469506af14520deabaabf9f"}, {file = "breez_sdk_liquid-0.11.11-cp39-cp39-manylinux_2_31_x86_64.whl", hash = "sha256:9ee716853388d23466e28b81b79835a9713faba7a7f7648575aa194c9577e080"},
{file = "breez_sdk_liquid-0.9.1-cp39-cp39-win32.whl", hash = "sha256:b5b2c4bae13655d108475f4f22efdc2b07283f72d99b9c6f99166d9e41e62942"}, {file = "breez_sdk_liquid-0.11.11-cp39-cp39-win32.whl", hash = "sha256:f768e0e8326e26bdb92efd2dfb9850d9f6b51868d0a123986b7d70790c9446db"},
{file = "breez_sdk_liquid-0.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:f3ad7c65582558031451538c8a03caa3486a2d2c8906f2fbbccce84e9357d1ea"}, {file = "breez_sdk_liquid-0.11.11-cp39-cp39-win_amd64.whl", hash = "sha256:f8272efaae258167befe98a6551755beda397ed2a7e10b45a795419380e2c345"},
] ]
[[package]] [[package]]
@@ -1314,14 +1348,14 @@ standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[stand
[[package]] [[package]]
name = "fastapi-sso" name = "fastapi-sso"
version = "0.18.0" version = "0.19.0"
description = "FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)" description = "FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)"
optional = false optional = false
python-versions = "<4.0,>=3.9" python-versions = "<4.0,>=3.9"
groups = ["main"] groups = ["main"]
files = [ files = [
{file = "fastapi_sso-0.18.0-py3-none-any.whl", hash = "sha256:727754ad770b70690f1471f7b0a9e17c6dfd8ebd6e477616d3bde1eaf62e53dc"}, {file = "fastapi_sso-0.19.0-py3-none-any.whl", hash = "sha256:d958c46cd9996234c7b162e192168b4c0807a248224a55b0f877d3a82a16a930"},
{file = "fastapi_sso-0.18.0.tar.gz", hash = "sha256:d8df5a686af7a6a7be248817544b405cf77f7e9ffcd5d0d7d2a196fd071964bc"}, {file = "fastapi_sso-0.19.0.tar.gz", hash = "sha256:629f00581f72ea7e57f7b8775f8d2c425629c428c194359a2b4ebaa6bcb8e12b"},
] ]
[package.dependencies] [package.dependencies]
@@ -1333,21 +1367,16 @@ pyjwt = ">=2.10.1,<3.0.0"
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.18.0" version = "3.20.3"
description = "A platform independent file lock." description = "A platform independent file lock."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.10"
groups = ["dev"] groups = ["dev"]
files = [ files = [
{file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"},
{file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"},
] ]
[package.extras]
docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
[[package]] [[package]]
name = "filetype" name = "filetype"
version = "1.2.0" version = "1.2.0"
@@ -1476,67 +1505,60 @@ files = [
[[package]] [[package]]
name = "greenlet" name = "greenlet"
version = "3.2.4" version = "3.3.0"
description = "Lightweight in-process concurrent programming" description = "Lightweight in-process concurrent programming"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.10"
groups = ["main"] groups = ["main"]
markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""
files = [ files = [
{file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, {file = "greenlet-3.3.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f8496d434d5cb2dce025773ba5597f71f5410ae499d5dd9533e0653258cdb3d"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b96dc7eef78fd404e022e165ec55327f935b9b52ff355b067eb4a0267fc1cffb"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73631cd5cccbcfe63e3f9492aaa664d278fda0ce5c3d43aeda8e77317e38efbd"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b299a0cb979f5d7197442dccc3aee67fce53500cd88951b7e6c35575701c980b"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dee147740789a4632cace364816046e43310b59ff8fb79833ab043aefa72fd5"},
{file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, {file = "greenlet-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:39b28e339fc3c348427560494e28d8a6f3561c8d2bcf7d706e1c624ed8d822b9"},
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, {file = "greenlet-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3c374782c2935cc63b2a27ba8708471de4ad1abaa862ffdb1ef45a643ddbb7d"},
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, {file = "greenlet-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b49e7ed51876b459bd645d83db257f0180e345d3f768a35a85437a24d5a49082"},
{file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, {file = "greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e"},
{file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, {file = "greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, {file = "greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5"},
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, {file = "greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71"},
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, {file = "greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb"},
{file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3"},
{file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, {file = "greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, {file = "greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, {file = "greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39"},
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, {file = "greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739"},
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808"},
{file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54"},
{file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, {file = "greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, {file = "greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, {file = "greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, {file = "greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f"},
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365"},
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3"},
{file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45"},
{file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, {file = "greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, {file = "greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, {file = "greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, {file = "greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388"},
{file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3"},
{file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, {file = "greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, {file = "greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, {file = "greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"},
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"},
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"},
{file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"},
{file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"},
{file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"},
] ]
[package.extras] [package.extras]
@@ -4060,14 +4082,14 @@ files = [
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.6.0" version = "2.6.3"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main", "dev"] groups = ["main", "dev"]
files = [ files = [
{file = "urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f"}, {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"},
{file = "urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1"}, {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"},
] ]
[package.extras] [package.extras]
@@ -4150,20 +4172,21 @@ test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil",
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.31.2" version = "20.36.1"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"] groups = ["dev"]
files = [ files = [
{file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"}, {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"},
{file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"}, {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"},
] ]
[package.dependencies] [package.dependencies]
distlib = ">=0.3.7,<1" distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4" filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""}
platformdirs = ">=3.9.1,<5" platformdirs = ">=3.9.1,<5"
typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
@@ -4314,14 +4337,14 @@ files = [
[[package]] [[package]]
name = "werkzeug" name = "werkzeug"
version = "3.1.4" version = "3.1.5"
description = "The comprehensive WSGI web application library." description = "The comprehensive WSGI web application library."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["dev"] groups = ["dev"]
files = [ files = [
{file = "werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905"}, {file = "werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc"},
{file = "werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e"}, {file = "werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67"},
] ]
[package.dependencies] [package.dependencies]
@@ -4564,4 +4587,4 @@ migration = ["psycopg2-binary"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.10,<3.13" python-versions = ">=3.10,<3.13"
content-hash = "13f4b6c9c33e051dba10dfa6833c11bb3aabdd4cce395fa8480d1fa4ee9ddb96" content-hash = "b5509b4d38ab0c064d293d93105f075fa19eff10760c1bba27fbe5b96a464ac3"
+4 -3
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "lnbits" name = "lnbits"
version = "1.4.0-rc2" version = "1.4.2"
requires-python = ">=3.10,<3.13" requires-python = ">=3.10,<3.13"
description = "LNbits, free and open-source Lightning wallet and accounts system." description = "LNbits, free and open-source Lightning wallet and accounts system."
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }] authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
@@ -38,7 +38,7 @@ dependencies = [
"bolt11==2.1.1", "bolt11==2.1.1",
"pyjwt==2.10.1", "pyjwt==2.10.1",
"itsdangerous==2.2.0", "itsdangerous==2.2.0",
"fastapi-sso==0.18.0", "fastapi-sso==0.19.0",
# needed for boltz, lnurldevice, watchonly extensions # needed for boltz, lnurldevice, watchonly extensions
"embit==0.8.0", "embit==0.8.0",
# needed for scheduler extension # needed for scheduler extension
@@ -51,6 +51,7 @@ dependencies = [
"jsonpath-ng==1.7.0", "jsonpath-ng==1.7.0",
"pillow>=12.0.0", "pillow>=12.0.0",
"python-dotenv>=1.2.1", "python-dotenv>=1.2.1",
"greenlet (>=3.3.0,<4.0.0)",
] ]
[project.scripts] [project.scripts]
@@ -58,7 +59,7 @@ lnbits = "lnbits.server:main"
lnbits-cli = "lnbits.commands:main" lnbits-cli = "lnbits.commands:main"
[project.optional-dependencies] [project.optional-dependencies]
breez = ["breez-sdk==0.8.0", "breez-sdk-liquid==0.9.1"] breez = ["breez-sdk==0.8.0", "breez-sdk-liquid==0.11.11"]
liquid = ["wallycore==1.4.0"] liquid = ["wallycore==1.4.0"]
migration = ["psycopg2-binary==2.9.10"] migration = ["psycopg2-binary==2.9.10"]
+100
View File
@@ -5,6 +5,7 @@ import pytest
import shortuuid import shortuuid
from httpx import AsyncClient from httpx import AsyncClient
from lnbits.core.crud.wallets import get_wallets
from lnbits.core.models.users import Account, User from lnbits.core.models.users import Account, User
from lnbits.core.services.users import create_user_account from lnbits.core.services.users import create_user_account
from lnbits.settings import Settings from lnbits.settings import Settings
@@ -510,3 +511,102 @@ async def test_search_users(http_client: AsyncClient, superuser_token):
data = create_resp.json() data = create_resp.json()
assert data["total"] == 1 assert data["total"] == 1
assert data["data"][0]["username"] == users[0].username assert data["data"][0]["username"] == users[0].username
@pytest.mark.anyio
async def test_delete_user_success(http_client: AsyncClient, superuser_token):
# Create a user first
tiny_id = shortuuid.uuid()[:8]
data = {
"username": f"delete_{tiny_id}",
"password": "secret1234",
"password_repeat": "secret1234",
"email": f"delete_{tiny_id}@lnbits.com",
}
create_resp = await http_client.post(
"/users/api/v1/user",
json=data,
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert create_resp.status_code == 200
user = create_resp.json()
user_id = user["id"]
wallets = await get_wallets(user_id=user_id)
assert len(wallets) == 1
# Delete the user
delete_resp = await http_client.delete(
f"/users/api/v1/user/{user_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert delete_resp.status_code == 200
assert delete_resp.json()["success"] is True
# Ensure user is deleted
get_resp = await http_client.get(
f"/users/api/v1/user/{user_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert get_resp.status_code == 404
wallets = await get_wallets(user_id=user_id)
assert len(wallets) == 0
@pytest.mark.anyio
async def test_delete_and_undelete_wallet(http_client: AsyncClient, superuser_token):
# Create a user
tiny_id = shortuuid.uuid()[:8]
user_data = {
"username": f"walletuser_{tiny_id}",
"password": "secret1234",
"password_repeat": "secret1234",
"email": f"walletuser_{tiny_id}@lnbits.com",
}
user_resp = await http_client.post(
"/users/api/v1/user",
json=user_data,
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert user_resp.status_code == 200
user = user_resp.json()
user_id = user["id"]
# Create a wallet for the user
wallet_resp = await http_client.post(
f"/users/api/v1/user/{user_id}/wallet",
json={"name": "Test Wallet"},
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert wallet_resp.status_code == 200
wallet = wallet_resp.json()
wallet_id = wallet["id"]
# Delete the wallet (soft delete)
delete_resp = await http_client.delete(
f"/users/api/v1/user/{user_id}/wallet/{wallet_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert delete_resp.status_code == 200
assert delete_resp.json()["success"] is True
# Wallet should be marked as deleted
wallets = await get_wallets(user_id=user_id, deleted=True)
deleted_wallet = next((w for w in wallets if w.id == wallet_id), None)
assert deleted_wallet is not None
assert deleted_wallet.deleted is True
# Undelete the wallet
undelete_resp = await http_client.put(
f"/users/api/v1/user/{user_id}/wallet/{wallet_id}/undelete",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert undelete_resp.status_code == 200
assert undelete_resp.json()["success"] is True
# Wallet should be active again
wallets = await get_wallets(user_id=user_id, deleted=False)
undeleted_wallet = next((w for w in wallets if w.id == wallet_id), None)
assert undeleted_wallet is not None
assert undeleted_wallet.deleted is False
+2 -2
View File
@@ -331,8 +331,8 @@ def _settings_cleanup(settings: Settings):
settings.lnbits_allowed_users = [] settings.lnbits_allowed_users = []
settings.auth_allowed_methods = AuthMethods.all() settings.auth_allowed_methods = AuthMethods.all()
settings.auth_credetials_update_threshold = 120 settings.auth_credetials_update_threshold = 120
settings.lnbits_reserve_fee_percent = 1 settings.lnbits_reserve_fee_percent = 2
settings.lnbits_reserve_fee_min = 2000 settings.lnbits_reserve_fee_min = 20000
settings.lnbits_service_fee = 0 settings.lnbits_service_fee = 0
settings.lnbits_reserve_fee_percent = 0 settings.lnbits_reserve_fee_percent = 0
settings.lnbits_wallet_limit_daily_max_withdraw = 0 settings.lnbits_wallet_limit_daily_max_withdraw = 0
+22 -1
View File
@@ -6,6 +6,11 @@ from subprocess import PIPE, Popen, TimeoutExpired
from loguru import logger from loguru import logger
from lnbits.wallets import get_funding_source
funding_source = get_funding_source()
is_boltz_wallet = funding_source.__class__.__name__ == "BoltzWallet"
docker_lightning_cli = [ docker_lightning_cli = [
"docker", "docker",
"exec", "exec",
@@ -27,6 +32,16 @@ docker_bitcoin_cli = [
] ]
docker_elements_cli = [
"docker",
"exec",
"lnbits-elementsd-1",
"elements-cli",
"-rpcport=18884",
"-chain=liquidregtest",
]
docker_lightning_unconnected_cli = [ docker_lightning_unconnected_cli = [
"docker", "docker",
"exec", "exec",
@@ -50,7 +65,7 @@ docker_lightning_noroute_cli = [
def run_cmd(cmd: list) -> str: def run_cmd(cmd: list) -> str:
timeout = 10 timeout = 30
process = Popen(cmd, stdout=PIPE, stderr=PIPE) process = Popen(cmd, stdout=PIPE, stderr=PIPE)
logger.debug(f"running command: {cmd}") logger.debug(f"running command: {cmd}")
@@ -128,6 +143,12 @@ def mine_blocks(blocks: int = 1) -> str:
return run_cmd(cmd) return run_cmd(cmd)
def mine_blocks_liquid(blocks: int = 1) -> str:
cmd = docker_elements_cli.copy()
cmd.extend(["-generate", str(blocks)])
return run_cmd(cmd)
def get_unconnected_node_uri() -> str: def get_unconnected_node_uri() -> str:
cmd = docker_lightning_unconnected_cli.copy() cmd = docker_lightning_unconnected_cli.copy()
cmd.append("getinfo") cmd.append("getinfo")
+12 -4
View File
@@ -18,6 +18,7 @@ from ..helpers import FakeError, is_fake, is_regtest
from .helpers import ( from .helpers import (
cancel_invoice, cancel_invoice,
get_real_invoice, get_real_invoice,
mine_blocks_liquid,
pay_real_invoice, pay_real_invoice,
settle_invoice, settle_invoice,
) )
@@ -66,7 +67,7 @@ async def test_pay_real_invoice(
await asyncio.sleep(1) await asyncio.sleep(1)
balance = await get_node_balance_sats() balance = await get_node_balance_sats()
assert prev_balance - balance == 100 assert prev_balance - balance == 100 + abs(payment.fee // 1000)
@pytest.mark.anyio @pytest.mark.anyio
@@ -153,7 +154,8 @@ async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_
async def on_paid(payment: Payment): async def on_paid(payment: Payment):
assert payment.checking_id == invoice["payment_hash"] assert payment.payment_hash == invoice["payment_hash"]
assert payment.checking_id == invoice["checking_id"]
response = await client.get( response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
@@ -164,7 +166,8 @@ async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_
await asyncio.sleep(1) await asyncio.sleep(1)
balance = await get_node_balance_sats() balance = await get_node_balance_sats()
assert balance - prev_balance == create_invoice.amount fee = abs(payment_status.get("details", {}).get("fee", 0) // 1000)
assert balance - prev_balance == create_invoice.amount - fee
assert payment_status.get("preimage") is not None assert payment_status.get("preimage") is not None
@@ -200,6 +203,8 @@ async def test_pay_real_invoice_set_pending_and_check_state(
assert len(invoice["payment_hash"]) == 64 assert len(invoice["payment_hash"]) == 64
assert len(invoice["checking_id"]) > 0 assert len(invoice["checking_id"]) > 0
mine_blocks_liquid(1)
await asyncio.sleep(1)
# check the payment status # check the payment status
response = await client.get( response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
@@ -341,6 +346,7 @@ async def test_pay_hold_invoice_check_pending_and_fail_cancel_payment_task_in_me
assert preimage_hash == invoice_obj.payment_hash assert preimage_hash == invoice_obj.payment_hash
cancel_invoice(preimage_hash) cancel_invoice(preimage_hash)
mine_blocks_liquid(1)
# check if paid # check if paid
await asyncio.sleep(1) await asyncio.sleep(1)
@@ -382,7 +388,9 @@ async def test_receive_real_invoice_set_pending_and_check_state(
assert not payment_status["paid"] assert not payment_status["paid"]
async def on_paid(payment: Payment): async def on_paid(payment: Payment):
assert payment.checking_id == invoice["payment_hash"]
assert payment.payment_hash == invoice["payment_hash"]
assert payment.checking_id == invoice["checking_id"]
response = await client.get( response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
+10 -2
View File
@@ -7,6 +7,8 @@ from lnbits.core.services import (
from lnbits.wallets import get_funding_source from lnbits.wallets import get_funding_source
from lnbits.wallets.base import PaymentStatus from lnbits.wallets.base import PaymentStatus
from .helpers import is_boltz_wallet
description = "test create invoice" description = "test create invoice"
@@ -17,7 +19,10 @@ async def test_create_invoice(from_wallet):
amount=1000, amount=1000,
memo=description, memo=description,
) )
assert payment.preimage
# we cannot know the preimage of the swap yet
if not is_boltz_wallet:
assert payment.preimage
invoice = decode(payment.bolt11) invoice = decode(payment.bolt11)
assert invoice.payment_hash == payment.payment_hash assert invoice.payment_hash == payment.payment_hash
@@ -35,7 +40,10 @@ async def test_create_internal_invoice(from_wallet):
payment = await create_invoice( payment = await create_invoice(
wallet_id=from_wallet.id, amount=1000, memo=description, internal=True wallet_id=from_wallet.id, amount=1000, memo=description, internal=True
) )
assert payment.preimage
# we cannot know the preimage of the swap yet
if not is_boltz_wallet:
assert payment.preimage
invoice = decode(payment.bolt11) invoice = decode(payment.bolt11)
assert invoice.payment_hash == payment.payment_hash assert invoice.payment_hash == payment.payment_hash
+7 -2
View File
@@ -6,6 +6,8 @@ from lnbits.core.services import (
) )
from lnbits.exceptions import PaymentError from lnbits.exceptions import PaymentError
from .helpers import is_boltz_wallet
description = "test pay invoice" description = "test pay invoice"
@@ -17,9 +19,12 @@ async def test_services_pay_invoice(to_wallet, real_invoice):
description=description, description=description,
) )
assert payment assert payment
assert payment.status == PaymentState.SUCCESS
assert payment.memo == description assert payment.memo == description
assert payment.preimage if not is_boltz_wallet:
assert payment.status == PaymentState.SUCCESS
assert payment.preimage
else:
assert payment.status == PaymentState.PENDING
@pytest.mark.anyio @pytest.mark.anyio
+12
View File
@@ -1,3 +1,5 @@
import asyncio
import pytest import pytest
from lnbits.core.crud import ( from lnbits.core.crud import (
@@ -6,6 +8,7 @@ from lnbits.core.crud import (
get_payments_paginated, get_payments_paginated,
update_payment, update_payment,
) )
from lnbits.core.crud.payments import get_standalone_payment
from lnbits.core.models import PaymentFilters, PaymentState from lnbits.core.models import PaymentFilters, PaymentState
from lnbits.core.services import ( from lnbits.core.services import (
create_invoice, create_invoice,
@@ -39,6 +42,15 @@ async def test_crud_get_payments(app):
filters = Filters(limit=100) filters = Filters(limit=100)
payments = await get_payments(wallet_id=wallet.id, filters=filters) payments = await get_payments(wallet_id=wallet.id, filters=filters)
assert len(payments) == 22, "should return 22 successful payments" assert len(payments) == 22, "should return 22 successful payments"
first_payment = payments[0]
first_payment_updated_at = first_payment.updated_at.replace()
await update_payment(first_payment)
await asyncio.sleep(0.1) # ensure updated_at will be different
first_payment_updated = await get_standalone_payment(first_payment.checking_id)
assert first_payment_updated, "Updated payment not found"
assert (
first_payment_updated.updated_at > first_payment_updated_at
), "Updated payment timestamp is not newer"
payments = await get_payments(wallet_id=wallet.id, incoming=True, filters=filters) payments = await get_payments(wallet_id=wallet.id, incoming=True, filters=filters)
assert len(payments) == 11, "should return 11 successful incoming payments" assert len(payments) == 11, "should return 11 successful incoming payments"
+53 -16
View File
@@ -8,19 +8,21 @@ import os
import sqlite3 import sqlite3
import sys import sys
from loguru import logger
from lnbits.settings import settings from lnbits.settings import settings
try: try:
import psycopg2 # type: ignore import psycopg2 # type: ignore
except ImportError: except ImportError:
print("Please install psycopg2") logger.warning("Please install psycopg2")
sys.exit(1) sys.exit(1)
sqfolder = settings.lnbits_data_folder sqfolder = settings.lnbits_data_folder
db_url = settings.lnbits_database_url db_url = settings.lnbits_database_url
if db_url is None: if db_url is None:
print("missing LNBITS_DATABASE_URL") logger.warning("missing LNBITS_DATABASE_URL")
sys.exit(1) sys.exit(1)
else: else:
# parse postgres://lnbits:postgres@localhost:5432/lnbits # parse postgres://lnbits:postgres@localhost:5432/lnbits
@@ -66,7 +68,7 @@ def check_db_versions(sqdb):
postgres.close() postgres.close()
connection.close() connection.close()
print("Database versions OK, converting") logger.info("Database versions OK, converting")
def fix_id(seq, values): def fix_id(seq, values):
@@ -95,11 +97,11 @@ def insert_to_pg(query, data):
cursor.execute(query, d) cursor.execute(query, d)
except Exception as exc: except Exception as exc:
if args.ignore_errors: if args.ignore_errors:
print(exc) logger.error(exc)
print(f"Failed to insert {d}") logger.error(f"Failed to insert {d}")
else: else:
print("query:", query) logger.error("query:", query)
print("data:", d) logger.error("data:", d)
raise ValueError(f"Failed to insert {d}") from exc raise ValueError(f"Failed to insert {d}") from exc
connection.commit() connection.commit()
@@ -110,17 +112,20 @@ def insert_to_pg(query, data):
def migrate_core(file: str, exclude_tables: list[str] | None = None): def migrate_core(file: str, exclude_tables: list[str] | None = None):
if exclude_tables is None: if exclude_tables is None:
exclude_tables = [] exclude_tables = []
print(f"Migrating core: {file}") logger.info(f"Migrating core: {file}")
migrate_db(file, "public", exclude_tables) migrate_db(file, "public", exclude_tables)
print("✅ Migrated core") logger.info("✅ Migrated core")
def migrate_ext(file: str): def migrate_ext(file: str):
filename = os.path.basename(file) filename = os.path.basename(file)
schema = filename.replace("ext_", "").split(".")[0] schema = filename.replace("ext_", "").split(".")[0]
print(f"Migrating ext: {schema} from file {file}") try:
migrate_db(file, schema) logger.info(f"Migrating ext: {schema} from file {file}")
print(f"✅ Migrated ext: {schema}") migrate_db(file, schema)
logger.info(f"✅ Migrated ext: {schema}")
except Exception as exc:
logger.error(f"🛑 Failed to migrate extension {schema}: {exc}")
def migrate_db(file: str, schema: str, exclude_tables: list[str] | None = None): def migrate_db(file: str, schema: str, exclude_tables: list[str] | None = None):
@@ -139,7 +144,7 @@ def migrate_db(file: str, schema: str, exclude_tables: list[str] | None = None):
for table in tables: for table in tables:
table_name = table[0] table_name = table[0]
print(f"Migrating table {table_name}") logger.info(f"Migrating table {table_name}")
# hard coded skip for dbversions (already produced during startup) # hard coded skip for dbversions (already produced during startup)
if table_name == "dbversions": if table_name == "dbversions":
continue continue
@@ -152,7 +157,7 @@ def migrate_db(file: str, schema: str, exclude_tables: list[str] | None = None):
data = cursor.execute(f"SELECT * FROM {table_name};").fetchall() data = cursor.execute(f"SELECT * FROM {table_name};").fetchall()
if len(data) == 0: if len(data) == 0:
print(f"🛑 You sneaky dev! Table {table_name} is empty!") logger.warning(f"🛑 You sneaky dev! Table {table_name} is empty!")
insert_to_pg(q, data) insert_to_pg(q, data)
cursor.close() cursor.close()
@@ -161,12 +166,44 @@ def migrate_db(file: str, schema: str, exclude_tables: list[str] | None = None):
def build_insert_query(schema, table_name, columns): def build_insert_query(schema, table_name, columns):
to_columns = ", ".join([f'"{column[1].lower()}"' for column in columns]) to_columns = ", ".join([f'"{column[1].lower()}"' for column in columns])
values = ", ".join([to_column_type(column[2]) for column in columns]) values = ", ".join([to_column_type(column[2]) for column in columns])
on_conflict_update = build_on_conflict_query_statement(schema, table_name, columns)
return f""" return f"""
INSERT INTO {schema}.{table_name}({to_columns}) INSERT INTO {schema}.{table_name}({to_columns})
VALUES ({values}); VALUES ({values})
{on_conflict_update}
""" """
def build_on_conflict_query_statement(schema, table_name, columns):
unique_cols = table_unique_columns(schema, table_name)
if len(unique_cols) == 0:
return ""
return f"""
ON CONFLICT ({", ".join([f'"{col}"' for col in unique_cols])})
DO UPDATE SET
{", ".join([
f'"{column[1].lower()}"=EXCLUDED."{column[1].lower()}"'
for column in columns
])}
"""
def table_unique_columns(schema, table_name):
cursor = get_postgres_cursor()
query = f"""
SELECT a.attname
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = '{schema}.{table_name}'::regclass
AND i.indisunique;
"""
cursor.execute(query)
columns = [row[0] for row in cursor.fetchall()]
cursor.close()
return columns
def to_column_type(column_type): def to_column_type(column_type):
if column_type == "TIMESTAMP": if column_type == "TIMESTAMP":
return "to_timestamp(%s)" return "to_timestamp(%s)"
@@ -218,7 +255,7 @@ parser.add_argument(
args = parser.parse_args() args = parser.parse_args()
print("Selected path: ", args.sqlite_path) logger.info("Selected path: ", args.sqlite_path)
if os.path.isdir(args.sqlite_path): if os.path.isdir(args.sqlite_path):
exclude_tables = ["dbversions"] exclude_tables = ["dbversions"]
Generated
+56 -63
View File
@@ -407,24 +407,24 @@ wheels = [
[[package]] [[package]]
name = "breez-sdk-liquid" name = "breez-sdk-liquid"
version = "0.9.1" version = "0.11.11"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/18/82738bb946fbd5a62fe363f2e9b608a7ca9e2e9ca8d566787da56af3b93f/breez_sdk_liquid-0.9.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:414a7c04dbf869435fe234b3b9987699c15dc2f8d4660d9ca1a51af608331b45", size = 26024860, upload-time = "2025-05-22T12:42:07.945Z" }, { url = "https://files.pythonhosted.org/packages/1f/ba/38408250e136343c5e7ee063f187df29a391c233fac32f8bf924f0d00c85/breez_sdk_liquid-0.11.11-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0c1da93d69112cee65c07f1bf3efbcfbb9182c5e3d8933ca27adc659eae064e0", size = 24855120, upload-time = "2025-12-01T16:16:31.212Z" },
{ url = "https://files.pythonhosted.org/packages/95/e2/8c86e571c2e1798b5d0ef20ebb588e6e76a85ebf0136b600fbfb334b7153/breez_sdk_liquid-0.9.1-cp310-cp310-manylinux_2_31_aarch64.whl", hash = "sha256:7f461b6b10ddfe9cfb58119e590e5f67da4d7c93fc580a84a0116ce9fd21dffa", size = 16400772, upload-time = "2025-05-22T12:42:10.925Z" }, { url = "https://files.pythonhosted.org/packages/e0/0a/73a0f2ce7550c04bb78939709d7f0c4c0d8222821457d8ec0791f0c0dd3d/breez_sdk_liquid-0.11.11-cp310-cp310-manylinux_2_31_aarch64.whl", hash = "sha256:c2b2d9a04022e05eb56699b6e8a628e7b53676d6f90b1c461c0b1b38b781d952", size = 16045266, upload-time = "2025-12-01T16:16:33.679Z" },
{ url = "https://files.pythonhosted.org/packages/96/41/9f49558415ac13df2371fc9d486aaf0d07d49735caf845dd944b3cbe5f3c/breez_sdk_liquid-0.9.1-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:c27efe35539e4092dddbe5315f5cefffccbdd699017ed8733611d11eb52f4ce0", size = 15658538, upload-time = "2025-05-22T12:42:13.421Z" }, { url = "https://files.pythonhosted.org/packages/75/e9/ddc34c2ebd2a2181c61669bebc5f40853ff7a72aad0bea8162d8c6329b65/breez_sdk_liquid-0.11.11-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:6fb44fb16bd55a53fcbd22c7a1982bc8b83de655d0b3e677fe6debd366b41890", size = 15214330, upload-time = "2025-12-01T16:16:35.994Z" },
{ url = "https://files.pythonhosted.org/packages/7d/f4/81fe1dbb63b13611e5541d5aae70e6a0166f73208e0d01aa56a954bb521a/breez_sdk_liquid-0.9.1-cp310-cp310-win32.whl", hash = "sha256:375f6322c2552d15d6ba68302a85fdc345a7671eeda8fea92c013a16c5e96505", size = 10526907, upload-time = "2025-05-22T12:42:16.258Z" }, { url = "https://files.pythonhosted.org/packages/f7/19/84f59b425683a1be127b0fe203d3040ee21fbd7f773dbe6e6153b288372d/breez_sdk_liquid-0.11.11-cp310-cp310-win32.whl", hash = "sha256:04719223f8b5a44401c02aef6202076e0e557f28476b7cbe787ed9e7947e641a", size = 10214705, upload-time = "2025-12-01T16:16:39.137Z" },
{ url = "https://files.pythonhosted.org/packages/38/11/b8f2f0daa02f97fb1b23c32cad5f195fcf30bf7093dda982d01d58d4f51e/breez_sdk_liquid-0.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:f28d107099fb7caaac4051d5950a4f11bf823e11b56e797976c5fa2a7280d597", size = 11079422, upload-time = "2025-05-22T12:42:18.709Z" }, { url = "https://files.pythonhosted.org/packages/eb/d0/2d94e9afa5a7900a3c75b8a428386c3ba48de8a383eecd2b5403ad4d2b1d/breez_sdk_liquid-0.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0d36272277e8d5b0287b45cacce5444934579ae9f6726de377c9de7d4535dc6f", size = 10743378, upload-time = "2025-12-01T16:16:40.884Z" },
{ url = "https://files.pythonhosted.org/packages/7e/70/b428e714c433d9193ef6917059f4d9d8e9d1c32d1d2eee632ce65efdca3b/breez_sdk_liquid-0.9.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:acefa49896bdb4ca06d351faacbf075646d574dda5539033df11310ec5a94446", size = 26024861, upload-time = "2025-05-22T12:42:21.166Z" }, { url = "https://files.pythonhosted.org/packages/a7/c8/324891a06997e7c8a9713891484b4350613754db663067fac89fd5d0025f/breez_sdk_liquid-0.11.11-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4ab4029773e5d2f946872ced5b3c76c5686e00cf6786b9515b707e16691e40dd", size = 24855121, upload-time = "2025-12-01T16:16:43.094Z" },
{ url = "https://files.pythonhosted.org/packages/42/ab/f92f258ba54053a48948b54912f72d9124e92699c809267473034c2a3797/breez_sdk_liquid-0.9.1-cp311-cp311-manylinux_2_31_aarch64.whl", hash = "sha256:d0e54fd5c8ce542dc2061d8a1ef46e6a1d430dbaea5eab369bb91ae2c4a1be7b", size = 16400775, upload-time = "2025-05-22T12:42:23.886Z" }, { url = "https://files.pythonhosted.org/packages/82/04/93adc12245f6e53fb036f4f51eb59b463e629f372543c6a72a31a3903a82/breez_sdk_liquid-0.11.11-cp311-cp311-manylinux_2_31_aarch64.whl", hash = "sha256:b3ab95f85f7454710c312443d4c0bc96fd1f06a4d3453305eb2fc9c510e9441e", size = 16045269, upload-time = "2025-12-01T16:16:45.194Z" },
{ url = "https://files.pythonhosted.org/packages/0d/e3/0fec9245a1006151fe572d46cf50f3c882c5ca15cef306a63f1b467d2bf7/breez_sdk_liquid-0.9.1-cp311-cp311-manylinux_2_31_x86_64.whl", hash = "sha256:a5d1bfd4d3286d57c110d925332416b0903dda076dadede02c4b7db67576f0bf", size = 15658540, upload-time = "2025-05-22T12:42:25.914Z" }, { url = "https://files.pythonhosted.org/packages/04/51/e831d1c0d2639a33b4123e991acbc594838c4a22ac356b9f027e09ac1d32/breez_sdk_liquid-0.11.11-cp311-cp311-manylinux_2_31_x86_64.whl", hash = "sha256:bf85cecaad3d433ee9af75814a921d5ae582a74c9f4009c8bc865c820b9f8cd9", size = 15214332, upload-time = "2025-12-01T16:16:48.287Z" },
{ url = "https://files.pythonhosted.org/packages/83/2c/4c508330a2735a49a815e06ae7876a57fb75a41e256f5dd70994df8f0f20/breez_sdk_liquid-0.9.1-cp311-cp311-win32.whl", hash = "sha256:3ce2152f0a2ba65f4425fd22efb8e1dafb419e5fcf3b93dfe06fc061b1d1c0c6", size = 10526908, upload-time = "2025-05-22T12:42:27.803Z" }, { url = "https://files.pythonhosted.org/packages/81/ad/c46ae0bcbf3479f41a4c4f1d11e9d6d7152bfcea84e85de2fe53077c0093/breez_sdk_liquid-0.11.11-cp311-cp311-win32.whl", hash = "sha256:f9b0c5393111f7de3511c061f680f94cfd867f6f2e114904c3f5e31fe1fa2824", size = 10214707, upload-time = "2025-12-01T16:16:50.345Z" },
{ url = "https://files.pythonhosted.org/packages/3f/c4/1ac65ea96afd5e847f3027eeab67c629e03f19d5fb95fe6f36d6042812cc/breez_sdk_liquid-0.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:8cdb4f247bccc0f4b9bbfff4451faa78d7b17e76a9dc918804880b600f1d4249", size = 11079420, upload-time = "2025-05-22T12:42:29.988Z" }, { url = "https://files.pythonhosted.org/packages/cc/2b/262bd6dbba9a9ce021290b8fa724eabed7dd562dd1a419147ef92fd3e6b2/breez_sdk_liquid-0.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:7d2862108da64e2b73de46f61ff7d2df5607cbb6bb78a1bc437d19d06caa9f93", size = 10743377, upload-time = "2025-12-01T16:16:52.028Z" },
{ url = "https://files.pythonhosted.org/packages/00/6b/0adb00ea6ac9f9c8b917a690859e16f01f097533808c8ebe261c71171bf0/breez_sdk_liquid-0.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:c463191a89a829afcae19c94f7683dac415667755d68c212afc60d8f2b21ba63", size = 2113486, upload-time = "2025-05-22T12:42:31.709Z" }, { url = "https://files.pythonhosted.org/packages/ec/ad/1733513975b1363844fb341194f67f53b78ce0fe942b5cc9650b93630974/breez_sdk_liquid-0.11.11-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:93faa55da3b4c850b1655b66b8822bd36b40a0491990d31d5d6f9000e0e61521", size = 24855161, upload-time = "2025-12-01T16:16:54.455Z" },
{ url = "https://files.pythonhosted.org/packages/d3/c2/5d3f8feeaf61d04c5266385df5f96bc86c0f2f30536c0aea195d02018dcd/breez_sdk_liquid-0.9.1-cp312-cp312-manylinux_2_31_aarch64.whl", hash = "sha256:52efaeae2c278b498b37de1090e80f73987ee943743e69a363877ed93cd7a31e", size = 16400811, upload-time = "2025-05-22T12:42:33.062Z" }, { url = "https://files.pythonhosted.org/packages/e0/40/400ac6ce7f3c1cdf58558d6d4cc42dbee6a4a8fd46dde362a0c979aac400/breez_sdk_liquid-0.11.11-cp312-cp312-manylinux_2_31_aarch64.whl", hash = "sha256:337be3e6c59bba6890629fc82e7b8f342bbf19fdb0f9c4a2f0d2aa5e93dfa690", size = 16045270, upload-time = "2025-12-01T16:16:56.668Z" },
{ url = "https://files.pythonhosted.org/packages/90/90/78e5394e103861bddfa96cf57346e525789825e5f86beb650796f9c3bdc9/breez_sdk_liquid-0.9.1-cp312-cp312-manylinux_2_31_x86_64.whl", hash = "sha256:68aebea98990319c677bcbd7d1de258627b85063a29720879986694b9d393adb", size = 15658577, upload-time = "2025-05-22T12:42:35.501Z" }, { url = "https://files.pythonhosted.org/packages/1c/6a/5428ba3ce098ee4c62d4da9212d08a9017a83614df796e8224aee791745b/breez_sdk_liquid-0.11.11-cp312-cp312-manylinux_2_31_x86_64.whl", hash = "sha256:129baf243381bf5ce4d2927f52dabc68f61f9e173d498ee53d9aef55f4e44fb2", size = 15214331, upload-time = "2025-12-01T16:16:58.766Z" },
{ url = "https://files.pythonhosted.org/packages/d6/82/0384263df1d6f214faacdedb2e4080ec63c26749249262dab51a06695147/breez_sdk_liquid-0.9.1-cp312-cp312-win32.whl", hash = "sha256:f53e4a1973dfd41df3ac3ecb42cf72acb71f49d5727a8acab25668b51a953bb2", size = 10526907, upload-time = "2025-05-22T12:42:37.851Z" }, { url = "https://files.pythonhosted.org/packages/ca/0c/e8f64c8ca1e8468abc7a17efa5a248029b724417cdb9181125178cffe83b/breez_sdk_liquid-0.11.11-cp312-cp312-win32.whl", hash = "sha256:9acd3eea87c861ed5cde91e188d38e893090d5de925c9ed3c9b21f9be15abe0d", size = 10214706, upload-time = "2025-12-01T16:17:00.776Z" },
{ url = "https://files.pythonhosted.org/packages/0c/6e/8b3fe3e0d89c0d5ca6d85bdbdd6e99be31faf07a716666bd3c605f8d1a06/breez_sdk_liquid-0.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:6adb1f3f3ccd4b743a11dcc0b307db604c3424f42e2547f94fa939f0afb9bb85", size = 11079422, upload-time = "2025-05-22T12:42:40.968Z" }, { url = "https://files.pythonhosted.org/packages/5c/8f/d7aa56c865ac060a540eb6fb214d6ea597c7f3408980ef5d0b1555c5984b/breez_sdk_liquid-0.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:466824ad3124ef22262ccc5ce76f5cedefd4a2540bb0cd0408e9b2f4e67ac58a", size = 10743378, upload-time = "2025-12-01T16:17:02.784Z" },
] ]
[[package]] [[package]]
@@ -784,7 +784,7 @@ wheels = [
[[package]] [[package]]
name = "fastapi-sso" name = "fastapi-sso"
version = "0.18.0" version = "0.19.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "fastapi" }, { name = "fastapi" },
@@ -793,18 +793,18 @@ dependencies = [
{ name = "pydantic", extra = ["email"] }, { name = "pydantic", extra = ["email"] },
{ name = "pyjwt" }, { name = "pyjwt" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/d2/57/cc971c018af5d09eb5f8d1cd12abdd99ab4c59ea5c0b0b1b96349ffe117d/fastapi_sso-0.18.0.tar.gz", hash = "sha256:d8df5a686af7a6a7be248817544b405cf77f7e9ffcd5d0d7d2a196fd071964bc", size = 16811, upload-time = "2025-03-20T17:09:09.958Z" } sdist = { url = "https://files.pythonhosted.org/packages/74/fc/644bc8f82fc887fffcf9a3eab8eb3dea06ee9ea160ef20441455ed7a0001/fastapi_sso-0.19.0.tar.gz", hash = "sha256:629f00581f72ea7e57f7b8775f8d2c425629c428c194359a2b4ebaa6bcb8e12b", size = 17278, upload-time = "2025-12-17T15:18:06.721Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/03/70ca13994f5569d343a9f99dba2930c8ae3471171f161b8887d44b6c526f/fastapi_sso-0.18.0-py3-none-any.whl", hash = "sha256:727754ad770b70690f1471f7b0a9e17c6dfd8ebd6e477616d3bde1eaf62e53dc", size = 26103, upload-time = "2025-03-20T17:09:08.656Z" }, { url = "https://files.pythonhosted.org/packages/d7/61/d2ac0800cb1f390ec2aad464ea4cc5944de285fabc8f987327da4633f17d/fastapi_sso-0.19.0-py3-none-any.whl", hash = "sha256:d958c46cd9996234c7b162e192168b4c0807a248224a55b0f877d3a82a16a930", size = 26419, upload-time = "2025-12-17T15:18:05.439Z" },
] ]
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.20.0" version = "3.20.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" },
] ]
[[package]] [[package]]
@@ -878,43 +878,34 @@ wheels = [
[[package]] [[package]]
name = "greenlet" name = "greenlet"
version = "3.2.4" version = "3.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/7d/ed/6bfa4109fcb23a58819600392564fea69cdc6551ffd5e69ccf1d52a40cbc/greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c", size = 271061, upload-time = "2025-08-07T13:17:15.373Z" }, { url = "https://files.pythonhosted.org/packages/32/6a/33d1702184d94106d3cdd7bfb788e19723206fce152e303473ca3b946c7b/greenlet-3.3.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f8496d434d5cb2dce025773ba5597f71f5410ae499d5dd9533e0653258cdb3d", size = 273658, upload-time = "2025-12-04T14:23:37.494Z" },
{ url = "https://files.pythonhosted.org/packages/2a/fc/102ec1a2fc015b3a7652abab7acf3541d58c04d3d17a8d3d6a44adae1eb1/greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590", size = 629475, upload-time = "2025-08-07T13:42:54.009Z" }, { url = "https://files.pythonhosted.org/packages/d6/b7/2b5805bbf1907c26e434f4e448cd8b696a0b71725204fa21a211ff0c04a7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b96dc7eef78fd404e022e165ec55327f935b9b52ff355b067eb4a0267fc1cffb", size = 574810, upload-time = "2025-12-04T14:50:04.154Z" },
{ url = "https://files.pythonhosted.org/packages/c5/26/80383131d55a4ac0fb08d71660fd77e7660b9db6bdb4e8884f46d9f2cc04/greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", size = 640802, upload-time = "2025-08-07T13:45:25.52Z" }, { url = "https://files.pythonhosted.org/packages/94/38/343242ec12eddf3d8458c73f555c084359883d4ddc674240d9e61ec51fd6/greenlet-3.3.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73631cd5cccbcfe63e3f9492aaa664d278fda0ce5c3d43aeda8e77317e38efbd", size = 586248, upload-time = "2025-12-04T14:57:39.35Z" },
{ url = "https://files.pythonhosted.org/packages/9f/7c/e7833dbcd8f376f3326bd728c845d31dcde4c84268d3921afcae77d90d08/greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", size = 636703, upload-time = "2025-08-07T13:53:12.622Z" }, { url = "https://files.pythonhosted.org/packages/f0/d0/0ae86792fb212e4384041e0ef8e7bc66f59a54912ce407d26a966ed2914d/greenlet-3.3.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b299a0cb979f5d7197442dccc3aee67fce53500cd88951b7e6c35575701c980b", size = 597403, upload-time = "2025-12-04T15:07:10.831Z" },
{ url = "https://files.pythonhosted.org/packages/e9/49/547b93b7c0428ede7b3f309bc965986874759f7d89e4e04aeddbc9699acb/greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", size = 635417, upload-time = "2025-08-07T13:18:25.189Z" }, { url = "https://files.pythonhosted.org/packages/b6/a8/15d0aa26c0036a15d2659175af00954aaaa5d0d66ba538345bd88013b4d7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dee147740789a4632cace364816046e43310b59ff8fb79833ab043aefa72fd5", size = 586910, upload-time = "2025-12-04T14:25:59.705Z" },
{ url = "https://files.pythonhosted.org/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" }, { url = "https://files.pythonhosted.org/packages/e1/9b/68d5e3b7ccaba3907e5532cf8b9bf16f9ef5056a008f195a367db0ff32db/greenlet-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:39b28e339fc3c348427560494e28d8a6f3561c8d2bcf7d706e1c624ed8d822b9", size = 1547206, upload-time = "2025-12-04T15:04:21.027Z" },
{ url = "https://files.pythonhosted.org/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" }, { url = "https://files.pythonhosted.org/packages/66/bd/e3086ccedc61e49f91e2cfb5ffad9d8d62e5dc85e512a6200f096875b60c/greenlet-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3c374782c2935cc63b2a27ba8708471de4ad1abaa862ffdb1ef45a643ddbb7d", size = 1613359, upload-time = "2025-12-04T14:27:26.548Z" },
{ url = "https://files.pythonhosted.org/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" }, { url = "https://files.pythonhosted.org/packages/f4/6b/d4e73f5dfa888364bbf02efa85616c6714ae7c631c201349782e5b428925/greenlet-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b49e7ed51876b459bd645d83db257f0180e345d3f768a35a85437a24d5a49082", size = 300740, upload-time = "2025-12-04T14:47:52.773Z" },
{ url = "https://files.pythonhosted.org/packages/f1/29/74242b7d72385e29bcc5563fba67dad94943d7cd03552bac320d597f29b2/greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7", size = 1544904, upload-time = "2025-11-04T12:42:04.763Z" }, { url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" },
{ url = "https://files.pythonhosted.org/packages/c8/e2/1572b8eeab0f77df5f6729d6ab6b141e4a84ee8eb9bc8c1e7918f94eda6d/greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8", size = 1611228, upload-time = "2025-11-04T12:42:08.423Z" }, { url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" },
{ url = "https://files.pythonhosted.org/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" }, { url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" },
{ url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, { url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" },
{ url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, { url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" },
{ url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, { url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" },
{ url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, { url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" },
{ url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, { url = "https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71", size = 301164, upload-time = "2025-12-04T14:42:51.577Z" },
{ url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" },
{ url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" },
{ url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" },
{ url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" }, { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" },
{ url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" }, { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" },
{ url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" },
{ url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" },
{ url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" },
{ url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" },
{ url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" },
{ url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" },
{ url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" },
{ url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" },
{ url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" },
{ url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" },
{ url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" },
{ url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" },
] ]
[[package]] [[package]]
@@ -1247,7 +1238,7 @@ wheels = [
[[package]] [[package]]
name = "lnbits" name = "lnbits"
version = "1.4.0rc2" version = "1.4.2"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiosqlite" }, { name = "aiosqlite" },
@@ -1261,6 +1252,7 @@ dependencies = [
{ name = "fastapi" }, { name = "fastapi" },
{ name = "fastapi-sso" }, { name = "fastapi-sso" },
{ name = "filetype" }, { name = "filetype" },
{ name = "greenlet" },
{ name = "grpcio" }, { name = "grpcio" },
{ name = "httpx" }, { name = "httpx" },
{ name = "itsdangerous" }, { name = "itsdangerous" },
@@ -1337,13 +1329,14 @@ requires-dist = [
{ name = "bech32", specifier = "==1.2.0" }, { name = "bech32", specifier = "==1.2.0" },
{ name = "bolt11", specifier = "==2.1.1" }, { name = "bolt11", specifier = "==2.1.1" },
{ name = "breez-sdk", marker = "extra == 'breez'", specifier = "==0.8.0" }, { name = "breez-sdk", marker = "extra == 'breez'", specifier = "==0.8.0" },
{ name = "breez-sdk-liquid", marker = "extra == 'breez'", specifier = "==0.9.1" }, { name = "breez-sdk-liquid", marker = "extra == 'breez'", specifier = "==0.11.11" },
{ name = "click", specifier = "==8.2.1" }, { name = "click", specifier = "==8.2.1" },
{ name = "ecdsa", specifier = "==0.19.1" }, { name = "ecdsa", specifier = "==0.19.1" },
{ name = "embit", specifier = "==0.8.0" }, { name = "embit", specifier = "==0.8.0" },
{ name = "fastapi", specifier = "==0.116.1" }, { name = "fastapi", specifier = "==0.116.1" },
{ name = "fastapi-sso", specifier = "==0.18.0" }, { name = "fastapi-sso", specifier = "==0.19.0" },
{ name = "filetype", specifier = "==1.2.0" }, { name = "filetype", specifier = "==1.2.0" },
{ name = "greenlet", specifier = ">=3.3.0,<4.0.0" },
{ name = "grpcio", specifier = "==1.69.0" }, { name = "grpcio", specifier = "==1.69.0" },
{ name = "httpx", specifier = "==0.27.0" }, { name = "httpx", specifier = "==0.27.0" },
{ name = "itsdangerous", specifier = "==2.2.0" }, { name = "itsdangerous", specifier = "==2.2.0" },
@@ -2691,7 +2684,7 @@ wheels = [
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.35.4" version = "20.36.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "distlib" }, { name = "distlib" },
@@ -2699,9 +2692,9 @@ dependencies = [
{ name = "platformdirs" }, { name = "platformdirs" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" },
] ]
[[package]] [[package]]