Skip to content

Commit

Permalink
loopout: can set exact amount
Browse files Browse the repository at this point in the history
This was proposed here: lightninglabs#234 (comment)
  • Loading branch information
ratpoison4 committed Nov 26, 2020
1 parent 6a44f9d commit f8f6e46
Show file tree
Hide file tree
Showing 14 changed files with 654 additions and 179 deletions.
14 changes: 12 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,14 +458,24 @@ func (s *Client) LoopOutQuote(ctx context.Context,
return nil, err
}

minerFee, err := s.sweeper.GetSweepFee(
var changeAddr btcutil.Address
if request.WithChange {
changeAddr = p2wshAddress
}

minerFeeOnlyDest, _, minerFeeBoth, err := s.sweeper.GetSweepFee(
ctx, swap.QuoteHtlc.AddSuccessToEstimator,
p2wshAddress, request.SweepConfTarget,
p2wshAddress, changeAddr, request.SweepConfTarget,
)
if err != nil {
return nil, err
}

minerFee := minerFeeOnlyDest
if request.WithChange {
minerFee = minerFeeBoth
}

return &LoopOutQuote{
SwapFee: swapFee,
MinerFee: minerFee,
Expand Down
26 changes: 26 additions & 0 deletions cmd/loop/loopout.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ var loopOutCommand = cli.Command{
Name: "amt",
Usage: "the amount in satoshis to loop out",
},
cli.Uint64Flag{
Name: "dest_amt",
Usage: "the optional exact amount in satoshis to send to " +
"'addr'; useful to pay to merchants",
},
cli.StringFlag{
Name: "change_addr",
Usage: "the optional address where to send change in case " +
"'dest_amt' is specified; if left empty and 'dest_amt' " +
"is provided, the funds will go to lnd's wallet",
},
cli.Uint64Flag{
Name: "htlc_confs",
Usage: "the number of of confirmations, in blocks " +
Expand Down Expand Up @@ -128,6 +139,19 @@ func loopOut(ctx *cli.Context) error {
destAddr = args.First()
}

var destAmount btcutil.Amount
if ctx.IsSet("dest_amt") {
destAmount, err = parseAmt(ctx.String("dest_amt"))
if err != nil {
return err
}
}

var changeAddr string
if ctx.IsSet("change_addr") {
changeAddr = ctx.String("changeAddr")
}

client, cleanup, err := getClient(ctx)
if err != nil {
return err
Expand Down Expand Up @@ -185,6 +209,8 @@ func loopOut(ctx *cli.Context) error {
resp, err := client.LoopOut(context.Background(), &looprpc.LoopOutRequest{
Amt: int64(amt),
Dest: destAddr,
DestAmount: int64(destAmount),
ChangeAddr: changeAddr,
MaxMinerFee: int64(limits.maxMinerFee),
MaxPrepayAmt: int64(limits.maxPrepayAmt),
MaxSwapFee: int64(limits.maxSwapFee),
Expand Down
6 changes: 6 additions & 0 deletions cmd/loop/quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ var quoteOutCommand = cli.Command{
ArgsUsage: "amt",
Description: "Allows to determine the cost of a swap up front",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "with_change",
Usage: "Indicate you want to add change output; " +
"this is needed when making exact amount payment",
},
cli.Uint64Flag{
Name: "conf_target",
Usage: "the number of blocks from the swap " +
Expand Down Expand Up @@ -124,6 +129,7 @@ func quoteOut(ctx *cli.Context) error {
ctxb := context.Background()
quoteReq := &looprpc.QuoteRequest{
Amt: int64(amt),
WithChange: ctx.Bool("with_change"),
ConfTarget: int32(ctx.Uint64("conf_target")),
SwapPublicationDeadline: uint64(swapDeadline.Unix()),
}
Expand Down
16 changes: 16 additions & 0 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ type OutRequest struct {
// Destination address for the swap.
DestAddr btcutil.Address

// DestAmount specifies the exact amount to send to DestAddr. If this
// field is enabled, ChangeAddr must also be specified. If it fails to
// send exactly DestAmount to DestAddr due to fees increase, it sends
// everything to ChangeAddr. Otherwise only the change goes to ChangeAddr.
// This is useful when a merchant asks you to pay exactly X to address.
DestAmount btcutil.Amount

// Where to send change in case DestAmount is specified. See description
// of DestAmount.
ChangeAddr btcutil.Address

// MaxSwapRoutingFee is the maximum off-chain fee in msat that may be
// paid for payment to the server. This limit is applied during path
// finding. Typically this value is taken from the response of the
Expand Down Expand Up @@ -111,6 +122,11 @@ type LoopOutQuoteRequest struct {
// include the swap and miner fee.
Amount btcutil.Amount

// WithChange specifies if change output is added to sweep tx.
// This is useful when a merchant asks you to pay exactly X to address.
// See the description of OutRequest.DestAmount.
WithChange bool

// SweepConfTarget specifies the targeted confirmation target for the
// client sweep tx.
SweepConfTarget int32
Expand Down
21 changes: 21 additions & 0 deletions loopd/swapclient_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
}
}

var changeAddr btcutil.Address
if in.ChangeAddr != "" {
var err error
changeAddr, err = btcutil.DecodeAddress(
in.ChangeAddr, s.lnd.ChainParams,
)
if err != nil {
return nil, fmt.Errorf("decode change address: %v", err)
}
} else if in.DestAmount != 0 {
// Generate change address.
var err error
changeAddr, err = s.lnd.WalletKit.NextAddr(context.Background())
if err != nil {
return nil, fmt.Errorf("NextAddr error: %v", err)
}
}

// Check that the label is valid.
if err := labels.Validate(in.Label); err != nil {
return nil, err
Expand All @@ -89,6 +107,8 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
req := &loop.OutRequest{
Amount: btcutil.Amount(in.Amt),
DestAddr: sweepAddr,
DestAmount: btcutil.Amount(in.DestAmount),
ChangeAddr: changeAddr,
MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
MaxPrepayAmount: btcutil.Amount(in.MaxPrepayAmt),
MaxPrepayRoutingFee: btcutil.Amount(in.MaxPrepayRoutingFee),
Expand Down Expand Up @@ -410,6 +430,7 @@ func (s *swapClientServer) LoopOutQuote(ctx context.Context,
}
quote, err := s.impl.LoopOutQuote(ctx, &loop.LoopOutQuoteRequest{
Amount: btcutil.Amount(req.Amt),
WithChange: req.WithChange,
SweepConfTarget: confTarget,
SwapPublicationDeadline: time.Unix(
int64(req.SwapPublicationDeadline), 0,
Expand Down
8 changes: 8 additions & 0 deletions loopdb/loopout.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ type LoopOutContract struct {
// DestAddr is the destination address of the loop out swap.
DestAddr btcutil.Address

// DestAmount specifies the exact amount to send to DestAddr. Optional.
// Used together with ChangeAddr.
DestAmount btcutil.Amount

// Where to send change in case DestAmount is specified. Optional.
// Used together with DestAmount.
ChangeAddr btcutil.Address

// SwapInvoice is the invoice that is to be paid by the client to
// initiate the loop out swap.
SwapInvoice string
Expand Down
6 changes: 3 additions & 3 deletions loopin.go
Original file line number Diff line number Diff line change
Expand Up @@ -849,8 +849,8 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
}

// Calculate sweep tx fee
fee, err := s.sweeper.GetSweepFee(
ctx, s.htlc.AddTimeoutToEstimator, s.timeoutAddr,
fee, _, _, err := s.sweeper.GetSweepFee(
ctx, s.htlc.AddTimeoutToEstimator, s.timeoutAddr, nil,
TimeoutTxConfTarget,
)
if err != nil {
Expand All @@ -864,7 +864,7 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
sequence := uint32(0)
timeoutTx, err := s.sweeper.CreateSweepTx(
ctx, s.height, sequence, s.htlc, *htlcOutpoint, s.SenderKey,
witnessFunc, htlcValue, fee, s.timeoutAddr,
witnessFunc, htlcValue, 0, fee, 0, 0, s.timeoutAddr, nil,
)
if err != nil {
return err
Expand Down
29 changes: 23 additions & 6 deletions loopout.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
contract := loopdb.LoopOutContract{
SwapInvoice: swapResp.swapInvoice,
DestAddr: request.DestAddr,
DestAmount: request.DestAmount,
ChangeAddr: request.ChangeAddr,
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
SweepConfTarget: request.SweepConfTarget,
HtlcConfirmations: confs,
Expand Down Expand Up @@ -910,23 +912,27 @@ func (s *loopOutSwap) sweep(ctx context.Context,
confTarget > DefaultSweepConfTarget {
confTarget = DefaultSweepConfTarget
}
fee, err := s.sweeper.GetSweepFee(
ctx, s.htlc.AddSuccessToEstimator, s.DestAddr, confTarget,

feeOnlyDest, feeOnlyChange, feeBoth, err := s.sweeper.GetSweepFee(
ctx, s.htlc.AddSuccessToEstimator, s.DestAddr, s.ChangeAddr, confTarget,
)
if err != nil {
return err
}

// Ensure it doesn't exceed our maximum fee allowed.
if fee > s.MaxMinerFee {
for _, fee := range []*btcutil.Amount{&feeOnlyDest, &feeOnlyChange, &feeBoth} {
if *fee == 0 || *fee <= s.MaxMinerFee {
continue
}
s.log.Warnf("Required fee %v exceeds max miner fee of %v",
fee, s.MaxMinerFee)
*fee, s.MaxMinerFee)

if s.state == loopdb.StatePreimageRevealed {
// The currently required fee exceeds the max, but we
// already revealed the preimage. The best we can do now
// is to republish with the max fee.
fee = s.MaxMinerFee
*fee = s.MaxMinerFee
} else {
s.log.Warnf("Not revealing preimage")
return nil
Expand All @@ -936,7 +942,9 @@ func (s *loopOutSwap) sweep(ctx context.Context,
// Create sweep tx.
sweepTx, err := s.sweeper.CreateSweepTx(
ctx, s.height, s.htlc.SuccessSequence(), s.htlc, htlcOutpoint,
s.ReceiverKey, witnessFunc, htlcValue, fee, s.DestAddr,
s.ReceiverKey, witnessFunc, htlcValue, s.DestAmount,
feeOnlyDest, feeOnlyChange, feeBoth,
s.DestAddr, s.ChangeAddr,
)
if err != nil {
return err
Expand All @@ -955,6 +963,11 @@ func (s *loopOutSwap) sweep(ctx context.Context,
}

// Publish tx.
var sumOutputs int64
for _, txout := range sweepTx.TxOut {
sumOutputs += txout.Value
}
fee := htlcValue - btcutil.Amount(sumOutputs)
s.log.Infof("Sweep on chain HTLC to address %v with fee %v (tx %v)",
s.DestAddr, fee, sweepTx.TxHash())

Expand All @@ -975,6 +988,10 @@ func validateLoopOutContract(lnd *lndclient.LndServices,
height int32, request *OutRequest, swapHash lntypes.Hash,
response *newLoopOutResponse) error {

if (request.DestAmount != 0) != (request.ChangeAddr != nil) {
return fmt.Errorf("provide either both DestAmount and ChangeAddr or none of them")
}

// Check invoice amounts.
chainParams := lnd.ChainParams

Expand Down
Loading

0 comments on commit f8f6e46

Please sign in to comment.