Exemplo n.º 1
0
def create_invoice(
    *,
    wallet_id: str,
    amount: int,
    memo: str,
    description_hash: Optional[bytes] = None,
    extra: Optional[Dict] = None,
) -> Tuple[str, str]:
    invoice_memo = None if description_hash else memo
    storeable_memo = memo

    ok, checking_id, payment_request, error_message = WALLET.create_invoice(
        amount=amount, memo=invoice_memo, description_hash=description_hash)
    if not ok:
        raise Exception(error_message or "Unexpected backend error.")

    invoice = bolt11.decode(payment_request)

    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,
        memo=storeable_memo,
        extra=extra,
    )

    g.db.commit()
    return invoice.payment_hash, payment_request
Exemplo n.º 2
0
 def validate_action(self, query: Dict[str, str]) -> 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}"')
Exemplo n.º 3
0
async def api_payments_decode():
    try:
        if g.data["data"][:5] == "LNURL":
            url = lnurl.decode(g.data["data"])
            return (
                jsonify({"domain": url}),
                HTTPStatus.OK,
            )
        else:
            invoice = bolt11.decode(g.data["data"])
            return (
                jsonify(
                    {
                        "payment_hash": invoice.payment_hash,
                        "amount_msat": invoice.amount_msat,
                        "description": invoice.description,
                        "description_hash": invoice.description_hash,
                        "payee": invoice.payee,
                        "date": invoice.date,
                        "expiry": invoice.expiry,
                        "secret": invoice.secret,
                        "route_hints": invoice.route_hints,
                        "min_final_cltv_expiry": invoice.min_final_cltv_expiry,
                    }
                ),
                HTTPStatus.OK,
            )
    except:
        return jsonify({"message": "Failed to decode"}), HTTPStatus.BAD_REQUEST
Exemplo n.º 4
0
def lndhub_payinvoice():
    try:
        pay_invoice(
            wallet_id=g.wallet.id,
            payment_request=g.data["invoice"],
            extra={"tag": "lndhub"},
        )
    except Exception as e:
        return jsonify({
            "error": True,
            "code": 10,
            "message": "Payment failed: " + str(e),
        })

    invoice: bolt11.Invoice = bolt11.decode(g.data["invoice"])
    return jsonify({
        "payment_error": "",
        "payment_preimage": "0" * 64,
        "route": {},
        "payment_hash": invoice.payment_hash,
        "decoded": decoded_as_lndhub(invoice),
        "fee_msat": 0,
        "type": "paid_invoice",
        "fee": 0,
        "value": invoice.amount_msat / 1000,
        "timestamp": int(time.time()),
        "memo": invoice.description,
    })
Exemplo n.º 5
0
def delete_expired_invoices() -> None:
    rows = g.db.fetchall(
        """
        SELECT bolt11
        FROM apipayments
        WHERE pending = 1 AND amount > 0 AND time < strftime('%s', 'now') - 86400
    """
    )
    for (payment_request,) in rows:
        try:
            invoice = bolt11.decode(payment_request)
        except:
            continue

        expiration_date = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry)
        if expiration_date > datetime.datetime.utcnow():
            continue

        g.db.execute(
            """
            DELETE FROM apipayments
            WHERE pending = 1 AND hash = ?
            """,
            (invoice.payment_hash,),
        )
Exemplo n.º 6
0
async def delete_expired_invoices(conn: Optional[Connection] = None, ) -> None:
    # first we delete all invoices older than one month
    await (conn or db).execute("""
        DELETE FROM apipayments
        WHERE pending = 1 AND amount > 0 AND time < strftime('%s', 'now') - 2592000
        """)

    # then we delete all expired invoices, checking one by one
    rows = await (conn or db).fetchall("""
        SELECT bolt11
        FROM apipayments
        WHERE pending = 1
          AND bolt11 IS NOT NULL
          AND amount > 0 AND time < strftime('%s', 'now') - 86400
        """)
    for (payment_request, ) in rows:
        try:
            invoice = bolt11.decode(payment_request)
        except:
            continue

        expiration_date = datetime.datetime.fromtimestamp(invoice.date +
                                                          invoice.expiry)
        if expiration_date > datetime.datetime.utcnow():
            continue

        await (conn or db).execute(
            """
            DELETE FROM apipayments
            WHERE pending = 1 AND hash = ?
            """,
            (invoice.payment_hash, ),
        )
