From 228717195b0eb4a5a0f87544432a3b3d00afb8d0 Mon Sep 17 00:00:00 2001 From: benarc Date: Sun, 30 Jan 2022 19:43:30 +0000 Subject: [PATCH] back --- lnbits/app.py | 5 +- lnbits/bolt11.py | 107 +++++++++++--------- lnbits/core/crud.py | 6 +- lnbits/core/services.py | 15 +-- lnbits/core/views/api.py | 29 +++--- lnbits/core/views/generic.py | 4 +- lnbits/decorators.py | 4 +- lnbits/extensions/lnaddress/lnurl.py | 6 +- lnbits/extensions/lnaddress/models.py | 2 +- lnbits/extensions/lnaddress/views.py | 4 +- lnbits/extensions/lnurldevice/crud.py | 26 +++-- lnbits/extensions/lnurldevice/lnurl.py | 20 ++-- lnbits/extensions/lnurldevice/migrations.py | 6 +- lnbits/extensions/lnurldevice/models.py | 5 +- lnbits/extensions/lnurldevice/views.py | 7 +- lnbits/extensions/lnurldevice/views_api.py | 7 +- lnbits/extensions/lnurlpayout/__init__.py | 2 + lnbits/extensions/lnurlpayout/crud.py | 27 ++++- lnbits/extensions/lnurlpayout/models.py | 2 + lnbits/extensions/lnurlpayout/tasks.py | 32 +++--- lnbits/extensions/lnurlpayout/views_api.py | 57 ++++++++--- lnbits/extensions/satspay/__init__.py | 1 + lnbits/extensions/satspay/crud.py | 2 +- lnbits/extensions/satspay/tasks.py | 1 - lnbits/extensions/satspay/views.py | 3 +- lnbits/extensions/satspay/views_api.py | 5 +- lnbits/utils/exchange_rates.py | 1 + lnbits/wallets/fake.py | 54 +++++----- lnbits/wallets/lndrest.py | 11 +- lnbits/wallets/void.py | 7 +- package-lock.json | 2 +- 31 files changed, 278 insertions(+), 182 deletions(-) diff --git a/lnbits/app.py b/lnbits/app.py index d7ad05e18..64e4ba4e1 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -48,10 +48,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI: origins = ["*"] app.add_middleware( - CORSMiddleware, - allow_origins=origins, - allow_methods=["*"], - allow_headers=["*"], + CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"] ) g().config = lnbits.settings diff --git a/lnbits/bolt11.py b/lnbits/bolt11.py index 41436de84..5b9dacaf8 100644 --- a/lnbits/bolt11.py +++ b/lnbits/bolt11.py @@ -116,7 +116,6 @@ def decode(pr: str) -> Invoice: return invoice - def encode(options): """ Convert options into LnAddr and pass it to the encoder """ @@ -131,27 +130,31 @@ def encode(options): addr.paymenthash = unhexlify(options.paymenthash) if options.description: - addr.tags.append(('d', options.description)) + addr.tags.append(("d", options.description)) if options.description_hashed: - addr.tags.append(('h', options.description_hashed)) + addr.tags.append(("h", options.description_hashed)) if options.expires: - addr.tags.append(('x', options.expires)) + addr.tags.append(("x", options.expires)) if options.fallback: - addr.tags.append(('f', options.fallback)) + addr.tags.append(("f", options.fallback)) for r in options.route: - splits = r.split('/') - route=[] + splits = r.split("/") + route = [] while len(splits) >= 5: - route.append((unhexlify(splits[0]), - unhexlify(splits[1]), - int(splits[2]), - int(splits[3]), - int(splits[4]))) + route.append( + ( + unhexlify(splits[0]), + unhexlify(splits[1]), + int(splits[2]), + int(splits[3]), + int(splits[4]), + ) + ) splits = splits[5:] - assert(len(splits) == 0) - addr.tags.append(('r', route)) + assert len(splits) == 0 + addr.tags.append(("r", route)) return lnencode(addr, options.privkey) @@ -159,21 +162,22 @@ def lnencode(addr, privkey): if addr.amount: amount = Decimal(str(addr.amount)) # We can only send down to millisatoshi. - if amount * 10**12 % 10: - raise ValueError("Cannot encode {}: too many decimal places".format( - addr.amount)) + if amount * 10 ** 12 % 10: + raise ValueError( + "Cannot encode {}: too many decimal places".format(addr.amount) + ) amount = addr.currency + shorten_amount(amount) else: - amount = addr.currency if addr.currency else '' + amount = addr.currency if addr.currency else "" - hrp = 'ln' + amount + hrp = "ln" + amount # Start with the timestamp - data = bitstring.pack('uint:35', addr.date) + data = bitstring.pack("uint:35", addr.date) # Payment hash - data += tagged_bytes('p', addr.paymenthash) + data += tagged_bytes("p", addr.paymenthash) tags_set = set() for k, v in addr.tags: @@ -181,30 +185,36 @@ def lnencode(addr, privkey): # BOLT #11: # # A writer MUST NOT include more than one `d`, `h`, `n` or `x` fields, - if k in ('d', 'h', 'n', 'x'): + if k in ("d", "h", "n", "x"): if k in tags_set: raise ValueError("Duplicate '{}' tag".format(k)) - if k == 'r': + if k == "r": route = bitstring.BitArray() for step in v: pubkey, channel, feebase, feerate, cltv = step - route.append(bitstring.BitArray(pubkey) + bitstring.BitArray(channel) + bitstring.pack('intbe:32', feebase) + bitstring.pack('intbe:32', feerate) + bitstring.pack('intbe:16', cltv)) - data += tagged('r', route) - elif k == 'f': + route.append( + bitstring.BitArray(pubkey) + + bitstring.BitArray(channel) + + bitstring.pack("intbe:32", feebase) + + bitstring.pack("intbe:32", feerate) + + bitstring.pack("intbe:16", cltv) + ) + data += tagged("r", route) + elif k == "f": data += encode_fallback(v, addr.currency) - elif k == 'd': - data += tagged_bytes('d', v.encode()) - elif k == 'x': + elif k == "d": + data += tagged_bytes("d", v.encode()) + elif k == "x": # Get minimal length by trimming leading 5 bits at a time. - expirybits = bitstring.pack('intbe:64', v)[4:64] - while expirybits.startswith('0b00000'): + expirybits = bitstring.pack("intbe:64", v)[4:64] + while expirybits.startswith("0b00000"): expirybits = expirybits[5:] - data += tagged('x', expirybits) - elif k == 'h': - data += tagged_bytes('h', hashlib.sha256(v.encode('utf-8')).digest()) - elif k == 'n': - data += tagged_bytes('n', v) + data += tagged("x", expirybits) + elif k == "h": + data += tagged_bytes("h", hashlib.sha256(v.encode("utf-8")).digest()) + elif k == "n": + data += tagged_bytes("n", v) else: # FIXME: Support unknown tags? raise ValueError("Unknown tag {}".format(k)) @@ -215,26 +225,31 @@ def lnencode(addr, privkey): # # A writer MUST include either a `d` or `h` field, and MUST NOT include # both. - if 'd' in tags_set and 'h' in tags_set: + if "d" in tags_set and "h" in tags_set: raise ValueError("Cannot include both 'd' and 'h'") - if not 'd' in tags_set and not 'h' in tags_set: + if not "d" in tags_set and not "h" in tags_set: raise ValueError("Must include either 'd' or 'h'") - + # We actually sign the hrp, then data (padded to 8 bits with zeroes). privkey = secp256k1.PrivateKey(bytes(unhexlify(privkey))) - sig = privkey.ecdsa_sign_recoverable(bytearray([ord(c) for c in hrp]) + data.tobytes()) + sig = privkey.ecdsa_sign_recoverable( + bytearray([ord(c) for c in hrp]) + data.tobytes() + ) # This doesn't actually serialize, but returns a pair of values :( sig, recid = privkey.ecdsa_recoverable_serialize(sig) data += bytes(sig) + bytes([recid]) return bech32_encode(hrp, bitarray_to_u5(data)) + class LnAddr(object): - def __init__(self, paymenthash=None, amount=None, currency='bc', tags=None, date=None): + def __init__( + self, paymenthash=None, amount=None, currency="bc", tags=None, date=None + ): self.date = int(time.time()) if not date else int(date) self.tags = [] if not tags else tags self.unknown_tags = [] - self.paymenthash=paymenthash + self.paymenthash = paymenthash self.signature = None self.pubkey = None self.currency = currency @@ -242,13 +257,13 @@ class LnAddr(object): def __str__(self): return "LnAddr[{}, amount={}{} tags=[{}]]".format( - hexlify(self.pubkey.serialize()).decode('utf-8'), - self.amount, self.currency, - ", ".join([k + '=' + str(v) for k, v in self.tags]) + hexlify(self.pubkey.serialize()).decode("utf-8"), + self.amount, + self.currency, + ", ".join([k + "=" + str(v) for k, v in self.tags]), ) - def _unshorten_amount(amount: str) -> int: """Given a shortened amount, return millisatoshis""" # BOLT #11: diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 4b8867c13..f69ca95b1 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -30,8 +30,7 @@ async def get_account( user_id: str, conn: Optional[Connection] = None ) -> Optional[User]: row = await (conn or db).fetchone( - "SELECT id, email, pass as password FROM accounts WHERE id = ?", ( - user_id,) + "SELECT id, email, pass as password FROM accounts WHERE id = ?", (user_id,) ) return User(**row) if row else None @@ -305,8 +304,7 @@ async def delete_expired_invoices(conn: Optional[Connection] = None,) -> None: except: continue - expiration_date = datetime.datetime.fromtimestamp( - invoice.date + invoice.expiry) + expiration_date = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry) if expiration_date > datetime.datetime.utcnow(): continue diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 34b50c565..be21a84ef 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -95,7 +95,7 @@ async def pay_invoice( if max_sat and invoice.amount_msat > max_sat * 1000: raise ValueError("Amount in invoice is too high.") - wallet = await get_wallet(wallet_id, conn=conn) + wallet = await get_wallet(wallet_id, conn=conn) # put all parameters that don't change here PaymentKwargs = TypedDict( @@ -141,15 +141,19 @@ async def pay_invoice( # do the balance check if internal payment if internal_checking_id: - wallet = await get_wallet(wallet_id, conn=conn) + wallet = await get_wallet(wallet_id, conn=conn) assert wallet if wallet.balance_msat < 0: raise PermissionError("Insufficient balance.") # do the balance check if external payment else: - if invoice.amount_msat > wallet.balance_msat - (wallet.balance_msat / 100 * 2): - raise PermissionError("LNbits requires you keep at least 2% reserve to cover potential routing fees.") + if invoice.amount_msat > wallet.balance_msat - ( + wallet.balance_msat / 100 * 2 + ): + raise PermissionError( + "LNbits requires you keep at least 2% reserve to cover potential routing fees." + ) if internal_checking_id: # mark the invoice from the other side as not pending anymore @@ -326,8 +330,7 @@ async def check_invoice_status( if not payment.pending: return status if payment.is_out and status.failed: - print( - f" - deleting outgoing failed payment {payment.checking_id}: {status}") + print(f" - deleting outgoing failed payment {payment.checking_id}: {status}") await payment.delete() elif not status.pending: print( diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 667083e20..2dceed490 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -77,7 +77,9 @@ async def api_update_wallet( @core_app.get("/api/v1/payments") async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)): await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True) - pendingPayments = await get_payments(wallet_id=wallet.wallet.id, pending=True, exclude_uncheckable=True) + pendingPayments = await get_payments( + wallet_id=wallet.wallet.id, pending=True, exclude_uncheckable=True + ) for payment in pendingPayments: await check_invoice_status( wallet_id=payment.wallet_id, payment_hash=payment.payment_hash @@ -198,8 +200,7 @@ async def api_payments_create( invoiceData: CreateInvoiceData = Body(...), ): if wallet.wallet_type < 0 or wallet.wallet_type > 2: - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid") + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid") if invoiceData.out is True and wallet.wallet_type == 0: if not invoiceData.bolt11: @@ -379,16 +380,14 @@ async def api_lnurlscan(code: str): params.update(callback=url) # with k1 already in it lnurlauth_key = g().wallet.lnurlauth_key(domain) - params.update( - pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex()) + params.update(pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex()) else: async with httpx.AsyncClient() as client: r = await client.get(url, timeout=5) if r.is_error: raise HTTPException( status_code=HTTPStatus.SERVICE_UNAVAILABLE, - detail={"domain": domain, - "message": "failed to get parameters"}, + detail={"domain": domain, "message": "failed to get parameters"}, ) try: @@ -418,8 +417,7 @@ async def api_lnurlscan(code: str): if tag == "withdrawRequest": params.update(kind="withdraw") - params.update(fixed=data["minWithdrawable"] - == data["maxWithdrawable"]) + params.update(fixed=data["minWithdrawable"] == data["maxWithdrawable"]) # callback with k1 already in it parsed_callback: ParseResult = urlparse(data["callback"]) @@ -509,18 +507,21 @@ async def api_list_currencies_available(): class ConversionData(BaseModel): - from_: str = Field('sat', alias="from") + from_: str = Field("sat", alias="from") amount: float - to: str = Query('usd') + to: str = Query("usd") + @core_app.post("/api/v1/conversion") async def api_fiat_as_sats(data: ConversionData): output = {} - if data.from_ == 'sat': + if data.from_ == "sat": output["sats"] = int(data.amount) output["BTC"] = data.amount / 100000000 - for currency in data.to.split(','): - output[currency.strip().upper()] = await satoshis_amount_as_fiat(data.amount, currency.strip()) + for currency in data.to.split(","): + output[currency.strip().upper()] = await satoshis_amount_as_fiat( + data.amount, currency.strip() + ) return output else: output[data.from_.upper()] = data.amount diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 5dbe92a78..b3ae97b13 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -43,9 +43,7 @@ async def home(request: Request, lightning: str = None): @core_html_routes.get( - "/extensions", - name="core.extensions", - response_class=HTMLResponse, + "/extensions", name="core.extensions", response_class=HTMLResponse ) async def extensions( request: Request, diff --git a/lnbits/decorators.py b/lnbits/decorators.py index f7cf86ec9..e76a1fd1c 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -171,6 +171,7 @@ async def require_admin_key( else: return wallet + async def require_invoice_key( r: Request, api_key_header: str = Security(api_key_header), @@ -184,7 +185,8 @@ async def require_invoice_key( # If wallet type is not invoice then return the unauthorized status # This also covers when the user passes an invalid key type raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail="Invoice (or Admin) key required." + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invoice (or Admin) key required.", ) else: return wallet diff --git a/lnbits/extensions/lnaddress/lnurl.py b/lnbits/extensions/lnaddress/lnurl.py index 471403153..fa26fa913 100644 --- a/lnbits/extensions/lnaddress/lnurl.py +++ b/lnbits/extensions/lnaddress/lnurl.py @@ -52,7 +52,7 @@ async def lnurl_callback(address_id, amount: int = Query(...)): amount_received = amount domain = await get_domain(address.domain) - + base_url = ( address.wallet_endpoint[:-1] if address.wallet_endpoint.endswith("/") @@ -71,7 +71,9 @@ async def lnurl_callback(address_id, amount: int = Query(...)): "out": False, "amount": int(amount_received / 1000), "description_hash": hashlib.sha256( - (await address.lnurlpay_metadata(domain=domain.domain)).encode("utf-8") + (await address.lnurlpay_metadata(domain=domain.domain)).encode( + "utf-8" + ) ).hexdigest(), "extra": {"tag": f"Payment to {address.username}@{domain.domain}"}, }, diff --git a/lnbits/extensions/lnaddress/models.py b/lnbits/extensions/lnaddress/models.py index 6f21278ef..248f856c5 100644 --- a/lnbits/extensions/lnaddress/models.py +++ b/lnbits/extensions/lnaddress/models.py @@ -53,5 +53,5 @@ class Addresses(BaseModel): text = f"Payment to {self.username}" identifier = f"{self.username}@{domain}" metadata = [["text/plain", text], ["text/identifier", identifier]] - + return LnurlPayMetadata(json.dumps(metadata)) diff --git a/lnbits/extensions/lnaddress/views.py b/lnbits/extensions/lnaddress/views.py index ef6d2b766..8c838f0c5 100644 --- a/lnbits/extensions/lnaddress/views.py +++ b/lnbits/extensions/lnaddress/views.py @@ -36,7 +36,7 @@ async def display(domain_id, request: Request): wallet = await get_wallet(domain.wallet) url = urlparse(str(request.url)) - + return lnaddress_renderer().TemplateResponse( "lnaddress/display.html", { @@ -45,6 +45,6 @@ async def display(domain_id, request: Request): "domain_domain": domain.domain, "domain_cost": domain.cost, "domain_wallet_inkey": wallet.inkey, - "root_url": f"{url.scheme}://{url.netloc}" + "root_url": f"{url.scheme}://{url.netloc}", }, ) diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index 2869b91ac..431108279 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -24,7 +24,15 @@ async def create_lnurldevice(data: createLnurldevice,) -> lnurldevices: ) VALUES (?, ?, ?, ?, ?, ?, ?) """, - (lnurldevice_id, lnurldevice_key, data.title, data.wallet, data.currency, data.device, data.profit,), + ( + lnurldevice_id, + lnurldevice_key, + data.title, + data.wallet, + data.currency, + data.device, + data.profit, + ), ) return await get_lnurldevice(lnurldevice_id) @@ -63,7 +71,9 @@ async def get_lnurldevices(wallet_ids: Union[str, List[str]]) -> List[lnurldevic async def delete_lnurldevice(lnurldevice_id: str) -> None: - await db.execute("DELETE FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,)) + await db.execute( + "DELETE FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) + ) ########################lnuldevice payments########################### @@ -102,19 +112,23 @@ async def update_lnurldevicepayment( (*kwargs.values(), lnurldevicepayment_id), ) row = await db.fetchone( - "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", (lnurldevicepayment_id,) + "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", + (lnurldevicepayment_id,), ) return lnurldevicepayment(**row) if row else None async def get_lnurldevicepayment(lnurldevicepayment_id: str) -> lnurldevicepayment: row = await db.fetchone( - "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", (lnurldevicepayment_id,) + "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", + (lnurldevicepayment_id,), ) return lnurldevicepayment(**row) if row else None + async def get_lnurlpayload(lnurldevicepayment_payload: str) -> lnurldevicepayment: row = await db.fetchone( - "SELECT * FROM lnurldevice.lnurldevicepayment WHERE payload = ?", (lnurldevicepayment_payload,) + "SELECT * FROM lnurldevice.lnurldevicepayment WHERE payload = ?", + (lnurldevicepayment_payload,), ) - return lnurldevicepayment(**row) if row else None \ No newline at end of file + return lnurldevicepayment(**row) if row else None diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index b3e4869d4..520a85ebe 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -105,10 +105,7 @@ async def lnurl_v1_params( paymentcheck = await get_lnurlpayload(p) if device.device == "atm": if paymentcheck: - return { - "status": "ERROR", - "reason": f"Payment already claimed", - } + return {"status": "ERROR", "reason": f"Payment already claimed"} if len(p) % 4 > 0: p += "=" * (4 - (len(p) % 4)) @@ -174,27 +171,28 @@ async def lnurl_v1_params( } - @lnurldevice_ext.get( "/api/v1/lnurl/cb/{paymentid}", status_code=HTTPStatus.OK, name="lnurldevice.lnurl_callback", ) -async def lnurl_callback(request: Request, paymentid: str = Query(None), pr: str = Query(None), k1: str = Query(None)): +async def lnurl_callback( + request: Request, + paymentid: str = Query(None), + pr: str = Query(None), + k1: str = Query(None), +): lnurldevicepayment = await get_lnurldevicepayment(paymentid) device = await get_lnurldevice(lnurldevicepayment.deviceid) if not device: raise HTTPException( status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found." ) - if pr: + if pr: if lnurldevicepayment.id != k1: return {"status": "ERROR", "reason": "Bad K1"} if lnurldevicepayment.payhash != "payment_hash": - return { - "status": "ERROR", - "reason": f"Payment already claimed", - } + return {"status": "ERROR", "reason": f"Payment already claimed"} lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload ) diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py index 4929cb4da..67065347c 100644 --- a/lnbits/extensions/lnurldevice/migrations.py +++ b/lnbits/extensions/lnurldevice/migrations.py @@ -2,6 +2,7 @@ from lnbits.db import Database db2 = Database("ext_lnurlpos") + async def m001_initial(db): """ Initial lnurldevice table. @@ -59,7 +60,8 @@ async def m002_redux(db): (row[0], row[1], row[2], row[3], row[4], "pos", 0), ) for row in [ - list(row) for row in await db2.fetchall("SELECT * FROM lnurlpos.lnurlpospayment") + list(row) + for row in await db2.fetchall("SELECT * FROM lnurlpos.lnurlpospayment") ]: await db.execute( """ @@ -76,4 +78,4 @@ async def m002_redux(db): (row[0], row[1], row[3], row[4], row[5], row[6]), ) except: - return \ No newline at end of file + return diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index b436f9d42..fef0aec1e 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -33,12 +33,15 @@ class lnurldevices(BaseModel): return cls(**dict(row)) def lnurl(self, req: Request) -> Lnurl: - url = req.url_for("lnurldevice.lnurl_response", device_id=self.id, _external=True) + url = req.url_for( + "lnurldevice.lnurl_response", device_id=self.id, _external=True + ) return lnurl_encode(url) async def lnurlpay_metadata(self) -> LnurlPayMetadata: return LnurlPayMetadata(json.dumps([["text/plain", self.title]])) + class lnurldevicepayment(BaseModel): id: str deviceid: str diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index 6d1747d30..3389e17c7 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -41,10 +41,13 @@ async def displaypin(request: Request, paymentid: str = Query(None)): ) status = await api_payment(lnurldevicepayment.payhash) if status["paid"]: - await update_payment_status(checking_id=lnurldevicepayment.payhash, pending=True) + await update_payment_status( + checking_id=lnurldevicepayment.payhash, pending=True + ) return lnurldevice_renderer().TemplateResponse( "lnurldevice/paid.html", {"request": request, "pin": lnurldevicepayment.pin} ) return lnurldevice_renderer().TemplateResponse( - "lnurldevice/error.html", {"request": request, "pin": "filler", "not_paid": True} + "lnurldevice/error.html", + {"request": request, "pin": "filler", "not_paid": True}, ) diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index 5987809fa..d152d210c 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -48,7 +48,9 @@ async def api_lnurldevice_create_or_update( async def api_lnurldevices_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids try: - return [{**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)] + return [ + {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids) + ] except: return "" @@ -71,7 +73,8 @@ async def api_lnurldevice_retrieve( @lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}") async def api_lnurldevice_delete( - wallet: WalletTypeInfo = Depends(require_admin_key), lnurldevice_id: str = Query(None) + wallet: WalletTypeInfo = Depends(require_admin_key), + lnurldevice_id: str = Query(None), ): lnurldevice = await get_lnurldevice(lnurldevice_id) diff --git a/lnbits/extensions/lnurlpayout/__init__.py b/lnbits/extensions/lnurlpayout/__init__.py index ad14dd370..1626e2e53 100644 --- a/lnbits/extensions/lnurlpayout/__init__.py +++ b/lnbits/extensions/lnurlpayout/__init__.py @@ -13,10 +13,12 @@ lnurlpayout_ext: APIRouter = APIRouter(prefix="/lnurlpayout", tags=["lnurlpayout def lnurlpayout_renderer(): return template_renderer(["lnbits/extensions/lnurlpayout/templates"]) + from .tasks import wait_for_paid_invoices from .views import * # noqa from .views_api import * # noqa + def lnurlpayout_start(): loop = asyncio.get_event_loop() loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/lnurlpayout/crud.py b/lnbits/extensions/lnurlpayout/crud.py index 4a09a2230..6cbf6c547 100644 --- a/lnbits/extensions/lnurlpayout/crud.py +++ b/lnbits/extensions/lnurlpayout/crud.py @@ -6,14 +6,23 @@ from . import db from .models import lnurlpayout, CreateLnurlPayoutData -async def create_lnurlpayout(wallet_id: str, admin_key: str, data: CreateLnurlPayoutData) -> lnurlpayout: +async def create_lnurlpayout( + wallet_id: str, admin_key: str, data: CreateLnurlPayoutData +) -> lnurlpayout: lnurlpayout_id = urlsafe_short_hash() await db.execute( """ INSERT INTO lnurlpayout.lnurlpayouts (id, title, wallet, admin_key, lnurlpay, threshold) VALUES (?, ?, ?, ?, ?, ?) """, - (lnurlpayout_id, data.title, wallet_id, admin_key, data.lnurlpay, data.threshold), + ( + lnurlpayout_id, + data.title, + wallet_id, + admin_key, + data.lnurlpay, + data.threshold, + ), ) lnurlpayout = await get_lnurlpayout(lnurlpayout_id) @@ -22,13 +31,19 @@ async def create_lnurlpayout(wallet_id: str, admin_key: str, data: CreateLnurlPa async def get_lnurlpayout(lnurlpayout_id: str) -> Optional[lnurlpayout]: - row = await db.fetchone("SELECT * FROM lnurlpayout.lnurlpayouts WHERE id = ?", (lnurlpayout_id,)) + row = await db.fetchone( + "SELECT * FROM lnurlpayout.lnurlpayouts WHERE id = ?", (lnurlpayout_id,) + ) return lnurlpayout(**row) if row else None + async def get_lnurlpayout_from_wallet(wallet_id: str) -> Optional[lnurlpayout]: - row = await db.fetchone("SELECT * FROM lnurlpayout.lnurlpayouts WHERE wallet = ?", (wallet_id,)) + row = await db.fetchone( + "SELECT * FROM lnurlpayout.lnurlpayouts WHERE wallet = ?", (wallet_id,) + ) return lnurlpayout(**row) if row else None + async def get_lnurlpayouts(wallet_ids: Union[str, List[str]]) -> List[lnurlpayout]: if isinstance(wallet_ids, str): wallet_ids = [wallet_ids] @@ -42,4 +57,6 @@ async def get_lnurlpayouts(wallet_ids: Union[str, List[str]]) -> List[lnurlpayou async def delete_lnurlpayout(lnurlpayout_id: str) -> None: - await db.execute("DELETE FROM lnurlpayout.lnurlpayouts WHERE id = ?", (lnurlpayout_id,)) + await db.execute( + "DELETE FROM lnurlpayout.lnurlpayouts WHERE id = ?", (lnurlpayout_id,) + ) diff --git a/lnbits/extensions/lnurlpayout/models.py b/lnbits/extensions/lnurlpayout/models.py index 3d6a14011..fc8be5759 100644 --- a/lnbits/extensions/lnurlpayout/models.py +++ b/lnbits/extensions/lnurlpayout/models.py @@ -2,11 +2,13 @@ from sqlite3 import Row from pydantic import BaseModel + class CreateLnurlPayoutData(BaseModel): title: str lnurlpay: str threshold: int + class lnurlpayout(BaseModel): id: str title: str diff --git a/lnbits/extensions/lnurlpayout/tasks.py b/lnbits/extensions/lnurlpayout/tasks.py index deb287382..e0757129b 100644 --- a/lnbits/extensions/lnurlpayout/tasks.py +++ b/lnbits/extensions/lnurlpayout/tasks.py @@ -26,28 +26,33 @@ async def on_invoice_paid(payment: Payment) -> None: # Check its got a payout associated with it lnurlpayout_link = await get_lnurlpayout_from_wallet(payment.wallet_id) if lnurlpayout_link: - + # Check the wallet balance is more than the threshold - + wallet = await get_wallet(lnurlpayout_link.wallet) - if wallet.balance < lnurlpayout_link.threshold + (lnurlpayout_link.threshold*0.02): + if wallet.balance < lnurlpayout_link.threshold + ( + lnurlpayout_link.threshold * 0.02 + ): return - + # Get the invoice from the LNURL to pay async with httpx.AsyncClient() as client: try: url = await api_payments_decode({"data": lnurlpayout_link.lnurlpay}) if str(url["domain"])[0:4] != "http": - raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="LNURL broken") - try: - r = await client.get( - str(url["domain"]), - timeout=40, + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="LNURL broken" ) + try: + r = await client.get(str(url["domain"]), timeout=40) res = r.json() try: r = await client.get( - res["callback"] + "?amount=" + str(int((wallet.balance - wallet.balance*0.02) * 1000)), + res["callback"] + + "?amount=" + + str( + int((wallet.balance - wallet.balance * 0.02) * 1000) + ), timeout=40, ) res = r.json() @@ -65,6 +70,9 @@ async def on_invoice_paid(payment: Payment) -> None: except (httpx.ConnectError, httpx.RequestError): return except Exception: - raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Failed to save LNURLPayout") + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, + detail="Failed to save LNURLPayout", + ) except: - return \ No newline at end of file + return diff --git a/lnbits/extensions/lnurlpayout/views_api.py b/lnbits/extensions/lnurlpayout/views_api.py index 03cd32828..d45de6fe6 100644 --- a/lnbits/extensions/lnurlpayout/views_api.py +++ b/lnbits/extensions/lnurlpayout/views_api.py @@ -10,10 +10,17 @@ from lnbits.core.views.api import api_payment, api_payments_decode from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import lnurlpayout_ext -from .crud import create_lnurlpayout, delete_lnurlpayout, get_lnurlpayout, get_lnurlpayouts, get_lnurlpayout_from_wallet +from .crud import ( + create_lnurlpayout, + delete_lnurlpayout, + get_lnurlpayout, + get_lnurlpayouts, + get_lnurlpayout_from_wallet, +) from .models import lnurlpayout, CreateLnurlPayoutData from .tasks import on_invoice_paid + @lnurlpayout_ext.get("/api/v1/lnurlpayouts", status_code=HTTPStatus.OK) async def api_lnurlpayouts( all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) @@ -28,23 +35,33 @@ async def api_lnurlpayouts( @lnurlpayout_ext.post("/api/v1/lnurlpayouts", status_code=HTTPStatus.CREATED) async def api_lnurlpayout_create( data: CreateLnurlPayoutData, wallet: WalletTypeInfo = Depends(get_key_type) -): +): if await get_lnurlpayout_from_wallet(wallet.wallet.id): - raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Wallet already has lnurlpayout set") + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, + detail="Wallet already has lnurlpayout set", + ) return url = await api_payments_decode({"data": data.lnurlpay}) if "domain" not in url: - raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="LNURL could not be decoded") + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="LNURL could not be decoded" + ) return if str(url["domain"])[0:4] != "http": raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not valid LNURL") return - lnurlpayout = await create_lnurlpayout(wallet_id=wallet.wallet.id, admin_key=wallet.wallet.adminkey, data=data) + lnurlpayout = await create_lnurlpayout( + wallet_id=wallet.wallet.id, admin_key=wallet.wallet.adminkey, data=data + ) if not lnurlpayout: - raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Failed to save LNURLPayout") + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Failed to save LNURLPayout" + ) return return lnurlpayout.dict() + @lnurlpayout_ext.delete("/api/v1/lnurlpayouts/{lnurlpayout_id}") async def api_lnurlpayout_delete( lnurlpayout_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) @@ -57,26 +74,34 @@ async def api_lnurlpayout_delete( ) if lnurlpayout.wallet != wallet.wallet.id: - raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your lnurlpayout.") + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not your lnurlpayout." + ) await delete_lnurlpayout(lnurlpayout_id) raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + @lnurlpayout_ext.get("/api/v1/lnurlpayouts/{lnurlpayout_id}", status_code=HTTPStatus.OK) async def api_lnurlpayout_check( lnurlpayout_id: str, wallet: WalletTypeInfo = Depends(get_key_type) -): +): lnurlpayout = await get_lnurlpayout(lnurlpayout_id) payments = await get_payments( - wallet_id=lnurlpayout.wallet, complete=True, pending=False, outgoing=True, incoming=True + wallet_id=lnurlpayout.wallet, + complete=True, + pending=False, + outgoing=True, + incoming=True, ) result = await on_invoice_paid(payments[0]) return - # get payouts func - # lnurlpayouts = await get_lnurlpayouts(wallet_ids) - # for lnurlpayout in lnurlpayouts: - # payments = await get_payments( - # wallet_id=lnurlpayout.wallet, complete=True, pending=False, outgoing=True, incoming=True - # ) - # await on_invoice_paid(payments[0]) \ No newline at end of file + +# get payouts func +# lnurlpayouts = await get_lnurlpayouts(wallet_ids) +# for lnurlpayout in lnurlpayouts: +# payments = await get_payments( +# wallet_id=lnurlpayout.wallet, complete=True, pending=False, outgoing=True, incoming=True +# ) +# await on_invoice_paid(payments[0]) diff --git a/lnbits/extensions/satspay/__init__.py b/lnbits/extensions/satspay/__init__.py index ddbd1bb97..f33f3aa52 100644 --- a/lnbits/extensions/satspay/__init__.py +++ b/lnbits/extensions/satspay/__init__.py @@ -15,6 +15,7 @@ satspay_ext: APIRouter = APIRouter(prefix="/satspay", tags=["satspay"]) def satspay_renderer(): return template_renderer(["lnbits/extensions/satspay/templates"]) + from .tasks import wait_for_paid_invoices from .views import * # noqa from .views_api import * # noqa diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index a4fe5f14d..9deb32154 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -115,7 +115,7 @@ async def check_address_balance(charge_id: str) -> List[Charges]: pass if charge.lnbitswallet: invoice_status = await api_payment(charge.payment_hash) - + if invoice_status["paid"]: return await update_charge(charge_id=charge_id, balance=charge.amount) row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,)) diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index bddd4ab2c..87f74b7d0 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -28,4 +28,3 @@ async def on_invoice_paid(payment: Payment) -> None: await payment.set_pending(False) await check_address_balance(charge_id=charge.id) - diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 07ed71ef8..d33d5c17e 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -32,5 +32,6 @@ async def display(request: Request, charge_id): ) wallet = await get_wallet(charge.lnbitswallet) return satspay_renderer().TemplateResponse( - "satspay/display.html", {"request": request, "charge": charge, "wallet_key": wallet.inkey} + "satspay/display.html", + {"request": request, "charge": charge, "wallet_key": wallet.inkey}, ) diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 428f2ef69..c3e38f0cd 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -25,14 +25,15 @@ from .models import CreateCharge #############################CHARGES########################## + @satspay_ext.post("/api/v1/charge") async def api_charge_create( - data: CreateCharge, - wallet: WalletTypeInfo = Depends(require_invoice_key) + data: CreateCharge, wallet: WalletTypeInfo = Depends(require_invoice_key) ): charge = await create_charge(user=wallet.wallet.user, data=data) return charge.dict() + @satspay_ext.put("/api/v1/charge/{charge_id}") async def api_charge_update( data: CreateCharge, diff --git a/lnbits/utils/exchange_rates.py b/lnbits/utils/exchange_rates.py index 585904385..53a5a80ba 100644 --- a/lnbits/utils/exchange_rates.py +++ b/lnbits/utils/exchange_rates.py @@ -285,5 +285,6 @@ async def get_fiat_rate_satoshis(currency: str) -> float: async def fiat_amount_as_satoshis(amount: float, currency: str) -> int: return int(amount * (await get_fiat_rate_satoshis(currency))) + async def satoshis_amount_as_fiat(amount: float, currency: str) -> float: return float(amount / (await get_fiat_rate_satoshis(currency))) diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py index 15bd61db8..95cde5d96 100644 --- a/lnbits/wallets/fake.py +++ b/lnbits/wallets/fake.py @@ -2,6 +2,7 @@ import asyncio import json import httpx from os import getenv +from datetime import datetime, timedelta from typing import Optional, Dict, AsyncGenerator import hashlib from ..bolt11 import encode @@ -13,60 +14,65 @@ from .base import ( Wallet, ) - class FakeWallet(Wallet): """https://github.com/lnbits/lnbits""" - async def status(self) -> StatusResponse: - print("This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits.") - return StatusResponse( - None, - 21000000000, + + print( + "The FakeWallet backend is for using LNbits as a centralised, stand-alone payment system." ) + return StatusResponse(None, 21000000000) + async def create_invoice( self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None, ) -> InvoiceResponse: - - options.amount = amount - options.timestamp = datetime.now().timestamp() + class options: + def __init__(self, amount, timestamp, payments_hash, privkey, memo, ): + self.name = name + self.age = age + async def status(self) -> StatusResponse: + randomHash = hashlib.sha256(b"some random data").hexdigest() - options.payments_hash = hex(randomHash) - options.privkey = "v3qrevqrevm39qin0vq3r0ivmrewvmq3rimq03ig" + options = { + "amount": amount, + "timestamp": datetime.now().timestamp(), + "payments_hash": randomHash, + "privkey": "v3qrevqrevm39qin0vq3r0ivmrewvmq3rimq03ig", + "memo": "", + "description_hashed": "", + } if description_hash: options.description_hashed = description_hash else: options.memo = memo payment_request = encode(options) + print(payment_request) checking_id = randomHash return InvoiceResponse(ok, checking_id, payment_request) async def pay_invoice(self, bolt11: str) -> PaymentResponse: - return "" async def get_invoice_status(self, checking_id: str) -> PaymentStatus: - return "" + return "" async def get_payment_status(self, checking_id: str) -> PaymentStatus: - return "" async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: - url = f"{self.endpoint}/api/v1/payments/sse" - print("lost connection to lnbits /payments/sse, retrying in 5 seconds") - await asyncio.sleep(5) + yield "" -#invoice = "lnbc" -#invoice += str(data.amount) + "m1" -#invoice += str(datetime.now().timestamp()).to_bytes(35, byteorder='big')) -#invoice += str(hashlib.sha256(b"some random data").hexdigest()) # hash of preimage, can be fake as invoice handled internally -#invoice += "dpl" # d then pl (p = 1, l = 31; 1 * 32 + 31 == 63) -#invoice += "2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq" #description, how do I encode this? -#invoice += str(hashlib.sha224("lnbc" + str(data.amount) + "m1").hexdigest()) +# invoice = "lnbc" +# invoice += str(data.amount) + "m1" +# invoice += str(datetime.now().timestamp()).to_bytes(35, byteorder='big')) +# invoice += str(hashlib.sha256(b"some random data").hexdigest()) # hash of preimage, can be fake as invoice handled internally +# invoice += "dpl" # d then pl (p = 1, l = 31; 1 * 32 + 31 == 63) +# invoice += "2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq" #description, how do I encode this? +# invoice += str(hashlib.sha224("lnbc" + str(data.amount) + "m1").hexdigest()) diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py index 4f7ee5265..251bf52f6 100644 --- a/lnbits/wallets/lndrest.py +++ b/lnbits/wallets/lndrest.py @@ -23,8 +23,7 @@ class LndRestWallet(Wallet): endpoint = getenv("LND_REST_ENDPOINT") endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint endpoint = ( - "https://" + - endpoint if not endpoint.startswith("http") else endpoint + "https://" + endpoint if not endpoint.startswith("http") else endpoint ) self.endpoint = endpoint @@ -103,10 +102,7 @@ class LndRestWallet(Wallet): r = await client.post( url=f"{self.endpoint}/v1/channels/transactions", headers=self.auth, - json={ - "payment_request": bolt11, - "fee_limit": lnrpcFeeLimit, - }, + json={"payment_request": bolt11, "fee_limit": lnrpcFeeLimit}, timeout=180, ) @@ -183,8 +179,7 @@ class LndRestWallet(Wallet): except: continue - payment_hash = base64.b64decode( - inv["r_hash"]).hex() + payment_hash = base64.b64decode(inv["r_hash"]).hex() yield payment_hash except (OSError, httpx.ConnectError, httpx.ReadError): pass diff --git a/lnbits/wallets/void.py b/lnbits/wallets/void.py index 0e37a3698..03b6db447 100644 --- a/lnbits/wallets/void.py +++ b/lnbits/wallets/void.py @@ -20,11 +20,10 @@ class VoidWallet(Wallet): raise Unsupported("") async def status(self) -> StatusResponse: - print("This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits.") - return StatusResponse( - None, - 0, + print( + "This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits." ) + return StatusResponse(None, 0) async def pay_invoice(self, bolt11: str) -> PaymentResponse: raise Unsupported("") diff --git a/package-lock.json b/package-lock.json index 3d0735425..f2ff24bdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "lnbits", + "name": "lnbits-legend", "lockfileVersion": 2, "requires": true, "packages": {