Browse Source

simplify environment variables required.

instead of multiple keys/macaroons with different permissions we request only one.
if someone wants to use lnbits with an invoice macaroon they're free to do it and
we will just fail on 'pay' methods, as before.

this also grandfathers the previous environment variables names so everything keeps
working without people having to change their setups.

in the meantime some bugs with lntxbot and c-lightning were fixed and the `requests`
dependency was eliminated because I can't organize myself into meaningful chunks of
changes.
atmext
fiatjaf 4 years ago
parent
commit
9185342c72
  1. 18
      .env.example
  2. 1
      Pipfile
  3. 55
      Pipfile.lock
  4. 7
      lnbits/core/views/api.py
  5. 6
      lnbits/extensions/amilk/views_api.py
  6. 6
      lnbits/extensions/diagonalley/crud.py
  7. 4
      lnbits/extensions/example/views_api.py
  8. 2
      lnbits/wallets/clightning.py
  9. 37
      lnbits/wallets/lnbits.py
  10. 16
      lnbits/wallets/lndgrpc.py
  11. 35
      lnbits/wallets/lndrest.py
  12. 16
      lnbits/wallets/lnpay.py
  13. 59
      lnbits/wallets/lntxbot.py
  14. 11
      lnbits/wallets/opennode.py
  15. 11
      requirements.txt

18
.env.example

@ -29,34 +29,28 @@ CLIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc"
# LnbitsWallet # LnbitsWallet
LNBITS_ENDPOINT=http://127.0.0.1:5000 LNBITS_ENDPOINT=http://127.0.0.1:5000
LNBITS_INVOICE_KEY=LNBITS_INVOICE_KEY LNBITS_KEY=LNBITS_ADMIN_KEY
LNBITS_ADMIN_KEY=LNBITS_ADMIN_KEY
# LndWallet # LndWallet
LND_GRPC_ENDPOINT=127.0.0.1 LND_GRPC_ENDPOINT=127.0.0.1
LND_GRPC_PORT=11009 LND_GRPC_PORT=11009
LND_GRPC_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_GRPC_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_GRPC_ADMIN_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon" LND_GRPC_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon"
LND_GRPC_INVOICE_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/invoice.macaroon"
# LndRestWallet # LndRestWallet
LND_REST_ENDPOINT=https://127.0.0.1:8080/ LND_REST_ENDPOINT=https://127.0.0.1:8080/
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert" LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_REST_ADMIN_MACAROON="HEXSTRING" LND_REST_MACAROON="HEXSTRING"
LND_REST_INVOICE_MACAROON="HEXSTRING"
# LNPayWallet # LNPayWallet
LNPAY_API_ENDPOINT=https://lnpay.co/v1/ LNPAY_API_ENDPOINT=https://lnpay.co/v1/
LNPAY_API_KEY=LNPAY_API_KEY LNPAY_API_KEY=LNPAY_API_KEY
LNPAY_ADMIN_KEY=LNPAY_ADMIN_KEY LNPAY_WALLET_KEY=LNPAY_ADMIN_KEY
LNPAY_INVOICE_KEY=LNPAY_INVOICE_KEY
# LntxbotWallet # LntxbotWallet
LNTXBOT_API_ENDPOINT=https://lntxbot.bigsun.xyz/ LNTXBOT_API_ENDPOINT=https://lntxbot.bigsun.xyz/
LNTXBOT_ADMIN_KEY=LNTXBOT_ADMIN_KEY LNTXBOT_KEY=LNTXBOT_ADMIN_KEY
LNTXBOT_INVOICE_KEY=LNTXBOT_INVOICE_KEY
# OpenNodeWallet # OpenNodeWallet
OPENNODE_API_ENDPOINT=https://api.opennode.com/ OPENNODE_API_ENDPOINT=https://api.opennode.com/
OPENNODE_ADMIN_KEY=OPENNODE_ADMIN_KEY OPENNODE_KEY=OPENNODE_ADMIN_KEY
OPENNODE_INVOICE_KEY=OPENNODE_INVOICE_KEY

1
Pipfile

@ -13,7 +13,6 @@ ecdsa = "*"
environs = "*" environs = "*"
lnurl = "*" lnurl = "*"
pyscss = "*" pyscss = "*"
requests = "*"
shortuuid = "*" shortuuid = "*"
quart = "*" quart = "*"
quart-cors = "*" quart-cors = "*"

