class LightningDaemon(object): def __init__(self, daemon_rpc): self.rpc = LightningRpc(daemon_rpc) def invoice_c_lightning(self, msatoshi, label, description): result = self.rpc.invoice(msatoshi, label, description, expiry=INVOICE_EXPIRY) log(json.dumps(result, indent=1, sort_keys=True)) return result def get_c_lightning_invoices(self): result = self.rpc.listinvoices() #log(json.dumps(result, indent=1, sort_keys=True)) return result def delete(self, label, state="paid"): result = self.rpc.delinvoice(label, state) # log(json.dumps(result, indent=1, sort_keys=True)) return result def getinfo(self): return self.rpc.getinfo() def listfunds(self): return self.rpc.listfunds() def listnodes(self): return self.rpc.listnodes()
class RealDaemon(Daemon): """ calls c-lightning via the rpc """ def __init__(self, path): super().__init__() self.path = path print("rpc path: %s" % self.path) self.rpc = LightningRpc(self.path) def invoice_c_lightning(self, msatoshi, label, description, expiry, preimage): print("invoice real") try: result = self.rpc.invoice(msatoshi, label, description, expiry=expiry, preimage=preimage) except: return None, "c-lightning invoice exception" print(json.dumps(result, indent=1, sort_keys=True)) return result, None def get_c_lightning_invoices(self): try: result = self.rpc.listinvoices() except: return None, "c-lightning listinvoices exception" print(json.dumps(result, indent=1, sort_keys=True)) return result['invoices'], None def delete(self, label, state='paid'): try: result = self.rpc.delinvoice(label, state) except: return None, "c-lightning delinvoice exception" print(json.dumps(result, indent=1, sort_keys=True)) return result, None
class CLightningWallet(Wallet): def __init__(self): self.l1 = LightningRpc(getenv("CLIGHTNING_RPC")) def create_invoice(self, amount: int, memo: str = "") -> InvoiceResponse: label = "lbl{}".format(random.random()) r = self.l1.invoice(amount*1000, label, memo, exposeprivatechannels=True) ok, checking_id, payment_request, error_message = True, r["payment_hash"], r["bolt11"], None return InvoiceResponse(ok, checking_id, payment_request, error_message) def pay_invoice(self, bolt11: str) -> PaymentResponse: r = self.l1.pay(bolt11) ok, checking_id, fee_msat, error_message = True, None, None, None return PaymentResponse(ok, checking_id, fee_msat, error_message) def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = self.l1.listinvoices(checking_id) if r['invoices'][0]['status'] == 'unpaid': return PaymentStatus(False) return PaymentStatus(True) def get_payment_status(self, checking_id: str) -> PaymentStatus: r = self.l1.listsendpays(checking_id) if not r.ok: return PaymentStatus(r, None) payments = [p for p in r.json()["payments"] if p["payment_hash"] == payment_hash] payment = payments[0] if payments else None statuses = {"UNKNOWN": None, "IN_FLIGHT": None, "SUCCEEDED": True, "FAILED": False} return PaymentStatus(statuses[payment["status"]] if payment else None)
class CLightningWallet(Wallet): def __init__(self): if LightningRpc is None: # pragma: nocover raise ImportError( "The `pylightning` library must be installed to use `CLightningWallet`." ) self.l1 = LightningRpc(getenv("CLIGHTNING_RPC")) 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()) r = self.l1.invoice(amount * 1000, label, memo, exposeprivatechannels=True) ok, checking_id, payment_request, error_message = True, r[ "payment_hash"], r["bolt11"], None return InvoiceResponse(ok, checking_id, payment_request, error_message) def pay_invoice(self, bolt11: str) -> PaymentResponse: r = self.l1.pay(bolt11) ok, checking_id, fee_msat, error_message = True, r[ "payment_hash"], r["msatoshi_sent"] - r["msatoshi"], None return PaymentResponse(ok, checking_id, fee_msat, error_message) def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = self.l1.listinvoices(checking_id) if not r["invoices"]: return PaymentStatus(False) if r["invoices"][0]["label"] == checking_id: return PaymentStatus(r["pays"][0]["status"] == "paid") raise KeyError("supplied an invalid checking_id") def get_payment_status(self, checking_id: str) -> PaymentStatus: r = self.l1.listpays(payment_hash=checking_id) if not r["pays"]: return PaymentStatus(False) if r["pays"][0]["payment_hash"] == checking_id: status = r["pays"][0]["status"] if status == "complete": return PaymentStatus(True) elif status == "failed": return PaymentStatus(False) return PaymentStatus(None) raise KeyError("supplied an invalid checking_id")
class CLightningWallet(Wallet): def __init__(self): if LightningRpc is None: # pragma: nocover raise ImportError( "The `pylightning` library must be installed to use `CLightningWallet`." ) self.rpc = getenv("CLIGHTNING_RPC") self.ln = LightningRpc(self.rpc) # check description_hash support (could be provided by a plugin) self.supports_description_hash = False try: answer = self.ln.help("invoicewithdescriptionhash") if answer["help"][0]["command"].startswith( "invoicewithdescriptionhash msatoshi label description_hash", ): self.supports_description_hash = True except: pass # check last payindex so we can listen from that point on self.last_pay_index = 0 invoices = self.ln.listinvoices() for inv in invoices["invoices"][::-1]: if "pay_index" in inv: self.last_pay_index = inv["pay_index"] break def create_invoice( self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None) -> InvoiceResponse: label = "lbl{}".format(random.random()) msat = amount * 1000 try: if description_hash: if not self.supports_description_hash: raise Unsupported("description_hash") params = [msat, label, description_hash.hex()] r = self.ln.call("invoicewithdescriptionhash", params) return InvoiceResponse(True, label, r["bolt11"], "") else: r = self.ln.invoice(msat, label, memo, exposeprivatechannels=True) return InvoiceResponse(True, label, r["bolt11"], "") except RpcError as exc: error_message = f"lightningd '{exc.method}' failed with '{exc.error}'." return InvoiceResponse(False, label, None, error_message) def pay_invoice(self, bolt11: str) -> PaymentResponse: r = self.ln.pay(bolt11) return PaymentResponse(True, r["payment_hash"], r["msatoshi_sent"] - r["msatoshi"], None) def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = self.ln.listinvoices(checking_id) if not r["invoices"]: return PaymentStatus(False) if r["invoices"][0]["label"] == checking_id: return PaymentStatus(r["invoices"][0]["status"] == "paid") raise KeyError("supplied an invalid checking_id") def get_payment_status(self, checking_id: str) -> PaymentStatus: r = self.ln.call("listpays", {"payment_hash": checking_id}) if not r["pays"]: return PaymentStatus(False) if r["pays"][0]["payment_hash"] == checking_id: status = r["pays"][0]["status"] if status == "complete": return PaymentStatus(True) elif status == "failed": return PaymentStatus(False) return PaymentStatus(None) raise KeyError("supplied an invalid checking_id") async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: stream = await trio.open_unix_socket(self.rpc) i = 0 while True: call = json.dumps({ "method": "waitanyinvoice", "id": 0, "params": [self.last_pay_index], }) await stream.send_all(call.encode("utf-8")) data = await stream.receive_some() paid = json.loads(data.decode("ascii")) paid = self.ln.waitanyinvoice(self.last_pay_index) self.last_pay_index = paid["pay_index"] yield paid["label"] i += 1
class CLightningWallet(Wallet): def __init__(self): if LightningRpc is None: # pragma: nocover raise ImportError( "The `pylightning` library must be installed to use `CLightningWallet`." ) self.rpc = getenv("CLIGHTNING_RPC") self.ln = LightningRpc(self.rpc) # check description_hash support (could be provided by a plugin) self.supports_description_hash = False try: answer = self.ln.help("invoicewithdescriptionhash") if answer["help"][0]["command"].startswith( "invoicewithdescriptionhash msatoshi label description_hash", ): self.supports_description_hash = True except: pass # check last payindex so we can listen from that point on self.last_pay_index = 0 invoices = self.ln.listinvoices() for inv in invoices["invoices"][::-1]: if "pay_index" in inv: self.last_pay_index = inv["pay_index"] break async def status(self) -> StatusResponse: try: funds = self.ln.listfunds() return StatusResponse( None, sum([ch["channel_sat"] * 1000 for ch in funds["channels"]]), ) except RpcError as exc: error_message = f"lightningd '{exc.method}' failed with '{exc.error}'." return StatusResponse(error_message, 0) async def create_invoice( self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None, ) -> InvoiceResponse: label = "lbl{}".format(random.random()) msat = amount * 1000 try: if description_hash: if not self.supports_description_hash: raise Unsupported("description_hash") params = [msat, label, description_hash.hex()] r = self.ln.call("invoicewithdescriptionhash", params) return InvoiceResponse(True, label, r["bolt11"], "") else: r = self.ln.invoice(msat, label, memo, exposeprivatechannels=True) return InvoiceResponse(True, label, r["bolt11"], "") except RpcError as exc: error_message = f"lightningd '{exc.method}' failed with '{exc.error}'." return InvoiceResponse(False, label, None, error_message) # WARNING: correct handling of fee_limit_msat is required to avoid security vulnerabilities! # The backend MUST NOT spend satoshis above invoice amount + fee_limit_msat. 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) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = self.ln.listinvoices(checking_id) if not r["invoices"]: return PaymentStatus(False) if r["invoices"][0]["label"] == checking_id: return PaymentStatus(r["invoices"][0]["status"] == "paid") raise KeyError("supplied an invalid checking_id") async def get_payment_status(self, checking_id: str) -> PaymentStatus: r = self.ln.call("listpays", {"payment_hash": checking_id}) if not r["pays"]: return PaymentStatus(False) if r["pays"][0]["payment_hash"] == checking_id: status = r["pays"][0]["status"] if status == "complete": return PaymentStatus(True) elif status == "failed": return PaymentStatus(False) return PaymentStatus(None) raise KeyError("supplied an invalid checking_id") async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: stream = await trio.open_unix_socket(self.rpc) i = 0 while True: call = json.dumps({ "method": "waitanyinvoice", "id": 0, "params": [self.last_pay_index], }) await stream.send_all(call.encode("utf-8")) data = await stream.receive_some() paid = json.loads(data.decode("ascii")) paid = self.ln.waitanyinvoice(self.last_pay_index) self.last_pay_index = paid["pay_index"] yield paid["label"] i += 1