glclient

  1from . import scheduler_pb2 as schedpb
  2from . import greenlight_pb2 as nodepb
  3from pyln import grpc as clnpb  # type: ignore
  4from pyln.grpc import Amount, AmountOrAll, AmountOrAny  # noqa: F401
  5from . import glclient as native
  6from .glclient import backup_decrypt_with_seed  # noqa: F401
  7from .tls import TlsConfig
  8from google.protobuf.message import Message as PbMessage
  9from binascii import hexlify, unhexlify
 10from typing import Optional, List, Iterable, Any, Type, TypeVar
 11import logging
 12from glclient.lsps import LspClient
 13from glclient.glclient import Credentials
 14
 15
 16backup_decrypt_with_seed = native.backup_decrypt_with_seed
 17
 18
 19# Keep in sync with the libhsmd version, this is tested in unit tests.
 20__version__ = "v24.02"
 21
 22
 23E = TypeVar("E", bound=PbMessage)
 24
 25
 26def _convert(cls: Type[E], res: Iterable[Any]) -> E:
 27    return cls.FromString(bytes(res))
 28
 29
 30class Signer(object):
 31    def __init__(self, secret: bytes, network: str, creds: Credentials):
 32        self.inner = native.Signer(secret, network, creds)
 33        self.creds = creds
 34        self.handle: Optional[native.SignerHandle] = None
 35
 36    def run_in_thread(self) -> "native.SignerHandle":
 37        if self.handle is not None:
 38            raise ValueError(
 39                "This signer is already running, please shut it down before starting it again"
 40            )
 41        self.handle = self.inner.run_in_thread()
 42        return self.handle
 43
 44    def run_in_foreground(self) -> None:
 45        return self.inner.run_in_foreground()
 46
 47    def node_id(self) -> bytes:
 48        return bytes(self.inner.node_id())
 49
 50    def version(self) -> str:
 51        return self.inner.version()
 52
 53    def sign_challenge(self, message: bytes) -> bytes:
 54        return bytes(self.inner.sign_challenge(message))
 55
 56    def shutdown(self) -> None:
 57        if self.handle is None:
 58            raise ValueError("Attempted to shut down a signer that is not running")
 59        self.handle.shutdown()
 60        self.handle = None
 61
 62    def create_rune(
 63        self, restrictions: List[List[str]], rune: Optional[str] = None
 64    ) -> str:
 65        return self.inner.create_rune(restrictions, rune)
 66
 67    def is_running(self) -> bool:
 68        return self.handle is not None
 69
 70
 71class Scheduler(object):
 72
 73    def __init__(self, network: str, creds: Optional[Credentials] = None):
 74        self.network = network
 75        self.creds = creds if creds is not None else native.Credentials()
 76        self.inner = native.Scheduler(network, self.creds)
 77
 78    def schedule(self) -> schedpb.NodeInfoResponse:
 79        res = self.inner.schedule()
 80        return schedpb.NodeInfoResponse.FromString(bytes(res))
 81
 82    def get_node_info(self, wait: bool = False):
 83        res = self.inner.get_node_info(wait)
 84        return schedpb.NodeInfoResponse.FromString(bytes(res))
 85
 86    def register(self, signer: Signer, invite_code: Optional[str] = None) -> schedpb.RegistrationResponse:
 87        res = self.inner.register(signer.inner, invite_code)
 88        return schedpb.RegistrationResponse.FromString(bytes(res))
 89
 90    def recover(self, signer: Signer) -> schedpb.RecoveryResponse:
 91        res = self.inner.recover(signer.inner)
 92        return schedpb.RecoveryResponse.FromString(bytes(res))
 93
 94    def authenticate(self, creds: Credentials):
 95        self.creds = creds
 96        self.inner = self.inner.authenticate(creds)
 97        return self
 98
 99    def export_node(self) -> schedpb.ExportNodeResponse:
