support description_hash across all APIs.

This commit is contained in:
fiatjaf 2020-06-07 19:46:16 -03:00
parent 61f736878c
commit bc27293315
10 changed files with 89 additions and 45 deletions

View file

@ -7,9 +7,12 @@ from lnbits.settings import WALLET
from .crud import get_wallet, create_payment, delete_payment from .crud import get_wallet, create_payment, delete_payment
def create_invoice(*, wallet_id: str, amount: int, memo: str) -> Tuple[str, str]: def create_invoice(*, wallet_id: str, amount: int, memo: str, description_hash: bytes) -> Tuple[str, str]:
try: try:
ok, checking_id, payment_request, error_message = WALLET.create_invoice(amount=amount, memo=memo) ok, checking_id, payment_request, error_message = WALLET.create_invoice(
amount=amount, memo=memo, description_hash=description_hash
)
except Exception as e: except Exception as e:
ok, error_message = False, str(e) ok, error_message = False, str(e)
@ -35,11 +38,7 @@ def pay_invoice(*, wallet_id: str, bolt11: str, max_sat: Optional[int] = None) -
fee_reserve = max(1000, int(invoice.amount_msat * 0.01)) fee_reserve = max(1000, int(invoice.amount_msat * 0.01))
create_payment( create_payment(
wallet_id=wallet_id, wallet_id=wallet_id, checking_id=temp_id, amount=-invoice.amount_msat, fee=-fee_reserve, memo=temp_id,
checking_id=temp_id,
amount=-invoice.amount_msat,
fee=-fee_reserve,
memo=temp_id,
) )
wallet = get_wallet(wallet_id) wallet = get_wallet(wallet_id)

View file

@ -1,5 +1,6 @@
from flask import g, jsonify, request from flask import g, jsonify, request
from http import HTTPStatus from http import HTTPStatus
from binascii import unhexlify
from lnbits.core import core_app from lnbits.core import core_app
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
@ -27,13 +28,21 @@ def api_payments():
@api_validate_post_request( @api_validate_post_request(
schema={ schema={
"amount": {"type": "integer", "min": 1, "required": True}, "amount": {"type": "integer", "min": 1, "required": True},
"memo": {"type": "string", "empty": False, "required": True}, "memo": {"type": "string", "empty": False, "required": False},
"description_hash": {"type": "string", "empty": False, "required": False},
} }
) )
def api_payments_create_invoice(): 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: try:
checking_id, payment_request = create_invoice( checking_id, payment_request = create_invoice(
wallet_id=g.wallet.id, amount=g.data["amount"], memo=g.data["memo"] wallet_id=g.wallet.id, amount=g.data["amount"], memo=memo, description_hash=description_hash
) )
except Exception as e: except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR

View file

@ -26,7 +26,7 @@ class PaymentStatus(NamedTuple):
class Wallet(ABC): class Wallet(ABC):
@abstractmethod @abstractmethod
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: def create_invoice(self, amount: int, memo: str = "", description_hash: bytes = b"") -> InvoiceResponse:
pass pass
@abstractmethod @abstractmethod
@ -40,3 +40,7 @@ class Wallet(ABC):
@abstractmethod @abstractmethod
def get_payment_status(self, checking_id: str) -> PaymentStatus: def get_payment_status(self, checking_id: str) -> PaymentStatus:
pass pass
class Unsupported(Exception):
pass

View file

