Compare commits
5
Commits
v1.3.0-rc3
...
v1.3.0-rc4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15984fa49b | ||
|
|
67dc096909 | ||
|
|
2daddf8b74 | ||
|
|
ae6cf47244 | ||
|
|
1488f580ff |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
from time import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from lnurl import LnAddress, 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
|
||||||
@@ -18,3 +20,13 @@ class CreateLnurlWithdraw(BaseModel):
|
|||||||
|
|
||||||
class LnurlScan(BaseModel):
|
class LnurlScan(BaseModel):
|
||||||
lnurl: Lnurl | LnAddress
|
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] = []
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
|
from time import time
|
||||||
|
|
||||||
from lnurl import (
|
from lnurl import (
|
||||||
|
LnAddress,
|
||||||
|
Lnurl,
|
||||||
LnurlErrorResponse,
|
LnurlErrorResponse,
|
||||||
LnurlPayActionResponse,
|
LnurlPayActionResponse,
|
||||||
LnurlPayResponse,
|
LnurlPayResponse,
|
||||||
@@ -6,8 +10,11 @@ from lnurl import (
|
|||||||
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
|
||||||
|
|
||||||
@@ -34,12 +41,26 @@ async def get_pr_from_lnurl(
|
|||||||
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
|
||||||
@@ -49,10 +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=amount_msat,
|
msat=amount_msat,
|
||||||
comment=data.comment,
|
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}."
|
||||||
|
)
|
||||||
|
|||||||
@@ -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')"
|
||||||
|
|||||||
@@ -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} "
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ 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)],
|
||||||
@@ -47,13 +59,7 @@ lnurl_router = APIRouter(tags=["LNURL"])
|
|||||||
| 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
|
||||||
@@ -68,13 +74,7 @@ async def api_lnurlscan(code: str) -> LnurlResponseModel:
|
|||||||
| LnurlErrorResponse,
|
| LnurlErrorResponse,
|
||||||
)
|
)
|
||||||
async def api_lnurlscan_post(scan: LnurlScan) -> LnurlResponseModel:
|
async def api_lnurlscan_post(scan: LnurlScan) -> LnurlResponseModel:
|
||||||
try:
|
return await _handle(scan.lnurl)
|
||||||
res = await lnurl_handle(scan.lnurl, user_agent=settings.user_agent, timeout=5)
|
|
||||||
except LnurlResponseException as exc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
@lnurl_router.post("/api/v1/lnurlauth")
|
@lnurl_router.post("/api/v1/lnurlauth")
|
||||||
@@ -101,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":
|
||||||
@@ -119,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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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:]
|
||||||
|
|||||||
+8
-8
@@ -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)
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -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',
|
||||||
|
|||||||
@@ -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}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -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,
|
||||||
@@ -177,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(
|
||||||
@@ -536,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,
|
||||||
@@ -1098,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 =
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -635,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
|
||||||
|
|||||||
@@ -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
@@ -2133,14 +2133,14 @@ valkey = ["valkey (>=6)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lnurl"
|
name = "lnurl"
|
||||||
version = "0.7.2"
|
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.7.2-py3-none-any.whl", hash = "sha256:4323ac398d49e5b883000c166b4c2448df2acad2eddca99f656c67f7b97cd80f"},
|
{file = "lnurl-0.7.3-py3-none-any.whl", hash = "sha256:1a6796cb5124047e2d289ca8ba2015bbf62e3f78d717802205dc6e303e31898a"},
|
||||||
{file = "lnurl-0.7.2.tar.gz", hash = "sha256:9f0881cb5909512cadb35d238c27347d6a8afcec10b2b7aadd560083fd210c0b"},
|
{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 = "7100b67d94f82cb53ceb8f4e47ae8a4aa426408feabeb0746c323d09f229c242"
|
content-hash = "97407d81374521c715f792e64cd17fcbe9b487f7de515e62891657aa4fa4e383"
|
||||||
|
|||||||
+10
-3
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.3.0-rc3"
|
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.7.2"
|
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 = [
|
||||||
|
|||||||
@@ -792,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"
|
||||||
|
|||||||
Reference in New Issue
Block a user