100        res = schedpb.ExportNodeResponse
101        return res.FromString(bytes(self.inner.export_node()))
102
103    def node(self) -> "Node":
104        res = self.inner.node()
105        info = schedpb.NodeInfoResponse.FromString(bytes(res))
106        return Node(
107            node_id=self.creds.node_id(),
108            grpc_uri=info.grpc_uri,
109            creds=self.creds,
110        )
111
112    def get_invite_codes(self) -> schedpb.ListInviteCodesResponse:
113        cls = schedpb.ListInviteCodesResponse
114        return cls.FromString(bytes(self.inner.get_invite_codes()))
115
116    def add_outgoing_webhook(self, uri: str) -> schedpb.AddOutgoingWebhookResponse:
117        res = self.inner.add_outgoing_webhook(uri)
118        return schedpb.AddOutgoingWebhookResponse.FromString(bytes(res))
119
120    def list_outgoing_webhooks(self) -> schedpb.ListOutgoingWebhooksResponse:
121        res = self.inner.list_outgoing_webhooks()
122        return schedpb.ListOutgoingWebhooksResponse.FromString(bytes(res))
123
124    def delete_outgoing_webhook(self, webhook_id: int) -> None:
125        res = self.inner.delete_outgoing_webhooks([webhook_id])
126
127    def delete_outgoing_webhooks(self, webhook_ids: List[int]) -> None:
128        res = self.inner.delete_outgoing_webhooks(webhook_ids)
129
130    def rotate_outgoing_webhook_secret(self, webhook_id: int) -> schedpb.WebhookSecretResponse:
131        res = self.inner.rotate_outgoing_webhook_secret(webhook_id)
132        return schedpb.WebhookSecretResponse.FromString(bytes(res))
133
134
135class Node(object):
136
137    def __init__(
138        self, node_id: bytes, grpc_uri: str, creds: Credentials
139    ) -> None:
140        self.creds = creds
141        self.grpc_uri = grpc_uri
142        self.inner = native.Node(
143            node_id=node_id, grpc_uri=grpc_uri, creds=creds
144        )
145        self.logger = logging.getLogger("glclient.Node")
146
147    def get_info(self) -> clnpb.GetinfoResponse:
148        uri = "/cln.Node/Getinfo"
149        req = clnpb.GetinfoRequest().SerializeToString()
150        res = clnpb.GetinfoResponse
151
152        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
153
154    def stop(self) -> None:
155        uri = "/cln.Node/Stop"
156        req = clnpb.StopRequest().SerializeToString()
157
158        try:
159            # This fails, since we just get disconnected, but that's
160            # on purpose, so drop the error silently.
161            self.inner.call(uri, bytes(req))
162        except ValueError as e:
163            self.logger.debug(
164                f"Caught an expected exception: {e}. Don't worry it's expected."
165            )
166
167    def list_funds(
168        self,
169        spent: Optional[bool] = None,
170    ) -> clnpb.ListfundsResponse:
171        uri = "/cln.Node/ListFunds"
172        res = clnpb.ListfundsResponse
173        req = clnpb.ListfundsRequest(
174            spent=spent,
175        ).SerializeToString()
176
177        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
178
179    def list_peers(self) -> clnpb.ListpeersResponse:
180        uri = "/cln.Node/ListPeers"
181        req = clnpb.ListpeersRequest().SerializeToString()
182        res = clnpb.ListpeersResponse
183
184        return res.FromString(
185            bytes(self.inner.call(uri, bytes(req)))
186        )
187
188    def list_peer_channels(
189            self,
190            node_id: Optional[bytes] = None
191    ) -> clnpb.ListpeerchannelsResponse:
192        uri = "/cln.Node/ListPeerChannels"
193        req = clnpb.ListpeerchannelsRequest(
194            id=node_id,
195        ).SerializeToString()
196        res = clnpb.ListpeerchannelsResponse
197        return res.FromString(
198            bytes(self.inner.call(uri, bytes(req)))
199        )
200
201    def list_closed_channels(self) -> clnpb.ListclosedchannelsResponse:
202        uri = "/cln.Node/ListClosedChannels"
203        req = clnpb.ListclosedchannelsRequest().SerializeToString()
204        res = clnpb.ListclosedchannelsResponse
205
206        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
207
208    def list_channels(
209        self,
210        short_channel_id: Optional[str] = None,
211        source: Optional[bytes] = None,
212        destination: Optional[bytes] = None,
213    ) -> clnpb.ListchannelsResponse:
214        uri = "/cln.Node/ListChannels"
215        req = clnpb.ListchannelsRequest(
216            short_channel_id=short_channel_id, source=source, destination=destination
217        ).SerializeToString()
218        res = clnpb.ListchannelsResponse
219
220        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
221
222    def listpays(
223        self,
224        bolt11: Optional[str] = None,
225        payment_hash: Optional[bytes] = None,
226        status: Optional[clnpb.ListpaysRequest.ListpaysStatus.ValueType] = None,
227    ) -> clnpb.ListpaysResponse:
228        uri = "/cln.Node/ListPays"
229        req = clnpb.ListpaysRequest(
230            bolt11=bolt11,
231            payment_hash=payment_hash,
232            status=status,
233        ).SerializeToString()
234        res = clnpb.ListpaysResponse
235
236        return res.FromString(bytes(self.inner.call(uri, req)))
237
238    def list_invoices(
239        self,
240        label: Optional[str] = None,
241        invstring: Optional[str] = None,
242        payment_hash: Optional[bytes] = None,
243        offer_id: Optional[str] = None,
244        index: Optional[clnpb.ListinvoicesRequest.ListinvoicesIndex.ValueType] = None,
245        start: Optional[int] = None,
246        limit: Optional[int] = None,
247    ) -> clnpb.ListinvoicesResponse:
248        uri = "/cln.Node/ListInvoices"
249        res = clnpb.ListinvoicesResponse
250        req = clnpb.ListinvoicesRequest(
251            label=label,
252            invstring=invstring,
253            payment_hash=payment_hash,
254            offer_id=offer_id,
255            index=index,
256            start=start,
257            limit=limit,
258        ).SerializeToString()
259        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
260
261    def connect_peer(
262        self, node_id, host: Optional[str] = None, port: Optional[int] = None
263    ) -> clnpb.ConnectResponse:
264        if len(node_id) == 33:
265            node_id = hexlify(node_id)
266
267        if isinstance(node_id, bytes):
268            node_id = node_id.decode("ASCII")
269
270        uri = "/cln.Node/ConnectPeer"
271        res = clnpb.ConnectResponse
272        req = clnpb.ConnectRequest(
273            id=node_id,
274            host=host,
275            port=port,
276        ).SerializeToString()
277
278        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
279
280    def decode(self, string: str) -> clnpb.DecodeResponse:
281        uri = "/cln.Node/Decode"
282        res = clnpb.DecodeResponse
283        req = clnpb.DecodeRequest(
284            string=string,
285        ).SerializeToString()
286
287        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
288
289    def decodepay(
290        self, bolt11: str, description: Optional[str]
291    ) -> clnpb.DecodepayResponse:
292        uri = "/cln.Node/DecodePay"
293        res = clnpb.DecodepayResponse
294        req = clnpb.DecodepayRequest(
295            bolt11=bolt11,
296            description=description,
297        ).SerializeToString()
298
299        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
300
301    def disconnect_peer(self, peer_id: str, force=False) -> clnpb.DisconnectResponse:
302        uri = "/cln.Node/Disconnect"
303        res = clnpb.DisconnectResponse
304        req = clnpb.DisconnectRequest(
305            id=bytes.fromhex(peer_id),
306            force=force,
307        ).SerializeToString()
308
309        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
310
311    def new_address(self) -> clnpb.NewaddrResponse:
312        uri = "/cln.Node/NewAddr"
313        req = clnpb.NewaddrRequest().SerializeToString()
314        res = clnpb.NewaddrResponse
315
316        return res.FromString(bytes(self.inner.call(uri, req)))
317
318    def withdraw(
319        self, destination, amount: AmountOrAll, minconf: int = 0
320    ) -> clnpb.WithdrawResponse:
321        uri = "/cln.Node/Withdraw"
322        res = clnpb.WithdrawResponse
323        req = clnpb.WithdrawRequest(
324            destination=destination, satoshi=amount, minconf=minconf
325        ).SerializeToString()
326
327        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
328
329    def fund_channel(
330        self,
331        id: bytes,
332        amount,
333        announce: Optional[bool] = False,
334        minconf: Optional[int] = 1,
335    ) -> clnpb.FundchannelResponse:
336
337        if len(id) != 33:
338            raise ValueError("id is not 33 bytes long")
339
340        uri = "/cln.Node/FundChannel"
341        res = clnpb.FundchannelResponse
342        req = clnpb.FundchannelRequest(
343            id=id,
344            amount=amount,
345            announce=announce,
346            minconf=minconf,
347        ).SerializeToString()
348
349        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
350
351    def close(
352        self, id: bytes, unilateraltimeout=None, destination=None
353    ) -> clnpb.CloseResponse:
354        if len(id) != 33:
355            raise ValueError("node_id is not 33 bytes long")
356
357        uri = "/cln.Node/Close"
358        res = clnpb.CloseResponse
359        req = clnpb.CloseRequest(
360            id=id.hex(),
361            unilateraltimeout=unilateraltimeout,
362            destination=destination,
363        ).SerializeToString()
364
365        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
366
367    def invoice(
368        self,
369        amount_msat: clnpb.AmountOrAny,
370        label: str,
371        description: str,
372        expiry: Optional[int] = None,
373        fallbacks: Optional[List[str]] = None,
374        preimage: Optional[bytes] = None,
375        cltv: Optional[int] = None,
376        deschashonly: Optional[bool] = None,
377    ) -> clnpb.InvoiceResponse:
378        if preimage and len(preimage) != 32:
379            raise ValueError("Preimage must be 32 bytes in length")
380
381        uri = "/cln.Node/Invoice"
382        res = clnpb.InvoiceResponse
383        req = clnpb.InvoiceRequest(
384            amount_msat=amount_msat,
385            label=label,
386            description=description,
387            preimage=preimage,
388            expiry=expiry,
389            fallbacks=fallbacks,
390            cltv=cltv,
391            deschashonly=deschashonly,
392        ).SerializeToString()
393
394        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
395
396    def pay(
397        self,
398        bolt11: str,
399        amount_msat: Optional[clnpb.Amount] = None,
400        retry_for: int = 0,
401        maxfee: Optional[clnpb.Amount] = None,
402        maxfeepercent: Optional[float] = None,
403    ) -> clnpb.PayResponse:
404        uri = "/cln.Node/Pay"
405        res = clnpb.PayResponse
406        req = clnpb.PayRequest(
407            bolt11=bolt11,
408            amount_msat=amount_msat,
409            retry_for=retry_for,
410            maxfeepercent=maxfeepercent,
411            maxfee=maxfee,
412        ).SerializeToString()
413
414        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
415
416    def trampoline_pay(self, bolt11: str, trampoline_node_id: bytes, amount_msat: Optional[int] = None, label: Optional[str] = None):
417        res = self.inner.trampoline_pay(
418            bolt11=bolt11,
419            trampoline_node_id=trampoline_node_id,
420            amount_msat=amount_msat,
421            label=label,
422        )
423        return nodepb.TrampolinePayResponse.FromString(bytes(res))
424
425    def keysend(
426        self,
427        destination: bytes,
428        amount: clnpb.Amount,
429        label: Optional[str] = None,
430        routehints: Optional[clnpb.RoutehintList] = None,
431        extratlvs: Optional[clnpb.TlvStream] = None,
432    ) -> clnpb.KeysendResponse:
433        uri = "/cln.Node/KeySend"
434        res = clnpb.KeysendResponse
435        req = clnpb.KeysendRequest(
436            destination=normalize_node_id(destination, string=False),
437            amount_msat=amount,
438            label=label if label else "",
439            routehints=routehints,
440            extratlvs=extratlvs,
441        ).SerializeToString()
442
443        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
444
445    def stream_log(self):
446        """Stream logs as they get generated on the server side."""
447        stream = self.inner.stream_log(b"")
448        while True:
449            n = stream.next()
450            if n is None:
451                break
452            yield nodepb.LogEntry.FromString(bytes(n))
453
454    def stream_incoming(self):
455        stream = self.inner.stream_incoming(b"")
456        while True:
457            n = stream.next()
458            if n is None:
459                break
460            yield nodepb.IncomingPayment.FromString(bytes(n))
461
462    def stream_custommsg(self):
463        stream = self.inner.stream_custommsg(b"")
464        while True:
465            n = stream.next()
466            if n is None:
467                break
468            yield nodepb.Custommsg.FromString(bytes(n))
469
470    def send_custommsg(self, node_id: str, msg: bytes) -> clnpb.SendcustommsgResponse:
471        uri = "/cln.Node/SendCustomMsg"
472        res = clnpb.SendcustommsgResponse
473        req = clnpb.SendcustommsgRequest(
474            node_id=normalize_node_id(node_id),
475            msg=msg,
476        ).SerializeToString()
477
478        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
479
480    def datastore(self, key, string=None, hex=None, mode=None, generation=None):
481        uri = "/cln.Node/Datastore"
482        req = clnpb.DatastoreRequest(
483            key=key, string=string, hex=hex, mode=mode, generation=generation
484        ).SerializeToString()
485        res = clnpb.DatastoreResponse
486        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
487
488    def del_datastore(self, key, generation=None):
489        uri = "/cln.Node/DelDatastore"
490        req = clnpb.DeldatastoreRequest(
491            key=key, generation=generation
492        ).SerializeToString()
493        res = clnpb.DeldatastoreResponse
494        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
495
496    def list_datastore(self, key=None):
497        uri = "/cln.Node/ListDatastore"
498        req = clnpb.ListdatastoreRequest(key=key).SerializeToString()
499        res = clnpb.ListdatastoreResponse
500        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
501
502    def get_lsp_client(
503        self,
504    ) -> LspClient:
505        native_lsps = self.inner.get_lsp_client()
506        return LspClient(native_lsps)
507
508    def configure(self, close_to_addr: str) -> None:
509        req = nodepb.GlConfig(close_to_addr=close_to_addr).SerializeToString()
510
511        return self.inner.configure(req)
512
513    def wait_blockheight(
514        self,
515        blockheight: int,
516        timeout: Optional[int] = None,
517    ):
518        """Wait until the blockchain has reached the specified blockheight."""
519        uri = "/cln.Node/WaitBlockheight"
520        req = clnpb.WaitblockheightRequest(
521            blockheight=blockheight, timeout=timeout
522        ).SerializeToString()
523        res = clnpb.WaitblockheightResponse
524        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
525
526    def fetch_invoice(
527        self,
528        offer: str,
529        amount_msat: Optional[Amount] = None,
530        quantity: Optional[int] = None,
531        recurrence_counter: Optional[int] = None,
532        recurrence_start: Optional[int] = None,
533        recurrence_label: Optional[str] = None,
534        timeout: Optional[int] = None,
535        payer_note: Optional[str] = None,
536    ) -> clnpb.FetchinvoiceResponse:
537        """Fetch an invoice based on an offer.
538
539        Contact the issuer of an offer to get an actual invoice that
540        can be paid. It highlights any changes between the offer and
541        the returned invoice.
542
543        """
544        uri = "/cln.Node/FetchInvoice"
545        req = clnpb.FetchinvoiceRequest(
546            offer=offer,
547            amount_msat=amount_msat,
548            quantity=quantity,
549            recurrence_counter=recurrence_counter,
550            recurrence_start=recurrence_start,
551            recurrence_label=recurrence_label,
552            timeout=timeout,
553            payer_note=payer_note,
554        ).SerializeToString()
555        res = clnpb.FetchinvoiceResponse
556        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
557
558    def wait(self, subsystem, indexname, nextvalue: int) -> clnpb.WaitResponse:
559        """Wait for the next event in the provided subsystem.
560
561        Returns once the index given by indexname in subsystem reaches
562        or exceeds nextvalue.
563
564        """
565        uri = "/cln.Node/Wait"
566        req = clnpb.WaitRequest(
567            subsystem=subsystem,
568            indexname=indexname,
569            nextvalue=nextvalue,
570        ).SerializeToString()
571        res = clnpb.WaitResponse
572        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
573
574
575def normalize_node_id(node_id, string=False):
576    if len(node_id) == 66:
577        node_id = unhexlify(node_id)
578
579    if len(node_id) != 33:
580        raise ValueError("node_id is not 33 (binary) or 66 (hex) bytes long")
581
582    if isinstance(node_id, str):
583        node_id = node_id.encode("ASCII")
584    return node_id if not string else hexlify(node_id).encode("ASCII")
def backup_decrypt_with_seed(encrypted, seed):
class Signer:
31class Signer(object):
32    def __init__(self, secret: bytes, network: str, creds: Credentials):
33        self.inner = native.Signer(secret, network, creds)
34        self.creds = creds
35        self.handle: Optional[native.SignerHandle] = None
36
37    def run_in_thread(self) -> "native.SignerHandle":
38        if self.handle is not None:
39            raise ValueError(
40                "This signer is already running, please shut it down before starting it again"
41            )
42        self.handle = self.inner.run_in_thread()
43        return self.handle
44
45    def run_in_foreground(self) -> None:
46        return self.inner.run_in_foreground()
47
48    def node_id(self) -> bytes:
49        return bytes(self.inner.node_id())
50
51    def version(self) -> str:
52        return self.inner.version()
53
54    def sign_challenge(self, message: bytes) -> bytes:
55        return bytes(self.inner.sign_challenge(message))
56
57    def shutdown(self) -> None:
58        if self.handle is None:
59            raise ValueError("Attempted to shut down a signer that is not running")
60        self.handle.shutdown()
61        self.handle = None
62
63    def create_rune(
64        self, restrictions: List[List[str]], rune: Optional[str] = None
65    ) -> str:
66        return self.inner.create_rune(restrictions, rune)
67
68    def is_running(self) -> bool:
69        return self.handle is not None
Signer(secret: bytes, network: str, creds: Credentials)
32    def __init__(self, secret: bytes, network: str, creds: Credentials):
33        self.inner = native.Signer(secret, network, creds)
34        self.creds = creds
35        self.handle: Optional[native.SignerHandle] = None
def run_in_thread(self) -> SignerHandle:
37    def run_in_thread(self) -> "native.SignerHandle":
38        if self.handle is not None:
39            raise ValueError(
40                "This signer is already running, please shut it down before starting it again"
41            )
42        self.handle = self.inner.run_in_thread()
43        return self.handle
def run_in_foreground(self) -> None:
45    def run_in_foreground(self) -> None:
46        return self.inner.run_in_foreground()
def node_id(self) -> bytes:
48    def node_id(self) -> bytes:
49        return bytes(self.inner.node_id())
def version(self) -> str:
51    def version(self) -> str:
52        return self.inner.version()
def sign_challenge(self, message: bytes) -> bytes:
54    def sign_challenge(self, message: bytes) -> bytes:
55        return bytes(self.inner.sign_challenge(message))
def shutdown(self) -> None:
57    def shutdown(self) -> None:
58        if self.handle is None:
59            raise ValueError("Attempted to shut down a signer that is not running")
60        self.handle.shutdown()
61        self.handle = None
def create_rune(self, restrictions: List[List[str]], rune: Optional[str] = None) -> str:
63    def create_rune(
64        self, restrictions: List[List[str]], rune: Optional[str] = None
65    ) -> str:
66        return self.inner.create_rune(restrictions, rune)
def is_running(self) -> bool:
68    def is_running(self) -> bool:
69        return self.handle is not None
class Scheduler:
 72class Scheduler(object):
 73
 74    def __init__(self, network: str, creds: Optional[Credentials] = None):
 75        self.network = network
 76        self.creds = creds if creds is not None else native.Credentials()
 77        self.inner = native.Scheduler(network, self.creds)
 78
 79    def schedule(self) -> schedpb.NodeInfoResponse:
 80        res = self.inner.schedule()
 81        return schedpb.NodeInfoResponse.FromString(bytes(res))
 82
 83    def get_node_info(self, wait: bool = False):
 84        res = self.inner.get_node_info(wait)
 85        return schedpb.NodeInfoResponse.FromString(bytes(res))
 86
 87    def register(self, signer: Signer, invite_code: Optional[str] = None) -> schedpb.RegistrationResponse:
 88        res = self.inner.register(signer.inner, invite_code)
 89        return schedpb.RegistrationResponse.FromString(bytes(res))
 90
 91    def recover(self, signer: Signer) -> schedpb.RecoveryResponse:
 92        res = self.inner.recover(signer.inner)
 93        return schedpb.RecoveryResponse.FromString(bytes(res))
 94
 95    def authenticate(self, creds: Credentials):
 96        self.creds = creds
 97        self.inner = self.inner.authenticate(creds)
 98        return self
 99
