Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55484b9cb4 | ||
|
|
4db7d20e60 | ||
|
|
02fec90a29 | ||
|
|
4efe1a9fca | ||
|
|
cba8c02d68 | ||
|
|
5c9d375713 | ||
|
|
264d1fa07c | ||
|
|
fc3e2b2b4d | ||
|
|
64edeebaaf | ||
|
|
c2739188d0 | ||
|
|
3f4d778c95 | ||
|
|
c1788c7219 | ||
|
|
07c62285c3 | ||
|
|
841c8ce3a9 | ||
|
|
cc6430221c | ||
|
|
4a34548864 | ||
|
|
4a3a3a0425 | ||
|
|
bf8b33a7b2 | ||
|
|
7e08211887 | ||
|
|
0bbb0aca2d | ||
|
|
9b1418b1f4 | ||
|
|
5f0a8d78e0 | ||
|
|
2fe8ae1cb1 | ||
|
|
1a3d23bbee | ||
|
|
ae45c1b958 | ||
|
|
8337351d6a | ||
|
|
f4dbd369be | ||
|
|
974ee26224 | ||
|
|
e4fb4453c3 | ||
|
|
aa7129a351 | ||
|
|
aba6e4cd87 | ||
|
|
f4e7d0c70c | ||
|
|
4247b9d9a3 |
+4
-9
@@ -4,7 +4,6 @@ import importlib
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
@@ -27,7 +26,7 @@ from lnbits.core.crud.extensions import create_installed_extension
|
|||||||
from lnbits.core.helpers import migrate_extension_database
|
from lnbits.core.helpers import migrate_extension_database
|
||||||
from lnbits.core.models.notifications import NotificationType
|
from lnbits.core.models.notifications import NotificationType
|
||||||
from lnbits.core.services.extensions import deactivate_extension, get_valid_extensions
|
from lnbits.core.services.extensions import deactivate_extension, get_valid_extensions
|
||||||
from lnbits.core.services.notifications import enqueue_admin_notification
|
from lnbits.core.services.notifications import enqueue_notification
|
||||||
from lnbits.core.services.payments import check_pending_payments
|
from lnbits.core.services.payments import check_pending_payments
|
||||||
from lnbits.core.tasks import (
|
from lnbits.core.tasks import (
|
||||||
audit_queue,
|
audit_queue,
|
||||||
@@ -71,9 +70,8 @@ from .tasks import internal_invoice_listener, invoice_listener, run_interval
|
|||||||
|
|
||||||
|
|
||||||
async def startup(app: FastAPI):
|
async def startup(app: FastAPI):
|
||||||
logger.info(f"Starting LNbits Version: {settings.version}")
|
|
||||||
start = time.perf_counter()
|
|
||||||
settings.lnbits_running = True
|
settings.lnbits_running = True
|
||||||
|
|
||||||
# wait till migration is done
|
# wait till migration is done
|
||||||
await migrate_databases()
|
await migrate_databases()
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ async def startup(app: FastAPI):
|
|||||||
# initialize tasks
|
# initialize tasks
|
||||||
register_async_tasks()
|
register_async_tasks()
|
||||||
|
|
||||||
enqueue_admin_notification(
|
enqueue_notification(
|
||||||
NotificationType.server_start_stop,
|
NotificationType.server_start_stop,
|
||||||
{
|
{
|
||||||
"message": "LNbits server started.",
|
"message": "LNbits server started.",
|
||||||
@@ -112,13 +110,10 @@ async def startup(app: FastAPI):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
end = time.perf_counter()
|
|
||||||
logger.success(f"LNbits started in {end - start:.2f} seconds.")
|
|
||||||
|
|
||||||
|
|
||||||
async def shutdown():
|
async def shutdown():
|
||||||
logger.warning("LNbits shutting down...")
|
logger.warning("LNbits shutting down...")
|
||||||
enqueue_admin_notification(
|
enqueue_notification(
|
||||||
NotificationType.server_start_stop,
|
NotificationType.server_start_stop,
|
||||||
{
|
{
|
||||||
"message": "LNbits server shutting down...",
|
"message": "LNbits server shutting down...",
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from .views.fiat_api import fiat_router
|
|||||||
|
|
||||||
# this compat is needed for usermanager extension
|
# this compat is needed for usermanager extension
|
||||||
from .views.generic import generic_router
|
from .views.generic import generic_router
|
||||||
from .views.lnurl_api import lnurl_router
|
|
||||||
from .views.node_api import node_router, public_node_router, super_node_router
|
from .views.node_api import node_router, public_node_router, super_node_router
|
||||||
from .views.payment_api import payment_router
|
from .views.payment_api import payment_router
|
||||||
from .views.tinyurl_api import tinyurl_router
|
from .views.tinyurl_api import tinyurl_router
|
||||||
@@ -43,7 +42,6 @@ def init_core_routers(app: FastAPI):
|
|||||||
app.include_router(users_router)
|
app.include_router(users_router)
|
||||||
app.include_router(audit_router)
|
app.include_router(audit_router)
|
||||||
app.include_router(fiat_router)
|
app.include_router(fiat_router)
|
||||||
app.include_router(lnurl_router)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["core_app", "core_app_extra", "db"]
|
__all__ = ["core_app", "core_app_extra", "db"]
|
||||||
|
|||||||
@@ -19,8 +19,13 @@ from ..models import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_payment_extra():
|
async def update_payment_extra(
|
||||||
pass
|
payment: Payment, conn: Optional[Connection] = None
|
||||||
|
) -> Payment:
|
||||||
|
return await (conn or db).execute(
|
||||||
|
"UPDATE apipayments SET extra = :extra WHERE checking_id = :checking_id",
|
||||||
|
{"extra": payment.extra, "checking_id": payment.checking_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_payment(checking_id: str, conn: Optional[Connection] = None) -> Payment:
|
async def get_payment(checking_id: str, conn: Optional[Connection] = None) -> Payment:
|
||||||
@@ -380,7 +385,6 @@ async def get_payment_count_stats(
|
|||||||
user_id: Optional[str] = None,
|
user_id: Optional[str] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> list[PaymentCountStat]:
|
) -> list[PaymentCountStat]:
|
||||||
|
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = Filters()
|
filters = Filters()
|
||||||
extra_stmts = []
|
extra_stmts = []
|
||||||
@@ -413,7 +417,6 @@ async def get_daily_stats(
|
|||||||
user_id: Optional[str] = None,
|
user_id: Optional[str] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> tuple[list[PaymentDailyStats], list[PaymentDailyStats]]:
|
) -> tuple[list[PaymentDailyStats], list[PaymentDailyStats]]:
|
||||||
|
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = Filters()
|
filters = Filters()
|
||||||
|
|
||||||
@@ -463,7 +466,6 @@ async def get_wallets_stats(
|
|||||||
user_id: Optional[str] = None,
|
user_id: Optional[str] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> list[PaymentWalletStats]:
|
) -> list[PaymentWalletStats]:
|
||||||
|
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = Filters()
|
filters = Filters()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from .audit import AuditEntry, AuditFilters
|
from .audit import AuditEntry, AuditFilters
|
||||||
from .lnurl import CreateLnurlPayment, CreateLnurlWithdraw
|
from .lnurl import CreateLnurl, CreateLnurlAuth, PayLnurlWData
|
||||||
from .misc import (
|
from .misc import (
|
||||||
BalanceDelta,
|
BalanceDelta,
|
||||||
Callback,
|
Callback,
|
||||||
@@ -9,7 +9,6 @@ from .misc import (
|
|||||||
SimpleStatus,
|
SimpleStatus,
|
||||||
)
|
)
|
||||||
from .payments import (
|
from .payments import (
|
||||||
CancelInvoice,
|
|
||||||
CreateInvoice,
|
CreateInvoice,
|
||||||
CreatePayment,
|
CreatePayment,
|
||||||
DecodePayment,
|
DecodePayment,
|
||||||
@@ -24,7 +23,6 @@ from .payments import (
|
|||||||
PaymentsStatusCount,
|
PaymentsStatusCount,
|
||||||
PaymentState,
|
PaymentState,
|
||||||
PaymentWalletStats,
|
PaymentWalletStats,
|
||||||
SettleInvoice,
|
|
||||||
)
|
)
|
||||||
from .tinyurl import TinyURL
|
from .tinyurl import TinyURL
|
||||||
from .users import (
|
from .users import (
|
||||||
@@ -59,12 +57,11 @@ __all__ = [
|
|||||||
"BalanceDelta",
|
"BalanceDelta",
|
||||||
"BaseWallet",
|
"BaseWallet",
|
||||||
"Callback",
|
"Callback",
|
||||||
"CancelInvoice",
|
|
||||||
"ConversionData",
|
"ConversionData",
|
||||||
"CoreAppExtra",
|
"CoreAppExtra",
|
||||||
"CreateInvoice",
|
"CreateInvoice",
|
||||||
"CreateLnurlPayment",
|
"CreateLnurl",
|
||||||
"CreateLnurlWithdraw",
|
"CreateLnurlAuth",
|
||||||
"CreatePayment",
|
"CreatePayment",
|
||||||
"CreateUser",
|
"CreateUser",
|
||||||
"CreateWallet",
|
"CreateWallet",
|
||||||
@@ -75,6 +72,7 @@ __all__ = [
|
|||||||
"LoginUsernamePassword",
|
"LoginUsernamePassword",
|
||||||
"LoginUsr",
|
"LoginUsr",
|
||||||
"PayInvoice",
|
"PayInvoice",
|
||||||
|
"PayLnurlWData",
|
||||||
"Payment",
|
"Payment",
|
||||||
"PaymentCountField",
|
"PaymentCountField",
|
||||||
"PaymentCountStat",
|
"PaymentCountStat",
|
||||||
@@ -87,7 +85,6 @@ __all__ = [
|
|||||||
"PaymentsStatusCount",
|
"PaymentsStatusCount",
|
||||||
"RegisterUser",
|
"RegisterUser",
|
||||||
"ResetUserPassword",
|
"ResetUserPassword",
|
||||||
"SettleInvoice",
|
|
||||||
"SimpleStatus",
|
"SimpleStatus",
|
||||||
"TinyURL",
|
"TinyURL",
|
||||||
"UpdateBalance",
|
"UpdateBalance",
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from lnurl import LnAddress, Lnurl, LnurlPayResponse
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class CreateLnurlPayment(BaseModel):
|
class CreateLnurl(BaseModel):
|
||||||
res: LnurlPayResponse
|
description_hash: str
|
||||||
|
callback: str
|
||||||
amount: int
|
amount: int
|
||||||
comment: Optional[str] = None
|
comment: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
unit: Optional[str] = None
|
unit: Optional[str] = None
|
||||||
internal_memo: Optional[str] = None
|
internal_memo: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class CreateLnurlWithdraw(BaseModel):
|
class CreateLnurlAuth(BaseModel):
|
||||||
lnurl_w: Lnurl
|
callback: str
|
||||||
|
|
||||||
|
|
||||||
class LnurlScan(BaseModel):
|
class PayLnurlWData(BaseModel):
|
||||||
lnurl: Lnurl | LnAddress
|
lnurl_w: str
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ from enum import Enum
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from lnbits.core.models.users import UserNotifications
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationType(Enum):
|
class NotificationType(Enum):
|
||||||
server_status = "server_status"
|
server_status = "server_status"
|
||||||
@@ -20,7 +18,6 @@ class NotificationType(Enum):
|
|||||||
class NotificationMessage(BaseModel):
|
class NotificationMessage(BaseModel):
|
||||||
message_type: NotificationType
|
message_type: NotificationType
|
||||||
values: dict
|
values: dict
|
||||||
user_notifications: UserNotifications | None = None
|
|
||||||
|
|
||||||
|
|
||||||
NOTIFICATION_TEMPLATES = {
|
NOTIFICATION_TEMPLATES = {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from enum import Enum
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
from lnurl import LnurlWithdrawResponse
|
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
|
|
||||||
from lnbits.db import FilterModel
|
from lnbits.db import FilterModel
|
||||||
@@ -253,25 +252,13 @@ class CreateInvoice(BaseModel):
|
|||||||
memo: str | None = Query(None, max_length=640)
|
memo: str | None = Query(None, max_length=640)
|
||||||
description_hash: str | None = None
|
description_hash: str | None = None
|
||||||
unhashed_description: str | None = None
|
unhashed_description: str | None = None
|
||||||
payment_hash: str | None = Query(
|
|
||||||
None,
|
|
||||||
description="The payment hash of the hold invoice.",
|
|
||||||
min_length=64,
|
|
||||||
max_length=64,
|
|
||||||
)
|
|
||||||
expiry: int | None = None
|
expiry: int | None = None
|
||||||
extra: dict | None = None
|
extra: dict | None = None
|
||||||
webhook: str | None = None
|
webhook: str | None = None
|
||||||
bolt11: str | None = None
|
bolt11: str | None = None
|
||||||
lnurl_withdraw: LnurlWithdrawResponse | None = None
|
lnurl_callback: str | None = None
|
||||||
fiat_provider: str | None = None
|
fiat_provider: str | None = None
|
||||||
|
|
||||||
@validator("payment_hash")
|
|
||||||
def check_hex(cls, v):
|
|
||||||
if v:
|
|
||||||
_ = bytes.fromhex(v)
|
|
||||||
return v
|
|
||||||
|
|
||||||
@validator("unit")
|
@validator("unit")
|
||||||
@classmethod
|
@classmethod
|
||||||
def unit_is_from_allowed_currencies(cls, v):
|
def unit_is_from_allowed_currencies(cls, v):
|
||||||
@@ -285,31 +272,3 @@ class PaymentsStatusCount(BaseModel):
|
|||||||
outgoing: int = 0
|
outgoing: int = 0
|
||||||
failed: int = 0
|
failed: int = 0
|
||||||
pending: int = 0
|
pending: int = 0
|
||||||
|
|
||||||
|
|
||||||
class SettleInvoice(BaseModel):
|
|
||||||
preimage: str = Field(
|
|
||||||
...,
|
|
||||||
description="The preimage of the payment hash to settle the invoice.",
|
|
||||||
min_length=64,
|
|
||||||
max_length=64,
|
|
||||||
)
|
|
||||||
|
|
||||||
@validator("preimage")
|
|
||||||
def check_hex(cls, v):
|
|
||||||
_ = bytes.fromhex(v)
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
class CancelInvoice(BaseModel):
|
|
||||||
payment_hash: str = Field(
|
|
||||||
...,
|
|
||||||
description="The payment hash of the invoice to cancel.",
|
|
||||||
min_length=64,
|
|
||||||
max_length=64,
|
|
||||||
)
|
|
||||||
|
|
||||||
@validator("payment_hash")
|
|
||||||
def check_hex(cls, v):
|
|
||||||
_ = bytes.fromhex(v)
|
|
||||||
return v
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ from __future__ import annotations
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from bcrypt import checkpw, gensalt, hashpw
|
|
||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
|
from passlib.context import CryptContext
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from lnbits.core.models.misc import SimpleItem
|
from lnbits.core.models.misc import SimpleItem
|
||||||
@@ -20,15 +20,6 @@ from lnbits.settings import settings
|
|||||||
from .wallets import Wallet
|
from .wallets import Wallet
|
||||||
|
|
||||||
|
|
||||||
class UserNotifications(BaseModel):
|
|
||||||
nostr_identifier: str | None = None
|
|
||||||
telegram_chat_id: str | None = None
|
|
||||||
email_address: str | None = None
|
|
||||||
excluded_wallets: list[str] = []
|
|
||||||
outgoing_payments_sats: int = 0
|
|
||||||
incoming_payments_sats: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
class UserExtra(BaseModel):
|
class UserExtra(BaseModel):
|
||||||
email_verified: bool | None = False
|
email_verified: bool | None = False
|
||||||
first_name: str | None = None
|
first_name: str | None = None
|
||||||
@@ -44,8 +35,6 @@ class UserExtra(BaseModel):
|
|||||||
# how many wallets are shown in the user interface
|
# how many wallets are shown in the user interface
|
||||||
visible_wallet_count: int | None = 10
|
visible_wallet_count: int | None = 10
|
||||||
|
|
||||||
notifications: UserNotifications = UserNotifications()
|
|
||||||
|
|
||||||
|
|
||||||
class EndpointAccess(BaseModel):
|
class EndpointAccess(BaseModel):
|
||||||
path: str
|
path: str
|
||||||
@@ -131,18 +120,16 @@ class Account(BaseModel):
|
|||||||
|
|
||||||
def hash_password(self, password: str) -> str:
|
def hash_password(self, password: str) -> str:
|
||||||
"""sets and returns the hashed password"""
|
"""sets and returns the hashed password"""
|
||||||
salt = gensalt()
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
hashed_pw = hashpw(password.encode(), salt)
|
self.password_hash = pwd_context.hash(password)
|
||||||
if not hashed_pw:
|
|
||||||
raise ValueError("Password hashing failed.")
|
|
||||||
self.password_hash = hashed_pw.decode()
|
|
||||||
return self.password_hash
|
return self.password_hash
|
||||||
|
|
||||||
def verify_password(self, password: str) -> bool:
|
def verify_password(self, password: str) -> bool:
|
||||||
"""returns True if the password matches the hash"""
|
"""returns True if the password matches the hash"""
|
||||||
if not self.password_hash:
|
if not self.password_hash:
|
||||||
return False
|
return False
|
||||||
return checkpw(password.encode(), self.password_hash.encode())
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
return pwd_context.verify(password, self.password_hash)
|
||||||
|
|
||||||
def validate_fields(self):
|
def validate_fields(self):
|
||||||
if self.username and not is_valid_username(self.username):
|
if self.username and not is_valid_username(self.username):
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ from datetime import datetime, timezone
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from ecdsa import SECP256k1, SigningKey
|
from ecdsa import SECP256k1, SigningKey
|
||||||
from lnurl import encode as lnurl_encode
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from lnbits.db import FilterModel
|
from lnbits.db import FilterModel
|
||||||
from lnbits.helpers import url_for
|
from lnbits.helpers import url_for
|
||||||
|
from lnbits.lnurl import encode as lnurl_encode
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ from .funding_source import (
|
|||||||
get_balance_delta,
|
get_balance_delta,
|
||||||
switch_to_voidwallet,
|
switch_to_voidwallet,
|
||||||
)
|
)
|
||||||
from .lnurl import fetch_lnurl_pay_request, get_pr_from_lnurl
|
from .lnurl import perform_lnurlauth, redeem_lnurl_withdraw
|
||||||
from .notifications import enqueue_admin_notification, send_payment_notification
|
from .notifications import enqueue_notification, send_payment_notification
|
||||||
from .payments import (
|
from .payments import (
|
||||||
calculate_fiat_amounts,
|
calculate_fiat_amounts,
|
||||||
cancel_hold_invoice,
|
|
||||||
check_transaction_status,
|
check_transaction_status,
|
||||||
check_wallet_limits,
|
check_wallet_limits,
|
||||||
create_fiat_invoice,
|
create_fiat_invoice,
|
||||||
@@ -18,7 +17,6 @@ from .payments import (
|
|||||||
get_payments_daily_stats,
|
get_payments_daily_stats,
|
||||||
pay_invoice,
|
pay_invoice,
|
||||||
service_fee,
|
service_fee,
|
||||||
settle_hold_invoice,
|
|
||||||
update_pending_payment,
|
update_pending_payment,
|
||||||
update_pending_payments,
|
update_pending_payments,
|
||||||
update_wallet_balance,
|
update_wallet_balance,
|
||||||
@@ -38,7 +36,6 @@ from .websockets import websocket_manager, websocket_updater
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"calculate_fiat_amounts",
|
"calculate_fiat_amounts",
|
||||||
"cancel_hold_invoice",
|
|
||||||
"check_admin_settings",
|
"check_admin_settings",
|
||||||
"check_transaction_status",
|
"check_transaction_status",
|
||||||
"check_wallet_limits",
|
"check_wallet_limits",
|
||||||
@@ -49,17 +46,16 @@ __all__ = [
|
|||||||
"create_user_account",
|
"create_user_account",
|
||||||
"create_user_account_no_ckeck",
|
"create_user_account_no_ckeck",
|
||||||
"create_wallet_invoice",
|
"create_wallet_invoice",
|
||||||
"enqueue_admin_notification",
|
"enqueue_notification",
|
||||||
"fee_reserve",
|
"fee_reserve",
|
||||||
"fee_reserve_total",
|
"fee_reserve_total",
|
||||||
"fetch_lnurl_pay_request",
|
|
||||||
"get_balance_delta",
|
"get_balance_delta",
|
||||||
"get_payments_daily_stats",
|
"get_payments_daily_stats",
|
||||||
"get_pr_from_lnurl",
|
|
||||||
"pay_invoice",
|
"pay_invoice",
|
||||||
|
"perform_lnurlauth",
|
||||||
|
"redeem_lnurl_withdraw",
|
||||||
"send_payment_notification",
|
"send_payment_notification",
|
||||||
"service_fee",
|
"service_fee",
|
||||||
"settle_hold_invoice",
|
|
||||||
"switch_to_voidwallet",
|
"switch_to_voidwallet",
|
||||||
"update_cached_settings",
|
"update_cached_settings",
|
||||||
"update_pending_payment",
|
"update_pending_payment",
|
||||||
|
|||||||
@@ -176,11 +176,6 @@ async def test_connection(provider: str) -> SimpleStatus:
|
|||||||
This function should be called when setting up or testing the Stripe integration.
|
This function should be called when setting up or testing the Stripe integration.
|
||||||
"""
|
"""
|
||||||
fiat_provider = await get_fiat_provider(provider)
|
fiat_provider = await get_fiat_provider(provider)
|
||||||
if not fiat_provider:
|
|
||||||
return SimpleStatus(
|
|
||||||
success=False,
|
|
||||||
message=f"Fiat provider '{provider}' not found.",
|
|
||||||
)
|
|
||||||
status = await fiat_provider.status()
|
status = await fiat_provider.status()
|
||||||
if status.error_message:
|
if status.error_message:
|
||||||
return SimpleStatus(
|
return SimpleStatus(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.models.notifications import NotificationType
|
from lnbits.core.models.notifications import NotificationType
|
||||||
from lnbits.core.services.notifications import enqueue_admin_notification
|
from lnbits.core.services.notifications import enqueue_notification
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_funding_source, set_funding_source
|
from lnbits.wallets import get_funding_source, set_funding_source
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ async def check_server_balance_against_node():
|
|||||||
f"Balance delta reached: {status.delta_sats} sats."
|
f"Balance delta reached: {status.delta_sats} sats."
|
||||||
f" Switch to void wallet: {use_voidwallet}."
|
f" Switch to void wallet: {use_voidwallet}."
|
||||||
)
|
)
|
||||||
enqueue_admin_notification(
|
enqueue_notification(
|
||||||
NotificationType.watchdog_check,
|
NotificationType.watchdog_check,
|
||||||
{
|
{
|
||||||
"delta_sats": status.delta_sats,
|
"delta_sats": status.delta_sats,
|
||||||
@@ -71,7 +71,7 @@ async def check_balance_delta_changed():
|
|||||||
settings.latest_balance_delta_sats = status.delta_sats
|
settings.latest_balance_delta_sats = status.delta_sats
|
||||||
return
|
return
|
||||||
if status.delta_sats != settings.latest_balance_delta_sats:
|
if status.delta_sats != settings.latest_balance_delta_sats:
|
||||||
enqueue_admin_notification(
|
enqueue_notification(
|
||||||
NotificationType.balance_delta,
|
NotificationType.balance_delta,
|
||||||
{
|
{
|
||||||
"delta_sats": status.delta_sats,
|
"delta_sats": status.delta_sats,
|
||||||
|
|||||||
+149
-48
@@ -1,58 +1,159 @@
|
|||||||
from lnurl import (
|
import asyncio
|
||||||
LnurlErrorResponse,
|
import json
|
||||||
LnurlPayActionResponse,
|
from io import BytesIO
|
||||||
LnurlPayResponse,
|
from typing import Optional
|
||||||
LnurlResponseException,
|
from urllib.parse import parse_qs, urlparse
|
||||||
execute_pay_request,
|
|
||||||
handle,
|
import httpx
|
||||||
|
from fastapi import Depends
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from lnbits.db import Connection
|
||||||
|
from lnbits.decorators import (
|
||||||
|
WalletTypeInfo,
|
||||||
|
require_admin_key,
|
||||||
)
|
)
|
||||||
|
from lnbits.helpers import check_callback_url, url_for
|
||||||
from lnbits.core.models import CreateLnurlPayment
|
from lnbits.lnurl import LnurlErrorResponse
|
||||||
|
from lnbits.lnurl import decode as decode_lnurl
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
|
|
||||||
|
from .payments import create_invoice
|
||||||
|
|
||||||
|
|
||||||
async def get_pr_from_lnurl(
|
async def redeem_lnurl_withdraw(
|
||||||
lnurl: str, amount_msat: int, comment: str | None = None
|
wallet_id: str,
|
||||||
) -> str:
|
lnurl_request: str,
|
||||||
res = await handle(lnurl, user_agent=settings.user_agent, timeout=10)
|
memo: Optional[str] = None,
|
||||||
if isinstance(res, LnurlErrorResponse):
|
extra: Optional[dict] = None,
|
||||||
raise LnurlResponseException(res.reason)
|
wait_seconds: int = 0,
|
||||||
if not isinstance(res, LnurlPayResponse):
|
conn: Optional[Connection] = None,
|
||||||
raise LnurlResponseException(
|
) -> None:
|
||||||
"Invalid LNURL response. Expected LnurlPayResponse."
|
if not lnurl_request:
|
||||||
|
return None
|
||||||
|
|
||||||
|
res = {}
|
||||||
|
|
||||||
|
headers = {"User-Agent": settings.user_agent}
|
||||||
|
async with httpx.AsyncClient(headers=headers) as client:
|
||||||
|
lnurl = decode_lnurl(lnurl_request)
|
||||||
|
check_callback_url(str(lnurl))
|
||||||
|
r = await client.get(str(lnurl))
|
||||||
|
res = r.json()
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, payment_request = await create_invoice(
|
||||||
|
wallet_id=wallet_id,
|
||||||
|
amount=int(res["maxWithdrawable"] / 1000),
|
||||||
|
memo=memo or res["defaultDescription"] or "",
|
||||||
|
extra=extra,
|
||||||
|
conn=conn,
|
||||||
)
|
)
|
||||||
res2 = await execute_pay_request(
|
except Exception:
|
||||||
res,
|
logger.warning(
|
||||||
msat=amount_msat,
|
f"failed to create invoice on redeem_lnurl_withdraw "
|
||||||
comment=comment,
|
f"from {lnurl}. params: {res}"
|
||||||
user_agent=settings.user_agent,
|
)
|
||||||
timeout=10,
|
return None
|
||||||
)
|
|
||||||
if isinstance(res2, LnurlErrorResponse):
|
if wait_seconds:
|
||||||
raise LnurlResponseException(res2.reason)
|
await asyncio.sleep(wait_seconds)
|
||||||
return res2.pr
|
|
||||||
|
params = {"k1": res["k1"], "pr": payment_request}
|
||||||
|
|
||||||
|
try:
|
||||||
|
params["balanceNotify"] = url_for(
|
||||||
|
f"/withdraw/notify/{urlparse(lnurl_request).netloc}",
|
||||||
|
external=True,
|
||||||
|
wal=wallet_id,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug(exc)
|
||||||
|
|
||||||
|
headers = {"User-Agent": settings.user_agent}
|
||||||
|
async with httpx.AsyncClient(headers=headers) as client:
|
||||||
|
try:
|
||||||
|
check_callback_url(res["callback"])
|
||||||
|
await client.get(res["callback"], params=params)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug(exc)
|
||||||
|
|
||||||
|
|
||||||
async def fetch_lnurl_pay_request(data: CreateLnurlPayment) -> LnurlPayActionResponse:
|
async def perform_lnurlauth(
|
||||||
"""
|
callback: str,
|
||||||
Pay an LNURL payment request.
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
|
) -> Optional[LnurlErrorResponse]:
|
||||||
|
cb = urlparse(callback)
|
||||||
|
|
||||||
raises `LnurlResponseException` if pay request fails
|
k1 = bytes.fromhex(parse_qs(cb.query)["k1"][0])
|
||||||
"""
|
|
||||||
|
|
||||||
if data.unit and data.unit != "sat":
|
key = wallet.wallet.lnurlauth_key(cb.netloc)
|
||||||
# shift to float with 2 decimal places
|
|
||||||
amount = round(data.amount / 1000, 2)
|
|
||||||
amount_msat = await fiat_amount_as_satoshis(amount, data.unit)
|
|
||||||
amount_msat *= 1000
|
|
||||||
else:
|
|
||||||
amount_msat = data.amount
|
|
||||||
|
|
||||||
return await execute_pay_request(
|
def int_to_bytes_suitable_der(x: int) -> bytes:
|
||||||
data.res,
|
"""for strict DER we need to encode the integer with some quirks"""
|
||||||
msat=amount_msat,
|
b = x.to_bytes((x.bit_length() + 7) // 8, "big")
|
||||||
comment=data.comment,
|
|
||||||
user_agent=settings.user_agent,
|
if len(b) == 0:
|
||||||
timeout=10,
|
# ensure there's at least one byte when the int is zero
|
||||||
)
|
return bytes([0])
|
||||||
|
|
||||||
|
if b[0] & 0x80 != 0:
|
||||||
|
# ensure it doesn't start with a 0x80 and so it isn't
|
||||||
|
# interpreted as a negative number
|
||||||
|
return bytes([0]) + b
|
||||||
|
|
||||||
|
return b
|
||||||
|
|
||||||
|
def encode_strict_der(r: int, s: int, order: int):
|
||||||
|
# if s > order/2 verification will fail sometimes
|
||||||
|
# so we must fix it here see:
|
||||||
|
# https://github.com/indutny/elliptic/blob/e71b2d9359c5fe9437fbf46f1f05096de447de57/lib/elliptic/ec/index.js#L146-L147
|
||||||
|
if s > order // 2:
|
||||||
|
s = order - s
|
||||||
|
|
||||||
|
# now we do the strict DER encoding copied from
|
||||||
|
# https://github.com/KiriKiri/bip66 (without any checks)
|
||||||
|
r_temp = int_to_bytes_suitable_der(r)
|
||||||
|
s_temp = int_to_bytes_suitable_der(s)
|
||||||
|
|
||||||
|
r_len = len(r_temp)
|
||||||
|
s_len = len(s_temp)
|
||||||
|
sign_len = 6 + r_len + s_len
|
||||||
|
|
||||||
|
signature = BytesIO()
|
||||||
|
signature.write(0x30.to_bytes(1, "big", signed=False))
|
||||||
|
signature.write((sign_len - 2).to_bytes(1, "big", signed=False))
|
||||||
|
signature.write(0x02.to_bytes(1, "big", signed=False))
|
||||||
|
signature.write(r_len.to_bytes(1, "big", signed=False))
|
||||||
|
signature.write(r_temp)
|
||||||
|
signature.write(0x02.to_bytes(1, "big", signed=False))
|
||||||
|
signature.write(s_len.to_bytes(1, "big", signed=False))
|
||||||
|
signature.write(s_temp)
|
||||||
|
|
||||||
|
return signature.getvalue()
|
||||||
|
|
||||||
|
sig = key.sign_digest_deterministic(k1, sigencode=encode_strict_der)
|
||||||
|
|
||||||
|
headers = {"User-Agent": settings.user_agent}
|
||||||
|
async with httpx.AsyncClient(headers=headers) as client:
|
||||||
|
if not key.verifying_key:
|
||||||
|
raise ValueError("LNURLauth verifying_key does not exist")
|
||||||
|
check_callback_url(callback)
|
||||||
|
r = await client.get(
|
||||||
|
callback,
|
||||||
|
params={
|
||||||
|
"k1": k1.hex(),
|
||||||
|
"key": key.verifying_key.to_string("compressed").hex(),
|
||||||
|
"sig": sig.hex(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
resp = json.loads(r.text)
|
||||||
|
if resp["status"] == "OK":
|
||||||
|
return None
|
||||||
|
|
||||||
|
return LnurlErrorResponse(reason=resp["reason"])
|
||||||
|
except (KeyError, json.decoder.JSONDecodeError):
|
||||||
|
return LnurlErrorResponse(
|
||||||
|
reason=r.text[:200] + "..." if len(r.text) > 200 else r.text
|
||||||
|
)
|
||||||
|
|||||||
@@ -16,14 +16,12 @@ from lnbits.core.crud import (
|
|||||||
get_webpush_subscriptions_for_user,
|
get_webpush_subscriptions_for_user,
|
||||||
mark_webhook_sent,
|
mark_webhook_sent,
|
||||||
)
|
)
|
||||||
from lnbits.core.crud.users import get_user
|
|
||||||
from lnbits.core.models import Payment, Wallet
|
from lnbits.core.models import Payment, Wallet
|
||||||
from lnbits.core.models.notifications import (
|
from lnbits.core.models.notifications import (
|
||||||
NOTIFICATION_TEMPLATES,
|
NOTIFICATION_TEMPLATES,
|
||||||
NotificationMessage,
|
NotificationMessage,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
)
|
)
|
||||||
from lnbits.core.models.users import UserNotifications
|
|
||||||
from lnbits.core.services.nostr import fetch_nip5_details, send_nostr_dm
|
from lnbits.core.services.nostr import fetch_nip5_details, send_nostr_dm
|
||||||
from lnbits.core.services.websockets import websocket_manager
|
from lnbits.core.services.websockets import websocket_manager
|
||||||
from lnbits.helpers import check_callback_url, is_valid_email_address
|
from lnbits.helpers import check_callback_url, is_valid_email_address
|
||||||
@@ -33,8 +31,8 @@ from lnbits.utils.nostr import normalize_private_key
|
|||||||
notifications_queue: asyncio.Queue[NotificationMessage] = asyncio.Queue()
|
notifications_queue: asyncio.Queue[NotificationMessage] = asyncio.Queue()
|
||||||
|
|
||||||
|
|
||||||
def enqueue_admin_notification(message_type: NotificationType, values: dict) -> None:
|
def enqueue_notification(message_type: NotificationType, values: dict) -> None:
|
||||||
if not _is_message_type_enabled(message_type):
|
if not is_message_type_enabled(message_type):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
notifications_queue.put_nowait(
|
notifications_queue.put_nowait(
|
||||||
@@ -44,125 +42,62 @@ def enqueue_admin_notification(message_type: NotificationType, values: dict) ->
|
|||||||
logger.error(f"Error enqueuing notification: {e}")
|
logger.error(f"Error enqueuing notification: {e}")
|
||||||
|
|
||||||
|
|
||||||
def enqueue_user_notification(
|
|
||||||
message_type: NotificationType, values: dict, user_notifications: UserNotifications
|
|
||||||
) -> None:
|
|
||||||
try:
|
|
||||||
notifications_queue.put_nowait(
|
|
||||||
NotificationMessage(
|
|
||||||
message_type=message_type,
|
|
||||||
values=values,
|
|
||||||
user_notifications=user_notifications,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error enqueuing notification: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
async def process_next_notification() -> None:
|
async def process_next_notification() -> None:
|
||||||
notification_message = await notifications_queue.get()
|
notification_message = await notifications_queue.get()
|
||||||
message_type, text = _notification_message_to_text(notification_message)
|
message_type, text = _notification_message_to_text(notification_message)
|
||||||
user_notifications = notification_message.user_notifications
|
await send_notification(text, message_type)
|
||||||
if user_notifications:
|
|
||||||
await send_user_notification(user_notifications, text, message_type)
|
|
||||||
else:
|
|
||||||
await send_admin_notification(text, message_type)
|
|
||||||
|
|
||||||
|
|
||||||
async def send_admin_notification(
|
|
||||||
message: str,
|
|
||||||
message_type: Optional[str] = None,
|
|
||||||
) -> None:
|
|
||||||
return await send_notification(
|
|
||||||
settings.lnbits_telegram_notifications_chat_id,
|
|
||||||
settings.lnbits_nostr_notifications_identifiers,
|
|
||||||
settings.lnbits_email_notifications_to_emails,
|
|
||||||
message,
|
|
||||||
message_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def send_user_notification(
|
|
||||||
user_notifications: UserNotifications,
|
|
||||||
message: str,
|
|
||||||
message_type: Optional[str] = None,
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
email_address = (
|
|
||||||
[user_notifications.email_address] if user_notifications.email_address else []
|
|
||||||
)
|
|
||||||
nostr_identifiers = (
|
|
||||||
[user_notifications.nostr_identifier]
|
|
||||||
if user_notifications.nostr_identifier
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
return await send_notification(
|
|
||||||
user_notifications.telegram_chat_id,
|
|
||||||
nostr_identifiers,
|
|
||||||
email_address,
|
|
||||||
message,
|
|
||||||
message_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def send_notification(
|
async def send_notification(
|
||||||
telegram_chat_id: str | None,
|
|
||||||
nostr_identifiers: list[str] | None,
|
|
||||||
email_addresses: list[str] | None,
|
|
||||||
message: str,
|
message: str,
|
||||||
message_type: Optional[str] = None,
|
message_type: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
if telegram_chat_id and settings.is_telegram_notifications_configured():
|
if settings.lnbits_telegram_notifications_enabled:
|
||||||
await send_telegram_notification(telegram_chat_id, message)
|
await send_telegram_notification(message)
|
||||||
logger.debug(f"Sent telegram notification: {message_type}")
|
logger.debug(f"Sent telegram notification: {message_type}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending telegram notification {message_type}: {e}")
|
logger.error(f"Error sending telegram notification {message_type}: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if nostr_identifiers and settings.is_nostr_notifications_configured():
|
if settings.lnbits_nostr_notifications_enabled:
|
||||||
await send_nostr_notifications(nostr_identifiers, message)
|
await send_nostr_notification(message)
|
||||||
logger.debug(f"Sent nostr notification: {message_type}")
|
logger.debug(f"Sent nostr notification: {message_type}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending nostr notification {message_type}: {e}")
|
logger.error(f"Error sending nostr notification {message_type}: {e}")
|
||||||
try:
|
try:
|
||||||
if email_addresses and settings.lnbits_email_notifications_enabled:
|
if settings.lnbits_email_notifications_enabled:
|
||||||
await send_email_notification(email_addresses, message)
|
await send_email_notification(message)
|
||||||
logger.debug(f"Sent email notification: {message_type}")
|
logger.debug(f"Sent email notification: {message_type}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending email notification {message_type}: {e}")
|
logger.error(f"Error sending email notification {message_type}: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def send_nostr_notifications(identifiers: list[str], message: str) -> list[str]:
|
async def send_nostr_notification(message: str) -> dict:
|
||||||
success_sent: list[str] = []
|
for i in settings.lnbits_nostr_notifications_identifiers:
|
||||||
for identifier in identifiers:
|
|
||||||
try:
|
try:
|
||||||
await send_nostr_notification(identifier, message)
|
identifier = await fetch_nip5_details(i)
|
||||||
success_sent.append(identifier)
|
user_pubkey = identifier[0]
|
||||||
|
relays = identifier[1]
|
||||||
|
server_private_key = normalize_private_key(
|
||||||
|
settings.lnbits_nostr_notifications_private_key
|
||||||
|
)
|
||||||
|
await send_nostr_dm(
|
||||||
|
server_private_key,
|
||||||
|
user_pubkey,
|
||||||
|
message,
|
||||||
|
relays,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error notifying identifier {identifier}: {e}")
|
logger.warning(f"Error notifying identifier {i}: {e}")
|
||||||
return success_sent
|
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
async def send_nostr_notification(identifier: str, message: str):
|
async def send_telegram_notification(message: str) -> dict:
|
||||||
nip5 = await fetch_nip5_details(identifier)
|
|
||||||
user_pubkey = nip5[0]
|
|
||||||
relays = nip5[1]
|
|
||||||
server_private_key = normalize_private_key(
|
|
||||||
settings.lnbits_nostr_notifications_private_key
|
|
||||||
)
|
|
||||||
await send_nostr_dm(
|
|
||||||
server_private_key,
|
|
||||||
user_pubkey,
|
|
||||||
message,
|
|
||||||
relays,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def send_telegram_notification(chat_id: str, message: str) -> dict:
|
|
||||||
return await send_telegram_message(
|
return await send_telegram_message(
|
||||||
settings.lnbits_telegram_notifications_access_token,
|
settings.lnbits_telegram_notifications_access_token,
|
||||||
chat_id,
|
settings.lnbits_telegram_notifications_chat_id,
|
||||||
message,
|
message,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -177,7 +112,7 @@ async def send_telegram_message(token: str, chat_id: str, message: str) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
async def send_email_notification(
|
async def send_email_notification(
|
||||||
to_emails: list[str], message: str, subject: str = "LNbits Notification"
|
message: str, subject: str = "LNbits Notification"
|
||||||
) -> dict:
|
) -> dict:
|
||||||
if not settings.lnbits_email_notifications_enabled:
|
if not settings.lnbits_email_notifications_enabled:
|
||||||
return {"status": "error", "message": "Email notifications are disabled"}
|
return {"status": "error", "message": "Email notifications are disabled"}
|
||||||
@@ -188,7 +123,7 @@ async def send_email_notification(
|
|||||||
settings.lnbits_email_notifications_username,
|
settings.lnbits_email_notifications_username,
|
||||||
settings.lnbits_email_notifications_password,
|
settings.lnbits_email_notifications_password,
|
||||||
settings.lnbits_email_notifications_email,
|
settings.lnbits_email_notifications_email,
|
||||||
to_emails,
|
settings.lnbits_email_notifications_to_emails,
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
)
|
)
|
||||||
@@ -228,6 +163,41 @@ async def send_email(
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_message_type_enabled(message_type: NotificationType) -> bool:
|
||||||
|
if message_type == NotificationType.balance_update:
|
||||||
|
return settings.lnbits_notification_credit_debit
|
||||||
|
if message_type == NotificationType.settings_update:
|
||||||
|
return settings.lnbits_notification_settings_update
|
||||||
|
if message_type == NotificationType.watchdog_check:
|
||||||
|
return settings.lnbits_notification_watchdog
|
||||||
|
if message_type == NotificationType.balance_delta:
|
||||||
|
return settings.notification_balance_delta_changed
|
||||||
|
if message_type == NotificationType.server_start_stop:
|
||||||
|
return settings.lnbits_notification_server_start_stop
|
||||||
|
if message_type == NotificationType.server_status:
|
||||||
|
return settings.lnbits_notification_server_status_hours > 0
|
||||||
|
if message_type == NotificationType.incoming_payment:
|
||||||
|
return settings.lnbits_notification_incoming_payment_amount_sats > 0
|
||||||
|
if message_type == NotificationType.outgoing_payment:
|
||||||
|
return settings.lnbits_notification_outgoing_payment_amount_sats > 0
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _notification_message_to_text(
|
||||||
|
notification_message: NotificationMessage,
|
||||||
|
) -> tuple[str, str]:
|
||||||
|
message_type = notification_message.message_type.value
|
||||||
|
meesage_value = NOTIFICATION_TEMPLATES.get(message_type, message_type)
|
||||||
|
try:
|
||||||
|
text = meesage_value.format(**notification_message.values)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error formatting notification message: {e}")
|
||||||
|
text = meesage_value
|
||||||
|
text = f"""[{settings.lnbits_site_title}]\n{text}"""
|
||||||
|
return message_type, text
|
||||||
|
|
||||||
|
|
||||||
async def dispatch_webhook(payment: Payment):
|
async def dispatch_webhook(payment: Payment):
|
||||||
"""
|
"""
|
||||||
Dispatches the webhook to the webhook url.
|
Dispatches the webhook to the webhook url.
|
||||||
@@ -261,7 +231,7 @@ async def send_payment_notification(wallet: Wallet, payment: Payment):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending websocket payment notification {e!s}")
|
logger.error(f"Error sending websocket payment notification {e!s}")
|
||||||
try:
|
try:
|
||||||
await send_chat_payment_notification(wallet, payment)
|
send_chat_payment_notification(wallet, payment)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending chat payment notification {e!s}")
|
logger.error(f"Error sending chat payment notification {e!s}")
|
||||||
try:
|
try:
|
||||||
@@ -298,7 +268,7 @@ async def send_ws_payment_notification(wallet: Wallet, payment: Payment):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def send_chat_payment_notification(wallet: Wallet, payment: Payment):
|
def send_chat_payment_notification(wallet: Wallet, payment: Payment):
|
||||||
amount_sats = abs(payment.sat)
|
amount_sats = abs(payment.sat)
|
||||||
values: dict = {
|
values: dict = {
|
||||||
"wallet_id": wallet.id,
|
"wallet_id": wallet.id,
|
||||||
@@ -313,23 +283,10 @@ async def send_chat_payment_notification(wallet: Wallet, payment: Payment):
|
|||||||
|
|
||||||
if payment.is_out:
|
if payment.is_out:
|
||||||
if amount_sats >= settings.lnbits_notification_outgoing_payment_amount_sats:
|
if amount_sats >= settings.lnbits_notification_outgoing_payment_amount_sats:
|
||||||
enqueue_admin_notification(NotificationType.outgoing_payment, values)
|
enqueue_notification(NotificationType.outgoing_payment, values)
|
||||||
elif amount_sats >= settings.lnbits_notification_incoming_payment_amount_sats:
|
else:
|
||||||
enqueue_admin_notification(NotificationType.incoming_payment, values)
|
if amount_sats >= settings.lnbits_notification_incoming_payment_amount_sats:
|
||||||
|
enqueue_notification(NotificationType.incoming_payment, values)
|
||||||
user = await get_user(wallet.user)
|
|
||||||
user_notifications = user.extra.notifications if user else None
|
|
||||||
if user_notifications and wallet.id not in user_notifications.excluded_wallets:
|
|
||||||
out_limit = user_notifications.outgoing_payments_sats
|
|
||||||
in_limit = user_notifications.incoming_payments_sats
|
|
||||||
if payment.is_out and (amount_sats >= out_limit):
|
|
||||||
enqueue_user_notification(
|
|
||||||
NotificationType.outgoing_payment, values, user_notifications
|
|
||||||
)
|
|
||||||
elif amount_sats >= in_limit:
|
|
||||||
enqueue_user_notification(
|
|
||||||
NotificationType.incoming_payment, values, user_notifications
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def send_payment_push_notification(wallet: Wallet, payment: Payment):
|
async def send_payment_push_notification(wallet: Wallet, payment: Payment):
|
||||||
@@ -373,38 +330,3 @@ async def send_push_notification(subscription, title, body, url=""):
|
|||||||
f"failed sending push notification: "
|
f"failed sending push notification: "
|
||||||
f"{e.response.text if e.response else e}"
|
f"{e.response.text if e.response else e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _is_message_type_enabled(message_type: NotificationType) -> bool:
|
|
||||||
if message_type == NotificationType.balance_update:
|
|
||||||
return settings.lnbits_notification_credit_debit
|
|
||||||
if message_type == NotificationType.settings_update:
|
|
||||||
return settings.lnbits_notification_settings_update
|
|
||||||
if message_type == NotificationType.watchdog_check:
|
|
||||||
return settings.lnbits_notification_watchdog
|
|
||||||
if message_type == NotificationType.balance_delta:
|
|
||||||
return settings.notification_balance_delta_changed
|
|
||||||
if message_type == NotificationType.server_start_stop:
|
|
||||||
return settings.lnbits_notification_server_start_stop
|
|
||||||
if message_type == NotificationType.server_status:
|
|
||||||
return settings.lnbits_notification_server_status_hours > 0
|
|
||||||
if message_type == NotificationType.incoming_payment:
|
|
||||||
return settings.lnbits_notification_incoming_payment_amount_sats > 0
|
|
||||||
if message_type == NotificationType.outgoing_payment:
|
|
||||||
return settings.lnbits_notification_outgoing_payment_amount_sats > 0
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _notification_message_to_text(
|
|
||||||
notification_message: NotificationMessage,
|
|
||||||
) -> tuple[str, str]:
|
|
||||||
message_type = notification_message.message_type.value
|
|
||||||
meesage_value = NOTIFICATION_TEMPLATES.get(message_type, message_type)
|
|
||||||
try:
|
|
||||||
text = meesage_value.format(**notification_message.values)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Error formatting notification message: {e}")
|
|
||||||
text = meesage_value
|
|
||||||
text = f"""[{settings.lnbits_site_title}]\n{text}"""
|
|
||||||
return message_type, text
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
from bolt11 import Bolt11, MilliSatoshi, Tags
|
from bolt11 import Bolt11, MilliSatoshi, Tags
|
||||||
from bolt11 import decode as bolt11_decode
|
from bolt11 import decode as bolt11_decode
|
||||||
from bolt11 import encode as bolt11_encode
|
from bolt11 import encode as bolt11_encode
|
||||||
from lnurl import LnurlErrorResponse, LnurlSuccessResponse
|
|
||||||
from lnurl import execute_withdraw as lnurl_withdraw
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.crud.payments import get_daily_stats
|
from lnbits.core.crud.payments import get_daily_stats
|
||||||
@@ -16,16 +16,15 @@ from lnbits.core.models import PaymentDailyStats, PaymentFilters
|
|||||||
from lnbits.core.models.payments import CreateInvoice
|
from lnbits.core.models.payments import CreateInvoice
|
||||||
from lnbits.db import Connection, Filters
|
from lnbits.db import Connection, Filters
|
||||||
from lnbits.decorators import check_user_extension_access
|
from lnbits.decorators import check_user_extension_access
|
||||||
from lnbits.exceptions import InvoiceError, PaymentError, UnsupportedError
|
from lnbits.exceptions import InvoiceError, PaymentError
|
||||||
from lnbits.fiat import get_fiat_provider
|
from lnbits.fiat import get_fiat_provider
|
||||||
from lnbits.helpers import check_callback_url
|
from lnbits.helpers import check_callback_url
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.tasks import create_task, internal_invoice_queue_put
|
from lnbits.tasks import create_task, internal_invoice_queue_put
|
||||||
from lnbits.utils.crypto import fake_privkey, random_secret_and_hash, verify_preimage
|
from lnbits.utils.crypto import fake_privkey, random_secret_and_hash
|
||||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
||||||
from lnbits.wallets import fake_wallet, get_funding_source
|
from lnbits.wallets import fake_wallet, get_funding_source
|
||||||
from lnbits.wallets.base import (
|
from lnbits.wallets.base import (
|
||||||
InvoiceResponse,
|
|
||||||
PaymentPendingStatus,
|
PaymentPendingStatus,
|
||||||
PaymentResponse,
|
PaymentResponse,
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
@@ -91,8 +90,12 @@ async def pay_invoice(
|
|||||||
|
|
||||||
payment = await _pay_invoice(wallet.id, create_payment_model, conn)
|
payment = await _pay_invoice(wallet.id, create_payment_model, conn)
|
||||||
|
|
||||||
|
service_fee_memo = f"""
|
||||||
|
Service fee for payment of {abs(payment.sat)} sats.
|
||||||
|
Wallet: '{wallet.name}' ({wallet.id})."""
|
||||||
|
|
||||||
async with db.reuse_conn(conn) if conn else db.connect() as new_conn:
|
async with db.reuse_conn(conn) if conn else db.connect() as new_conn:
|
||||||
await _credit_service_fee_wallet(wallet, payment, new_conn)
|
await _credit_service_fee_wallet(payment, service_fee_memo, new_conn)
|
||||||
|
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
@@ -132,9 +135,6 @@ async def create_fiat_invoice(
|
|||||||
internal_payment = await create_wallet_invoice(wallet_id, invoice_data)
|
internal_payment = await create_wallet_invoice(wallet_id, invoice_data)
|
||||||
|
|
||||||
fiat_provider = await get_fiat_provider(fiat_provider_name)
|
fiat_provider = await get_fiat_provider(fiat_provider_name)
|
||||||
if not fiat_provider:
|
|
||||||
raise InvoiceError("No fiat provider found.", status="failed")
|
|
||||||
|
|
||||||
fiat_invoice = await fiat_provider.create_invoice(
|
fiat_invoice = await fiat_provider.create_invoice(
|
||||||
amount=invoice_data.amount,
|
amount=invoice_data.amount,
|
||||||
payment_hash=internal_payment.payment_hash,
|
payment_hash=internal_payment.payment_hash,
|
||||||
@@ -168,8 +168,8 @@ async def create_fiat_invoice(
|
|||||||
|
|
||||||
|
|
||||||
async def create_wallet_invoice(wallet_id: str, data: CreateInvoice) -> Payment:
|
async def create_wallet_invoice(wallet_id: str, data: CreateInvoice) -> Payment:
|
||||||
description_hash = None
|
description_hash = b""
|
||||||
unhashed_description = None
|
unhashed_description = b""
|
||||||
memo = data.memo or settings.lnbits_site_title
|
memo = data.memo or settings.lnbits_site_title
|
||||||
if data.description_hash or data.unhashed_description:
|
if data.description_hash or data.unhashed_description:
|
||||||
if data.description_hash:
|
if data.description_hash:
|
||||||
@@ -200,29 +200,30 @@ async def create_wallet_invoice(wallet_id: str, data: CreateInvoice) -> Payment:
|
|||||||
extra=data.extra,
|
extra=data.extra,
|
||||||
webhook=data.webhook,
|
webhook=data.webhook,
|
||||||
internal=data.internal,
|
internal=data.internal,
|
||||||
payment_hash=data.payment_hash,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if data.lnurl_withdraw:
|
# lnurl_response is not saved in the database
|
||||||
try:
|
if data.lnurl_callback:
|
||||||
check_callback_url(data.lnurl_withdraw.callback)
|
headers = {"User-Agent": settings.user_agent}
|
||||||
res = await lnurl_withdraw(
|
async with httpx.AsyncClient(headers=headers) as client:
|
||||||
data.lnurl_withdraw,
|
try:
|
||||||
payment.bolt11,
|
check_callback_url(data.lnurl_callback)
|
||||||
user_agent=settings.user_agent,
|
r = await client.get(
|
||||||
timeout=10,
|
data.lnurl_callback,
|
||||||
)
|
params={"pr": payment.bolt11},
|
||||||
if isinstance(res, LnurlErrorResponse):
|
timeout=10,
|
||||||
payment.extra["lnurl_response"] = res.reason
|
)
|
||||||
payment.status = "failed"
|
if r.is_error:
|
||||||
elif isinstance(res, LnurlSuccessResponse):
|
payment.extra["lnurl_response"] = r.text
|
||||||
payment.extra["lnurl_response"] = True
|
else:
|
||||||
payment.status = "success"
|
resp = json.loads(r.text)
|
||||||
except Exception as exc:
|
if resp["status"] != "OK":
|
||||||
payment.extra["lnurl_response"] = str(exc)
|
payment.extra["lnurl_response"] = resp["reason"]
|
||||||
payment.status = "failed"
|
else:
|
||||||
# updating to payment here would run into a race condition
|
payment.extra["lnurl_response"] = True
|
||||||
# with the payment listeners and they will overwrite each other
|
except (httpx.ConnectError, httpx.RequestError) as ex:
|
||||||
|
logger.error(ex)
|
||||||
|
payment.extra["lnurl_response"] = False
|
||||||
|
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
@@ -239,7 +240,6 @@ async def create_invoice(
|
|||||||
extra: Optional[dict] = None,
|
extra: Optional[dict] = None,
|
||||||
webhook: Optional[str] = None,
|
webhook: Optional[str] = None,
|
||||||
internal: Optional[bool] = False,
|
internal: Optional[bool] = False,
|
||||||
payment_hash: str | None = None,
|
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Payment:
|
) -> Payment:
|
||||||
if not amount > 0:
|
if not amount > 0:
|
||||||
@@ -273,54 +273,39 @@ async def create_invoice(
|
|||||||
status="failed",
|
status="failed",
|
||||||
)
|
)
|
||||||
|
|
||||||
if payment_hash:
|
payment_response = await funding_source.create_invoice(
|
||||||
try:
|
amount=amount_sat,
|
||||||
invoice_response = await funding_source.create_hold_invoice(
|
memo=invoice_memo,
|
||||||
amount=amount_sat,
|
description_hash=description_hash,
|
||||||
memo=invoice_memo,
|
unhashed_description=unhashed_description,
|
||||||
payment_hash=payment_hash,
|
expiry=expiry or settings.lightning_invoice_expiry,
|
||||||
description_hash=description_hash,
|
)
|
||||||
)
|
|
||||||
extra["hold_invoice"] = True
|
|
||||||
except UnsupportedError as exc:
|
|
||||||
raise InvoiceError(
|
|
||||||
"Hold invoices are not supported by the funding source.",
|
|
||||||
status="failed",
|
|
||||||
) from exc
|
|
||||||
else:
|
|
||||||
invoice_response = await funding_source.create_invoice(
|
|
||||||
amount=amount_sat,
|
|
||||||
memo=invoice_memo,
|
|
||||||
description_hash=description_hash,
|
|
||||||
unhashed_description=unhashed_description,
|
|
||||||
expiry=expiry or settings.lightning_invoice_expiry,
|
|
||||||
)
|
|
||||||
if (
|
if (
|
||||||
not invoice_response.ok
|
not payment_response.ok
|
||||||
or not invoice_response.payment_request
|
or not payment_response.payment_request
|
||||||
or not invoice_response.checking_id
|
or not payment_response.checking_id
|
||||||
):
|
):
|
||||||
raise InvoiceError(
|
raise InvoiceError(
|
||||||
message=invoice_response.error_message or "unexpected backend error.",
|
message=payment_response.error_message or "unexpected backend error.",
|
||||||
status="pending",
|
status="pending",
|
||||||
)
|
)
|
||||||
invoice = bolt11_decode(invoice_response.payment_request)
|
invoice = bolt11_decode(payment_response.payment_request)
|
||||||
|
|
||||||
create_payment_model = CreatePayment(
|
create_payment_model = CreatePayment(
|
||||||
wallet_id=wallet_id,
|
wallet_id=wallet_id,
|
||||||
bolt11=invoice_response.payment_request,
|
bolt11=payment_response.payment_request,
|
||||||
payment_hash=invoice.payment_hash,
|
payment_hash=invoice.payment_hash,
|
||||||
preimage=invoice_response.preimage,
|
preimage=payment_response.preimage,
|
||||||
amount_msat=amount_sat * 1000,
|
amount_msat=amount_sat * 1000,
|
||||||
expiry=invoice.expiry_date,
|
expiry=invoice.expiry_date,
|
||||||
memo=memo,
|
memo=memo,
|
||||||
extra=extra,
|
extra=extra,
|
||||||
webhook=webhook,
|
webhook=webhook,
|
||||||
fee=invoice_response.fee_msat or 0,
|
fee=payment_response.fee_msat or 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
payment = await create_payment(
|
payment = await create_payment(
|
||||||
checking_id=invoice_response.checking_id,
|
checking_id=payment_response.checking_id,
|
||||||
data=create_payment_model,
|
data=create_payment_model,
|
||||||
conn=conn,
|
conn=conn,
|
||||||
)
|
)
|
||||||
@@ -901,16 +886,12 @@ def _validate_payment_request(
|
|||||||
|
|
||||||
|
|
||||||
async def _credit_service_fee_wallet(
|
async def _credit_service_fee_wallet(
|
||||||
wallet: Wallet, payment: Payment, conn: Optional[Connection] = None
|
payment: Payment, memo: str, conn: Optional[Connection] = None
|
||||||
):
|
):
|
||||||
service_fee_msat = service_fee(payment.amount, internal=payment.is_internal)
|
service_fee_msat = service_fee(payment.amount, internal=payment.is_internal)
|
||||||
if not settings.lnbits_service_fee_wallet or not service_fee_msat:
|
if not settings.lnbits_service_fee_wallet or not service_fee_msat:
|
||||||
return
|
return
|
||||||
|
|
||||||
memo = f"""
|
|
||||||
Service fee for payment of {abs(payment.sat)} sats.
|
|
||||||
Wallet: '{wallet.name}' ({wallet.id})."""
|
|
||||||
|
|
||||||
create_payment_model = CreatePayment(
|
create_payment_model = CreatePayment(
|
||||||
wallet_id=settings.lnbits_service_fee_wallet,
|
wallet_id=settings.lnbits_service_fee_wallet,
|
||||||
bolt11=payment.bolt11,
|
bolt11=payment.bolt11,
|
||||||
@@ -968,40 +949,3 @@ async def _check_fiat_invoice_limits(
|
|||||||
f"The amount exceeds the '{fiat_provider_name}'"
|
f"The amount exceeds the '{fiat_provider_name}'"
|
||||||
"faucet wallet balance.",
|
"faucet wallet balance.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def settle_hold_invoice(payment: Payment, preimage: str) -> InvoiceResponse:
|
|
||||||
if verify_preimage(preimage, payment.payment_hash) is False:
|
|
||||||
raise InvoiceError("Invalid preimage.", status="failed")
|
|
||||||
|
|
||||||
funding_source = get_funding_source()
|
|
||||||
response = await funding_source.settle_hold_invoice(preimage=preimage)
|
|
||||||
|
|
||||||
if not response.ok:
|
|
||||||
raise InvoiceError(
|
|
||||||
response.error_message or "Unexpected backend error.", status="failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
payment.preimage = preimage
|
|
||||||
payment.extra["hold_invoice_settled"] = True
|
|
||||||
await update_payment(payment)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
async def cancel_hold_invoice(payment: Payment) -> InvoiceResponse:
|
|
||||||
funding_source = get_funding_source()
|
|
||||||
response = await funding_source.cancel_hold_invoice(
|
|
||||||
payment_hash=payment.payment_hash
|
|
||||||
)
|
|
||||||
|
|
||||||
if not response.ok:
|
|
||||||
raise InvoiceError(
|
|
||||||
response.error_message or "Unexpected backend error.", status="failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
payment.status = PaymentState.FAILED
|
|
||||||
payment.extra["hold_invoice_cancelled"] = True
|
|
||||||
await update_payment(payment)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from lnbits.core.services.funding_source import (
|
|||||||
get_balance_delta,
|
get_balance_delta,
|
||||||
)
|
)
|
||||||
from lnbits.core.services.notifications import (
|
from lnbits.core.services.notifications import (
|
||||||
enqueue_admin_notification,
|
enqueue_notification,
|
||||||
process_next_notification,
|
process_next_notification,
|
||||||
send_payment_notification,
|
send_payment_notification,
|
||||||
)
|
)
|
||||||
@@ -87,7 +87,7 @@ async def _notify_server_status() -> None:
|
|||||||
"lnbits_balance_sats": status.lnbits_balance_sats,
|
"lnbits_balance_sats": status.lnbits_balance_sats,
|
||||||
"node_balance_sats": status.node_balance_sats,
|
"node_balance_sats": status.node_balance_sats,
|
||||||
}
|
}
|
||||||
enqueue_admin_notification(NotificationType.server_status, values)
|
enqueue_notification(NotificationType.server_status, values)
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue) -> None:
|
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue) -> None:
|
||||||
@@ -123,7 +123,7 @@ async def wait_notification_messages() -> None:
|
|||||||
try:
|
try:
|
||||||
await process_next_notification()
|
await process_next_notification()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.warning("Payment notification error", ex)
|
logger.log("error", ex)
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,71 +7,38 @@
|
|||||||
<div class="row q-col-gutter-md q-mb-md">
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<q-card>
|
<q-card>
|
||||||
<div class="q-pa-sm">
|
<div>
|
||||||
<div class="row items-center justify-between q-gutter-xs">
|
<div class="q-gutter-y-md">
|
||||||
<div class="col">
|
<q-tabs v-model="tab" align="left">
|
||||||
<q-btn @click="updateAccount" unelevated color="primary">
|
<q-tab
|
||||||
<span v-text="$t('update_account')"></span>
|
name="user"
|
||||||
</q-btn>
|
:label="$t('account_settings')"
|
||||||
</div>
|
@update="val => tab = val.name"
|
||||||
|
></q-tab>
|
||||||
|
<q-tab
|
||||||
|
name="theme"
|
||||||
|
:label="$t('look_and_feel')"
|
||||||
|
@update="val => tab = val.name"
|
||||||
|
></q-tab>
|
||||||
|
<q-tab
|
||||||
|
name="api_acls"
|
||||||
|
:label="$t('access_control_list')"
|
||||||
|
@update="val => tab = val.name"
|
||||||
|
></q-tab>
|
||||||
|
</q-tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row q-col-gutter-md q-mb-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12">
|
<div v-if="user" class="col-md-12 col-lg-6 q-gutter-y-md">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-splitter>
|
<q-card-section>
|
||||||
<template v-slot:before>
|
<div class="row">
|
||||||
<q-tabs v-model="tab" vertical active-color="primary">
|
<div class="col">
|
||||||
<q-tab
|
<q-tab-panels v-model="tab">
|
||||||
name="user"
|
|
||||||
icon="person"
|
|
||||||
:label="$q.screen.gt.sm ? $t('account_settings') : ''"
|
|
||||||
@update="val => tab = val.name"
|
|
||||||
>
|
|
||||||
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
||||||
><span v-text="$t('account_settings')"></span
|
|
||||||
></q-tooltip>
|
|
||||||
</q-tab>
|
|
||||||
|
|
||||||
<q-tab
|
|
||||||
name="notifications"
|
|
||||||
icon="notifications"
|
|
||||||
:label="$q.screen.gt.sm ? $t('notifications') : ''"
|
|
||||||
@update="val => tab = val.name"
|
|
||||||
>
|
|
||||||
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
||||||
><span v-text="$t('notifications')"></span
|
|
||||||
></q-tooltip>
|
|
||||||
</q-tab>
|
|
||||||
<q-tab
|
|
||||||
name="theme"
|
|
||||||
icon="palette"
|
|
||||||
:label="$q.screen.gt.sm ? $t('look_and_feel') : ''"
|
|
||||||
@update="val => tab = val.name"
|
|
||||||
>
|
|
||||||
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
||||||
><span v-text="$t('look_and_feel')"></span
|
|
||||||
></q-tooltip>
|
|
||||||
</q-tab>
|
|
||||||
<q-tab
|
|
||||||
name="api_acls"
|
|
||||||
icon="lock"
|
|
||||||
:label="$q.screen.gt.sm ? $t('access_control_list') : ''"
|
|
||||||
@update="val => tab = val.name"
|
|
||||||
>
|
|
||||||
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
||||||
><span v-text="$t('access_control_list')"></span
|
|
||||||
></q-tooltip>
|
|
||||||
</q-tab>
|
|
||||||
</q-tabs>
|
|
||||||
</template>
|
|
||||||
<template v-slot:after>
|
|
||||||
<q-scroll-area style="height: 80vh">
|
|
||||||
<q-tab-panels v-if="user" v-model="tab">
|
|
||||||
<q-tab-panel name="user">
|
<q-tab-panel name="user">
|
||||||
<div v-if="credentialsData.show">
|
<div v-if="credentialsData.show">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
@@ -326,6 +293,9 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
|
<q-btn @click="updateAccount" unelevated color="primary">
|
||||||
|
<span v-text="$t('update_account')"></span>
|
||||||
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
@click="showUpdateCredentials()"
|
@click="showUpdateCredentials()"
|
||||||
:label="$t('change_password')"
|
:label="$t('change_password')"
|
||||||
@@ -589,101 +559,12 @@
|
|||||||
</q-select>
|
</q-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-tab-panel>
|
<q-separator></q-separator>
|
||||||
<q-tab-panel name="notifications">
|
<div class="row q-mb-md q-mt-md">
|
||||||
<q-card-section>
|
<q-btn @click="updateAccount" unelevated color="primary">
|
||||||
<div class="row q-mb-md">
|
<span v-text="$t('update_account')"></span>
|
||||||
<div class="col-4">
|
</q-btn>
|
||||||
<span
|
</div>
|
||||||
v-text="$t('notifications_nostr_identifier')"
|
|
||||||
></span>
|
|
||||||
{%if not nostr_configured%}
|
|
||||||
<br />
|
|
||||||
<q-badge v-text="$t('not_connected')"></q-badge>
|
|
||||||
{%endif%}
|
|
||||||
</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model="user.extra.notifications.nostr_identifier"
|
|
||||||
:hint="$t('notifications_nostr_identifier_desc')"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row q-mb-md">
|
|
||||||
<div class="col-4">
|
|
||||||
<span v-text="$t('notifications_chat_id')"></span>
|
|
||||||
{%if not telegram_configured%}
|
|
||||||
<br />
|
|
||||||
<q-badge v-text="$t('not_connected')"></q-badge>
|
|
||||||
{%endif%}
|
|
||||||
</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model="user.extra.notifications.telegram_chat_id"
|
|
||||||
:hint="$t('notifications_chat_id_desc')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-separator class="q-mb-md"></q-separator>
|
|
||||||
<div class="row q-mb-md">
|
|
||||||
<div class="col-4">
|
|
||||||
<span v-text="$t('notification_outgoing_payment')"></span>
|
|
||||||
</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
v-model="user.extra.notifications.outgoing_payments_sats"
|
|
||||||
:hint="$t('notification_outgoing_payment_desc')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row q-mb-md">
|
|
||||||
<div class="col-4">
|
|
||||||
<span v-text="$t('notification_incoming_payment')"></span>
|
|
||||||
</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
v-model="user.extra.notifications.incoming_payments_sats"
|
|
||||||
:hint="$t('notification_incoming_payment_desc')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row q-mb-md">
|
|
||||||
<div class="col-4">
|
|
||||||
<span v-text="$t('exclude_wallets')"></span>
|
|
||||||
</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
multiple
|
|
||||||
v-model="user.extra.notifications.excluded_wallets"
|
|
||||||
:options="g.user.walletOptions"
|
|
||||||
:label="$t('exclude_wallets')"
|
|
||||||
:hint="$t('notifications_excluded_wallets_desc')"
|
|
||||||
class="q-mt-sm"
|
|
||||||
>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
<q-tab-panel name="api_acls">
|
<q-tab-panel name="api_acls">
|
||||||
<div class="row q-mb-md">
|
<div class="row q-mb-md">
|
||||||
@@ -910,9 +791,16 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
</q-scroll-area>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</q-splitter>
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
<div v-else class="col-12 col-md-6 q-gutter-y-md">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<h4 class="q-my-none"><span v-text="$t('account')"></span></h4>
|
||||||
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -661,13 +661,7 @@
|
|||||||
:readonly="receive.lnurl && receive.lnurl.fixed"
|
:readonly="receive.lnurl && receive.lnurl.fixed"
|
||||||
></q-input>
|
></q-input>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<q-input
|
|
||||||
v-if="has_holdinvoice"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model="receive.data.payment_hash"
|
|
||||||
:label="$t('hold_invoice_payment_hash')"
|
|
||||||
></q-input>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from lnbits.core.models import User
|
|||||||
from lnbits.core.models.misc import Image, SimpleStatus
|
from lnbits.core.models.misc import Image, SimpleStatus
|
||||||
from lnbits.core.models.notifications import NotificationType
|
from lnbits.core.models.notifications import NotificationType
|
||||||
from lnbits.core.services import (
|
from lnbits.core.services import (
|
||||||
enqueue_admin_notification,
|
enqueue_notification,
|
||||||
get_balance_delta,
|
get_balance_delta,
|
||||||
update_cached_settings,
|
update_cached_settings,
|
||||||
)
|
)
|
||||||
@@ -65,9 +65,7 @@ async def api_monitor():
|
|||||||
)
|
)
|
||||||
async def api_test_email():
|
async def api_test_email():
|
||||||
return await send_email_notification(
|
return await send_email_notification(
|
||||||
settings.lnbits_email_notifications_to_emails,
|
"This is a LNbits test email.", "LNbits Test Email"
|
||||||
"This is a LNbits test email.",
|
|
||||||
"LNbits Test Email",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -84,9 +82,7 @@ async def api_get_settings(
|
|||||||
status_code=HTTPStatus.OK,
|
status_code=HTTPStatus.OK,
|
||||||
)
|
)
|
||||||
async def api_update_settings(data: UpdateSettings, user: User = Depends(check_admin)):
|
async def api_update_settings(data: UpdateSettings, user: User = Depends(check_admin)):
|
||||||
enqueue_admin_notification(
|
enqueue_notification(NotificationType.settings_update, {"username": user.username})
|
||||||
NotificationType.settings_update, {"username": user.username}
|
|
||||||
)
|
|
||||||
await update_admin_settings(data)
|
await update_admin_settings(data)
|
||||||
admin_settings = await get_admin_settings(user.super_user)
|
admin_settings = await get_admin_settings(user.super_user)
|
||||||
if not admin_settings:
|
if not admin_settings:
|
||||||
@@ -117,9 +113,7 @@ async def api_reset_settings(field_name: str):
|
|||||||
|
|
||||||
@admin_router.delete("/api/v1/settings", status_code=HTTPStatus.OK)
|
@admin_router.delete("/api/v1/settings", status_code=HTTPStatus.OK)
|
||||||
async def api_delete_settings(user: User = Depends(check_super_user)) -> None:
|
async def api_delete_settings(user: User = Depends(check_super_user)) -> None:
|
||||||
enqueue_admin_notification(
|
enqueue_notification(NotificationType.settings_update, {"username": user.username})
|
||||||
NotificationType.settings_update, {"username": user.username}
|
|
||||||
)
|
|
||||||
await reset_core_settings()
|
await reset_core_settings()
|
||||||
server_restart.set()
|
server_restart.set()
|
||||||
|
|
||||||
|
|||||||
+157
-3
@@ -1,20 +1,36 @@
|
|||||||
|
import hashlib
|
||||||
|
import json
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
|
||||||
|
|
||||||
|
import httpx
|
||||||
import pyqrcode
|
import pyqrcode
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import (
|
||||||
|
APIRouter,
|
||||||
|
Depends,
|
||||||
|
)
|
||||||
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
|
|
||||||
from lnbits.core.models import (
|
from lnbits.core.models import (
|
||||||
BaseWallet,
|
BaseWallet,
|
||||||
ConversionData,
|
ConversionData,
|
||||||
|
CreateLnurlAuth,
|
||||||
CreateWallet,
|
CreateWallet,
|
||||||
User,
|
User,
|
||||||
Wallet,
|
Wallet,
|
||||||
)
|
)
|
||||||
from lnbits.decorators import check_user_exists
|
from lnbits.decorators import (
|
||||||
|
WalletTypeInfo,
|
||||||
|
check_user_exists,
|
||||||
|
require_admin_key,
|
||||||
|
require_invoice_key,
|
||||||
|
)
|
||||||
|
from lnbits.helpers import check_callback_url
|
||||||
|
from lnbits.lnurl import decode as lnurl_decode
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.utils.exchange_rates import (
|
from lnbits.utils.exchange_rates import (
|
||||||
allowed_currencies,
|
allowed_currencies,
|
||||||
@@ -25,7 +41,7 @@ from lnbits.utils.exchange_rates import (
|
|||||||
from lnbits.wallets import get_funding_source
|
from lnbits.wallets import get_funding_source
|
||||||
from lnbits.wallets.base import StatusResponse
|
from lnbits.wallets.base import StatusResponse
|
||||||
|
|
||||||
from ..services import create_user_account
|
from ..services import create_user_account, perform_lnurlauth
|
||||||
|
|
||||||
api_router = APIRouter(tags=["Core"])
|
api_router = APIRouter(tags=["Core"])
|
||||||
|
|
||||||
@@ -76,6 +92,144 @@ async def api_create_account(data: CreateWallet) -> Wallet:
|
|||||||
return user.wallets[0]
|
return user.wallets[0]
|
||||||
|
|
||||||
|
|
||||||
|
@api_router.get("/api/v1/lnurlscan/{code}")
|
||||||
|
async def api_lnurlscan( # noqa: C901
|
||||||
|
code: str, wallet: WalletTypeInfo = Depends(require_invoice_key)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
url = str(lnurl_decode(code))
|
||||||
|
domain = urlparse(url).netloc
|
||||||
|
except Exception as exc:
|
||||||
|
# parse internet identifier (user@domain.com)
|
||||||
|
name_domain = code.split("@")
|
||||||
|
if len(name_domain) == 2 and len(name_domain[1].split(".")) >= 2:
|
||||||
|
name, domain = name_domain
|
||||||
|
url = (
|
||||||
|
("http://" if domain.endswith(".onion") else "https://")
|
||||||
|
+ domain
|
||||||
|
+ "/.well-known/lnurlp/"
|
||||||
|
+ name
|
||||||
|
)
|
||||||
|
# will proceed with these values
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
# params is what will be returned to the client
|
||||||
|
params: dict = {"domain": domain}
|
||||||
|
|
||||||
|
if "tag=login" in url:
|
||||||
|
params.update(kind="auth")
|
||||||
|
params.update(callback=url) # with k1 already in it
|
||||||
|
|
||||||
|
lnurlauth_key = wallet.wallet.lnurlauth_key(domain)
|
||||||
|
if not lnurlauth_key.verifying_key:
|
||||||
|
raise ValueError("LNURL auth key not found for this domain.")
|
||||||
|
params.update(pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex())
|
||||||
|
else:
|
||||||
|
headers = {"User-Agent": settings.user_agent}
|
||||||
|
async with httpx.AsyncClient(headers=headers, follow_redirects=True) as client:
|
||||||
|
check_callback_url(url)
|
||||||
|
try:
|
||||||
|
r = await client.get(url, timeout=5)
|
||||||
|
r.raise_for_status()
|
||||||
|
except httpx.HTTPStatusError as exc:
|
||||||
|
if exc.response.status_code == 404:
|
||||||
|
raise HTTPException(HTTPStatus.NOT_FOUND, "Not found") from exc
|
||||||
|
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
|
||||||
|
detail={
|
||||||
|
"domain": domain,
|
||||||
|
"message": "failed to get parameters",
|
||||||
|
},
|
||||||
|
) from exc
|
||||||
|
try:
|
||||||
|
data = json.loads(r.text)
|
||||||
|
except json.decoder.JSONDecodeError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
|
||||||
|
detail={
|
||||||
|
"domain": domain,
|
||||||
|
"message": f"got invalid response '{r.text[:200]}'",
|
||||||
|
},
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
try:
|
||||||
|
tag: str = data.get("tag")
|
||||||
|
params.update(**data)
|
||||||
|
if tag == "channelRequest":
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail={
|
||||||
|
"domain": domain,
|
||||||
|
"kind": "channel",
|
||||||
|
"message": "unsupported",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
elif tag == "withdrawRequest":
|
||||||
|
params.update(kind="withdraw")
|
||||||
|
params.update(fixed=data["minWithdrawable"] == data["maxWithdrawable"])
|
||||||
|
|
||||||
|
# callback with k1 already in it
|
||||||
|
parsed_callback: ParseResult = urlparse(data["callback"])
|
||||||
|
qs: dict = parse_qs(parsed_callback.query)
|
||||||
|
qs["k1"] = data["k1"]
|
||||||
|
|
||||||
|
# balanceCheck/balanceNotify
|
||||||
|
if "balanceCheck" in data:
|
||||||
|
params.update(balanceCheck=data["balanceCheck"])
|
||||||
|
|
||||||
|
# format callback url and send to client
|
||||||
|
parsed_callback = parsed_callback._replace(
|
||||||
|
query=urlencode(qs, doseq=True)
|
||||||
|
)
|
||||||
|
params.update(callback=urlunparse(parsed_callback))
|
||||||
|
elif tag == "payRequest":
|
||||||
|
params.update(kind="pay")
|
||||||
|
params.update(fixed=data["minSendable"] == data["maxSendable"])
|
||||||
|
|
||||||
|
params.update(
|
||||||
|
description_hash=hashlib.sha256(
|
||||||
|
data["metadata"].encode()
|
||||||
|
).hexdigest()
|
||||||
|
)
|
||||||
|
metadata = json.loads(data["metadata"])
|
||||||
|
for [k, v] in metadata:
|
||||||
|
if k == "text/plain":
|
||||||
|
params.update(description=v)
|
||||||
|
if k in ("image/jpeg;base64", "image/png;base64"):
|
||||||
|
data_uri = f"data:{k},{v}"
|
||||||
|
params.update(image=data_uri)
|
||||||
|
if k in ("text/email", "text/identifier"):
|
||||||
|
params.update(targetUser=v)
|
||||||
|
params.update(commentAllowed=data.get("commentAllowed", 0))
|
||||||
|
|
||||||
|
except KeyError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
|
||||||
|
detail={
|
||||||
|
"domain": domain,
|
||||||
|
"message": f"lnurl JSON response invalid: {exc}",
|
||||||
|
},
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
@api_router.post("/api/v1/lnurlauth")
|
||||||
|
async def api_perform_lnurlauth(
|
||||||
|
data: CreateLnurlAuth, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
|
):
|
||||||
|
err = await perform_lnurlauth(data.callback, wallet=wallet)
|
||||||
|
if err:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail=err.reason
|
||||||
|
)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@api_router.get(
|
@api_router.get(
|
||||||
"/api/v1/rate/history",
|
"/api/v1/rate/history",
|
||||||
dependencies=[Depends(check_user_exists)],
|
dependencies=[Depends(check_user_exists)],
|
||||||
|
|||||||
@@ -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"Disabling extension: {ext_id}.")
|
logger.info(f"Disabeling 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.")
|
||||||
|
|||||||
@@ -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 url_decode
|
from lnurl import decode as lnurl_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
|
||||||
@@ -206,15 +206,11 @@ async def account(
|
|||||||
request: Request,
|
request: Request,
|
||||||
user: User = Depends(check_user_exists),
|
user: User = Depends(check_user_exists),
|
||||||
):
|
):
|
||||||
nostr_configured = settings.is_nostr_notifications_configured()
|
|
||||||
telegram_configured = settings.is_telegram_notifications_configured()
|
|
||||||
return template_renderer().TemplateResponse(
|
return template_renderer().TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"core/account.html",
|
"core/account.html",
|
||||||
{
|
{
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
"nostr_configured": nostr_configured,
|
|
||||||
"telegram_configured": telegram_configured,
|
|
||||||
"ajax": _is_ajax_request(request),
|
"ajax": _is_ajax_request(request),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -345,6 +341,7 @@ async def node(request: Request, user: User = Depends(check_admin)):
|
|||||||
"node/index.html",
|
"node/index.html",
|
||||||
{
|
{
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
|
"settings": settings.dict(),
|
||||||
"balance": balance,
|
"balance": balance,
|
||||||
"wallets": user.wallets[0].json(),
|
"wallets": user.wallets[0].json(),
|
||||||
"ajax": _is_ajax_request(request),
|
"ajax": _is_ajax_request(request),
|
||||||
@@ -364,6 +361,7 @@ async def node_public(request: Request):
|
|||||||
request,
|
request,
|
||||||
"node/public.html",
|
"node/public.html",
|
||||||
{
|
{
|
||||||
|
"settings": settings.dict(),
|
||||||
"balance": balance,
|
"balance": balance,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -382,6 +380,7 @@ async def admin_index(request: Request, user: User = Depends(check_admin)):
|
|||||||
"admin/index.html",
|
"admin/index.html",
|
||||||
{
|
{
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
|
"settings": settings.dict(),
|
||||||
"balance": balance,
|
"balance": balance,
|
||||||
"currencies": list(currencies.keys()),
|
"currencies": list(currencies.keys()),
|
||||||
"ajax": _is_ajax_request(request),
|
"ajax": _is_ajax_request(request),
|
||||||
@@ -399,6 +398,7 @@ async def users_index(request: Request, user: User = Depends(check_admin)):
|
|||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
|
"settings": settings.dict(),
|
||||||
"currencies": list(currencies.keys()),
|
"currencies": list(currencies.keys()),
|
||||||
"ajax": _is_ajax_request(request),
|
"ajax": _is_ajax_request(request),
|
||||||
},
|
},
|
||||||
@@ -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 = url_decode(lightning)
|
lnurl = lnurl_decode(lightning)
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
check_callback_url(lnurl)
|
check_callback_url(lnurl)
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
from http import HTTPStatus
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from fastapi import (
|
|
||||||
APIRouter,
|
|
||||||
Depends,
|
|
||||||
HTTPException,
|
|
||||||
)
|
|
||||||
from lnurl import (
|
|
||||||
LnurlResponseException,
|
|
||||||
LnurlSuccessResponse,
|
|
||||||
)
|
|
||||||
from lnurl import execute_login as lnurlauth
|
|
||||||
from lnurl import execute_withdraw as lnurl_withdraw
|
|
||||||
from lnurl import handle as lnurl_handle
|
|
||||||
from lnurl.models import (
|
|
||||||
LnurlAuthResponse,
|
|
||||||
LnurlErrorResponse,
|
|
||||||
LnurlPayResponse,
|
|
||||||
LnurlResponseModel,
|
|
||||||
LnurlWithdrawResponse,
|
|
||||||
)
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from lnbits.core.models import CreateLnurlWithdraw, Payment
|
|
||||||
from lnbits.core.models.lnurl import CreateLnurlPayment, LnurlScan
|
|
||||||
from lnbits.decorators import (
|
|
||||||
WalletTypeInfo,
|
|
||||||
require_admin_key,
|
|
||||||
require_invoice_key,
|
|
||||||
)
|
|
||||||
from lnbits.helpers import check_callback_url
|
|
||||||
from lnbits.settings import settings
|
|
||||||
|
|
||||||
from ..services import fetch_lnurl_pay_request, pay_invoice
|
|
||||||
|
|
||||||
lnurl_router = APIRouter(tags=["LNURL"])
|
|
||||||
|
|
||||||
|
|
||||||
@lnurl_router.get(
|
|
||||||
"/api/v1/lnurlscan/{code}",
|
|
||||||
dependencies=[Depends(require_invoice_key)],
|
|
||||||
deprecated=True,
|
|
||||||
response_model=LnurlPayResponse
|
|
||||||
| LnurlWithdrawResponse
|
|
||||||
| LnurlAuthResponse
|
|
||||||
| LnurlErrorResponse,
|
|
||||||
)
|
|
||||||
async def api_lnurlscan(code: str) -> LnurlResponseModel:
|
|
||||||
try:
|
|
||||||
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)):
|
|
||||||
check_callback_url(res.callback)
|
|
||||||
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:
|
|
||||||
try:
|
|
||||||
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")
|
|
||||||
async def api_perform_lnurlauth(
|
|
||||||
data: LnurlAuthResponse, key_type: WalletTypeInfo = Depends(require_admin_key)
|
|
||||||
) -> LnurlResponseModel:
|
|
||||||
check_callback_url(data.callback)
|
|
||||||
try:
|
|
||||||
res = await lnurlauth(
|
|
||||||
res=data,
|
|
||||||
seed=key_type.wallet.adminkey,
|
|
||||||
user_agent=settings.user_agent,
|
|
||||||
timeout=5,
|
|
||||||
)
|
|
||||||
return res
|
|
||||||
except LnurlResponseException as exc:
|
|
||||||
logger.warning(exc)
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.BAD_REQUEST, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
|
|
||||||
@lnurl_router.post("/api/v1/payments/lnurl")
|
|
||||||
async def api_payments_pay_lnurl(
|
|
||||||
data: CreateLnurlPayment, wallet: WalletTypeInfo = Depends(require_admin_key)
|
|
||||||
) -> Payment:
|
|
||||||
try:
|
|
||||||
res = await fetch_lnurl_pay_request(data=data)
|
|
||||||
except LnurlResponseException as exc:
|
|
||||||
logger.warning(exc)
|
|
||||||
msg = f"Failed to connect to {data.res.callback}."
|
|
||||||
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) from exc
|
|
||||||
|
|
||||||
extra: dict[str, Any] = {}
|
|
||||||
if res.success_action:
|
|
||||||
extra["success_action"] = res.success_action.json()
|
|
||||||
if data.comment:
|
|
||||||
extra["comment"] = data.comment
|
|
||||||
if data.unit and data.unit != "sat":
|
|
||||||
extra["fiat_currency"] = data.unit
|
|
||||||
extra["fiat_amount"] = data.amount / 1000
|
|
||||||
|
|
||||||
payment = await pay_invoice(
|
|
||||||
wallet_id=wallet.wallet.id,
|
|
||||||
payment_request=str(res.pr),
|
|
||||||
description=data.res.metadata.text,
|
|
||||||
extra=extra,
|
|
||||||
)
|
|
||||||
|
|
||||||
return payment
|
|
||||||
|
|
||||||
|
|
||||||
@lnurl_router.post(
|
|
||||||
"/api/v1/payments/{payment_request}/pay-with-nfc", status_code=HTTPStatus.OK
|
|
||||||
)
|
|
||||||
async def api_payment_pay_with_nfc(
|
|
||||||
payment_request: str,
|
|
||||||
lnurl_data: CreateLnurlWithdraw,
|
|
||||||
) -> LnurlErrorResponse | LnurlSuccessResponse:
|
|
||||||
if not lnurl_data.lnurl_w.lud17:
|
|
||||||
return LnurlErrorResponse(reason="LNURL-withdraw lud17 not provided.")
|
|
||||||
try:
|
|
||||||
url = lnurl_data.lnurl_w.lud17
|
|
||||||
res = await lnurl_handle(url, user_agent=settings.user_agent, timeout=10)
|
|
||||||
except (LnurlResponseException, Exception) as exc:
|
|
||||||
return LnurlErrorResponse(reason=str(exc))
|
|
||||||
|
|
||||||
if not isinstance(res, LnurlWithdrawResponse):
|
|
||||||
return LnurlErrorResponse(reason="Invalid LNURL-withdraw response.")
|
|
||||||
try:
|
|
||||||
check_callback_url(res.callback)
|
|
||||||
except ValueError as exc:
|
|
||||||
return LnurlErrorResponse(reason=f"Invalid callback URL: {exc!s}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
res2 = await lnurl_withdraw(
|
|
||||||
res, payment_request, user_agent=settings.user_agent, timeout=10
|
|
||||||
)
|
|
||||||
except (LnurlResponseException, Exception) as exc:
|
|
||||||
logger.warning(exc)
|
|
||||||
return LnurlErrorResponse(reason=str(exc))
|
|
||||||
if not isinstance(res2, (LnurlSuccessResponse, LnurlErrorResponse)):
|
|
||||||
return LnurlErrorResponse(reason="Invalid LNURL-withdraw response.")
|
|
||||||
|
|
||||||
return res2
|
|
||||||
@@ -7,9 +7,8 @@ from pydantic import BaseModel
|
|||||||
from starlette.status import HTTP_503_SERVICE_UNAVAILABLE
|
from starlette.status import HTTP_503_SERVICE_UNAVAILABLE
|
||||||
|
|
||||||
from lnbits.decorators import check_admin, check_super_user, parse_filters
|
from lnbits.decorators import check_admin, check_super_user, parse_filters
|
||||||
|
from lnbits.nodes import get_node_class
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_funding_source
|
|
||||||
from lnbits.wallets.base import Feature
|
|
||||||
|
|
||||||
from ...db import Filters, Page
|
from ...db import Filters, Page
|
||||||
from ...nodes.base import (
|
from ...nodes.base import (
|
||||||
@@ -27,13 +26,9 @@ from ...nodes.base import (
|
|||||||
from ...utils.cache import cache
|
from ...utils.cache import cache
|
||||||
|
|
||||||
|
|
||||||
def require_node() -> Node:
|
def require_node():
|
||||||
funding_source = get_funding_source()
|
node_class = get_node_class()
|
||||||
if (
|
if not node_class:
|
||||||
not funding_source.features
|
|
||||||
or Feature.nodemanager not in funding_source.features
|
|
||||||
or not funding_source.__node_cls__
|
|
||||||
):
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_IMPLEMENTED,
|
status_code=HTTPStatus.NOT_IMPLEMENTED,
|
||||||
detail="Active backend does not implement Node API",
|
detail="Active backend does not implement Node API",
|
||||||
@@ -43,7 +38,7 @@ def require_node() -> Node:
|
|||||||
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
|
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
|
||||||
detail="Not enabled",
|
detail="Not enabled",
|
||||||
)
|
)
|
||||||
return funding_source.__node_cls__(funding_source)
|
return node_class
|
||||||
|
|
||||||
|
|
||||||
def check_public():
|
def check_public():
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
from hashlib import sha256
|
import json
|
||||||
|
import ssl
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from math import ceil
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import httpx
|
||||||
from fastapi import (
|
from fastapi import (
|
||||||
APIRouter,
|
APIRouter,
|
||||||
Depends,
|
Depends,
|
||||||
@@ -10,7 +14,7 @@ from fastapi import (
|
|||||||
Query,
|
Query,
|
||||||
)
|
)
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from lnurl import url_decode
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits import bolt11
|
from lnbits import bolt11
|
||||||
from lnbits.core.crud.payments import (
|
from lnbits.core.crud.payments import (
|
||||||
@@ -18,10 +22,11 @@ from lnbits.core.crud.payments import (
|
|||||||
get_wallets_stats,
|
get_wallets_stats,
|
||||||
)
|
)
|
||||||
from lnbits.core.models import (
|
from lnbits.core.models import (
|
||||||
CancelInvoice,
|
|
||||||
CreateInvoice,
|
CreateInvoice,
|
||||||
|
CreateLnurl,
|
||||||
DecodePayment,
|
DecodePayment,
|
||||||
KeyType,
|
KeyType,
|
||||||
|
PayLnurlWData,
|
||||||
Payment,
|
Payment,
|
||||||
PaymentCountField,
|
PaymentCountField,
|
||||||
PaymentCountStat,
|
PaymentCountStat,
|
||||||
@@ -29,7 +34,6 @@ from lnbits.core.models import (
|
|||||||
PaymentFilters,
|
PaymentFilters,
|
||||||
PaymentHistoryPoint,
|
PaymentHistoryPoint,
|
||||||
PaymentWalletStats,
|
PaymentWalletStats,
|
||||||
SettleInvoice,
|
|
||||||
)
|
)
|
||||||
from lnbits.core.models.users import User
|
from lnbits.core.models.users import User
|
||||||
from lnbits.db import Filters, Page
|
from lnbits.db import Filters, Page
|
||||||
@@ -41,10 +45,13 @@ from lnbits.decorators import (
|
|||||||
require_invoice_key,
|
require_invoice_key,
|
||||||
)
|
)
|
||||||
from lnbits.helpers import (
|
from lnbits.helpers import (
|
||||||
|
check_callback_url,
|
||||||
filter_dict_keys,
|
filter_dict_keys,
|
||||||
generate_filter_params_openapi,
|
generate_filter_params_openapi,
|
||||||
)
|
)
|
||||||
from lnbits.wallets.base import InvoiceResponse
|
from lnbits.lnurl import decode as lnurl_decode
|
||||||
|
from lnbits.settings import settings
|
||||||
|
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
|
||||||
|
|
||||||
from ..crud import (
|
from ..crud import (
|
||||||
DateTrunc,
|
DateTrunc,
|
||||||
@@ -53,14 +60,13 @@ from ..crud import (
|
|||||||
get_payments_paginated,
|
get_payments_paginated,
|
||||||
get_standalone_payment,
|
get_standalone_payment,
|
||||||
get_wallet_for_key,
|
get_wallet_for_key,
|
||||||
|
update_payment_extra,
|
||||||
)
|
)
|
||||||
from ..services import (
|
from ..services import (
|
||||||
cancel_hold_invoice,
|
|
||||||
create_payment_request,
|
create_payment_request,
|
||||||
fee_reserve_total,
|
fee_reserve_total,
|
||||||
get_payments_daily_stats,
|
get_payments_daily_stats,
|
||||||
pay_invoice,
|
pay_invoice,
|
||||||
settle_hold_invoice,
|
|
||||||
update_pending_payment,
|
update_pending_payment,
|
||||||
update_pending_payments,
|
update_pending_payments,
|
||||||
)
|
)
|
||||||
@@ -258,6 +264,38 @@ async def api_payments_create(
|
|||||||
return await create_payment_request(wallet_id, invoice_data)
|
return await create_payment_request(wallet_id, invoice_data)
|
||||||
|
|
||||||
|
|
||||||
|
@payment_router.patch(
|
||||||
|
"/extra/{payment_hash}", description="Update extra data for a payment"
|
||||||
|
)
|
||||||
|
async def api_payments_update_extra(
|
||||||
|
payment_hash: str,
|
||||||
|
extra: dict,
|
||||||
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
|
) -> Payment:
|
||||||
|
payment = await get_standalone_payment(payment_hash, wallet_id=wallet.wallet.id)
|
||||||
|
if payment is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="Payment does not exist."
|
||||||
|
)
|
||||||
|
if not isinstance(extra, dict):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail="Extra data must be a dictionary.",
|
||||||
|
)
|
||||||
|
if payment.extra is None:
|
||||||
|
payment.extra = {}
|
||||||
|
|
||||||
|
for key, value in extra.items():
|
||||||
|
if key in payment.extra:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail=f"Key '{key}' already exists in extra data.",
|
||||||
|
)
|
||||||
|
payment.extra[key] = value
|
||||||
|
|
||||||
|
return await update_payment_extra(payment)
|
||||||
|
|
||||||
|
|
||||||
@payment_router.get("/fee-reserve")
|
@payment_router.get("/fee-reserve")
|
||||||
async def api_payments_fee_reserve(invoice: str = Query("invoice")) -> JSONResponse:
|
async def api_payments_fee_reserve(invoice: str = Query("invoice")) -> JSONResponse:
|
||||||
invoice_obj = bolt11.decode(invoice)
|
invoice_obj = bolt11.decode(invoice)
|
||||||
@@ -273,6 +311,92 @@ async def api_payments_fee_reserve(invoice: str = Query("invoice")) -> JSONRespo
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_lnurl_response(
|
||||||
|
params: dict, domain: str, amount_msat: int
|
||||||
|
) -> bolt11.Invoice:
|
||||||
|
if params.get("status") == "ERROR":
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail=f"{domain} said: '{params.get('reason', '')}'",
|
||||||
|
)
|
||||||
|
if not params.get("pr"):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail=f"{domain} did not return a payment request.",
|
||||||
|
)
|
||||||
|
invoice = bolt11.decode(params["pr"])
|
||||||
|
if invoice.amount_msat != amount_msat:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail=(
|
||||||
|
f"{domain} returned an invalid invoice. Expected"
|
||||||
|
f" {amount_msat} msat, got {invoice.amount_msat}."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return invoice
|
||||||
|
|
||||||
|
|
||||||
|
async def _fetch_lnurl_params(data: CreateLnurl, amount_msat: int, domain: str) -> dict:
|
||||||
|
headers = {"User-Agent": settings.user_agent}
|
||||||
|
async with httpx.AsyncClient(headers=headers, follow_redirects=True) as client:
|
||||||
|
try:
|
||||||
|
r: httpx.Response = await client.get(
|
||||||
|
data.callback,
|
||||||
|
params={"amount": amount_msat, "comment": data.comment},
|
||||||
|
timeout=40,
|
||||||
|
)
|
||||||
|
if r.is_error:
|
||||||
|
raise httpx.ConnectError("LNURL callback connection error")
|
||||||
|
r.raise_for_status()
|
||||||
|
except (httpx.HTTPError, ssl.SSLError) as exc:
|
||||||
|
logger.warning(exc)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail=f"Failed to connect to {domain}.",
|
||||||
|
) from exc
|
||||||
|
return json.loads(r.text)
|
||||||
|
|
||||||
|
|
||||||
|
@payment_router.post("/lnurl")
|
||||||
|
async def api_payments_pay_lnurl(
|
||||||
|
data: CreateLnurl, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
|
) -> Payment:
|
||||||
|
domain = urlparse(data.callback).netloc
|
||||||
|
check_callback_url(data.callback)
|
||||||
|
|
||||||
|
if data.unit and data.unit != "sat":
|
||||||
|
amount_msat = await fiat_amount_as_satoshis(data.amount, data.unit)
|
||||||
|
amount_msat = ceil(amount_msat // 1000) * 1000
|
||||||
|
else:
|
||||||
|
amount_msat = data.amount
|
||||||
|
|
||||||
|
params = await _fetch_lnurl_params(data, amount_msat, domain)
|
||||||
|
_validate_lnurl_response(params, domain, amount_msat)
|
||||||
|
|
||||||
|
extra = {}
|
||||||
|
if params.get("successAction"):
|
||||||
|
extra["success_action"] = params["successAction"]
|
||||||
|
if data.comment:
|
||||||
|
extra["comment"] = data.comment
|
||||||
|
if data.unit and data.unit != "sat":
|
||||||
|
extra["fiat_currency"] = data.unit
|
||||||
|
extra["fiat_amount"] = data.amount / 1000
|
||||||
|
if data.internal_memo is not None:
|
||||||
|
if len(data.internal_memo) > 512:
|
||||||
|
raise ValueError("Internal memo must be 512 characters or less.")
|
||||||
|
extra["internal_memo"] = data.internal_memo
|
||||||
|
if data.description is None:
|
||||||
|
raise ValueError("Description is required")
|
||||||
|
|
||||||
|
payment = await pay_invoice(
|
||||||
|
wallet_id=wallet.wallet.id,
|
||||||
|
payment_request=params["pr"],
|
||||||
|
description=data.description,
|
||||||
|
extra=extra,
|
||||||
|
)
|
||||||
|
return payment
|
||||||
|
|
||||||
|
|
||||||
# TODO: refactor this route into a public and admin one
|
# TODO: refactor this route into a public and admin one
|
||||||
@payment_router.get("/{payment_hash}")
|
@payment_router.get("/{payment_hash}")
|
||||||
async def api_payment(payment_hash, x_api_key: Optional[str] = Header(None)):
|
async def api_payment(payment_hash, x_api_key: Optional[str] = Header(None)):
|
||||||
@@ -318,7 +442,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(url_decode(payment_str))
|
url = str(lnurl_decode(payment_str))
|
||||||
return JSONResponse({"domain": url})
|
return JSONResponse({"domain": url})
|
||||||
else:
|
else:
|
||||||
invoice = bolt11.decode(payment_str)
|
invoice = bolt11.decode(payment_str)
|
||||||
@@ -331,32 +455,57 @@ async def api_payments_decode(data: DecodePayment) -> JSONResponse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@payment_router.post("/settle")
|
@payment_router.post("/{payment_request}/pay-with-nfc", status_code=HTTPStatus.OK)
|
||||||
async def api_payments_settle(
|
async def api_payment_pay_with_nfc(
|
||||||
data: SettleInvoice, key_type: WalletTypeInfo = Depends(require_admin_key)
|
payment_request: str,
|
||||||
) -> InvoiceResponse:
|
lnurl_data: PayLnurlWData,
|
||||||
payment_hash = sha256(bytes.fromhex(data.preimage)).hexdigest()
|
) -> JSONResponse:
|
||||||
payment = await get_standalone_payment(
|
lnurl = lnurl_data.lnurl_w.lower()
|
||||||
payment_hash, incoming=True, wallet_id=key_type.wallet.id
|
|
||||||
)
|
|
||||||
if not payment:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND,
|
|
||||||
detail="Payment does not exist or does not belong to this wallet.",
|
|
||||||
)
|
|
||||||
return await settle_hold_invoice(payment, data.preimage)
|
|
||||||
|
|
||||||
|
# Follow LUD-17 -> https://github.com/lnurl/luds/blob/luds/17.md
|
||||||
|
url = lnurl.replace("lnurlw://", "https://")
|
||||||
|
|
||||||
@payment_router.post("/cancel")
|
headers = {"User-Agent": settings.user_agent}
|
||||||
async def api_payments_cancel(
|
async with httpx.AsyncClient(headers=headers, follow_redirects=True) as client:
|
||||||
data: CancelInvoice, key_type: WalletTypeInfo = Depends(require_admin_key)
|
try:
|
||||||
) -> InvoiceResponse:
|
check_callback_url(url)
|
||||||
payment = await get_standalone_payment(
|
lnurl_req = await client.get(url, timeout=10)
|
||||||
data.payment_hash, incoming=True, wallet_id=key_type.wallet.id
|
if lnurl_req.is_error:
|
||||||
)
|
return JSONResponse(
|
||||||
if not payment:
|
{"success": False, "detail": "Error loading LNURL request"}
|
||||||
raise HTTPException(
|
)
|
||||||
status_code=HTTPStatus.NOT_FOUND,
|
|
||||||
detail="Payment does not exist or does not belong to this wallet.",
|
lnurl_res = lnurl_req.json()
|
||||||
)
|
|
||||||
return await cancel_hold_invoice(payment)
|
if lnurl_res.get("status") == "ERROR":
|
||||||
|
return JSONResponse({"success": False, "detail": lnurl_res["reason"]})
|
||||||
|
|
||||||
|
if lnurl_res.get("tag") != "withdrawRequest":
|
||||||
|
return JSONResponse(
|
||||||
|
{"success": False, "detail": "Invalid LNURL-withdraw"}
|
||||||
|
)
|
||||||
|
|
||||||
|
callback_url = lnurl_res["callback"]
|
||||||
|
k1 = lnurl_res["k1"]
|
||||||
|
|
||||||
|
callback_req = await client.get(
|
||||||
|
callback_url,
|
||||||
|
params={"k1": k1, "pr": payment_request},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
if callback_req.is_error:
|
||||||
|
return JSONResponse(
|
||||||
|
{"success": False, "detail": "Error loading callback request"}
|
||||||
|
)
|
||||||
|
|
||||||
|
callback_res = callback_req.json()
|
||||||
|
|
||||||
|
if callback_res.get("status") == "ERROR":
|
||||||
|
return JSONResponse(
|
||||||
|
{"success": False, "detail": callback_res["reason"]}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return JSONResponse({"success": True, "detail": callback_res})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse({"success": False, "detail": f"Unexpected error: {e}"})
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from lnbits.core.models.notifications import NotificationType
|
|||||||
from lnbits.core.models.users import Account
|
from lnbits.core.models.users import Account
|
||||||
from lnbits.core.services import (
|
from lnbits.core.services import (
|
||||||
create_user_account_no_ckeck,
|
create_user_account_no_ckeck,
|
||||||
enqueue_admin_notification,
|
enqueue_notification,
|
||||||
update_user_account,
|
update_user_account,
|
||||||
update_user_extensions,
|
update_user_extensions,
|
||||||
update_wallet_balance,
|
update_wallet_balance,
|
||||||
@@ -321,7 +321,7 @@ async def api_update_balance(data: UpdateBalance) -> SimpleStatus:
|
|||||||
if not wallet:
|
if not wallet:
|
||||||
raise HTTPException(HTTPStatus.NOT_FOUND, "Wallet not found.")
|
raise HTTPException(HTTPStatus.NOT_FOUND, "Wallet not found.")
|
||||||
await update_wallet_balance(wallet=wallet, amount=int(data.amount))
|
await update_wallet_balance(wallet=wallet, amount=int(data.amount))
|
||||||
enqueue_admin_notification(
|
enqueue_notification(
|
||||||
NotificationType.balance_update,
|
NotificationType.balance_update,
|
||||||
{
|
{
|
||||||
"amount": int(data.amount),
|
"amount": int(data.amount),
|
||||||
|
|||||||
+4
-11
@@ -3,8 +3,6 @@ from __future__ import annotations
|
|||||||
import importlib
|
import importlib
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from lnbits.fiat.base import FiatProvider
|
from lnbits.fiat.base import FiatProvider
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
@@ -17,7 +15,7 @@ class FiatProviderType(Enum):
|
|||||||
stripe = "StripeWallet"
|
stripe = "StripeWallet"
|
||||||
|
|
||||||
|
|
||||||
async def get_fiat_provider(name: str) -> FiatProvider | None:
|
async def get_fiat_provider(name: str) -> FiatProvider:
|
||||||
if name not in FiatProviderType.__members__:
|
if name not in FiatProviderType.__members__:
|
||||||
raise ValueError(f"Fiat provider '{name}' is not supported.")
|
raise ValueError(f"Fiat provider '{name}' is not supported.")
|
||||||
|
|
||||||
@@ -29,18 +27,13 @@ async def get_fiat_provider(name: str) -> FiatProvider | None:
|
|||||||
del fiat_providers[name]
|
del fiat_providers[name]
|
||||||
else:
|
else:
|
||||||
return fiat_provider
|
return fiat_provider
|
||||||
_provider = _init_fiat_provider(FiatProviderType[name])
|
fiat_providers[name] = _init_fiat_provider(FiatProviderType[name])
|
||||||
if not _provider:
|
|
||||||
return None
|
|
||||||
|
|
||||||
fiat_providers[name] = _provider
|
|
||||||
return fiat_providers[name]
|
return fiat_providers[name]
|
||||||
|
|
||||||
|
|
||||||
def _init_fiat_provider(fiat_provider: FiatProviderType) -> FiatProvider | None:
|
def _init_fiat_provider(fiat_provider: FiatProviderType) -> FiatProvider:
|
||||||
if not settings.is_fiat_provider_enabled(fiat_provider.name):
|
if not settings.is_fiat_provider_enabled(fiat_provider.name):
|
||||||
logger.warning(f"Fiat provider '{fiat_provider.name}' not enabled.")
|
raise ValueError(f"Fiat provider '{fiat_provider.name}' not enabled.")
|
||||||
return None
|
|
||||||
provider_constructor = getattr(fiat_module, fiat_provider.value)
|
provider_constructor = getattr(fiat_module, fiat_provider.value)
|
||||||
return provider_constructor()
|
return provider_constructor()
|
||||||
|
|
||||||
|
|||||||
+8
-4
@@ -16,6 +16,7 @@ from packaging import version
|
|||||||
from pydantic.schema import field_schema
|
from pydantic.schema import field_schema
|
||||||
|
|
||||||
from lnbits.jinja2_templating import Jinja2Templates
|
from lnbits.jinja2_templating import Jinja2Templates
|
||||||
|
from lnbits.nodes import get_node_class
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.utils.crypto import AESCipher
|
from lnbits.utils.crypto import AESCipher
|
||||||
|
|
||||||
@@ -82,8 +83,8 @@ def template_renderer(additional_folders: Optional[list] = None) -> Jinja2Templa
|
|||||||
"LNBITS_CUSTOM_BADGE_COLOR": settings.lnbits_custom_badge_color,
|
"LNBITS_CUSTOM_BADGE_COLOR": settings.lnbits_custom_badge_color,
|
||||||
"LNBITS_EXTENSIONS_DEACTIVATE_ALL": settings.lnbits_extensions_deactivate_all,
|
"LNBITS_EXTENSIONS_DEACTIVATE_ALL": settings.lnbits_extensions_deactivate_all,
|
||||||
"LNBITS_NEW_ACCOUNTS_ALLOWED": settings.new_accounts_allowed,
|
"LNBITS_NEW_ACCOUNTS_ALLOWED": settings.new_accounts_allowed,
|
||||||
"LNBITS_NODE_UI": settings.lnbits_node_ui and settings.has_nodemanager,
|
"LNBITS_NODE_UI": settings.lnbits_node_ui and get_node_class() is not None,
|
||||||
"LNBITS_NODE_UI_AVAILABLE": settings.has_nodemanager,
|
"LNBITS_NODE_UI_AVAILABLE": get_node_class() is not None,
|
||||||
"LNBITS_QR_LOGO": settings.lnbits_qr_logo,
|
"LNBITS_QR_LOGO": settings.lnbits_qr_logo,
|
||||||
"LNBITS_SERVICE_FEE": settings.lnbits_service_fee,
|
"LNBITS_SERVICE_FEE": settings.lnbits_service_fee,
|
||||||
"LNBITS_SERVICE_FEE_MAX": settings.lnbits_service_fee_max,
|
"LNBITS_SERVICE_FEE_MAX": settings.lnbits_service_fee_max,
|
||||||
@@ -99,8 +100,11 @@ 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": settings.lnbits_denomination,
|
"LNBITS_DENOMINATION": (
|
||||||
"has_holdinvoice": settings.has_holdinvoice,
|
settings.lnbits_denomination
|
||||||
|
if settings.lnbits_denomination == "FakeWallet"
|
||||||
|
else "sats"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
t.env.globals["WINDOW_SETTINGS"] = window_settings
|
t.env.globals["WINDOW_SETTINGS"] = window_settings
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from fastapi import HTTPException, Request, Response
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
from fastapi.routing import APIRoute
|
||||||
|
from lnurl import LnurlErrorResponse, decode, encode, handle
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from lnbits.exceptions import InvoiceError, PaymentError
|
||||||
|
|
||||||
|
|
||||||
|
class LnurlErrorResponseHandler(APIRoute):
|
||||||
|
"""
|
||||||
|
Custom APIRoute class to handle LNURL errors.
|
||||||
|
LNURL errors always return with status 200 and
|
||||||
|
a JSON response with `status="ERROR"` and a `reason` key.
|
||||||
|
Helps to catch HTTPException and return a valid lnurl error response
|
||||||
|
|
||||||
|
Example:
|
||||||
|
withdraw_lnurl_router = APIRouter(prefix="/api/v1/lnurl")
|
||||||
|
withdraw_lnurl_router.route_class = LnurlErrorResponseHandler
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_route_handler(self) -> Callable:
|
||||||
|
original_route_handler = super().get_route_handler()
|
||||||
|
|
||||||
|
async def lnurl_route_handler(request: Request) -> Response:
|
||||||
|
try:
|
||||||
|
response = await original_route_handler(request)
|
||||||
|
return response
|
||||||
|
except (InvoiceError, PaymentError) as exc:
|
||||||
|
logger.debug(f"Wallet Error: {exc}")
|
||||||
|
response = JSONResponse(
|
||||||
|
status_code=HTTPStatus.OK,
|
||||||
|
content={"status": "ERROR", "reason": f"{exc.message}"},
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
except HTTPException as exc:
|
||||||
|
logger.debug(f"HTTPException: {exc}")
|
||||||
|
response = JSONResponse(
|
||||||
|
status_code=HTTPStatus.OK,
|
||||||
|
content={"status": "ERROR", "reason": f"{exc.detail}"},
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error("Unknown Error:", exc)
|
||||||
|
response = JSONResponse(
|
||||||
|
status_code=HTTPStatus.OK,
|
||||||
|
content={
|
||||||
|
"status": "ERROR",
|
||||||
|
"reason": f"UNKNOWN ERROR: {exc!s}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
return lnurl_route_handler
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"LnurlErrorResponse",
|
||||||
|
"LnurlErrorResponseHandler",
|
||||||
|
"decode",
|
||||||
|
"encode",
|
||||||
|
"handle",
|
||||||
|
]
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .base import Node
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_class() -> Optional[Node]:
|
||||||
|
return NODE
|
||||||
|
|
||||||
|
|
||||||
|
def set_node_class(node: Node):
|
||||||
|
global NODE
|
||||||
|
NODE = node
|
||||||
|
|
||||||
|
|
||||||
|
NODE: Optional[Node] = None
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ from httpx import HTTPStatusError
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.db import Filters, Page
|
from lnbits.db import Filters, Page
|
||||||
|
from lnbits.nodes import Node
|
||||||
from lnbits.nodes.base import (
|
from lnbits.nodes.base import (
|
||||||
ChannelBalance,
|
ChannelBalance,
|
||||||
ChannelPoint,
|
ChannelPoint,
|
||||||
ChannelState,
|
ChannelState,
|
||||||
ChannelStats,
|
ChannelStats,
|
||||||
Node,
|
|
||||||
NodeChannel,
|
NodeChannel,
|
||||||
NodeFees,
|
NodeFees,
|
||||||
NodeInfoResponse,
|
NodeInfoResponse,
|
||||||
|
|||||||
@@ -432,18 +432,6 @@ class NotificationsSettings(LNbitsSettings):
|
|||||||
default=1_000_000, ge=0
|
default=1_000_000, ge=0
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_nostr_notifications_configured(self) -> bool:
|
|
||||||
return (
|
|
||||||
self.lnbits_nostr_notifications_enabled
|
|
||||||
and self.lnbits_nostr_notifications_private_key is not None
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_telegram_notifications_configured(self) -> bool:
|
|
||||||
return (
|
|
||||||
self.lnbits_telegram_notifications_enabled
|
|
||||||
and self.lnbits_telegram_notifications_access_token is not None
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeWalletFundingSource(LNbitsSettings):
|
class FakeWalletFundingSource(LNbitsSettings):
|
||||||
fake_wallet_secret: str = Field(default="ToTheMoon1")
|
fake_wallet_secret: str = Field(default="ToTheMoon1")
|
||||||
@@ -999,9 +987,6 @@ class TransientSettings(InstalledExtensionsSettings, ExchangeHistorySettings):
|
|||||||
|
|
||||||
server_startup_time: int = Field(default=time())
|
server_startup_time: int = Field(default=time())
|
||||||
|
|
||||||
has_holdinvoice: bool = Field(default=False)
|
|
||||||
has_nodemanager: bool = Field(default=False)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lnbits_server_up_time(self) -> str:
|
def lnbits_server_up_time(self) -> str:
|
||||||
up_time = int(time() - self.server_startup_time)
|
up_time = int(time() - self.server_startup_time)
|
||||||
|
|||||||
+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
Vendored
+3
-3
File diff suppressed because one or more lines are too long
@@ -62,14 +62,6 @@ body[data-theme=monochrome].neon-border .q-date--dark {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-theme=salvador].neon-border .q-card,
|
|
||||||
body[data-theme=salvador].neon-border .q-card.q-card--dark,
|
|
||||||
body[data-theme=salvador].neon-border .q-date,
|
|
||||||
body[data-theme=salvador].neon-border .q-date--dark {
|
|
||||||
border: 2px solid #1976d2;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.hard-border .q-card,
|
body.hard-border .q-card,
|
||||||
body.hard-border .q-card.q-card--dark,
|
body.hard-border .q-card.q-card--dark,
|
||||||
body.hard-border .q-date,
|
body.hard-border .q-date,
|
||||||
@@ -117,21 +109,21 @@ body[data-theme=bitcoin] [data-theme=bitcoin] .q-stepper--dark {
|
|||||||
body[data-theme=freedom] {
|
body[data-theme=freedom] {
|
||||||
--q-primary: #e22156;
|
--q-primary: #e22156;
|
||||||
--q-secondary: #b91a45;
|
--q-secondary: #b91a45;
|
||||||
--q-dark-page: #462f36;
|
--q-dark-page: #0a0a0a;
|
||||||
}
|
}
|
||||||
body[data-theme=freedom] [data-theme=freedom] .q-card--dark,
|
body[data-theme=freedom] [data-theme=freedom] .q-card--dark,
|
||||||
body[data-theme=freedom] [data-theme=freedom] .q-stepper--dark {
|
body[data-theme=freedom] [data-theme=freedom] .q-stepper--dark {
|
||||||
background: #47393d !important;
|
background: #1b1b1b !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-theme=cyber] {
|
body[data-theme=cyber] {
|
||||||
--q-primary: #7cb342;
|
--q-primary: #7cb342;
|
||||||
--q-secondary: #558b2f;
|
--q-secondary: #558b2f;
|
||||||
--q-dark-page: #000;
|
--q-dark-page: #0a0a0a;
|
||||||
}
|
}
|
||||||
body[data-theme=cyber] [data-theme=cyber] .q-card--dark,
|
body[data-theme=cyber] [data-theme=cyber] .q-card--dark,
|
||||||
body[data-theme=cyber] [data-theme=cyber] .q-stepper--dark {
|
body[data-theme=cyber] [data-theme=cyber] .q-stepper--dark {
|
||||||
background: #1f2915 !important;
|
background: #1b1b1b !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-theme=mint] {
|
body[data-theme=mint] {
|
||||||
@@ -174,16 +166,6 @@ body[data-theme=monochrome] [data-theme=monochrome] .q-stepper--dark {
|
|||||||
background: rgb(39, 39, 39) !important;
|
background: rgb(39, 39, 39) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[data-theme=salvador] {
|
|
||||||
--q-primary: #1976d2;
|
|
||||||
--q-secondary: #26a69a;
|
|
||||||
--q-dark-page: #253647;
|
|
||||||
}
|
|
||||||
body[data-theme=salvador] [data-theme=salvador] .q-card--dark,
|
|
||||||
body[data-theme=salvador] [data-theme=salvador] .q-stepper--dark {
|
|
||||||
background: #343d47 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.gradient-bg {
|
body.gradient-bg {
|
||||||
background-image: linear-gradient(to bottom right, var(--q-dark-page), #0a0a0a);
|
background-image: linear-gradient(to bottom right, var(--q-dark-page), #0a0a0a);
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ window.localisation.en = {
|
|||||||
wallet: 'Wallet: ',
|
wallet: 'Wallet: ',
|
||||||
wallet_name: 'Wallet name',
|
wallet_name: 'Wallet name',
|
||||||
wallets: 'Wallets',
|
wallets: 'Wallets',
|
||||||
exclude_wallets: 'Exclude Wallets',
|
|
||||||
add_wallet: 'Add wallet',
|
add_wallet: 'Add wallet',
|
||||||
add_new_wallet: 'Add a new wallet',
|
add_new_wallet: 'Add a new wallet',
|
||||||
pin_wallet: 'Pin wallet',
|
pin_wallet: 'Pin wallet',
|
||||||
@@ -177,17 +176,7 @@ window.localisation.en = {
|
|||||||
extension_required_lnbits_version: 'This release requires LNbits version',
|
extension_required_lnbits_version: 'This release requires LNbits version',
|
||||||
min_version: 'Minimum (included)',
|
min_version: 'Minimum (included)',
|
||||||
max_version: 'Maximum (excluded)',
|
max_version: 'Maximum (excluded)',
|
||||||
preimage: 'Preimage',
|
|
||||||
preimage_hint: 'Preimage to settle the hold invoice',
|
|
||||||
hold_invoice: 'Hold Invoice',
|
|
||||||
hold_invoice_description:
|
|
||||||
'This invoice is on hold and requires a preimage to settle.',
|
|
||||||
payment_hash: 'Payment Hash',
|
payment_hash: 'Payment Hash',
|
||||||
invoice_cancelled: 'Invoice Cancelled',
|
|
||||||
invoice_settled: 'Invoice Settled',
|
|
||||||
hold_invoice_payment_hash: 'Payment hash for hold invoice (optional)',
|
|
||||||
settle_invoice: 'Settle Invoice',
|
|
||||||
cancel_invoice: 'Cancel Invoice',
|
|
||||||
fee: 'Fee',
|
fee: 'Fee',
|
||||||
amount: 'Amount',
|
amount: 'Amount',
|
||||||
amount_limits: 'Amount Limits',
|
amount_limits: 'Amount Limits',
|
||||||
@@ -230,9 +219,6 @@ window.localisation.en = {
|
|||||||
notifications_nostr_private_key: 'Nostr Private Key',
|
notifications_nostr_private_key: 'Nostr Private Key',
|
||||||
notifications_nostr_private_key_desc:
|
notifications_nostr_private_key_desc:
|
||||||
'Private key (hex or nsec) to sign the messages sent to Nostr',
|
'Private key (hex or nsec) to sign the messages sent to Nostr',
|
||||||
notifications_nostr_identifier: 'Nostr Identifier',
|
|
||||||
notifications_nostr_identifier_desc:
|
|
||||||
'Nip5 identifier to send notifications to',
|
|
||||||
notifications_nostr_identifiers: 'Nostr Identifiers',
|
notifications_nostr_identifiers: 'Nostr Identifiers',
|
||||||
notifications_nostr_identifiers_desc:
|
notifications_nostr_identifiers_desc:
|
||||||
'List of identifiers to send notifications to',
|
'List of identifiers to send notifications to',
|
||||||
@@ -242,10 +228,9 @@ window.localisation.en = {
|
|||||||
notifications_enable_telegram_desc: 'Send notfications over Telegram',
|
notifications_enable_telegram_desc: 'Send notfications over Telegram',
|
||||||
notifications_telegram_access_token: 'Access Token',
|
notifications_telegram_access_token: 'Access Token',
|
||||||
notifications_telegram_access_token_desc: 'Access token for the bot',
|
notifications_telegram_access_token_desc: 'Access token for the bot',
|
||||||
notifications_chat_id: 'Telegram Chat ID',
|
notifications_chat_id: 'Chat ID',
|
||||||
notifications_chat_id_desc: 'Telegram Chat ID to send the notifications to',
|
notifications_chat_id_desc: 'Chat ID to send the notifications to',
|
||||||
notifications_excluded_wallets_desc:
|
|
||||||
'Do not send notifications for these wallets',
|
|
||||||
notifications_email_config: 'Email Configuration',
|
notifications_email_config: 'Email Configuration',
|
||||||
notifications_enable_email: 'Enable Email',
|
notifications_enable_email: 'Enable Email',
|
||||||
notifications_enable_email_desc: 'Send notifications over email',
|
notifications_enable_email_desc: 'Send notifications over email',
|
||||||
@@ -649,7 +634,5 @@ window.localisation.en = {
|
|||||||
'Signing secret for the webhook. Messages will be signed with this secret.',
|
'Signing secret for the webhook. Messages will be signed with this secret.',
|
||||||
callback_success_url: 'Callback Success URL',
|
callback_success_url: 'Callback Success URL',
|
||||||
callback_success_url_hint:
|
callback_success_url_hint:
|
||||||
'The user will be redirected to this URL after the payment is successful',
|
'The user will be redirected to this URL after the payment is successful'
|
||||||
connected: 'Connected',
|
|
||||||
not_connected: 'Not Connected'
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ window.localisation.fi = {
|
|||||||
active_channels: 'Aktiivisia kanavia',
|
active_channels: 'Aktiivisia kanavia',
|
||||||
connect_peer: 'Yhdistä naapuriin',
|
connect_peer: 'Yhdistä naapuriin',
|
||||||
connect: 'Yhdistä',
|
connect: 'Yhdistä',
|
||||||
reconnect: 'Uudista yhteys',
|
|
||||||
open_channel: 'Avaa kanava',
|
open_channel: 'Avaa kanava',
|
||||||
open: 'Avaa',
|
open: 'Avaa',
|
||||||
close_channel: 'Sulje kanava',
|
close_channel: 'Sulje kanava',
|
||||||
@@ -52,18 +51,14 @@ window.localisation.fi = {
|
|||||||
'Tämä QR-koodi sisältää URL-osoitteen, jolla saa lompakkoosi täydet valtuudet. Voit lukea sen puhelimellasi ja avata sillä lompakkosi. Voit myös lisätä lompakkosi selaimella käytettäväksi PWA-sovellukseksi puhelimen aloitusruudulle. ',
|
'Tämä QR-koodi sisältää URL-osoitteen, jolla saa lompakkoosi täydet valtuudet. Voit lukea sen puhelimellasi ja avata sillä lompakkosi. Voit myös lisätä lompakkosi selaimella käytettäväksi PWA-sovellukseksi puhelimen aloitusruudulle. ',
|
||||||
access_wallet_on_mobile: 'Mobiili käyttö',
|
access_wallet_on_mobile: 'Mobiili käyttö',
|
||||||
wallet: 'Lompakko:',
|
wallet: 'Lompakko:',
|
||||||
wallet_name: 'Lompakon nimi',
|
|
||||||
wallets: 'Lompakot',
|
wallets: 'Lompakot',
|
||||||
add_wallet: 'Lisää lompakko',
|
add_wallet: 'Lisää lompakko',
|
||||||
add_new_wallet: 'Lisää uusi lompakko',
|
|
||||||
pin_wallet: 'Kiinnitä lompakko',
|
|
||||||
delete_wallet: 'Poista lompakko',
|
delete_wallet: 'Poista lompakko',
|
||||||
delete_wallet_desc:
|
delete_wallet_desc:
|
||||||
'Lompakko poistetaan pysyvästi. Siirrä lompakosta varat ennalta muualle, sillä tämä toiminto on PERUUTTAMATON!',
|
'Lompakko poistetaan pysyvästi. Siirrä lompakosta varat ennalta muualle, sillä tämä toiminto on PERUUTTAMATON!',
|
||||||
rename_wallet: 'Nimeä lompakko uudelleen',
|
rename_wallet: 'Nimeä lompakko uudelleen',
|
||||||
update_name: 'Tallenna',
|
update_name: 'Tallenna',
|
||||||
fiat_tracking: 'Käytettävä valuutta',
|
fiat_tracking: 'Käytettävä valuutta',
|
||||||
fiat_providers: 'Valuutan välittäjät',
|
|
||||||
currency: 'Valuutta',
|
currency: 'Valuutta',
|
||||||
update_currency: 'Tallenna',
|
update_currency: 'Tallenna',
|
||||||
press_to_claim: 'Lunasta varat painamalla tästä',
|
press_to_claim: 'Lunasta varat painamalla tästä',
|
||||||
@@ -127,7 +122,6 @@ window.localisation.fi = {
|
|||||||
no_extensions: 'Laajennuksia ei ole asennettu :(',
|
no_extensions: 'Laajennuksia ei ole asennettu :(',
|
||||||
created: 'Luotu',
|
created: 'Luotu',
|
||||||
search_extensions: 'Etsi laajennuksia',
|
search_extensions: 'Etsi laajennuksia',
|
||||||
search_wallets: 'Etsi lompakkoa',
|
|
||||||
extension_sources: 'Laajennuslähteet',
|
extension_sources: 'Laajennuslähteet',
|
||||||
ext_sources_hint: 'Lähteet joista laajennuksia voi ladata',
|
ext_sources_hint: 'Lähteet joista laajennuksia voi ladata',
|
||||||
ext_sources_label:
|
ext_sources_label:
|
||||||
@@ -140,7 +134,6 @@ window.localisation.fi = {
|
|||||||
uninstall: 'Poista',
|
uninstall: 'Poista',
|
||||||
drop_db: 'Poista tiedot',
|
drop_db: 'Poista tiedot',
|
||||||
enable: 'Ota käyttöön',
|
enable: 'Ota käyttöön',
|
||||||
enabled: 'Käytössä',
|
|
||||||
pay_to_enable: 'Maksa ottaaksesi käyttöön',
|
pay_to_enable: 'Maksa ottaaksesi käyttöön',
|
||||||
enable_extension_details: 'Ota laajennus käyttöön tälle käyttäjälle',
|
enable_extension_details: 'Ota laajennus käyttöön tälle käyttäjälle',
|
||||||
disable: 'Poista käytöstä',
|
disable: 'Poista käytöstä',
|
||||||
@@ -172,33 +165,12 @@ window.localisation.fi = {
|
|||||||
payment_hash: 'Maksun tiiviste',
|
payment_hash: 'Maksun tiiviste',
|
||||||
fee: 'Kulu',
|
fee: 'Kulu',
|
||||||
amount: 'Määrä',
|
amount: 'Määrä',
|
||||||
amount_limits: 'Määrien rajat',
|
|
||||||
amount_sats: 'Määrä (sat)',
|
amount_sats: 'Määrä (sat)',
|
||||||
faucest_wallet: 'Faucet Wallet',
|
|
||||||
faucest_wallet_desc_1:
|
|
||||||
'Each time a payment is confirmed by the {provider} provider funds will be subtracted from this wallet.',
|
|
||||||
faucest_wallet_desc_2:
|
|
||||||
'This helps monitor all {provider} payments and their status.',
|
|
||||||
faucest_wallet_desc_3:
|
|
||||||
'This wallet must be topped up with the amount of sats that the admin is willing to offer in exchange for the fiat currency.',
|
|
||||||
faucest_wallet_desc_4:
|
|
||||||
'If this wallet is configured, but is empty, the {provider} payments will not be processed.',
|
|
||||||
faucest_wallet_desc_5:
|
|
||||||
'This wallet can eventually get to a negative balance if parallel fiat payments are made.',
|
|
||||||
faucest_wallet_id: 'Faucet Wallet ID (optional)',
|
|
||||||
faucest_wallet_id_hint:
|
|
||||||
'Wallet ID to use for the faucet. It will be used to send the funds to the user.',
|
|
||||||
tag: 'Tunniste',
|
tag: 'Tunniste',
|
||||||
unit: 'Yksikkö',
|
unit: 'Yksikkö',
|
||||||
description: 'Kuvaus',
|
description: 'Kuvaus',
|
||||||
expiry: 'Vanhenee',
|
expiry: 'Vanhenee',
|
||||||
webhook: 'Webhook',
|
webhook: 'Webhook',
|
||||||
webhook_url: 'Webhook URL',
|
|
||||||
webhook_url_hint:
|
|
||||||
'Webhook URL to send the payment details to. It will be called when the payment is completed.',
|
|
||||||
webhook_events_list: 'The following events must be supported by the webhook:',
|
|
||||||
webhook_stripe_description:
|
|
||||||
'One the stripe side you must configure a webhook with a URL that points to your LNbits server.',
|
|
||||||
payment_proof: 'Maksun varmenne',
|
payment_proof: 'Maksun varmenne',
|
||||||
update: 'Päivitä',
|
update: 'Päivitä',
|
||||||
update_available: 'Saatavilla on päivitys {version}-versioon!',
|
update_available: 'Saatavilla on päivitys {version}-versioon!',
|
||||||
@@ -290,7 +262,6 @@ window.localisation.fi = {
|
|||||||
notification_source_label:
|
notification_source_label:
|
||||||
'Lähde-URL (käytä ainoastaan LNbits:iä tai muuta luotettavaa lähdettä)',
|
'Lähde-URL (käytä ainoastaan LNbits:iä tai muuta luotettavaa lähdettä)',
|
||||||
more: 'näytä lisää',
|
more: 'näytä lisää',
|
||||||
more_count: 'näytä {count} lisää',
|
|
||||||
less: 'supista',
|
less: 'supista',
|
||||||
releases: 'Julkaisut',
|
releases: 'Julkaisut',
|
||||||
watchdog: 'Watchdog',
|
watchdog: 'Watchdog',
|
||||||
@@ -305,7 +276,7 @@ window.localisation.fi = {
|
|||||||
callback_url_rules: 'Callback URL -säännöt',
|
callback_url_rules: 'Callback URL -säännöt',
|
||||||
enter_callback_url_rule: 'Anna URL-sääntö regex-muodossa ja paina enter',
|
enter_callback_url_rule: 'Anna URL-sääntö regex-muodossa ja paina enter',
|
||||||
callback_url_rule_hint:
|
callback_url_rule_hint:
|
||||||
'Callback URL:it (kuten LNURL) tarkistetaan kaikkien näiden sääntöjen mukaisesti. Jos sääntöjä ei ole määritetty, kaikki URL:it ovat sallittuja.',
|
'Callback URL:it (kuten LNURL) tarkistetaan näiden sääntöjen mukaisesti. Jos sääntöjä ei ole määritetty, kaikki URL:it ovat sallittuja.',
|
||||||
wallet_limiter: 'Lompakon käyttörajoitin',
|
wallet_limiter: 'Lompakon käyttörajoitin',
|
||||||
wallet_config: 'Wallet Config',
|
wallet_config: 'Wallet Config',
|
||||||
wallet_charts: 'Wallet Charts',
|
wallet_charts: 'Wallet Charts',
|
||||||
@@ -330,9 +301,6 @@ window.localisation.fi = {
|
|||||||
login_with_user_id: 'Kirjaudu käyttäjä-ID:llä',
|
login_with_user_id: 'Kirjaudu käyttäjä-ID:llä',
|
||||||
or: 'tai',
|
or: 'tai',
|
||||||
create_new_wallet: 'Avaa uusi lompakko',
|
create_new_wallet: 'Avaa uusi lompakko',
|
||||||
delete_all_wallets: 'Poista kaikki lompakot',
|
|
||||||
confirm_delete_all_wallets:
|
|
||||||
'Oletko todellakin varma, että haluat poistaa käyttäjältä KAIKKI lompakot?',
|
|
||||||
login_to_account: 'Kirjaudu käyttäjänimellä',
|
login_to_account: 'Kirjaudu käyttäjänimellä',
|
||||||
create_account: 'Luo tili',
|
create_account: 'Luo tili',
|
||||||
account_settings: 'Tilin asetukset',
|
account_settings: 'Tilin asetukset',
|
||||||
@@ -341,7 +309,7 @@ window.localisation.fi = {
|
|||||||
signin_with_nostr: 'Kirjaudu Nostr:lla',
|
signin_with_nostr: 'Kirjaudu Nostr:lla',
|
||||||
signin_with_google: 'Kirjaudu Google-tunnuksella',
|
signin_with_google: 'Kirjaudu Google-tunnuksella',
|
||||||
signin_with_github: 'Kirjaudu GitHub-tunnuksella',
|
signin_with_github: 'Kirjaudu GitHub-tunnuksella',
|
||||||
signin_with_custom_org: 'Kirjaudu {custom_org}-palvelulla',
|
signin_with_keycloak: 'Kirjaudu Keycloak-tunnuksella',
|
||||||
username_or_email: 'Käyttäjänimi tai sähköposti',
|
username_or_email: 'Käyttäjänimi tai sähköposti',
|
||||||
password: 'Anna uusi salasana',
|
password: 'Anna uusi salasana',
|
||||||
password_config: 'Salasanan määritys',
|
password_config: 'Salasanan määritys',
|
||||||
@@ -350,10 +318,7 @@ window.localisation.fi = {
|
|||||||
change_password: 'Vaihda salasana',
|
change_password: 'Vaihda salasana',
|
||||||
update_credentials: 'Päivitä käyttöoikeustiedot',
|
update_credentials: 'Päivitä käyttöoikeustiedot',
|
||||||
update_pubkey: 'Päivitä julkinen avain',
|
update_pubkey: 'Päivitä julkinen avain',
|
||||||
nostr_pubkey_tooltip:
|
|
||||||
'Syötä tämän käyttäjän julkinen Nostr avain (hex arvona)',
|
|
||||||
set_password: 'Aseta salasana',
|
set_password: 'Aseta salasana',
|
||||||
set_password_tooltip: 'Aseta käyttäjätunnukselle salasana',
|
|
||||||
invalid_password: 'Salasanassa tulee olla vähintään kahdeksan merkkiä',
|
invalid_password: 'Salasanassa tulee olla vähintään kahdeksan merkkiä',
|
||||||
invalid_password_repeat: 'Salasanat eivät täsmää',
|
invalid_password_repeat: 'Salasanat eivät täsmää',
|
||||||
reset_key_generated: 'Salasanan vaihtoavain on luotu.',
|
reset_key_generated: 'Salasanan vaihtoavain on luotu.',
|
||||||
@@ -362,8 +327,7 @@ window.localisation.fi = {
|
|||||||
register: 'Rekisteröidy',
|
register: 'Rekisteröidy',
|
||||||
username: 'Käyttäjänimi',
|
username: 'Käyttäjänimi',
|
||||||
pubkey: 'Julkinen avain',
|
pubkey: 'Julkinen avain',
|
||||||
user_id: 'Käyttäjä tunnus',
|
user_id: 'Käyttäjä ID',
|
||||||
id: 'tunnus',
|
|
||||||
email: 'Sähköposti',
|
email: 'Sähköposti',
|
||||||
first_name: 'Etunimi',
|
first_name: 'Etunimi',
|
||||||
last_name: 'Sukunimi',
|
last_name: 'Sukunimi',
|
||||||
@@ -379,8 +343,6 @@ window.localisation.fi = {
|
|||||||
back: 'Takaisin',
|
back: 'Takaisin',
|
||||||
logout: 'Poistu',
|
logout: 'Poistu',
|
||||||
look_and_feel: 'Kieli ja värit',
|
look_and_feel: 'Kieli ja värit',
|
||||||
endpoint: 'Endpoint',
|
|
||||||
api: 'API',
|
|
||||||
api_token: 'API Token',
|
api_token: 'API Token',
|
||||||
api_tokens: 'API Tokens',
|
api_tokens: 'API Tokens',
|
||||||
access_control_list: 'Access Control List',
|
access_control_list: 'Access Control List',
|
||||||
@@ -392,14 +354,11 @@ window.localisation.fi = {
|
|||||||
gradient_background: 'Gradient Background',
|
gradient_background: 'Gradient Background',
|
||||||
language: 'Kieli',
|
language: 'Kieli',
|
||||||
color_scheme: 'Väriteema',
|
color_scheme: 'Väriteema',
|
||||||
visible_wallet_count: 'Näytettävien lompakkojen määrä',
|
|
||||||
admin_settings: 'Pääkäyttäjän asetukset',
|
admin_settings: 'Pääkäyttäjän asetukset',
|
||||||
extension_cost: 'Tämä laajennus edellyttää vähintään {cost} sat maksua.',
|
extension_cost: 'Tämä laajennus edellyttää vähintään {cost} sat maksua.',
|
||||||
extension_paid_sats: 'Olet jo maksanut {paid_sats} satsia.',
|
extension_paid_sats: 'Olet jo maksanut {paid_sats} satsia.',
|
||||||
release_details_error: 'Ei voi hakea julkaisun tietoja.',
|
release_details_error: 'Ei voi hakea julkaisun tietoja.',
|
||||||
pay_from_wallet: 'Maksa lompakosta',
|
pay_from_wallet: 'Maksa lompakosta',
|
||||||
pay_with: 'Maksa {provider}:lla',
|
|
||||||
select_payment_provider: 'Valitse maksun välittäjä',
|
|
||||||
wallet_required: 'Lompakko *',
|
wallet_required: 'Lompakko *',
|
||||||
show_qr: 'Näytä QR',
|
show_qr: 'Näytä QR',
|
||||||
retry_install: 'Yritä asennusta uudelleen',
|
retry_install: 'Yritä asennusta uudelleen',
|
||||||
@@ -412,9 +371,6 @@ window.localisation.fi = {
|
|||||||
'{name} -laajennuksen aktivointi edellyttää vähintään {amount} sat maksua.',
|
'{name} -laajennuksen aktivointi edellyttää vähintään {amount} sat maksua.',
|
||||||
hide_empty_wallets: 'Piilota tyhjät lompakot',
|
hide_empty_wallets: 'Piilota tyhjät lompakot',
|
||||||
recheck: 'Tarkista uudelleen',
|
recheck: 'Tarkista uudelleen',
|
||||||
check: 'Tarkista',
|
|
||||||
check_connection: 'Tarkista yhteys',
|
|
||||||
check_webhook: 'Tarkista Webhook',
|
|
||||||
contributors: 'Avustajat',
|
contributors: 'Avustajat',
|
||||||
license: 'Lisenssi',
|
license: 'Lisenssi',
|
||||||
reset_key: 'Vaihda avain',
|
reset_key: 'Vaihda avain',
|
||||||
@@ -491,9 +447,9 @@ window.localisation.fi = {
|
|||||||
fee_reserve_percent: 'Kuluvaraus prosentteina',
|
fee_reserve_percent: 'Kuluvaraus prosentteina',
|
||||||
fee_reserve_msats: 'Kuluvaraus milli-sat',
|
fee_reserve_msats: 'Kuluvaraus milli-sat',
|
||||||
reserve_fee_in_percent: 'Kuluvaraus prosentteina',
|
reserve_fee_in_percent: 'Kuluvaraus prosentteina',
|
||||||
payment_wait_time: 'Maksun odotusaika (sekuntia)',
|
payment_wait_time: 'Maksun odotusaika',
|
||||||
payment_wait_time_desc:
|
payment_wait_time_desc:
|
||||||
'Kuinka pitkään maksua odotetaan saapuvaksi, ennen kuin se merkitään Odotetaan-tilaan. Aseta pidemmäksi käytettäessä HODL-laskuja, Boltz-palvelua, tms',
|
'Kuinka pitkään maksua odotetaan saapuvaksi ennenkuin se merkitään Odotetaan -tilaan. Aseta pidemmäksi käytettäessä HODL-laskuja, Boltz-palvelua tms',
|
||||||
server_management: 'Palvelimen hallinta',
|
server_management: 'Palvelimen hallinta',
|
||||||
base_url: 'Palvelimen URL-osoite',
|
base_url: 'Palvelimen URL-osoite',
|
||||||
base_url_label: 'Palvelun staattinen pohja-URL',
|
base_url_label: 'Palvelun staattinen pohja-URL',
|
||||||
@@ -518,16 +474,12 @@ window.localisation.fi = {
|
|||||||
auth_keycloak_ci_hint:
|
auth_keycloak_ci_hint:
|
||||||
'Varmista, että valtuutuksen palautus-URL on asetettu muotoon https://{domain}/api/v1/auth/keycloak/token',
|
'Varmista, että valtuutuksen palautus-URL on asetettu muotoon https://{domain}/api/v1/auth/keycloak/token',
|
||||||
auth_keycloak_cs_label: 'Keycloak-asiakassalasana',
|
auth_keycloak_cs_label: 'Keycloak-asiakassalasana',
|
||||||
auth_keycloak_custom_org_label: 'Valinnainen Keycloak-organisaatio',
|
|
||||||
auth_keycloak_custom_icon_label: 'Valinnainen Keycloak-kuvake (URL)',
|
|
||||||
currency_settings: 'Valuutta-asetukset',
|
currency_settings: 'Valuutta-asetukset',
|
||||||
allowed_currencies: 'Käytettävät valuutat',
|
allowed_currencies: 'Käytettävät valuutat',
|
||||||
allowed_currencies_hint: 'Valitse käytettävissä olevat fiat-valuutat',
|
allowed_currencies_hint: 'Valitse käytettävissä olevat fiat-valuutat',
|
||||||
default_account_currency: 'Tilin oletusvaluutta',
|
default_account_currency: 'Tilin oletusvaluutta',
|
||||||
default_account_currency_hint: 'Kirjanpidon oletusvaluutta',
|
default_account_currency_hint: 'Kirjanpidon oletusvaluutta',
|
||||||
min_incoming_payment_amount: 'Pienin vastaanotettava maksun määrä',
|
|
||||||
min_incoming_payment_amount_desc:
|
|
||||||
'Pienin maksun määrä jolle voi luoda laskun',
|
|
||||||
max_incoming_payment_amount: 'Saapuvan maksun enimmäismäärä',
|
max_incoming_payment_amount: 'Saapuvan maksun enimmäismäärä',
|
||||||
max_incoming_payment_amount_desc: 'Enimmäismäärä jonka voi laskuttaa',
|
max_incoming_payment_amount_desc: 'Enimmäismäärä jonka voi laskuttaa',
|
||||||
max_outgoing_payment_amount: 'Lähtevän maksun enimmäismäärä',
|
max_outgoing_payment_amount: 'Lähtevän maksun enimmäismäärä',
|
||||||
@@ -590,8 +542,6 @@ window.localisation.fi = {
|
|||||||
admin_users_label: 'Käyttäjätunnus',
|
admin_users_label: 'Käyttäjätunnus',
|
||||||
allowed_users: 'Sallitut käyttäjät',
|
allowed_users: 'Sallitut käyttäjät',
|
||||||
allowed_users_hint: 'Vain nämä käyttäjät voivat käyttää LNbitsiä',
|
allowed_users_hint: 'Vain nämä käyttäjät voivat käyttää LNbitsiä',
|
||||||
allowed_users_hint_feature:
|
|
||||||
'Ainoastaan nämä käyttäjät voivat käyttää ominaisuutta {feature}',
|
|
||||||
allowed_users_label: 'Käyttäjätunnus',
|
allowed_users_label: 'Käyttäjätunnus',
|
||||||
allow_creation_user: 'Salli uusien käyttäjien luominen',
|
allow_creation_user: 'Salli uusien käyttäjien luominen',
|
||||||
allow_creation_user_desc: 'Etusivulta on mahdollisuus luoda uusia käyttäjiä',
|
allow_creation_user_desc: 'Etusivulta on mahdollisuus luoda uusia käyttäjiä',
|
||||||
@@ -627,12 +577,5 @@ window.localisation.fi = {
|
|||||||
view_column: 'Näytä lompakot rinnakkain',
|
view_column: 'Näytä lompakot rinnakkain',
|
||||||
filter_payments: 'Suodata maksuja',
|
filter_payments: 'Suodata maksuja',
|
||||||
filter_date: 'Suodata päiväyksellä',
|
filter_date: 'Suodata päiväyksellä',
|
||||||
websocket_example: 'Websocket example',
|
websocket_example: 'Websocket esimerkki'
|
||||||
secret_key: 'Secret Key',
|
|
||||||
signing_secret: 'Signing Secret',
|
|
||||||
signing_secret_hint:
|
|
||||||
'Signing secret for the webhook. Messages will be signed with this secret.',
|
|
||||||
callback_success_url: 'Callback Success URL',
|
|
||||||
callback_success_url_hint:
|
|
||||||
'The user will be redirected to this URL after the payment is successful'
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,11 +80,6 @@ window.AccountPageLogic = {
|
|||||||
token_id_list: [],
|
token_id_list: [],
|
||||||
allRead: false,
|
allRead: false,
|
||||||
allWrite: false
|
allWrite: false
|
||||||
},
|
|
||||||
notifications: {
|
|
||||||
nostr: {
|
|
||||||
identifier: ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -401,7 +396,6 @@ window.AccountPageLogic = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.getAuthenticatedUser()
|
const {data} = await LNbits.api.getAuthenticatedUser()
|
||||||
|
|||||||
+36
-14
@@ -19,19 +19,17 @@ window.LNbits = {
|
|||||||
amount,
|
amount,
|
||||||
memo,
|
memo,
|
||||||
unit = 'sat',
|
unit = 'sat',
|
||||||
lnurlWithdraw = null,
|
lnurlCallback = null,
|
||||||
fiatProvider = null,
|
fiatProvider = null,
|
||||||
internalMemo = null,
|
internalMemo = null
|
||||||
payment_hash = null
|
|
||||||
) {
|
) {
|
||||||
const data = {
|
const data = {
|
||||||
out: false,
|
out: false,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
memo: memo,
|
memo: memo,
|
||||||
unit: unit,
|
unit: unit,
|
||||||
lnurl_withdraw: lnurlWithdraw,
|
lnurl_callback: lnurlCallback,
|
||||||
fiat_provider: fiatProvider,
|
fiat_provider: fiatProvider
|
||||||
payment_hash: payment_hash
|
|
||||||
}
|
}
|
||||||
if (internalMemo) {
|
if (internalMemo) {
|
||||||
data.extra = {
|
data.extra = {
|
||||||
@@ -52,14 +50,39 @@ window.LNbits = {
|
|||||||
}
|
}
|
||||||
return this.request('post', '/api/v1/payments', wallet.adminkey, data)
|
return this.request('post', '/api/v1/payments', wallet.adminkey, data)
|
||||||
},
|
},
|
||||||
cancelInvoice(wallet, paymentHash) {
|
payLnurl(
|
||||||
return this.request('post', '/api/v1/payments/cancel', wallet.adminkey, {
|
wallet,
|
||||||
payment_hash: paymentHash
|
callback,
|
||||||
})
|
description_hash,
|
||||||
|
amount,
|
||||||
|
description = '',
|
||||||
|
comment = '',
|
||||||
|
unit = '',
|
||||||
|
internalMemo = null
|
||||||
|
) {
|
||||||
|
const data = {
|
||||||
|
callback,
|
||||||
|
description_hash,
|
||||||
|
amount,
|
||||||
|
comment,
|
||||||
|
description,
|
||||||
|
unit
|
||||||
|
}
|
||||||
|
|
||||||
|
if (internalMemo) {
|
||||||
|
data.internal_memo = String(internalMemo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.request(
|
||||||
|
'post',
|
||||||
|
'/api/v1/payments/lnurl',
|
||||||
|
wallet.adminkey,
|
||||||
|
data
|
||||||
|
)
|
||||||
},
|
},
|
||||||
settleInvoice(wallet, preimage) {
|
authLnurl(wallet, callback) {
|
||||||
return this.request('post', `/api/v1/payments/settle`, wallet.adminkey, {
|
return this.request('post', '/api/v1/lnurlauth', wallet.adminkey, {
|
||||||
preimage: preimage
|
callback
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
createAccount(name) {
|
createAccount(name) {
|
||||||
@@ -615,7 +638,6 @@ window.windowMixin = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
applyBackgroundImage() {
|
applyBackgroundImage() {
|
||||||
if (this.bgimageChoice == 'null') this.bgimageChoice = ''
|
|
||||||
if (this.bgimageChoice == '') {
|
if (this.bgimageChoice == '') {
|
||||||
document.body.classList.remove('bg-image')
|
document.body.classList.remove('bg-image')
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -118,13 +118,7 @@ window.app.component('payment-list', {
|
|||||||
field: row => row.extra.wallet_fiat_amount
|
field: row => row.extra.wallet_fiat_amount
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
preimage: null,
|
|
||||||
loading: false
|
loading: false
|
||||||
},
|
|
||||||
hodlInvoice: {
|
|
||||||
show: false,
|
|
||||||
payment: null,
|
|
||||||
preimage: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -229,35 +223,6 @@ window.app.component('payment-list', {
|
|||||||
})
|
})
|
||||||
.catch(LNbits.utils.notifyApiError)
|
.catch(LNbits.utils.notifyApiError)
|
||||||
},
|
},
|
||||||
showHoldInvoiceDialog(payment) {
|
|
||||||
this.hodlInvoice.show = true
|
|
||||||
this.hodlInvoice.preimage = ''
|
|
||||||
this.hodlInvoice.payment = payment
|
|
||||||
},
|
|
||||||
cancelHoldInvoice(payment_hash) {
|
|
||||||
LNbits.api
|
|
||||||
.cancelInvoice(this.g.wallet, payment_hash)
|
|
||||||
.then(() => {
|
|
||||||
this.update = !this.update
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: this.$t('invoice_cancelled')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(LNbits.utils.notifyApiError)
|
|
||||||
},
|
|
||||||
settleHoldInvoice(preimage) {
|
|
||||||
LNbits.api
|
|
||||||
.settleInvoice(this.g.wallet, preimage)
|
|
||||||
.then(() => {
|
|
||||||
this.update = !this.update
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: this.$t('invoice_settled')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(LNbits.utils.notifyApiError)
|
|
||||||
},
|
|
||||||
paymentTableRowKey(row) {
|
paymentTableRowKey(row) {
|
||||||
return row.payment_hash + row.amount
|
return row.payment_hash + row.amount
|
||||||
},
|
},
|
||||||
|
|||||||
+113
-100
@@ -40,8 +40,7 @@ window.WalletPageLogic = {
|
|||||||
data: {
|
data: {
|
||||||
amount: null,
|
amount: null,
|
||||||
memo: '',
|
memo: '',
|
||||||
internalMemo: null,
|
internalMemo: null
|
||||||
payment_hash: null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
invoiceQrCode: '',
|
invoiceQrCode: '',
|
||||||
@@ -141,13 +140,6 @@ 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() {
|
||||||
@@ -208,7 +200,6 @@ window.WalletPageLogic = {
|
|||||||
this.receive.data.amount = null
|
this.receive.data.amount = null
|
||||||
this.receive.data.memo = null
|
this.receive.data.memo = null
|
||||||
this.receive.data.internalMemo = null
|
this.receive.data.internalMemo = null
|
||||||
this.receive.data.payment_hash = null
|
|
||||||
this.receive.unit = this.isFiatPriority
|
this.receive.unit = this.isFiatPriority
|
||||||
? this.g.wallet.currency || 'sat'
|
? this.g.wallet.currency || 'sat'
|
||||||
: 'sat'
|
: 'sat'
|
||||||
@@ -265,10 +256,9 @@ window.WalletPageLogic = {
|
|||||||
this.receive.data.amount,
|
this.receive.data.amount,
|
||||||
this.receive.data.memo,
|
this.receive.data.memo,
|
||||||
this.receive.unit,
|
this.receive.unit,
|
||||||
this.receive.lnurlWithdraw,
|
this.receive.lnurl && this.receive.lnurl.callback,
|
||||||
this.receive.fiatProvider,
|
this.receive.fiatProvider,
|
||||||
this.receive.data.internalMemo,
|
this.receive.data.internalMemo
|
||||||
this.receive.data.payment_hash
|
|
||||||
)
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.g.updatePayments = !this.g.updatePayments
|
this.g.updatePayments = !this.g.updatePayments
|
||||||
@@ -281,29 +271,27 @@ window.WalletPageLogic = {
|
|||||||
if (!this.receive.lnurl) {
|
if (!this.receive.lnurl) {
|
||||||
this.readNfcTag()
|
this.readNfcTag()
|
||||||
}
|
}
|
||||||
|
// TODO: lnurl_callback and lnurl_response
|
||||||
// WITHDRAW
|
// WITHDRAW
|
||||||
if (
|
if (response.data.lnurl_response !== null) {
|
||||||
this.receive.lnurl &&
|
if (response.data.lnurl_response === false) {
|
||||||
response.data.extra?.lnurl_response !== null
|
response.data.lnurl_response = `Unable to connect`
|
||||||
) {
|
|
||||||
if (response.data.extra.lnurl_response === false) {
|
|
||||||
response.data.extra.lnurl_response = `Unable to connect`
|
|
||||||
}
|
}
|
||||||
const domain = this.receive.lnurl.callback.split('/')[2]
|
|
||||||
if (typeof response.data.extra.lnurl_response === 'string') {
|
if (typeof response.data.lnurl_response === 'string') {
|
||||||
// failure
|
// failure
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: `${domain} lnurl-withdraw call failed.`,
|
message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
|
||||||
caption: response.data.extra.lnurl_response
|
caption: response.data.lnurl_response
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
} else if (response.data.extra.lnurl_response === true) {
|
} else if (response.data.lnurl_response === true) {
|
||||||
// success
|
// success
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
timeout: 3000,
|
timeout: 5000,
|
||||||
message: `Invoice sent to ${domain}!`,
|
message: `Invoice sent to ${this.receive.lnurl.domain}!`,
|
||||||
spinner: true
|
spinner: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -352,30 +340,32 @@ window.WalletPageLogic = {
|
|||||||
},
|
},
|
||||||
lnurlScan() {
|
lnurlScan() {
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request('POST', '/api/v1/lnurlscan', this.g.wallet.adminkey, {
|
.request(
|
||||||
lnurl: this.parse.data.request
|
'GET',
|
||||||
})
|
'/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') {
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: `lnurl scan failed.`,
|
message: `${data.domain} lnurl call failed.`,
|
||||||
caption: data.reason
|
caption: data.reason
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.tag === 'payRequest') {
|
if (data.kind === 'pay') {
|
||||||
this.parse.lnurlpay = Object.freeze(data)
|
this.parse.lnurlpay = Object.freeze(data)
|
||||||
this.parse.data.amount = data.minSendable / 1000
|
this.parse.data.amount = data.minSendable / 1000
|
||||||
} else if (data.tag === 'login') {
|
} else if (data.kind === 'auth') {
|
||||||
this.parse.lnurlauth = Object.freeze(data)
|
this.parse.lnurlauth = Object.freeze(data)
|
||||||
} else if (data.tag === 'withdrawRequest') {
|
} else if (data.kind === 'withdraw') {
|
||||||
this.parse.show = false
|
this.parse.show = false
|
||||||
this.receive.show = true
|
this.receive.show = true
|
||||||
this.receive.lnurlWithdraw = Object.freeze(data)
|
|
||||||
this.receive.status = 'pending'
|
this.receive.status = 'pending'
|
||||||
this.receive.paymentReq = null
|
this.receive.paymentReq = null
|
||||||
this.receive.paymentHash = null
|
this.receive.paymentHash = null
|
||||||
@@ -385,9 +375,8 @@ window.WalletPageLogic = {
|
|||||||
data.minWithdrawable / 1000,
|
data.minWithdrawable / 1000,
|
||||||
data.maxWithdrawable / 1000
|
data.maxWithdrawable / 1000
|
||||||
]
|
]
|
||||||
const domain = data.callback.split('/')[2]
|
|
||||||
this.receive.lnurl = {
|
this.receive.lnurl = {
|
||||||
domain: domain,
|
domain: data.domain,
|
||||||
callback: data.callback,
|
callback: data.callback,
|
||||||
fixed: data.fixed
|
fixed: data.fixed
|
||||||
}
|
}
|
||||||
@@ -402,29 +391,19 @@ 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()
|
this.parse.data.request = this.parse.data.request.trim().toLowerCase()
|
||||||
const req = this.parse.data.request.toLowerCase()
|
let req = this.parse.data.request
|
||||||
if (req.startsWith('lightning:')) {
|
if (req.startsWith('lightning:')) {
|
||||||
this.parse.data.request = this.parse.data.request.slice(10)
|
this.parse.data.request = req.slice(10)
|
||||||
} else if (req.startsWith('lnurl:')) {
|
} else if (req.startsWith('lnurl:')) {
|
||||||
this.parse.data.request = this.parse.data.request.slice(6)
|
this.parse.data.request = req.slice(6)
|
||||||
} else if (req.includes('lightning=lnurl1')) {
|
} else if (req.includes('lightning=lnurl1')) {
|
||||||
this.parse.data.request = this.parse.data.request
|
this.parse.data.request = req.split('lightning=')[1].split('&')[0]
|
||||||
.split('lightning=')[1]
|
|
||||||
.split('&')[0]
|
|
||||||
}
|
}
|
||||||
if (this.isLnurl(this.parse.data.request)) {
|
req = this.parse.data.request
|
||||||
|
if (req.startsWith('lnurl1') || req.match(/[\w.+-~_]+@[\w.+-~_]/)) {
|
||||||
this.lnurlScan()
|
this.lnurlScan()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -533,49 +512,86 @@ window.WalletPageLogic = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
payLnurl() {
|
payLnurl() {
|
||||||
|
const dismissPaymentMsg = Quasar.Notify.create({
|
||||||
|
timeout: 0,
|
||||||
|
message: 'Processing payment...'
|
||||||
|
})
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request('post', '/api/v1/payments/lnurl', this.g.wallet.adminkey, {
|
.payLnurl(
|
||||||
res: this.parse.lnurlpay,
|
this.g.wallet,
|
||||||
unit: this.parse.data.unit,
|
this.parse.lnurlpay.callback,
|
||||||
amount: this.parse.data.amount * 1000,
|
this.parse.lnurlpay.description_hash,
|
||||||
comment: this.parse.data.comment,
|
this.parse.data.amount * 1000,
|
||||||
internalMemo: this.parse.data.internalMemo
|
this.parse.lnurlpay.description.slice(0, 120),
|
||||||
})
|
this.parse.data.comment,
|
||||||
|
this.parse.data.unit,
|
||||||
|
this.parse.data.internalMemo
|
||||||
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.parse.show = false
|
this.parse.show = false
|
||||||
if (response.data.extra.success_action) {
|
|
||||||
const action = JSON.parse(response.data.extra.success_action)
|
clearInterval(this.parse.paymentChecker)
|
||||||
switch (action.tag) {
|
setTimeout(() => {
|
||||||
case 'url':
|
clearInterval(this.parse.paymentChecker)
|
||||||
Quasar.Notify.create({
|
}, 40000)
|
||||||
message: `<a target="_blank" style="color: inherit" href="${action.url}">${action.url}</a>`,
|
this.parse.paymentChecker = setInterval(() => {
|
||||||
caption: action.description,
|
LNbits.api
|
||||||
html: true,
|
.getPayment(this.g.wallet, response.data.payment_hash)
|
||||||
type: 'positive',
|
.then(res => {
|
||||||
timeout: 0,
|
if (res.data.paid) {
|
||||||
closeBtn: true
|
dismissPaymentMsg()
|
||||||
})
|
clearInterval(this.parse.paymentChecker)
|
||||||
break
|
// show lnurlpay success action
|
||||||
case 'message':
|
const extra = response.data.extra
|
||||||
Quasar.Notify.create({
|
if (extra.success_action) {
|
||||||
message: action.message,
|
switch (extra.success_action.tag) {
|
||||||
type: 'positive',
|
case 'url':
|
||||||
timeout: 0,
|
Quasar.Notify.create({
|
||||||
closeBtn: true
|
message: `<a target="_blank" style="color: inherit" href="${extra.success_action.url}">${extra.success_action.url}</a>`,
|
||||||
})
|
caption: extra.success_action.description,
|
||||||
break
|
html: true,
|
||||||
case 'aes':
|
type: 'positive',
|
||||||
decryptLnurlPayAES(action, response.data.preimage)
|
timeout: 0,
|
||||||
Quasar.Notify.create({
|
closeBtn: true
|
||||||
message: value,
|
})
|
||||||
caption: extra.success_action.description,
|
break
|
||||||
html: true,
|
case 'message':
|
||||||
type: 'positive',
|
Quasar.Notify.create({
|
||||||
timeout: 0,
|
message: extra.success_action.message,
|
||||||
closeBtn: true
|
type: 'positive',
|
||||||
})
|
timeout: 0,
|
||||||
}
|
closeBtn: true
|
||||||
}
|
})
|
||||||
|
break
|
||||||
|
case 'aes':
|
||||||
|
LNbits.api
|
||||||
|
.getPayment(this.g.wallet, response.data.payment_hash)
|
||||||
|
.then(({data: payment}) =>
|
||||||
|
decryptLnurlPayAES(
|
||||||
|
extra.success_action,
|
||||||
|
payment.preimage
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(value => {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
message: value,
|
||||||
|
caption: extra.success_action.description,
|
||||||
|
html: true,
|
||||||
|
type: 'positive',
|
||||||
|
timeout: 0,
|
||||||
|
closeBtn: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
dismissPaymentMsg()
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
authLnurl() {
|
authLnurl() {
|
||||||
@@ -583,13 +599,9 @@ window.WalletPageLogic = {
|
|||||||
timeout: 10,
|
timeout: 10,
|
||||||
message: 'Performing authentication...'
|
message: 'Performing authentication...'
|
||||||
})
|
})
|
||||||
|
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request(
|
.authLnurl(this.g.wallet, this.parse.lnurlauth.callback)
|
||||||
'post',
|
|
||||||
'/api/v1/lnurlauth',
|
|
||||||
wallet.adminkey,
|
|
||||||
this.parse.lnurlauth
|
|
||||||
)
|
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
dismissAuthMsg()
|
dismissAuthMsg()
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
@@ -600,9 +612,10 @@ window.WalletPageLogic = {
|
|||||||
this.parse.show = false
|
this.parse.show = false
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
dismissAuthMsg()
|
||||||
if (err.response.data.reason) {
|
if (err.response.data.reason) {
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
message: `Authentication failed. ${this.parse.lnurlauth.callback} says:`,
|
message: `Authentication failed. ${this.parse.lnurlauth.domain} says:`,
|
||||||
caption: err.response.data.reason,
|
caption: err.response.data.reason,
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
timeout: 5000
|
timeout: 5000
|
||||||
|
|||||||
@@ -18,16 +18,16 @@ $themes: (
|
|||||||
'freedom': (
|
'freedom': (
|
||||||
primary: #e22156,
|
primary: #e22156,
|
||||||
secondary: #b91a45,
|
secondary: #b91a45,
|
||||||
dark: #462f36,
|
dark: #0a0a0a,
|
||||||
info: #47393d,
|
info: #1b1b1b,
|
||||||
marginal-bg: #2d293b,
|
marginal-bg: #2d293b,
|
||||||
marginal-text: #fff
|
marginal-text: #fff
|
||||||
),
|
),
|
||||||
'cyber': (
|
'cyber': (
|
||||||
primary: #7cb342,
|
primary: #7cb342,
|
||||||
secondary: #558b2f,
|
secondary: #558b2f,
|
||||||
dark: #000,
|
dark: #0a0a0a,
|
||||||
info: #1f2915,
|
info: #1b1b1b,
|
||||||
marginal-bg: #2d293b,
|
marginal-bg: #2d293b,
|
||||||
marginal-text: #fff
|
marginal-text: #fff
|
||||||
),
|
),
|
||||||
@@ -62,13 +62,5 @@ $themes: (
|
|||||||
info: rgb(39, 39, 39),
|
info: rgb(39, 39, 39),
|
||||||
marginal-bg: #000,
|
marginal-bg: #000,
|
||||||
marginal-text: rgb(255, 255, 255)
|
marginal-text: rgb(255, 255, 255)
|
||||||
),
|
|
||||||
'salvador': (
|
|
||||||
primary: #1976d2,
|
|
||||||
secondary: #26a69a,
|
|
||||||
dark: #253647,
|
|
||||||
info: #343d47,
|
|
||||||
marginal-bg: #0d1620,
|
|
||||||
marginal-text: rgb(255, 255, 255)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
+2
-2
File diff suppressed because one or more lines are too long
@@ -370,12 +370,7 @@
|
|||||||
v-if="extras.length"
|
v-if="extras.length"
|
||||||
>
|
>
|
||||||
<template v-for="entry in extras">
|
<template v-for="entry in extras">
|
||||||
<q-item
|
<q-item v-if="!!entry.value" key="entry.key" class="text-grey-4">
|
||||||
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>
|
||||||
@@ -885,19 +880,6 @@
|
|||||||
:props="props"
|
:props="props"
|
||||||
style="white-space: normal; word-break: break-all"
|
style="white-space: normal; word-break: break-all"
|
||||||
>
|
>
|
||||||
<q-icon
|
|
||||||
v-if="
|
|
||||||
props.row.isIn &&
|
|
||||||
props.row.isPending &&
|
|
||||||
props.row.extra.hold_invoice
|
|
||||||
"
|
|
||||||
name="pause_presentation"
|
|
||||||
color="grey"
|
|
||||||
class="cursor-pointer q-mr-sm"
|
|
||||||
@click="showHoldInvoiceDialog(props.row)"
|
|
||||||
>
|
|
||||||
<q-tooltip><span v-text="$t('hold_invoice')"></span></q-tooltip>
|
|
||||||
</q-icon>
|
|
||||||
<q-badge
|
<q-badge
|
||||||
v-if="props.row.tag"
|
v-if="props.row.tag"
|
||||||
color="yellow"
|
color="yellow"
|
||||||
@@ -959,7 +941,7 @@
|
|||||||
</q-td>
|
</q-td>
|
||||||
<q-dialog v-model="props.expand" :props="props" position="top">
|
<q-dialog v-model="props.expand" :props="props" position="top">
|
||||||
<q-card class="q-pa-sm q-pt-xl lnbits__dialog-card">
|
<q-card class="q-pa-sm q-pt-xl lnbits__dialog-card">
|
||||||
<q-card-section>
|
<q-card-section class="">
|
||||||
<q-list bordered separator>
|
<q-list bordered separator>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
expand-separator
|
expand-separator
|
||||||
@@ -1022,7 +1004,6 @@
|
|||||||
></lnbits-payment-details>
|
></lnbits-payment-details>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="props.row.isIn && props.row.isPending && props.row.bolt11"
|
v-if="props.row.isIn && props.row.isPending && props.row.bolt11"
|
||||||
class="text-center q-my-lg"
|
class="text-center q-my-lg"
|
||||||
@@ -1079,55 +1060,6 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<q-dialog v-model="hodlInvoice.show" position="top">
|
|
||||||
<q-card class="q-pa-sm q-pt-xl lnbits__dialog-card">
|
|
||||||
<q-card-section>
|
|
||||||
<q-item-label class="text-h6">
|
|
||||||
<span v-text="$t('hold_invoice')"></span>
|
|
||||||
</q-item-label>
|
|
||||||
<q-item-label class="text-subtitle2">
|
|
||||||
<span v-text="$t('hold_invoice_description')"></span>
|
|
||||||
</q-item-label>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
:label="$t('preimage')"
|
|
||||||
:hint="$t('preimage_hint')"
|
|
||||||
v-model="hodlInvoice.preimage"
|
|
||||||
dense
|
|
||||||
autofocus
|
|
||||||
@keyup.enter="settleHoldInvoice(hodlInvoice.preimage)"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="row q-gutter-x-sm">
|
|
||||||
<q-btn
|
|
||||||
@click="settleHoldInvoice(hodlInvoice.preimage)"
|
|
||||||
outline
|
|
||||||
v-close-popup
|
|
||||||
color="grey"
|
|
||||||
:label="$t('settle_invoice')"
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-sm"
|
|
||||||
@click="cancelHoldInvoice(hodlInvoice.payment.payment_hash)"
|
|
||||||
:label="$t('cancel_invoice')"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
:label="$t('close')"
|
|
||||||
></q-btn>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from lnbits.settings import settings
|
|||||||
|
|
||||||
|
|
||||||
def log_server_info():
|
def log_server_info():
|
||||||
logger.info("LNbits Info")
|
logger.info("Starting LNbits")
|
||||||
logger.info(f"Version: {settings.version}")
|
logger.info(f"Version: {settings.version}")
|
||||||
logger.info(f"Baseurl: {settings.lnbits_baseurl}")
|
logger.info(f"Baseurl: {settings.lnbits_baseurl}")
|
||||||
logger.info(f"Host: {settings.host}")
|
logger.info(f"Host: {settings.host}")
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
|
from lnbits.nodes import set_node_class
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets.base import Feature, Wallet
|
from lnbits.wallets.base import Wallet
|
||||||
|
|
||||||
from .alby import AlbyWallet
|
from .alby import AlbyWallet
|
||||||
from .blink import BlinkWallet
|
from .blink import BlinkWallet
|
||||||
@@ -34,13 +35,13 @@ from .void import VoidWallet
|
|||||||
from .zbd import ZBDWallet
|
from .zbd import ZBDWallet
|
||||||
|
|
||||||
|
|
||||||
def set_funding_source(class_name: str | None = None) -> None:
|
def set_funding_source(class_name: str | None = None):
|
||||||
backend_wallet_class = class_name or settings.lnbits_backend_wallet_class
|
backend_wallet_class = class_name or settings.lnbits_backend_wallet_class
|
||||||
funding_source_constructor = getattr(wallets_module, backend_wallet_class)
|
funding_source_constructor = getattr(wallets_module, backend_wallet_class)
|
||||||
global funding_source
|
global funding_source
|
||||||
funding_source = funding_source_constructor()
|
funding_source = funding_source_constructor()
|
||||||
settings.has_nodemanager = funding_source.has_feature(Feature.nodemanager)
|
if funding_source.__node_cls__:
|
||||||
settings.has_holdinvoice = funding_source.has_feature(Feature.holdinvoice)
|
set_node_class(funding_source.__node_cls__(funding_source))
|
||||||
|
|
||||||
|
|
||||||
def get_funding_source() -> Wallet:
|
def get_funding_source() -> Wallet:
|
||||||
|
|||||||
@@ -3,24 +3,16 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections.abc import AsyncGenerator, Coroutine
|
from collections.abc import AsyncGenerator, Coroutine
|
||||||
from enum import Enum
|
|
||||||
from typing import TYPE_CHECKING, NamedTuple
|
from typing import TYPE_CHECKING, NamedTuple
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.exceptions import InvoiceError
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from lnbits.nodes.base import Node
|
from lnbits.nodes.base import Node
|
||||||
|
|
||||||
|
|
||||||
class Feature(Enum):
|
|
||||||
nodemanager = "nodemanager"
|
|
||||||
holdinvoice = "holdinvoice"
|
|
||||||
# bolt12 = "bolt12"
|
|
||||||
|
|
||||||
|
|
||||||
class StatusResponse(NamedTuple):
|
class StatusResponse(NamedTuple):
|
||||||
error_message: str | None
|
error_message: str | None
|
||||||
balance_msat: int
|
balance_msat: int
|
||||||
@@ -108,10 +100,6 @@ class PaymentPendingStatus(PaymentStatus):
|
|||||||
class Wallet(ABC):
|
class Wallet(ABC):
|
||||||
|
|
||||||
__node_cls__: type[Node] | None = None
|
__node_cls__: type[Node] | None = None
|
||||||
features: list[Feature] | None = None
|
|
||||||
|
|
||||||
def has_feature(self, feature: Feature) -> bool:
|
|
||||||
return self.features is not None and feature in self.features
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.pending_invoices: list[str] = []
|
self.pending_invoices: list[str] = []
|
||||||
@@ -153,29 +141,6 @@ class Wallet(ABC):
|
|||||||
) -> Coroutine[None, None, PaymentStatus]:
|
) -> Coroutine[None, None, PaymentStatus]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def create_hold_invoice(
|
|
||||||
self,
|
|
||||||
amount: int,
|
|
||||||
payment_hash: str,
|
|
||||||
memo: str | None = None,
|
|
||||||
description_hash: bytes | None = None,
|
|
||||||
unhashed_description: bytes | None = None,
|
|
||||||
**kwargs,
|
|
||||||
) -> InvoiceResponse:
|
|
||||||
raise InvoiceError(
|
|
||||||
message="Hold invoices are not supported by this wallet.", status="failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def settle_hold_invoice(self, preimage: str) -> InvoiceResponse:
|
|
||||||
raise InvoiceError(
|
|
||||||
message="Hold invoices are not supported by this wallet.", status="failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def cancel_hold_invoice(self, payment_hash: str) -> InvoiceResponse:
|
|
||||||
raise InvoiceError(
|
|
||||||
message="Hold invoices are not supported by this wallet.", status="failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||||
while settings.lnbits_running:
|
while settings.lnbits_running:
|
||||||
for invoice in self.pending_invoices:
|
for invoice in self.pending_invoices:
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ from typing import Optional
|
|||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from websockets import Subprotocol, connect
|
from websockets.legacy.client import connect
|
||||||
|
from websockets.typing import Subprotocol
|
||||||
|
|
||||||
from lnbits import bolt11
|
from lnbits import bolt11
|
||||||
from lnbits.helpers import normalize_endpoint
|
from lnbits.helpers import normalize_endpoint
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from lnbits.settings import settings
|
|||||||
from lnbits.utils.crypto import random_secret_and_hash
|
from lnbits.utils.crypto import random_secret_and_hash
|
||||||
|
|
||||||
from .base import (
|
from .base import (
|
||||||
Feature,
|
|
||||||
InvoiceResponse,
|
InvoiceResponse,
|
||||||
PaymentFailedStatus,
|
PaymentFailedStatus,
|
||||||
PaymentPendingStatus,
|
PaymentPendingStatus,
|
||||||
@@ -32,16 +31,12 @@ async def run_sync(func) -> Any:
|
|||||||
|
|
||||||
|
|
||||||
class CoreLightningWallet(Wallet):
|
class CoreLightningWallet(Wallet):
|
||||||
"""Core Lightning RPC implementation."""
|
|
||||||
|
|
||||||
__node_cls__ = CoreLightningNode
|
__node_cls__ = CoreLightningNode
|
||||||
features = [Feature.nodemanager]
|
|
||||||
|
|
||||||
async def cleanup(self):
|
async def cleanup(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
rpc = settings.corelightning_rpc or settings.clightning_rpc
|
rpc = settings.corelightning_rpc or settings.clightning_rpc
|
||||||
if not rpc:
|
if not rpc:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from typing import Any, Optional
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from websockets import connect
|
from websockets.legacy.client import connect
|
||||||
|
|
||||||
from lnbits.helpers import normalize_endpoint
|
from lnbits.helpers import normalize_endpoint
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
@@ -245,9 +245,7 @@ class EclairWallet(Wallet):
|
|||||||
try:
|
try:
|
||||||
async with connect(
|
async with connect(
|
||||||
self.ws_url,
|
self.ws_url,
|
||||||
additional_headers=[
|
extra_headers=[("Authorization", self.headers["Authorization"])],
|
||||||
("Authorization", self.headers["Authorization"])
|
|
||||||
],
|
|
||||||
) as ws:
|
) as ws:
|
||||||
while settings.lnbits_running:
|
while settings.lnbits_running:
|
||||||
message = await ws.recv()
|
message = await ws.recv()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import Optional
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from websockets import connect
|
from websockets.legacy.client import connect
|
||||||
|
|
||||||
from lnbits.helpers import normalize_endpoint
|
from lnbits.helpers import normalize_endpoint
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
||||||
# NO CHECKED-IN PROTOBUF GENCODE
|
|
||||||
# source: invoices.proto
|
|
||||||
# Protobuf Python Version: 5.28.1
|
|
||||||
"""Generated protocol buffer code."""
|
|
||||||
from google.protobuf import descriptor as _descriptor
|
|
||||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
||||||
from google.protobuf import runtime_version as _runtime_version
|
|
||||||
from google.protobuf import symbol_database as _symbol_database
|
|
||||||
from google.protobuf.internal import builder as _builder
|
|
||||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
||||||
_runtime_version.Domain.PUBLIC,
|
|
||||||
5,
|
|
||||||
28,
|
|
||||||
1,
|
|
||||||
'',
|
|
||||||
'invoices.proto'
|
|
||||||
)
|
|
||||||
# @@protoc_insertion_point(imports)
|
|
||||||
|
|
||||||
_sym_db = _symbol_database.Default()
|
|
||||||
|
|
||||||
|
|
||||||
import lnbits.wallets.lnd_grpc_files.lightning_pb2 as lightning__pb2
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0einvoices.proto\x12\x0binvoicesrpc\x1a\x0flightning.proto\"(\n\x10\x43\x61ncelInvoiceMsg\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\"\x13\n\x11\x43\x61ncelInvoiceResp\"\xe4\x01\n\x15\x41\x64\x64HoldInvoiceRequest\x12\x0c\n\x04memo\x18\x01 \x01(\t\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\x12\r\n\x05value\x18\x03 \x01(\x03\x12\x12\n\nvalue_msat\x18\n \x01(\x03\x12\x18\n\x10\x64\x65scription_hash\x18\x04 \x01(\x0c\x12\x0e\n\x06\x65xpiry\x18\x05 \x01(\x03\x12\x15\n\rfallback_addr\x18\x06 \x01(\t\x12\x13\n\x0b\x63ltv_expiry\x18\x07 \x01(\x04\x12%\n\x0broute_hints\x18\x08 \x03(\x0b\x32\x10.lnrpc.RouteHint\x12\x0f\n\x07private\x18\t \x01(\x08\"V\n\x12\x41\x64\x64HoldInvoiceResp\x12\x17\n\x0fpayment_request\x18\x01 \x01(\t\x12\x11\n\tadd_index\x18\x02 \x01(\x04\x12\x14\n\x0cpayment_addr\x18\x03 \x01(\x0c\"$\n\x10SettleInvoiceMsg\x12\x10\n\x08preimage\x18\x01 \x01(\x0c\"\x13\n\x11SettleInvoiceResp\"5\n\x1dSubscribeSingleInvoiceRequest\x12\x0e\n\x06r_hash\x18\x02 \x01(\x0cJ\x04\x08\x01\x10\x02\"\x99\x01\n\x10LookupInvoiceMsg\x12\x16\n\x0cpayment_hash\x18\x01 \x01(\x0cH\x00\x12\x16\n\x0cpayment_addr\x18\x02 \x01(\x0cH\x00\x12\x10\n\x06set_id\x18\x03 \x01(\x0cH\x00\x12\x34\n\x0flookup_modifier\x18\x04 \x01(\x0e\x32\x1b.invoicesrpc.LookupModifierB\r\n\x0binvoice_ref\".\n\nCircuitKey\x12\x0f\n\x07\x63han_id\x18\x01 \x01(\x04\x12\x0f\n\x07htlc_id\x18\x02 \x01(\x04\"\xdd\x02\n\x11HtlcModifyRequest\x12\x1f\n\x07invoice\x18\x01 \x01(\x0b\x32\x0e.lnrpc.Invoice\x12\x36\n\x15\x65xit_htlc_circuit_key\x18\x02 \x01(\x0b\x32\x17.invoicesrpc.CircuitKey\x12\x15\n\rexit_htlc_amt\x18\x03 \x01(\x04\x12\x18\n\x10\x65xit_htlc_expiry\x18\x04 \x01(\r\x12\x16\n\x0e\x63urrent_height\x18\x05 \x01(\r\x12\x64\n\x1d\x65xit_htlc_wire_custom_records\x18\x06 \x03(\x0b\x32=.invoicesrpc.HtlcModifyRequest.ExitHtlcWireCustomRecordsEntry\x1a@\n\x1e\x45xitHtlcWireCustomRecordsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"z\n\x12HtlcModifyResponse\x12,\n\x0b\x63ircuit_key\x18\x01 \x01(\x0b\x32\x17.invoicesrpc.CircuitKey\x12\x15\n\x08\x61mt_paid\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x12\n\ncancel_set\x18\x03 \x01(\x08\x42\x0b\n\t_amt_paid*D\n\x0eLookupModifier\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x11\n\rHTLC_SET_ONLY\x10\x01\x12\x12\n\x0eHTLC_SET_BLANK\x10\x02\x32\xf0\x03\n\x08Invoices\x12V\n\x16SubscribeSingleInvoice\x12*.invoicesrpc.SubscribeSingleInvoiceRequest\x1a\x0e.lnrpc.Invoice0\x01\x12N\n\rCancelInvoice\x12\x1d.invoicesrpc.CancelInvoiceMsg\x1a\x1e.invoicesrpc.CancelInvoiceResp\x12U\n\x0e\x41\x64\x64HoldInvoice\x12\".invoicesrpc.AddHoldInvoiceRequest\x1a\x1f.invoicesrpc.AddHoldInvoiceResp\x12N\n\rSettleInvoice\x12\x1d.invoicesrpc.SettleInvoiceMsg\x1a\x1e.invoicesrpc.SettleInvoiceResp\x12@\n\x0fLookupInvoiceV2\x12\x1d.invoicesrpc.LookupInvoiceMsg\x1a\x0e.lnrpc.Invoice\x12S\n\x0cHtlcModifier\x12\x1f.invoicesrpc.HtlcModifyResponse\x1a\x1e.invoicesrpc.HtlcModifyRequest(\x01\x30\x01\x42\x33Z1github.com/lightningnetwork/lnd/lnrpc/invoicesrpcb\x06proto3')
|
|
||||||
|
|
||||||
_globals = globals()
|
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'invoices_pb2', _globals)
|
|
||||||
if not _descriptor._USE_C_DESCRIPTORS:
|
|
||||||
_globals['DESCRIPTOR']._loaded_options = None
|
|
||||||
_globals['DESCRIPTOR']._serialized_options = b'Z1github.com/lightningnetwork/lnd/lnrpc/invoicesrpc'
|
|
||||||
_globals['_HTLCMODIFYREQUEST_EXITHTLCWIRECUSTOMRECORDSENTRY']._loaded_options = None
|
|
||||||
_globals['_HTLCMODIFYREQUEST_EXITHTLCWIRECUSTOMRECORDSENTRY']._serialized_options = b'8\001'
|
|
||||||
_globals['_LOOKUPMODIFIER']._serialized_start=1224
|
|
||||||
_globals['_LOOKUPMODIFIER']._serialized_end=1292
|
|
||||||
_globals['_CANCELINVOICEMSG']._serialized_start=48
|
|
||||||
_globals['_CANCELINVOICEMSG']._serialized_end=88
|
|
||||||
_globals['_CANCELINVOICERESP']._serialized_start=90
|
|
||||||
_globals['_CANCELINVOICERESP']._serialized_end=109
|
|
||||||
_globals['_ADDHOLDINVOICEREQUEST']._serialized_start=112
|
|
||||||
_globals['_ADDHOLDINVOICEREQUEST']._serialized_end=340
|
|
||||||
_globals['_ADDHOLDINVOICERESP']._serialized_start=342
|
|
||||||
_globals['_ADDHOLDINVOICERESP']._serialized_end=428
|
|
||||||
_globals['_SETTLEINVOICEMSG']._serialized_start=430
|
|
||||||
_globals['_SETTLEINVOICEMSG']._serialized_end=466
|
|
||||||
_globals['_SETTLEINVOICERESP']._serialized_start=468
|
|
||||||
_globals['_SETTLEINVOICERESP']._serialized_end=487
|
|
||||||
_globals['_SUBSCRIBESINGLEINVOICEREQUEST']._serialized_start=489
|
|
||||||
_globals['_SUBSCRIBESINGLEINVOICEREQUEST']._serialized_end=542
|
|
||||||
_globals['_LOOKUPINVOICEMSG']._serialized_start=545
|
|
||||||
_globals['_LOOKUPINVOICEMSG']._serialized_end=698
|
|
||||||
_globals['_CIRCUITKEY']._serialized_start=700
|
|
||||||
_globals['_CIRCUITKEY']._serialized_end=746
|
|
||||||
_globals['_HTLCMODIFYREQUEST']._serialized_start=749
|
|
||||||
_globals['_HTLCMODIFYREQUEST']._serialized_end=1098
|
|
||||||
_globals['_HTLCMODIFYREQUEST_EXITHTLCWIRECUSTOMRECORDSENTRY']._serialized_start=1034
|
|
||||||
_globals['_HTLCMODIFYREQUEST_EXITHTLCWIRECUSTOMRECORDSENTRY']._serialized_end=1098
|
|
||||||
_globals['_HTLCMODIFYRESPONSE']._serialized_start=1100
|
|
||||||
_globals['_HTLCMODIFYRESPONSE']._serialized_end=1222
|
|
||||||
_globals['_INVOICES']._serialized_start=1295
|
|
||||||
_globals['_INVOICES']._serialized_end=1791
|
|
||||||
# @@protoc_insertion_point(module_scope)
|
|
||||||
@@ -1,392 +0,0 @@
|
|||||||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
|
||||||
"""Client and server classes corresponding to protobuf-defined services."""
|
|
||||||
import grpc
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
import lnbits.wallets.lnd_grpc_files.invoices_pb2 as invoices__pb2
|
|
||||||
import lnbits.wallets.lnd_grpc_files.lightning_pb2 as lightning__pb2
|
|
||||||
|
|
||||||
GRPC_GENERATED_VERSION = '1.68.1'
|
|
||||||
GRPC_VERSION = grpc.__version__
|
|
||||||
_version_not_supported = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
from grpc._utilities import first_version_is_lower
|
|
||||||
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
|
|
||||||
except ImportError:
|
|
||||||
_version_not_supported = True
|
|
||||||
|
|
||||||
if _version_not_supported:
|
|
||||||
raise RuntimeError(
|
|
||||||
f'The grpc package installed is at version {GRPC_VERSION},'
|
|
||||||
+ f' but the generated code in invoices_pb2_grpc.py depends on'
|
|
||||||
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
|
|
||||||
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
|
|
||||||
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class InvoicesStub(object):
|
|
||||||
"""
|
|
||||||
Comments in this file will be directly parsed into the API
|
|
||||||
Documentation as descriptions of the associated method, message, or field.
|
|
||||||
These descriptions should go right above the definition of the object, and
|
|
||||||
can be in either block or // comment format.
|
|
||||||
|
|
||||||
An RPC method can be matched to an lncli command by placing a line in the
|
|
||||||
beginning of the description in exactly the following format:
|
|
||||||
lncli: `methodname`
|
|
||||||
|
|
||||||
Failure to specify the exact name of the command will cause documentation
|
|
||||||
generation to fail.
|
|
||||||
|
|
||||||
More information on how exactly the gRPC documentation is generated from
|
|
||||||
this proto file can be found here:
|
|
||||||
https://github.com/lightninglabs/lightning-api
|
|
||||||
|
|
||||||
Invoices is a service that can be used to create, accept, settle and cancel
|
|
||||||
invoices.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, channel):
|
|
||||||
"""Constructor.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
channel: A grpc.Channel.
|
|
||||||
"""
|
|
||||||
self.SubscribeSingleInvoice = channel.unary_stream(
|
|
||||||
'/invoicesrpc.Invoices/SubscribeSingleInvoice',
|
|
||||||
request_serializer=invoices__pb2.SubscribeSingleInvoiceRequest.SerializeToString,
|
|
||||||
response_deserializer=lightning__pb2.Invoice.FromString,
|
|
||||||
_registered_method=True)
|
|
||||||
self.CancelInvoice = channel.unary_unary(
|
|
||||||
'/invoicesrpc.Invoices/CancelInvoice',
|
|
||||||
request_serializer=invoices__pb2.CancelInvoiceMsg.SerializeToString,
|
|
||||||
response_deserializer=invoices__pb2.CancelInvoiceResp.FromString,
|
|
||||||
_registered_method=True)
|
|
||||||
self.AddHoldInvoice = channel.unary_unary(
|
|
||||||
'/invoicesrpc.Invoices/AddHoldInvoice',
|
|
||||||
request_serializer=invoices__pb2.AddHoldInvoiceRequest.SerializeToString,
|
|
||||||
response_deserializer=invoices__pb2.AddHoldInvoiceResp.FromString,
|
|
||||||
_registered_method=True)
|
|
||||||
self.SettleInvoice = channel.unary_unary(
|
|
||||||
'/invoicesrpc.Invoices/SettleInvoice',
|
|
||||||
request_serializer=invoices__pb2.SettleInvoiceMsg.SerializeToString,
|
|
||||||
response_deserializer=invoices__pb2.SettleInvoiceResp.FromString,
|
|
||||||
_registered_method=True)
|
|
||||||
self.LookupInvoiceV2 = channel.unary_unary(
|
|
||||||
'/invoicesrpc.Invoices/LookupInvoiceV2',
|
|
||||||
request_serializer=invoices__pb2.LookupInvoiceMsg.SerializeToString,
|
|
||||||
response_deserializer=lightning__pb2.Invoice.FromString,
|
|
||||||
_registered_method=True)
|
|
||||||
self.HtlcModifier = channel.stream_stream(
|
|
||||||
'/invoicesrpc.Invoices/HtlcModifier',
|
|
||||||
request_serializer=invoices__pb2.HtlcModifyResponse.SerializeToString,
|
|
||||||
response_deserializer=invoices__pb2.HtlcModifyRequest.FromString,
|
|
||||||
_registered_method=True)
|
|
||||||
|
|
||||||
|
|
||||||
class InvoicesServicer(object):
|
|
||||||
"""
|
|
||||||
Comments in this file will be directly parsed into the API
|
|
||||||
Documentation as descriptions of the associated method, message, or field.
|
|
||||||
These descriptions should go right above the definition of the object, and
|
|
||||||
can be in either block or // comment format.
|
|
||||||
|
|
||||||
An RPC method can be matched to an lncli command by placing a line in the
|
|
||||||
beginning of the description in exactly the following format:
|
|
||||||
lncli: `methodname`
|
|
||||||
|
|
||||||
Failure to specify the exact name of the command will cause documentation
|
|
||||||
generation to fail.
|
|
||||||
|
|
||||||
More information on how exactly the gRPC documentation is generated from
|
|
||||||
this proto file can be found here:
|
|
||||||
https://github.com/lightninglabs/lightning-api
|
|
||||||
|
|
||||||
Invoices is a service that can be used to create, accept, settle and cancel
|
|
||||||
invoices.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def SubscribeSingleInvoice(self, request, context):
|
|
||||||
"""
|
|
||||||
SubscribeSingleInvoice returns a uni-directional stream (server -> client)
|
|
||||||
to notify the client of state transitions of the specified invoice.
|
|
||||||
Initially the current invoice state is always sent out.
|
|
||||||
"""
|
|
||||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
||||||
context.set_details('Method not implemented!')
|
|
||||||
raise NotImplementedError('Method not implemented!')
|
|
||||||
|
|
||||||
def CancelInvoice(self, request, context):
|
|
||||||
"""lncli: `cancelinvoice`
|
|
||||||
CancelInvoice cancels a currently open invoice. If the invoice is already
|
|
||||||
canceled, this call will succeed. If the invoice is already settled, it will
|
|
||||||
fail.
|
|
||||||
"""
|
|
||||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
||||||
context.set_details('Method not implemented!')
|
|
||||||
raise NotImplementedError('Method not implemented!')
|
|
||||||
|
|
||||||
def AddHoldInvoice(self, request, context):
|
|
||||||
"""lncli: `addholdinvoice`
|
|
||||||
AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
|
|
||||||
supplied in the request.
|
|
||||||
"""
|
|
||||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
||||||
context.set_details('Method not implemented!')
|
|
||||||
raise NotImplementedError('Method not implemented!')
|
|
||||||
|
|
||||||
def SettleInvoice(self, request, context):
|
|
||||||
"""lncli: `settleinvoice`
|
|
||||||
SettleInvoice settles an accepted invoice. If the invoice is already
|
|
||||||
settled, this call will succeed.
|
|
||||||
"""
|
|
||||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
||||||
context.set_details('Method not implemented!')
|
|
||||||
raise NotImplementedError('Method not implemented!')
|
|
||||||
|
|
||||||
def LookupInvoiceV2(self, request, context):
|
|
||||||
"""
|
|
||||||
LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced
|
|
||||||
using either its payment hash, payment address, or set ID.
|
|
||||||
"""
|
|
||||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
||||||
context.set_details('Method not implemented!')
|
|
||||||
raise NotImplementedError('Method not implemented!')
|
|
||||||
|
|
||||||
def HtlcModifier(self, request_iterator, context):
|
|
||||||
"""
|
|
||||||
HtlcModifier is a bidirectional streaming RPC that allows a client to
|
|
||||||
intercept and modify the HTLCs that attempt to settle the given invoice. The
|
|
||||||
server will send HTLCs of invoices to the client and the client can modify
|
|
||||||
some aspects of the HTLC in order to pass the invoice acceptance tests.
|
|
||||||
"""
|
|
||||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
||||||
context.set_details('Method not implemented!')
|
|
||||||
raise NotImplementedError('Method not implemented!')
|
|
||||||
|
|
||||||
|
|
||||||
def add_InvoicesServicer_to_server(servicer, server):
|
|
||||||
rpc_method_handlers = {
|
|
||||||
'SubscribeSingleInvoice': grpc.unary_stream_rpc_method_handler(
|
|
||||||
servicer.SubscribeSingleInvoice,
|
|
||||||
request_deserializer=invoices__pb2.SubscribeSingleInvoiceRequest.FromString,
|
|
||||||
response_serializer=lightning__pb2.Invoice.SerializeToString,
|
|
||||||
),
|
|
||||||
'CancelInvoice': grpc.unary_unary_rpc_method_handler(
|
|
||||||
servicer.CancelInvoice,
|
|
||||||
request_deserializer=invoices__pb2.CancelInvoiceMsg.FromString,
|
|
||||||
response_serializer=invoices__pb2.CancelInvoiceResp.SerializeToString,
|
|
||||||
),
|
|
||||||
'AddHoldInvoice': grpc.unary_unary_rpc_method_handler(
|
|
||||||
servicer.AddHoldInvoice,
|
|
||||||
request_deserializer=invoices__pb2.AddHoldInvoiceRequest.FromString,
|
|
||||||
response_serializer=invoices__pb2.AddHoldInvoiceResp.SerializeToString,
|
|
||||||
),
|
|
||||||
'SettleInvoice': grpc.unary_unary_rpc_method_handler(
|
|
||||||
servicer.SettleInvoice,
|
|
||||||
request_deserializer=invoices__pb2.SettleInvoiceMsg.FromString,
|
|
||||||
response_serializer=invoices__pb2.SettleInvoiceResp.SerializeToString,
|
|
||||||
),
|
|
||||||
'LookupInvoiceV2': grpc.unary_unary_rpc_method_handler(
|
|
||||||
servicer.LookupInvoiceV2,
|
|
||||||
request_deserializer=invoices__pb2.LookupInvoiceMsg.FromString,
|
|
||||||
response_serializer=lightning__pb2.Invoice.SerializeToString,
|
|
||||||
),
|
|
||||||
'HtlcModifier': grpc.stream_stream_rpc_method_handler(
|
|
||||||
servicer.HtlcModifier,
|
|
||||||
request_deserializer=invoices__pb2.HtlcModifyResponse.FromString,
|
|
||||||
response_serializer=invoices__pb2.HtlcModifyRequest.SerializeToString,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
generic_handler = grpc.method_handlers_generic_handler(
|
|
||||||
'invoicesrpc.Invoices', rpc_method_handlers)
|
|
||||||
server.add_generic_rpc_handlers((generic_handler,))
|
|
||||||
server.add_registered_method_handlers('invoicesrpc.Invoices', rpc_method_handlers)
|
|
||||||
|
|
||||||
|
|
||||||
# This class is part of an EXPERIMENTAL API.
|
|
||||||
class Invoices(object):
|
|
||||||
"""
|
|
||||||
Comments in this file will be directly parsed into the API
|
|
||||||
Documentation as descriptions of the associated method, message, or field.
|
|
||||||
These descriptions should go right above the definition of the object, and
|
|
||||||
can be in either block or // comment format.
|
|
||||||
|
|
||||||
An RPC method can be matched to an lncli command by placing a line in the
|
|
||||||
beginning of the description in exactly the following format:
|
|
||||||
lncli: `methodname`
|
|
||||||
|
|
||||||
Failure to specify the exact name of the command will cause documentation
|
|
||||||
generation to fail.
|
|
||||||
|
|
||||||
More information on how exactly the gRPC documentation is generated from
|
|
||||||
this proto file can be found here:
|
|
||||||
https://github.com/lightninglabs/lightning-api
|
|
||||||
|
|
||||||
Invoices is a service that can be used to create, accept, settle and cancel
|
|
||||||
invoices.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def SubscribeSingleInvoice(request,
|
|
||||||
target,
|
|
||||||
options=(),
|
|
||||||
channel_credentials=None,
|
|
||||||
call_credentials=None,
|
|
||||||
insecure=False,
|
|
||||||
compression=None,
|
|
||||||
wait_for_ready=None,
|
|
||||||
timeout=None,
|
|
||||||
metadata=None):
|
|
||||||
return grpc.experimental.unary_stream(
|
|
||||||
request,
|
|
||||||
target,
|
|
||||||
'/invoicesrpc.Invoices/SubscribeSingleInvoice',
|
|
||||||
invoices__pb2.SubscribeSingleInvoiceRequest.SerializeToString,
|
|
||||||
lightning__pb2.Invoice.FromString,
|
|
||||||
options,
|
|
||||||
channel_credentials,
|
|
||||||
insecure,
|
|
||||||
call_credentials,
|
|
||||||
compression,
|
|
||||||
wait_for_ready,
|
|
||||||
timeout,
|
|
||||||
metadata,
|
|
||||||
_registered_method=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def CancelInvoice(request,
|
|
||||||
target,
|
|
||||||
options=(),
|
|
||||||
channel_credentials=None,
|
|
||||||
call_credentials=None,
|
|
||||||
insecure=False,
|
|
||||||
compression=None,
|
|
||||||
wait_for_ready=None,
|
|
||||||
timeout=None,
|
|
||||||
metadata=None):
|
|
||||||
return grpc.experimental.unary_unary(
|
|
||||||
request,
|
|
||||||
target,
|
|
||||||
'/invoicesrpc.Invoices/CancelInvoice',
|
|
||||||
invoices__pb2.CancelInvoiceMsg.SerializeToString,
|
|
||||||
invoices__pb2.CancelInvoiceResp.FromString,
|
|
||||||
options,
|
|
||||||
channel_credentials,
|
|
||||||
insecure,
|
|
||||||
call_credentials,
|
|
||||||
compression,
|
|
||||||
wait_for_ready,
|
|
||||||
timeout,
|
|
||||||
metadata,
|
|
||||||
_registered_method=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def AddHoldInvoice(request,
|
|
||||||
target,
|
|
||||||
options=(),
|
|
||||||
channel_credentials=None,
|
|
||||||
call_credentials=None,
|
|
||||||
insecure=False,
|
|
||||||
compression=None,
|
|
||||||
wait_for_ready=None,
|
|
||||||
timeout=None,
|
|
||||||
metadata=None):
|
|
||||||
return grpc.experimental.unary_unary(
|
|
||||||
request,
|
|
||||||
target,
|
|
||||||
'/invoicesrpc.Invoices/AddHoldInvoice',
|
|
||||||
invoices__pb2.AddHoldInvoiceRequest.SerializeToString,
|
|
||||||
invoices__pb2.AddHoldInvoiceResp.FromString,
|
|
||||||
options,
|
|
||||||
channel_credentials,
|
|
||||||
insecure,
|
|
||||||
call_credentials,
|
|
||||||
compression,
|
|
||||||
wait_for_ready,
|
|
||||||
timeout,
|
|
||||||
metadata,
|
|
||||||
_registered_method=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def SettleInvoice(request,
|
|
||||||
target,
|
|
||||||
options=(),
|
|
||||||
channel_credentials=None,
|
|
||||||
call_credentials=None,
|
|
||||||
insecure=False,
|
|
||||||
compression=None,
|
|
||||||
wait_for_ready=None,
|
|
||||||
timeout=None,
|
|
||||||
metadata=None):
|
|
||||||
return grpc.experimental.unary_unary(
|
|
||||||
request,
|
|
||||||
target,
|
|
||||||
'/invoicesrpc.Invoices/SettleInvoice',
|
|
||||||
invoices__pb2.SettleInvoiceMsg.SerializeToString,
|
|
||||||
invoices__pb2.SettleInvoiceResp.FromString,
|
|
||||||
options,
|
|
||||||
channel_credentials,
|
|
||||||
insecure,
|
|
||||||
call_credentials,
|
|
||||||
compression,
|
|
||||||
wait_for_ready,
|
|
||||||
timeout,
|
|
||||||
metadata,
|
|
||||||
_registered_method=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def LookupInvoiceV2(request,
|
|
||||||
target,
|
|
||||||
options=(),
|
|
||||||
channel_credentials=None,
|
|
||||||
call_credentials=None,
|
|
||||||
insecure=False,
|
|
||||||
compression=None,
|
|
||||||
wait_for_ready=None,
|
|
||||||
timeout=None,
|
|
||||||
metadata=None):
|
|
||||||
return grpc.experimental.unary_unary(
|
|
||||||
request,
|
|
||||||
target,
|
|
||||||
'/invoicesrpc.Invoices/LookupInvoiceV2',
|
|
||||||
invoices__pb2.LookupInvoiceMsg.SerializeToString,
|
|
||||||
lightning__pb2.Invoice.FromString,
|
|
||||||
options,
|
|
||||||
channel_credentials,
|
|
||||||
insecure,
|
|
||||||
call_credentials,
|
|
||||||
compression,
|
|
||||||
wait_for_ready,
|
|
||||||
timeout,
|
|
||||||
metadata,
|
|
||||||
_registered_method=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def HtlcModifier(request_iterator,
|
|
||||||
target,
|
|
||||||
options=(),
|
|
||||||
channel_credentials=None,
|
|
||||||
call_credentials=None,
|
|
||||||
insecure=False,
|
|
||||||
compression=None,
|
|
||||||
wait_for_ready=None,
|
|
||||||
timeout=None,
|
|
||||||
metadata=None):
|
|
||||||
return grpc.experimental.stream_stream(
|
|
||||||
request_iterator,
|
|
||||||
target,
|
|
||||||
'/invoicesrpc.Invoices/HtlcModifier',
|
|
||||||
invoices__pb2.HtlcModifyResponse.SerializeToString,
|
|
||||||
invoices__pb2.HtlcModifyRequest.FromString,
|
|
||||||
options,
|
|
||||||
channel_credentials,
|
|
||||||
insecure,
|
|
||||||
call_credentials,
|
|
||||||
compression,
|
|
||||||
wait_for_ready,
|
|
||||||
timeout,
|
|
||||||
metadata,
|
|
||||||
_registered_method=True)
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -8,18 +8,15 @@ from typing import Optional
|
|||||||
import grpc
|
import grpc
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
import lnbits.wallets.lnd_grpc_files.invoices_pb2 as invoices
|
|
||||||
import lnbits.wallets.lnd_grpc_files.invoices_pb2_grpc as invoicesrpc
|
|
||||||
import lnbits.wallets.lnd_grpc_files.lightning_pb2 as ln
|
import lnbits.wallets.lnd_grpc_files.lightning_pb2 as ln
|
||||||
import lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc as lnrpc
|
import lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc as lnrpc
|
||||||
import lnbits.wallets.lnd_grpc_files.router_pb2 as router
|
import lnbits.wallets.lnd_grpc_files.router_pb2 as router
|
||||||
|
import lnbits.wallets.lnd_grpc_files.router_pb2_grpc as routerrpc
|
||||||
from lnbits.helpers import normalize_endpoint
|
from lnbits.helpers import normalize_endpoint
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.utils.crypto import random_secret_and_hash
|
from lnbits.utils.crypto import random_secret_and_hash
|
||||||
from lnbits.wallets.lnd_grpc_files.router_pb2_grpc import RouterStub
|
|
||||||
|
|
||||||
from .base import (
|
from .base import (
|
||||||
Feature,
|
|
||||||
InvoiceResponse,
|
InvoiceResponse,
|
||||||
PaymentFailedStatus,
|
PaymentFailedStatus,
|
||||||
PaymentPendingStatus,
|
PaymentPendingStatus,
|
||||||
@@ -65,8 +62,6 @@ environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
|
|||||||
|
|
||||||
|
|
||||||
class LndWallet(Wallet):
|
class LndWallet(Wallet):
|
||||||
features = [Feature.holdinvoice]
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if not settings.lnd_grpc_endpoint:
|
if not settings.lnd_grpc_endpoint:
|
||||||
raise ValueError("cannot initialize LndWallet: missing lnd_grpc_endpoint")
|
raise ValueError("cannot initialize LndWallet: missing lnd_grpc_endpoint")
|
||||||
@@ -103,8 +98,7 @@ class LndWallet(Wallet):
|
|||||||
f"{self.endpoint}:{self.port}", composite_creds
|
f"{self.endpoint}:{self.port}", composite_creds
|
||||||
)
|
)
|
||||||
self.rpc = lnrpc.LightningStub(channel)
|
self.rpc = lnrpc.LightningStub(channel)
|
||||||
self.routerpc = RouterStub(channel)
|
self.routerpc = routerrpc.RouterStub(channel)
|
||||||
self.invoicesrpc = invoicesrpc.InvoicesStub(channel)
|
|
||||||
|
|
||||||
def metadata_callback(self, _, callback):
|
def metadata_callback(self, _, callback):
|
||||||
callback([("macaroon", self.macaroon)], None)
|
callback([("macaroon", self.macaroon)], None)
|
||||||
@@ -114,7 +108,7 @@ class LndWallet(Wallet):
|
|||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
try:
|
try:
|
||||||
resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest()) # type: ignore
|
resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return StatusResponse(f"Unable to connect, got: '{exc}'", 0)
|
return StatusResponse(f"Unable to connect, got: '{exc}'", 0)
|
||||||
|
|
||||||
@@ -150,7 +144,7 @@ class LndWallet(Wallet):
|
|||||||
data["r_hash"] = bytes.fromhex(payment_hash)
|
data["r_hash"] = bytes.fromhex(payment_hash)
|
||||||
data["r_preimage"] = bytes.fromhex(preimage)
|
data["r_preimage"] = bytes.fromhex(preimage)
|
||||||
try:
|
try:
|
||||||
req = ln.Invoice(**data) # type: ignore
|
req = ln.Invoice(**data)
|
||||||
resp = await self.rpc.AddInvoice(req)
|
resp = await self.rpc.AddInvoice(req)
|
||||||
# response model
|
# response model
|
||||||
# {
|
# {
|
||||||
@@ -174,7 +168,7 @@ class LndWallet(Wallet):
|
|||||||
|
|
||||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||||
# fee_limit_fixed = ln.FeeLimit(fixed=fee_limit_msat // 1000)
|
# fee_limit_fixed = ln.FeeLimit(fixed=fee_limit_msat // 1000)
|
||||||
req = router.SendPaymentRequest( # type: ignore
|
req = router.SendPaymentRequest(
|
||||||
payment_request=bolt11,
|
payment_request=bolt11,
|
||||||
fee_limit_msat=fee_limit_msat,
|
fee_limit_msat=fee_limit_msat,
|
||||||
timeout_seconds=30,
|
timeout_seconds=30,
|
||||||
@@ -233,7 +227,7 @@ class LndWallet(Wallet):
|
|||||||
# that use different checking_id formats
|
# that use different checking_id formats
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
resp = await self.rpc.LookupInvoice(ln.PaymentHash(r_hash=r_hash)) # type: ignore
|
resp = await self.rpc.LookupInvoice(ln.PaymentHash(r_hash=r_hash))
|
||||||
if resp.settled:
|
if resp.settled:
|
||||||
return PaymentSuccessStatus(preimage=resp.r_preimage.hex())
|
return PaymentSuccessStatus(preimage=resp.r_preimage.hex())
|
||||||
|
|
||||||
@@ -277,7 +271,7 @@ class LndWallet(Wallet):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
resp = self.routerpc.TrackPaymentV2(
|
resp = self.routerpc.TrackPaymentV2(
|
||||||
router.TrackPaymentRequest(payment_hash=r_hash) # type: ignore
|
router.TrackPaymentRequest(payment_hash=r_hash)
|
||||||
)
|
)
|
||||||
async for payment in resp:
|
async for payment in resp:
|
||||||
if len(payment.htlcs) and statuses[payment.status]:
|
if len(payment.htlcs) and statuses[payment.status]:
|
||||||
@@ -294,7 +288,7 @@ class LndWallet(Wallet):
|
|||||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||||
while settings.lnbits_running:
|
while settings.lnbits_running:
|
||||||
try:
|
try:
|
||||||
request = ln.InvoiceSubscription() # type: ignore
|
request = ln.InvoiceSubscription()
|
||||||
async for i in self.rpc.SubscribeInvoices(request):
|
async for i in self.rpc.SubscribeInvoices(request):
|
||||||
if not i.settled:
|
if not i.settled:
|
||||||
continue
|
continue
|
||||||
@@ -307,65 +301,3 @@ class LndWallet(Wallet):
|
|||||||
"retrying in 5 seconds"
|
"retrying in 5 seconds"
|
||||||
)
|
)
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
async def create_hold_invoice(
|
|
||||||
self,
|
|
||||||
amount: int,
|
|
||||||
payment_hash: str,
|
|
||||||
memo: Optional[str] = None,
|
|
||||||
description_hash: Optional[bytes] = None,
|
|
||||||
unhashed_description: Optional[bytes] = None,
|
|
||||||
**kwargs,
|
|
||||||
) -> InvoiceResponse:
|
|
||||||
data: dict = {
|
|
||||||
"description_hash": b"",
|
|
||||||
"value": amount,
|
|
||||||
"hash": hex_to_bytes(payment_hash),
|
|
||||||
"private": True,
|
|
||||||
"memo": memo or "",
|
|
||||||
}
|
|
||||||
if kwargs.get("expiry"):
|
|
||||||
data["expiry"] = kwargs["expiry"]
|
|
||||||
if description_hash:
|
|
||||||
data["description_hash"] = description_hash
|
|
||||||
elif unhashed_description:
|
|
||||||
data["description_hash"] = sha256(unhashed_description).digest()
|
|
||||||
try:
|
|
||||||
req = invoices.AddHoldInvoiceRequest(**data) # type: ignore
|
|
||||||
res = await self.invoicesrpc.AddHoldInvoice(req)
|
|
||||||
logger.debug(f"AddHoldInvoice response: {res}")
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(exc)
|
|
||||||
error_message = str(exc)
|
|
||||||
return InvoiceResponse(ok=False, error_message=error_message)
|
|
||||||
return InvoiceResponse(
|
|
||||||
ok=True, checking_id=payment_hash, payment_request=str(res.payment_request)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def settle_hold_invoice(self, preimage: str) -> InvoiceResponse:
|
|
||||||
try:
|
|
||||||
req = invoices.SettleInvoiceMsg(preimage=hex_to_bytes(preimage)) # type: ignore
|
|
||||||
await self.invoicesrpc.SettleInvoice(req)
|
|
||||||
except grpc.aio.AioRpcError as exc:
|
|
||||||
return InvoiceResponse(
|
|
||||||
ok=False, error_message=exc.details() or "unknown grpc exception"
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(exc)
|
|
||||||
return InvoiceResponse(ok=False, error_message=str(exc))
|
|
||||||
return InvoiceResponse(ok=True, preimage=preimage)
|
|
||||||
|
|
||||||
async def cancel_hold_invoice(self, payment_hash: str) -> InvoiceResponse:
|
|
||||||
try:
|
|
||||||
req = invoices.CancelInvoiceMsg(payment_hash=hex_to_bytes(payment_hash)) # type: ignore
|
|
||||||
res = await self.invoicesrpc.CancelInvoice(req)
|
|
||||||
logger.debug(f"CancelInvoice response: {res}")
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(exc)
|
|
||||||
# If we cannot cancel the invoice, we return an error message
|
|
||||||
# and True for ok that should be ignored by the service
|
|
||||||
return InvoiceResponse(
|
|
||||||
ok=False, checking_id=payment_hash, error_message=str(exc)
|
|
||||||
)
|
|
||||||
# If we reach here, the invoice was successfully canceled and payment failed
|
|
||||||
return InvoiceResponse(True, checking_id=payment_hash)
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from lnbits.settings import settings
|
|||||||
from lnbits.utils.crypto import random_secret_and_hash
|
from lnbits.utils.crypto import random_secret_and_hash
|
||||||
|
|
||||||
from .base import (
|
from .base import (
|
||||||
Feature,
|
|
||||||
InvoiceResponse,
|
InvoiceResponse,
|
||||||
PaymentFailedStatus,
|
PaymentFailedStatus,
|
||||||
PaymentPendingStatus,
|
PaymentPendingStatus,
|
||||||
@@ -28,10 +27,9 @@ from .macaroon import load_macaroon
|
|||||||
|
|
||||||
|
|
||||||
class LndRestWallet(Wallet):
|
class LndRestWallet(Wallet):
|
||||||
"""https://api.lightning.community/#lnd-rest-api-reference"""
|
"""https://api.lightning.community/rest/index.html#lnd-rest-api-reference"""
|
||||||
|
|
||||||
__node_cls__ = LndRestNode
|
__node_cls__ = LndRestNode
|
||||||
features = [Feature.nodemanager, Feature.holdinvoice]
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if not settings.lnd_rest_endpoint:
|
if not settings.lnd_rest_endpoint:
|
||||||
@@ -322,77 +320,3 @@ class LndRestWallet(Wallet):
|
|||||||
" seconds"
|
" seconds"
|
||||||
)
|
)
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
async def create_hold_invoice(
|
|
||||||
self,
|
|
||||||
amount: int,
|
|
||||||
payment_hash: str,
|
|
||||||
memo: Optional[str] = None,
|
|
||||||
description_hash: Optional[bytes] = None,
|
|
||||||
unhashed_description: Optional[bytes] = None,
|
|
||||||
**_,
|
|
||||||
) -> InvoiceResponse:
|
|
||||||
data: dict = {
|
|
||||||
"value": amount,
|
|
||||||
"private": True,
|
|
||||||
"hash": base64.b64encode(bytes.fromhex(payment_hash)).decode("ascii"),
|
|
||||||
}
|
|
||||||
if description_hash:
|
|
||||||
data["description_hash"] = base64.b64encode(description_hash).decode(
|
|
||||||
"ascii"
|
|
||||||
)
|
|
||||||
elif unhashed_description:
|
|
||||||
data["description_hash"] = base64.b64encode(
|
|
||||||
hashlib.sha256(unhashed_description).digest()
|
|
||||||
).decode("ascii")
|
|
||||||
else:
|
|
||||||
data["memo"] = memo or ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = await self.client.post(url="/v2/invoices/hodl", json=data)
|
|
||||||
r.raise_for_status()
|
|
||||||
data = r.json()
|
|
||||||
except httpx.HTTPStatusError as exc:
|
|
||||||
logger.warning(exc)
|
|
||||||
return InvoiceResponse(ok=False, error_message=exc.response.text)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(exc)
|
|
||||||
return InvoiceResponse(ok=False, error_message=str(exc))
|
|
||||||
|
|
||||||
payment_request = data["payment_request"]
|
|
||||||
payment_hash = base64.b64encode(bytes.fromhex(payment_hash)).decode("ascii")
|
|
||||||
|
|
||||||
return InvoiceResponse(
|
|
||||||
ok=True, checking_id=payment_hash, payment_request=payment_request
|
|
||||||
)
|
|
||||||
|
|
||||||
async def settle_hold_invoice(self, preimage: str) -> InvoiceResponse:
|
|
||||||
data: dict = {
|
|
||||||
"preimage": base64.b64encode(bytes.fromhex(preimage)).decode("ascii")
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
r = await self.client.post(url="/v2/invoices/settle", json=data)
|
|
||||||
r.raise_for_status()
|
|
||||||
return InvoiceResponse(ok=True, preimage=preimage)
|
|
||||||
except httpx.HTTPStatusError as exc:
|
|
||||||
logger.warning(exc)
|
|
||||||
return InvoiceResponse(ok=False, error_message=exc.response.text)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(exc)
|
|
||||||
return InvoiceResponse(ok=False, error_message=str(exc))
|
|
||||||
|
|
||||||
async def cancel_hold_invoice(self, payment_hash: str) -> InvoiceResponse:
|
|
||||||
rhash = bytes.fromhex(payment_hash)
|
|
||||||
try:
|
|
||||||
r = await self.client.post(
|
|
||||||
url="/v2/invoices/cancel",
|
|
||||||
json={"payment_hash": base64.b64encode(rhash).decode("ascii")},
|
|
||||||
)
|
|
||||||
r.raise_for_status()
|
|
||||||
logger.debug(f"Cancel hold invoice response: {r.text}")
|
|
||||||
return InvoiceResponse(ok=True, checking_id=payment_hash)
|
|
||||||
except httpx.HTTPStatusError as exc:
|
|
||||||
return InvoiceResponse(ok=False, error_message=exc.response.text)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(exc)
|
|
||||||
return InvoiceResponse(ok=False, error_message=str(exc))
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from urllib.parse import parse_qs, unquote, urlparse
|
|||||||
import secp256k1
|
import secp256k1
|
||||||
from bolt11 import decode as bolt11_decode
|
from bolt11 import decode as bolt11_decode
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from websockets import connect as ws_connect
|
from websockets.legacy.client import connect as ws_connect
|
||||||
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.utils.nostr import (
|
from lnbits.utils.nostr import (
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from typing import Any, Optional
|
|||||||
import httpx
|
import httpx
|
||||||
from httpx import RequestError, TimeoutException
|
from httpx import RequestError, TimeoutException
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from websockets import connect
|
from websockets.legacy.client import connect
|
||||||
|
|
||||||
from lnbits.helpers import normalize_endpoint
|
from lnbits.helpers import normalize_endpoint
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
@@ -300,9 +300,7 @@ class PhoenixdWallet(Wallet):
|
|||||||
try:
|
try:
|
||||||
async with connect(
|
async with connect(
|
||||||
self.ws_url,
|
self.ws_url,
|
||||||
additional_headers=[
|
extra_headers=[("Authorization", self.headers["Authorization"])],
|
||||||
("Authorization", self.headers["Authorization"])
|
|
||||||
],
|
|
||||||
) as ws:
|
) as ws:
|
||||||
logger.info("connected to phoenixd invoices stream")
|
logger.info("connected to phoenixd invoices stream")
|
||||||
while settings.lnbits_running:
|
while settings.lnbits_running:
|
||||||
|
|||||||
Generated
+21
-202
@@ -15,7 +15,7 @@
|
|||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"underscore": "^1.13.7",
|
"underscore": "^1.13.7",
|
||||||
"vue": "3.5.8",
|
"vue": "3.5.8",
|
||||||
"vue-i18n": "^10.0.8",
|
"vue-i18n": "^10.0.6",
|
||||||
"vue-qrcode-reader": "^5.5.10",
|
"vue-qrcode-reader": "^5.5.10",
|
||||||
"vue-router": "4.4.5",
|
"vue-router": "4.4.5",
|
||||||
"vuex": "4.1.0"
|
"vuex": "4.1.0"
|
||||||
@@ -72,13 +72,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@intlify/core-base": {
|
"node_modules/@intlify/core-base": {
|
||||||
"version": "10.0.8",
|
"version": "10.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-10.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-10.0.6.tgz",
|
||||||
"integrity": "sha512-FoHslNWSoHjdUBLy35bpm9PV/0LVI/DSv9L6Km6J2ad8r/mm0VaGg06C40FqlE8u2ADcGUM60lyoU7Myo4WNZQ==",
|
"integrity": "sha512-/NINGvy7t8qSCyyuqMIPmHS6CBQjqPIPVOps0Rb7xWrwwkwHJKtahiFnW1HC4iQVhzoYwEW6Js0923zTScLDiA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intlify/message-compiler": "10.0.8",
|
"@intlify/message-compiler": "10.0.6",
|
||||||
"@intlify/shared": "10.0.8"
|
"@intlify/shared": "10.0.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
@@ -88,12 +88,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@intlify/message-compiler": {
|
"node_modules/@intlify/message-compiler": {
|
||||||
"version": "10.0.8",
|
"version": "10.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.6.tgz",
|
||||||
"integrity": "sha512-DV+sYXIkHVd5yVb2mL7br/NEUwzUoLBsMkV3H0InefWgmYa34NLZUvMCGi5oWX+Hqr2Y2qUxnVrnOWF4aBlgWg==",
|
"integrity": "sha512-QcUYprK+e4X2lU6eJDxLuf/mUtCuVPj2RFBoFRlJJxK3wskBejzlRvh1Q0lQCi9tDOnD4iUK1ftcGylE3X3idA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intlify/shared": "10.0.8",
|
"@intlify/shared": "10.0.6",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -104,9 +104,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@intlify/shared": {
|
"node_modules/@intlify/shared": {
|
||||||
"version": "10.0.8",
|
"version": "10.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.6.tgz",
|
||||||
"integrity": "sha512-BcmHpb5bQyeVNrptC3UhzpBZB/YHHDoEREOUERrmF2BRxsyOEuRrq+Z96C/D4+2KJb8kuHiouzAei7BXlG0YYw==",
|
"integrity": "sha512-2xqwm05YPpo7TM//+v0bzS0FWiTzsjpSMnWdt7ZXs5/ZfQIedSuBXIrskd8HZ7c/cZzo1G9ALHTksnv/74vk/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
@@ -448,19 +448,6 @@
|
|||||||
"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",
|
||||||
@@ -602,20 +589,6 @@
|
|||||||
"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",
|
||||||
@@ -627,51 +600,6 @@
|
|||||||
"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",
|
||||||
@@ -709,15 +637,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
"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": {
|
||||||
@@ -738,52 +663,6 @@
|
|||||||
"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",
|
||||||
@@ -796,57 +675,6 @@
|
|||||||
"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",
|
||||||
@@ -948,15 +776,6 @@
|
|||||||
"@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",
|
||||||
@@ -1486,13 +1305,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue-i18n": {
|
"node_modules/vue-i18n": {
|
||||||
"version": "10.0.8",
|
"version": "10.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.6.tgz",
|
||||||
"integrity": "sha512-mIjy4utxMz9lMMo6G9vYePv7gUFt4ztOMhY9/4czDJxZ26xPeJ49MAGa9wBAE3XuXbYCrtVPmPxNjej7JJJkZQ==",
|
"integrity": "sha512-pQPspK5H4srzlu+47+HEY2tmiY3GyYIvSPgSBdQaYVWv7t1zj1t9p1FvHlxBXyJ17t9stG/Vxj+pykrvPWBLeQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intlify/core-base": "10.0.8",
|
"@intlify/core-base": "10.0.6",
|
||||||
"@intlify/shared": "10.0.8",
|
"@intlify/shared": "10.0.6",
|
||||||
"@vue/devtools-api": "^6.5.0"
|
"@vue/devtools-api": "^6.5.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@
|
|||||||
"nostr-tools": "^2.7.2",
|
"nostr-tools": "^2.7.2",
|
||||||
"underscore": "^1.13.7",
|
"underscore": "^1.13.7",
|
||||||
"vue": "3.5.8",
|
"vue": "3.5.8",
|
||||||
"vue-i18n": "^10.0.8",
|
"vue-i18n": "^10.0.6",
|
||||||
"vue-qrcode-reader": "^5.5.10",
|
"vue-qrcode-reader": "^5.5.10",
|
||||||
"vue-router": "4.4.5",
|
"vue-router": "4.4.5",
|
||||||
"vuex": "4.1.0"
|
"vuex": "4.1.0"
|
||||||
|
|||||||
Generated
+83
-156
@@ -378,71 +378,6 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"]
|
tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bcrypt"
|
|
||||||
version = "4.3.0"
|
|
||||||
description = "Modern password hashing for your software and your servers"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.8"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4"},
|
|
||||||
{file = "bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51"},
|
|
||||||
{file = "bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a"},
|
|
||||||
{file = "bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b"},
|
|
||||||
{file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1"},
|
|
||||||
{file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d"},
|
|
||||||
{file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492"},
|
|
||||||
{file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90"},
|
|
||||||
{file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a"},
|
|
||||||
{file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce"},
|
|
||||||
{file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8"},
|
|
||||||
{file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938"},
|
|
||||||
{file = "bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
|
||||||
typecheck = ["mypy"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bech32"
|
name = "bech32"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -455,21 +390,6 @@ files = [
|
|||||||
{file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"},
|
{file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bip32"
|
|
||||||
version = "4.0"
|
|
||||||
description = "Minimalistic implementation of the BIP32 key derivation scheme"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
groups = ["main"]
|
|
||||||
files = [
|
|
||||||
{file = "bip32-4.0-py3-none-any.whl", hash = "sha256:9728b38336129c00e1f870bbb3e328c9632d51c1bddeef4011fd3115cb3aeff9"},
|
|
||||||
{file = "bip32-4.0.tar.gz", hash = "sha256:8035588f252f569bb414bc60df151ae431fc1c6789a19488a32890532ef3a2fc"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
coincurve = ">=15.0,<21"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitarray"
|
name = "bitarray"
|
||||||
version = "3.4.3"
|
version = "3.4.3"
|
||||||
@@ -1371,25 +1291,24 @@ test = ["pytest (>=6)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
version = "0.116.1"
|
version = "0.115.13"
|
||||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565"},
|
{file = "fastapi-0.115.13-py3-none-any.whl", hash = "sha256:0a0cab59afa7bab22f5eb347f8c9864b681558c278395e94035a741fc10cd865"},
|
||||||
{file = "fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143"},
|
{file = "fastapi-0.115.13.tar.gz", hash = "sha256:55d1d25c2e1e0a0a50aceb1c8705cd932def273c102bff0b1c1da88b3c6eb307"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||||
starlette = ">=0.40.0,<0.48.0"
|
starlette = ">=0.40.0,<0.47.0"
|
||||||
typing-extensions = ">=4.8.0"
|
typing-extensions = ">=4.8.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi-sso"
|
name = "fastapi-sso"
|
||||||
@@ -2133,23 +2052,21 @@ valkey = ["valkey (>=6)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lnurl"
|
name = "lnurl"
|
||||||
version = "0.7.2"
|
version = "0.5.3"
|
||||||
description = "LNURL implementation for Python."
|
description = "LNURL implementation for Python."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = "<4.0,>=3.9"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "lnurl-0.7.2-py3-none-any.whl", hash = "sha256:4323ac398d49e5b883000c166b4c2448df2acad2eddca99f656c67f7b97cd80f"},
|
{file = "lnurl-0.5.3-py3-none-any.whl", hash = "sha256:feaf6c60b0b7f104894ef3accbd30d23d52e038c2797c58432baea7f4a8aa952"},
|
||||||
{file = "lnurl-0.7.2.tar.gz", hash = "sha256:9f0881cb5909512cadb35d238c27347d6a8afcec10b2b7aadd560083fd210c0b"},
|
{file = "lnurl-0.5.3.tar.gz", hash = "sha256:60154bcfdbb98fb143eeca970a16d73a582f28e057a826b5f222259411c497fe"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
bech32 = "*"
|
bech32 = ">=1.2.0,<2.0.0"
|
||||||
bip32 = ">=4.0,<5.0"
|
bolt11 = ">=2.0.5,<3.0.0"
|
||||||
bolt11 = "*"
|
ecdsa = ">=0.19.0,<0.20.0"
|
||||||
ecdsa = "*"
|
httpx = ">=0.27.0,<0.28.0"
|
||||||
httpx = "*"
|
|
||||||
pycryptodomex = ">=3.21.0,<4.0.0"
|
|
||||||
pydantic = ">=1,<2"
|
pydantic = ">=1,<2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2622,6 +2539,24 @@ files = [
|
|||||||
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
|
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "passlib"
|
||||||
|
version = "1.7.4"
|
||||||
|
description = "comprehensive password hashing framework supporting over 30 schemes"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
||||||
|
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
argon2 = ["argon2-cffi (>=18.2.0)"]
|
||||||
|
bcrypt = ["bcrypt (>=3.1.0)"]
|
||||||
|
build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
|
||||||
|
totp = ["cryptography"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathable"
|
name = "pathable"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@@ -2998,62 +2933,55 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "1.10.22"
|
version = "1.10.18"
|
||||||
description = "Data validation and settings management using python type hints"
|
description = "Data validation and settings management using python type hints"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
groups = ["main", "dev"]
|
groups = ["main", "dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pydantic-1.10.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:57889565ccc1e5b7b73343329bbe6198ebc472e3ee874af2fa1865cfe7048228"},
|
{file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"},
|
||||||
{file = "pydantic-1.10.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90729e22426de79bc6a3526b4c45ec4400caf0d4f10d7181ba7f12c01bb3897d"},
|
{file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"},
|
||||||
{file = "pydantic-1.10.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8684d347f351554ec94fdcb507983d3116dc4577fb8799fed63c65869a2d10"},
|
{file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"},
|
||||||
{file = "pydantic-1.10.22-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8dad498ceff2d9ef1d2e2bc6608f5b59b8e1ba2031759b22dfb8c16608e1802"},
|
{file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"},
|
||||||
{file = "pydantic-1.10.22-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fac529cc654d4575cf8de191cce354b12ba705f528a0a5c654de6d01f76cd818"},
|
{file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"},
|
||||||
{file = "pydantic-1.10.22-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4148232aded8dd1dd13cf910a01b32a763c34bd79a0ab4d1ee66164fcb0b7b9d"},
|
{file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"},
|
||||||
{file = "pydantic-1.10.22-cp310-cp310-win_amd64.whl", hash = "sha256:ece68105d9e436db45d8650dc375c760cc85a6793ae019c08769052902dca7db"},
|
{file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"},
|
||||||
{file = "pydantic-1.10.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e530a8da353f791ad89e701c35787418605d35085f4bdda51b416946070e938"},
|
{file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"},
|
||||||
{file = "pydantic-1.10.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:654322b85642e9439d7de4c83cb4084ddd513df7ff8706005dada43b34544946"},
|
{file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"},
|
||||||
{file = "pydantic-1.10.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8bece75bd1b9fc1c32b57a32831517943b1159ba18b4ba32c0d431d76a120ae"},
|
{file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"},
|
||||||
{file = "pydantic-1.10.22-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eccb58767f13c6963dcf96d02cb8723ebb98b16692030803ac075d2439c07b0f"},
|
{file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"},
|
||||||
{file = "pydantic-1.10.22-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7778e6200ff8ed5f7052c1516617423d22517ad36cc7a3aedd51428168e3e5e8"},
|
{file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"},
|
||||||
{file = "pydantic-1.10.22-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffe02767d27c39af9ca7dc7cd479c00dda6346bb62ffc89e306f665108317a2"},
|
{file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"},
|
||||||
{file = "pydantic-1.10.22-cp311-cp311-win_amd64.whl", hash = "sha256:23bc19c55427091b8e589bc08f635ab90005f2dc99518f1233386f46462c550a"},
|
{file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"},
|
||||||
{file = "pydantic-1.10.22-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:92d0f97828a075a71d9efc65cf75db5f149b4d79a38c89648a63d2932894d8c9"},
|
{file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"},
|
||||||
{file = "pydantic-1.10.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af5a2811b6b95b58b829aeac5996d465a5f0c7ed84bd871d603cf8646edf6ff"},
|
{file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"},
|
||||||
{file = "pydantic-1.10.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cf06d8d40993e79af0ab2102ef5da77b9ddba51248e4cb27f9f3f591fbb096e"},
|
{file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"},
|
||||||
{file = "pydantic-1.10.22-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:184b7865b171a6057ad97f4a17fbac81cec29bd103e996e7add3d16b0d95f609"},
|
{file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"},
|
||||||
{file = "pydantic-1.10.22-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:923ad861677ab09d89be35d36111156063a7ebb44322cdb7b49266e1adaba4bb"},
|
{file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"},
|
||||||
{file = "pydantic-1.10.22-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:82d9a3da1686443fb854c8d2ab9a473251f8f4cdd11b125522efb4d7c646e7bc"},
|
{file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"},
|
||||||
{file = "pydantic-1.10.22-cp312-cp312-win_amd64.whl", hash = "sha256:1612604929af4c602694a7f3338b18039d402eb5ddfbf0db44f1ebfaf07f93e7"},
|
{file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"},
|
||||||
{file = "pydantic-1.10.22-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b259dc89c9abcd24bf42f31951fb46c62e904ccf4316393f317abeeecda39978"},
|
{file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"},
|
||||||
{file = "pydantic-1.10.22-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9238aa0964d80c0908d2f385e981add58faead4412ca80ef0fa352094c24e46d"},
|
{file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"},
|
||||||
{file = "pydantic-1.10.22-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f8029f05b04080e3f1a550575a1bca747c0ea4be48e2d551473d47fd768fc1b"},
|
{file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"},
|
||||||
{file = "pydantic-1.10.22-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5c06918894f119e0431a36c9393bc7cceeb34d1feeb66670ef9b9ca48c073937"},
|
{file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"},
|
||||||
{file = "pydantic-1.10.22-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e205311649622ee8fc1ec9089bd2076823797f5cd2c1e3182dc0e12aab835b35"},
|
{file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"},
|
||||||
{file = "pydantic-1.10.22-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:815f0a73d5688d6dd0796a7edb9eca7071bfef961a7b33f91e618822ae7345b7"},
|
{file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"},
|
||||||
{file = "pydantic-1.10.22-cp313-cp313-win_amd64.whl", hash = "sha256:9dfce71d42a5cde10e78a469e3d986f656afc245ab1b97c7106036f088dd91f8"},
|
{file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"},
|
||||||
{file = "pydantic-1.10.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3ecaf8177b06aac5d1f442db1288e3b46d9f05f34fd17fdca3ad34105328b61a"},
|
{file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"},
|
||||||
{file = "pydantic-1.10.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb36c2de9ea74bd7f66b5481dea8032d399affd1cbfbb9bb7ce539437f1fce62"},
|
{file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"},
|
||||||
{file = "pydantic-1.10.22-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6b8d14a256be3b8fff9286d76c532f1a7573fbba5f189305b22471c6679854d"},
|
{file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"},
|
||||||
{file = "pydantic-1.10.22-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:1c33269e815db4324e71577174c29c7aa30d1bba51340ce6be976f6f3053a4c6"},
|
{file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"},
|
||||||
{file = "pydantic-1.10.22-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:8661b3ab2735b2a9ccca2634738534a795f4a10bae3ab28ec0a10c96baa20182"},
|
{file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"},
|
||||||
{file = "pydantic-1.10.22-cp37-cp37m-win_amd64.whl", hash = "sha256:22bdd5fe70d4549995981c55b970f59de5c502d5656b2abdfcd0a25be6f3763e"},
|
{file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"},
|
||||||
{file = "pydantic-1.10.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e3f33d1358aa4bc2795208cc29ff3118aeaad0ea36f0946788cf7cadeccc166b"},
|
{file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"},
|
||||||
{file = "pydantic-1.10.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:813f079f9cd136cac621f3f9128a4406eb8abd2ad9fdf916a0731d91c6590017"},
|
{file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"},
|
||||||
{file = "pydantic-1.10.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab618ab8dca6eac7f0755db25f6aba3c22c40e3463f85a1c08dc93092d917704"},
|
{file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"},
|
||||||
{file = "pydantic-1.10.22-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d128e1aaa38db88caca920d5822c98fc06516a09a58b6d3d60fa5ea9099b32cc"},
|
{file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"},
|
||||||
{file = "pydantic-1.10.22-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:cc97bbc25def7025e55fc9016080773167cda2aad7294e06a37dda04c7d69ece"},
|
{file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"},
|
||||||
{file = "pydantic-1.10.22-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dda5d7157d543b1fa565038cae6e952549d0f90071c839b3740fb77c820fab8"},
|
{file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"},
|
||||||
{file = "pydantic-1.10.22-cp38-cp38-win_amd64.whl", hash = "sha256:a093fe44fe518cb445d23119511a71f756f8503139d02fcdd1173f7b76c95ffe"},
|
{file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"},
|
||||||
{file = "pydantic-1.10.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec54c89b2568b258bb30d7348ac4d82bec1b58b377fb56a00441e2ac66b24587"},
|
{file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"},
|
||||||
{file = "pydantic-1.10.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8f1d1a1532e4f3bcab4e34e8d2197a7def4b67072acd26cfa60e92d75803a48"},
|
{file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"},
|
||||||
{file = "pydantic-1.10.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ad83ca35508c27eae1005b6b61f369f78aae6d27ead2135ec156a2599910121"},
|
|
||||||
{file = "pydantic-1.10.22-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53cdb44b78c420f570ff16b071ea8cd5a477635c6b0efc343c8a91e3029bbf1a"},
|
|
||||||
{file = "pydantic-1.10.22-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:16d0a5ae9d98264186ce31acdd7686ec05fd331fab9d68ed777d5cb2d1514e5e"},
|
|
||||||
{file = "pydantic-1.10.22-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8aee040e25843f036192b1a1af62117504a209a043aa8db12e190bb86ad7e611"},
|
|
||||||
{file = "pydantic-1.10.22-cp39-cp39-win_amd64.whl", hash = "sha256:7f691eec68dbbfca497d3c11b92a3e5987393174cbedf03ec7a4184c35c2def6"},
|
|
||||||
{file = "pydantic-1.10.22-py3-none-any.whl", hash = "sha256:343037d608bcbd34df937ac259708bfc83664dadf88afe8516c4f282d7d471a9"},
|
|
||||||
{file = "pydantic-1.10.22.tar.gz", hash = "sha256:ee1006cebd43a8e7158fb7190bb8f4e2da9649719bff65d0c287282ec38dec6d"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -3888,22 +3816,21 @@ uvicorn = ["uvicorn (>=0.34.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "starlette"
|
name = "starlette"
|
||||||
version = "0.47.1"
|
version = "0.40.0"
|
||||||
description = "The little ASGI library that shines."
|
description = "The little ASGI library that shines."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.8"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527"},
|
{file = "starlette-0.40.0-py3-none-any.whl", hash = "sha256:c494a22fae73805376ea6bf88439783ecfba9aac88a43911b48c653437e784c4"},
|
||||||
{file = "starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b"},
|
{file = "starlette-0.40.0.tar.gz", hash = "sha256:1a3139688fb298ce5e2d661d37046a66ad996ce94be4d4983be019a23a04ea35"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
anyio = ">=3.6.2,<5"
|
anyio = ">=3.4.0,<5"
|
||||||
typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""}
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
|
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tlv8"
|
name = "tlv8"
|
||||||
@@ -4573,4 +4500,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 = "265fb5ec81a3c703ca7db07dfe206194fbb8616cb6e196d0917f33e6df58753c"
|
||||||
|
|||||||
+5
-6
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.3.0-rc3"
|
version = "1.2.0"
|
||||||
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"
|
||||||
@@ -16,12 +16,11 @@ python = "~3.12 | ~3.11 | ~3.10"
|
|||||||
bech32 = "1.2.0"
|
bech32 = "1.2.0"
|
||||||
click = "8.2.1"
|
click = "8.2.1"
|
||||||
ecdsa = "0.19.1"
|
ecdsa = "0.19.1"
|
||||||
fastapi = "0.116.1"
|
fastapi = "0.115.13"
|
||||||
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.5.3"
|
||||||
pydantic = "1.10.22"
|
pydantic = "1.10.18"
|
||||||
pyqrcode = "1.2.1"
|
pyqrcode = "1.2.1"
|
||||||
shortuuid = "1.0.13"
|
shortuuid = "1.0.13"
|
||||||
sse-starlette = "2.3.6"
|
sse-starlette = "2.3.6"
|
||||||
@@ -43,6 +42,7 @@ pycryptodomex = "3.23.0"
|
|||||||
packaging = "25.0"
|
packaging = "25.0"
|
||||||
bolt11 = "2.1.1"
|
bolt11 = "2.1.1"
|
||||||
pyjwt = "2.10.1"
|
pyjwt = "2.10.1"
|
||||||
|
passlib = "1.7.4"
|
||||||
itsdangerous = "2.2.0"
|
itsdangerous = "2.2.0"
|
||||||
fastapi-sso = "0.18.0"
|
fastapi-sso = "0.18.0"
|
||||||
# needed for boltz, lnurldevice, watchonly extensions
|
# needed for boltz, lnurldevice, watchonly extensions
|
||||||
@@ -66,7 +66,6 @@ pynostr = "^0.6.2"
|
|||||||
python-multipart = "^0.0.20"
|
python-multipart = "^0.0.20"
|
||||||
filetype = "^1.2.0"
|
filetype = "^1.2.0"
|
||||||
nostr-sdk = "^0.42.1"
|
nostr-sdk = "^0.42.1"
|
||||||
bcrypt = "^4.3.0"
|
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
breez = ["breez-sdk", "breez-sdk-liquid"]
|
breez = ["breez-sdk", "breez-sdk-liquid"]
|
||||||
|
|||||||
+31
-67
@@ -1,6 +1,5 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from json import JSONDecodeError
|
|
||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -588,14 +587,13 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
"tag": "withdrawRequest",
|
"tag": "withdrawRequest",
|
||||||
"callback": "https://example.com/callback",
|
"callback": "https://example.com/callback",
|
||||||
"k1": "randomk1value",
|
"k1": "randomk1value",
|
||||||
"minWithdrawable": 1000,
|
|
||||||
"maxWithdrawable": 1_500_000,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "OK",
|
"success": True,
|
||||||
|
"detail": {"status": "OK"},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Error loading LNURL request
|
# Error loading LNURL request
|
||||||
@@ -603,34 +601,33 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
"error_loading_lnurl",
|
"error_loading_lnurl",
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"success": False,
|
||||||
"reason": "Error loading callback request",
|
"detail": "Error loading LNURL request",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# LNURL response with error status
|
# LNURL response with error status
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"status": "ERROR",
|
||||||
|
"reason": "LNURL request failed",
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"success": False,
|
||||||
"reason": "Invalid LNURL-withdraw response.",
|
"detail": "LNURL request failed",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Invalid LNURL-withdraw pay request
|
# Invalid LNURL-withdraw
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
"tag": "payRequest",
|
"tag": "payRequest",
|
||||||
"callback": "https://example.com/callback",
|
"callback": "https://example.com/callback",
|
||||||
"minSendable": 1000,
|
"k1": "randomk1value",
|
||||||
"maxSendable": 1_500_000,
|
|
||||||
"metadata": '[["text/plain", "Payment to yo"]]',
|
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"success": False,
|
||||||
"reason": "Invalid LNURL-withdraw response.",
|
"detail": "Invalid LNURL-withdraw",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Error loading callback request
|
# Error loading callback request
|
||||||
@@ -639,13 +636,11 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
"tag": "withdrawRequest",
|
"tag": "withdrawRequest",
|
||||||
"callback": "https://example.com/callback",
|
"callback": "https://example.com/callback",
|
||||||
"k1": "randomk1value",
|
"k1": "randomk1value",
|
||||||
"minWithdrawable": 1000,
|
|
||||||
"maxWithdrawable": 1_500_000,
|
|
||||||
},
|
},
|
||||||
"error_loading_callback",
|
"error_loading_callback",
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"success": False,
|
||||||
"reason": "Error loading callback request",
|
"detail": "Error loading callback request",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Callback response with error status
|
# Callback response with error status
|
||||||
@@ -654,16 +649,14 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
"tag": "withdrawRequest",
|
"tag": "withdrawRequest",
|
||||||
"callback": "https://example.com/callback",
|
"callback": "https://example.com/callback",
|
||||||
"k1": "randomk1value",
|
"k1": "randomk1value",
|
||||||
"minWithdrawable": 1000,
|
|
||||||
"maxWithdrawable": 1_500_000,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"status": "ERROR",
|
||||||
"reason": "Callback failed",
|
"reason": "Callback failed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"success": False,
|
||||||
"reason": "Callback failed",
|
"detail": "Callback failed",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Unexpected exception during LNURL response JSON parsing
|
# Unexpected exception during LNURL response JSON parsing
|
||||||
@@ -671,8 +664,8 @@ async def test_fiat_tracking(client, adminkey_headers_from, settings: Settings):
|
|||||||
"exception_in_lnurl_response_json",
|
"exception_in_lnurl_response_json",
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"status": "ERROR",
|
"success": False,
|
||||||
"reason": "Invalid JSON response from https://example.com/lnurl",
|
"detail": "Unexpected error: Simulated exception",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -684,35 +677,25 @@ async def test_api_payment_pay_with_nfc(
|
|||||||
callback_response_data,
|
callback_response_data,
|
||||||
expected_response,
|
expected_response,
|
||||||
):
|
):
|
||||||
payment_request = (
|
payment_request = "lnbc1..."
|
||||||
"lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdq"
|
|
||||||
"svfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfu"
|
|
||||||
"vqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0"
|
|
||||||
"rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs"
|
|
||||||
)
|
|
||||||
lnurl = "lnurlw://example.com/lnurl"
|
lnurl = "lnurlw://example.com/lnurl"
|
||||||
|
lnurl_data = {"lnurl_w": lnurl}
|
||||||
|
|
||||||
# Create a mock for httpx.AsyncClient
|
# Create a mock for httpx.AsyncClient
|
||||||
mock_async_client = AsyncMock()
|
mock_async_client = AsyncMock()
|
||||||
mock_async_client.__aenter__.return_value = mock_async_client
|
mock_async_client.__aenter__.return_value = mock_async_client
|
||||||
|
|
||||||
# Mock the get method
|
# Mock the get method
|
||||||
async def mock_get(url, *_, **__):
|
async def mock_get(url, *args, **kwargs):
|
||||||
if url == "https://example.com/lnurl":
|
if url == "https://example.com/lnurl":
|
||||||
if lnurl_response_data == "error_loading_lnurl":
|
if lnurl_response_data == "error_loading_lnurl":
|
||||||
response = Mock()
|
response = Mock()
|
||||||
response.is_error = True
|
response.is_error = True
|
||||||
response.status_code = 500
|
|
||||||
response.raise_for_status.side_effect = Exception(
|
|
||||||
"Error loading callback request"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
elif lnurl_response_data == "exception_in_lnurl_response_json":
|
elif lnurl_response_data == "exception_in_lnurl_response_json":
|
||||||
response = Mock()
|
response = Mock()
|
||||||
response.is_error = False
|
response.is_error = False
|
||||||
response.json.side_effect = JSONDecodeError(
|
response.json.side_effect = Exception("Simulated exception")
|
||||||
doc="Simulated exception", pos=0, msg="JSONDecodeError"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
elif isinstance(lnurl_response_data, dict):
|
elif isinstance(lnurl_response_data, dict):
|
||||||
response = Mock()
|
response = Mock()
|
||||||
@@ -723,19 +706,11 @@ async def test_api_payment_pay_with_nfc(
|
|||||||
# Handle unexpected data
|
# Handle unexpected data
|
||||||
response = Mock()
|
response = Mock()
|
||||||
response.is_error = True
|
response.is_error = True
|
||||||
response.status_code = 500
|
|
||||||
response.raise_for_status.side_effect = Exception(
|
|
||||||
"Error loading callback request"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
elif url == "https://example.com/callback":
|
elif url == "https://example.com/callback":
|
||||||
if callback_response_data == "error_loading_callback":
|
if callback_response_data == "error_loading_callback":
|
||||||
response = Mock()
|
response = Mock()
|
||||||
response.is_error = True
|
response.is_error = True
|
||||||
response.status_code = 500
|
|
||||||
response.raise_for_status.side_effect = Exception(
|
|
||||||
"Error loading callback request"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
elif isinstance(callback_response_data, dict):
|
elif isinstance(callback_response_data, dict):
|
||||||
response = Mock()
|
response = Mock()
|
||||||
@@ -746,16 +721,10 @@ async def test_api_payment_pay_with_nfc(
|
|||||||
# Handle cases where callback is not called
|
# Handle cases where callback is not called
|
||||||
response = Mock()
|
response = Mock()
|
||||||
response.is_error = True
|
response.is_error = True
|
||||||
response.raise_for_status.side_effect = Exception(
|
|
||||||
"Error loading callback request"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
response = Mock()
|
response = Mock()
|
||||||
response.is_error = True
|
response.is_error = True
|
||||||
response.raise_for_status.side_effect = Exception(
|
|
||||||
"Error loading callback request"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
mock_async_client.get.side_effect = mock_get
|
mock_async_client.get.side_effect = mock_get
|
||||||
@@ -765,7 +734,7 @@ async def test_api_payment_pay_with_nfc(
|
|||||||
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
f"/api/v1/payments/{payment_request}/pay-with-nfc",
|
f"/api/v1/payments/{payment_request}/pay-with-nfc",
|
||||||
json={"lnurl_w": lnurl},
|
json=lnurl_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.OK
|
assert response.status_code == HTTPStatus.OK
|
||||||
@@ -774,32 +743,27 @@ async def test_api_payment_pay_with_nfc(
|
|||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_api_payments_pay_lnurl(client, adminkey_headers_from):
|
async def test_api_payments_pay_lnurl(client, adminkey_headers_from):
|
||||||
lnurl_data = {
|
valid_lnurl_data = {
|
||||||
"res": {
|
"description_hash": "randomhash",
|
||||||
"callback": "https://xxxxxxx.lnbits.com",
|
"callback": "https://xxxxxxx.lnbits.com",
|
||||||
"minSendable": 1000,
|
|
||||||
"maxSendable": 1_500_000,
|
|
||||||
"metadata": '[["text/plain", "Payment to yo"]]',
|
|
||||||
},
|
|
||||||
"amount": 1000,
|
"amount": 1000,
|
||||||
"unit": "sat",
|
"unit": "sat",
|
||||||
"comment": "test comment",
|
"comment": "test comment",
|
||||||
"description": "test description",
|
"description": "test description",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invalid_lnurl_data = {**valid_lnurl_data, "callback": "invalid_url"}
|
||||||
|
|
||||||
# Test with valid callback URL
|
# Test with valid callback URL
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
"/api/v1/payments/lnurl", json=lnurl_data, headers=adminkey_headers_from
|
"/api/v1/payments/lnurl", json=valid_lnurl_data, headers=adminkey_headers_from
|
||||||
)
|
)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert (
|
assert response.json()["detail"] == "Failed to connect to xxxxxxx.lnbits.com."
|
||||||
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"
|
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
"/api/v1/payments/lnurl", json=lnurl_data, headers=adminkey_headers_from
|
"/api/v1/payments/lnurl", json=invalid_lnurl_data, headers=adminkey_headers_from
|
||||||
)
|
)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "value_error.url.scheme" in response.json()["detail"]
|
assert "Callback not allowed." in response.json()["detail"]
|
||||||
|
|||||||
@@ -1,118 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
|
||||||
from lnbits.core.services.payments import (
|
|
||||||
cancel_hold_invoice,
|
|
||||||
create_invoice,
|
|
||||||
get_standalone_payment,
|
|
||||||
settle_hold_invoice,
|
|
||||||
)
|
|
||||||
from lnbits.exceptions import InvoiceError
|
|
||||||
from lnbits.utils.crypto import random_secret_and_hash
|
|
||||||
|
|
||||||
from ..helpers import funding_source, is_fake
|
|
||||||
from .helpers import pay_real_invoice
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
funding_source.__class__.__name__ in ["LndRestWallet", "LndWallet"],
|
|
||||||
reason="this should not raise for lnd",
|
|
||||||
)
|
|
||||||
async def test_pay_raise_unsupported(app):
|
|
||||||
payment_hash = "0" * 32
|
|
||||||
payment = Payment(
|
|
||||||
checking_id=payment_hash,
|
|
||||||
amount=1000,
|
|
||||||
wallet_id="fake_wallet_id",
|
|
||||||
bolt11="fake_holdinvoice",
|
|
||||||
payment_hash=payment_hash,
|
|
||||||
fee=0,
|
|
||||||
)
|
|
||||||
with pytest.raises(InvoiceError):
|
|
||||||
await create_invoice(
|
|
||||||
wallet_id="fake_wallet_id",
|
|
||||||
amount=1000,
|
|
||||||
memo="fake_holdinvoice",
|
|
||||||
payment_hash=payment_hash,
|
|
||||||
)
|
|
||||||
with pytest.raises(InvoiceError):
|
|
||||||
await settle_hold_invoice(payment, payment_hash)
|
|
||||||
with pytest.raises(InvoiceError):
|
|
||||||
await cancel_hold_invoice(payment)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
funding_source.__class__.__name__ not in ["LndRestWallet", "LndWallet"],
|
|
||||||
reason="this only works for lndrest",
|
|
||||||
)
|
|
||||||
async def test_cancel_real_hold_invoice(app, from_wallet):
|
|
||||||
|
|
||||||
_, payment_hash = random_secret_and_hash()
|
|
||||||
payment = await create_invoice(
|
|
||||||
wallet_id=from_wallet.id,
|
|
||||||
amount=1000,
|
|
||||||
memo="test_cancel_holdinvoice",
|
|
||||||
payment_hash=payment_hash,
|
|
||||||
)
|
|
||||||
assert payment.amount == 1000 * 1000
|
|
||||||
assert payment.memo == "test_cancel_holdinvoice"
|
|
||||||
assert payment.status == "pending"
|
|
||||||
assert payment.wallet_id == from_wallet.id
|
|
||||||
|
|
||||||
payment = await cancel_hold_invoice(payment=payment)
|
|
||||||
assert payment.ok is True
|
|
||||||
|
|
||||||
updated_payment = await get_standalone_payment(payment_hash, incoming=True)
|
|
||||||
|
|
||||||
assert updated_payment
|
|
||||||
assert updated_payment.status == "failed"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
funding_source.__class__.__name__ not in ["LndRestWallet", "LndWallet"],
|
|
||||||
reason="this only works for lndrest",
|
|
||||||
)
|
|
||||||
async def test_settle_real_hold_invoice(app, from_wallet):
|
|
||||||
|
|
||||||
preimage, payment_hash = random_secret_and_hash()
|
|
||||||
payment = await create_invoice(
|
|
||||||
wallet_id=from_wallet.id,
|
|
||||||
amount=1000,
|
|
||||||
memo="test_settle_holdinvoice",
|
|
||||||
payment_hash=payment_hash,
|
|
||||||
)
|
|
||||||
assert payment.amount == 1000 * 1000
|
|
||||||
assert payment.memo == "test_settle_holdinvoice"
|
|
||||||
assert payment.status == "pending"
|
|
||||||
assert payment.wallet_id == from_wallet.id
|
|
||||||
|
|
||||||
# invoice should still be open
|
|
||||||
with pytest.raises(InvoiceError):
|
|
||||||
await settle_hold_invoice(payment=payment, preimage=preimage)
|
|
||||||
|
|
||||||
def pay_invoice():
|
|
||||||
pay_real_invoice(payment.bolt11)
|
|
||||||
|
|
||||||
async def settle():
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
invoice_response = await settle_hold_invoice(payment=payment, preimage=preimage)
|
|
||||||
assert invoice_response.ok is True
|
|
||||||
|
|
||||||
coro = asyncio.to_thread(pay_invoice)
|
|
||||||
task = asyncio.create_task(coro)
|
|
||||||
await asyncio.gather(task, settle())
|
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
updated_payment = await get_standalone_payment(payment_hash, incoming=True)
|
|
||||||
|
|
||||||
assert updated_payment
|
|
||||||
assert updated_payment.status == "success"
|
|
||||||
@@ -9,8 +9,7 @@ import secp256k1
|
|||||||
from Cryptodome import Random
|
from Cryptodome import Random
|
||||||
from Cryptodome.Cipher import AES
|
from Cryptodome.Cipher import AES
|
||||||
from Cryptodome.Util.Padding import pad, unpad
|
from Cryptodome.Util.Padding import pad, unpad
|
||||||
from websockets import ServerConnection
|
from websockets.legacy.server import serve as ws_serve
|
||||||
from websockets import serve as ws_serve
|
|
||||||
|
|
||||||
from lnbits.wallets.nwc import NWCWallet
|
from lnbits.wallets.nwc import NWCWallet
|
||||||
from tests.wallets.helpers import (
|
from tests.wallets.helpers import (
|
||||||
@@ -75,9 +74,7 @@ def sign_event(pub_key, priv_key, event):
|
|||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
async def handle( # noqa: C901
|
async def handle(wallet, mock_settings, data, websocket, path): # noqa: C901
|
||||||
wallet, mock_settings, data, websocket: ServerConnection
|
|
||||||
):
|
|
||||||
async for message in websocket:
|
async for message in websocket:
|
||||||
if not wallet:
|
if not wallet:
|
||||||
continue
|
continue
|
||||||
@@ -165,8 +162,8 @@ async def run(data: WalletTest):
|
|||||||
if mock_settings is None:
|
if mock_settings is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
def handler(websocket):
|
async def handler(websocket, path):
|
||||||
return handle(wallet, mock_settings, data, websocket)
|
return await handle(wallet, mock_settings, data, websocket, path)
|
||||||
|
|
||||||
if mock_settings is not None:
|
if mock_settings is not None:
|
||||||
async with ws_serve(handler, "localhost", mock_settings["port"]) as server:
|
async with ws_serve(handler, "localhost", mock_settings["port"]) as server:
|
||||||
|
|||||||
Reference in New Issue
Block a user