mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2024-11-20 10:39:59 +01:00
143 lines
4.6 KiB
Python
143 lines
4.6 KiB
Python
import json
|
|
import time
|
|
from typing import Dict
|
|
|
|
from fastapi import Query, Request
|
|
from loguru import logger
|
|
from pydantic import BaseModel, validator
|
|
|
|
from lnbits import bolt11
|
|
from lnbits.core.services import PaymentFailure, pay_invoice
|
|
|
|
from . import db
|
|
from .exchange_rates import exchange_rate_providers, fiat_currencies
|
|
from .helpers import LnurlValidationError, get_callback_url
|
|
|
|
|
|
class CreateBleskomat(BaseModel):
|
|
name: str = Query(...)
|
|
fiat_currency: str = Query(...)
|
|
exchange_rate_provider: str = Query(...)
|
|
fee: str = Query(...)
|
|
|
|
@validator("fiat_currency")
|
|
def allowed_fiat_currencies(cls, v):
|
|
if v not in fiat_currencies.keys():
|
|
raise ValueError("Not allowed currency")
|
|
return v
|
|
|
|
@validator("exchange_rate_provider")
|
|
def allowed_providers(cls, v):
|
|
if v not in exchange_rate_providers.keys():
|
|
raise ValueError("Not allowed provider")
|
|
return v
|
|
|
|
@validator("fee")
|
|
def fee_type(cls, v):
|
|
if not isinstance(v, (str, float, int)):
|
|
raise ValueError("Fee type not allowed")
|
|
return v
|
|
|
|
|
|
class Bleskomat(BaseModel):
|
|
id: str
|
|
wallet: str
|
|
api_key_id: str
|
|
api_key_secret: str
|
|
api_key_encoding: str
|
|
name: str
|
|
fiat_currency: str
|
|
exchange_rate_provider: str
|
|
fee: str
|
|
|
|
|
|
class BleskomatLnurl(BaseModel):
|
|
id: str
|
|
bleskomat: str
|
|
wallet: str
|
|
hash: str
|
|
tag: str
|
|
params: str
|
|
api_key_id: str
|
|
initial_uses: int
|
|
remaining_uses: int
|
|
created_time: int
|
|
updated_time: int
|
|
|
|
def has_uses_remaining(self) -> bool:
|
|
# When initial uses is 0 then the LNURL has unlimited uses.
|
|
return self.initial_uses == 0 or self.remaining_uses > 0
|
|
|
|
def get_info_response_object(self, secret: str, req: Request) -> Dict[str, str]:
|
|
tag = self.tag
|
|
params = json.loads(self.params)
|
|
response = {"tag": tag}
|
|
if tag == "withdrawRequest":
|
|
for key in ["minWithdrawable", "maxWithdrawable", "defaultDescription"]:
|
|
response[key] = params[key]
|
|
response["callback"] = get_callback_url(req)
|
|
response["k1"] = secret
|
|
return response
|
|
|
|
def validate_action(self, query) -> None:
|
|
tag = self.tag
|
|
params = json.loads(self.params)
|
|
# Perform tag-specific checks.
|
|
if tag == "withdrawRequest":
|
|
for field in ["pr"]:
|
|
if not field in query:
|
|
raise LnurlValidationError(f'Missing required parameter: "{field}"')
|
|
# Check the bolt11 invoice(s) provided.
|
|
pr = query["pr"]
|
|
if "," in pr:
|
|
raise LnurlValidationError("Multiple payment requests not supported")
|
|
try:
|
|
invoice = bolt11.decode(pr)
|
|
except ValueError:
|
|
raise LnurlValidationError(
|
|
'Invalid parameter ("pr"): Lightning payment request expected'
|
|
)
|
|
if invoice.amount_msat < params["minWithdrawable"]:
|
|
raise LnurlValidationError(
|
|
'Amount in invoice must be greater than or equal to "minWithdrawable"'
|
|
)
|
|
if invoice.amount_msat > params["maxWithdrawable"]:
|
|
raise LnurlValidationError(
|
|
'Amount in invoice must be less than or equal to "maxWithdrawable"'
|
|
)
|
|
else:
|
|
raise LnurlValidationError(f'Unknown subprotocol: "{tag}"')
|
|
|
|
async def execute_action(self, query):
|
|
self.validate_action(query)
|
|
used = False
|
|
async with db.connect() as conn:
|
|
if self.initial_uses > 0:
|
|
used = await self.use(conn)
|
|
if not used:
|
|
raise LnurlValidationError("Maximum number of uses already reached")
|
|
tag = self.tag
|
|
if tag == "withdrawRequest":
|
|
try:
|
|
await pay_invoice(
|
|
wallet_id=self.wallet, payment_request=query["pr"]
|
|
)
|
|
except (ValueError, PermissionError, PaymentFailure) as e:
|
|
raise LnurlValidationError("Failed to pay invoice: " + str(e))
|
|
except Exception as e:
|
|
logger.error(str(e))
|
|
raise LnurlValidationError("Unexpected error")
|
|
|
|
async def use(self, conn) -> bool:
|
|
now = int(time.time())
|
|
result = await conn.execute(
|
|
"""
|
|
UPDATE bleskomat.bleskomat_lnurls
|
|
SET remaining_uses = remaining_uses - 1, updated_time = ?
|
|
WHERE id = ?
|
|
AND remaining_uses > 0
|
|
""",
|
|
(now, self.id),
|
|
)
|
|
return result.rowcount > 0
|