100    def export_node(self) -> schedpb.ExportNodeResponse:
101        res = schedpb.ExportNodeResponse
102        return res.FromString(bytes(self.inner.export_node()))
103
104    def node(self) -> "Node":
105        res = self.inner.node()
106        info = schedpb.NodeInfoResponse.FromString(bytes(res))
107        return Node(
108            node_id=self.creds.node_id(),
109            grpc_uri=info.grpc_uri,
110            creds=self.creds,
111        )
112
113    def get_invite_codes(self) -> schedpb.ListInviteCodesResponse:
114        cls = schedpb.ListInviteCodesResponse
115        return cls.FromString(bytes(self.inner.get_invite_codes()))
116
117    def add_outgoing_webhook(self, uri: str) -> schedpb.AddOutgoingWebhookResponse:
118        res = self.inner.add_outgoing_webhook(uri)
119        return schedpb.AddOutgoingWebhookResponse.FromString(bytes(res))
120
121    def list_outgoing_webhooks(self) -> schedpb.ListOutgoingWebhooksResponse:
122        res = self.inner.list_outgoing_webhooks()
123        return schedpb.ListOutgoingWebhooksResponse.FromString(bytes(res))
124
125    def delete_outgoing_webhook(self, webhook_id: int) -> None:
126        res = self.inner.delete_outgoing_webhooks([webhook_id])
127
128    def delete_outgoing_webhooks(self, webhook_ids: List[int]) -> None:
129        res = self.inner.delete_outgoing_webhooks(webhook_ids)
130
131    def rotate_outgoing_webhook_secret(self, webhook_id: int) -> schedpb.WebhookSecretResponse:
132        res = self.inner.rotate_outgoing_webhook_secret(webhook_id)
133        return schedpb.WebhookSecretResponse.FromString(bytes(res))
Scheduler(network: str, creds: Optional[Credentials] = None)
74    def __init__(self, network: str, creds: Optional[Credentials] = None):
75        self.network = network
76        self.creds = creds if creds is not None else native.Credentials()
77        self.inner = native.Scheduler(network, self.creds)
def schedule(self) -> glclient.scheduler_pb2.NodeInfoResponse:
79    def schedule(self) -> schedpb.NodeInfoResponse:
80        res = self.inner.schedule()
81        return schedpb.NodeInfoResponse.FromString(bytes(res))
def get_node_info(self, wait: bool = False):
83    def get_node_info(self, wait: bool = False):
84        res = self.inner.get_node_info(wait)
85        return schedpb.NodeInfoResponse.FromString(bytes(res))
def register( self, signer: glclient.Signer, invite_code: Optional[str] = None) -> glclient.scheduler_pb2.RegistrationResponse:
87    def register(self, signer: Signer, invite_code: Optional[str] = None) -> schedpb.RegistrationResponse:
88        res = self.inner.register(signer.inner, invite_code)
89        return schedpb.RegistrationResponse.FromString(bytes(res))
def recover(self, signer: glclient.Signer) -> glclient.scheduler_pb2.RecoveryResponse:
91    def recover(self, signer: Signer) -> schedpb.RecoveryResponse:
92        res = self.inner.recover(signer.inner)
93        return schedpb.RecoveryResponse.FromString(bytes(res))
def authenticate(self, creds: Credentials):
95    def authenticate(self, creds: Credentials):
96        self.creds = creds
97        self.inner = self.inner.authenticate(creds)
98        return self
def export_node(self) -> glclient.scheduler_pb2.ExportNodeResponse:
100    def export_node(self) -> schedpb.ExportNodeResponse:
101        res = schedpb.ExportNodeResponse
102        return res.FromString(bytes(self.inner.export_node()))
def node(self) -> glclient.Node:
104    def node(self) -> "Node":
105        res = self.inner.node()
106        info = schedpb.NodeInfoResponse.FromString(bytes(res))
107        return Node(
108            node_id=self.creds.node_id(),
109            grpc_uri=info.grpc_uri,
110            creds=self.creds,
111        )
def get_invite_codes(self) -> glclient.scheduler_pb2.ListInviteCodesResponse:
113    def get_invite_codes(self) -> schedpb.ListInviteCodesResponse:
114        cls = schedpb.ListInviteCodesResponse
115        return cls.FromString(bytes(self.inner.get_invite_codes()))
def add_outgoing_webhook(self, uri: str) -> glclient.scheduler_pb2.AddOutgoingWebhookResponse:
117    def add_outgoing_webhook(self, uri: str) -> schedpb.AddOutgoingWebhookResponse:
118        res = self.inner.add_outgoing_webhook(uri)
119        return schedpb.AddOutgoingWebhookResponse.FromString(bytes(res))
def list_outgoing_webhooks(self) -> glclient.scheduler_pb2.ListOutgoingWebhooksResponse:
121    def list_outgoing_webhooks(self) -> schedpb.ListOutgoingWebhooksResponse:
122        res = self.inner.list_outgoing_webhooks()
123        return schedpb.ListOutgoingWebhooksResponse.FromString(bytes(res))
def delete_outgoing_webhook(self, webhook_id: int) -> None:
125    def delete_outgoing_webhook(self, webhook_id: int) -> None:
126        res = self.inner.delete_outgoing_webhooks([webhook_id])
def delete_outgoing_webhooks(self, webhook_ids: List[int]) -> None:
128    def delete_outgoing_webhooks(self, webhook_ids: List[int]) -> None:
129        res = self.inner.delete_outgoing_webhooks(webhook_ids)
def rotate_outgoing_webhook_secret(self, webhook_id: int) -> glclient.scheduler_pb2.WebhookSecretResponse:
131    def rotate_outgoing_webhook_secret(self, webhook_id: int) -> schedpb.WebhookSecretResponse:
132        res = self.inner.rotate_outgoing_webhook_secret(webhook_id)
133        return schedpb.WebhookSecretResponse.FromString(bytes(res))
class Node:
136class Node(object):
137
138    def __init__(
139        self, node_id: bytes, grpc_uri: str, creds: Credentials
140    ) -> None:
141        self.creds = creds
142        self.grpc_uri = grpc_uri
143        self.inner = native.Node(
144            node_id=node_id, grpc_uri=grpc_uri, creds=creds
145        )
146        self.logger = logging.getLogger("glclient.Node")
147
148    def get_info(self) -> clnpb.GetinfoResponse:
149        uri = "/cln.Node/Getinfo"
150        req = clnpb.GetinfoRequest().SerializeToString()
151        res = clnpb.GetinfoResponse
152
153        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
154
155    def stop(self) -> None:
156        uri = "/cln.Node/Stop"
157        req = clnpb.StopRequest().SerializeToString()
158
159        try:
160            # This fails, since we just get disconnected, but that's
161            # on purpose, so drop the error silently.
162            self.inner.call(uri, bytes(req))
163        except ValueError as e:
164            self.logger.debug(
165                f"Caught an expected exception: {e}. Don't worry it's expected."
166            )
167
168    def list_funds(
169        self,
170        spent: Optional[bool] = None,
171    ) -> clnpb.ListfundsResponse:
172        uri = "/cln.Node/ListFunds"
173        res = clnpb.ListfundsResponse
174        req = clnpb.ListfundsRequest(
175            spent=spent,
176        ).SerializeToString()
177
178        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
179
180    def list_peers(self) -> clnpb.ListpeersResponse:
181        uri = "/cln.Node/ListPeers"
182        req = clnpb.ListpeersRequest().SerializeToString()
183        res = clnpb.ListpeersResponse
184
185        return res.FromString(
186            bytes(self.inner.call(uri, bytes(req)))
187        )
188
189    def list_peer_channels(
190            self,
191            node_id: Optional[bytes] = None
192    ) -> clnpb.ListpeerchannelsResponse:
193        uri = "/cln.Node/ListPeerChannels"
194        req = clnpb.ListpeerchannelsRequest(
195            id=node_id,
196        ).SerializeToString()
197        res = clnpb.ListpeerchannelsResponse
198        return res.FromString(
199            bytes(self.inner.call(uri, bytes(req)))
200        )
201
202    def list_closed_channels(self) -> clnpb.ListclosedchannelsResponse:
203        uri = "/cln.Node/ListClosedChannels"
204        req = clnpb.ListclosedchannelsRequest().SerializeToString()
205        res = clnpb.ListclosedchannelsResponse
206
207        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
208
209    def list_channels(
210        self,
211        short_channel_id: Optional[str] = None,
212        source: Optional[bytes] = None,
213        destination: Optional[bytes] = None,
214    ) -> clnpb.ListchannelsResponse:
215        uri = "/cln.Node/ListChannels"
216        req = clnpb.ListchannelsRequest(
217            short_channel_id=short_channel_id, source=source, destination=destination
218        ).SerializeToString()
219        res = clnpb.ListchannelsResponse
220
221        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
222
223    def listpays(
224        self,
225        bolt11: Optional[str] = None,
226        payment_hash: Optional[bytes] = None,
227        status: Optional[clnpb.ListpaysRequest.ListpaysStatus.ValueType] = None,
228    ) -> clnpb.ListpaysResponse:
229        uri = "/cln.Node/ListPays"
230        req = clnpb.ListpaysRequest(
231            bolt11=bolt11,
232            payment_hash=payment_hash,
233            status=status,
234        ).SerializeToString()
235        res = clnpb.ListpaysResponse
236
237        return res.FromString(bytes(self.inner.call(uri, req)))
238
239    def list_invoices(
240        self,
241        label: Optional[str] = None,
242        invstring: Optional[str] = None,
243        payment_hash: Optional[bytes] = None,
244        offer_id: Optional[str] = None,
245        index: Optional[clnpb.ListinvoicesRequest.ListinvoicesIndex.ValueType] = None,
246        start: Optional[int] = None,
247        limit: Optional[int] = None,
248    ) -> clnpb.ListinvoicesResponse:
249        uri = "/cln.Node/ListInvoices"
250        res = clnpb.ListinvoicesResponse
251        req = clnpb.ListinvoicesRequest(
252            label=label,
253            invstring=invstring,
254            payment_hash=payment_hash,
255            offer_id=offer_id,
256            index=index,
257            start=start,
258            limit=limit,
259        ).SerializeToString()
260        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
261
262    def connect_peer(
263        self, node_id, host: Optional[str] = None, port: Optional[int] = None
264    ) -> clnpb.ConnectResponse:
265        if len(node_id) == 33:
266            node_id = hexlify(node_id)
267
268        if isinstance(node_id, bytes):
269            node_id = node_id.decode("ASCII")
270
271        uri = "/cln.Node/ConnectPeer"
272        res = clnpb.ConnectResponse
273        req = clnpb.ConnectRequest(
274            id=node_id,
275            host=host,
276            port=port,
277        ).SerializeToString()
278
279        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
280
281    def decode(self, string: str) -> clnpb.DecodeResponse:
282        uri = "/cln.Node/Decode"
283        res = clnpb.DecodeResponse
284        req = clnpb.DecodeRequest(
285            string=string,
286        ).SerializeToString()
287
288        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
289
290    def decodepay(
291        self, bolt11: str, description: Optional[str]
292    ) -> clnpb.DecodepayResponse:
293        uri = "/cln.Node/DecodePay"
294        res = clnpb.DecodepayResponse
295        req = clnpb.DecodepayRequest(
296            bolt11=bolt11,
297            description=description,
298        ).SerializeToString()
299
300        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
301
302    def disconnect_peer(self, peer_id: str, force=False) -> clnpb.DisconnectResponse:
303        uri = "/cln.Node/Disconnect"
304        res = clnpb.DisconnectResponse
305        req = clnpb.DisconnectRequest(
306            id=bytes.fromhex(peer_id),
307            force=force,
308        ).SerializeToString()
309
310        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
311
312    def new_address(self) -> clnpb.NewaddrResponse:
313        uri = "/cln.Node/NewAddr"
314        req = clnpb.NewaddrRequest().SerializeToString()
315        res = clnpb.NewaddrResponse
316
317        return res.FromString(bytes(self.inner.call(uri, req)))
318
319    def withdraw(
320        self, destination, amount: AmountOrAll, minconf: int = 0
321    ) -> clnpb.WithdrawResponse:
322        uri = "/cln.Node/Withdraw"
323        res = clnpb.WithdrawResponse
324        req = clnpb.WithdrawRequest(
325            destination=destination, satoshi=amount, minconf=minconf
326        ).SerializeToString()
327
328        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
329
330    def fund_channel(
331        self,
332        id: bytes,
333        amount,
334        announce: Optional[bool] = False,
335        minconf: Optional[int] = 1,
336    ) -> clnpb.FundchannelResponse:
337
338        if len(id) != 33:
339            raise ValueError("id is not 33 bytes long")
340
341        uri = "/cln.Node/FundChannel"
342        res = clnpb.FundchannelResponse
343        req = clnpb.FundchannelRequest(
344            id=id,
345            amount=amount,
346            announce=announce,
347            minconf=minconf,
348        ).SerializeToString()
349
350        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
351
352    def close(
353        self, id: bytes, unilateraltimeout=None, destination=None
354    ) -> clnpb.CloseResponse:
355        if len(id) != 33:
356            raise ValueError("node_id is not 33 bytes long")
357
358        uri = "/cln.Node/Close"
359        res = clnpb.CloseResponse
360        req = clnpb.CloseRequest(
361            id=id.hex(),
362            unilateraltimeout=unilateraltimeout,
363            destination=destination,
364        ).SerializeToString()
365
366        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
367
368    def invoice(
369        self,
370        amount_msat: clnpb.AmountOrAny,
371        label: str,
372        description: str,
373        expiry: Optional[int] = None,
374        fallbacks: Optional[List[str]] = None,
375        preimage: Optional[bytes] = None,
376        cltv: Optional[int] = None,
377        deschashonly: Optional[bool] = None,
378    ) -> clnpb.InvoiceResponse:
379        if preimage and len(preimage) != 32:
380            raise ValueError("Preimage must be 32 bytes in length")
381
382        uri = "/cln.Node/Invoice"
383        res = clnpb.InvoiceResponse
384        req = clnpb.InvoiceRequest(
385            amount_msat=amount_msat,
386            label=label,
387            description=description,
388            preimage=preimage,
389            expiry=expiry,
390            fallbacks=fallbacks,
391            cltv=cltv,
392            deschashonly=deschashonly,
393        ).SerializeToString()
394
395        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
396
397    def pay(
398        self,
399        bolt11: str,
400        amount_msat: Optional[clnpb.Amount] = None,
401        retry_for: int = 0,
402        maxfee: Optional[clnpb.Amount] = None,
403        maxfeepercent: Optional[float] = None,
404    ) -> clnpb.PayResponse:
405        uri = "/cln.Node/Pay"
406        res = clnpb.PayResponse
407        req = clnpb.PayRequest(
408            bolt11=bolt11,
409            amount_msat=amount_msat,
410            retry_for=retry_for,
411            maxfeepercent=maxfeepercent,
412            maxfee=maxfee,
413        ).SerializeToString()
414
415        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
416
417    def trampoline_pay(self, bolt11: str, trampoline_node_id: bytes, amount_msat: Optional[int] = None, label: Optional[str] = None):
418        res = self.inner.trampoline_pay(
419            bolt11=bolt11,
420            trampoline_node_id=trampoline_node_id,
421            amount_msat=amount_msat,
422            label=label,
423        )
424        return nodepb.TrampolinePayResponse.FromString(bytes(res))
425
426    def keysend(
427        self,
428        destination: bytes,
429        amount: clnpb.Amount,
430        label: Optional[str] = None,
431        routehints: Optional[clnpb.RoutehintList] = None,
432        extratlvs: Optional[clnpb.TlvStream] = None,
433    ) -> clnpb.KeysendResponse:
434        uri = "/cln.Node/KeySend"
435        res = clnpb.KeysendResponse
436        req = clnpb.KeysendRequest(
437            destination=normalize_node_id(destination, string=False),
438            amount_msat=amount,
439            label=label if label else "",
440            routehints=routehints,
441            extratlvs=extratlvs,
442        ).SerializeToString()
443
444        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
445
446    def stream_log(self):
447        """Stream logs as they get generated on the server side."""
448        stream = self.inner.stream_log(b"")
449        while True:
450            n = stream.next()
451            if n is None:
452                break
453            yield nodepb.LogEntry.FromString(bytes(n))
454
455    def stream_incoming(self):
456        stream = self.inner.stream_incoming(b"")
457        while True:
458            n = stream.next()
459            if n is None:
460                break
461            yield nodepb.IncomingPayment.FromString(bytes(n))
462
463    def stream_custommsg(self):
464        stream = self.inner.stream_custommsg(b"")
465        while True:
466            n = stream.next()
467            if n is None:
468                break
469            yield nodepb.Custommsg.FromString(bytes(n))
470
471    def send_custommsg(self, node_id: str, msg: bytes) -> clnpb.SendcustommsgResponse:
472        uri = "/cln.Node/SendCustomMsg"
473        res = clnpb.SendcustommsgResponse
474        req = clnpb.SendcustommsgRequest(
475            node_id=normalize_node_id(node_id),
476            msg=msg,
477        ).SerializeToString()
478
479        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
480
481    def datastore(self, key, string=None, hex=None, mode=None, generation=None):
482        uri = "/cln.Node/Datastore"
483        req = clnpb.DatastoreRequest(
484            key=key, string=string, hex=hex, mode=mode, generation=generation
485        ).SerializeToString()
486        res = clnpb.DatastoreResponse
487        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
488
489    def del_datastore(self, key, generation=None):
490        uri = "/cln.Node/DelDatastore"
491        req = clnpb.DeldatastoreRequest(
492            key=key, generation=generation
493        ).SerializeToString()
494        res = clnpb.DeldatastoreResponse
495        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
496
497    def list_datastore(self, key=None):
498        uri = "/cln.Node/ListDatastore"
499        req = clnpb.ListdatastoreRequest(key=key).SerializeToString()
500        res = clnpb.ListdatastoreResponse
501        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
502
503    def get_lsp_client(
504        self,
505    ) -> LspClient:
506        native_lsps = self.inner.get_lsp_client()
507        return LspClient(native_lsps)
508
509    def configure(self, close_to_addr: str) -> None:
510        req = nodepb.GlConfig(close_to_addr=close_to_addr).SerializeToString()
511
512        return self.inner.configure(req)
513
514    def wait_blockheight(
515        self,
516        blockheight: int,
517        timeout: Optional[int] = None,
518    ):
519        """Wait until the blockchain has reached the specified blockheight."""
520        uri = "/cln.Node/WaitBlockheight"
521        req = clnpb.WaitblockheightRequest(
522            blockheight=blockheight, timeout=timeout
523        ).SerializeToString()
524        res = clnpb.WaitblockheightResponse
525        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
526
527    def fetch_invoice(
528        self,
529        offer: str,
530        amount_msat: Optional[Amount] = None,
531        quantity: Optional[int] = None,
532        recurrence_counter: Optional[int] = None,
533        recurrence_start: Optional[int] = None,
534        recurrence_label: Optional[str] = None,
535        timeout: Optional[int] = None,
536        payer_note: Optional[str] = None,
537    ) -> clnpb.FetchinvoiceResponse:
538        """Fetch an invoice based on an offer.
539
540        Contact the issuer of an offer to get an actual invoice that
541        can be paid. It highlights any changes between the offer and
542        the returned invoice.
543
544        """
545        uri = "/cln.Node/FetchInvoice"
546        req = clnpb.FetchinvoiceRequest(
547            offer=offer,
548            amount_msat=amount_msat,
549            quantity=quantity,
550            recurrence_counter=recurrence_counter,
551            recurrence_start=recurrence_start,
552            recurrence_label=recurrence_label,
553            timeout=timeout,
554            payer_note=payer_note,
555        ).SerializeToString()
556        res = clnpb.FetchinvoiceResponse
557        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
558
559    def wait(self, subsystem, indexname, nextvalue: int) -> clnpb.WaitResponse:
560        """Wait for the next event in the provided subsystem.
561
562        Returns once the index given by indexname in subsystem reaches
563        or exceeds nextvalue.
564
565        """
566        uri = "/cln.Node/Wait"
567        req = clnpb.WaitRequest(
568            subsystem=subsystem,
569            indexname=indexname,
570            nextvalue=nextvalue,
571        ).SerializeToString()
572        res = clnpb.WaitResponse
573        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
Node(node_id: bytes, grpc_uri: str, creds: Credentials)
138    def __init__(
139        self, node_id: bytes, grpc_uri: str, creds: Credentials
140    ) -> None:
141        self.creds = creds
142        self.grpc_uri = grpc_uri
143        self.inner = native.Node(
144            node_id=node_id, grpc_uri=grpc_uri, creds=creds
145        )
146        self.logger = logging.getLogger("glclient.Node")
def get_info(self) -> node_pb2.GetinfoResponse:
148    def get_info(self) -> clnpb.GetinfoResponse:
149        uri = "/cln.Node/Getinfo"
150        req = clnpb.GetinfoRequest().SerializeToString()
151        res = clnpb.GetinfoResponse
152
153        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def stop(self) -> None:
155    def stop(self) -> None:
156        uri = "/cln.Node/Stop"
157        req = clnpb.StopRequest().SerializeToString()
158
159        try:
160            # This fails, since we just get disconnected, but that's
161            # on purpose, so drop the error silently.
162            self.inner.call(uri, bytes(req))
163        except ValueError as e:
164            self.logger.debug(
165                f"Caught an expected exception: {e}. Don't worry it's expected."
166            )
def list_funds(self, spent: Optional[bool] = None) -> node_pb2.ListfundsResponse:
168    def list_funds(
169        self,
170        spent: Optional[bool] = None,
171    ) -> clnpb.ListfundsResponse:
172        uri = "/cln.Node/ListFunds"
173        res = clnpb.ListfundsResponse
174        req = clnpb.ListfundsRequest(
175            spent=spent,
176        ).SerializeToString()
177
178        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def list_peers(self) -> node_pb2.ListpeersResponse:
180    def list_peers(self) -> clnpb.ListpeersResponse:
181        uri = "/cln.Node/ListPeers"
182        req = clnpb.ListpeersRequest().SerializeToString()
183        res = clnpb.ListpeersResponse
184
185        return res.FromString(
186            bytes(self.inner.call(uri, bytes(req)))
187        )
def list_peer_channels( self, node_id: Optional[bytes] = None) -> node_pb2.ListpeerchannelsResponse:
189    def list_peer_channels(
190            self,
191            node_id: Optional[bytes] = None
192    ) -> clnpb.ListpeerchannelsResponse:
193        uri = "/cln.Node/ListPeerChannels"
194        req = clnpb.ListpeerchannelsRequest(
195            id=node_id,
196        ).SerializeToString()
197        res = clnpb.ListpeerchannelsResponse
198        return res.FromString(
199            bytes(self.inner.call(uri, bytes(req)))
200        )
def list_closed_channels(self) -> node_pb2.ListclosedchannelsResponse:
202    def list_closed_channels(self) -> clnpb.ListclosedchannelsResponse:
203        uri = "/cln.Node/ListClosedChannels"
204        req = clnpb.ListclosedchannelsRequest().SerializeToString()
205        res = clnpb.ListclosedchannelsResponse
206
207        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def list_channels( self, short_channel_id: Optional[str] = None, source: Optional[bytes] = None, destination: Optional[bytes] = None) -> node_pb2.ListchannelsResponse:
209    def list_channels(
210        self,
211        short_channel_id: Optional[str] = None,
212        source: Optional[bytes] = None,
213        destination: Optional[bytes] = None,
214    ) -> clnpb.ListchannelsResponse:
215        uri = "/cln.Node/ListChannels"
216        req = clnpb.ListchannelsRequest(
217            short_channel_id=short_channel_id, source=source, destination=destination
218        ).SerializeToString()
219        res = clnpb.ListchannelsResponse
220
221        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def listpays( self, bolt11: Optional[str] = None, payment_hash: Optional[bytes] = None, status: Optional[int] = None) -> node_pb2.ListpaysResponse:
223    def listpays(
224        self,
225        bolt11: Optional[str] = None,
226        payment_hash: Optional[bytes] = None,
227        status: Optional[clnpb.ListpaysRequest.ListpaysStatus.ValueType] = None,
228    ) -> clnpb.ListpaysResponse:
229        uri = "/cln.Node/ListPays"
230        req = clnpb.ListpaysRequest(
231            bolt11=bolt11,
232            payment_hash=payment_hash,
233            status=status,
234        ).SerializeToString()
235        res = clnpb.ListpaysResponse
236
237        return res.FromString(bytes(self.inner.call(uri, req)))
def list_invoices( self, label: Optional[str] = None, invstring: Optional[str] = None, payment_hash: Optional[bytes] = None, offer_id: Optional[str] = None, index: Optional[int] = None, start: Optional[int] = None, limit: Optional[int] = None) -> node_pb2.ListinvoicesResponse:
239    def list_invoices(
240        self,
241        label: Optional[str] = None,
242        invstring: Optional[str] = None,
243        payment_hash: Optional[bytes] = None,
244        offer_id: Optional[str] = None,
245        index: Optional[clnpb.ListinvoicesRequest.ListinvoicesIndex.ValueType] = None,
246        start: Optional[int] = None,
247        limit: Optional[int] = None,
248    ) -> clnpb.ListinvoicesResponse:
249        uri = "/cln.Node/ListInvoices"
250        res = clnpb.ListinvoicesResponse
251        req = clnpb.ListinvoicesRequest(
252            label=label,
253            invstring=invstring,
254            payment_hash=payment_hash,
255            offer_id=offer_id,
256            index=index,
257            start=start,
258            limit=limit,
259        ).SerializeToString()
260        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def connect_peer( self, node_id, host: Optional[str] = None, port: Optional[int] = None) -> node_pb2.ConnectResponse:
262    def connect_peer(
263        self, node_id, host: Optional[str] = None, port: Optional[int] = None
264    ) -> clnpb.ConnectResponse:
265        if len(node_id) == 33:
266            node_id = hexlify(node_id)
267
268        if isinstance(node_id, bytes):
269            node_id = node_id.decode("ASCII")
270
271        uri = "/cln.Node/ConnectPeer"
272        res = clnpb.ConnectResponse
273        req = clnpb.ConnectRequest(
274            id=node_id,
275            host=host,
276            port=port,
277        ).SerializeToString()
278
279        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def decode(self, string: str) -> node_pb2.DecodeResponse:
281    def decode(self, string: str) -> clnpb.DecodeResponse:
282        uri = "/cln.Node/Decode"
283        res = clnpb.DecodeResponse
284        req = clnpb.DecodeRequest(
285            string=string,
286        ).SerializeToString()
287
288        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def decodepay( self, bolt11: str, description: Optional[str]) -> node_pb2.DecodepayResponse:
290    def decodepay(
291        self, bolt11: str, description: Optional[str]
292    ) -> clnpb.DecodepayResponse:
293        uri = "/cln.Node/DecodePay"
294        res = clnpb.DecodepayResponse
295        req = clnpb.DecodepayRequest(
296            bolt11=bolt11,
297            description=description,
298        ).SerializeToString()
299
300        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def disconnect_peer(self, peer_id: str, force=False) -> node_pb2.DisconnectResponse:
302    def disconnect_peer(self, peer_id: str, force=False) -> clnpb.DisconnectResponse:
303        uri = "/cln.Node/Disconnect"
304        res = clnpb.DisconnectResponse
305        req = clnpb.DisconnectRequest(
306            id=bytes.fromhex(peer_id),
307            force=force,
308        ).SerializeToString()
309
310        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def new_address(self) -> node_pb2.NewaddrResponse:
312    def new_address(self) -> clnpb.NewaddrResponse:
313        uri = "/cln.Node/NewAddr"
314        req = clnpb.NewaddrRequest().SerializeToString()
315        res = clnpb.NewaddrResponse
316
317        return res.FromString(bytes(self.inner.call(uri, req)))
def withdraw( self, destination, amount: primitives_pb2.AmountOrAll, minconf: int = 0) -> node_pb2.WithdrawResponse:
319    def withdraw(
320        self, destination, amount: AmountOrAll, minconf: int = 0
321    ) -> clnpb.WithdrawResponse:
322        uri = "/cln.Node/Withdraw"
323        res = clnpb.WithdrawResponse
324        req = clnpb.WithdrawRequest(
325            destination=destination, satoshi=amount, minconf=minconf
326        ).SerializeToString()
327
328        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def fund_channel( self, id: bytes, amount, announce: Optional[bool] = False, minconf: Optional[int] = 1) -> node_pb2.FundchannelResponse:
330    def fund_channel(
331        self,
332        id: bytes,
333        amount,
334        announce: Optional[bool] = False,
335        minconf: Optional[int] = 1,
336    ) -> clnpb.FundchannelResponse:
337
338        if len(id) != 33:
339            raise ValueError("id is not 33 bytes long")
340
341        uri = "/cln.Node/FundChannel"
342        res = clnpb.FundchannelResponse
343        req = clnpb.FundchannelRequest(
344            id=id,
345            amount=amount,
346            announce=announce,
347            minconf=minconf,
348        ).SerializeToString()
349
350        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def close( self, id: bytes, unilateraltimeout=None, destination=None) -> node_pb2.CloseResponse:
352    def close(
353        self, id: bytes, unilateraltimeout=None, destination=None
354    ) -> clnpb.CloseResponse:
355        if len(id) != 33:
356            raise ValueError("node_id is not 33 bytes long")
357
358        uri = "/cln.Node/Close"
359        res = clnpb.CloseResponse
360        req = clnpb.CloseRequest(
361            id=id.hex(),
362            unilateraltimeout=unilateraltimeout,
363            destination=destination,
364        ).SerializeToString()
365
366        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def invoice( self, amount_msat: primitives_pb2.AmountOrAny, label: str, description: str, expiry: Optional[int] = None, fallbacks: Optional[List[str]] = None, preimage: Optional[bytes] = None, cltv: Optional[int] = None, deschashonly: Optional[bool] = None) -> node_pb2.InvoiceResponse:
368    def invoice(
369        self,
370        amount_msat: clnpb.AmountOrAny,
371        label: str,
372        description: str,
373        expiry: Optional[int] = None,
374        fallbacks: Optional[List[str]] = None,
375        preimage: Optional[bytes] = None,
376        cltv: Optional[int] = None,
377        deschashonly: Optional[bool] = None,
378    ) -> clnpb.InvoiceResponse:
379        if preimage and len(preimage) != 32:
380            raise ValueError("Preimage must be 32 bytes in length")
381
382        uri = "/cln.Node/Invoice"
383        res = clnpb.InvoiceResponse
384        req = clnpb.InvoiceRequest(
385            amount_msat=amount_msat,
386            label=label,
387            description=description,
388            preimage=preimage,
389            expiry=expiry,
390            fallbacks=fallbacks,
391            cltv=cltv,
392            deschashonly=deschashonly,
393        ).SerializeToString()
394
395        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def pay( self, bolt11: str, amount_msat: Optional[primitives_pb2.Amount] = None, retry_for: int = 0, maxfee: Optional[primitives_pb2.Amount] = None, maxfeepercent: Optional[float] = None) -> node_pb2.PayResponse:
397    def pay(
398        self,
399        bolt11: str,
400        amount_msat: Optional[clnpb.Amount] = None,
401        retry_for: int = 0,
402        maxfee: Optional[clnpb.Amount] = None,
403        maxfeepercent: Optional[float] = None,
404    ) -> clnpb.PayResponse:
405        uri = "/cln.Node/Pay"
406        res = clnpb.PayResponse
407        req = clnpb.PayRequest(
408            bolt11=bolt11,
409            amount_msat=amount_msat,
410            retry_for=retry_for,
411            maxfeepercent=maxfeepercent,
412            maxfee=maxfee,
413        ).SerializeToString()
414
415        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def trampoline_pay( self, bolt11: str, trampoline_node_id: bytes, amount_msat: Optional[int] = None, label: Optional[str] = None):
417    def trampoline_pay(self, bolt11: str, trampoline_node_id: bytes, amount_msat: Optional[int] = None, label: Optional[str] = None):
418        res = self.inner.trampoline_pay(
419            bolt11=bolt11,
420            trampoline_node_id=trampoline_node_id,
421            amount_msat=amount_msat,
422            label=label,
423        )
424        return nodepb.TrampolinePayResponse.FromString(bytes(res))
def keysend( self, destination: bytes, amount: primitives_pb2.Amount, label: Optional[str] = None, routehints: Optional[primitives_pb2.RoutehintList] = None, extratlvs: Optional[primitives_pb2.TlvStream] = None) -> node_pb2.KeysendResponse:
426    def keysend(
427        self,
428        destination: bytes,
429        amount: clnpb.Amount,
430        label: Optional[str] = None,
431        routehints: Optional[clnpb.RoutehintList] = None,
432        extratlvs: Optional[clnpb.TlvStream] = None,
433    ) -> clnpb.KeysendResponse:
434        uri = "/cln.Node/KeySend"
435        res = clnpb.KeysendResponse
436        req = clnpb.KeysendRequest(
437            destination=normalize_node_id(destination, string=False),
438            amount_msat=amount,
439            label=label if label else "",
440            routehints=routehints,
441            extratlvs=extratlvs,
442        ).SerializeToString()
443
444        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def stream_log(self):
446    def stream_log(self):
447        """Stream logs as they get generated on the server side."""
448        stream = self.inner.stream_log(b"")
449        while True:
450            n = stream.next()
451            if n is None:
452                break
453            yield nodepb.LogEntry.FromString(bytes(n))