Exemplo n.º 7
0
async def api_public_payment_longpolling(payment_hash):
    payment = await get_standalone_payment(payment_hash)

    if not payment:
        return jsonify({"message":
                        "Payment does not exist."}), HTTPStatus.NOT_FOUND
    elif not payment.pending:
        return jsonify({"status": "paid"}), HTTPStatus.OK

    try:
        invoice = bolt11.decode(payment.bolt11)
        expiration = datetime.datetime.fromtimestamp(invoice.date +
                                                     invoice.expiry)
        if expiration < datetime.datetime.now():
            return jsonify({"status": "expired"}), HTTPStatus.OK
    except:
        return jsonify({"message":
                        "Invalid bolt11 invoice."}), HTTPStatus.BAD_REQUEST

    send_payment, receive_payment = trio.open_memory_channel(0)

    print("adding standalone invoice listener", payment_hash, send_payment)
    api_invoice_listeners.append(send_payment)

    async for payment in receive_payment:
        if payment.payment_hash == payment_hash:
            return jsonify({"status": "paid"}), HTTPStatus.OK
Exemplo n.º 8
0
async def lndhub_addinvoice():
    try:
        _, pr = await create_invoice(
            wallet_id=g.wallet.id,
            amount=int(g.data["amt"]),
            memo=g.data["memo"],
            extra={"tag": "lndhub"},
        )
    except Exception as e:
        return jsonify(
            {
                "error": True,
                "code": 7,
                "message": "Failed to create invoice: " + str(e),
            }
        )

    invoice = bolt11.decode(pr)
    return jsonify(
        {
            "pay_req": pr,
            "payment_request": pr,
            "add_index": "500",
            "r_hash": to_buffer(invoice.payment_hash),
            "hash": invoice.payment_hash,
        }
    )
Exemplo n.º 9
0
    def GetInvoiceFromDbRecord(self, invoice, index):
        payment_request = invoice[6]
        pending = invoice[1]

        # IF IT IS PAID (NOT PENDING)
        if (pending == 0):
            settled = True
            state = ln.Invoice.InvoiceState.SETTLED
            amt_paid_sat = int(invoice[2] / 1000)
            settle_date = invoice[5]
        else:
            settled = False
            state = ln.Invoice.InvoiceState.OPEN
            amt_paid_sat = 0
            settle_date = None

        decoded = bolt11.decode(payment_request)
        payment_hash = decoded.payment_hash
        rhash = bytes.fromhex(payment_hash)

        return ln.Invoice(value=int(invoice[2] / 1000),
                          memo=invoice[4],
                          creation_date=invoice[5],
                          payment_request=invoice[6],
                          r_hash=rhash,
                          add_index=index,
                          expiry=decoded.expiry,
                          state=state,
                          settled=settled,
                          amt_paid_sat=amt_paid_sat,
                          settle_date=settle_date)
Exemplo n.º 10
0
def create_invoice(wallet_id: int,
                   amount: int,
                   memo: str,
                   description_hash: Optional[bytes] = None,
                   extra: Optional[Dict] = None,
                   webhook: Optional[str] = None) -> Tuple[str, str]:
    invoice_memo = None if description_hash else memo
    storeable_memo = memo

    ok, checking_id, payment_request, error_message = WALLET.create_invoice(
        amount=amount, memo=invoice_memo, description_hash=description_hash)
    if not ok:
        raise Exception(error_message or "Unexpected backend error.")

    invoice = bolt11.decode(payment_request)

    amount_msat = amount * 1000
    print(amount_msat)
    Payment.objects.create(wallet_id=wallet_id,
                           checking_id=checking_id,
                           payment_request=payment_request,
                           payment_hash=invoice.payment_hash,
                           amount=amount_msat,
                           memo=storeable_memo,
                           extra=json.dumps(extra) if extra and extra != {}
                           and type(extra) is dict else None,
                           webhook=webhook)

    return invoice.payment_hash, payment_request
Exemplo n.º 11
0
async def api_payments_create_invoice():
    if "description_hash" in g.data:
        description_hash = unhexlify(g.data["description_hash"])
        memo = ""
    else:
        description_hash = b""
        memo = g.data["memo"]

    try:
        payment_hash, payment_request = 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

    invoice = bolt11.decode(payment_request)
    return (
        jsonify({
            "payment_hash": invoice.payment_hash,
            "payment_request": payment_request,
            # maintain backwards compatibility with API clients:
            "checking_id": invoice.payment_hash,
        }),
        HTTPStatus.CREATED,
    )
