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