Stream logs as they get generated on the server side.

def stream_incoming(self):
455    def stream_incoming(self):
456        stream = self.inner.stream_incoming(b"")
457        while True:
458            n = stream.next()
459            if n is None:
460                break
461            yield nodepb.IncomingPayment.FromString(bytes(n))
def stream_custommsg(self):
463    def stream_custommsg(self):
464        stream = self.inner.stream_custommsg(b"")
465        while True:
466            n = stream.next()
467            if n is None:
468                break
469            yield nodepb.Custommsg.FromString(bytes(n))
def send_custommsg(self, node_id: str, msg: bytes) -> node_pb2.SendcustommsgResponse:
471    def send_custommsg(self, node_id: str, msg: bytes) -> clnpb.SendcustommsgResponse:
472        uri = "/cln.Node/SendCustomMsg"
473        res = clnpb.SendcustommsgResponse
474        req = clnpb.SendcustommsgRequest(
475            node_id=normalize_node_id(node_id),
476            msg=msg,
477        ).SerializeToString()
478
479        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def datastore(self, key, string=None, hex=None, mode=None, generation=None):
481    def datastore(self, key, string=None, hex=None, mode=None, generation=None):
482        uri = "/cln.Node/Datastore"
483        req = clnpb.DatastoreRequest(
484            key=key, string=string, hex=hex, mode=mode, generation=generation
485        ).SerializeToString()
486        res = clnpb.DatastoreResponse
487        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def del_datastore(self, key, generation=None):
489    def del_datastore(self, key, generation=None):
490        uri = "/cln.Node/DelDatastore"
491        req = clnpb.DeldatastoreRequest(
492            key=key, generation=generation
493        ).SerializeToString()
494        res = clnpb.DeldatastoreResponse
495        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def list_datastore(self, key=None):
497    def list_datastore(self, key=None):
498        uri = "/cln.Node/ListDatastore"
499        req = clnpb.ListdatastoreRequest(key=key).SerializeToString()
500        res = clnpb.ListdatastoreResponse
501        return res.FromString(bytes(self.inner.call(uri, bytes(req))))
def get_lsp_client(self) -> glclient.lsps.LspClient:
503    def get_lsp_client(
504        self,
505    ) -> LspClient:
506        native_lsps = self.inner.get_lsp_client()
507        return LspClient(native_lsps)
def configure(self, close_to_addr: str) -> None:
509    def configure(self, close_to_addr: str) -> None:
510        req = nodepb.GlConfig(close_to_addr=close_to_addr).SerializeToString()
511
512        return self.inner.configure(req)
def wait_blockheight(self, blockheight: int, timeout: Optional[int] = None):
514    def wait_blockheight(
515        self,
516        blockheight: int,
517        timeout: Optional[int] = None,
518    ):
519        """Wait until the blockchain has reached the specified blockheight."""
520        uri = "/cln.Node/WaitBlockheight"
521        req = clnpb.WaitblockheightRequest(
522            blockheight=blockheight, timeout=timeout
523        ).SerializeToString()
524        res = clnpb.WaitblockheightResponse
525        return res.FromString(bytes(self.inner.call(uri, bytes(req))))