Exemplo n.º 12
0
async def create_invoice(
    *,
    wallet_id: str,
    amount: int,  # in satoshis
    memo: str,
    description_hash: Optional[bytes] = None,
    extra: Optional[Dict] = None,
    webhook: Optional[str] = None,
    conn: Optional[Connection] = None,
) -> Tuple[str, str]:
    invoice_memo = None if description_hash else memo
    storeable_memo = memo

    ok, checking_id, payment_request, error_message = await WALLET.create_invoice(
        amount=amount, memo=invoice_memo, description_hash=description_hash)
    if not ok:
        raise InvoiceFailure(error_message or "Unexpected backend error.")

    invoice = bolt11.decode(payment_request)

    amount_msat = amount * 1000
    await create_payment(
        wallet_id=wallet_id,
        checking_id=checking_id,
        payment_request=payment_request,
        payment_hash=invoice.payment_hash,
        amount=amount_msat,
        memo=storeable_memo,
        extra=extra,
        webhook=webhook,
        conn=conn,
    )

    return invoice.payment_hash, payment_request
Exemplo n.º 13
0
Arquivo: api.py Projeto: arcbtc/lnbits
async def api_payments_create_invoice():
    if "description_hash" in g.data:
        description_hash = unhexlify(g.data["description_hash"])
        memo = ""
    else:
        description_hash = b""
        memo = g.data["memo"]

    try:
        payment_hash, payment_request = await create_invoice(
            wallet_id=g.wallet.id,
            amount=g.data["amount"],
            memo=memo,
            description_hash=description_hash,
            extra=g.data.get("extra"),
            webhook=g.data.get("webhook"),
        )
    except Exception as exc:
        await db.rollback()
        raise exc

    await db.commit()

    invoice = bolt11.decode(payment_request)

    lnurl_response: Union[None, bool, str] = None
    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,
                )
                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):
                lnurl_response = False

    return (
        jsonify(
            {
                "payment_hash": invoice.payment_hash,
                "payment_request": payment_request,
                # maintain backwards compatibility with API clients:
                "checking_id": invoice.payment_hash,
                "lnurl_response": lnurl_response,
            }
        ),
        HTTPStatus.CREATED,
    )
Exemplo n.º 14
0
def save_link_invoice(link_id: int, payment_request: str) -> None:
    inv = bolt11.decode(payment_request)

    with open_ext_db("lnurlp") as db:
        db.execute(
            """
            INSERT INTO invoices (pay_link, payment_hash, expiry)
            VALUES (?, ?, ?)
            """,
            (link_id, inv.payment_hash, inv.expiry),
        )
Exemplo n.º 15
0
async def lnurlwallet():
    memo = "LNbits LNURL funding"

    try:
        withdraw_res = handle_lnurl(request.args.get("lightning"))
        if not withdraw_res.ok:
            abort(HTTPStatus.BAD_REQUEST, f"Could not process LNURL-withdraw: {withdraw_res.error_msg}")
        if not isinstance(withdraw_res, LnurlWithdrawResponse):
            abort(HTTPStatus.BAD_REQUEST, "Not a valid LNURL-withdraw.")
    except LnurlException:
        abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process LNURL-withdraw.")

    try:
        ok, checking_id, payment_request, error_message = WALLET.create_invoice(withdraw_res.max_sats, memo)
    except Exception as e:
        ok, error_message = False, str(e)

    if not ok:
        abort(HTTPStatus.INTERNAL_SERVER_ERROR, error_message)

    r = requests.get(
        withdraw_res.callback.base,
        params={**withdraw_res.callback.query_params, **{"k1": withdraw_res.k1, "pr": payment_request}},
    )

    if not r.ok:
        abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process LNURL-withdraw.")

    inv = bolt11.decode(payment_request)

    for i in range(10):
        invoice_status = WALLET.get_invoice_status(checking_id)
        sleep(i)
        if not invoice_status.paid:
            continue
        break

    user = get_user(create_account().id)
    wallet = create_wallet(user_id=user.id)
    create_payment(
        wallet_id=wallet.id,
        checking_id=checking_id,
        amount=withdraw_res.max_sats * 1000,
        memo=memo,
        pending=invoice_status.pending,
        payment_request=payment_request,
        payment_hash=inv.payment_hash,
    )

    return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))