55
Pipfile.lock

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "76a3823f58d720ea680fdcd246f2a8b5fa16ce0a87a650e5e9fff5559dca7309" "sha256": "193ef930ac0906127fd292b2fc7ba5b4d786227b6795d182dad9b57448fca75e"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -106,13 +106,6 @@
], ],
"version": "==2020.6.20" "version": "==2020.6.20"
}, },
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": { "click": {
"hashes": [ "hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
@ -139,10 +132,10 @@
}, },
"h11": { "h11": {
"hashes": [ "hashes": [
"sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1", "sha256:3c6c61d69c6f13d41f1b80ab0322f1872702a3ba26e12aa864c928f6a43fbaab",
"sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1" "sha256:ab6c335e1b6ef34b205d5ca3e228c9299cc7218b049819ec84a388c2525e5d87"
], ],
"version": "==0.9.0" "version": "==0.11.0"
}, },
"h2": { "h2": {
"hashes": [ "hashes": [
@ -162,30 +155,30 @@
}, },
"httpcore": { "httpcore": {
"hashes": [ "hashes": [
"sha256:72cfaa461dbdc262943ff4c9abf5b195391a03cdcc152e636adb4239b15e77e1", "sha256:18c4afcbfe884b635e59739105aed1692e132bc5d31597109f3c1c97e4ec1cac",
"sha256:a35dddd1f4cc34ff37788337ef507c0ad0276241ece6daf663ac9e77c0b87232" "sha256:2526a38f31ac5967d38b7f593b5d8c4bd3fa82c21400402f866ba3312946acbf"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==0.11.1" "version": "==0.12.0"
}, },
"httpx": { "httpx": {
"hashes": [ "hashes": [
"sha256:02326f2d3c61133db31e4b88dd3432479b434e52a68d813eab6db930f13611ea", "sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537",
"sha256:254b371e3880a8e2387bf9ead6949bac797bd557fda26eba19a6153a0c06bd2b" "sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.15.5" "version": "==0.16.1"
}, },
"hypercorn": { "hypercorn": {
"extras": [ "extras": [
"trio" "trio"
], ],
"hashes": [ "hashes": [
"sha256:6540faeba9dd44f7e74c7cc1beae3a438a7efb5f77323d1199457da46d32c2c2", "sha256:81c69dd84a87b8e8b3ebf06ef5dd92836a8238f0ac65ded3d86befb8ba9acfeb",
"sha256:b5c479023757e279f954b46a4ec9dd85e58a2bcbf4d959d5601cbced593e711d" "sha256:e3f317d6d64d15ce589f49e4f5057947259fa35332d169e62cb060e9997189e4"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.11.0" "version": "==0.11.1"
}, },
"hyperframe": { "hyperframe": {
"hashes": [ "hashes": [
@ -356,14 +349,6 @@
"index": "pypi", "index": "pypi",
"version": "==0.5.1" "version": "==0.5.1"
}, },
"requests": {
"hashes": [
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
],
"index": "pypi",
"version": "==2.24.0"
},
"rfc3986": { "rfc3986": {
"extras": [ "extras": [
"idna2008" "idna2008"
@ -437,14 +422,6 @@
"index": "pypi", "index": "pypi",
"version": "==3.7.4.3" "version": "==3.7.4.3"
}, },
"urllib3": {
"hashes": [
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.25.10"
},
"werkzeug": { "werkzeug": {
"hashes": [ "hashes": [
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
@ -657,12 +634,17 @@
"hashes": [ "hashes": [
"sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef", "sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef",
"sha256:1fe0a41437bbd06063aa184c34804efa886bcc128222e9916310c92cd54c3b4c", "sha256:1fe0a41437bbd06063aa184c34804efa886bcc128222e9916310c92cd54c3b4c",
"sha256:3d20024a70b97b4f9546696cbf2fd30bae5f42229fbddf8661261b1eaff0deb7",
"sha256:41bb65f54bba392643557e617316d0d899ed5b4946dccee1cb6696152b29844b", "sha256:41bb65f54bba392643557e617316d0d899ed5b4946dccee1cb6696152b29844b",
"sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c", "sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c",
"sha256:4707f3695b34335afdfb09be3802c87fa0bc27030471dbc082f815f23688bc63", "sha256:4707f3695b34335afdfb09be3802c87fa0bc27030471dbc082f815f23688bc63",
"sha256:49f23ebd5ac073765ecbcf046edc10d63dcab2f4ae2bce160982cb30df0c0302",
"sha256:5533a959a1748a5c042a6da71fe9267a908e21eded7a4f373efd23a2cbdb0ecc", "sha256:5533a959a1748a5c042a6da71fe9267a908e21eded7a4f373efd23a2cbdb0ecc",
"sha256:5d892a4f1c999834eaa3c32bc9e8b976c5825116cde553928c4c8e7e48ebda67",
"sha256:5f18875ac23d9aa2f060838e8b79093e8bb2313dbaaa9f54c6d8e52a5df097be", "sha256:5f18875ac23d9aa2f060838e8b79093e8bb2313dbaaa9f54c6d8e52a5df097be",
"sha256:60b0e9e6dc45683e569ec37c55ac20c582973841927a85f2d8a7d20ee80216ab", "sha256:60b0e9e6dc45683e569ec37c55ac20c582973841927a85f2d8a7d20ee80216ab",
"sha256:816064fc915796ea1f26966163f6845de5af78923dfcecf6551e095f00983650",
"sha256:84cada8effefe9a9f53f9b0d2ba9b7b6f5edf8d2155f9fdbe34616e06ececf81",
"sha256:84e9407db1b2eb368b7ecc283121b5e592c9aaedbe8c78b1a2f1102eb2e21d19", "sha256:84e9407db1b2eb368b7ecc283121b5e592c9aaedbe8c78b1a2f1102eb2e21d19",
"sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637", "sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637",
"sha256:9a02d0ae31d35e1ec12a4ea4d4cca990800f66a917d0fb997b20fbc13f5321fc", "sha256:9a02d0ae31d35e1ec12a4ea4d4cca990800f66a917d0fb997b20fbc13f5321fc",
@ -670,6 +652,7 @@
"sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d", "sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d",
"sha256:ae91972f8ac958039920ef6e8769277c084971a142ce2b660691793ae44aae6b", "sha256:ae91972f8ac958039920ef6e8769277c084971a142ce2b660691793ae44aae6b",
"sha256:c570f6fa14b9c4c8a4924aaad354652366577b4f98213cf76305067144f7b100", "sha256:c570f6fa14b9c4c8a4924aaad354652366577b4f98213cf76305067144f7b100",
"sha256:c9443124c67b1515e4fe0bb0aa18df640965e1030f468a2a5dc2589b26d130ad",
"sha256:d23a18037313714fb3bb5a94434d3151ee4300bae631894b1ac08111abeaa4a3", "sha256:d23a18037313714fb3bb5a94434d3151ee4300bae631894b1ac08111abeaa4a3",
"sha256:eaf548d117b6737df379fdd53bdde4f08870e66d7ea653e230477f071f861121", "sha256:eaf548d117b6737df379fdd53bdde4f08870e66d7ea653e230477f071f861121",
"sha256:ebbe29186a3d9b0c591e71b7393f1ae08c83cb2d8e517d2a822b8f7ec99dfd8b", "sha256:ebbe29186a3d9b0c591e71b7393f1ae08c83cb2d8e517d2a822b8f7ec99dfd8b",

7
lnbits/core/views/api.py

@ -1,5 +1,6 @@
import trio # type: ignore import trio # type: ignore
import json import json
import traceback
from quart import g, jsonify, request, make_response from quart import g, jsonify, request, make_response
from http import HTTPStatus from http import HTTPStatus
from binascii import unhexlify from binascii import unhexlify
@ -87,10 +88,10 @@ async def api_payments_pay_invoice():
return jsonify({"message": str(e)}), HTTPStatus.BAD_REQUEST return jsonify({"message": str(e)}), HTTPStatus.BAD_REQUEST
except PermissionError as e: except PermissionError as e:
return jsonify({"message": str(e)}), HTTPStatus.FORBIDDEN return jsonify({"message": str(e)}), HTTPStatus.FORBIDDEN
except Exception as e: except Exception as exc:
print(e) traceback.print_exc(7)
g.db.rollback() g.db.rollback()
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR return jsonify({"message": str(exc)}), HTTPStatus.INTERNAL_SERVER_ERROR
return ( return (
jsonify( jsonify(

6
lnbits/extensions/amilk/views_api.py

@ -1,4 +1,4 @@
import requests import httpx
from quart import g, jsonify, request, abort from quart import g, jsonify, request, abort
from http import HTTPStatus from http import HTTPStatus
from lnurl import LnurlWithdrawResponse, handle as handle_lnurl from lnurl import LnurlWithdrawResponse, handle as handle_lnurl
@ -38,12 +38,12 @@ async def api_amilkit(amilk_id):
wallet_id=milk.wallet, amount=withdraw_res.max_sats, memo=memo, extra={"tag": "amilk"} wallet_id=milk.wallet, amount=withdraw_res.max_sats, memo=memo, extra={"tag": "amilk"}
) )
r = requests.get( r = httpx.get(
withdraw_res.callback.base, withdraw_res.callback.base,
params={**withdraw_res.callback.query_params, **{"k1": withdraw_res.k1, "pr": payment_request}}, params={**withdraw_res.callback.query_params, **{"k1": withdraw_res.k1, "pr": payment_request}},
) )
if not r.ok: if r.is_error:
abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.") abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.")
for i in range(10): for i in range(10):

6
lnbits/extensions/diagonalley/crud.py

@ -1,7 +1,7 @@
from base64 import urlsafe_b64encode from base64 import urlsafe_b64encode
from uuid import uuid4 from uuid import uuid4
from typing import List, Optional, Union from typing import List, Optional, Union
import requests import httpx
from lnbits.db import open_ext_db from lnbits.db import open_ext_db
from lnbits.settings import WALLET from lnbits.settings import WALLET
from .models import Products, Orders, Indexers from .models import Products, Orders, Indexers
@ -120,7 +120,7 @@ def get_diagonalleys_indexer(indexer_id: str) -> Optional[Indexers]:
with open_ext_db("diagonalley") as db: with open_ext_db("diagonalley") as db:
roww = db.fetchone("SELECT * FROM indexers WHERE id = ?", (indexer_id,)) roww = db.fetchone("SELECT * FROM indexers WHERE id = ?", (indexer_id,))
try: try:
x = requests.get(roww["indexeraddress"] + "/" + roww["ratingkey"]) x = httpx.get(roww["indexeraddress"] + "/" + roww["ratingkey"])
if x.status_code == 200: if x.status_code == 200:
print(x) print(x)
print("poo") print("poo")
@ -158,7 +158,7 @@ def get_diagonalleys_indexers(wallet_ids: Union[str, List[str]]) -> List[Indexer
for r in rows: for r in rows:
try: try:
x = requests.get(r["indexeraddress"] + "/" + r["ratingkey"]) x = httpx.get(r["indexeraddress"] + "/" + r["ratingkey"])
if x.status_code == 200: if x.status_code == 200:
with open_ext_db("diagonalley") as db: with open_ext_db("diagonalley") as db:
db.execute( db.execute(

4
lnbits/extensions/example/views_api.py

@ -3,7 +3,9 @@
# add your dependencies here # add your dependencies here
# import json # import json
# import requests # import httpx
# (use httpx just like requests, except instead of response.ok there's only the
# response.is_error that is its inverse)
from quart import jsonify from quart import jsonify
from http import HTTPStatus from http import HTTPStatus

2
lnbits/wallets/clightning.py

@ -73,7 +73,7 @@ class CLightningWallet(Wallet):
raise KeyError("supplied an invalid checking_id") raise KeyError("supplied an invalid checking_id")
def get_payment_status(self, checking_id: str) -> PaymentStatus: def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = self.ln.listpays(payment_hash=checking_id) r = self.ln.call("listpays", {"payment_hash": checking_id})
if not r["pays"]: if not r["pays"]:
return PaymentStatus(False) return PaymentStatus(False)
if r["pays"][0]["payment_hash"] == checking_id: if r["pays"][0]["payment_hash"] == checking_id:

37
lnbits/wallets/lnbits.py

@ -1,7 +1,7 @@
import trio # type: ignore import trio # type: ignore
import httpx
from os import getenv from os import getenv
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
from requests import get, post
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
@ -11,8 +11,9 @@ class LNbitsWallet(Wallet):
def __init__(self): def __init__(self):
self.endpoint = getenv("LNBITS_ENDPOINT") self.endpoint = getenv("LNBITS_ENDPOINT")
self.auth_admin = {"X-Api-Key": getenv("LNBITS_ADMIN_KEY")}
self.auth_invoice = {"X-Api-Key": getenv("LNBITS_INVOICE_KEY")} key = getenv("LNBITS_KEY") or getenv("LNBITS_ADMIN_KEY") or getenv("LNBITS_INVOICE_KEY")
self.key = {"X-Api-Key": key}
def create_invoice( def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
@ -23,45 +24,45 @@ class LNbitsWallet(Wallet):
else: else:
data["memo"] = memo or "" data["memo"] = memo or ""
r = post( r = httpx.post(
url=f"{self.endpoint}/api/v1/payments", url=f"{self.endpoint}/api/v1/payments",
headers=self.auth_invoice, headers=self.key,
json=data, json=data,
) )
ok, checking_id, payment_request, error_message = r.ok, None, None, None ok, checking_id, payment_request, error_message = not r.is_error, None, None, None
if r.ok: if r.is_error:
error_message = r.json()["message"]
else:
data = r.json() data = r.json()
checking_id, payment_request = data["checking_id"], data["payment_request"] checking_id, payment_request = data["checking_id"], data["payment_request"]
else:
error_message = r.json()["message"]
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(url=f"{self.endpoint}/api/v1/payments", headers=self.auth_admin, json={"out": True, "bolt11": bolt11}) r = httpx.post(url=f"{self.endpoint}/api/v1/payments", headers=self.key, json={"out": True, "bolt11": bolt11})
ok, checking_id, fee_msat, error_message = True, None, 0, None ok, checking_id, fee_msat, error_message = not r.is_error, None, 0, None
if r.ok: if r.is_error:
error_message = r.json()["message"]
else:
data = r.json() data = r.json()
checking_id = data["checking_id"] checking_id = data["checking_id"]
else:
error_message = r.json()["message"]
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:
r = get(url=f"{self.endpoint}/api/v1/payments/{checking_id}", headers=self.auth_invoice) r = httpx.get(url=f"{self.endpoint}/api/v1/payments/{checking_id}", headers=self.key)
if not r.ok: if r.is_error:
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 = httpx.get(url=f"{self.endpoint}/api/v1/payments/{checking_id}", headers=self.key)
if not r.ok: if r.is_error:
return PaymentStatus(None) return PaymentStatus(None)
return PaymentStatus(r.json()["paid"]) return PaymentStatus(r.json()["paid"])

16
lnbits/wallets/lndgrpc.py

@ -46,22 +46,28 @@ class LndWallet(Wallet):
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
self.port = int(getenv("LND_GRPC_PORT")) self.port = int(getenv("LND_GRPC_PORT"))
self.cert_path = getenv("LND_GRPC_CERT") or getenv("LND_CERT") self.cert_path = getenv("LND_GRPC_CERT") or getenv("LND_CERT")
self.auth_admin = getenv("LND_GRPC_ADMIN_MACAROON") or getenv("LND_ADMIN_MACAROON")
self.auth_invoices = getenv("LND_GRPC_INVOICE_MACAROON") or getenv("LND_INVOICE_MACAROON") self.macaroon_path = (
getenv("LND_GRPC_MACAROON")
or getenv("LND_GRPC_ADMIN_MACAROON")
or getenv("LND_ADMIN_MACAROON")
or getenv("LND_GRPC_INVOICE_MACAROON")
or getenv("LND_INVOICE_MACAROON")
)
network = getenv("LND_GRPC_NETWORK", "mainnet") network = getenv("LND_GRPC_NETWORK", "mainnet")
self.admin_rpc = lndgrpc.LNDClient( self.admin_rpc = lndgrpc.LNDClient(
f"{self.endpoint}:{self.port}", f"{self.endpoint}:{self.port}",
cert_filepath=self.cert_path, cert_filepath=self.cert_path,
network=network, network=network,
macaroon_filepath=self.auth_admin, macaroon_filepath=self.macaroon_path,
) )
self.invoices_rpc = lndgrpc.LNDClient( self.invoices_rpc = lndgrpc.LNDClient(
f"{self.endpoint}:{self.port}", f"{self.endpoint}:{self.port}",
cert_filepath=self.cert_path, cert_filepath=self.cert_path,
network=network, network=network,
macaroon_filepath=self.auth_invoices, macaroon_filepath=self.macaroon_path,
) )
def create_invoice( def create_invoice(
@ -129,7 +135,7 @@ class LndWallet(Wallet):
ln.Invoice, ln.Invoice,
), ),
) )
macaroon = load_macaroon(self.auth_admin) macaroon = load_macaroon(self.macaroon_path)
async for inv in subscribe_invoices( async for inv in subscribe_invoices(
ln.InvoiceSubscription(), ln.InvoiceSubscription(),

35
lnbits/wallets/lndrest.py

@ -11,19 +11,20 @@ class LndRestWallet(Wallet):
"""https://api.lightning.community/rest/index.html#lnd-rest-api-reference""" """https://api.lightning.community/rest/index.html#lnd-rest-api-reference"""
def __init__(self): def __init__(self):
endpoint = getenv("LND_REST_ENDPOINT") endpoint = getenv("LND_REST_ENDPOINT")
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
endpoint = "https://" + endpoint if not endpoint.startswith("http") else endpoint endpoint = "https://" + endpoint if not endpoint.startswith("http") else endpoint
self.endpoint = endpoint self.endpoint = endpoint
self.auth_admin = { macaroon = (
"Grpc-Metadata-macaroon": getenv("LND_ADMIN_MACAROON") or getenv("LND_REST_ADMIN_MACAROON"), getenv("LND_MACAROON")
} or getenv("LND_ADMIN_MACAROON")
self.auth_invoice = { or getenv("LND_REST_ADMIN_MACAROON")
"Grpc-Metadata-macaroon": getenv("LND_INVOICE_MACAROON") or getenv("LND_REST_INVOICE_MACAROON") or getenv("LND_INVOICE_MACAROON")
} or getenv("LND_REST_INVOICE_MACAROON")
self.auth_cert = getenv("LND_REST_CERT") )
self.auth = {"Grpc-Metadata-macaroon": macaroon}
self.cert = getenv("LND_REST_CERT")
def create_invoice( def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
@ -39,8 +40,8 @@ class LndRestWallet(Wallet):
r = httpx.post( r = httpx.post(
url=f"{self.endpoint}/v1/invoices", url=f"{self.endpoint}/v1/invoices",
headers=self.auth_invoice, headers=self.auth,
verify=self.auth_cert, verify=self.cert,
json=data, json=data,
) )
@ -62,8 +63,8 @@ class LndRestWallet(Wallet):
def pay_invoice(self, bolt11: str) -> PaymentResponse: def pay_invoice(self, bolt11: str) -> PaymentResponse:
r = httpx.post( r = httpx.post(
url=f"{self.endpoint}/v1/channels/transactions", url=f"{self.endpoint}/v1/channels/transactions",
headers=self.auth_admin, headers=self.auth,
verify=self.auth_cert, verify=self.cert,
json={"payment_request": bolt11}, json={"payment_request": bolt11},
) )
@ -84,8 +85,8 @@ class LndRestWallet(Wallet):
checking_id = checking_id.replace("_", "/") checking_id = checking_id.replace("_", "/")
r = httpx.get( r = httpx.get(
url=f"{self.endpoint}/v1/invoice/{checking_id}", url=f"{self.endpoint}/v1/invoice/{checking_id}",
headers=self.auth_invoice, headers=self.auth,
verify=self.auth_cert, verify=self.cert,
) )
if r.is_error or not r.json().get("settled"): if r.is_error or not r.json().get("settled"):
@ -98,8 +99,8 @@ class LndRestWallet(Wallet):
def get_payment_status(self, checking_id: str) -> PaymentStatus: def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = httpx.get( r = httpx.get(
url=f"{self.endpoint}/v1/payments", url=f"{self.endpoint}/v1/payments",
headers=self.auth_admin, headers=self.auth,
verify=self.auth_cert, verify=self.cert,
params={"include_incomplete": "True", "max_payments": "20"}, params={"include_incomplete": "True", "max_payments": "20"},
) )
@ -118,7 +119,7 @@ class LndRestWallet(Wallet):
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
url = self.endpoint + "/v1/invoices/subscribe" url = self.endpoint + "/v1/invoices/subscribe"
async with httpx.AsyncClient(timeout=None, headers=self.auth_admin, verify=self.auth_cert) as client: async with httpx.AsyncClient(timeout=None, headers=self.auth, verify=self.cert) as client:
async with client.stream("GET", url) as r: async with client.stream("GET", url) as r:
async for line in r.aiter_lines(): async for line in r.aiter_lines():
try: try:

16
lnbits/wallets/lnpay.py

@ -15,8 +15,8 @@ class LNPayWallet(Wallet):
def __init__(self): def __init__(self):
endpoint = getenv("LNPAY_API_ENDPOINT", "https://lnpay.co/v1") endpoint = getenv("LNPAY_API_ENDPOINT", "https://lnpay.co/v1")
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
self.auth_admin = getenv("LNPAY_ADMIN_KEY") self.wallet_key = getenv("LNPAY_WALLET_KEY") or getenv("LNPAY_ADMIN_KEY")
self.auth_api = {"X-Api-Key": getenv("LNPAY_API_KEY")} self.auth = {"X-Api-Key": getenv("LNPAY_API_KEY")}
def create_invoice( def create_invoice(
self, self,
@ -31,8 +31,8 @@ class LNPayWallet(Wallet):
data["memo"] = memo or "" data["memo"] = memo or ""
r = httpx.post( r = httpx.post(
url=f"{self.endpoint}/user/wallet/{self.auth_admin}/invoice", url=f"{self.endpoint}/user/wallet/{self.wallet_key}/invoice",
headers=self.auth_api, headers=self.auth,
json=data, json=data,
) )
ok, checking_id, payment_request, error_message = ( ok, checking_id, payment_request, error_message = (
@ -50,8 +50,8 @@ class LNPayWallet(Wallet):
def pay_invoice(self, bolt11: str) -> PaymentResponse: def pay_invoice(self, bolt11: str) -> PaymentResponse:
r = httpx.post( r = httpx.post(
url=f"{self.endpoint}/user/wallet/{self.auth_admin}/withdraw", url=f"{self.endpoint}/user/wallet/{self.wallet_key}/withdraw",
headers=self.auth_api, headers=self.auth,
json={"payment_request": bolt11}, json={"payment_request": bolt11},
) )
ok, checking_id, fee_msat, error_message = r.status_code == 201, None, 0, None ok, checking_id, fee_msat, error_message = r.status_code == 201, None, 0, None
@ -67,7 +67,7 @@ class LNPayWallet(Wallet):
def get_payment_status(self, checking_id: str) -> PaymentStatus: def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = httpx.get( r = httpx.get(
url=f"{self.endpoint}/user/lntx/{checking_id}?fields=settled", url=f"{self.endpoint}/user/lntx/{checking_id}?fields=settled",
headers=self.auth_api, headers=self.auth,
) )
if r.is_error: if r.is_error:
@ -91,7 +91,7 @@ class LNPayWallet(Wallet):
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.get( r = await client.get(
f"{self.endpoint}/user/lntx/{lntx_id}?fields=settled", f"{self.endpoint}/user/lntx/{lntx_id}?fields=settled",
headers=self.auth_api, headers=self.auth,
) )
data = r.json() data = r.json()
if data["settled"]: if data["settled"]:

59
lnbits/wallets/lntxbot.py

@ -1,7 +1,7 @@
import trio # type: ignore import trio # type: ignore
import httpx
from os import getenv from os import getenv
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
from requests import post
from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet from .base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
@ -12,8 +12,9 @@ class LntxbotWallet(Wallet):
def __init__(self): def __init__(self):
endpoint = getenv("LNTXBOT_API_ENDPOINT") endpoint = getenv("LNTXBOT_API_ENDPOINT")
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
self.auth_admin = {"Authorization": f"Basic {getenv('LNTXBOT_ADMIN_KEY')}"}
self.auth_invoice = {"Authorization": f"Basic {getenv('LNTXBOT_INVOICE_KEY')}"} key = getenv("LNTXBOT_KEY") or getenv("LNTXBOT_ADMIN_KEY") or getenv("LNTXBOT_INVOICE_KEY")
self.auth = {"Authorization": f"Basic {key}"}
def create_invoice( def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
@ -24,44 +25,47 @@ class LntxbotWallet(Wallet):
else: else:
data["memo"] = memo or "" data["memo"] = memo or ""
r = post( r = httpx.post(
url=f"{self.endpoint}/addinvoice", url=f"{self.endpoint}/addinvoice",
headers=self.auth_invoice, headers=self.auth,
json=data, json=data,
) )
ok, checking_id, payment_request, error_message = r.ok, None, None, None
if r.ok: if r.is_error:
try:
data = r.json() data = r.json()
checking_id, payment_request = data["payment_hash"], data["pay_req"]
if "error" in data and data["error"]:
ok = False
error_message = data["message"] error_message = data["message"]
except:
error_message = r.text
pass
return InvoiceResponse(ok, checking_id, payment_request, error_message) return InvoiceResponse(False, None, None, error_message)
data = r.json()
return InvoiceResponse(True, data["payment_hash"], data["pay_req"], None)
def pay_invoice(self, bolt11: str) -> PaymentResponse: def pay_invoice(self, bolt11: str) -> PaymentResponse:
r = post(url=f"{self.endpoint}/payinvoice", headers=self.auth_admin, json={"invoice": bolt11}) r = httpx.post(url=f"{self.endpoint}/payinvoice", headers=self.auth, json={"invoice": bolt11})
ok, checking_id, fee_msat, error_message = r.ok, None, 0, None
if r.ok: if r.is_error:
try:
data = r.json() data = r.json()
error_message = data["message"]
except:
error_message = r.text
pass
if "payment_hash" in data: return PaymentResponse(False, None, 0, error_message)
checking_id, fee_msat = data["decoded"]["payment_hash"], data["fee_msat"]
elif "error" in data and data["error"]:
ok, error_message = False, data["message"]
return PaymentResponse(ok, checking_id, fee_msat, error_message) data = r.json()
return PaymentResponse(True, data["decoded"]["payment_hash"], data["fee_msat"], None)
def get_invoice_status(self, checking_id: str) -> PaymentStatus: def get_invoice_status(self, checking_id: str) -> PaymentStatus:
r = post(url=f"{self.endpoint}/invoicestatus/{checking_id}?wait=false", headers=self.auth_invoice) r = httpx.post(url=f"{self.endpoint}/invoicestatus/{checking_id}?wait=false", headers=self.auth)
if not r.ok or "error" in r.json():
return PaymentStatus(None)
data = r.json() data = r.json()
if r.is_error or "error" in data:
return PaymentStatus(None)
if "preimage" not in data: if "preimage" not in data:
return PaymentStatus(False) return PaymentStatus(False)
@ -69,13 +73,14 @@ class LntxbotWallet(Wallet):
return PaymentStatus(True) return PaymentStatus(True)
def get_payment_status(self, checking_id: str) -> PaymentStatus: def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = post(url=f"{self.endpoint}/paymentstatus/{checking_id}", headers=self.auth_invoice) r = httpx.post(url=f"{self.endpoint}/paymentstatus/{checking_id}", headers=self.auth)
if not r.ok or "error" in r.json(): data = r.json()
if r.is_error or "error" in data:
return PaymentStatus(None) return PaymentStatus(None)
statuses = {"complete": True, "failed": False, "pending": None, "unknown": None} statuses = {"complete": True, "failed": False, "pending": None, "unknown": None}
return PaymentStatus(statuses[r.json().get("status", "unknown")]) return PaymentStatus(statuses[data.get("status", "unknown")])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
print("lntxbot does not support paid invoices stream yet") print("lntxbot does not support paid invoices stream yet")

11
lnbits/wallets/opennode.py

@ -16,8 +16,9 @@ class OpenNodeWallet(Wallet):
def __init__(self): def __init__(self):
endpoint = getenv("OPENNODE_API_ENDPOINT") endpoint = getenv("OPENNODE_API_ENDPOINT")
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
self.auth_admin = {"Authorization": getenv("OPENNODE_ADMIN_KEY")}
self.auth_invoice = {"Authorization": getenv("OPENNODE_INVOICE_KEY")} key = getenv("OPENNODE_KEY") or getenv("OPENNODE_ADMIN_KEY") or getenv("OPENNODE_INVOICE_KEY")
self.auth = {"Authorization": key}
def create_invoice( def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
@ -45,9 +46,7 @@ class OpenNodeWallet(Wallet):
return InvoiceResponse(True, checking_id, payment_request, None) return InvoiceResponse(True, checking_id, payment_request, None)
def pay_invoice(self, bolt11: str) -> PaymentResponse: def pay_invoice(self, bolt11: str) -> PaymentResponse:
r = httpx.post( r = httpx.post(f"{self.endpoint}/v2/withdrawals", headers=self.auth, json={"type": "ln", "address": bolt11})
f"{self.endpoint}/v2/withdrawals", headers=self.auth_admin, json={"type": "ln", "address": bolt11}
)
if r.is_error: if r.is_error:
error_message = r.json()["message"] error_message = r.json()["message"]
@ -68,7 +67,7 @@ class OpenNodeWallet(Wallet):
return PaymentStatus(statuses[r.json()["data"]["status"]]) return PaymentStatus(statuses[r.json()["data"]["status"]])
def get_payment_status(self, checking_id: str) -> PaymentStatus: def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = httpx.get(f"{self.endpoint}/v1/withdrawal/{checking_id}", headers=self.auth_admin) r = httpx.get(f"{self.endpoint}/v1/withdrawal/{checking_id}", headers=self.auth)
if r.is_error: if r.is_error:
return PaymentStatus(None) return PaymentStatus(None)

11
requirements.txt

@ -7,16 +7,15 @@ blinker==1.4
brotli==1.0.9 brotli==1.0.9
cerberus==1.3.2 cerberus==1.3.2
certifi==2020.6.20 certifi==2020.6.20
chardet==3.0.4
click==7.1.2 click==7.1.2
ecdsa==0.16.0 ecdsa==0.16.0
environs==8.0.0 environs==8.0.0
h11==0.9.0 h11==0.11.0
h2==4.0.0 h2==4.0.0
hpack==4.0.0 hpack==4.0.0
httpcore==0.11.1 httpcore==0.12.0
httpx==0.15.5 httpx==0.16.1
hypercorn==0.11.0 hypercorn==0.11.1
hyperframe==6.0.0 hyperframe==6.0.0
idna==2.10 idna==2.10
itsdangerous==1.1.0 itsdangerous==1.1.0
@ -33,7 +32,6 @@ quart==0.13.1
quart-compress==0.2.1 quart-compress==0.2.1
quart-cors==0.3.0 quart-cors==0.3.0
quart-trio==0.5.1 quart-trio==0.5.1
requests==2.24.0
rfc3986==1.4.0 rfc3986==1.4.0
secure==0.2.1 secure==0.2.1
shortuuid==1.0.1 shortuuid==1.0.1
@ -43,6 +41,5 @@ sortedcontainers==2.2.2
toml==0.10.1 toml==0.10.1
trio==0.17.0 trio==0.17.0
typing-extensions==3.7.4.3 typing-extensions==3.7.4.3
urllib3==1.25.10
werkzeug==1.0.1 werkzeug==1.0.1
wsproto==0.15.0 wsproto==0.15.0

Loading…
Cancel
Save