Wait until the blockchain has reached the specified blockheight.

def fetch_invoice( self, offer: str, amount_msat: Optional[primitives_pb2.Amount] = None, quantity: Optional[int] = None, recurrence_counter: Optional[int] = None, recurrence_start: Optional[int] = None, recurrence_label: Optional[str] = None, timeout: Optional[int] = None, payer_note: Optional[str] = None) -> node_pb2.FetchinvoiceResponse:
527    def fetch_invoice(
528        self,
529        offer: str,
530        amount_msat: Optional[Amount] = None,
531        quantity: Optional[int] = None,
532        recurrence_counter: Optional[int] = None,
533        recurrence_start: Optional[int] = None,
534        recurrence_label: Optional[str] = None,
535        timeout: Optional[int] = None,
536        payer_note: Optional[str] = None,
537    ) -> clnpb.FetchinvoiceResponse:
538        """Fetch an invoice based on an offer.
539
540        Contact the issuer of an offer to get an actual invoice that
541        can be paid. It highlights any changes between the offer and
542        the returned invoice.
543
544        """
545        uri = "/cln.Node/FetchInvoice"
546        req = clnpb.FetchinvoiceRequest(
547            offer=offer,
548            amount_msat=amount_msat,
549            quantity=quantity,
550            recurrence_counter=recurrence_counter,
551            recurrence_start=recurrence_start,
552            recurrence_label=recurrence_label,
553            timeout=timeout,
554            payer_note=payer_note,
555        ).SerializeToString()
556        res = clnpb.FetchinvoiceResponse
557        return res.FromString(bytes(self.inner.call(uri, bytes(req))))