Exemplo n.º 16
0
    def DecodePayReq(self, request, context):
        decoded = bolt11.decode(request.pay_req)
        try:
            pr = ln.PayReq(timestamp=int(time.time()),
                           destination=decoded.payee,
                           payment_hash=decoded.payment_hash,
                           num_satoshis=int(decoded.amount_msat / 1000),
                           expiry=decoded.expiry,
                           description=decoded.description,
                           description_hash=decoded.description_hash,
                           payment_addr=bytes.fromhex(decoded.payee),
                           features={})
        except Exception as inst:
            print("error")
            print(inst)

        return pr
Exemplo n.º 17
0
async def api_public_payment_longpolling(payment_hash):
    payment = await get_standalone_payment(payment_hash)

    if not payment:
        return jsonify({"message": "Payment does not exist."}), HTTPStatus.NOT_FOUND
    elif not payment.pending:
        return jsonify({"status": "paid"}), HTTPStatus.OK

    try:
        invoice = bolt11.decode(payment.bolt11)
        expiration = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry)
        if expiration < datetime.datetime.now():
            return jsonify({"status": "expired"}), HTTPStatus.OK
    except:
        return jsonify({"message": "Invalid bolt11 invoice."}), HTTPStatus.BAD_REQUEST

    send_payment, receive_payment = trio.open_memory_channel(0)

    print("adding standalone invoice listener", payment_hash, send_payment)
    api_invoice_listeners.append(send_payment)

    response = None

    async def payment_info_receiver(cancel_scope):
        async for payment in receive_payment:
            if payment.payment_hash == payment_hash:
                nonlocal response
                response = (jsonify({"status": "paid"}), HTTPStatus.OK)
                cancel_scope.cancel()

    async def timeouter(cancel_scope):
        await trio.sleep(45)
        cancel_scope.cancel()

    async with trio.open_nursery() as nursery:
        nursery.start_soon(payment_info_receiver, nursery.cancel_scope)
        nursery.start_soon(timeouter, nursery.cancel_scope)

    if response:
        return response
    else:
        return jsonify({"message": "timeout"}), HTTPStatus.REQUEST_TIMEOUT
Exemplo n.º 18
0
    async def pay_invoice(self, bolt11: str,
                          fee_limit_msat: int) -> PaymentResponse:
        invoice = lnbits_bolt11.decode(bolt11)
        fee_limit_percent = fee_limit_msat / invoice.amount_msat * 100

        payload = {
            "bolt11": bolt11,
            "maxfeepercent": "{:.11}".format(fee_limit_percent),
            "exemptfee":
            0  # so fee_limit_percent is applied even on payments with fee under 5000 millisatoshi (which is default value of exemptfee)
        }

        try:
            r = self.ln.call("pay", payload)
        except RpcError as exc:
            return PaymentResponse(False, None, 0, None, str(exc))

        fee_msat = r["msatoshi_sent"] - r["msatoshi"]
        preimage = r["payment_preimage"]
        return PaymentResponse(True, r["payment_hash"], fee_msat, preimage,
                               None)
Exemplo n.º 19
0
    def AddInvoice(self, request, context):
        print("s" + request.memo + "s")
        memo = request.memo
        if not memo:
            memo = "new request"
        params = {'out': False, 'amount': request.value, 'memo': memo}
        invoices = self.GetInvoices(context)
        add_index = len(invoices) + 1
        print("AddInvoice")
        test_url = "https://ptsv2.com/t/qu294-1600515698/post"
        resp = self.DoPostWithApiKey(self.add_invoice_url, params, context)
        #resp = self.DoPostWithApiKey(test_url, params, context)
        payment_request = resp["payment_request"]
        decoded = bolt11.decode(payment_request)
        payment_hash = decoded.payment_hash
        rhash = bytes.fromhex(payment_hash)

        return ln.AddInvoiceResponse(
            # add_index=resp["checking_id"], no tengo index en la response de lnbits
            # add_index=int(time.time()),
            add_index=add_index,
            r_hash=rhash,
            payment_request=payment_request)
