2020-09-29 18:24:08 -03:00
|
|
|
import httpx
|
2020-09-04 21:24:30 +02:00
|
|
|
from typing import Optional, Tuple, Dict
|
2020-09-13 21:31:05 -03:00
|
|
|
from quart import g
|
2020-10-13 14:46:23 -03:00
|
|
|
from lnurl import LnurlWithdrawResponse # type: ignore
|
2020-09-04 21:24:30 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
from typing import TypedDict # type: ignore
|
|
|
|
except ImportError: # pragma: nocover
|
|
|
|
from typing_extensions import TypedDict
|
2020-04-11 22:18:17 +02:00
|
|
|
|
2020-08-31 22:12:46 -03:00
|
|
|
from lnbits import bolt11
|
2020-04-24 13:13:05 +02:00
|
|
|
from lnbits.helpers import urlsafe_short_hash
|
2020-04-21 15:44:02 +02:00
|
|
|
from lnbits.settings import WALLET
|
2020-10-12 23:18:37 -03:00
|
|
|
from lnbits.wallets.base import PaymentStatus, PaymentResponse
|
2020-04-11 22:18:17 +02:00
|
|
|
|
2020-08-31 22:12:46 -03:00
|
|
|
from .crud import get_wallet, create_payment, delete_payment, check_internal, update_payment_status, get_wallet_payment
|
2020-04-11 22:18:17 +02:00
|
|
|
|
|
|
|
|
2020-08-31 22:12:46 -03:00
|
|
|
def create_invoice(
|
2020-09-03 23:02:15 +02:00
|
|
|
*,
|
|
|
|
wallet_id: str,
|
2020-10-20 23:19:22 -03:00
|
|
|
amount: int, # in satoshis
|
2020-09-03 23:02:15 +02:00
|
|
|
memo: str,
|
|
|
|
description_hash: Optional[bytes] = None,
|
|
|
|
extra: Optional[Dict] = None,
|
2020-08-31 22:12:46 -03:00
|
|
|
) -> Tuple[str, str]:
|
2020-09-02 21:11:08 -03:00
|
|
|
invoice_memo = None if description_hash else memo
|
|
|
|
storeable_memo = memo
|
2020-04-11 22:18:17 +02:00
|
|
|
|
2020-09-02 21:11:08 -03:00
|
|
|
ok, checking_id, payment_request, error_message = WALLET.create_invoice(
|
|
|
|
amount=amount, memo=invoice_memo, description_hash=description_hash
|
|
|
|
)
|
2020-04-11 22:18:17 +02:00
|
|
|
if not ok:
|
|
|
|
raise Exception(error_message or "Unexpected backend error.")
|
|
|
|
|
2020-08-31 22:12:46 -03:00
|
|
|
invoice = bolt11.decode(payment_request)
|
2020-04-11 22:18:17 +02:00
|
|
|
|
2020-08-31 22:12:46 -03:00
|
|
|
amount_msat = amount * 1000
|
|
|
|
create_payment(
|
|
|
|
wallet_id=wallet_id,
|
|
|
|
checking_id=checking_id,
|
|
|
|
payment_request=payment_request,
|
|
|
|
payment_hash=invoice.payment_hash,
|
|
|
|
amount=amount_msat,
|
2020-09-02 21:11:08 -03:00
|
|
|
memo=storeable_memo,
|
2020-08-31 22:12:46 -03:00
|
|
|
extra=extra,
|
|
|
|
)
|
|
|
|
|
2020-09-11 21:24:41 -03:00
|
|
|
g.db.commit()
|
2020-08-31 22:12:46 -03:00
|
|
|
return invoice.payment_hash, payment_request
|
|
|
|
|
|
|
|
|
|
|
|
def pay_invoice(
|
2020-10-12 18:15:27 -03:00
|
|
|
*,
|
|
|
|
wallet_id: str,
|
|
|
|
payment_request: str,
|
|
|
|
max_sat: Optional[int] = None,
|
|
|
|
extra: Optional[Dict] = None,
|
|
|
|
description: str = "",
|
2020-08-31 22:12:46 -03:00
|
|
|
) -> str:
|
2020-04-24 13:13:05 +02:00
|
|
|
temp_id = f"temp_{urlsafe_short_hash()}"
|
2020-09-02 00:32:52 -03:00
|
|
|
internal_id = f"internal_{urlsafe_short_hash()}"
|
|
|
|
|
2020-09-02 21:11:08 -03:00
|
|
|
invoice = bolt11.decode(payment_request)
|
|
|
|
if invoice.amount_msat == 0:
|
|
|
|
raise ValueError("Amountless invoices not supported.")
|
|
|
|
if max_sat and invoice.amount_msat > max_sat * 1000:
|
|
|
|
raise ValueError("Amount in invoice is too high.")
|
|
|
|
|
|
|
|
# put all parameters that don't change here
|
|
|
|
PaymentKwargs = TypedDict(
|
|
|
|
"PaymentKwargs",
|
|
|
|
{
|
|
|
|
"wallet_id": str,
|
|
|
|
"payment_request": str,
|
|
|
|
"payment_hash": str,
|
|
|
|
"amount": int,
|
|
|
|
"memo": str,
|
|
|
|
"extra": Optional[Dict],
|
|
|
|
},
|
|
|
|
)
|
|
|
|
payment_kwargs: PaymentKwargs = dict(
|
|
|
|
wallet_id=wallet_id,
|
|
|
|
payment_request=payment_request,
|
|
|
|
payment_hash=invoice.payment_hash,
|
|
|
|
amount=-invoice.amount_msat,
|
2020-10-12 18:15:27 -03:00
|
|
|
memo=description or invoice.description or "",
|
2020-09-02 21:11:08 -03:00
|
|
|
extra=extra,
|
|
|
|
)
|
|
|
|
|
|
|
|
# check_internal() returns the checking_id of the invoice we're waiting for
|
|
|
|
internal = check_internal(invoice.payment_hash)
|
|
|
|
if internal:
|
|
|
|
# create a new payment from this wallet
|
|
|
|
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)
|
|
|
|
|
|
|
|
# do the balance check
|
|
|
|
wallet = get_wallet(wallet_id)
|
2020-09-29 18:24:08 -03:00
|
|
|
assert wallet
|
2020-09-02 21:11:08 -03:00
|
|
|
if wallet.balance_msat < 0:
|
2020-09-07 00:47:13 -03:00
|
|
|
g.db.rollback()
|
2020-09-02 21:11:08 -03:00
|
|
|
raise PermissionError("Insufficient balance.")
|
2020-09-11 21:24:41 -03:00
|
|
|
else:
|
|
|
|
g.db.commit()
|
2020-09-02 21:11:08 -03:00
|
|
|
|
|
|
|
if internal:
|
|
|
|
# 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, pending=False)
|
|
|
|
else:
|
|
|
|
# actually pay the external invoice
|
2020-10-12 23:18:37 -03:00
|
|
|
payment: PaymentResponse = WALLET.pay_invoice(payment_request)
|
2020-10-13 14:46:23 -03:00
|
|
|
if payment.ok and payment.checking_id:
|
2020-10-12 23:18:37 -03:00
|
|
|
create_payment(
|
|
|
|
checking_id=payment.checking_id,
|
|
|
|
fee=payment.fee_msat,
|
|
|
|
preimage=payment.preimage,
|
|
|
|
**payment_kwargs,
|
|
|
|
)
|
2020-09-02 21:11:08 -03:00
|
|
|
delete_payment(temp_id)
|
2020-09-06 23:39:46 -03:00
|
|
|
else:
|
2020-10-12 23:18:37 -03:00
|
|
|
raise Exception(payment.error_message or "Failed to pay_invoice on backend.")
|
2020-04-11 22:18:17 +02:00
|
|
|
|
2020-09-11 21:24:41 -03:00
|
|
|
g.db.commit()
|
2020-08-31 22:12:46 -03:00
|
|
|
return invoice.payment_hash
|
2020-04-16 17:10:53 +02:00
|
|
|
|
|
|
|
|
2020-09-29 18:24:08 -03:00
|
|
|
async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo: Optional[str] = None) -> None:
|
|
|
|
_, payment_request = create_invoice(
|
|
|
|
wallet_id=wallet_id,
|
|
|
|
amount=res.max_sats,
|
2020-10-13 14:46:23 -03:00
|
|
|
memo=memo or res.default_description or "",
|
2020-09-29 18:24:08 -03:00
|
|
|
extra={"tag": "lnurlwallet"},
|
|
|
|
)
|
|
|
|
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
|
|
await client.get(
|
|
|
|
res.callback.base,
|
|
|
|
params={**res.callback.query_params, **{"k1": res.k1, "pr": payment_request}},
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-09-02 21:11:08 -03:00
|
|
|
def check_invoice_status(wallet_id: str, payment_hash: str) -> PaymentStatus:
|
2020-08-31 22:12:46 -03:00
|
|
|
payment = get_wallet_payment(wallet_id, payment_hash)
|
2020-09-02 21:11:08 -03:00
|
|
|
if not payment:
|
|
|
|
return PaymentStatus(None)
|
|
|
|
|
2020-08-31 22:12:46 -03:00
|
|
|
return WALLET.get_invoice_status(payment.checking_id)
|