Fetch an invoice based on an offer.

Contact the issuer of an offer to get an actual invoice that can be paid. It highlights any changes between the offer and the returned invoice.

def wait(self, subsystem, indexname, nextvalue: int) -> node_pb2.WaitResponse:
559    def wait(self, subsystem, indexname, nextvalue: int) -> clnpb.WaitResponse:
560        """Wait for the next event in the provided subsystem.
561
562        Returns once the index given by indexname in subsystem reaches
563        or exceeds nextvalue.
564
565        """
566        uri = "/cln.Node/Wait"
567        req = clnpb.WaitRequest(
568            subsystem=subsystem,
569            indexname=indexname,
570            nextvalue=nextvalue,
571        ).SerializeToString()
572        res = clnpb.WaitResponse
573        return res.FromString(bytes(self.inner.call(uri, bytes(req))))

Wait for the next event in the provided subsystem.

Returns once the index given by indexname in subsystem reaches or exceeds nextvalue.

def normalize_node_id(node_id, string=False):
576def normalize_node_id(node_id, string=False):
577    if len(node_id) == 66:
578        node_id = unhexlify(node_id)
579
580    if len(node_id) != 33:
581        raise ValueError("node_id is not 33 (binary) or 66 (hex) bytes long")
582
583    if isinstance(node_id, str):
584        node_id = node_id.encode("ASCII")
585    return node_id if not string else hexlify(node_id).encode("ASCII")