@ -7,18 +7,20 @@ import random
from os import getenv from os import getenv
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported
class CLightningWallet(Wallet): class CLightningWallet(Wallet):
def __init__(self): def __init__(self):
if LightningRpc is None: # pragma: nocover if LightningRpc is None: # pragma: nocover
raise ImportError("The `pylightning` library must be installed to use `CLightningWallet`.") raise ImportError("The `pylightning` library must be installed to use `CLightningWallet`.")
self.l1 = LightningRpc(getenv("CLIGHTNING_RPC")) self.l1 = LightningRpc(getenv("CLIGHTNING_RPC"))
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: def create_invoice(self, amount: int, memo: str = "", description_hash: bytes = b"") -> InvoiceResponse:
if description_hash:
raise Unsupported("description_hash")
label = "lbl{}".format(random.random()) label = "lbl{}".format(random.random())
r = self.l1.invoice(amount * 1000, label, memo, exposeprivatechannels=True) r = self.l1.invoice(amount * 1000, label, memo, exposeprivatechannels=True)
ok, checking_id, payment_request, error_message = True, r["payment_hash"], r["bolt11"], None ok, checking_id, payment_request, error_message = True, r["payment_hash"], r["bolt11"], None
@ -31,7 +33,7 @@ class CLightningWallet(Wallet):
def get_invoice_status(self, checking_id: str) -> PaymentStatus: def get_invoice_status(self, checking_id: str) -> PaymentStatus:
r = self.l1.listinvoices(checking_id) r = self.l1.listinvoices(checking_id)
if r['invoices'][0]['status'] == 'unpaid': if r["invoices"][0]["status"] == "unpaid":
return PaymentStatus(False) return PaymentStatus(False)
return PaymentStatus(True) return PaymentStatus(True)

View file

@ -1,5 +1,6 @@
from os import getenv from os import getenv
from requests import get, post from requests import get, post
from binascii import hexlify
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
@ -12,11 +13,16 @@ class LNbitsWallet(Wallet):
self.auth_admin = {"X-Api-Key": getenv("LNBITS_ADMIN_KEY")} self.auth_admin = {"X-Api-Key": getenv("LNBITS_ADMIN_KEY")}
self.auth_invoice = {"X-Api-Key": getenv("LNBITS_INVOICE_KEY")} self.auth_invoice = {"X-Api-Key": getenv("LNBITS_INVOICE_KEY")}
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: def create_invoice(self, amount: int, memo: str = "", description_hash: bytes = b"") -> InvoiceResponse:
r = post( r = post(
url=f"{self.endpoint}/api/v1/payments", url=f"{self.endpoint}/api/v1/payments",
headers=self.auth_invoice, headers=self.auth_invoice,
json={"out": False, "amount": amount, "memo": memo} json={
"out": False,
"amount": amount,
"memo": memo,
"description_hash": hexlify(description_hash).decode("ascii"),
},
) )
ok, checking_id, payment_request, error_message = r.ok, None, None, None ok, checking_id, payment_request, error_message = r.ok, None, None, None
@ -29,11 +35,7 @@ class LNbitsWallet(Wallet):
return InvoiceResponse(ok, checking_id, payment_request, error_message) return InvoiceResponse(ok, checking_id, payment_request, error_message)
def pay_invoice(self, bolt11: str) -> PaymentResponse: def pay_invoice(self, bolt11: str) -> PaymentResponse:
r = post( r = post(url=f"{self.endpoint}/api/v1/payments", headers=self.auth_admin, json={"out": True, "bolt11": bolt11})
url=f"{self.endpoint}/api/v1/payments",
headers=self.auth_admin,
json={"out": True, "bolt11": bolt11}
)
ok, checking_id, fee_msat, error_message = True, None, 0, None ok, checking_id, fee_msat, error_message = True, None, 0, None
if r.ok: if r.ok:
@ -50,7 +52,7 @@ class LNbitsWallet(Wallet):
if not r.ok: if not r.ok:
return PaymentStatus(None) return PaymentStatus(None)
return PaymentStatus(r.json()['paid']) return PaymentStatus(r.json()["paid"])
def get_payment_status(self, checking_id: str) -> PaymentStatus: def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = get(url=f"{self.endpoint}/api/v1/payments/{checking_id}", headers=self.auth_invoice) r = get(url=f"{self.endpoint}/api/v1/payments/{checking_id}", headers=self.auth_invoice)
@ -58,4 +60,4 @@ class LNbitsWallet(Wallet):
if not r.ok: if not r.ok:
return PaymentStatus(None) return PaymentStatus(None)
return PaymentStatus(r.json()['paid']) return PaymentStatus(r.json()["paid"])

View file