Exemplo n.º 20
0
def pay_invoice(wallet_id: int,
                payment_request: str,
                max_sat: Optional[int] = None,
                extra: Optional[Dict] = None,
                description: str = "") -> str:
    temp_id: str = f"temp_{shortuuid.uuid()}"
    internal_id: str = f"internal_{shortuuid.uuid()}"
    invoice: 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,
                                         memo=description
                                         or invoice.description or "",
                                         extra=extra)
    try:
        payment = Payment.objects.get(payment_hash=invoice.payment_hash,
                                      pending=True,
                                      amount__gt=0)
    except Payment.DoesNotExist:
        payment = None
    if payment:
        Payment.objects.create(checking_id=internal_id,
                               fee=0,
                               pending=False,
                               **payment_kwargs)
    else:
        fee_reserve = max(1000, int(invoice.amount_msat * 0.01))
        Payment.objects.create(checking_id=temp_id,
                               fee=-fee_reserve,
                               **payment_kwargs)
    try:
        wallet = Wallet.objects.get(id=wallet_id)
    except Wallet.DoesNotExist:
        raise ValueError("Wallet {} not found".format(wallet_id))
    if wallet.balance < 0:
        raise PermissionError("Insufficient balance")

    if payment:
        Payment.objects.get(checking_id=payment.checking_id, pending=False)
    else:
        ln_payment: PaymentResponse = WALLET.pay_invoice(payment_request)
        if ln_payment.ok and ln_payment.checking_id:
            Payment.objects.create(checking_id=ln_payment.checking_id,
                                   fee=ln_payment.fee_msat,
                                   preimage=ln_payment.preimage,
                                   pending=False,
                                   **payment_kwargs)
            Payment.objects.filter(checking_id=temp_id).delete()
        else:
            Payment.objects.filter(checking_id=temp_id).delete()
            raise Exception(payment.error_message
                            or "Failed to pay_invoice on backend")

    return invoice.payment_hash
Exemplo n.º 21
0
async def api_payments_create_invoice():
    if "description_hash" in g.data:
        description_hash = unhexlify(g.data["description_hash"])
        memo = ""
    else:
        description_hash = b""
        memo = g.data["memo"]

    async with db.connect() as conn:
        try:
            payment_hash, payment_request = await create_invoice(
                wallet_id=g.wallet.id,
                amount=g.data["amount"],
                memo=memo,
                description_hash=description_hash,
                extra=g.data.get("extra"),
                webhook=g.data.get("webhook"),
                conn=conn,
            )
        except InvoiceFailure as e:
            return jsonify({"message": str(e)}), 520
        except Exception as exc:
            raise exc

    invoice = bolt11.decode(payment_request)

    lnurl_response: Union[None, bool, str] = None
    if g.data.get("lnurl_callback"):
        if "lnurl_balance_check" in g.data:
            save_balance_check(g.wallet.id, g.data["lnurl_balance_check"])

        async with httpx.AsyncClient() as client:
            try:
                r = await client.get(
                    g.data["lnurl_callback"],
                    params={
                        "pr":
                        payment_request,
                        "balanceNotify":
                        url_for(
                            "core.lnurl_balance_notify",
                            service=urlparse(g.data["lnurl_callback"]).netloc,
                            wal=g.wallet.id,
                            _external=True,
                        ),
                    },
                    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):
                lnurl_response = False

    return (
        jsonify({
            "payment_hash": invoice.payment_hash,
            "payment_request": payment_request,
            # maintain backwards compatibility with API clients:
            "checking_id": invoice.payment_hash,
            "lnurl_response": lnurl_response,
        }),
        HTTPStatus.CREATED,
    )
Exemplo n.º 22
0
async def api_payments_pay_lnurl():
    domain = urlparse(g.data["callback"]).netloc

    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,
            )
            if r.is_error:
                return jsonify({"message":
                                "failed to connect"}), HTTPStatus.BAD_REQUEST
        except (httpx.ConnectError, httpx.RequestError):
            return jsonify({"message":
                            "failed to connect"}), HTTPStatus.BAD_REQUEST

    params = json.loads(r.text)
    if params.get("status") == "ERROR":
        return (
            jsonify(
                {"message": f"{domain} said: '{params.get('reason', '')}'"}),
            HTTPStatus.BAD_REQUEST,
        )

    invoice = bolt11.decode(params["pr"])
    if invoice.amount_msat != g.data["amount"]:
        return (
            jsonify({
                "message":
                f"{domain} returned an invalid invoice. Expected {g.data['amount']} msat, got {invoice.amount_msat}."
            }),
            HTTPStatus.BAD_REQUEST,
        )
    if invoice.description_hash != g.data["description_hash"]:
        return (
            jsonify({
                "message":
                f"{domain} returned an invalid invoice. Expected description_hash == {g.data['description_hash']}, got {invoice.description_hash}."
            }),
            HTTPStatus.BAD_REQUEST,
        )

    extra = {}

    if params.get("successAction"):
        extra["success_action"] = params["successAction"]
    if g.data["comment"]:
        extra["comment"] = g.data["comment"]

    payment_hash = await pay_invoice(
        wallet_id=g.wallet.id,
        payment_request=params["pr"],
        description=g.data.get("description", ""),
        extra=extra,
    )

    return (
        jsonify({
            "success_action": params.get("successAction"),
            "payment_hash": payment_hash,
            # maintain backwards compatibility with API clients:
            "checking_id": payment_hash,
        }),
        HTTPStatus.CREATED,
    )
