1import json
2import logging
3import os
4import socket
5import warnings
6from contextlib import contextmanager
7from decimal import Decimal
8from json import JSONEncoder
9from math import floor, log10
10from typing import Optional, Union
11
12
13def _patched_default(self, obj):
14    return getattr(obj.__class__, "to_json", _patched_default.default)(obj)
15
16
17def monkey_patch_json(patch=True):
18    is_patched = JSONEncoder.default == _patched_default
19
20    if patch and not is_patched:
21        _patched_default.default = JSONEncoder.default  # Save unmodified
22        JSONEncoder.default = _patched_default  # Replace it.
23    elif not patch and is_patched:
24        JSONEncoder.default = _patched_default.default
25
26
27class RpcError(ValueError):
28    def __init__(self, method: str, payload: dict, error: str):
29        super(ValueError, self).__init__(
30            "RPC call failed: method: {}, payload: {}, error: {}".format(
31                method, payload, error
32            )
33        )
34
35        self.method = method
36        self.payload = payload
37        self.error = error
38
39
40class Millisatoshi:
41    """
42    A subtype to represent thousandths of a satoshi.
43
44    Many JSON API fields are expressed in millisatoshis: these automatically
45    get turned into Millisatoshi types. Converts to and from int.
46    """
47    def __init__(self, v: Union[int, str, Decimal]):
48        """
49        Takes either a string ending in 'msat', 'sat', 'btc' or an integer.
50        """
51        if isinstance(v, str):
52            if v.endswith("msat"):
53                parsed = Decimal(v[0:-4])
54            elif v.endswith("sat"):
55                parsed = Decimal(v[0:-3]) * 1000
56            elif v.endswith("btc"):
57                parsed = Decimal(v[0:-3]) * 1000 * 10**8
58            else:
59                raise TypeError(
60                    "Millisatoshi must be string with msat/sat/btc suffix or"
61                    " int"
62                )
63            if parsed != int(parsed):
64                raise ValueError("Millisatoshi must be a whole number")
65            self.millisatoshis = int(parsed)
66
67        elif isinstance(v, Millisatoshi):
68            self.millisatoshis = v.millisatoshis
69
70        elif int(v) == v:
71            self.millisatoshis = int(v)
72
73        elif isinstance(v, float):
74            raise TypeError("Millisatoshi by float is currently not supported")
75
76        else:
77            raise TypeError(
78                "Millisatoshi must be string with msat/sat/btc suffix or int"
79            )
80
81        if self.millisatoshis < 0:
82            raise ValueError("Millisatoshi must be >= 0")
83
84    def __repr__(self) -> str:
85        """
86        Appends the 'msat' as expected for this type.
87        """
88        return str(self.millisatoshis) + "msat"
89
90    def to_satoshi(self) -> Decimal:
91        """
92        Return a Decimal representing the number of satoshis.
93        """
94        return Decimal(self.millisatoshis) / 1000
95
96    def to_whole_satoshi(self) -> int:
97        """
98        Return an int respresenting the number of satoshis;
99        rounded up to the nearest satoshi
100        """
101        return (self.millisatoshis + 999) // 1000
102
103    def to_btc(self) -> Decimal:
104        """
105        Return a Decimal representing the number of bitcoin.
106        """
107        return Decimal(self.millisatoshis) / 1000 / 10**8
108
109    def to_satoshi_str(self) -> str:
110        """
111        Return a string of form 1234sat or 1234.567sat.
112        """
113        if self.millisatoshis % 1000:
114            return '{:.3f}sat'.format(self.to_satoshi())
115        else:
116            return '{:.0f}sat'.format(self.to_satoshi())
117
118    def to_btc_str(self) -> str:
119        """
120        Return a string of form 12.34567890btc or 12.34567890123btc.
121        """
122        if self.millisatoshis % 1000:
123            return '{:.11f}btc'.format(self.to_btc())
124        else:
125            return '{:.8f}btc'.format(self.to_btc())
126
127    def to_approx_str(self, digits: int = 3) -> str:
128        """Returns the shortmost string using common units representation.
129
130        Rounds to significant `digits`. Default: 3
131        """
132        def round_to_n(x: int, n: int) -> float:
133            return round(x, -int(floor(log10(x))) + (n - 1))
134        result = self.to_satoshi_str()
135
136        # we try to increase digits to check if we did loose out on precision
137        # without gaining a shorter string, since this is a rarely used UI
138        # function, performance is not an issue. Adds at least one iteration.
139        while True:
140            # first round everything down to effective digits
141            amount_rounded = round_to_n(self.millisatoshis, digits)
142            # try different units and take shortest resulting normalized string
143            amounts_str = [
144                "%gbtc" % (amount_rounded / 1000 / 10**8),
145                "%gsat" % (amount_rounded / 1000),
146                "%gmsat" % (amount_rounded),
147            ]
148            test_result = min(amounts_str, key=len)
149
150            # check result and do another run if necessary
151            if test_result == result:
152                return result
153            elif not result or len(test_result) <= len(result):
154                digits = digits + 1
155                result = test_result
156            else:
157                return result
158
159    def to_json(self) -> str:
160        return self.__repr__()
161
162    def __int__(self) -> int:
163        return self.millisatoshis
164
165    def __lt__(self, other: 'Millisatoshi') -> bool:
166        return self.millisatoshis < other.millisatoshis
167
168    def __le__(self, other: 'Millisatoshi') -> bool:
169        return self.millisatoshis <= other.millisatoshis
170
171    def __eq__(self, other: object) -> bool:
172        if isinstance(other, Millisatoshi):
173            return self.millisatoshis == other.millisatoshis
174        elif isinstance(other, int):
175            return self.millisatoshis == other
176        else:
177            return False
178
179    def __gt__(self, other: 'Millisatoshi') -> bool:
180        return self.millisatoshis > other.millisatoshis
181
182    def __ge__(self, other: 'Millisatoshi') -> bool:
183        return self.millisatoshis >= other.millisatoshis
184
185    def __add__(self, other: 'Millisatoshi') -> 'Millisatoshi':
186        return Millisatoshi(int(self) + int(other))
187
188    def __sub__(self, other: 'Millisatoshi') -> 'Millisatoshi':
189        return Millisatoshi(int(self) - int(other))
190
191    def __mul__(self, other: Union[int, float]) -> 'Millisatoshi':
192        if isinstance(other, Millisatoshi):
193            raise TypeError("Resulting unit msat^2 is not supported")
194        return Millisatoshi(floor(self.millisatoshis * other))
195
196    def __truediv__(self, other: Union[int, float, 'Millisatoshi']) -> Union['Millisatoshi', float]:
197        if isinstance(other, Millisatoshi):
198            return self.millisatoshis / other.millisatoshis
199        return Millisatoshi(floor(self.millisatoshis / other))
200
201    def __floordiv__(self, other: Union[int, float, 'Millisatoshi']) -> Union['Millisatoshi', int]:
202        if isinstance(other, Millisatoshi):
203            return self.millisatoshis // other.millisatoshis
204        return Millisatoshi(floor(self.millisatoshis // float(other)))
205
206    def __mod__(self, other: Union[float, int]) -> 'Millisatoshi':
207        return Millisatoshi(int(self.millisatoshis % other))
208
209    def __radd__(self, other: 'Millisatoshi') -> 'Millisatoshi':
210        return Millisatoshi(int(self) + int(other))
211
212
213class UnixSocket(object):
214    """A wrapper for socket.socket that is specialized to unix sockets.
215
216    Some OS implementations impose restrictions on the Unix sockets.
217
218     - On linux OSs the socket path must be shorter than the in-kernel buffer
219       size (somewhere around 100 bytes), thus long paths may end up failing
220       the `socket.connect` call.
221
222    This is a small wrapper that tries to work around these limitations.
223
224    """
225
226    def __init__(self, path: str):
227        self.path = path
228        self.sock: Optional[socket.SocketType] = None
229        self.connect()
230
231    def connect(self) -> None:
232        try:
233            self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
234            self.sock.connect(self.path)
235        except OSError as e:
236            self.close()
237
238            if (e.args[0] == "AF_UNIX path too long" and os.uname()[0] == "Linux"):
239                # If this is a Linux system we may be able to work around this
240                # issue by opening our directory and using `/proc/self/fd/` to
241                # get a short alias for the socket file.
242                #
243                # This was heavily inspired by the Open vSwitch code see here:
244                # https://github.com/openvswitch/ovs/blob/master/python/ovs/socket_util.py
245
246                dirname = os.path.dirname(self.path)
247                basename = os.path.basename(self.path)
248
249                # Open an fd to our home directory, that we can then find
250                # through `/proc/self/fd` and access the contents.
251                dirfd = os.open(dirname, os.O_DIRECTORY | os.O_RDONLY)
252                short_path = "/proc/self/fd/%d/%s" % (dirfd, basename)
253                self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
254                self.sock.connect(short_path)
255            else:
256                # There is no good way to recover from this.
257                raise
258
259    def close(self) -> None:
260        if self.sock is not None:
261            self.sock.close()
262        self.sock = None
263
264    def sendall(self, b: bytes) -> None:
265        if self.sock is None:
266            raise socket.error("not connected")
267
268        self.sock.sendall(b)
269
270    def recv(self, length: int) -> bytes:
271        if self.sock is None:
272            raise socket.error("not connected")
273
274        return self.sock.recv(length)
275
276    def __del__(self) -> None:
277        self.close()
278
279
280class UnixDomainSocketRpc(object):
281    def __init__(self, socket_path, executor=None, logger=logging, encoder_cls=json.JSONEncoder, decoder=json.JSONDecoder()):
282        self.socket_path = socket_path
283        self.encoder_cls = encoder_cls
284        self.decoder = decoder
285        self.executor = executor
286        self.logger = logger
287        self._notify = None
288
289        self.next_id = 1
290
291    def _writeobj(self, sock, obj):
292        s = json.dumps(obj, ensure_ascii=False, cls=self.encoder_cls)
293        sock.sendall(bytearray(s, 'UTF-8'))
294
295    def _readobj(self, sock, buff=b''):
296        """Read a JSON object, starting with buff; returns object and any buffer left over."""
297        while True:
298            parts = buff.split(b'\n\n', 1)
299            if len(parts) == 1:
300                # Didn't read enough.
301                b = sock.recv(max(1024, len(buff)))
302                buff += b
303                if len(b) == 0:
304                    return {'error': 'Connection to RPC server lost.'}, buff
305            else:
306                buff = parts[1]
307                obj, _ = self.decoder.raw_decode(parts[0].decode("UTF-8"))
308                return obj, buff
309
310    def __getattr__(self, name):
311        """Intercept any call that is not explicitly defined and call @call.
312
313        We might still want to define the actual methods in the subclasses for
314        documentation purposes.
315        """
316        name = name.replace('_', '-')
317
318        def wrapper(*args, **kwargs):
319            if len(args) != 0 and len(kwargs) != 0:
320                raise RpcError("Cannot mix positional and non-positional arguments")
321            elif len(args) != 0:
322                return self.call(name, payload=args)
323            else:
324                return self.call(name, payload=kwargs)
325        return wrapper
326
327    def call(self, method, payload=None):
328        self.logger.debug("Calling %s with payload %r", method, payload)
329
330        if payload is None:
331            payload = {}
332        # Filter out arguments that are None
333        if isinstance(payload, dict):
334            payload = {k: v for k, v in payload.items() if v is not None}
335
336        # FIXME: we open a new socket for every readobj call...
337        sock = UnixSocket(self.socket_path)
338        this_id = self.next_id
339        self.next_id += 0
340        buf = b''
341
342        if self._notify is not None:
343            # Opt into the notifications support
344            self._writeobj(sock, {
345                "jsonrpc": "2.0",
346                "method": "notifications",
347                "id": 0,
348                "params": {
349                    "enable": True
350                },
351            })
352            # FIXME: Notification schema support?
353            _, buf = self._readobj(sock, buf)
354
355        request = {
356            "jsonrpc": "2.0",
357            "method": method,
358            "params": payload,
359            "id": this_id,
360        }
361
362        self._writeobj(sock, request)
363        while True:
364            resp, buf = self._readobj(sock, buf)
365            id = resp.get("id", None)
366            meth = resp.get("method", None)
367
368            if meth == 'message' and self._notify is not None:
369                n = resp['params']
370                self._notify(
371                    message=n.get('message', None),
372                    progress=n.get('progress', None),
373                    request=request
374                )
375                continue
376
377            if meth is None or id is None:
378                break
379
380        self.logger.debug("Received response for %s call: %r", method, resp)
381        if 'id' in resp and resp['id'] != this_id:
382            raise ValueError("Malformed response, id is not {}: {}.".format(this_id, resp))
383        sock.close()
384
385        if not isinstance(resp, dict):
386            raise ValueError("Malformed response, response is not a dictionary %s." % resp)
387        elif "error" in resp:
388            raise RpcError(method, payload, resp['error'])
389        elif "result" not in resp:
390            raise ValueError("Malformed response, \"result\" missing.")
391        return resp["result"]
392
393    @contextmanager
394    def notify(self, fn):
395        """Register a notification callback to use for a set of RPC calls.
396
397        This is a context manager and should be used like this:
398
399        ```python
400        def fn(message, progress, request, **kwargs):
401            print(message)
402
403        with rpc.notify(fn):
404            rpc.somemethod()
405        ```
406
407        The `fn` function will be called once for each notification
408        the is sent by `somemethod`. This is a context manager,
409        meaning that multiple commands can share the same context, and
410        the same notification function.
411
412        """
413        old = self._notify
414        self._notify = fn
415        yield
416        self._notify = old
417
418
419class LightningRpc(UnixDomainSocketRpc):
420    """
421    RPC client for the `lightningd` daemon.
422
423    This RPC client connects to the `lightningd` daemon through a unix
424    domain socket and passes calls through. Since some of the calls
425    are blocking, the corresponding python methods include an `async`
426    keyword argument. If `async` is set to true then the method
427    returns a future immediately, instead of blocking indefinitely.
428
429    This implementation is thread safe in that it locks the socket
430    between calls, but it does not (yet) support concurrent calls.
431    """
432
433    class LightningJSONEncoder(json.JSONEncoder):
434        def default(self, o):
435            try:
436                return o.to_json()
437            except NameError:
438                pass
439            return json.JSONEncoder.default(self, o)
440
441    class LightningJSONDecoder(json.JSONDecoder):
442        def __init__(self, *, object_hook=None, parse_float=None,
443                     parse_int=None, parse_constant=None,
444                     strict=True, object_pairs_hook=None,
445                     patch_json=True):
446            self.object_hook_next = object_hook
447            super().__init__(object_hook=self.millisatoshi_hook, parse_float=parse_float, parse_int=parse_int, parse_constant=parse_constant, strict=strict, object_pairs_hook=object_pairs_hook)
448
449        @staticmethod
450        def replace_amounts(obj):
451            """
452            Recursively replace _msat fields with appropriate values with Millisatoshi.
453            """
454            if isinstance(obj, dict):
455                for k, v in obj.items():
456                    if k.endswith('msat'):
457                        if isinstance(v, str) and v.endswith('msat'):
458                            obj[k] = Millisatoshi(v)
459                        # Special case for array of msat values
460                        elif isinstance(v, list) and all(isinstance(e, str) and e.endswith('msat') for e in v):
461                            obj[k] = [Millisatoshi(e) for e in v]
462                    else:
463                        obj[k] = LightningRpc.LightningJSONDecoder.replace_amounts(v)
464            elif isinstance(obj, list):
465                obj = [LightningRpc.LightningJSONDecoder.replace_amounts(e) for e in obj]
466
467            return obj
468
469        def millisatoshi_hook(self, obj):
470            obj = LightningRpc.LightningJSONDecoder.replace_amounts(obj)
471            if self.object_hook_next:
472                obj = self.object_hook_next(obj)
473            return obj
474
475    def __init__(self, socket_path, executor=None, logger=logging,
476                 patch_json=True):
477        super().__init__(
478            socket_path,
479            executor,
480            logger,
481            self.LightningJSONEncoder,
482            self.LightningJSONDecoder()
483        )
484
485        if patch_json:
486            monkey_patch_json(patch=True)
487
488    def addgossip(self, message):
489        """
490        Inject this (hex-encoded) gossip message.
491        """
492        payload = {
493            "message": message,
494        }
495        return self.call("addgossip", payload)
496
497    def autocleaninvoice(self, cycle_seconds=None, expired_by=None):
498        """
499        Sets up automatic cleaning of expired invoices. {cycle_seconds} sets
500        the cleaning frequency in seconds (defaults to 3600) and {expired_by}
501        sets the minimum time an invoice should have been expired for to be
502        cleaned in seconds (defaults to 86400).
503        """
504        payload = {
505            "cycle_seconds": cycle_seconds,
506            "expired_by": expired_by
507        }
508        return self.call("autocleaninvoice", payload)
509
510    def check(self, command_to_check, **kwargs):
511        """
512        Checks if a command is valid without running it.
513        """
514        payload = {"command_to_check": command_to_check}
515        payload.update({k: v for k, v in kwargs.items()})
516        return self.call("check", payload)
517
518    def close(self, peer_id, unilateraltimeout=None, destination=None,
519              fee_negotiation_step=None, force_lease_closed=None, feerange=None):
520        """
521        Close the channel with peer {id}, forcing a unilateral
522        close after {unilateraltimeout} seconds if non-zero, and
523        the to-local output will be sent to {destination}.
524
525        If channel funds have been leased to the peer and the
526        lease has not yet expired, you can force a close with
527        {force_lease_closed}. Note that your funds will still be
528        locked until the lease expires.
529        """
530        payload = {
531            "id": peer_id,
532            "unilateraltimeout": unilateraltimeout,
533            "destination": destination,
534            "fee_negotiation_step": fee_negotiation_step,
535            "force_lease_closed": force_lease_closed,
536            "feerange": feerange,
537        }
538        return self.call("close", payload)
539
540    def connect(self, peer_id, host=None, port=None):
541        """
542        Connect to {peer_id} at {host} and {port}.
543        """
544        payload = {
545            "id": peer_id,
546            "host": host,
547            "port": port
548        }
549        return self.call("connect", payload)
550
551    def decodepay(self, bolt11, description=None):
552        """
553        Decode {bolt11}, using {description} if necessary.
554        """
555        payload = {
556            "bolt11": bolt11,
557            "description": description
558        }
559        return self.call("decodepay", payload)
560
561    def delexpiredinvoice(self, maxexpirytime=None):
562        """
563        Delete all invoices that have expired on or before the given {maxexpirytime}.
564        """
565        payload = {
566            "maxexpirytime": maxexpirytime
567        }
568        return self.call("delexpiredinvoice", payload)
569
570    def delinvoice(self, label, status):
571        """
572        Delete unpaid invoice {label} with {status}.
573        """
574        payload = {
575            "label": label,
576            "status": status
577        }
578        return self.call("delinvoice", payload)
579
580    def dev_crash(self):
581        """
582        Crash lightningd by calling fatal().
583        """
584        payload = {
585            "subcommand": "crash"
586        }
587        return self.call("dev", payload)
588
589    def dev_fail(self, peer_id):
590        """
591        Fail with peer {peer_id}.
592        """
593        payload = {
594            "id": peer_id
595        }
596        return self.call("dev-fail", payload)
597
598    def dev_forget_channel(self, peerid, force=False):
599        """ Forget the channel with id=peerid.
600        """
601        return self.call(
602            "dev-forget-channel",
603            payload={"id": peerid, "force": force}
604        )
605
606    def dev_memdump(self):
607        """
608        Show memory objects currently in use.
609        """
610        return self.call("dev-memdump")
611
612    def dev_memleak(self):
613        """
614        Show unreferenced memory objects.
615        """
616        return self.call("dev-memleak")
617
618    def dev_pay(self, bolt11, msatoshi=None, label=None, riskfactor=None,
619                maxfeepercent=None, retry_for=None,
620                maxdelay=None, exemptfee=None, use_shadow=True):
621        """
622        A developer version of `pay`, with the possibility to deactivate
623        shadow routing (used for testing).
624        """
625        payload = {
626            "bolt11": bolt11,
627            "msatoshi": msatoshi,
628            "label": label,
629            "riskfactor": riskfactor,
630            "maxfeepercent": maxfeepercent,
631            "retry_for": retry_for,
632            "maxdelay": maxdelay,
633            "exemptfee": exemptfee,
634            "use_shadow": use_shadow,
635        }
636        return self.call("pay", payload)
637
638    def dev_reenable_commit(self, peer_id):
639        """
640        Re-enable the commit timer on peer {id}.
641        """
642        payload = {
643            "id": peer_id
644        }
645        return self.call("dev-reenable-commit", payload)
646
647    def dev_rescan_outputs(self):
648        """
649        Synchronize the state of our funds with bitcoind.
650        """
651        return self.call("dev-rescan-outputs")
652
653    def dev_rhash(self, secret):
654        """
655        Show SHA256 of {secret}
656        """
657        payload = {
658            "subcommand": "rhash",
659            "secret": secret
660        }
661        return self.call("dev", payload)
662
663    def dev_sign_last_tx(self, peer_id):
664        """
665        Sign and show the last commitment transaction with peer {id}.
666        """
667        payload = {
668            "id": peer_id
669        }
670        return self.call("dev-sign-last-tx", payload)
671
672    def dev_slowcmd(self, msec=None):
673        """
674        Torture test for slow commands, optional {msec}.
675        """
676        payload = {
677            "subcommand": "slowcmd",
678            "msec": msec
679        }
680        return self.call("dev", payload)
681
682    def disconnect(self, peer_id, force=False):
683        """
684        Disconnect from peer with {peer_id}, optional {force} even if has active channel.
685        """
686        payload = {
687            "id": peer_id,
688            "force": force,
689        }
690        return self.call("disconnect", payload)
691
692    def feerates(self, style, urgent=None, normal=None, slow=None):
693        """
694        Supply feerate estimates manually.
695        """
696        payload = {
697            "style": style,
698            "urgent": urgent,
699            "normal": normal,
700            "slow": slow
701        }
702        return self.call("feerates", payload)
703
704    def fundchannel(self, node_id, amount, feerate=None, announce=True, minconf=None, utxos=None, push_msat=None, close_to=None, request_amt=None, compact_lease=None):
705        """
706        Fund channel with {id} using {amount} satoshis with feerate
707        of {feerate} (uses default feerate if unset).
708        If {announce} is False, don't send channel announcements.
709        Only select outputs with {minconf} confirmations.
710        If {utxos} is specified (as a list of 'txid:vout' strings),
711        fund a channel from these specifics utxos.
712        {close_to} is a valid Bitcoin address.
713
714        {request_amt} is the lease amount to request from the peer. Only
715        valid if peer is advertising a liquidity ad + supports v2 channel opens
716        (dual-funding)
717        """
718        payload = {
719            "id": node_id,
720            "amount": amount,
721            "feerate": feerate,
722            "announce": announce,
723            "minconf": minconf,
724            "utxos": utxos,
725            "push_msat": push_msat,
726            "close_to": close_to,
727            "request_amt": request_amt,
728            "compact_lease": compact_lease,
729        }
730        return self.call("fundchannel", payload)
731
732    def fundchannel_start(self, node_id, amount, feerate=None, announce=True, close_to=None):
733        """
734        Start channel funding with {id} for {amount} satoshis
735        with feerate of {feerate} (uses default feerate if unset).
736        If {announce} is False, don't send channel announcements.
737        Returns a Bech32 {funding_address} for an external wallet
738        to create a funding transaction for. Requires a call to
739        'fundchannel_complete' to complete channel establishment
740        with peer.
741        """
742        payload = {
743            "id": node_id,
744            "amount": amount,
745            "feerate": feerate,
746            "announce": announce,
747            "close_to": close_to,
748        }
749        return self.call("fundchannel_start", payload)
750
751    def fundchannel_cancel(self, node_id):
752        """
753        Cancel a 'started' fundchannel with node {id}.
754        """
755        payload = {
756            "id": node_id,
757        }
758        return self.call("fundchannel_cancel", payload)
759
760    def _deprecated_fundchannel_complete(self, node_id, funding_txid, funding_txout):
761        warnings.warn("fundchannel_complete: funding_txid & funding_txout replaced by psbt: expect removal"
762                      " in Mid-2021",
763                      DeprecationWarning)
764
765        payload = {
766            "id": node_id,
767            "txid": funding_txid,
768            "txout": funding_txout,
769        }
770        return self.call("fundchannel_complete", payload)
771
772    def fundchannel_complete(self, node_id, *args, **kwargs):
773        """
774        Complete channel establishment with {id}, using {psbt}.
775        """
776        if 'txid' in kwargs or len(args) == 2:
777            return self._deprecated_fundchannel_complete(node_id, *args, **kwargs)
778
779        def _fundchannel_complete(node_id, psbt):
780            payload = {
781                "id": node_id,
782                "psbt": psbt,
783            }
784            return self.call("fundchannel_complete", payload)
785
786        return _fundchannel_complete(node_id, *args, **kwargs)
787
788    def getinfo(self):
789        """
790        Show information about this node.
791        """
792        return self.call("getinfo")
793
794    def getlog(self, level=None):
795        """
796        Show logs, with optional log {level} (info|unusual|debug|io).
797        """
798        payload = {
799            "level": level
800        }
801        return self.call("getlog", payload)
802
803    def getpeer(self, peer_id, level=None):
804        """
805        Show peer with {peer_id}, if {level} is set, include {log}s.
806        """
807        payload = {
808            "id": peer_id,
809            "level": level
810        }
811        res = self.call("listpeers", payload)
812        return res.get("peers") and res["peers"][0] or None
813
814    def getroute(self, node_id, msatoshi, riskfactor, cltv=9, fromid=None, fuzzpercent=None, exclude=[], maxhops=20):
815        """
816        Show route to {id} for {msatoshi}, using {riskfactor} and optional
817        {cltv} (default 9). If specified search from {fromid} otherwise use
818        this node as source. Randomize the route with up to {fuzzpercent}
819        (0.0 -> 100.0, default 5.0). {exclude} is an optional array of
820        scid/direction or node-id to exclude. Limit the number of hops in the
821        route to {maxhops}.
822        """
823        payload = {
824            "id": node_id,
825            "msatoshi": msatoshi,
826            "riskfactor": riskfactor,
827            "cltv": cltv,
828            "fromid": fromid,
829            "fuzzpercent": fuzzpercent,
830            "exclude": exclude,
831            "maxhops": maxhops
832        }
833        return self.call("getroute", payload)
834
835    def help(self, command=None):
836        """
837        Show available commands, or just {command} if supplied.
838        """
839        payload = {
840            "command": command,
841        }
842        return self.call("help", payload)
843
844    def invoice(self, msatoshi, label, description, expiry=None, fallbacks=None, preimage=None, exposeprivatechannels=None, cltv=None):
845        """
846        Create an invoice for {msatoshi} with {label} and {description} with
847        optional {expiry} seconds (default 1 week).
848        """
849        payload = {
850            "msatoshi": msatoshi,
851            "label": label,
852            "description": description,
853            "expiry": expiry,
854            "fallbacks": fallbacks,
855            "preimage": preimage,
856            "exposeprivatechannels": exposeprivatechannels,
857            "cltv": cltv,
858        }
859        return self.call("invoice", payload)
860
861    def listchannels(self, short_channel_id=None, source=None, destination=None):
862        """
863        Show all known channels or filter by optional
864        {short_channel_id}, {source} or {destination}.
865        """
866        payload = {
867            "short_channel_id": short_channel_id,
868            "source": source,
869            "destination": destination
870        }
871        return self.call("listchannels", payload)
872
873    def listconfigs(self, config=None):
874        """List this node's config.
875        """
876        payload = {
877            "config": config
878        }
879        return self.call("listconfigs", payload)
880
881    def listforwards(self, status=None, in_channel=None, out_channel=None):
882        """List all forwarded payments and their information matching
883        forward {status}, {in_channel} and {out_channel}.
884        """
885        payload = {
886            "status": status,
887            "in_channel": in_channel,
888            "out_channel": out_channel,
889        }
890        return self.call("listforwards", payload)
891
892    def listfunds(self, spent=None):
893        """
894        Show funds available for opening channels
895        or both unspent and spent funds if {spent} is True.
896        """
897
898        payload = {
899            "spent": spent
900        }
901        return self.call("listfunds", payload)
902
903    def listtransactions(self):
904        """
905        Show wallet history.
906        """
907        return self.call("listtransactions")
908
909    def listinvoices(self, label=None, payment_hash=None, invstring=None, offer_id=None):
910        """Query invoices
911
912        Show invoice matching {label}, {payment_hash}, {invstring} or {offer_id}
913        (or all, if no filters are present).
914
915        """
916        payload = {
917            "label": label,
918            "payment_hash": payment_hash,
919            "invstring": invstring,
920            "offer_id": offer_id,
921        }
922        return self.call("listinvoices", payload)
923
924    def listnodes(self, node_id=None):
925        """
926        Show all nodes in our local network view, filter on node {id}
927        if provided.
928        """
929        payload = {
930            "id": node_id
931        }
932        return self.call("listnodes", payload)
933
934    def listpays(self, bolt11=None, payment_hash=None, status=None):
935        """
936        Show outgoing payments, regarding {bolt11} or {payment_hash} if set
937        Can only specify one of {bolt11} or {payment_hash}. It is possible
938        filter the payments by {status}.
939        """
940        assert not (bolt11 and payment_hash)
941        payload = {
942            "bolt11": bolt11,
943            "payment_hash": payment_hash,
944            "status": status
945        }
946        return self.call("listpays", payload)
947
948    def listpeers(self, peerid=None, level=None):
949        """
950        Show current peers, if {level} is set, include {log}s".
951        """
952        payload = {
953            "id": peerid,
954            "level": level,
955        }
956        return self.call("listpeers", payload)
957
958    def listsendpays(self, bolt11=None, payment_hash=None, status=None):
959        """Show all sendpays results, or only for `bolt11` or `payment_hash`."""
960        payload = {
961            "bolt11": bolt11,
962            "payment_hash": payment_hash,
963            "status": status
964        }
965        return self.call("listsendpays", payload)
966
967    def multifundchannel(self, destinations, feerate=None, minconf=None, utxos=None, minchannels=None, **kwargs):
968        """
969        Fund channels to an array of {destinations},
970        each entry of which is a dict of node {id}
971        and {amount} to fund, and optionally whether
972        to {announce} and how much {push_msat} to
973        give outright to the node.
974        You may optionally specify {feerate},
975        {minconf} depth, and the {utxos} set to use
976        for the single transaction that funds all
977        the channels.
978        """
979        payload = {
980            "destinations": destinations,
981            "feerate": feerate,
982            "minconf": minconf,
983            "utxos": utxos,
984            "minchannels": minchannels,
985        }
986        payload.update({k: v for k, v in kwargs.items()})
987        return self.call("multifundchannel", payload)
988
989    def multiwithdraw(self, outputs, feerate=None, minconf=None, utxos=None, **kwargs):
990        """
991        Send to {outputs}
992        via Bitcoin transaction. Only select outputs
993        with {minconf} confirmations.
994        """
995        payload = {
996            "outputs": outputs,
997            "feerate": feerate,
998            "minconf": minconf,
999            "utxos": utxos,
1000        }
1001        payload.update({k: v for k, v in kwargs.items()})
1002        return self.call("multiwithdraw", payload)
1003
1004    def newaddr(self, addresstype=None):
1005        """Get a new address of type {addresstype} of the internal wallet.
1006        """
1007        return self.call("newaddr", {"addresstype": addresstype})
1008
1009    def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None,
1010            maxfeepercent=None, retry_for=None,
1011            maxdelay=None, exemptfee=None):
1012        """
1013        Send payment specified by {bolt11} with {msatoshi}
1014        (ignored if {bolt11} has an amount), optional {label}
1015        and {riskfactor} (default 1.0).
1016        """
1017        payload = {
1018            "bolt11": bolt11,
1019            "msatoshi": msatoshi,
1020            "label": label,
1021            "riskfactor": riskfactor,
1022            "maxfeepercent": maxfeepercent,
1023            "retry_for": retry_for,
1024            "maxdelay": maxdelay,
1025            "exemptfee": exemptfee,
1026        }
1027        return self.call("pay", payload)
1028
1029    def openchannel_init(self, node_id, channel_amount, psbt, feerate=None, funding_feerate=None, announce=True, close_to=None, request_amt=None, *args, **kwargs):
1030        """Initiate an openchannel with a peer """
1031        payload = {
1032            "id": node_id,
1033            "amount": channel_amount,
1034            "initialpsbt": psbt,
1035            "commitment_feerate": feerate,
1036            "funding_feerate": funding_feerate,
1037            "announce": announce,
1038            "close_to": close_to,
1039            "request_amt": request_amt,
1040        }
1041        return self.call("openchannel_init", payload)
1042
1043    def openchannel_signed(self, channel_id, signed_psbt, *args, **kwargs):
1044        """ Send the funding transaction signatures to the peer, finish
1045            the channel open """
1046        payload = {
1047            "channel_id": channel_id,
1048            "signed_psbt": signed_psbt,
1049        }
1050        return self.call("openchannel_signed", payload)
1051
1052    def openchannel_update(self, channel_id, psbt, *args, **kwargs):
1053        """Update an openchannel with a peer """
1054        payload = {
1055            "channel_id": channel_id,
1056            "psbt": psbt,
1057        }
1058        return self.call("openchannel_update", payload)
1059
1060    def openchannel_bump(self, channel_id, amount, initialpsbt, funding_feerate=None):
1061        """ Initiate an RBF for an in-progress open """
1062        payload = {
1063            "channel_id": channel_id,
1064            "amount": amount,
1065            "initialpsbt": initialpsbt,
1066            "funding_feerate": funding_feerate,
1067        }
1068        return self.call("openchannel_bump", payload)
1069
1070    def openchannel_abort(self, channel_id):
1071        """ Abort a channel open """
1072        payload = {
1073            "channel_id": channel_id,
1074        }
1075        return self.call("openchannel_abort", payload)
1076
1077    def paystatus(self, bolt11=None):
1078        """Detail status of attempts to pay {bolt11} or any."""
1079        payload = {
1080            "bolt11": bolt11
1081        }
1082        return self.call("paystatus", payload)
1083
1084    def ping(self, peer_id, length=128, pongbytes=128):
1085        """
1086        Send {peer_id} a ping of length {len} asking for {pongbytes}.
1087        """
1088        payload = {
1089            "id": peer_id,
1090            "len": length,
1091            "pongbytes": pongbytes
1092        }
1093        return self.call("ping", payload)
1094
1095    def plugin_start(self, plugin, **kwargs):
1096        """
1097        Adds a plugin to lightningd.
1098        """
1099        payload = {
1100            "subcommand": "start",
1101            "plugin": plugin,
1102        }
1103        payload.update({k: v for k, v in kwargs.items()})
1104        return self.call("plugin", payload)
1105
1106    def plugin_startdir(self, directory):
1107        """
1108        Adds all plugins from a directory to lightningd.
1109        """
1110        payload = {
1111            "subcommand": "startdir",
1112            "directory": directory
1113        }
1114        return self.call("plugin", payload)
1115
1116    def plugin_stop(self, plugin):
1117        """
1118        Stops a lightningd plugin, will fail if plugin is not dynamic.
1119        """
1120        payload = {
1121            "subcommand": "stop",
1122            "plugin": plugin
1123        }
1124        return self.call("plugin", payload)
1125
1126    def plugin_list(self):
1127        """
1128        Lists all plugins lightningd knows about.
1129        """
1130        payload = {
1131            "subcommand": "list"
1132        }
1133        return self.call("plugin", payload)
1134
1135    def plugin_rescan(self):
1136        payload = {
1137            "subcommand": "rescan"
1138        }
1139        return self.call("plugin", payload)
1140
1141    def sendpay(self, route, payment_hash, label=None, msatoshi=None, bolt11=None, payment_secret=None, partid=None, groupid=None):
1142        """
1143        Send along {route} in return for preimage of {payment_hash}.
1144        """
1145        payload = {
1146            "route": route,
1147            "payment_hash": payment_hash,
1148            "label": label,
1149            "msatoshi": msatoshi,
1150            "bolt11": bolt11,
1151            "payment_secret": payment_secret,
1152            "partid": partid,
1153            "groupid": groupid,
1154        }
1155        return self.call("sendpay", payload)
1156
1157    def sendonion(
1158            self, onion, first_hop, payment_hash, label=None,
1159            shared_secrets=None, partid=None, bolt11=None, msatoshi=None,
1160            destination=None
1161    ):
1162        """Send an outgoing payment using the specified onion.
1163
1164        This method allows sending a payment using an externally
1165        generated routing onion, with optional metadata to facilitate
1166        internal handling, but not required.
1167
1168        """
1169        payload = {
1170            "onion": onion,
1171            "first_hop": first_hop,
1172            "payment_hash": payment_hash,
1173            "label": label,
1174            "shared_secrets": shared_secrets,
1175            "partid": partid,
1176            "bolt11": bolt11,
1177            "msatoshi": msatoshi,
1178            "destination": destination,
1179        }
1180        return self.call("sendonion", payload)
1181
1182    def setchannelfee(self, id, base=None, ppm=None, enforcedelay=None):
1183        """
1184        Set routing fees for a channel/peer {id} (or 'all'). {base} is a value in millisatoshi
1185        that is added as base fee to any routed payment. {ppm} is a value added proportionally
1186        per-millionths to any routed payment volume in satoshi. {enforcedelay} is the number of seconds before enforcing this change.
1187        """
1188        payload = {
1189            "id": id,
1190            "base": base,
1191            "ppm": ppm,
1192            "enforcedelay": enforcedelay,
1193        }
1194        return self.call("setchannelfee", payload)
1195
1196    def stop(self):
1197        """
1198        Shut down the lightningd process.
1199        """
1200        return self.call("stop")
1201
1202    def waitanyinvoice(self, lastpay_index=None, timeout=None, **kwargs):
1203        """
1204        Wait for the next invoice to be paid, after {lastpay_index}
1205        (if supplied).
1206        Fail after {timeout} seconds has passed without an invoice
1207        being paid.
1208        """
1209        payload = {
1210            "lastpay_index": lastpay_index,
1211            "timeout": timeout
1212        }
1213        payload.update({k: v for k, v in kwargs.items()})
1214        return self.call("waitanyinvoice", payload)
1215
1216    def waitblockheight(self, blockheight, timeout=None):
1217        """
1218        Wait for the blockchain to reach the specified block height.
1219        """
1220        payload = {
1221            "blockheight": blockheight,
1222            "timeout": timeout
1223        }
1224        return self.call("waitblockheight", payload)
1225
1226    def waitinvoice(self, label):
1227        """
1228        Wait for an incoming payment matching the invoice with {label}.
1229        """
1230        payload = {
1231            "label": label
1232        }
1233        return self.call("waitinvoice", payload)
1234
1235    def waitsendpay(self, payment_hash, timeout=None, partid=None, groupid=None):
1236        """
1237        Wait for payment for preimage of {payment_hash} to complete.
1238        """
1239        payload = {
1240            "payment_hash": payment_hash,
1241            "timeout": timeout,
1242            "partid": partid,
1243            "groupid": groupid,
1244        }
1245        return self.call("waitsendpay", payload)
1246
1247    def withdraw(self, destination, satoshi, feerate=None, minconf=None, utxos=None):
1248        """
1249        Send to {destination} address {satoshi} (or "all")
1250        amount via Bitcoin transaction. Only select outputs
1251        with {minconf} confirmations.
1252        """
1253        payload = {
1254            "destination": destination,
1255            "satoshi": satoshi,
1256            "feerate": feerate,
1257            "minconf": minconf,
1258            "utxos": utxos,
1259        }
1260
1261        return self.call("withdraw", payload)
1262
1263    def txprepare(self, outputs, feerate=None, minconf=None, utxos=None):
1264        """
1265        Prepare a Bitcoin transaction which sends to [outputs].
1266        The format of output is like [{address1: amount1},
1267        {address2: amount2}], or [{address: "all"}]).
1268        Only select outputs with {minconf} confirmations.
1269
1270        Outputs will be reserved until you call txdiscard or txsend, or
1271        lightningd restarts.
1272        """
1273        payload = {
1274            "outputs": outputs,
1275            "feerate": feerate,
1276            "minconf": minconf,
1277            "utxos": utxos,
1278        }
1279        return self.call("txprepare", payload)
1280
1281    def txdiscard(self, txid):
1282        """
1283        Cancel a Bitcoin transaction returned from txprepare. The outputs
1284        it was spending are released for other use.
1285        """
1286        payload = {
1287            "txid": txid
1288        }
1289        return self.call("txdiscard", payload)
1290
1291    def txsend(self, txid):
1292        """
1293        Sign and broadcast a Bitcoin transaction returned from txprepare.
1294        """
1295        payload = {
1296            "txid": txid
1297        }
1298        return self.call("txsend", payload)
1299
1300    def reserveinputs(self, psbt, exclusive=True, reserve=None):
1301        """
1302        Reserve any inputs in this psbt.
1303        """
1304        payload = {
1305            "psbt": psbt,
1306            "exclusive": exclusive,
1307            "reserve": reserve,
1308        }
1309        return self.call("reserveinputs", payload)
1310
1311    def unreserveinputs(self, psbt, reserve=None):
1312        """
1313        Unreserve (or reduce reservation) on any UTXOs in this psbt were previously reserved.
1314        """
1315        payload = {
1316            "psbt": psbt,
1317            "reserve": reserve,
1318        }
1319        return self.call("unreserveinputs", payload)
1320
1321    def fundpsbt(self, satoshi, feerate, startweight, minconf=None, reserve=True, locktime=None, min_witness_weight=None, excess_as_change=False):
1322        """
1323        Create a PSBT with inputs sufficient to give an output of satoshi.
1324        """
1325        payload = {
1326            "satoshi": satoshi,
1327            "feerate": feerate,
1328            "startweight": startweight,
1329            "minconf": minconf,
1330            "reserve": reserve,
1331            "locktime": locktime,
1332            "min_witness_weight": min_witness_weight,
1333            "excess_as_change": excess_as_change,
1334        }
1335        return self.call("fundpsbt", payload)
1336
1337    def utxopsbt(self, satoshi, feerate, startweight, utxos, reserve=True, reservedok=False, locktime=None, min_witness_weight=None, excess_as_change=False):
1338        """
1339        Create a PSBT with given inputs, to give an output of satoshi.
1340        """
1341        payload = {
1342            "satoshi": satoshi,
1343            "feerate": feerate,
1344            "startweight": startweight,
1345            "utxos": utxos,
1346            "reserve": reserve,
1347            "reservedok": reservedok,
1348            "locktime": locktime,
1349            "min_witness_weight": min_witness_weight,
1350            "excess_as_change": excess_as_change,
1351        }
1352        return self.call("utxopsbt", payload)
1353
1354    def signpsbt(self, psbt, signonly=None):
1355        """
1356        Add internal wallet's signatures to PSBT
1357        """
1358        payload = {
1359            "psbt": psbt,
1360            "signonly": signonly,
1361        }
1362        return self.call("signpsbt", payload)
1363
1364    def sendpsbt(self, psbt, reserve=None):
1365        """
1366        Finalize extract and broadcast a PSBT
1367        """
1368        payload = {
1369            "psbt": psbt,
1370            "reserve": reserve,
1371        }
1372        return self.call("sendpsbt", payload)
1373
1374    def signmessage(self, message):
1375        """
1376        Sign a message with this node's secret key.
1377        """
1378        payload = {
1379            "message": message
1380        }
1381        return self.call("signmessage", payload)
1382
1383    def checkmessage(self, message, zbase, pubkey=None):
1384        """
1385        Check if a message was signed (with a specific key).
1386        Use returned field ['verified'] to get result.
1387        """
1388        payload = {
1389            "message": message,
1390            "zbase": zbase,
1391            "pubkey": pubkey,
1392        }
1393        return self.call("checkmessage", payload)
1394
1395    def getsharedsecret(self, point, **kwargs):
1396        """
1397        Compute the hash of the Elliptic Curve Diffie Hellman shared
1398        secret point from this node private key and an
1399        input {point}.
1400        """
1401        payload = {
1402            "point": point
1403        }
1404        payload.update({k: v for k, v in kwargs.items()})
1405        return self.call("getsharedsecret", payload)
1406
1407    def keysend(self, destination, msatoshi, label=None, maxfeepercent=None,
1408                retry_for=None, maxdelay=None, exemptfee=None,
1409                extratlvs=None):
1410        """
1411        """
1412
1413        if extratlvs is not None and not isinstance(extratlvs, dict):
1414            raise ValueError(
1415                "extratlvs is not a dictionary with integer keys and hexadecimal values"
1416            )
1417
1418        payload = {
1419            "destination": destination,
1420            "msatoshi": msatoshi,
1421            "label": label,
1422            "maxfeepercent": maxfeepercent,
1423            "retry_for": retry_for,
1424            "maxdelay": maxdelay,
1425            "exemptfee": exemptfee,
1426            "extratlvs": extratlvs,
1427        }
1428        return self.call("keysend", payload)
1429