@ -23,7 +23,7 @@ class LndWallet(Wallet):
self.auth_read = getenv("LND_READ_MACAROON") self.auth_read = getenv("LND_READ_MACAROON")
self.auth_cert = getenv("LND_CERT") self.auth_cert = getenv("LND_CERT")
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: def create_invoice(self, amount: int, memo: str = "", description_hash: bytes = b"") -> InvoiceResponse:
lnd_rpc = lnd_grpc.Client( lnd_rpc = lnd_grpc.Client(
lnd_dir=None, lnd_dir=None,
macaroon_path=self.auth_invoice, macaroon_path=self.auth_invoice,
@ -33,7 +33,13 @@ class LndWallet(Wallet):
grpc_port=self.port, grpc_port=self.port,
) )
lndResponse = lnd_rpc.add_invoice(memo=memo, value=amount, expiry=600, private=True) lndResponse = lnd_rpc.add_invoice(
memo=memo,
description_hash=base64.b64encode(description_hash).decode("ascii"),
value=amount,
expiry=600,
private=True,
)
decoded_hash = base64.b64encode(lndResponse.r_hash).decode("utf-8").replace("/", "_") decoded_hash = base64.b64encode(lndResponse.r_hash).decode("utf-8").replace("/", "_")
print(lndResponse.r_hash) print(lndResponse.r_hash)
ok, checking_id, payment_request, error_message = True, decoded_hash, str(lndResponse.payment_request), None ok, checking_id, payment_request, error_message = True, decoded_hash, str(lndResponse.payment_request), None

View file

@ -1,5 +1,4 @@
from os import getenv from os import getenv
import os
import base64 import base64
from requests import get, post from requests import get, post
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
@ -18,13 +17,17 @@ class LndRestWallet(Wallet):
self.auth_read = {"Grpc-Metadata-macaroon": getenv("LND_REST_READ_MACAROON")} self.auth_read = {"Grpc-Metadata-macaroon": getenv("LND_REST_READ_MACAROON")}
self.auth_cert = getenv("LND_REST_CERT") self.auth_cert = getenv("LND_REST_CERT")
def create_invoice(self, amount: int, memo: str = "", description_hash: bytes = b"") -> InvoiceResponse:
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse:
r = post( r = post(
url=f"{self.endpoint}/v1/invoices", url=f"{self.endpoint}/v1/invoices",
headers=self.auth_invoice, verify=self.auth_cert, headers=self.auth_invoice,
json={"value": amount, "memo": memo, "private": True}, verify=self.auth_cert,
json={
"value": amount,
"memo": memo,
"description_hash": base64.b64encode(description_hash).decode("ascii"),
"private": True,
},
) )
print(self.auth_invoice) print(self.auth_invoice)
@ -44,10 +47,12 @@ class LndRestWallet(Wallet):
return InvoiceResponse(ok, checking_id, payment_request, error_message) return InvoiceResponse(ok, checking_id, payment_request, error_message)
def pay_invoice(self, bolt11: str) -> PaymentResponse: def pay_invoice(self, bolt11: str) -> PaymentResponse:
r = post( r = post(
url=f"{self.endpoint}/v1/channels/transactions", headers=self.auth_admin, verify=self.auth_cert, json={"payment_request": bolt11} url=f"{self.endpoint}/v1/channels/transactions",
headers=self.auth_admin,
verify=self.auth_cert,
json={"payment_request": bolt11},
) )
ok, checking_id, fee_msat, error_message = r.ok, None, 0, None ok, checking_id, fee_msat, error_message = r.ok, None, 0, None
r = get(url=f"{self.endpoint}/v1/payreq/{bolt11}", headers=self.auth_admin, verify=self.auth_cert,) r = get(url=f"{self.endpoint}/v1/payreq/{bolt11}", headers=self.auth_admin, verify=self.auth_cert,)
@ -59,7 +64,6 @@ class LndRestWallet(Wallet):
return PaymentResponse(ok, checking_id, fee_msat, error_message) return PaymentResponse(ok, checking_id, fee_msat, error_message)
def get_invoice_status(self, checking_id: str) -> PaymentStatus: def get_invoice_status(self, checking_id: str) -> PaymentStatus:
checking_id = checking_id.replace("_", "/") checking_id = checking_id.replace("_", "/")
print(checking_id) print(checking_id)
@ -71,7 +75,12 @@ class LndRestWallet(Wallet):
return PaymentStatus(r.json()["settled"]) return PaymentStatus(r.json()["settled"])
def get_payment_status(self, checking_id: str) -> PaymentStatus: def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = get(url=f"{self.endpoint}/v1/payments", headers=self.auth_admin, verify=self.auth_cert, params={"include_incomplete": "True", "max_payments": "20"}) r = get(
url=f"{self.endpoint}/v1/payments",
headers=self.auth_admin,
verify=self.auth_cert,
params={"include_incomplete": "True", "max_payments": "20"},
)
if not r.ok: if not r.ok:
return PaymentStatus(None) return PaymentStatus(None)

