chore: remove lnurl wallet and withdraw feature (#2293)
* chore: remove lnurl wallet and withdraw feature this feature is undocumented and the code is very outdated. i don't think it is worth to keep. looking at the `/lnurlwallet` endpoint for example, it creates a new user and wallet without even checking if the creation of users is allowed * remove lnurl callback --------- Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
This commit is contained in:
parent
55eb3be5d5
commit
25661ddff5
7 changed files with 10 additions and 270 deletions
|
|
@ -2,7 +2,6 @@ import datetime
|
||||||
import json
|
import json
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Any, Dict, List, Literal, Optional
|
from typing import Any, Dict, List, Literal, Optional
|
||||||
from urllib.parse import urlparse
|
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
import shortuuid
|
import shortuuid
|
||||||
|
|
@ -21,7 +20,6 @@ from lnbits.settings import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
BalanceCheck,
|
|
||||||
CreateUser,
|
CreateUser,
|
||||||
Payment,
|
Payment,
|
||||||
PaymentFilters,
|
PaymentFilters,
|
||||||
|
|
@ -1040,73 +1038,6 @@ async def mark_webhook_sent(payment_hash: str, status: int) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# balance_check
|
|
||||||
# -------------
|
|
||||||
|
|
||||||
|
|
||||||
async def save_balance_check(
|
|
||||||
wallet_id: str, url: str, conn: Optional[Connection] = None
|
|
||||||
):
|
|
||||||
domain = urlparse(url).netloc
|
|
||||||
|
|
||||||
await (conn or db).execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO balance_check (wallet, service, url) VALUES (?, ?, ?)
|
|
||||||
ON CONFLICT (wallet, service) DO UPDATE SET url = ?
|
|
||||||
""",
|
|
||||||
(wallet_id, domain, url, url),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_balance_check(
|
|
||||||
wallet_id: str, domain: str, conn: Optional[Connection] = None
|
|
||||||
) -> Optional[BalanceCheck]:
|
|
||||||
row = await (conn or db).fetchone(
|
|
||||||
"""
|
|
||||||
SELECT wallet, service, url
|
|
||||||
FROM balance_check
|
|
||||||
WHERE wallet = ? AND service = ?
|
|
||||||
""",
|
|
||||||
(wallet_id, domain),
|
|
||||||
)
|
|
||||||
return BalanceCheck.from_row(row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
async def get_balance_checks(conn: Optional[Connection] = None) -> List[BalanceCheck]:
|
|
||||||
rows = await (conn or db).fetchall("SELECT wallet, service, url FROM balance_check")
|
|
||||||
return [BalanceCheck.from_row(row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
# balance_notify
|
|
||||||
# --------------
|
|
||||||
|
|
||||||
|
|
||||||
async def save_balance_notify(
|
|
||||||
wallet_id: str, url: str, conn: Optional[Connection] = None
|
|
||||||
):
|
|
||||||
await (conn or db).execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO balance_notify (wallet, url) VALUES (?, ?)
|
|
||||||
ON CONFLICT (wallet) DO UPDATE SET url = ?
|
|
||||||
""",
|
|
||||||
(wallet_id, url, url),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_balance_notify(
|
|
||||||
wallet_id: str, conn: Optional[Connection] = None
|
|
||||||
) -> Optional[str]:
|
|
||||||
row = await (conn or db).fetchone(
|
|
||||||
"""
|
|
||||||
SELECT url
|
|
||||||
FROM balance_notify
|
|
||||||
WHERE wallet = ?
|
|
||||||
""",
|
|
||||||
(wallet_id,),
|
|
||||||
)
|
|
||||||
return row[0] if row else None
|
|
||||||
|
|
||||||
|
|
||||||
# admin
|
# admin
|
||||||
# --------
|
# --------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -331,16 +331,6 @@ class PaymentHistoryPoint(BaseModel):
|
||||||
balance: int
|
balance: int
|
||||||
|
|
||||||
|
|
||||||
class BalanceCheck(BaseModel):
|
|
||||||
wallet: str
|
|
||||||
service: str
|
|
||||||
url: str
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_row(cls, row: Row):
|
|
||||||
return cls(wallet=row["wallet"], service=row["service"], url=row["url"])
|
|
||||||
|
|
||||||
|
|
||||||
def _do_nothing(*_):
|
def _do_nothing(*_):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -394,8 +384,6 @@ class CreateInvoice(BaseModel):
|
||||||
description_hash: Optional[str] = None
|
description_hash: Optional[str] = None
|
||||||
unhashed_description: Optional[str] = None
|
unhashed_description: Optional[str] = None
|
||||||
expiry: Optional[int] = None
|
expiry: Optional[int] = None
|
||||||
lnurl_callback: Optional[str] = None
|
|
||||||
lnurl_balance_check: Optional[str] = None
|
|
||||||
extra: Optional[dict] = None
|
extra: Optional[dict] = None
|
||||||
webhook: Optional[str] = None
|
webhook: Optional[str] = None
|
||||||
bolt11: Optional[str] = None
|
bolt11: Optional[str] = None
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.crud import (
|
from lnbits.core.crud import (
|
||||||
get_balance_notify,
|
|
||||||
get_wallet,
|
get_wallet,
|
||||||
get_webpush_subscriptions_for_user,
|
get_webpush_subscriptions_for_user,
|
||||||
mark_webhook_sent,
|
mark_webhook_sent,
|
||||||
|
|
@ -76,8 +75,7 @@ async def watchdog_task():
|
||||||
|
|
||||||
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
|
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
|
||||||
"""
|
"""
|
||||||
This task dispatches events to all api_invoice_listeners,
|
This worker dispatches events to all extensions and dispatches webhooks.
|
||||||
webhooks, push notifications and balance notifications.
|
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
payment = await invoice_paid_queue.get()
|
payment = await invoice_paid_queue.get()
|
||||||
|
|
@ -91,28 +89,6 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
|
||||||
# dispatch webhook
|
# dispatch webhook
|
||||||
if payment.webhook and not payment.webhook_status:
|
if payment.webhook and not payment.webhook_status:
|
||||||
await dispatch_webhook(payment)
|
await dispatch_webhook(payment)
|
||||||
# dispatch balance_notify
|
|
||||||
url = await get_balance_notify(payment.wallet_id)
|
|
||||||
if url:
|
|
||||||
headers = {"User-Agent": settings.user_agent}
|
|
||||||
async with httpx.AsyncClient(headers=headers) as client:
|
|
||||||
try:
|
|
||||||
r = await client.post(url, timeout=4)
|
|
||||||
r.raise_for_status()
|
|
||||||
await mark_webhook_sent(payment.payment_hash, r.status_code)
|
|
||||||
except httpx.HTTPStatusError as exc:
|
|
||||||
status_code = exc.response.status_code
|
|
||||||
await mark_webhook_sent(payment.payment_hash, status_code)
|
|
||||||
logger.warning(
|
|
||||||
f"balance_notify returned a bad status_code: {status_code} "
|
|
||||||
f"while requesting {exc.request.url!r}."
|
|
||||||
)
|
|
||||||
logger.warning(exc)
|
|
||||||
except httpx.RequestError as exc:
|
|
||||||
await mark_webhook_sent(payment.payment_hash, -1)
|
|
||||||
logger.warning(f"Could not send balance_notify to {url}")
|
|
||||||
logger.warning(exc)
|
|
||||||
|
|
||||||
# dispatch push notification
|
# dispatch push notification
|
||||||
await send_payment_push_notification(payment)
|
await send_payment_push_notification(payment)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,35 @@
|
||||||
import asyncio
|
|
||||||
import sys
|
import sys
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Annotated, List, Optional, Union
|
from typing import Annotated, List, Optional, Union
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from fastapi import Cookie, Depends, Query, Request, status
|
from fastapi import Cookie, Depends, Query, Request
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse
|
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
|
||||||
from fastapi.routing import APIRouter
|
from fastapi.routing import APIRouter
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic.types import UUID4
|
from pydantic.types import UUID4
|
||||||
|
|
||||||
from lnbits.core.db import core_app_extra, db
|
from lnbits.core.db import core_app_extra
|
||||||
from lnbits.core.helpers import to_valid_user_id
|
from lnbits.core.helpers import to_valid_user_id
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
from lnbits.decorators import check_admin, check_user_exists
|
from lnbits.decorators import check_admin, check_user_exists
|
||||||
from lnbits.helpers import template_renderer, url_for
|
from lnbits.helpers import template_renderer
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_funding_source
|
from lnbits.wallets import get_funding_source
|
||||||
|
|
||||||
from ...extension_manager import InstallableExtension, get_valid_extensions
|
from ...extension_manager import InstallableExtension, get_valid_extensions
|
||||||
from ...utils.exchange_rates import allowed_currencies, currencies
|
from ...utils.exchange_rates import allowed_currencies, currencies
|
||||||
from ..crud import (
|
from ..crud import (
|
||||||
create_account,
|
|
||||||
create_wallet,
|
create_wallet,
|
||||||
get_balance_check,
|
|
||||||
get_dbversions,
|
get_dbversions,
|
||||||
get_inactive_extensions,
|
get_inactive_extensions,
|
||||||
get_installed_extensions,
|
get_installed_extensions,
|
||||||
get_user,
|
get_user,
|
||||||
save_balance_notify,
|
|
||||||
update_installed_extension_state,
|
update_installed_extension_state,
|
||||||
update_user_extension,
|
update_user_extension,
|
||||||
)
|
)
|
||||||
from ..services import pay_invoice, redeem_lnurl_withdraw
|
|
||||||
|
|
||||||
generic_router = APIRouter(
|
generic_router = APIRouter(
|
||||||
tags=["Core NON-API Website Routes"], include_in_schema=False
|
tags=["Core NON-API Website Routes"], include_in_schema=False
|
||||||
|
|
@ -243,115 +238,6 @@ async def account(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@generic_router.get("/withdraw", response_class=JSONResponse)
|
|
||||||
async def lnurl_full_withdraw(request: Request):
|
|
||||||
usr_param = request.query_params.get("usr")
|
|
||||||
if not usr_param:
|
|
||||||
return {"status": "ERROR", "reason": "usr parameter not provided."}
|
|
||||||
|
|
||||||
user = await get_user(usr_param)
|
|
||||||
if not user:
|
|
||||||
return {"status": "ERROR", "reason": "User does not exist."}
|
|
||||||
|
|
||||||
wal_param = request.query_params.get("wal")
|
|
||||||
if not wal_param:
|
|
||||||
return {"status": "ERROR", "reason": "wal parameter not provided."}
|
|
||||||
|
|
||||||
wallet = user.get_wallet(wal_param)
|
|
||||||
if not wallet:
|
|
||||||
return {"status": "ERROR", "reason": "Wallet does not exist."}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"tag": "withdrawRequest",
|
|
||||||
"callback": url_for("/withdraw/cb", external=True, usr=user.id, wal=wallet.id),
|
|
||||||
"k1": "0",
|
|
||||||
"minWithdrawable": 1000 if wallet.withdrawable_balance else 0,
|
|
||||||
"maxWithdrawable": wallet.withdrawable_balance,
|
|
||||||
"defaultDescription": (
|
|
||||||
f"{settings.lnbits_site_title} balance withdraw from {wallet.id[0:5]}"
|
|
||||||
),
|
|
||||||
"balanceCheck": url_for("/withdraw", external=True, usr=user.id, wal=wallet.id),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@generic_router.get("/withdraw/cb", response_class=JSONResponse)
|
|
||||||
async def lnurl_full_withdraw_callback(request: Request):
|
|
||||||
usr_param = request.query_params.get("usr")
|
|
||||||
if not usr_param:
|
|
||||||
return {"status": "ERROR", "reason": "usr parameter not provided."}
|
|
||||||
|
|
||||||
user = await get_user(usr_param)
|
|
||||||
if not user:
|
|
||||||
return {"status": "ERROR", "reason": "User does not exist."}
|
|
||||||
|
|
||||||
wal_param = request.query_params.get("wal")
|
|
||||||
if not wal_param:
|
|
||||||
return {"status": "ERROR", "reason": "wal parameter not provided."}
|
|
||||||
|
|
||||||
wallet = user.get_wallet(wal_param)
|
|
||||||
if not wallet:
|
|
||||||
return {"status": "ERROR", "reason": "Wallet does not exist."}
|
|
||||||
|
|
||||||
pr = request.query_params.get("pr")
|
|
||||||
if not pr:
|
|
||||||
return {"status": "ERROR", "reason": "payment_request not provided."}
|
|
||||||
|
|
||||||
async def pay():
|
|
||||||
try:
|
|
||||||
await pay_invoice(wallet_id=wallet.id, payment_request=pr)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
asyncio.create_task(pay())
|
|
||||||
|
|
||||||
balance_notify = request.query_params.get("balanceNotify")
|
|
||||||
if balance_notify:
|
|
||||||
await save_balance_notify(wallet.id, balance_notify)
|
|
||||||
|
|
||||||
return {"status": "OK"}
|
|
||||||
|
|
||||||
|
|
||||||
@generic_router.get("/withdraw/notify/{service}")
|
|
||||||
async def lnurl_balance_notify(request: Request, service: str):
|
|
||||||
wal_param = request.query_params.get("wal")
|
|
||||||
if not wal_param:
|
|
||||||
return {"status": "ERROR", "reason": "wal parameter not provided."}
|
|
||||||
|
|
||||||
bc = await get_balance_check(wal_param, service)
|
|
||||||
if bc:
|
|
||||||
await redeem_lnurl_withdraw(bc.wallet, bc.url)
|
|
||||||
|
|
||||||
|
|
||||||
@generic_router.get(
|
|
||||||
"/lnurlwallet", response_class=RedirectResponse, name="core.lnurlwallet"
|
|
||||||
)
|
|
||||||
async def lnurlwallet(request: Request):
|
|
||||||
async with db.connect() as conn:
|
|
||||||
account = await create_account(conn=conn)
|
|
||||||
user = await get_user(account.id, conn=conn)
|
|
||||||
assert user, "Newly created user not found."
|
|
||||||
wallet = await create_wallet(user_id=user.id, conn=conn)
|
|
||||||
|
|
||||||
lightning_param = request.query_params.get("lightning")
|
|
||||||
if not lightning_param:
|
|
||||||
return {"status": "ERROR", "reason": "lightning parameter not provided."}
|
|
||||||
|
|
||||||
asyncio.create_task(
|
|
||||||
redeem_lnurl_withdraw(
|
|
||||||
wallet.id,
|
|
||||||
lightning_param,
|
|
||||||
"LNbits initial funding: voucher redeem.",
|
|
||||||
{"tag": "lnurlwallet"},
|
|
||||||
5, # wait 5 seconds before sending the invoice to the service
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return RedirectResponse(
|
|
||||||
f"/wallet?usr={user.id}&wal={wallet.id}",
|
|
||||||
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@generic_router.get("/service-worker.js")
|
@generic_router.get("/service-worker.js")
|
||||||
async def service_worker(request: Request):
|
async def service_worker(request: Request):
|
||||||
return template_renderer().TemplateResponse(
|
return template_renderer().TemplateResponse(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import json
|
||||||
import uuid
|
import uuid
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -40,7 +40,7 @@ from lnbits.decorators import (
|
||||||
require_admin_key,
|
require_admin_key,
|
||||||
require_invoice_key,
|
require_invoice_key,
|
||||||
)
|
)
|
||||||
from lnbits.helpers import generate_filter_params_openapi, url_for
|
from lnbits.helpers import generate_filter_params_openapi
|
||||||
from lnbits.lnurl import decode as lnurl_decode
|
from lnbits.lnurl import decode as lnurl_decode
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
|
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
|
||||||
|
|
@ -52,7 +52,6 @@ from ..crud import (
|
||||||
get_payments_paginated,
|
get_payments_paginated,
|
||||||
get_standalone_payment,
|
get_standalone_payment,
|
||||||
get_wallet_for_key,
|
get_wallet_for_key,
|
||||||
save_balance_check,
|
|
||||||
update_pending_payments,
|
update_pending_payments,
|
||||||
)
|
)
|
||||||
from ..services import (
|
from ..services import (
|
||||||
|
|
@ -178,44 +177,11 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet):
|
||||||
|
|
||||||
invoice = bolt11.decode(payment_request)
|
invoice = bolt11.decode(payment_request)
|
||||||
|
|
||||||
lnurl_response: Union[None, bool, str] = None
|
|
||||||
if data.lnurl_callback:
|
|
||||||
if data.lnurl_balance_check is not None:
|
|
||||||
await save_balance_check(wallet.id, data.lnurl_balance_check)
|
|
||||||
|
|
||||||
headers = {"User-Agent": settings.user_agent}
|
|
||||||
async with httpx.AsyncClient(headers=headers) as client:
|
|
||||||
try:
|
|
||||||
r = await client.get(
|
|
||||||
data.lnurl_callback,
|
|
||||||
params={
|
|
||||||
"pr": payment_request,
|
|
||||||
"balanceNotify": url_for(
|
|
||||||
f"/withdraw/notify/{urlparse(data.lnurl_callback).netloc}",
|
|
||||||
external=True,
|
|
||||||
wal=wallet.id,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
if r.is_error:
|
|
||||||
lnurl_response = r.text
|
|
||||||
else:
|
|
||||||
resp = json.loads(r.text)
|
|
||||||
if resp["status"] != "OK":
|
|
||||||
lnurl_response = resp["reason"]
|
|
||||||
else:
|
|
||||||
lnurl_response = True
|
|
||||||
except (httpx.ConnectError, httpx.RequestError) as ex:
|
|
||||||
logger.error(ex)
|
|
||||||
lnurl_response = False
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"payment_hash": invoice.payment_hash,
|
"payment_hash": invoice.payment_hash,
|
||||||
"payment_request": payment_request,
|
"payment_request": payment_request,
|
||||||
# maintain backwards compatibility with API clients:
|
# maintain backwards compatibility with API clients:
|
||||||
"checking_id": checking_id,
|
"checking_id": checking_id,
|
||||||
"lnurl_response": lnurl_response,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -22,19 +22,12 @@ window.LNbits = {
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
createInvoice: async function (
|
createInvoice: async function (wallet, amount, memo, unit = 'sat') {
|
||||||
wallet,
|
|
||||||
amount,
|
|
||||||
memo,
|
|
||||||
unit = 'sat',
|
|
||||||
lnurlCallback = null
|
|
||||||
) {
|
|
||||||
return this.request('post', '/api/v1/payments', wallet.inkey, {
|
return this.request('post', '/api/v1/payments', wallet.inkey, {
|
||||||
out: false,
|
out: false,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
memo: memo,
|
memo: memo,
|
||||||
unit: unit,
|
unit: unit
|
||||||
lnurl_callback: lnurlCallback
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
payInvoice: function (wallet, bolt11) {
|
payInvoice: function (wallet, bolt11) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue