Compare commits

...
16 Commits
Author SHA1 Message Date
dni ⚡andGitHub 15984fa49b chore: update to v1.3.0-rc4 (#3323) 2025-08-19 12:10:52 +02:00
dni ⚡andGitHub 67dc096909 feat: use pydantic mypy plugin to have improved linting of models (#3311) 2025-08-19 11:37:00 +02:00
dni ⚡andGitHub 2daddf8b74 chore: update lnurl to v0.7.3 (#3320) 2025-08-19 11:34:19 +02:00
dni ⚡andGitHub ae6cf47244 feat: lud11 disposable and storeable payRequests. (#3317) 2025-08-19 11:33:57 +02:00
dni ⚡andGitHub 1488f580ff feat: frontend component for LNURLs (#3321) 2025-08-19 11:18:02 +02:00
dni ⚡andGitHub 609c949394 chore: update to v1.3.0-rc3 (#3313) 2025-08-18 10:07:29 +02:00
dni ⚡andGitHub c24d2f24cf fix: bump lnurl its not callback_url now, its just url (#3312) 2025-08-18 09:18:11 +02:00
dni ⚡andGitHub 0ba81ec719 bug: propergate LnurlErrorResponse in lnurl service and dont checks for redundant LnurlPayActionResponse (#3314) 2025-08-14 13:37:44 +02:00
dni ⚡andGitHub 5e03707be5 fix: mispelling in logs #3306 (#3310) 2025-08-13 14:40:24 +02:00
ArcandGitHub 50f1111356 Fix: fake wallet (#3309) 2025-08-13 14:39:42 +02:00
dni ⚡andGitHub 112a0f9698 fix: lnaddresses on new lnurlscan POST endpoint (#3305) 2025-08-13 08:45:36 +02:00
ArcandGitHub 75016d1a27 fix: lnurl_decode changed to url_decode (#3303) 2025-08-04 09:25:09 +02:00
Tiago VasconcelosandGitHub 5b9f244a9f fix: prevent overflow on dialog (#3295) 2025-07-31 20:06:17 +03:00
dni ⚡andGitHub 0d2ff2e663 feat: add lud17 support for wallet parse dialog (#3289) 2025-07-23 15:27:00 +02:00
Tiago VasconcelosandGitHub bdfefc2506 [Fix] Don't allow pay for expired invoice (#3290) 2025-07-23 15:25:27 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2a9f471783 build(deps): bump form-data from 4.0.0 to 4.0.4 (#3288)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-22 14:34:15 +03:00
29 changed files with 715 additions and 163 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ async def create_wallet(
async def update_wallet( async def update_wallet(
wallet: Wallet, wallet: Wallet,
conn: Optional[Connection] = None, conn: Optional[Connection] = None,
) -> Optional[Wallet]: ) -> Wallet:
wallet.updated_at = datetime.now(timezone.utc) wallet.updated_at = datetime.now(timezone.utc)
await (conn or db).update("wallets", wallet) await (conn or db).update("wallets", wallet)
return wallet return wallet
+8
View File
@@ -735,3 +735,11 @@ async def m032_add_external_id_to_accounts(db: Connection):
async def m033_update_payment_table(db: Connection): async def m033_update_payment_table(db: Connection):
await db.execute("ALTER TABLE apipayments ADD COLUMN fiat_provider TEXT") await db.execute("ALTER TABLE apipayments ADD COLUMN fiat_provider TEXT")
async def m034_add_stored_paylinks_to_wallet(db: Connection):
await db.execute(
"""
ALTER TABLE wallets ADD COLUMN stored_paylinks TEXT
"""
)
+19 -3
View File
@@ -1,11 +1,13 @@
from time import time
from typing import Optional from typing import Optional
from lnurl import Lnurl, LnurlPayResponse from lnurl import LnAddress, Lnurl, LnurlPayResponse
from pydantic import BaseModel from pydantic import BaseModel, Field
class CreateLnurlPayment(BaseModel): class CreateLnurlPayment(BaseModel):
res: LnurlPayResponse res: LnurlPayResponse | None = None
lnurl: Lnurl | LnAddress | None = None
amount: int amount: int
comment: Optional[str] = None comment: Optional[str] = None
unit: Optional[str] = None unit: Optional[str] = None
@@ -14,3 +16,17 @@ class CreateLnurlPayment(BaseModel):
class CreateLnurlWithdraw(BaseModel): class CreateLnurlWithdraw(BaseModel):
lnurl_w: Lnurl lnurl_w: Lnurl
class LnurlScan(BaseModel):
lnurl: Lnurl | LnAddress
class StoredPayLink(BaseModel):
lnurl: str
label: str
last_used: int = Field(default_factory=lambda: int(time()))
class StoredPayLinks(BaseModel):
links: list[StoredPayLink] = []
+2 -11
View File
@@ -1,15 +1,13 @@
from __future__ import annotations from __future__ import annotations
import hashlib
import hmac
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timezone from datetime import datetime, timezone
from enum import Enum from enum import Enum
from ecdsa import SECP256k1, SigningKey
from lnurl import encode as lnurl_encode from lnurl import encode as lnurl_encode
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from lnbits.core.models.lnurl import StoredPayLinks
from lnbits.db import FilterModel from lnbits.db import FilterModel
from lnbits.helpers import url_for from lnbits.helpers import url_for
from lnbits.settings import settings from lnbits.settings import settings
@@ -41,6 +39,7 @@ class Wallet(BaseModel):
currency: str | None = None currency: str | None = None
balance_msat: int = Field(default=0, no_database=True) balance_msat: int = Field(default=0, no_database=True)
extra: WalletExtra = WalletExtra() extra: WalletExtra = WalletExtra()
stored_paylinks: StoredPayLinks = StoredPayLinks()
@property @property
def balance(self) -> int: def balance(self) -> int:
@@ -58,14 +57,6 @@ class Wallet(BaseModel):
except Exception: except Exception:
return "" return ""
def lnurlauth_key(self, domain: str) -> SigningKey:
hashing_key = hashlib.sha256(self.id.encode()).digest()
linking_key = hmac.digest(hashing_key, domain.encode(), "sha256")
return SigningKey.from_string(
linking_key, curve=SECP256k1, hashfunc=hashlib.sha256
)
class CreateWallet(BaseModel): class CreateWallet(BaseModel):
name: str | None = None name: str | None = None
+95 -10
View File
@@ -1,41 +1,66 @@
from time import time
from lnurl import ( from lnurl import (
LnAddress,
Lnurl,
LnurlErrorResponse,
LnurlPayActionResponse, LnurlPayActionResponse,
LnurlPayResponse, LnurlPayResponse,
LnurlResponseException, LnurlResponseException,
execute_pay_request, execute_pay_request,
handle, handle,
) )
from loguru import logger
from lnbits.core.models import CreateLnurlPayment from lnbits.core.crud import update_wallet
from lnbits.core.models import CreateLnurlPayment, Wallet
from lnbits.core.models.lnurl import StoredPayLink
from lnbits.settings import settings from lnbits.settings import settings
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
async def get_pr_from_lnurl(lnurl: str, amount_msat: int) -> str: async def get_pr_from_lnurl(
lnurl: str, amount_msat: int, comment: str | None = None
) -> str:
res = await handle(lnurl, user_agent=settings.user_agent, timeout=10) res = await handle(lnurl, user_agent=settings.user_agent, timeout=10)
if isinstance(res, LnurlErrorResponse):
raise LnurlResponseException(res.reason)
if not isinstance(res, LnurlPayResponse): if not isinstance(res, LnurlPayResponse):
raise LnurlResponseException( raise LnurlResponseException(
"Invalid LNURL response. Expected LnurlPayResponse." "Invalid LNURL response. Expected LnurlPayResponse."
) )
res2 = await execute_pay_request( res2 = await execute_pay_request(
res, res,
msat=str(amount_msat), msat=amount_msat,
comment=comment,
user_agent=settings.user_agent, user_agent=settings.user_agent,
timeout=10, timeout=10,
) )
if not isinstance(res, LnurlPayActionResponse): if isinstance(res2, LnurlErrorResponse):
raise LnurlResponseException( raise LnurlResponseException(res2.reason)
"Invalid LNURL pay response. Expected LnurlPayActionResponse."
)
return res2.pr return res2.pr
async def fetch_lnurl_pay_request(data: CreateLnurlPayment) -> LnurlPayActionResponse: async def fetch_lnurl_pay_request(
data: CreateLnurlPayment, wallet: Wallet | None = None
) -> tuple[LnurlPayResponse, LnurlPayActionResponse]:
""" """
Pay an LNURL payment request. Pay an LNURL payment request.
optional `wallet` is used to store the pay link in the wallet's stored links.
raises `LnurlResponseException` if pay request fails raises `LnurlResponseException` if pay request fails
""" """
if not data.res and data.lnurl:
res = await handle(data.lnurl, user_agent=settings.user_agent, timeout=5)
if isinstance(res, LnurlErrorResponse):
raise LnurlResponseException(res.reason)
if not isinstance(res, LnurlPayResponse):
raise LnurlResponseException(
"Invalid LNURL response. Expected LnurlPayResponse."
)
data.res = res
if not data.res:
raise LnurlResponseException("No LNURL pay request provided.")
if data.unit and data.unit != "sat": if data.unit and data.unit != "sat":
# shift to float with 2 decimal places # shift to float with 2 decimal places
@@ -45,9 +70,69 @@ async def fetch_lnurl_pay_request(data: CreateLnurlPayment) -> LnurlPayActionRes
else: else:
amount_msat = data.amount amount_msat = data.amount
return await execute_pay_request( res2 = await execute_pay_request(
data.res, data.res,
msat=str(amount_msat), msat=amount_msat,
comment=data.comment,
user_agent=settings.user_agent, user_agent=settings.user_agent,
timeout=10, timeout=10,
) )
if wallet:
await store_paylink(data.res, res2, wallet, data.lnurl)
return data.res, res2
async def store_paylink(
res: LnurlPayResponse,
res2: LnurlPayActionResponse,
wallet: Wallet,
lnurl: LnAddress | Lnurl | None = None,
) -> None:
if res2.disposable is not False:
return # do not store disposable LNURL pay links
logger.debug(f"storing lnurl pay link for wallet {wallet.id}. ")
stored_paylink = None
# If we have only a LnurlPayResponse, we can use its lnaddress
# because the lnurl is not available.
if not lnurl:
for _data in res.metadata.list():
if _data[0] == "text/identifier":
stored_paylink = StoredPayLink(
lnurl=LnAddress(_data[1]), label=res.metadata.text
)
if not stored_paylink:
logger.warning(
"No lnaddress found in metadata for LNURL pay link. "
"Skipping storage."
)
return # skip if lnaddress not found in metadata
else:
if isinstance(lnurl, Lnurl):
_lnurl = str(lnurl.lud17 or lnurl.bech32)
else:
_lnurl = str(lnurl)
stored_paylink = StoredPayLink(lnurl=_lnurl, label=res.metadata.text)
# update last_used if its already stored
for pl in wallet.stored_paylinks.links:
if pl.lnurl == stored_paylink.lnurl:
pl.last_used = int(time())
await update_wallet(wallet)
logger.debug(
"Updated last used time for LNURL "
f"pay link {stored_paylink.lnurl} in wallet {wallet.id}."
)
return
# if not already stored, append it
if not any(stored_paylink.lnurl == pl.lnurl for pl in wallet.stored_paylinks.links):
wallet.stored_paylinks.links.append(stored_paylink)
await update_wallet(wallet)
logger.debug(
f"Stored LNURL pay link {stored_paylink.lnurl} for wallet {wallet.id}."
)
+80 -2
View File
@@ -289,9 +289,87 @@
</q-expansion-item> </q-expansion-item>
<q-separator></q-separator> <q-separator></q-separator>
{% endif %} {% endif %}
<q-expansion-item <q-expansion-item
v-if="'{{ LNBITS_DENOMINATION }}' == 'sats'" group="extras"
icon="qr_code"
v-if="stored_paylinks.length > 0"
:label="$t('stored_paylinks')"
>
<q-card>
<q-card-section>
<div class="row flex" v-for="paylink in stored_paylinks">
<q-btn
dense
flat
color="primary"
icon="send"
size="xs"
@click="sendToPaylink(paylink.lnurl)"
>
<q-tooltip>
<span v-text="`send to: ${paylink.lnurl}`"></span>
</q-tooltip>
</q-btn>
<q-btn
dense
flat
color="secondary"
icon="content_copy"
size="xs"
@click="copyText(paylink.lnurl)"
>
<q-tooltip>
<span v-text="`copy: ${paylink.lnurl}`"></span>
</q-tooltip>
</q-btn>
<span
v-text="paylink.label"
class="q-mr-xs q-ml-xs"
></span>
<q-btn dense flat color="primary" icon="edit" size="xs">
<q-popup-edit
@update:model-value="editPaylink()"
v-model="paylink.label"
v-slot="scope"
>
<q-input
dark
color="white"
v-model="scope.value"
dense
autofocus
counter
@keyup.enter="scope.set"
>
<template v-slot:append>
<q-icon name="edit" />
</template>
</q-input>
</q-popup-edit>
<q-tooltip>
<span v-text="$t('edit')"></span>
</q-tooltip>
</q-btn>
<span style="flex-grow: 1"></span>
<q-btn
dense
flat
color="red"
icon="delete"
size="xs"
@click="deletePaylink(paylink.lnurl)"
>
<q-tooltip>
<span v-text="$t('delete')"></span>
</q-tooltip>
</q-btn>
<span v-text="dateFromNow(paylink.last_used)"></span>
</div>
</q-card-section>
</q-card>
</q-expansion-item>
<q-separator></q-separator>
<q-expansion-item
group="extras" group="extras"
icon="phone_android" icon="phone_android"
:label="$t('access_wallet_on_mobile')" :label="$t('access_wallet_on_mobile')"
+1 -1
View File
@@ -147,7 +147,7 @@ async def api_download_backup() -> FileResponse:
pg_backup_filename = f"{settings.lnbits_data_folder}/lnbits-database.dmp" pg_backup_filename = f"{settings.lnbits_data_folder}/lnbits-database.dmp"
is_pg = db_url and db_url.startswith("postgres://") is_pg = db_url and db_url.startswith("postgres://")
if is_pg: if is_pg and db_url:
p = urlparse(db_url) p = urlparse(db_url)
command = ( command = (
f"pg_dump --host={p.hostname} " f"pg_dump --host={p.hostname} "
+1 -1
View File
@@ -231,7 +231,7 @@ async def api_disable_extension(
return SimpleStatus( return SimpleStatus(
success=True, message=f"Extension '{ext_id}' already disabled." success=True, message=f"Extension '{ext_id}' already disabled."
) )
logger.info(f"Disabeling extension: {ext_id}.") logger.info(f"Disabling extension: {ext_id}.")
user_ext.active = False user_ext.active = False
await update_user_extension(user_ext) await update_user_extension(user_ext)
return SimpleStatus(success=True, message=f"Extension '{ext_id}' disabled.") return SimpleStatus(success=True, message=f"Extension '{ext_id}' disabled.")
+2 -2
View File
@@ -7,7 +7,7 @@ from fastapi import Cookie, Depends, Query, Request
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
from fastapi.routing import APIRouter from fastapi.routing import APIRouter
from lnurl import decode as lnurl_decode from lnurl import url_decode
from pydantic.types import UUID4 from pydantic.types import UUID4
from lnbits.core.helpers import to_valid_user_id from lnbits.core.helpers import to_valid_user_id
@@ -456,7 +456,7 @@ async def lnurlwallet(request: Request, lightning: str = ""):
if not settings.lnbits_allow_new_accounts: if not settings.lnbits_allow_new_accounts:
return {"status": "ERROR", "reason": "New accounts are not allowed."} return {"status": "ERROR", "reason": "New accounts are not allowed."}
lnurl = lnurl_decode(lightning) lnurl = url_decode(lightning)
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
check_callback_url(lnurl) check_callback_url(lnurl)
+51 -19
View File
@@ -23,7 +23,7 @@ from lnurl.models import (
from loguru import logger from loguru import logger
from lnbits.core.models import CreateLnurlWithdraw, Payment from lnbits.core.models import CreateLnurlWithdraw, Payment
from lnbits.core.models.lnurl import CreateLnurlPayment from lnbits.core.models.lnurl import CreateLnurlPayment, LnurlScan
from lnbits.decorators import ( from lnbits.decorators import (
WalletTypeInfo, WalletTypeInfo,
require_admin_key, require_admin_key,
@@ -37,27 +37,46 @@ from ..services import fetch_lnurl_pay_request, pay_invoice
lnurl_router = APIRouter(tags=["LNURL"]) lnurl_router = APIRouter(tags=["LNURL"])
async def _handle(lnurl: str) -> LnurlResponseModel:
try:
res = await lnurl_handle(lnurl, user_agent=settings.user_agent, timeout=5)
if isinstance(res, LnurlErrorResponse):
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=res.reason)
except LnurlResponseException as exc:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
) from exc
return res
@lnurl_router.get( @lnurl_router.get(
"/api/v1/lnurlscan/{code}", "/api/v1/lnurlscan/{code}",
dependencies=[Depends(require_invoice_key)], dependencies=[Depends(require_invoice_key)],
deprecated=True,
response_model=LnurlPayResponse response_model=LnurlPayResponse
| LnurlWithdrawResponse | LnurlWithdrawResponse
| LnurlAuthResponse | LnurlAuthResponse
| LnurlErrorResponse, | LnurlErrorResponse,
) )
async def api_lnurlscan(code: str) -> LnurlResponseModel: async def api_lnurlscan(code: str) -> LnurlResponseModel:
try: res = await _handle(code)
res = await lnurl_handle(code, user_agent=settings.user_agent, timeout=5)
except LnurlResponseException as exc:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
) from exc
if isinstance(res, (LnurlPayResponse, LnurlWithdrawResponse, LnurlAuthResponse)): if isinstance(res, (LnurlPayResponse, LnurlWithdrawResponse, LnurlAuthResponse)):
check_callback_url(res.callback) check_callback_url(res.callback)
return res return res
@lnurl_router.post(
"/api/v1/lnurlscan",
dependencies=[Depends(require_invoice_key)],
response_model=LnurlPayResponse
| LnurlWithdrawResponse
| LnurlAuthResponse
| LnurlErrorResponse,
)
async def api_lnurlscan_post(scan: LnurlScan) -> LnurlResponseModel:
return await _handle(scan.lnurl)
@lnurl_router.post("/api/v1/lnurlauth") @lnurl_router.post("/api/v1/lnurlauth")
async def api_perform_lnurlauth( async def api_perform_lnurlauth(
data: LnurlAuthResponse, key_type: WalletTypeInfo = Depends(require_admin_key) data: LnurlAuthResponse, key_type: WalletTypeInfo = Depends(require_admin_key)
@@ -82,16 +101,29 @@ async def api_perform_lnurlauth(
async def api_payments_pay_lnurl( async def api_payments_pay_lnurl(
data: CreateLnurlPayment, wallet: WalletTypeInfo = Depends(require_admin_key) data: CreateLnurlPayment, wallet: WalletTypeInfo = Depends(require_admin_key)
) -> Payment: ) -> Payment:
"""
Pay an LNURL payment request.
Either provice `res` (LnurlPayResponse) or `lnurl` (str) in the `data` object.
"""
if not data.res and not data.lnurl:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="Missing LNURL or LnurlPayResponse data.",
)
try: try:
res = await fetch_lnurl_pay_request(data=data) res, res2 = await fetch_lnurl_pay_request(data=data, wallet=wallet.wallet)
except LnurlResponseException as exc: except LnurlResponseException as exc:
logger.warning(exc) logger.warning(exc)
msg = f"Failed to connect to {data.res.callback}." raise HTTPException(
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) from exc status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
) from exc
extra: dict[str, Any] = {} extra: dict[str, Any] = {}
if res.success_action: if res2.disposable is False:
extra["success_action"] = res.success_action.json() extra["stored"] = True
if res2.success_action:
extra["success_action"] = res2.success_action.json()
if data.comment: if data.comment:
extra["comment"] = data.comment extra["comment"] = data.comment
if data.unit and data.unit != "sat": if data.unit and data.unit != "sat":
@@ -100,8 +132,8 @@ async def api_payments_pay_lnurl(
payment = await pay_invoice( payment = await pay_invoice(
wallet_id=wallet.wallet.id, wallet_id=wallet.wallet.id,
payment_request=str(res.pr), payment_request=str(res2.pr),
description=data.res.metadata.text, description=res.metadata.text,
extra=extra, extra=extra,
) )
@@ -115,12 +147,12 @@ async def api_payment_pay_with_nfc(
payment_request: str, payment_request: str,
lnurl_data: CreateLnurlWithdraw, lnurl_data: CreateLnurlWithdraw,
) -> LnurlErrorResponse | LnurlSuccessResponse: ) -> LnurlErrorResponse | LnurlSuccessResponse:
if not lnurl_data.lnurl_w.lud17:
return LnurlErrorResponse(reason="LNURL-withdraw lud17 not provided.")
try: try:
res = await lnurl_handle( url = lnurl_data.lnurl_w.lud17
lnurl_data.lnurl_w.callback_url, user_agent=settings.user_agent, timeout=10 res = await lnurl_handle(url, user_agent=settings.user_agent, timeout=10)
)
except (LnurlResponseException, Exception) as exc: except (LnurlResponseException, Exception) as exc:
logger.warning(exc)
return LnurlErrorResponse(reason=str(exc)) return LnurlErrorResponse(reason=str(exc))
if not isinstance(res, LnurlWithdrawResponse): if not isinstance(res, LnurlWithdrawResponse):
+2 -2
View File
@@ -10,7 +10,7 @@ from fastapi import (
Query, Query,
) )
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from lnurl import decode as lnurl_decode from lnurl import url_decode
from lnbits import bolt11 from lnbits import bolt11
from lnbits.core.crud.payments import ( from lnbits.core.crud.payments import (
@@ -318,7 +318,7 @@ async def api_payments_decode(data: DecodePayment) -> JSONResponse:
payment_str = data.data payment_str = data.data
try: try:
if payment_str[:5] == "LNURL": if payment_str[:5] == "LNURL":
url = str(lnurl_decode(payment_str)) url = str(url_decode(payment_str))
return JSONResponse({"domain": url}) return JSONResponse({"domain": url})
else: else:
invoice = bolt11.decode(payment_str) invoice = bolt11.decode(payment_str)
+17 -2
View File
@@ -10,11 +10,11 @@ from fastapi import (
) )
from lnbits.core.crud.wallets import get_wallets_paginated from lnbits.core.crud.wallets import get_wallets_paginated
from lnbits.core.models import CreateWallet, KeyType, User, Wallet from lnbits.core.models import CreateWallet, KeyType, User, Wallet, WalletTypeInfo
from lnbits.core.models.lnurl import StoredPayLink, StoredPayLinks
from lnbits.core.models.wallets import WalletsFilters from lnbits.core.models.wallets import WalletsFilters
from lnbits.db import Filters, Page from lnbits.db import Filters, Page
from lnbits.decorators import ( from lnbits.decorators import (
WalletTypeInfo,
check_user_exists, check_user_exists,
parse_filters, parse_filters,
require_admin_key, require_admin_key,
@@ -93,6 +93,21 @@ async def api_reset_wallet_keys(
return wallet return wallet
@wallet_router.put("/stored_paylinks/{wallet_id}")
async def api_put_stored_paylinks(
wallet_id: str,
data: StoredPayLinks,
key_info: WalletTypeInfo = Depends(require_admin_key),
) -> list[StoredPayLink]:
if key_info.wallet.id != wallet_id:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="You cannot modify this wallet"
)
key_info.wallet.stored_paylinks.links = data.links
wallet = await update_wallet(key_info.wallet)
return wallet.stored_paylinks.links
@wallet_router.patch("") @wallet_router.patch("")
async def api_update_wallet( async def api_update_wallet(
name: Optional[str] = Body(None), name: Optional[str] = Body(None),
+9 -5
View File
@@ -281,15 +281,19 @@ class Database(Compat):
self.schema = self.name self.schema = self.name
self.type = DB_TYPE self.type = DB_TYPE
if DB_TYPE == SQLITE: if self.type == POSTGRES and settings.lnbits_database_url:
database_uri = settings.lnbits_database_url.replace(
"postgres://", "postgresql+asyncpg://"
)
elif self.type == COCKROACH and settings.lnbits_database_url:
database_uri = settings.lnbits_database_url.replace(
"cockroachdb://", "cockroachdb+asyncpg://"
)
else:
self.path = os.path.join( self.path = os.path.join(
settings.lnbits_data_folder, f"{self.name}.sqlite3" settings.lnbits_data_folder, f"{self.name}.sqlite3"
) )
database_uri = f"sqlite+aiosqlite:///{self.path}" database_uri = f"sqlite+aiosqlite:///{self.path}"
else:
database_uri = settings.lnbits_database_url.replace(
"postgres://", "postgresql+asyncpg://"
)
if self.name.startswith("ext_"): if self.name.startswith("ext_"):
self.schema = self.name[4:] self.schema = self.name[4:]
+1 -5
View File
@@ -99,11 +99,7 @@ def template_renderer(additional_folders: Optional[list] = None) -> Jinja2Templa
"USE_DEFAULT_BGIMAGE": settings.lnbits_default_bgimage, "USE_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_DENOMINATION": ( "LNBITS_DENOMINATION": settings.lnbits_denomination,
settings.lnbits_denomination
if settings.lnbits_denomination == "FakeWallet"
else "sats"
),
"has_holdinvoice": settings.has_holdinvoice, "has_holdinvoice": settings.has_holdinvoice,
} }
+8 -8
View File
@@ -142,14 +142,14 @@ class ExchangeRateProvider(BaseModel):
class InstalledExtensionsSettings(LNbitsSettings): class InstalledExtensionsSettings(LNbitsSettings):
# installed extensions that have been deactivated # installed extensions that have been deactivated
lnbits_deactivated_extensions: set[str] = Field(default=[]) lnbits_deactivated_extensions: set[str] = Field(default=set())
# upgraded extensions that require API redirects # upgraded extensions that require API redirects
lnbits_upgraded_extensions: dict[str, str] = Field(default={}) lnbits_upgraded_extensions: dict[str, str] = Field(default={})
# list of redirects that extensions want to perform # list of redirects that extensions want to perform
lnbits_extensions_redirects: list[RedirectPath] = Field(default=[]) lnbits_extensions_redirects: list[RedirectPath] = Field(default=[])
# list of all extension ids # list of all extension ids
lnbits_installed_extensions_ids: set[str] = Field(default=[]) lnbits_installed_extensions_ids: set[str] = Field(default=set())
def find_extension_redirect( def find_extension_redirect(
self, path: str, req_headers: list[tuple[bytes, bytes]] self, path: str, req_headers: list[tuple[bytes, bytes]]
@@ -266,7 +266,7 @@ class ThemesSettings(LNbitsSettings):
lnbits_default_theme: str = Field(default="salvador") lnbits_default_theme: str = Field(default="salvador")
lnbits_default_border: str = Field(default="hard-border") lnbits_default_border: str = Field(default="hard-border")
lnbits_default_gradient: bool = Field(default=True) lnbits_default_gradient: bool = Field(default=True)
lnbits_default_bgimage: str = Field(default=None) lnbits_default_bgimage: str | None = Field(default=None)
class OpsSettings(LNbitsSettings): class OpsSettings(LNbitsSettings):
@@ -729,7 +729,7 @@ class AuthMethods(Enum):
class AuthSettings(LNbitsSettings): class AuthSettings(LNbitsSettings):
auth_token_expire_minutes: int = Field(default=525600, gt=0) auth_token_expire_minutes: int = Field(default=525600, gt=0)
auth_all_methods = [a.value for a in AuthMethods] auth_all_methods: list[str] = [a.value for a in AuthMethods]
auth_allowed_methods: list[str] = Field( auth_allowed_methods: list[str] = Field(
default=[ default=[
AuthMethods.user_id_only.value, AuthMethods.user_id_only.value,
@@ -947,7 +947,7 @@ class EnvSettings(LNbitsSettings):
class PersistenceSettings(LNbitsSettings): class PersistenceSettings(LNbitsSettings):
lnbits_data_folder: str = Field(default="./data") lnbits_data_folder: str = Field(default="./data")
lnbits_database_url: str = Field(default=None) lnbits_database_url: str | None = Field(default=None)
class SuperUserSettings(LNbitsSettings): class SuperUserSettings(LNbitsSettings):
@@ -993,11 +993,11 @@ class TransientSettings(InstalledExtensionsSettings, ExchangeHistorySettings):
lnbits_running: bool = Field(default=True) lnbits_running: bool = Field(default=True)
# Remember the latest balance delta in order to compare with the current one # Remember the latest balance delta in order to compare with the current one
latest_balance_delta_sats: int = Field(default=None) latest_balance_delta_sats: int = Field(default=0)
lnbits_all_extensions_ids: set[str] = Field(default=[]) lnbits_all_extensions_ids: set[str] = Field(default=set())
server_startup_time: int = Field(default=time()) server_startup_time: int = Field(default=int(time()))
has_holdinvoice: bool = Field(default=False) has_holdinvoice: bool = Field(default=False)
has_nodemanager: bool = Field(default=False) has_nodemanager: bool = Field(default=False)
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -49,6 +49,7 @@ window.localisation.en = {
export_to_phone_desc: export_to_phone_desc:
'This QR code contains your wallet URL with full access. You can scan it from your phone to open your wallet from there.', 'This QR code contains your wallet URL with full access. You can scan it from your phone to open your wallet from there.',
access_wallet_on_mobile: 'Mobile Access', access_wallet_on_mobile: 'Mobile Access',
stored_paylinks: 'Stored LNURL pay links',
wallet: 'Wallet: ', wallet: 'Wallet: ',
wallet_name: 'Wallet name', wallet_name: 'Wallet name',
wallets: 'Wallets', wallets: 'Wallets',
-28
View File
@@ -187,34 +187,6 @@ window.app.component('lnbits-lnurlpay-success-action', {
} }
}) })
window.app.component('lnbits-qrcode', {
mixins: [window.windowMixin],
template: '#lnbits-qrcode',
components: {
QrcodeVue
},
props: {
value: {
type: String,
required: true
},
options: Object
},
data() {
return {
custom: {
margin: 3,
width: 350,
size: 350,
logo: LNBITS_QR_LOGO
}
}
},
created() {
this.custom = {...this.custom, ...this.options}
}
})
window.app.component('lnbits-notifications-btn', { window.app.component('lnbits-notifications-btn', {
template: '#lnbits-notifications-btn', template: '#lnbits-notifications-btn',
mixins: [window.windowMixin], mixins: [window.windowMixin],
@@ -0,0 +1,43 @@
window.app.component('lnbits-qrcode-lnurl', {
template: '#lnbits-qrcode-lnurl',
mixins: [window.windowMixin],
props: {
url: {
required: true,
type: String
},
prefix: {
type: String,
default: 'lnurlp'
}
},
data() {
return {
tab: 'bech32',
lnurl: ''
}
},
methods: {
setLnurl() {
if (this.tab == 'bech32') {
const bytes = new TextEncoder().encode(this.url)
const bech32 = NostrTools.nip19.encodeBytes('lnurl', bytes)
this.lnurl = `lightning:${bech32.toUpperCase()}`
} else if (this.tab == 'lud17') {
this.lnurl = this.url.replace('https://', this.prefix + '://')
}
this.$emit('update:lnurl', this.lnurl)
}
},
watch: {
url() {
this.setLnurl()
},
tab() {
this.setLnurl()
}
},
created() {
this.setLnurl()
}
})
@@ -0,0 +1,27 @@
window.app.component('lnbits-qrcode', {
mixins: [window.windowMixin],
template: '#lnbits-qrcode',
components: {
QrcodeVue
},
props: {
value: {
type: String,
required: true
},
options: Object
},
data() {
return {
custom: {
margin: 3,
width: 350,
size: 350,
logo: LNBITS_QR_LOGO
}
}
},
created() {
this.custom = {...this.custom, ...this.options}
}
})
+79 -13
View File
@@ -5,6 +5,7 @@ window.WalletPageLogic = {
origin: window.location.origin, origin: window.location.origin,
baseUrl: `${window.location.protocol}//${window.location.host}/`, baseUrl: `${window.location.protocol}//${window.location.host}/`,
websocketUrl: `${'http:' ? 'ws://' : 'wss://'}${window.location.host}/api/v1/ws`, websocketUrl: `${'http:' ? 'ws://' : 'wss://'}${window.location.host}/api/v1/ws`,
stored_paylinks: [],
parse: { parse: {
show: false, show: false,
invoice: null, invoice: null,
@@ -141,6 +142,13 @@ window.WalletPageLogic = {
}, },
canPay() { canPay() {
if (!this.parse.invoice) return false if (!this.parse.invoice) return false
if (this.parse.invoice.expired) {
Quasar.Notify.create({
message: 'Invoice has expired',
color: 'negative'
})
return false
}
return this.parse.invoice.sat <= this.g.wallet.sat return this.parse.invoice.sat <= this.g.wallet.sat
}, },
formattedAmount() { formattedAmount() {
@@ -170,6 +178,10 @@ window.WalletPageLogic = {
} }
}, },
methods: { methods: {
dateFromNow(unix) {
const date = new Date(unix * 1000)
return moment.utc(date).fromNow()
},
formatFiatAmount(amount, currency) { formatFiatAmount(amount, currency) {
this.update.currency = currency this.update.currency = currency
this.formattedFiatAmount = LNbits.utils.formatCurrency( this.formattedFiatAmount = LNbits.utils.formatCurrency(
@@ -275,7 +287,10 @@ window.WalletPageLogic = {
this.readNfcTag() this.readNfcTag()
} }
// WITHDRAW // WITHDRAW
if (response.data.extra.lnurl_response !== null) { if (
this.receive.lnurl &&
response.data.extra?.lnurl_response !== null
) {
if (response.data.extra.lnurl_response === false) { if (response.data.extra.lnurl_response === false) {
response.data.extra.lnurl_response = `Unable to connect` response.data.extra.lnurl_response = `Unable to connect`
} }
@@ -342,11 +357,9 @@ window.WalletPageLogic = {
}, },
lnurlScan() { lnurlScan() {
LNbits.api LNbits.api
.request( .request('POST', '/api/v1/lnurlscan', this.g.wallet.adminkey, {
'GET', lnurl: this.parse.data.request
'/api/v1/lnurlscan/' + this.parse.data.request, })
this.g.wallet.adminkey
)
.then(response => { .then(response => {
const data = response.data const data = response.data
if (data.status === 'ERROR') { if (data.status === 'ERROR') {
@@ -394,19 +407,29 @@ window.WalletPageLogic = {
this.decodeRequest() this.decodeRequest()
this.parse.camera.show = false this.parse.camera.show = false
}, },
isLnurl(req) {
return (
req.toLowerCase().startsWith('lnurl1') ||
req.startsWith('lnurlp://') ||
req.startsWith('lnurlw://') ||
req.startsWith('lnurlauth://') ||
req.match(/[\w.+-~_]+@[\w.+-~_]/)
)
},
decodeRequest() { decodeRequest() {
this.parse.show = true this.parse.show = true
this.parse.data.request = this.parse.data.request.trim().toLowerCase() this.parse.data.request = this.parse.data.request.trim()
let req = this.parse.data.request const req = this.parse.data.request.toLowerCase()
if (req.startsWith('lightning:')) { if (req.startsWith('lightning:')) {
this.parse.data.request = req.slice(10) this.parse.data.request = this.parse.data.request.slice(10)
} else if (req.startsWith('lnurl:')) { } else if (req.startsWith('lnurl:')) {
this.parse.data.request = req.slice(6) this.parse.data.request = this.parse.data.request.slice(6)
} else if (req.includes('lightning=lnurl1')) { } else if (req.includes('lightning=lnurl1')) {
this.parse.data.request = req.split('lightning=')[1].split('&')[0] this.parse.data.request = this.parse.data.request
.split('lightning=')[1]
.split('&')[0]
} }
req = this.parse.data.request if (this.isLnurl(this.parse.data.request)) {
if (req.startsWith('lnurl1') || req.match(/[\w.+-~_]+@[\w.+-~_]/)) {
this.lnurlScan() this.lnurlScan()
return return
} }
@@ -518,6 +541,7 @@ window.WalletPageLogic = {
LNbits.api LNbits.api
.request('post', '/api/v1/payments/lnurl', this.g.wallet.adminkey, { .request('post', '/api/v1/payments/lnurl', this.g.wallet.adminkey, {
res: this.parse.lnurlpay, res: this.parse.lnurlpay,
lnurl: this.parse.data.request,
unit: this.parse.data.unit, unit: this.parse.data.unit,
amount: this.parse.data.amount * 1000, amount: this.parse.data.amount * 1000,
comment: this.parse.data.comment, comment: this.parse.data.comment,
@@ -1080,9 +1104,51 @@ window.WalletPageLogic = {
saveChartsPreferences() { saveChartsPreferences() {
this.$q.localStorage.set('lnbits.wallets.chartConfig', this.chartConfig) this.$q.localStorage.set('lnbits.wallets.chartConfig', this.chartConfig)
this.refreshCharts() this.refreshCharts()
},
updatePaylinks() {
LNbits.api
.request(
'PUT',
`/api/v1/wallet/stored_paylinks/${this.g.wallet.id}`,
this.g.wallet.adminkey,
{
links: this.stored_paylinks
}
)
.then(() => {
Quasar.Notify.create({
message: 'Paylinks updated.',
type: 'positive',
timeout: 3500
})
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
sendToPaylink(lnurl) {
this.parse.data.request = lnurl
this.parse.show = true
this.lnurlScan()
},
editPaylink() {
this.$nextTick(() => {
this.updatePaylinks()
})
},
deletePaylink(lnurl) {
const links = []
this.stored_paylinks.forEach(link => {
if (link.lnurl !== lnurl) {
links.push(link)
}
})
this.stored_paylinks = links
this.updatePaylinks()
} }
}, },
created() { created() {
this.stored_paylinks = wallet.stored_paylinks.links
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
if (urlParams.has('lightning') || urlParams.has('lnurl')) { if (urlParams.has('lightning') || urlParams.has('lnurl')) {
this.parse.data.request = this.parse.data.request =
+2
View File
@@ -38,6 +38,8 @@
"js/bolt11-decoder.js" "js/bolt11-decoder.js"
], ],
"components": [ "components": [
"js/components/lnbits-qrcode.js",
"js/components/lnbits-qrcode-lnurl.js",
"js/components/lnbits-funding-sources.js", "js/components/lnbits-funding-sources.js",
"js/components/extension-settings.js", "js/components/extension-settings.js",
"js/components/payment-list.js", "js/components/payment-list.js",
+25 -1
View File
@@ -370,7 +370,12 @@
v-if="extras.length" v-if="extras.length"
> >
<template v-for="entry in extras"> <template v-for="entry in extras">
<q-item v-if="!!entry.value" key="entry.key" class="text-grey-4"> <q-item
v-if="!!entry.value"
key="entry.key"
class="text-grey-4"
style="white-space: normal; word-break: break-all"
>
<q-item-section> <q-item-section>
<q-item-label v-text="entry.key"></q-item-label> <q-item-label v-text="entry.key"></q-item-label>
<q-item-label caption v-text="entry.value"></q-item-label> <q-item-label caption v-text="entry.value"></q-item-label>
@@ -630,6 +635,25 @@
</div> </div>
</template> </template>
<template id="lnbits-qrcode-lnurl">
<div class="qrcode_lnurl__wrapper">
<q-tabs
v-model="tab"
dense
class="text-grey q-mb-md"
active-color="primary"
indicator-color="primary"
align="justify"
narrow-indicator
inline-label
>
<q-tab name="bech32" icon="qr_code" label="bech32"></q-tab>
<q-tab name="lud17" icon="link" label="url (lud17)"></q-tab>
</q-tabs>
<lnbits-qrcode :value="lnurl" class="rounded-borders"></lnbits-qrcode>
</div>
</template>
<template id="lnbits-lnurlpay-success-action"> <template id="lnbits-lnurlpay-success-action">
<div> <div>
<p <p
+184 -3
View File
@@ -448,6 +448,19 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true "dev": true
}, },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/camel-case": { "node_modules/camel-case": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
@@ -589,6 +602,20 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/entities": { "node_modules/entities": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -600,6 +627,51 @@
"url": "https://github.com/fb55/entities?sponsor=1" "url": "https://github.com/fb55/entities?sponsor=1"
} }
}, },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/estree-walker": { "node_modules/estree-walker": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
@@ -637,12 +709,15 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.0", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
}, },
"engines": { "engines": {
@@ -663,6 +738,52 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -675,6 +796,57 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/html-minifier-terser": { "node_modules/html-minifier-terser": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
@@ -776,6 +948,15 @@
"@jridgewell/sourcemap-codec": "^1.5.0" "@jridgewell/sourcemap-codec": "^1.5.0"
} }
}, },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": { "node_modules/mime-db": {
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+2
View File
@@ -90,6 +90,8 @@
"js/bolt11-decoder.js" "js/bolt11-decoder.js"
], ],
"components": [ "components": [
"js/components/lnbits-qrcode.js",
"js/components/lnbits-qrcode-lnurl.js",
"js/components/lnbits-funding-sources.js", "js/components/lnbits-funding-sources.js",
"js/components/extension-settings.js", "js/components/extension-settings.js",
"js/components/payment-list.js", "js/components/payment-list.js",
Generated
+43 -37
View File
@@ -2133,14 +2133,14 @@ valkey = ["valkey (>=6)"]
[[package]] [[package]]
name = "lnurl" name = "lnurl"
version = "0.6.8" version = "0.7.3"
description = "LNURL implementation for Python." description = "LNURL implementation for Python."
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"] groups = ["main"]
files = [ files = [
{file = "lnurl-0.6.8-py3-none-any.whl", hash = "sha256:4fff53efcdd401cf4169676bd1ab85e9e241a762a9a5407ee11e6a6e120e8279"}, {file = "lnurl-0.7.3-py3-none-any.whl", hash = "sha256:1a6796cb5124047e2d289ca8ba2015bbf62e3f78d717802205dc6e303e31898a"},
{file = "lnurl-0.6.8.tar.gz", hash = "sha256:de64a47179980a4b52cd6b89ad377cda14502f1998f53724490683f6f5c4ed90"}, {file = "lnurl-0.7.3.tar.gz", hash = "sha256:b8e8036bc1d215d1d9b0cbb18b8d9b8d2e7e98850be1c98d33887e03c9f4b144"},
] ]
[package.dependencies] [package.dependencies]
@@ -2430,44 +2430,50 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""}
[[package]] [[package]]
name = "mypy" name = "mypy"
version = "1.16.1" version = "1.17.1"
description = "Optional static typing for Python" description = "Optional static typing for Python"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["dev"] groups = ["dev"]
files = [ files = [
{file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"},
{file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"},
{file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"}, {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"},
{file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"}, {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"},
{file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"}, {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"},
{file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"}, {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"},
{file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"}, {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"},
{file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"}, {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"},
{file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"}, {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"},
{file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"}, {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"},
{file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"}, {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"},
{file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"}, {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"},
{file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"}, {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"},
{file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"}, {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"},
{file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"}, {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"},
{file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"}, {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"},
{file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"}, {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"},
{file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"}, {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"},
{file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"}, {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"},
{file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"}, {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"},
{file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"}, {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"},
{file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"}, {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"},
{file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"}, {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"},
{file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"}, {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"},
{file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"}, {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"},
{file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"}, {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"},
{file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"}, {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"},
{file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"}, {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"},
{file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"}, {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"},
{file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"}, {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"},
{file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"}, {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"},
{file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"}, {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"},
{file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"},
{file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"},
{file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"},
{file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"},
{file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"},
{file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"},
] ]
[package.dependencies] [package.dependencies]
@@ -4573,4 +4579,4 @@ migration = ["psycopg2-binary"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = "~3.12 | ~3.11 | ~3.10" python-versions = "~3.12 | ~3.11 | ~3.10"
content-hash = "c1714a4df0e8f7d8702fffe9c57b21fc39effb194631741b4718466c18b73d8f" content-hash = "97407d81374521c715f792e64cd17fcbe9b487f7de515e62891657aa4fa4e383"
+10 -3
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "lnbits" name = "lnbits"
version = "1.3.0-rc2" version = "1.3.0-rc4"
description = "LNbits, free and open-source Lightning wallet and accounts system." description = "LNbits, free and open-source Lightning wallet and accounts system."
authors = ["Alan Bits <alan@lnbits.com>"] authors = ["Alan Bits <alan@lnbits.com>"]
readme = "README.md" readme = "README.md"
@@ -20,7 +20,7 @@ fastapi = "0.116.1"
starlette = "0.47.1" starlette = "0.47.1"
httpx = "0.27.0" httpx = "0.27.0"
jinja2 = "3.1.6" jinja2 = "3.1.6"
lnurl = "0.6.8" lnurl = "0.7.3"
pydantic = "1.10.22" pydantic = "1.10.22"
pyqrcode = "1.2.1" pyqrcode = "1.2.1"
shortuuid = "1.0.13" shortuuid = "1.0.13"
@@ -75,7 +75,7 @@ migration = ["psycopg2-binary"]
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^25.1.0" black = "^25.1.0"
mypy = "^1.11.2" mypy = "^1.17.1"
types-protobuf = "^6.30.2.20250516" types-protobuf = "^6.30.2.20250516"
pre-commit = "^4.2.0" pre-commit = "^4.2.0"
openapi-spec-validator = "^0.7.1" openapi-spec-validator = "^0.7.1"
@@ -127,6 +127,13 @@ exclude = [
"^lnbits/extensions", "^lnbits/extensions",
"^lnbits/upgrades", "^lnbits/upgrades",
] ]
plugins = "pydantic.mypy"
[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = [ module = [
-4
View File
@@ -623,7 +623,6 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
{ {
"tag": "payRequest", "tag": "payRequest",
"callback": "https://example.com/callback", "callback": "https://example.com/callback",
"k1": "randomk1value",
"minSendable": 1000, "minSendable": 1000,
"maxSendable": 1_500_000, "maxSendable": 1_500_000,
"metadata": '[["text/plain", "Payment to yo"]]', "metadata": '[["text/plain", "Payment to yo"]]',
@@ -793,9 +792,6 @@ async def test_api_payments_pay_lnurl(client, adminkey_headers_from):
"/api/v1/payments/lnurl", json=lnurl_data, headers=adminkey_headers_from "/api/v1/payments/lnurl", json=lnurl_data, headers=adminkey_headers_from
) )
assert response.status_code == 400 assert response.status_code == 400
assert (
response.json()["detail"] == "Failed to connect to https://xxxxxxx.lnbits.com."
)
# Test with invalid callback URL # Test with invalid callback URL
lnurl_data["res"]["callback"] = "invalid-url.lnbits.com" lnurl_data["res"]["callback"] = "invalid-url.lnbits.com"