View file

@ -1,3 +1,4 @@
import base64
from os import getenv from os import getenv
from requests import get, post from requests import get, post
@ -15,11 +16,15 @@ class LNPayWallet(Wallet):
self.auth_read = getenv("LNPAY_READ_KEY") self.auth_read = getenv("LNPAY_READ_KEY")
self.auth_api = {"X-Api-Key": getenv("LNPAY_API_KEY")} self.auth_api = {"X-Api-Key": getenv("LNPAY_API_KEY")}
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: def create_invoice(self, amount: int, memo: str = "", description_hash: bytes = b"") -> InvoiceResponse:
r = post( r = post(
url=f"{self.endpoint}/user/wallet/{self.auth_invoice}/invoice", url=f"{self.endpoint}/user/wallet/{self.auth_invoice}/invoice",
headers=self.auth_api, headers=self.auth_api,
json={"num_satoshis": f"{amount}", "memo": memo}, json={
"num_satoshis": f"{amount}",
"memo": memo,
"description_hash": base64.b64encode(description_hash).decode("ascii"),
},
) )
ok, checking_id, payment_request, error_message = r.status_code == 201, None, None, None ok, checking_id, payment_request, error_message = r.status_code == 201, None, None, None

View file

@ -1,5 +1,6 @@
from os import getenv from os import getenv
from requests import post from requests import post
from binascii import hexlify
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
@ -13,8 +14,12 @@ class LntxbotWallet(Wallet):
self.auth_admin = {"Authorization": f"Basic {getenv('LNTXBOT_ADMIN_KEY')}"} self.auth_admin = {"Authorization": f"Basic {getenv('LNTXBOT_ADMIN_KEY')}"}
self.auth_invoice = {"Authorization": f"Basic {getenv('LNTXBOT_INVOICE_KEY')}"} self.auth_invoice = {"Authorization": f"Basic {getenv('LNTXBOT_INVOICE_KEY')}"}
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: def create_invoice(self, amount: int, memo: str = "", description_hash: bytes = b"") -> InvoiceResponse:
r = post(url=f"{self.endpoint}/addinvoice", headers=self.auth_invoice, json={"amt": str(amount), "memo": memo}) r = post(
url=f"{self.endpoint}/addinvoice",
headers=self.auth_invoice,
json={"amt": str(amount), "memo": memo, "description_hash": hexlify(description_hash).decode("ascii")},
)
ok, checking_id, payment_request, error_message = r.ok, None, None, None ok, checking_id, payment_request, error_message = r.ok, None, None, None
if r.ok: if r.ok:

View file

@ -1,7 +1,7 @@
from os import getenv from os import getenv
from requests import get, post from requests import get, post
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported
class OpenNodeWallet(Wallet): class OpenNodeWallet(Wallet):
@ -13,7 +13,10 @@ class OpenNodeWallet(Wallet):
self.auth_admin = {"Authorization": getenv("OPENNODE_ADMIN_KEY")} self.auth_admin = {"Authorization": getenv("OPENNODE_ADMIN_KEY")}
self.auth_invoice = {"Authorization": getenv("OPENNODE_INVOICE_KEY")} self.auth_invoice = {"Authorization": getenv("OPENNODE_INVOICE_KEY")}
def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: def create_invoice(self, amount: int, memo: str = "", description_hash: bytes = b"") -> InvoiceResponse:
if description_hash:
raise Unsupported("description_hash")
r = post( r = post(
url=f"{self.endpoint}/v1/charges", url=f"{self.endpoint}/v1/charges",
headers=self.auth_invoice, headers=self.auth_invoice,