feat: implement send all onchain funds flag

refs #139
This commit is contained in:
fusion44 2022-11-28 19:07:25 +01:00
parent b9f4577d21
commit 86442dde89
No known key found for this signature in database
4 changed files with 86 additions and 15 deletions

View file

@ -99,6 +99,17 @@ If neither __target_conf__, or __sat_per_vbyte__ are set, then the internal wall
* If __sat_per_vbyte__ is set then __target_conf__ is ignored and vbytes (sipabytes) will be used.
> 👉 See [https://lightning.readthedocs.io/lightning-txprepare.7.html](https://lightning.readthedocs.io/lightning-txprepare.7.html)
### Sending all onchain funds
> Keep the following points in mind when sending all onchain funds:
* If __send_all__ is set to __true__, the __amount__ field must be set to __0__.
* If the __amount__ field is greater than __0__, the __send_all__ field must be __false__.
* The API will return an error if neither or both conditions are met at the same time.
* If __send_all__ is set to __true__ the amount of satoshis to send will be calculated by subtracting the fee from the wallet balance.
* If the wallet balance is not sufficient to cover the fee, the call will fail.
* The call will __not__ close any channels.
* The implementation may keep a reserve of funds if there are still open channels.
"""
send_payment_desc = """

View file

@ -16,6 +16,7 @@ import app.lightning.impl.protos.cln.node_pb2_grpc as clnrpc
import app.lightning.impl.protos.cln.primitives_pb2 as lnp
from app.api.utils import config_get_hex_str, next_push_id
from app.bitcoind.utils import bitcoin_rpc_async
from app.api.utils import SSE, broadcast_sse_msg
from app.lightning.impl.ln_base import LightningNodeBase
from app.lightning.models import (
Channel,
@ -352,7 +353,7 @@ class LnNodeCLNgRPC(LightningNodeBase):
# TODO: Improve this once CLN reports the block height in bkpr-listincome
# see https://github.com/ElementsProject/lightning/issues/5694
# now get the block height for each tx ...
res = await _make_local_call("bkpr-listaccountevents")
if not res:
@ -567,7 +568,7 @@ class LnNodeCLNgRPC(LightningNodeBase):
utxos.append(lnp.Outpoint(txid=o.txid, outnum=o.output))
max_amt += o.amount_msat.msat
if max_amt <= input.amount:
if not input.send_all and max_amt <= input.amount:
raise HTTPException(
status.HTTP_412_PRECONDITION_FAILED,
detail=f"Could not afford {input.amount}sat. Not enough funds available",
@ -576,14 +577,17 @@ class LnNodeCLNgRPC(LightningNodeBase):
req = ln.WithdrawRequest(
destination=input.address,
satoshi=lnp.AmountOrAll(
amount=lnp.Amount(msat=input.amount), all=False
amount=lnp.Amount(msat=input.amount),
all=input.send_all,
),
minconf=input.min_confs,
feerate=fee_rate,
utxos=utxos,
)
res = await self._cln_stub.Withdraw(req)
return SendCoinsResponse.from_cln_grpc(res, input)
response = await self._cln_stub.Withdraw(req)
r = SendCoinsResponse.from_cln_grpc(response, input)
await broadcast_sse_msg(SSE.LN_ONCHAIN_PAYMENT_STATUS, r.dict())
return r
except grpc.aio._call.AioRpcError as error:
details = error.details()
if details and details.find("Could not parse destination address") > -1:

View file

@ -8,6 +8,7 @@ from decouple import config as dconfig
from fastapi.exceptions import HTTPException
from starlette import status
import app.bitcoind.service as btc
import app.lightning.impl.protos.lnd.lightning_pb2 as ln
import app.lightning.impl.protos.lnd.lightning_pb2_grpc as lnrpc
import app.lightning.impl.protos.lnd.router_pb2 as router
@ -503,14 +504,26 @@ This will show more debug information.
sat_per_vbyte=input.sat_per_vbyte,
min_confs=input.min_confs,
label=input.label,
send_all=input.send_all,
)
response = await self._lnd_stub.SendCoins(r)
r = SendCoinsResponse.from_lnd_grpc(response, input)
bi = await btc.get_blockchain_info()
sendResponse = await self._lnd_stub.SendCoins(r)
txResponse = await self._lnd_stub.GetTransactions(
ln.GetTransactionsRequest(start_height=-1, end_height=bi.blocks)
)
tx = None
for t in txResponse.transactions:
if t.tx_hash == sendResponse.txid:
tx = t
break
r = SendCoinsResponse.from_lnd_grpc(tx, input)
await broadcast_sse_msg(SSE.LN_ONCHAIN_PAYMENT_STATUS, r.dict())
return r
except grpc.aio._call.AioRpcError as error:
_check_if_locked()
_check_if_locked(error)
details = error.details()
if details and details.find("invalid bech32 string") > -1:
raise HTTPException(

View file

@ -4,7 +4,7 @@ from typing import List, Optional, Union
from deepdiff import DeepDiff
from fastapi.param_functions import Query
from pydantic import BaseModel
from pydantic import BaseModel, validator
from pydantic.types import conint
import app.lightning.docs as docs
@ -1136,10 +1136,6 @@ class SendCoinsInput(BaseModel):
...,
description="The base58 or bech32 encoded bitcoin address to send coins to on-chain",
)
amount: conint(gt=0) = Query(
...,
description="The number of bitcoin denominated in satoshis to send",
)
target_conf: int = Query(
None,
description="The number of blocks that the transaction *should* confirm in, will be used for fee estimation",
@ -1155,6 +1151,47 @@ class SendCoinsInput(BaseModel):
label: str = Query(
"", description="A label for the transaction. Ignored by CLN backend."
)
send_all: bool = Query(
False,
description="Send all available on-chain funds from the wallet. Will be executed `amount` is **0**",
)
amount: conint(ge=0) = Query(
0,
description="The number of bitcoin denominated in satoshis to send. Must not be set when `send_all` is true.",
)
@validator("amount", pre=True, always=True)
def check_amount_or_send_all(cls, amount, values):
if amount == None:
amount = 0
send_all = values.get("send_all") if "send_all" in values else False
if amount < 0:
raise ValueError("Amount must not be negative")
if amount == 0 and not send_all:
# neither amount nor send_all is set
raise ValueError(
"Either amount or send_all must be set. Please review the documentation."
)
if amount > 0 and not send_all:
# amount is set and send_all is false
return amount
if amount > 0 and send_all:
# amount is set and send_all is true
raise ValueError(
"Amount and send_all must not be set at the same time. Please review the documentation."
)
if amount == 0 and send_all:
# amount is not set and send_all is true
return amount
# normally this should never be reached
raise ValueError(f"Unknown input.")
class SendCoinsResponse(BaseModel):
@ -1167,16 +1204,22 @@ class SendCoinsResponse(BaseModel):
...,
description="The number of bitcoin denominated in satoshis which where sent",
)
fees: conint(ge=0) = Query(
None,
description="The number of bitcoin denominated in satoshis which where paid as fees",
)
label: str = Query(
"", description="The label used for the transaction. Ignored by CLN backend."
)
@classmethod
def from_lnd_grpc(cls, r, input: SendCoinsInput):
amount = input.amount if input.send_all == False else r.amount
return cls(
txid=r.txid,
txid=r.tx_hash,
address=input.address,
amount=input.amount,
amount=abs(amount),
fees=r.total_fees,
label=input.label,
)