Exemplo n.º 23
0
def lndhub_decodeinvoice():
    invoice = request.args.get("invoice")
    inv = bolt11.decode(invoice)
    return jsonify(decoded_as_lndhub(inv))
Exemplo n.º 24
0
def pay_invoice(*,
                wallet_id: str,
                payment_request: str,
                max_sat: Optional[int] = None,
                extra: Optional[Dict] = None) -> str:
    temp_id = f"temp_{urlsafe_short_hash()}"
    internal_id = f"internal_{urlsafe_short_hash()}"

    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,
        memo=invoice.description or "",
        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)
    assert wallet
    if wallet.balance_msat < 0:
        g.db.rollback()
        raise PermissionError("Insufficient balance.")
    else:
        g.db.commit()

    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
        ok, checking_id, fee_msat, error_message = WALLET.pay_invoice(
            payment_request)
        if ok:
            create_payment(checking_id=checking_id,
                           fee=fee_msat,
                           **payment_kwargs)
            delete_payment(temp_id)
        else:
            raise Exception(error_message
                            or "Failed to pay_invoice on backend.")

    g.db.commit()
    return invoice.payment_hash
Exemplo n.º 25
0
async def pay_invoice(
    *,
    wallet_id: str,
    payment_request: str,
    max_sat: Optional[int] = None,
    extra: Optional[Dict] = None,
    description: str = "",
    conn: Optional[Connection] = None,
) -> str:
    invoice = bolt11.decode(payment_request)
    fee_reserve_msat = fee_reserve(invoice.amount_msat)

    async with (db.reuse_conn(conn) if conn else db.connect()) as conn:
        temp_id = f"temp_{urlsafe_short_hash()}"
        internal_id = f"internal_{urlsafe_short_hash()}"

        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,
            memo=description or invoice.description or "",
            extra=extra,
        )

        # check_internal() returns the checking_id of the invoice we're waiting for
        internal_checking_id = await check_internal(invoice.payment_hash,
                                                    conn=conn)
        if internal_checking_id:
            # create a new payment from this wallet
            await create_payment(
                checking_id=internal_id,
                fee=0,
                pending=False,
                conn=conn,
                **payment_kwargs,
            )
        else:
            # create a temporary payment here so we can check if
            # the balance is enough in the next step
            await create_payment(
                checking_id=temp_id,
                fee=-fee_reserve_msat,
                conn=conn,
                **payment_kwargs,
            )

        # do the balance check
        wallet = await get_wallet(wallet_id, conn=conn)
        assert wallet
        if wallet.balance_msat < 0:
            raise PermissionError("Insufficient balance.")

    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
        async with db.connect() as conn:
            await update_payment_status(
                checking_id=internal_checking_id,
                pending=False,
                conn=conn,
            )

        # notify receiver asynchronously
        from lnbits.tasks import internal_invoice_paid

        await internal_invoice_paid.send(internal_checking_id)
    else:
        # actually pay the external invoice
        payment: PaymentResponse = await WALLET.pay_invoice(
            payment_request, fee_reserve_msat)
        if payment.checking_id:
            async with db.connect() as conn:
                await create_payment(
                    checking_id=payment.checking_id,
                    fee=payment.fee_msat,
                    preimage=payment.preimage,
                    pending=payment.ok == None,
                    conn=conn,
                    **payment_kwargs,
                )
                await delete_payment(temp_id, conn=conn)
        else:
            async with db.connect() as conn:
                await delete_payment(temp_id, conn=conn)
            raise PaymentFailure(
                payment.error_message or
                "Payment failed, but backend didn't give us an error message.")

    return invoice.payment_hash