migrate to sqlalchemy-aio.
a big refactor that:
- fixes some issues that might have happened (or not) with asynchronous
reactions to payments;
- paves the way to https://github.com/lnbits/lnbits/issues/121;
- uses more async/await notation which just looks nice; and
- makes it simple(r?) for one extension to modify stuff from other extensions.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
from quart import Blueprint
|
||||
from lnbits.db import Database
|
||||
|
||||
db = Database("database")
|
||||
|
||||
core_app: Blueprint = Blueprint(
|
||||
"core", __name__, template_folder="templates", static_folder="static", static_url_path="/core/static"
|
||||
|
||||
+39
-39
@@ -2,11 +2,11 @@ import json
|
||||
import datetime
|
||||
from uuid import uuid4
|
||||
from typing import List, Optional, Dict
|
||||
from quart import g
|
||||
|
||||
from lnbits import bolt11
|
||||
from lnbits.settings import DEFAULT_WALLET_NAME
|
||||
|
||||
from . import db
|
||||
from .models import User, Wallet, Payment
|
||||
|
||||
|
||||
@@ -14,28 +14,28 @@ from .models import User, Wallet, Payment
|
||||
# --------
|
||||
|
||||
|
||||
def create_account() -> User:
|
||||
async def create_account() -> User:
|
||||
user_id = uuid4().hex
|
||||
g.db.execute("INSERT INTO accounts (id) VALUES (?)", (user_id,))
|
||||
await db.execute("INSERT INTO accounts (id) VALUES (?)", (user_id,))
|
||||
|
||||
new_account = get_account(user_id=user_id)
|
||||
new_account = await get_account(user_id=user_id)
|
||||
assert new_account, "Newly created account couldn't be retrieved"
|
||||
|
||||
return new_account
|
||||
|
||||
|
||||
def get_account(user_id: str) -> Optional[User]:
|
||||
row = g.db.fetchone("SELECT id, email, pass as password FROM accounts WHERE id = ?", (user_id,))
|
||||
async def get_account(user_id: str) -> Optional[User]:
|
||||
row = await db.fetchone("SELECT id, email, pass as password FROM accounts WHERE id = ?", (user_id,))
|
||||
|
||||
return User(**row) if row else None
|
||||
|
||||
|
||||
def get_user(user_id: str) -> Optional[User]:
|
||||
user = g.db.fetchone("SELECT id, email FROM accounts WHERE id = ?", (user_id,))
|
||||
async def get_user(user_id: str) -> Optional[User]:
|
||||
user = await db.fetchone("SELECT id, email FROM accounts WHERE id = ?", (user_id,))
|
||||
|
||||
if user:
|
||||
extensions = g.db.fetchall("SELECT extension FROM extensions WHERE user = ? AND active = 1", (user_id,))
|
||||
wallets = g.db.fetchall(
|
||||
extensions = await db.fetchall("SELECT extension FROM extensions WHERE user = ? AND active = 1", (user_id,))
|
||||
wallets = await db.fetchall(
|
||||
"""
|
||||
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
|
||||
FROM wallets
|
||||
@@ -51,8 +51,8 @@ def get_user(user_id: str) -> Optional[User]:
|
||||
)
|
||||
|
||||
|
||||
def update_user_extension(*, user_id: str, extension: str, active: int) -> None:
|
||||
g.db.execute(
|
||||
async def update_user_extension(*, user_id: str, extension: str, active: int) -> None:
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO extensions (user, extension, active)
|
||||
VALUES (?, ?, ?)
|
||||
@@ -65,9 +65,9 @@ def update_user_extension(*, user_id: str, extension: str, active: int) -> None:
|
||||
# -------
|
||||
|
||||
|
||||
def create_wallet(*, user_id: str, wallet_name: Optional[str] = None) -> Wallet:
|
||||
async def create_wallet(*, user_id: str, wallet_name: Optional[str] = None) -> Wallet:
|
||||
wallet_id = uuid4().hex
|
||||
g.db.execute(
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO wallets (id, name, user, adminkey, inkey)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
@@ -75,14 +75,14 @@ def create_wallet(*, user_id: str, wallet_name: Optional[str] = None) -> Wallet:
|
||||
(wallet_id, wallet_name or DEFAULT_WALLET_NAME, user_id, uuid4().hex, uuid4().hex),
|
||||
)
|
||||
|
||||
new_wallet = get_wallet(wallet_id=wallet_id)
|
||||
new_wallet = await get_wallet(wallet_id=wallet_id)
|
||||
assert new_wallet, "Newly created wallet couldn't be retrieved"
|
||||
|
||||
return new_wallet
|
||||
|
||||
|
||||
def delete_wallet(*, user_id: str, wallet_id: str) -> None:
|
||||
g.db.execute(
|
||||
async def delete_wallet(*, user_id: str, wallet_id: str) -> None:
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE wallets AS w
|
||||
SET
|
||||
@@ -95,8 +95,8 @@ def delete_wallet(*, user_id: str, wallet_id: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def get_wallet(wallet_id: str) -> Optional[Wallet]:
|
||||
row = g.db.fetchone(
|
||||
async def get_wallet(wallet_id: str) -> Optional[Wallet]:
|
||||
row = await db.fetchone(
|
||||
"""
|
||||
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
|
||||
FROM wallets
|
||||
@@ -108,8 +108,8 @@ def get_wallet(wallet_id: str) -> Optional[Wallet]:
|
||||
return Wallet(**row) if row else None
|
||||
|
||||
|
||||
def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wallet]:
|
||||
row = g.db.fetchone(
|
||||
async def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wallet]:
|
||||
row = await db.fetchone(
|
||||
"""
|
||||
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
|
||||
FROM wallets
|
||||
@@ -131,8 +131,8 @@ def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wallet]:
|
||||
# ---------------
|
||||
|
||||
|
||||
def get_standalone_payment(checking_id: str) -> Optional[Payment]:
|
||||
row = g.db.fetchone(
|
||||
async def get_standalone_payment(checking_id: str) -> Optional[Payment]:
|
||||
row = await db.fetchone(
|
||||
"""
|
||||
SELECT *
|
||||
FROM apipayments
|
||||
@@ -144,8 +144,8 @@ def get_standalone_payment(checking_id: str) -> Optional[Payment]:
|
||||
return Payment.from_row(row) if row else None
|
||||
|
||||
|
||||
def get_wallet_payment(wallet_id: str, payment_hash: str) -> Optional[Payment]:
|
||||
row = g.db.fetchone(
|
||||
async def get_wallet_payment(wallet_id: str, payment_hash: str) -> Optional[Payment]:
|
||||
row = await db.fetchone(
|
||||
"""
|
||||
SELECT *
|
||||
FROM apipayments
|
||||
@@ -157,7 +157,7 @@ def get_wallet_payment(wallet_id: str, payment_hash: str) -> Optional[Payment]:
|
||||
return Payment.from_row(row) if row else None
|
||||
|
||||
|
||||
def get_wallet_payments(
|
||||
async def get_wallet_payments(
|
||||
wallet_id: str,
|
||||
*,
|
||||
complete: bool = False,
|
||||
@@ -197,7 +197,7 @@ def get_wallet_payments(
|
||||
clause += "AND checking_id NOT LIKE 'temp_%' "
|
||||
clause += "AND checking_id NOT LIKE 'internal_%' "
|
||||
|
||||
rows = g.db.fetchall(
|
||||
rows = await db.fetchall(
|
||||
f"""
|
||||
SELECT *
|
||||
FROM apipayments
|
||||
@@ -210,8 +210,8 @@ def get_wallet_payments(
|
||||
return [Payment.from_row(row) for row in rows]
|
||||
|
||||
|
||||
def delete_expired_invoices() -> None:
|
||||
rows = g.db.fetchall(
|
||||
async def delete_expired_invoices() -> None:
|
||||
rows = await db.fetchall(
|
||||
"""
|
||||
SELECT bolt11
|
||||
FROM apipayments
|
||||
@@ -228,7 +228,7 @@ def delete_expired_invoices() -> None:
|
||||
if expiration_date > datetime.datetime.utcnow():
|
||||
continue
|
||||
|
||||
g.db.execute(
|
||||
await db.execute(
|
||||
"""
|
||||
DELETE FROM apipayments
|
||||
WHERE pending = 1 AND hash = ?
|
||||
@@ -241,7 +241,7 @@ def delete_expired_invoices() -> None:
|
||||
# --------
|
||||
|
||||
|
||||
def create_payment(
|
||||
async def create_payment(
|
||||
*,
|
||||
wallet_id: str,
|
||||
checking_id: str,
|
||||
@@ -254,7 +254,7 @@ def create_payment(
|
||||
pending: bool = True,
|
||||
extra: Optional[Dict] = None,
|
||||
) -> Payment:
|
||||
g.db.execute(
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO apipayments
|
||||
(wallet, checking_id, bolt11, hash, preimage,
|
||||
@@ -275,14 +275,14 @@ def create_payment(
|
||||
),
|
||||
)
|
||||
|
||||
new_payment = get_wallet_payment(wallet_id, payment_hash)
|
||||
new_payment = await get_wallet_payment(wallet_id, payment_hash)
|
||||
assert new_payment, "Newly created payment couldn't be retrieved"
|
||||
|
||||
return new_payment
|
||||
|
||||
|
||||
def update_payment_status(checking_id: str, pending: bool) -> None:
|
||||
g.db.execute(
|
||||
async def update_payment_status(checking_id: str, pending: bool) -> None:
|
||||
await db.execute(
|
||||
"UPDATE apipayments SET pending = ? WHERE checking_id = ?",
|
||||
(
|
||||
int(pending),
|
||||
@@ -291,12 +291,12 @@ def update_payment_status(checking_id: str, pending: bool) -> None:
|
||||
)
|
||||
|
||||
|
||||
def delete_payment(checking_id: str) -> None:
|
||||
g.db.execute("DELETE FROM apipayments WHERE checking_id = ?", (checking_id,))
|
||||
async def delete_payment(checking_id: str) -> None:
|
||||
await db.execute("DELETE FROM apipayments WHERE checking_id = ?", (checking_id,))
|
||||
|
||||
|
||||
def check_internal(payment_hash: str) -> Optional[str]:
|
||||
row = g.db.fetchone(
|
||||
async def check_internal(payment_hash: str) -> Optional[str]:
|
||||
row = await db.fetchone(
|
||||
"""
|
||||
SELECT checking_id FROM apipayments
|
||||
WHERE hash = ? AND pending AND amount > 0
|
||||
|
||||
+19
-19
@@ -1,8 +1,8 @@
|
||||
import sqlite3
|
||||
from sqlalchemy.exc import OperationalError # type: ignore
|
||||
|
||||
|
||||
def m000_create_migrations_table(db):
|
||||
db.execute(
|
||||
async def m000_create_migrations_table(db):
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE dbversions (
|
||||
db TEXT PRIMARY KEY,
|
||||
@@ -12,11 +12,11 @@ def m000_create_migrations_table(db):
|
||||
)
|
||||
|
||||
|
||||
def m001_initial(db):
|
||||
async def m001_initial(db):
|
||||
"""
|
||||
Initial LNbits tables.
|
||||
"""
|
||||
db.execute(
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -25,7 +25,7 @@ def m001_initial(db):
|
||||
);
|
||||
"""
|
||||
)
|
||||
db.execute(
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS extensions (
|
||||
user TEXT NOT NULL,
|
||||
@@ -36,7 +36,7 @@ def m001_initial(db):
|
||||
);
|
||||
"""
|
||||
)
|
||||
db.execute(
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS wallets (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -47,7 +47,7 @@ def m001_initial(db):
|
||||
);
|
||||
"""
|
||||
)
|
||||
db.execute(
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS apipayments (
|
||||
payhash TEXT NOT NULL,
|
||||
@@ -63,7 +63,7 @@ def m001_initial(db):
|
||||
"""
|
||||
)
|
||||
|
||||
db.execute(
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE VIEW IF NOT EXISTS balances AS
|
||||
SELECT wallet, COALESCE(SUM(s), 0) AS balance FROM (
|
||||
@@ -82,22 +82,22 @@ def m001_initial(db):
|
||||
)
|
||||
|
||||
|
||||
def m002_add_fields_to_apipayments(db):
|
||||
async def m002_add_fields_to_apipayments(db):
|
||||
"""
|
||||
Adding fields to apipayments for better accounting,
|
||||
and renaming payhash to checking_id since that is what it really is.
|
||||
"""
|
||||
try:
|
||||
db.execute("ALTER TABLE apipayments RENAME COLUMN payhash TO checking_id")
|
||||
db.execute("ALTER TABLE apipayments ADD COLUMN hash TEXT")
|
||||
db.execute("CREATE INDEX by_hash ON apipayments (hash)")
|
||||
db.execute("ALTER TABLE apipayments ADD COLUMN preimage TEXT")
|
||||
db.execute("ALTER TABLE apipayments ADD COLUMN bolt11 TEXT")
|
||||
db.execute("ALTER TABLE apipayments ADD COLUMN extra TEXT")
|
||||
await db.execute("ALTER TABLE apipayments RENAME COLUMN payhash TO checking_id")
|
||||
await db.execute("ALTER TABLE apipayments ADD COLUMN hash TEXT")
|
||||
await db.execute("CREATE INDEX by_hash ON apipayments (hash)")
|
||||
await db.execute("ALTER TABLE apipayments ADD COLUMN preimage TEXT")
|
||||
await db.execute("ALTER TABLE apipayments ADD COLUMN bolt11 TEXT")
|
||||
await db.execute("ALTER TABLE apipayments ADD COLUMN extra TEXT")
|
||||
|
||||
import json
|
||||
|
||||
rows = db.fetchall("SELECT * FROM apipayments")
|
||||
rows = await (await db.execute("SELECT * FROM apipayments")).fetchall()
|
||||
for row in rows:
|
||||
if not row["memo"] or not row["memo"].startswith("#"):
|
||||
continue
|
||||
@@ -106,7 +106,7 @@ def m002_add_fields_to_apipayments(db):
|
||||
prefix = "#" + ext + " "
|
||||
if row["memo"].startswith(prefix):
|
||||
new = row["memo"][len(prefix) :]
|
||||
db.execute(
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE apipayments SET extra = ?, memo = ?
|
||||
WHERE checking_id = ? AND memo = ?
|
||||
@@ -114,7 +114,7 @@ def m002_add_fields_to_apipayments(db):
|
||||
(json.dumps({"tag": ext}), new, row["checking_id"], row["memo"]),
|
||||
)
|
||||
break
|
||||
except sqlite3.OperationalError:
|
||||
except OperationalError:
|
||||
# this is necessary now because it may be the case that this migration will
|
||||
# run twice in some environments.
|
||||
# catching errors like this won't be necessary in anymore now that we
|
||||
|
||||
+11
-15
@@ -40,18 +40,14 @@ class Wallet(NamedTuple):
|
||||
hashing_key = hashlib.sha256(self.id.encode("utf-8")).digest()
|
||||
linking_key = hmac.digest(hashing_key, domain.encode("utf-8"), "sha256")
|
||||
|
||||
return SigningKey.from_string(
|
||||
linking_key,
|
||||
curve=SECP256k1,
|
||||
hashfunc=hashlib.sha256,
|
||||
)
|
||||
return SigningKey.from_string(linking_key, curve=SECP256k1, hashfunc=hashlib.sha256,)
|
||||
|
||||
def get_payment(self, payment_hash: str) -> Optional["Payment"]:
|
||||
async def get_payment(self, payment_hash: str) -> Optional["Payment"]:
|
||||
from .crud import get_wallet_payment
|
||||
|
||||
return get_wallet_payment(self.id, payment_hash)
|
||||
return await get_wallet_payment(self.id, payment_hash)
|
||||
|
||||
def get_payments(
|
||||
async def get_payments(
|
||||
self,
|
||||
*,
|
||||
complete: bool = True,
|
||||
@@ -62,7 +58,7 @@ class Wallet(NamedTuple):
|
||||
) -> List["Payment"]:
|
||||
from .crud import get_wallet_payments
|
||||
|
||||
return get_wallet_payments(
|
||||
return await get_wallet_payments(
|
||||
self.id,
|
||||
complete=complete,
|
||||
pending=pending,
|
||||
@@ -125,12 +121,12 @@ class Payment(NamedTuple):
|
||||
def is_uncheckable(self) -> bool:
|
||||
return self.checking_id.startswith("temp_") or self.checking_id.startswith("internal_")
|
||||
|
||||
def set_pending(self, pending: bool) -> None:
|
||||
async def set_pending(self, pending: bool) -> None:
|
||||
from .crud import update_payment_status
|
||||
|
||||
update_payment_status(self.checking_id, pending)
|
||||
await update_payment_status(self.checking_id, pending)
|
||||
|
||||
def check_pending(self) -> None:
|
||||
async def check_pending(self) -> None:
|
||||
if self.is_uncheckable:
|
||||
return
|
||||
|
||||
@@ -139,9 +135,9 @@ class Payment(NamedTuple):
|
||||
else:
|
||||
pending = WALLET.get_invoice_status(self.checking_id)
|
||||
|
||||
self.set_pending(pending.pending)
|
||||
await self.set_pending(pending.pending)
|
||||
|
||||
def delete(self) -> None:
|
||||
async def delete(self) -> None:
|
||||
from .crud import delete_payment
|
||||
|
||||
delete_payment(self.checking_id)
|
||||
await delete_payment(self.checking_id)
|
||||
|
||||
+26
-36
@@ -1,4 +1,3 @@
|
||||
import trio # type: ignore
|
||||
import json
|
||||
import httpx
|
||||
from io import BytesIO
|
||||
@@ -18,10 +17,11 @@ from lnbits.helpers import urlsafe_short_hash
|
||||
from lnbits.settings import WALLET
|
||||
from lnbits.wallets.base import PaymentStatus, PaymentResponse
|
||||
|
||||
from . import db
|
||||
from .crud import get_wallet, create_payment, delete_payment, check_internal, update_payment_status, get_wallet_payment
|
||||
|
||||
|
||||
def create_invoice(
|
||||
async def create_invoice(
|
||||
*,
|
||||
wallet_id: str,
|
||||
amount: int, # in satoshis
|
||||
@@ -29,6 +29,7 @@ def create_invoice(
|
||||
description_hash: Optional[bytes] = None,
|
||||
extra: Optional[Dict] = None,
|
||||
) -> Tuple[str, str]:
|
||||
await db.begin()
|
||||
invoice_memo = None if description_hash else memo
|
||||
storeable_memo = memo
|
||||
|
||||
@@ -41,7 +42,7 @@ def create_invoice(
|
||||
invoice = bolt11.decode(payment_request)
|
||||
|
||||
amount_msat = amount * 1000
|
||||
create_payment(
|
||||
await create_payment(
|
||||
wallet_id=wallet_id,
|
||||
checking_id=checking_id,
|
||||
payment_request=payment_request,
|
||||
@@ -51,11 +52,11 @@ def create_invoice(
|
||||
extra=extra,
|
||||
)
|
||||
|
||||
g.db.commit()
|
||||
await db.commit()
|
||||
return invoice.payment_hash, payment_request
|
||||
|
||||
|
||||
def pay_invoice(
|
||||
async def pay_invoice(
|
||||
*,
|
||||
wallet_id: str,
|
||||
payment_request: str,
|
||||
@@ -63,6 +64,7 @@ def pay_invoice(
|
||||
extra: Optional[Dict] = None,
|
||||
description: str = "",
|
||||
) -> str:
|
||||
await db.begin()
|
||||
temp_id = f"temp_{urlsafe_short_hash()}"
|
||||
internal_id = f"internal_{urlsafe_short_hash()}"
|
||||
|
||||
@@ -94,58 +96,53 @@ def pay_invoice(
|
||||
)
|
||||
|
||||
# check_internal() returns the checking_id of the invoice we're waiting for
|
||||
internal_checking_id = check_internal(invoice.payment_hash)
|
||||
internal_checking_id = await check_internal(invoice.payment_hash)
|
||||
if internal_checking_id:
|
||||
# create a new payment from this wallet
|
||||
create_payment(checking_id=internal_id, fee=0, pending=False, **payment_kwargs)
|
||||
await create_payment(checking_id=internal_id, fee=0, pending=False, **payment_kwargs)
|
||||
else:
|
||||
# create a temporary payment here so we can check if
|
||||
# the balance is enough in the next step
|
||||
fee_reserve = max(1000, int(invoice.amount_msat * 0.01))
|
||||
create_payment(checking_id=temp_id, fee=-fee_reserve, **payment_kwargs)
|
||||
await create_payment(checking_id=temp_id, fee=-fee_reserve, **payment_kwargs)
|
||||
|
||||
# do the balance check
|
||||
wallet = get_wallet(wallet_id)
|
||||
wallet = await get_wallet(wallet_id)
|
||||
assert wallet
|
||||
if wallet.balance_msat < 0:
|
||||
g.db.rollback()
|
||||
await db.rollback()
|
||||
raise PermissionError("Insufficient balance.")
|
||||
else:
|
||||
g.db.commit()
|
||||
await db.commit()
|
||||
await db.begin()
|
||||
|
||||
if internal_checking_id:
|
||||
# mark the invoice from the other side as not pending anymore
|
||||
# so the other side only has access to his new money when we are sure
|
||||
# the payer has enough to deduct from
|
||||
update_payment_status(checking_id=internal_checking_id, pending=False)
|
||||
await update_payment_status(checking_id=internal_checking_id, pending=False)
|
||||
|
||||
# notify receiver asynchronously
|
||||
from lnbits.tasks import internal_invoice_paid
|
||||
|
||||
try:
|
||||
internal_invoice_paid.send_nowait(internal_checking_id)
|
||||
except trio.WouldBlock:
|
||||
pass
|
||||
await internal_invoice_paid.send(internal_checking_id)
|
||||
else:
|
||||
# actually pay the external invoice
|
||||
payment: PaymentResponse = WALLET.pay_invoice(payment_request)
|
||||
if payment.ok and payment.checking_id:
|
||||
create_payment(
|
||||
checking_id=payment.checking_id,
|
||||
fee=payment.fee_msat,
|
||||
preimage=payment.preimage,
|
||||
**payment_kwargs,
|
||||
await create_payment(
|
||||
checking_id=payment.checking_id, fee=payment.fee_msat, preimage=payment.preimage, **payment_kwargs,
|
||||
)
|
||||
delete_payment(temp_id)
|
||||
await delete_payment(temp_id)
|
||||
else:
|
||||
raise Exception(payment.error_message or "Failed to pay_invoice on backend.")
|
||||
|
||||
g.db.commit()
|
||||
await db.commit()
|
||||
return invoice.payment_hash
|
||||
|
||||
|
||||
async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo: Optional[str] = None) -> None:
|
||||
_, payment_request = create_invoice(
|
||||
_, payment_request = await create_invoice(
|
||||
wallet_id=wallet_id,
|
||||
amount=res.max_sats,
|
||||
memo=memo or res.default_description or "",
|
||||
@@ -154,8 +151,7 @@ async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.get(
|
||||
res.callback.base,
|
||||
params={**res.callback.query_params, **{"k1": res.k1, "pr": payment_request}},
|
||||
res.callback.base, params={**res.callback.query_params, **{"k1": res.k1, "pr": payment_request}},
|
||||
)
|
||||
|
||||
|
||||
@@ -212,11 +208,7 @@ async def perform_lnurlauth(callback: str) -> Optional[LnurlErrorResponse]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(
|
||||
callback,
|
||||
params={
|
||||
"k1": k1.hex(),
|
||||
"key": key.verifying_key.to_string("compressed").hex(),
|
||||
"sig": sig.hex(),
|
||||
},
|
||||
params={"k1": k1.hex(), "key": key.verifying_key.to_string("compressed").hex(), "sig": sig.hex(),},
|
||||
)
|
||||
try:
|
||||
resp = json.loads(r.text)
|
||||
@@ -225,13 +217,11 @@ async def perform_lnurlauth(callback: str) -> Optional[LnurlErrorResponse]:
|
||||
|
||||
return LnurlErrorResponse(reason=resp["reason"])
|
||||
except (KeyError, json.decoder.JSONDecodeError):
|
||||
return LnurlErrorResponse(
|
||||
reason=r.text[:200] + "..." if len(r.text) > 200 else r.text,
|
||||
)
|
||||
return LnurlErrorResponse(reason=r.text[:200] + "..." if len(r.text) > 200 else r.text,)
|
||||
|
||||
|
||||
def check_invoice_status(wallet_id: str, payment_hash: str) -> PaymentStatus:
|
||||
payment = get_wallet_payment(wallet_id, payment_hash)
|
||||
async def check_invoice_status(wallet_id: str, payment_hash: str) -> PaymentStatus:
|
||||
payment = await get_wallet_payment(wallet_id, payment_hash)
|
||||
if not payment:
|
||||
return PaymentStatus(None)
|
||||
|
||||
|
||||
+24
-43
@@ -2,7 +2,6 @@ import trio # type: ignore
|
||||
import json
|
||||
import lnurl # type: ignore
|
||||
import httpx
|
||||
import traceback
|
||||
from urllib.parse import urlparse, urlunparse, urlencode, parse_qs, ParseResult
|
||||
from quart import g, jsonify, request, make_response
|
||||
from http import HTTPStatus
|
||||
@@ -12,7 +11,7 @@ from typing import Dict, Union
|
||||
from lnbits import bolt11
|
||||
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
||||
|
||||
from .. import core_app
|
||||
from .. import core_app, db
|
||||
from ..services import create_invoice, pay_invoice, perform_lnurlauth
|
||||
from ..crud import delete_expired_invoices
|
||||
from ..tasks import sse_listeners
|
||||
@@ -22,13 +21,7 @@ from ..tasks import sse_listeners
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_wallet():
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"id": g.wallet.id,
|
||||
"name": g.wallet.name,
|
||||
"balance": g.wallet.balance_msat,
|
||||
}
|
||||
),
|
||||
jsonify({"id": g.wallet.id, "name": g.wallet.name, "balance": g.wallet.balance_msat,}),
|
||||
HTTPStatus.OK,
|
||||
)
|
||||
|
||||
@@ -37,12 +30,12 @@ async def api_wallet():
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_payments():
|
||||
if "check_pending" in request.args:
|
||||
delete_expired_invoices()
|
||||
await delete_expired_invoices()
|
||||
|
||||
for payment in g.wallet.get_payments(complete=False, pending=True, exclude_uncheckable=True):
|
||||
payment.check_pending()
|
||||
for payment in await g.wallet.get_payments(complete=False, pending=True, exclude_uncheckable=True):
|
||||
await payment.check_pending()
|
||||
|
||||
return jsonify(g.wallet.get_payments(pending=True)), HTTPStatus.OK
|
||||
return jsonify(await g.wallet.get_payments(pending=True)), HTTPStatus.OK
|
||||
|
||||
|
||||
@api_check_wallet_key("invoice")
|
||||
@@ -63,12 +56,14 @@ async def api_payments_create_invoice():
|
||||
memo = g.data["memo"]
|
||||
|
||||
try:
|
||||
payment_hash, payment_request = create_invoice(
|
||||
payment_hash, payment_request = await create_invoice(
|
||||
wallet_id=g.wallet.id, amount=g.data["amount"], memo=memo, description_hash=description_hash
|
||||
)
|
||||
except Exception as e:
|
||||
g.db.rollback()
|
||||
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
raise exc
|
||||
|
||||
await db.commit()
|
||||
|
||||
invoice = bolt11.decode(payment_request)
|
||||
|
||||
@@ -76,11 +71,7 @@ async def api_payments_create_invoice():
|
||||
if g.data.get("lnurl_callback"):
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
r = await client.get(
|
||||
g.data["lnurl_callback"],
|
||||
params={"pr": payment_request},
|
||||
timeout=10,
|
||||
)
|
||||
r = await client.get(g.data["lnurl_callback"], params={"pr": payment_request}, timeout=10,)
|
||||
if r.is_error:
|
||||
lnurl_response = r.text
|
||||
else:
|
||||
@@ -110,15 +101,14 @@ async def api_payments_create_invoice():
|
||||
@api_validate_post_request(schema={"bolt11": {"type": "string", "empty": False, "required": True}})
|
||||
async def api_payments_pay_invoice():
|
||||
try:
|
||||
payment_hash = pay_invoice(wallet_id=g.wallet.id, payment_request=g.data["bolt11"])
|
||||
payment_hash = await pay_invoice(wallet_id=g.wallet.id, payment_request=g.data["bolt11"])
|
||||
except ValueError as e:
|
||||
return jsonify({"message": str(e)}), HTTPStatus.BAD_REQUEST
|
||||
except PermissionError as e:
|
||||
return jsonify({"message": str(e)}), HTTPStatus.FORBIDDEN
|
||||
except Exception as exc:
|
||||
traceback.print_exc(7)
|
||||
g.db.rollback()
|
||||
return jsonify({"message": str(exc)}), HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
await db.rollback()
|
||||
raise exc
|
||||
|
||||
return (
|
||||
jsonify(
|
||||
@@ -157,9 +147,7 @@ async def api_payments_pay_lnurl():
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
r = await client.get(
|
||||
g.data["callback"],
|
||||
params={"amount": g.data["amount"], "comment": g.data["comment"]},
|
||||
timeout=40,
|
||||
g.data["callback"], params={"amount": g.data["amount"], "comment": g.data["comment"]}, timeout=40,
|
||||
)
|
||||
if r.is_error:
|
||||
return jsonify({"message": "failed to connect"}), HTTPStatus.BAD_REQUEST
|
||||
@@ -198,16 +186,12 @@ async def api_payments_pay_lnurl():
|
||||
if g.data["comment"]:
|
||||
extra["comment"] = g.data["comment"]
|
||||
|
||||
payment_hash = pay_invoice(
|
||||
wallet_id=g.wallet.id,
|
||||
payment_request=params["pr"],
|
||||
description=g.data.get("description", ""),
|
||||
extra=extra,
|
||||
payment_hash = await pay_invoice(
|
||||
wallet_id=g.wallet.id, payment_request=params["pr"], description=g.data.get("description", ""), extra=extra,
|
||||
)
|
||||
except Exception as exc:
|
||||
traceback.print_exc(7)
|
||||
g.db.rollback()
|
||||
return jsonify({"message": str(exc)}), HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
await db.rollback()
|
||||
raise exc
|
||||
|
||||
return (
|
||||
jsonify(
|
||||
@@ -225,7 +209,7 @@ async def api_payments_pay_lnurl():
|
||||
@core_app.route("/api/v1/payments/<payment_hash>", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_payment(payment_hash):
|
||||
payment = g.wallet.get_payment(payment_hash)
|
||||
payment = await g.wallet.get_payment(payment_hash)
|
||||
|
||||
if not payment:
|
||||
return jsonify({"message": "Payment does not exist."}), HTTPStatus.NOT_FOUND
|
||||
@@ -233,7 +217,7 @@ async def api_payment(payment_hash):
|
||||
return jsonify({"paid": True, "preimage": payment.preimage}), HTTPStatus.OK
|
||||
|
||||
try:
|
||||
payment.check_pending()
|
||||
await payment.check_pending()
|
||||
except Exception:
|
||||
return jsonify({"paid": False}), HTTPStatus.OK
|
||||
|
||||
@@ -243,7 +227,6 @@ async def api_payment(payment_hash):
|
||||
@core_app.route("/api/v1/payments/sse", methods=["GET"])
|
||||
@api_check_wallet_key("invoice", accept_querystring=True)
|
||||
async def api_payments_sse():
|
||||
g.db.close()
|
||||
this_wallet_id = g.wallet.id
|
||||
|
||||
send_payment, receive_payment = trio.open_memory_channel(0)
|
||||
@@ -364,9 +347,7 @@ async def api_lnurlscan(code: str):
|
||||
@core_app.route("/api/v1/lnurlauth", methods=["POST"])
|
||||
@api_check_wallet_key("admin")
|
||||
@api_validate_post_request(
|
||||
schema={
|
||||
"callback": {"type": "string", "required": True},
|
||||
}
|
||||
schema={"callback": {"type": "string", "required": True},}
|
||||
)
|
||||
async def api_perform_lnurlauth():
|
||||
err = await perform_lnurlauth(g.data["callback"])
|
||||
|
||||
@@ -8,8 +8,8 @@ from lnurl import LnurlResponse, LnurlWithdrawResponse, decode as decode_lnurl
|
||||
from lnbits.core import core_app
|
||||
from lnbits.decorators import check_user_exists, validate_uuids
|
||||
from lnbits.settings import LNBITS_ALLOWED_USERS, SERVICE_FEE
|
||||
from lnbits.tasks import run_on_pseudo_request
|
||||
|
||||
from .. import db
|
||||
from ..crud import (
|
||||
create_account,
|
||||
get_user,
|
||||
@@ -41,11 +41,11 @@ async def extensions():
|
||||
abort(HTTPStatus.BAD_REQUEST, "You can either `enable` or `disable` an extension.")
|
||||
|
||||
if extension_to_enable:
|
||||
update_user_extension(user_id=g.user.id, extension=extension_to_enable, active=1)
|
||||
await update_user_extension(user_id=g.user.id, extension=extension_to_enable, active=1)
|
||||
elif extension_to_disable:
|
||||
update_user_extension(user_id=g.user.id, extension=extension_to_disable, active=0)
|
||||
await update_user_extension(user_id=g.user.id, extension=extension_to_disable, active=0)
|
||||
|
||||
return await render_template("core/extensions.html", user=get_user(g.user.id))
|
||||
return await render_template("core/extensions.html", user=await get_user(g.user.id))
|
||||
|
||||
|
||||
@core_app.route("/wallet")
|
||||
@@ -63,9 +63,12 @@ async def wallet():
|
||||
# nothing: create everything
|
||||
|
||||
if not user_id:
|
||||
user = get_user(create_account().id)
|
||||
user = await get_user((await create_account()).id)
|
||||
else:
|
||||
user = get_user(user_id) or abort(HTTPStatus.NOT_FOUND, "User does not exist.")
|
||||
user = await get_user(user_id)
|
||||
if not user:
|
||||
abort(HTTPStatus.NOT_FOUND, "User does not exist.")
|
||||
return
|
||||
|
||||
if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
|
||||
abort(HTTPStatus.UNAUTHORIZED, "User not authorized.")
|
||||
@@ -74,7 +77,7 @@ async def wallet():
|
||||
if user.wallets and not wallet_name:
|
||||
wallet = user.wallets[0]
|
||||
else:
|
||||
wallet = create_wallet(user_id=user.id, wallet_name=wallet_name)
|
||||
wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name)
|
||||
|
||||
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))
|
||||
|
||||
@@ -95,7 +98,7 @@ async def deletewallet():
|
||||
if wallet_id not in user_wallet_ids:
|
||||
abort(HTTPStatus.FORBIDDEN, "Not your wallet.")
|
||||
else:
|
||||
delete_wallet(user_id=g.user.id, wallet_id=wallet_id)
|
||||
await delete_wallet(user_id=g.user.id, wallet_id=wallet_id)
|
||||
user_wallet_ids.remove(wallet_id)
|
||||
|
||||
if user_wallet_ids:
|
||||
@@ -120,14 +123,12 @@ async def lnurlwallet():
|
||||
except Exception as exc:
|
||||
return f"Could not process lnurl-withdraw: {exc}", HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
|
||||
account = create_account()
|
||||
user = get_user(account.id)
|
||||
wallet = create_wallet(user_id=user.id)
|
||||
g.db.commit()
|
||||
account = await create_account()
|
||||
user = await get_user(account.id)
|
||||
wallet = await create_wallet(user_id=user.id)
|
||||
await db.commit()
|
||||
|
||||
await run_on_pseudo_request(
|
||||
redeem_lnurl_withdraw, wallet.id, withdraw_res, "LNbits initial funding: voucher redeem."
|
||||
)
|
||||
g.nursery.start_soon(redeem_lnurl_withdraw, wallet.id, withdraw_res, "LNbits initial funding: voucher redeem.")
|
||||
await trio.sleep(3)
|
||||
|
||||
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))
|
||||
|
||||
Reference in New Issue
Block a user