diff --git a/app/lightning/exceptions.py b/app/lightning/exceptions.py index 39e71b6..b738527 100644 --- a/app/lightning/exceptions.py +++ b/app/lightning/exceptions.py @@ -1,6 +1,17 @@ from fastapi import HTTPException, status +class OpenChannelPushAmountError(HTTPException): + """Raised when a the push amount is lower than the channel size.""" + + def __init__(self, local_funding, push_amt): + self.status_code = status.HTTP_400_BAD_REQUEST + self.detail = ( + f"Push amount {push_amt} must be lower than " + f"local funding amount {local_funding}" + ) + + class NodeNotFoundError(HTTPException): """Raised when a node is not found in the graph.""" diff --git a/app/lightning/impl/cln_grpc.py b/app/lightning/impl/cln_grpc.py index 21e2ddb..a6a62b1 100644 --- a/app/lightning/impl/cln_grpc.py +++ b/app/lightning/impl/cln_grpc.py @@ -901,12 +901,17 @@ async def peer_resolve_alias(self, node_pub: bytes) -> str: @logger.catch(exclude=(HTTPException,)) async def channel_open( - self, local_funding_amount: int, node_URI: str, target_confs: int + self, + local_funding_amount: int, + node_URI: str, + target_confs: int, + push_amount_sat: int, ) -> str: logger.trace( ( f"channel_open(local_funding_amount={local_funding_amount}, " - f"node_URI={node_URI}, target_confs={target_confs})" + f"node_URI={node_URI}, target_confs={target_confs}, " + f"push_amount_sat={push_amount_sat})" ) ) @@ -924,8 +929,11 @@ async def channel_open( h = bytes.fromhex(node_URI.split("@")[0]) req = ln.FundchannelRequest( id=h, - amount=lnp.AmountOrAll(amount=lnp.Amount(msat=local_funding_amount)), + amount=lnp.AmountOrAll( + amount=lnp.Amount(msat=local_funding_amount * 1000) + ), feerate=fee_rate, + push_msat=lnp.Amount(msat=push_amount_sat * 1000), ) except TypeError as e: logger.error(f"channel_open() failed at ln.FundchannelRequest(): {e}") @@ -979,10 +987,15 @@ async def channel_open( raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=details) @logger.catch(exclude=(HTTPException,)) - async def channel_list(self) -> List[Channel]: - logger.trace("channel_list()") + async def channel_list( + self, + include_closed: bool, + peer_alias_lookup: bool, + ) -> List[Channel]: + logger.trace(f"channel_list({include_closed}, {peer_alias_lookup})") try: + # TODO: switch to ListPeerChannels res = await self._cln_stub.ListFunds(ln.ListfundsRequest()) peer_ids = [c.peer_id for c in res.channels] peer_res = await asyncio.gather( diff --git a/app/lightning/impl/cln_jrpc.py b/app/lightning/impl/cln_jrpc.py index 2f55785..a96fabe 100644 --- a/app/lightning/impl/cln_jrpc.py +++ b/app/lightning/impl/cln_jrpc.py @@ -724,21 +724,35 @@ async def listen_forward_events(self) -> AsyncGenerator[ForwardSuccessEvent, Non @logger.catch(exclude=(HTTPException,)) async def channel_open( - self, local_funding_amount: int, node_URI: str, target_confs: int + self, + local_funding_amount: int, + node_URI: str, + target_confs: int, + push_amount_sat: int, ) -> str: logger.trace( ( - f"channel_open(local_funding_amount={local_funding_amount}, " - f"node_URI={node_URI}, target_confs={target_confs})" + f"logger.channel_open(local_funding_amount={local_funding_amount}, " + f"node_URI={node_URI}, target_confs={target_confs}, " + f"push_amount_sat={push_amount_sat})" ) ) + # https://lightning.readthedocs.io/lightning-fundchannel.7.html # fundchannel id amount [feerate] [announce] [minconf] [utxos] [push_msat] # [close_to] [request_amt] [compact_lease] [reserve] await self.connect_peer(node_URI) fee_rate = calc_fee_rate_str(None, target_confs) pub = node_URI.split("@")[0] - params = {"id": pub, "amount": local_funding_amount, "feerate": fee_rate} + params = { + "id": pub, + "amount": local_funding_amount, + "feerate": fee_rate, + } + + if push_amount_sat is not None and push_amount_sat > 0: + params["push_msat"] = push_amount_sat * 1000 + res = await self._send_request("fundchannel", params) if "error" in res: @@ -821,20 +835,26 @@ async def peer_resolve_alias(self, node_pub: str) -> str: return str(nodes[0]["alias"]) @logger.catch(exclude=(HTTPException,)) - async def channel_list(self) -> List[Channel]: - logger.trace("channel_list()") + async def channel_list( + self, + include_closed: bool, + peer_alias_lookup: bool, + ) -> List[Channel]: + logger.trace(f"channel_list({include_closed}, {peer_alias_lookup})") - res = await self._send_request("listfunds") + res = await self._send_request("listpeerchannels") if "error" in res: self._raise_internal_server_error("listing channels", res) res = res["result"] - peer_ids = [c["peer_id"] for c in res["channels"]] - peers = await asyncio.gather( - *[alias_or_empty(self.peer_resolve_alias, p) for p in peer_ids], - return_exceptions=True, - ) + peers = [""] * len(res["channels"]) + if peer_alias_lookup: + peer_ids = [c["peer_id"] for c in res["channels"]] + peers = await asyncio.gather( + *[alias_or_empty(self.peer_resolve_alias, p) for p in peer_ids], + return_exceptions=True, + ) channels = [] for c, p in zip(res["channels"], peers): diff --git a/app/lightning/impl/ln_base.py b/app/lightning/impl/ln_base.py index 7380494..b7560f9 100644 --- a/app/lightning/impl/ln_base.py +++ b/app/lightning/impl/ln_base.py @@ -116,7 +116,11 @@ async def listen_forward_events(self) -> ForwardSuccessEvent: @abstractmethod async def channel_open( - self, local_funding_amount: int, node_URI: str, target_confs: int + self, + local_funding_amount: int, + node_URI: str, + target_confs: int, + push_amount_sat: int, ) -> str: raise NotImplementedError() @@ -125,7 +129,11 @@ async def peer_resolve_alias(self, node_pub: str) -> str: raise NotImplementedError() @abstractmethod - async def channel_list(self) -> List[Channel]: + async def channel_list( + self, + include_closed: bool, + peer_alias_lookup: bool, + ) -> List[Channel]: raise NotImplementedError() @abstractmethod diff --git a/app/lightning/impl/lnd_grpc.py b/app/lightning/impl/lnd_grpc.py index 0d96eae..38b974d 100644 --- a/app/lightning/impl/lnd_grpc.py +++ b/app/lightning/impl/lnd_grpc.py @@ -16,10 +16,12 @@ import app.lightning.impl.protos.lnd.walletunlocker_pb2 as unlocker import app.lightning.impl.protos.lnd.walletunlocker_pb2_grpc as unlockerrpc from app.api.utils import SSE, broadcast_sse_msg, config_get_hex_str -from app.lightning.exceptions import NodeNotFoundError +from app.lightning.exceptions import NodeNotFoundError, OpenChannelPushAmountError from app.lightning.impl.ln_base import LightningNodeBase from app.lightning.models import ( Channel, + ChannelInitiator, + ChannelState, FeeRevenue, ForwardSuccessEvent, GenericTx, @@ -816,12 +818,17 @@ async def listen_forward_events(self) -> ForwardSuccessEvent: @logger.catch(exclude=(HTTPException,)) async def channel_open( - self, local_funding_amount: int, node_URI: str, target_confs: int + self, + local_funding_amount: int, + node_URI: str, + target_confs: int, + push_amount_sat: int, ) -> str: logger.trace( ( f"logger.channel_open(local_funding_amount={local_funding_amount}, " - f"node_URI={node_URI}, target_confs={target_confs})" + f"node_URI={node_URI}, target_confs={target_confs}, " + f"push_amount_sat={push_amount_sat})" ) ) @@ -851,14 +858,28 @@ async def channel_open( node_pubkey=bytes.fromhex(pubkey), local_funding_amount=local_funding_amount, target_conf=target_confs, + push_sat=push_amount_sat, ) async for response in self._lnd_stub.OpenChannel(r): return str(response.chan_pending.txid.hex()) except grpc.aio._call.AioRpcError as error: - raise HTTPException( - status.HTTP_500_INTERNAL_SERVER_ERROR, detail=error.details() - ) + details = error.details() + if ( + "amount pushed to remote peer for initial state must " + "be below the local funding amount" + ) in details: + raise OpenChannelPushAmountError(local_funding_amount, push_amount_sat) + if "EOF" in details: + raise HTTPException( + status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=( + 'Got "EOF" from LND. Is the peer reachable ' + "and the pubkey correct?" + ), + ) + + raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=details) @logger.catch(exclude=(HTTPException, NodeNotFoundError)) async def peer_resolve_alias(self, node_pub: str) -> str: @@ -880,29 +901,152 @@ async def peer_resolve_alias(self, node_pub: str) -> str: raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=details) @logger.catch(exclude=(HTTPException,)) - async def channel_list(self) -> List[Channel]: - logger.trace("logger.channel_list()") + async def channel_list( + self, + include_closed: bool, + peer_alias_lookup: bool, + ) -> List[Channel]: + logger.trace(f"channel_list({include_closed}, {peer_alias_lookup})") try: - request = ln.ListChannelsRequest() - response = await self._lnd_stub.ListChannels(request) + res = await asyncio.gather( + *[ + self._lnd_stub.PendingChannels(ln.PendingChannelsRequest()), + self._lnd_stub.ListChannels( + ln.ListChannelsRequest(peer_alias_lookup=peer_alias_lookup) + ), + ( + self._lnd_stub.ClosedChannels(ln.ListChannelsRequest()) + if include_closed + else None + ), + ] + ) + + async def _chan_data(c, state: ChannelState): + _channel: Channel = None + if state == ChannelState.NORMAL: + _channel = Channel( + active=c.active, + state=state, + # use channel point as id because thats + # needed for closing the channel with lnd + channel_id=c.channel_point, + peer_publickey=c.remote_pubkey, + balance_local=c.local_balance, + balance_remote=c.remote_balance, + balance_capacity=c.capacity, + initiator=ChannelInitiator.from_lnd_grpc(c.initiator), + ) + elif state == ChannelState.OPENING: + _channel = Channel( + active=False, + state=state, + # use channel point as id because thats + # needed for closing the channel with lnd + channel_id=c.channel.channel_point, + peer_publickey=c.channel.remote_node_pub, + balance_local=c.channel.local_balance, + balance_remote=c.channel.remote_balance, + balance_capacity=c.channel.capacity, + initiator=ChannelInitiator.from_lnd_grpc(c.channel.initiator), + ) + elif state == ChannelState.CLOSING: + closer = ChannelInitiator.UNKNOWN + if "ChanStatusLocalCloseInitiator" in c.channel.chan_status_flags: + closer = ChannelInitiator.LOCAL + elif ( + "ChanStatusRemoteCloseInitiator" in c.channel.chan_status_flags + ): + closer = ChannelInitiator.REMOTE + + _channel = Channel( + active=False, + state=state, + # use channel point as id because thats + # needed for closing the channel with lnd + channel_id=c.channel.channel_point, + peer_publickey=c.channel.remote_node_pub, + balance_local=c.channel.local_balance, + balance_remote=c.channel.remote_balance, + balance_capacity=c.channel.capacity, + initiator=ChannelInitiator.from_lnd_grpc(c.channel.initiator), + closer=closer, + ) + elif state == ChannelState.FORCE_CLOSING: + closer = ChannelInitiator.UNKNOWN + if "ChanStatusLocalCloseInitiator" in c.channel.chan_status_flags: + closer = ChannelInitiator.LOCAL + elif ( + "ChanStatusRemoteCloseInitiator" in c.channel.chan_status_flags + ): + closer = ChannelInitiator.REMOTE + + _channel = Channel( + active=False, + state=state, + # use channel point as id because thats + # needed for closing the channel with lnd + channel_id=c.channel.channel_point, + peer_publickey=c.channel.remote_node_pub, + balance_local=c.recovered_balance, + balance_remote=c.channel.remote_balance, + balance_capacity=c.channel.capacity, + initiator=ChannelInitiator.from_lnd_grpc(c.channel.initiator), + closer=closer, + ) + elif state == ChannelState.CLOSED: + _channel = Channel( + active=False, + state=state, + # use channel point as id because thats + # needed for closing the channel with lnd + channel_id=c.channel_point, + peer_publickey=c.remote_pubkey, + balance_local=c.settled_balance, + balance_remote=c.capacity - c.settled_balance, + balance_capacity=c.capacity, + initiator=ChannelInitiator.from_lnd_grpc(c.open_initiator), + closer=ChannelInitiator.from_lnd_grpc(c.close_initiator), + ) + + if _channel is None: + logger.error("Unhandled channel state {state}") + return None + + # LND returns "unable to lookup peer alias: alias for node + # not found" when peer_alias_lookup is True and node_pub is + # not found. We ignore this and just return an empty string + if ( + peer_alias_lookup + and hasattr(c, "peer_alias") + and "unable to lookup peer alias: alias for node not found" + not in c.peer_alias + ): + _channel.peer_alias = c.peer_alias + elif peer_alias_lookup and not hasattr(c, "peer_alias"): + _channel.peer_alias = await alias_or_empty( + self.peer_resolve_alias, _channel.peer_publickey + ) + + return _channel channels = [] - for channel_grpc in response.channels: - channel = Channel.from_lnd_grpc(channel_grpc) - channel.peer_alias = await alias_or_empty( - self.peer_resolve_alias, channel.peer_publickey - ) - channels.append(channel) - - request = ln.PendingChannelsRequest() - response = await self._lnd_stub.PendingChannels(request) - for channel_grpc in response.pending_open_channels: - channel = Channel.from_lnd_grpc_pending(channel_grpc.channel) - channel.peer_alias = await alias_or_empty( - self.peer_resolve_alias, channel.peer_publickey - ) - channels.append(channel) + for c in res[0].pending_open_channels: + channels.append(await _chan_data(c, ChannelState.OPENING)) + for c in res[0].pending_force_closing_channels: + channels.append(await _chan_data(c, ChannelState.FORCE_CLOSING)) + for c in res[0].waiting_close_channels: + channels.append(await _chan_data(c, ChannelState.CLOSING)) + + for c in res[1].channels: # open + channels.append(await _chan_data(c, ChannelState.NORMAL)) + + if not include_closed: + return channels + + for c in res[2].channels: + channels.append(await _chan_data(c, ChannelState.CLOSED)) return channels @@ -918,7 +1062,10 @@ async def channel_close(self, channel_id: int, force_close: bool) -> str: ) if ":" not in channel_id: - raise ValueError("channel_id must contain : for lnd") + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="channel_id must contain : for lnd", + ) try: funding_txid = channel_id.split(":")[0] @@ -929,7 +1076,7 @@ async def channel_close(self, channel_id: int, force_close: bool) -> str: funding_txid_str=funding_txid, output_index=int(output_index) ), force=force_close, - target_conf=6, + target_conf=None if force_close else 6, ) async for response in self._lnd_stub.CloseChannel(request): return str(response.close_pending.txid.hex()) diff --git a/app/lightning/impl/protos/lnd/gen_protos_python.sh b/app/lightning/impl/protos/lnd/gen_protos_python.sh index e322068..11bf720 100644 --- a/app/lightning/impl/protos/lnd/gen_protos_python.sh +++ b/app/lightning/impl/protos/lnd/gen_protos_python.sh @@ -3,6 +3,8 @@ # clone lnd repo # cd into lnd/lnrpc # git clone https://github.com/googleapis/googleapis.git +# poetry shell +# bash gen_protos_python.sh set -e diff --git a/app/lightning/impl/specializations/cln_grpc_blitz.py b/app/lightning/impl/specializations/cln_grpc_blitz.py index 0c4189f..f0bb53d 100644 --- a/app/lightning/impl/specializations/cln_grpc_blitz.py +++ b/app/lightning/impl/specializations/cln_grpc_blitz.py @@ -179,10 +179,20 @@ async def listen_forward_events(self) -> ForwardSuccessEvent: yield i async def channel_open( - self, local_funding_amount: int, node_URI: str, target_confs: int + self, + local_funding_amount: int, + node_URI: str, + target_confs: int, + push_amount_sat: int, ) -> str: self._check_if_locked() - return await super().channel_open(local_funding_amount, node_URI, target_confs) + + return await super().channel_open( + local_funding_amount, + node_URI, + target_confs, + push_amount_sat, + ) async def peer_resolve_alias(self, node_pub: str) -> str: self._check_if_locked() diff --git a/app/lightning/models.py b/app/lightning/models.py index 4d97412..3c49cce 100644 --- a/app/lightning/models.py +++ b/app/lightning/models.py @@ -422,52 +422,152 @@ def from_cln_json(cls, h) -> "RouteHint": return cls(hop_hints=hop_hints) +class ChannelInitiator(str, Enum): + UNKNOWN = "unknown" + LOCAL = "local" + REMOTE = "remote" + BOTH = "both" + + @classmethod + def from_cln(cls, i: str) -> "ChannelInitiator": + if i == "local": + return cls.LOCAL + if i == "remote": + return cls.REMOTE + + raise NotImplementedError(f"Unknown CLN channel initiator id: {i}") + + @classmethod + def from_lnd_grpc(cls, i: Union[int, bool]) -> "ChannelInitiator": + # LND only returns whether we are initiator + # or not for NORMAL state channels + if i is True: + return cls.LOCAL + if i is False: + return cls.REMOTE + + # LND differentiates further with pending channels + if i == 1: + return cls.LOCAL + if i == 2: + return cls.REMOTE + if i == 3: + return cls.BOTH + + if i > 3: + # leave a notice in the logs + # that we don't know this case yet + logger.warning(f"Unknown channel initiator id: {i}") + + return cls.UNKNOWN + + +class ChannelState(str, Enum): + OPENING = "opening" + NORMAL = "normal" + CLOSING = "closing" + FORCE_CLOSING = "force_closing" + CLOSED = "closed" + ABANDONED = "abandoned" + UNKNOWN = "unknown" + + @classmethod + def from_cln_json(cls, s) -> "ChannelState": + openings = [ + "OPENINGD", + "CHANNELD_AWAITING_LOCKIN", + "DUALOPEND_OPEN_INIT", + "DUALOPEND_AWAITING_LOCKIN", + ] + closings = [ + "CHANNELD_SHUTTING_DOWN", + "CLOSINGD_SIGEXCHANGE", + "CLOSINGD_COMPLETE", + "FUNDING_SPEND_SEEN", + ] + + if s in openings: + return cls.OPENING + if s == "CHANNELD_NORMAL": + return cls.NORMAL + if s in closings: + return cls.CLOSING + if s == "AWAITING_UNILATERAL": + return cls.FORCE_CLOSING + if s == "ONCHAIN": + return cls.CLOSED + + return cls.UNKNOWN + + class Channel(BaseModel): - channel_id: Optional[str] - active: Optional[bool] + channel_id: str = Query( + "", + description="The unique identifier of the channel", + ) - peer_publickey: Optional[str] - peer_alias: Optional[str] + state: ChannelState = Query(..., description="The state of the channel") - balance_local: Optional[int] - balance_remote: Optional[int] - balance_capacity: Optional[int] + active: bool = Query( + ..., + description=( + "Whether the channel is active or not. " + "True if the channel is not closing and the peer is online." + ), + ) - @classmethod - def from_lnd_grpc(cls, c) -> "Channel": - return cls( - active=c.active, - # use channel point as id because thats needed - # for closing the channel with lnd - channel_id=c.channel_point, - peer_publickey=c.remote_pubkey, - peer_alias="n/a", - balance_local=c.local_balance, - balance_remote=c.remote_balance, - balance_capacity=c.capacity, - ) + peer_publickey: str = Query( + ..., + description="The public key of the peer", + ) - @classmethod - def from_lnd_grpc_pending(cls, c) -> "Channel": - return cls( - active=False, - # use channel point as id because thats needed - # for closing the channel with lnd - channel_id=c.channel_point, - peer_publickey=c.remote_node_pub, - peer_alias="n/a", - balance_local=-1, - balance_remote=-1, - balance_capacity=c.capacity, - ) + peer_alias: str = Query( + "", + description="The alias of the peer if available", + ) + + balance_local: int = Query( + 0, + description="This node's current balance in this channel", + ) + + balance_remote: int = Query( + 0, + description="The counterparty's current balance in this channel", + ) + + balance_capacity: int = Query( + 0, + description="The total capacity of the channel", + ) + + dual_funded: Optional[bool] = Query( + None, + description="Whether the channel was dual funded or not", + ) + + initiator: ChannelInitiator = Query( + ChannelInitiator.UNKNOWN, + description="Whether the channel was initiated by us, our peer or both", + ) + + closer: Union[ChannelInitiator, None] = Query( + None, + description=( + "If state is closing, this shows who initiated the close. " + "None, if not in a closing state." + ), + ) @classmethod def from_cln_grpc(cls, c, peer_alias="n/a") -> "Channel": # TODO: get alias and balance of the channel return cls( active=c.connected, + state=ChannelState.UNKNOWN, + # state=ChannelState.from_cln_jrpc(c.state), # use channel point as id because thats needed - # for closing the channel with lnd + # for closing the channel with lnd channel_id=c.short_channel_id, peer_publickey=c.peer_id.hex(), peer_alias=peer_alias, @@ -477,17 +577,25 @@ def from_cln_grpc(cls, c, peer_alias="n/a") -> "Channel": ) @classmethod - def from_cln_jrpc(cls, c, peer_alias="n/a") -> "Channel": - # TODO: get alias and balance of the channel + def from_cln_jrpc(cls, c, peer_alias="") -> "Channel": + state = ChannelState.from_cln_json(c["state"]) return cls( - active=c["connected"], - channel_id=c["short_channel_id"] if "short_channel_id" in c else None, + active=c["peer_connected"], + state=state, + channel_id=c["short_channel_id"] if "short_channel_id" in c else "", peer_publickey=c["peer_id"], peer_alias=peer_alias, - balance_local=parse_cln_msat(c["our_amount_msat"]), - balance_remote=parse_cln_msat(c["amount_msat"]) - - parse_cln_msat(c["our_amount_msat"]), - balance_capacity=parse_cln_msat(c["amount_msat"]), + balance_local=parse_cln_msat(c["to_us_msat"]) if "to_us_msat" in c else 0, + balance_remote=( + parse_cln_msat(c["total_msat"]) - parse_cln_msat(c["to_us_msat"]) + if "total_msat" in c + else 0 + ), + balance_capacity=( + parse_cln_msat(c["total_msat"]) if "total_msat" in c else 0 + ), + initiator=ChannelInitiator.from_cln(c["opener"]), + closer=ChannelInitiator.from_cln(c["closer"]) if "closer" in c else None, ) diff --git a/app/lightning/router.py b/app/lightning/router.py index b00c8fa..5200862 100644 --- a/app/lightning/router.py +++ b/app/lightning/router.py @@ -360,10 +360,32 @@ async def send_coins_path(input: SendCoinsInput): }, ) async def open_channel_path( - local_funding_amount: int, node_URI: str, target_confs: int = 3 + local_funding_amount: int = Query( + ..., description="The amount of satoshis to commit to the channel." + ), + node_URI: str = Query( + ..., + description=( + "The URI of the peer to open a channel with. " + "Format: @:" + ), + ), + target_confs: int = Query( + 3, description="The block target for the funding transaction." + ), + push_amount_sat: int = Query( + # Note: LND only supports satoshis as push amount, so we do the same. + None, + description="The amount of sats to push to the peer.", + ), ): try: - return await channel_open(local_funding_amount, node_URI, target_confs) + return await channel_open( + local_funding_amount, + node_URI, + target_confs, + push_amount_sat, + ) except HTTPException: raise except NotImplementedError as r: @@ -375,15 +397,27 @@ async def open_channel_path( @router.get( "/list-channels", name=f"{_PREFIX}.list-channels", - summary="Returns a list of open channels", + summary="Returns a list of all channels", response_model=List[Channel], - response_description="A list of all open channels.", + response_description="A list of all channels.", dependencies=[Depends(JWTBearer())], responses=responses, ) -async def list_channels_path(): +async def list_channels_path( + include_closed: bool = Query( + True, description="If true, then include closed channels." + ), + peer_alias_lookup: bool = Query( + False, + description=( + "If true, then include the peer alias of the channel. " + "⚠️ Enabling this flag does come with a performance cost " + "in the form of another roundtrip to the node." + ), + ), +) -> List[Channel]: try: - return await channel_list() + return await channel_list(include_closed, peer_alias_lookup) except HTTPException: raise except NotImplementedError as r: @@ -396,7 +430,10 @@ async def list_channels_path(): "/close-channel", name=f"{_PREFIX}.close-channel", summary="close a channel", - description="For additional information see [LND docs](https://api.lightning.community/#closechannel)", + description=( + "For additional information see " + "[LND docs](https://api.lightning.community/#closechannel)" + ), dependencies=[Depends(JWTBearer())], response_model=str, responses=responses, diff --git a/app/lightning/service.py b/app/lightning/service.py index b861c43..440fc92 100644 --- a/app/lightning/service.py +++ b/app/lightning/service.py @@ -143,7 +143,7 @@ async def send_payment( async def channel_open( - local_funding_amount: int, node_URI: str, target_confs: int + local_funding_amount: int, node_URI: str, target_confs: int, push_amount_sat: int ) -> str: if local_funding_amount < 1: raise ValueError("funding amount needs to be positive") @@ -157,12 +157,17 @@ async def channel_open( if "@" not in node_URI: raise ValueError("node_URI must contain @ with node physical address") - res = await ln.channel_open(local_funding_amount, node_URI, target_confs) + res = await ln.channel_open( + local_funding_amount, node_URI, target_confs, push_amount_sat + ) return res -async def channel_list() -> List[Channel]: - res = await ln.channel_list() +async def channel_list( + include_closed: bool, + peer_alias_lookup: bool, +) -> List[Channel]: + res = await ln.channel_list(include_closed, peer_alias_lookup) return res