loopout: can set exact amount

This was proposed here: https://github.com/lightninglabs/loop/issues/234#issuecomment-644417843
pull/313/head
Rat Poison 4 years ago
parent 6a44f9d7a6
commit 3eeb1214db

@ -458,14 +458,24 @@ func (s *Client) LoopOutQuote(ctx context.Context,
return nil, err 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, ctx, swap.QuoteHtlc.AddSuccessToEstimator,
p2wshAddress, request.SweepConfTarget, p2wshAddress, changeAddr, request.SweepConfTarget,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
minerFee := minerFeeOnlyDest
if request.WithChange {
minerFee = minerFeeBoth
}
return &LoopOutQuote{ return &LoopOutQuote{
SwapFee: swapFee, SwapFee: swapFee,
MinerFee: minerFee, MinerFee: minerFee,

@ -43,6 +43,18 @@ var loopOutCommand = cli.Command{
Name: "amt", Name: "amt",
Usage: "the amount in satoshis to loop out", 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; note that " +
"amt >= max_miner_fee + dest_amt",
},
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{ cli.Uint64Flag{
Name: "htlc_confs", Name: "htlc_confs",
Usage: "the number of of confirmations, in blocks " + Usage: "the number of of confirmations, in blocks " +
@ -128,6 +140,19 @@ func loopOut(ctx *cli.Context) error {
destAddr = args.First() 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("change_addr")
}
client, cleanup, err := getClient(ctx) client, cleanup, err := getClient(ctx)
if err != nil { if err != nil {
return err return err
@ -152,6 +177,7 @@ func loopOut(ctx *cli.Context) error {
Amt: int64(amt), Amt: int64(amt),
ConfTarget: sweepConfTarget, ConfTarget: sweepConfTarget,
SwapPublicationDeadline: uint64(swapDeadline.Unix()), SwapPublicationDeadline: uint64(swapDeadline.Unix()),
WithChange: changeAddr != "",
} }
quote, err := client.LoopOutQuote(context.Background(), quoteReq) quote, err := client.LoopOutQuote(context.Background(), quoteReq)
if err != nil { if err != nil {
@ -185,6 +211,8 @@ func loopOut(ctx *cli.Context) error {
resp, err := client.LoopOut(context.Background(), &looprpc.LoopOutRequest{ resp, err := client.LoopOut(context.Background(), &looprpc.LoopOutRequest{
Amt: int64(amt), Amt: int64(amt),
Dest: destAddr, Dest: destAddr,
DestAmount: int64(destAmount),
ChangeAddr: changeAddr,
MaxMinerFee: int64(limits.maxMinerFee), MaxMinerFee: int64(limits.maxMinerFee),
MaxPrepayAmt: int64(limits.maxPrepayAmt), MaxPrepayAmt: int64(limits.maxPrepayAmt),
MaxSwapFee: int64(limits.maxSwapFee), MaxSwapFee: int64(limits.maxSwapFee),

@ -76,6 +76,11 @@ var quoteOutCommand = cli.Command{
ArgsUsage: "amt", ArgsUsage: "amt",
Description: "Allows to determine the cost of a swap up front", Description: "Allows to determine the cost of a swap up front",
Flags: []cli.Flag{ 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{ cli.Uint64Flag{
Name: "conf_target", Name: "conf_target",
Usage: "the number of blocks from the swap " + Usage: "the number of blocks from the swap " +
@ -124,6 +129,7 @@ func quoteOut(ctx *cli.Context) error {
ctxb := context.Background() ctxb := context.Background()
quoteReq := &looprpc.QuoteRequest{ quoteReq := &looprpc.QuoteRequest{
Amt: int64(amt), Amt: int64(amt),
WithChange: ctx.Bool("with_change"),
ConfTarget: int32(ctx.Uint64("conf_target")), ConfTarget: int32(ctx.Uint64("conf_target")),
SwapPublicationDeadline: uint64(swapDeadline.Unix()), SwapPublicationDeadline: uint64(swapDeadline.Unix()),
} }

@ -19,6 +19,17 @@ type OutRequest struct {
// Destination address for the swap. // Destination address for the swap.
DestAddr btcutil.Address 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 // MaxSwapRoutingFee is the maximum off-chain fee in msat that may be
// paid for payment to the server. This limit is applied during path // paid for payment to the server. This limit is applied during path
// finding. Typically this value is taken from the response of the // finding. Typically this value is taken from the response of the
@ -111,6 +122,11 @@ type LoopOutQuoteRequest struct {
// include the swap and miner fee. // include the swap and miner fee.
Amount btcutil.Amount 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 // SweepConfTarget specifies the targeted confirmation target for the
// client sweep tx. // client sweep tx.
SweepConfTarget int32 SweepConfTarget int32

@ -81,6 +81,28 @@ 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)
}
}
if in.DestAmount != 0 && in.Amt < in.DestAmount+in.MaxMinerFee {
return nil, fmt.Errorf("amt is too small; the following rule is not satisfied: amt >= max_miner_fee + dest_amt; got values: amt=%d, max_miner_fee=%d, dest_amt=%d", in.Amt, in.DestAmount, in.MaxMinerFee)
}
// Check that the label is valid. // Check that the label is valid.
if err := labels.Validate(in.Label); err != nil { if err := labels.Validate(in.Label); err != nil {
return nil, err return nil, err
@ -89,6 +111,8 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
req := &loop.OutRequest{ req := &loop.OutRequest{
Amount: btcutil.Amount(in.Amt), Amount: btcutil.Amount(in.Amt),
DestAddr: sweepAddr, DestAddr: sweepAddr,
DestAmount: btcutil.Amount(in.DestAmount),
ChangeAddr: changeAddr,
MaxMinerFee: btcutil.Amount(in.MaxMinerFee), MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
MaxPrepayAmount: btcutil.Amount(in.MaxPrepayAmt), MaxPrepayAmount: btcutil.Amount(in.MaxPrepayAmt),
MaxPrepayRoutingFee: btcutil.Amount(in.MaxPrepayRoutingFee), MaxPrepayRoutingFee: btcutil.Amount(in.MaxPrepayRoutingFee),
@ -410,6 +434,7 @@ func (s *swapClientServer) LoopOutQuote(ctx context.Context,
} }
quote, err := s.impl.LoopOutQuote(ctx, &loop.LoopOutQuoteRequest{ quote, err := s.impl.LoopOutQuote(ctx, &loop.LoopOutQuoteRequest{
Amount: btcutil.Amount(req.Amt), Amount: btcutil.Amount(req.Amt),
WithChange: req.WithChange,
SweepConfTarget: confTarget, SweepConfTarget: confTarget,
SwapPublicationDeadline: time.Unix( SwapPublicationDeadline: time.Unix(
int64(req.SwapPublicationDeadline), 0, int64(req.SwapPublicationDeadline), 0,

@ -24,6 +24,14 @@ type LoopOutContract struct {
// DestAddr is the destination address of the loop out swap. // DestAddr is the destination address of the loop out swap.
DestAddr btcutil.Address 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 // SwapInvoice is the invoice that is to be paid by the client to
// initiate the loop out swap. // initiate the loop out swap.
SwapInvoice string SwapInvoice string
@ -205,6 +213,21 @@ func deserializeLoopOutContract(value []byte, chainParams *chaincfg.Params) (
} }
contract.SwapPublicationDeadline = time.Unix(0, deadlineNano) contract.SwapPublicationDeadline = time.Unix(0, deadlineNano)
if err := binary.Read(r, byteOrder, &contract.DestAmount); err != nil {
return nil, err
}
addr, err = wire.ReadVarString(r, 0)
if err != nil {
return nil, err
}
if addr != "" {
contract.ChangeAddr, err = btcutil.DecodeAddress(addr, chainParams)
if err != nil {
return nil, err
}
}
return &contract, nil return &contract, nil
} }
@ -294,5 +317,16 @@ func serializeLoopOutContract(swap *LoopOutContract) (
return nil, err return nil, err
} }
if err := binary.Write(&b, byteOrder, swap.DestAmount); err != nil {
return nil, err
}
var addressStr string
if swap.ChangeAddr != nil {
addressStr = swap.ChangeAddr.String()
}
if err := wire.WriteVarString(&b, 0, addressStr); err != nil {
return nil, err
}
return b.Bytes(), nil return b.Bytes(), nil
} }

@ -37,6 +37,7 @@ var (
migrateSwapPublicationDeadline, migrateSwapPublicationDeadline,
migrateLastHop, migrateLastHop,
migrateUpdates, migrateUpdates,
migrateExactAmount,
} }
latestDBVersion = uint32(len(migrations)) latestDBVersion = uint32(len(migrations))

@ -0,0 +1,69 @@
package loopdb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/coreos/bbolt"
)
// migrateExactAmount migrates the loop out contracts to new format, having
// two new fields in the end: DestAmount and ChangeAddr.
func migrateExactAmount(tx *bbolt.Tx, chainParams *chaincfg.Params) error {
// Prepare suffix.
var b bytes.Buffer
var amount btcutil.Amount
if err := binary.Write(&b, byteOrder, amount); err != nil {
return err
}
if err := wire.WriteVarString(&b, 0, ""); err != nil {
return err
}
suffix := b.Bytes()
// Make the list of buckets.
rootBucket := tx.Bucket(loopOutBucketKey)
if rootBucket == nil {
return fmt.Errorf("bucket %v does not exist", loopOutBucketKey)
}
var swaps [][]byte
// Do not modify inside the for each.
err := rootBucket.ForEach(func(swapHash, v []byte) error {
// Only go into things that we know are sub-bucket keys.
if rootBucket.Bucket(swapHash) != nil {
swaps = append(swaps, swapHash)
}
return nil
})
if err != nil {
return err
}
// With the swaps listed, migrate them one by one.
for _, swapHash := range swaps {
swapBucket := rootBucket.Bucket(swapHash)
if swapBucket == nil {
return fmt.Errorf("swap bucket %x not found", swapHash)
}
contractBytes := swapBucket.Get(contractKey)
if contractBytes == nil {
return errors.New("contract not found")
}
contractBytes = append(contractBytes, suffix...)
if err := swapBucket.Put(contractKey, contractBytes); err != nil {
return fmt.Errorf("failed to save the updated contract for %x: %v", swapHash, err)
}
}
return nil
}

@ -0,0 +1,65 @@
package loopdb
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/coreos/bbolt"
"github.com/stretchr/testify/require"
)
func TestMigrationExactAmount(t *testing.T) {
var (
legacyDbVersion = Hex("00000004")
)
legacyDb := map[string]interface{}{
"metadata": map[string]interface{}{
"dbp": legacyDbVersion,
},
"uncharge-swaps": map[string]interface{}{
Hex("c3b3d7a145dbd2bab5aa1f505305f31ee432fe23b0801f065fac453dd9b1f923"): map[string]interface{}{
"contract": Hex("161b2526643767387ca76e58c964a8f2b6c0a13392b2dea93bde260226a263fb836954054ed1756b000000000000c350fd11016c6e6263727431333337306e3170303072343775707035366c7671663836753565766135647868686c706c78303733756a70676e3979767977376130766a37746d307678793276683576716471327770657832757270307963717a7279787139377a76757173703570373232733970686a6e6e6e706c3778716e796a78353373706863346c396735306b396e347836703761793577707539306b6673397179397173717a353766676a7a67676838343439377375716b383436787a3333336a713036736c6b38637a323872657466363672796b7876396a746e6a3072683979666a6170777065617265713071396679797a666664676d6874687973617370757565746e6b72306b32376370326173366a750269d66fd2cea620dc06f1f7de7838f0c8b145b82c7033080c398862f3421a23230382cb637badbb07f9926a06ecd88b6150513ea0060dc8d6dc1c1fb623926b0a0f000077d400000000000b458c00000000000005f10000000000000024000077a22c6263727431713271756332666777737971376463617a73666e3332636a7874667671647671366a6c70706574fd0f016c6e626372743530313834306e317030307234377570703563776561306732396d30667434646432726167397870306e726d6a72396c33726b7a717037706a6c34337a6e6d6b64336c79337364713877646d6b7a757163717a7279787139377a767571737035616478717538766168643730743776747165777578366d6d64337977636639767835736476717567753833327230676e373466733971793971737168746773636638386e377664767136716e71307a657775366d7471616e326c7a306e7534737a72376c6b36646d343673336c78726572656e333972616b7a6c777378346c613538733966773630356d6767766b766879716e743339713976737367777879367571707236713273780000000600000000000003f20000000000000000161b25262710ce00"),
"outgoing-chan-set": nil,
"updates": map[string]interface{}{
Hex("0000000000000001"): map[string]interface{}{
string([]byte{0}): Hex("161b252a770e649b01000000000000053900000000000000000000000000000001"),
},
Hex("0000000000000002"): map[string]interface{}{
string([]byte{0}): Hex("161b252ab671bdd90200000000000005f10000000000001a9c0000000000000003"),
},
},
},
},
}
// Restore a legacy database.
tempDirName, err := ioutil.TempDir("", "clientstore")
require.NoError(t, err)
defer os.RemoveAll(tempDirName)
tempPath := filepath.Join(tempDirName, dbFileName)
db, err := bbolt.Open(tempPath, 0600, nil)
require.NoError(t, err)
err = db.Update(func(tx *bbolt.Tx) error {
return RestoreDB(tx, legacyDb)
})
// Close database regardless of update result.
db.Close()
// Assert update was successful.
require.NoError(t, err)
// Open db and migrate to the latest version.
store, err := NewBoltSwapStore(tempDirName, &chaincfg.MainNetParams)
require.NoError(t, err)
// Fetch the legacy loop out swap.
_, err = store.FetchLoopOutSwaps()
require.NoError(t, err)
}

@ -849,8 +849,8 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
} }
// Calculate sweep tx fee // Calculate sweep tx fee
fee, err := s.sweeper.GetSweepFee( fee, _, _, err := s.sweeper.GetSweepFee(
ctx, s.htlc.AddTimeoutToEstimator, s.timeoutAddr, ctx, s.htlc.AddTimeoutToEstimator, s.timeoutAddr, nil,
TimeoutTxConfTarget, TimeoutTxConfTarget,
) )
if err != nil { if err != nil {
@ -864,7 +864,7 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
sequence := uint32(0) sequence := uint32(0)
timeoutTx, err := s.sweeper.CreateSweepTx( timeoutTx, err := s.sweeper.CreateSweepTx(
ctx, s.height, sequence, s.htlc, *htlcOutpoint, s.SenderKey, ctx, s.height, sequence, s.htlc, *htlcOutpoint, s.SenderKey,
witnessFunc, htlcValue, fee, s.timeoutAddr, witnessFunc, htlcValue, 0, fee, 0, 0, s.timeoutAddr, nil, s.log.Warnf,
) )
if err != nil { if err != nil {
return err return err

@ -149,6 +149,8 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
contract := loopdb.LoopOutContract{ contract := loopdb.LoopOutContract{
SwapInvoice: swapResp.swapInvoice, SwapInvoice: swapResp.swapInvoice,
DestAddr: request.DestAddr, DestAddr: request.DestAddr,
DestAmount: request.DestAmount,
ChangeAddr: request.ChangeAddr,
MaxSwapRoutingFee: request.MaxSwapRoutingFee, MaxSwapRoutingFee: request.MaxSwapRoutingFee,
SweepConfTarget: request.SweepConfTarget, SweepConfTarget: request.SweepConfTarget,
HtlcConfirmations: confs, HtlcConfirmations: confs,
@ -910,23 +912,27 @@ func (s *loopOutSwap) sweep(ctx context.Context,
confTarget > DefaultSweepConfTarget { confTarget > DefaultSweepConfTarget {
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 { if err != nil {
return err return err
} }
// Ensure it doesn't exceed our maximum fee allowed. // 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", s.log.Warnf("Required fee %v exceeds max miner fee of %v",
fee, s.MaxMinerFee) *fee, s.MaxMinerFee)
if s.state == loopdb.StatePreimageRevealed { if s.state == loopdb.StatePreimageRevealed {
// The currently required fee exceeds the max, but we // The currently required fee exceeds the max, but we
// already revealed the preimage. The best we can do now // already revealed the preimage. The best we can do now
// is to republish with the max fee. // is to republish with the max fee.
fee = s.MaxMinerFee *fee = s.MaxMinerFee
} else { } else {
s.log.Warnf("Not revealing preimage") s.log.Warnf("Not revealing preimage")
return nil return nil
@ -936,7 +942,10 @@ func (s *loopOutSwap) sweep(ctx context.Context,
// Create sweep tx. // Create sweep tx.
sweepTx, err := s.sweeper.CreateSweepTx( sweepTx, err := s.sweeper.CreateSweepTx(
ctx, s.height, s.htlc.SuccessSequence(), s.htlc, htlcOutpoint, 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,
s.log.Warnf,
) )
if err != nil { if err != nil {
return err return err
@ -955,6 +964,11 @@ func (s *loopOutSwap) sweep(ctx context.Context,
} }
// Publish tx. // 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.log.Infof("Sweep on chain HTLC to address %v with fee %v (tx %v)",
s.DestAddr, fee, sweepTx.TxHash()) s.DestAddr, fee, sweepTx.TxHash())
@ -975,6 +989,10 @@ func validateLoopOutContract(lnd *lndclient.LndServices,
height int32, request *OutRequest, swapHash lntypes.Hash, height int32, request *OutRequest, swapHash lntypes.Hash,
response *newLoopOutResponse) error { 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. // Check invoice amounts.
chainParams := lnd.ChainParams chainParams := lnd.ChainParams

@ -208,6 +208,12 @@ type LoopOutRequest struct {
//Base58 encoded destination address for the swap. //Base58 encoded destination address for the swap.
Dest string `protobuf:"bytes,2,opt,name=dest,proto3" json:"dest,omitempty"` Dest string `protobuf:"bytes,2,opt,name=dest,proto3" json:"dest,omitempty"`
// //
//Exact amount in satoshis to send to 'dest'. Optional.
DestAmount int64 `protobuf:"varint,15,opt,name=dest_amount,json=destAmount,proto3" json:"dest_amount,omitempty"`
//
//Base58 encoded change address for the swap.
ChangeAddr string `protobuf:"bytes,16,opt,name=change_addr,json=changeAddr,proto3" json:"change_addr,omitempty"`
//
//Maximum off-chain fee in sat that may be paid for swap payment to the server. //Maximum off-chain fee in sat that may be paid for swap payment to the server.
//This limit is applied during path finding. Typically this value is taken //This limit is applied during path finding. Typically this value is taken
//from the response of the GetQuote call. //from the response of the GetQuote call.
@ -323,6 +329,20 @@ func (m *LoopOutRequest) GetDest() string {
return "" return ""
} }
func (m *LoopOutRequest) GetDestAmount() int64 {
if m != nil {
return m.DestAmount
}
return 0
}
func (m *LoopOutRequest) GetChangeAddr() string {
if m != nil {
return m.ChangeAddr
}
return ""
}
func (m *LoopOutRequest) GetMaxSwapRoutingFee() int64 { func (m *LoopOutRequest) GetMaxSwapRoutingFee() int64 {
if m != nil { if m != nil {
return m.MaxSwapRoutingFee return m.MaxSwapRoutingFee
@ -1128,6 +1148,9 @@ type QuoteRequest struct {
//The amount to swap in satoshis. //The amount to swap in satoshis.
Amt int64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"` Amt int64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"`
// //
//If change output will be added. This is needed to send exact amount.
WithChange bool `protobuf:"varint,5,opt,name=with_change,json=withChange,proto3" json:"with_change,omitempty"`
//
//The confirmation target that should be used either for the sweep of the //The confirmation target that should be used either for the sweep of the
//on-chain HTLC broadcast by the swap server in the case of a Loop Out, or for //on-chain HTLC broadcast by the swap server in the case of a Loop Out, or for
//the confirmation of the on-chain HTLC broadcast by the swap client in the //the confirmation of the on-chain HTLC broadcast by the swap client in the
@ -1181,6 +1204,13 @@ func (m *QuoteRequest) GetAmt() int64 {
return 0 return 0
} }
func (m *QuoteRequest) GetWithChange() bool {
if m != nil {
return m.WithChange
}
return false
}
func (m *QuoteRequest) GetConfTarget() int32 { func (m *QuoteRequest) GetConfTarget() int32 {
if m != nil { if m != nil {
return m.ConfTarget return m.ConfTarget
@ -2005,157 +2035,159 @@ func init() {
func init() { proto.RegisterFile("client.proto", fileDescriptor_014de31d7ac8c57c) } func init() { proto.RegisterFile("client.proto", fileDescriptor_014de31d7ac8c57c) }
var fileDescriptor_014de31d7ac8c57c = []byte{ var fileDescriptor_014de31d7ac8c57c = []byte{
// 2385 bytes of a gzipped FileDescriptorProto // 2431 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0xe3, 0xc6, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcf, 0x6f, 0xe3, 0xc6,
0xf5, 0x5f, 0x49, 0x94, 0x25, 0x3d, 0x51, 0x12, 0x3d, 0xde, 0xb5, 0x65, 0xc5, 0x41, 0xbc, 0x4c, 0xf5, 0x5f, 0x49, 0x94, 0x25, 0x3d, 0x51, 0x12, 0x3d, 0xde, 0xb5, 0x65, 0xc5, 0xc1, 0x7a, 0x99,
0xf6, 0x1b, 0xc7, 0x49, 0xac, 0x6f, 0x9c, 0x4b, 0x1b, 0x24, 0x05, 0xb4, 0xb2, 0x1c, 0xcb, 0xb5, 0xec, 0x37, 0x8e, 0x93, 0xac, 0xbe, 0x71, 0x2e, 0x6d, 0x90, 0x14, 0xd0, 0xca, 0x72, 0xac, 0xad,
0x25, 0x95, 0x92, 0x37, 0x48, 0x51, 0x80, 0x18, 0x4b, 0x63, 0x8b, 0x88, 0xf8, 0x63, 0xc9, 0xd1, 0x2d, 0xa9, 0x94, 0xbc, 0x41, 0x8a, 0x02, 0xc4, 0x58, 0x1a, 0x4b, 0x44, 0xc4, 0x1f, 0x21, 0x47,
0xae, 0x8d, 0xa0, 0x2d, 0x50, 0xa0, 0xe7, 0x1e, 0xfa, 0x1f, 0xf4, 0xde, 0x5b, 0x6f, 0xed, 0x9f, 0xbb, 0x36, 0x82, 0xb6, 0x40, 0x81, 0x9e, 0x7b, 0xe8, 0xbf, 0xd2, 0x5b, 0x7b, 0xeb, 0xb5, 0xa7,
0xd0, 0x53, 0x7b, 0xec, 0xb5, 0x97, 0x1e, 0xfa, 0x27, 0x14, 0x28, 0xe6, 0x0d, 0x49, 0x91, 0xb2, 0xf6, 0xd8, 0x6b, 0x2f, 0x3d, 0x14, 0xfd, 0x0b, 0x0a, 0x14, 0x6f, 0x86, 0xa4, 0x48, 0x59, 0x72,
0xe4, 0xa2, 0x87, 0xde, 0xc4, 0xf7, 0x3e, 0xf3, 0xe6, 0xcd, 0xfb, 0xfd, 0x04, 0xea, 0x78, 0x66, 0xd1, 0x43, 0x4f, 0x36, 0xdf, 0xfb, 0xcc, 0x9b, 0x79, 0xbf, 0xdf, 0x13, 0xa8, 0xe3, 0xb9, 0xc5,
0x31, 0x87, 0x1f, 0x79, 0xbe, 0xcb, 0x5d, 0x52, 0x98, 0xb9, 0xae, 0xe7, 0x7b, 0xe3, 0xc6, 0xde, 0x1c, 0xfe, 0xc2, 0xf3, 0x5d, 0xee, 0x92, 0xc2, 0xdc, 0x75, 0x3d, 0xdf, 0x1b, 0x37, 0x0e, 0xa6,
0xad, 0xeb, 0xde, 0xce, 0x58, 0x93, 0x7a, 0x56, 0x93, 0x3a, 0x8e, 0xcb, 0x29, 0xb7, 0x5c, 0x27, 0xae, 0x3b, 0x9d, 0xb3, 0x26, 0xf5, 0xac, 0x26, 0x75, 0x1c, 0x97, 0x53, 0x6e, 0xb9, 0x4e, 0x20,
0x90, 0x30, 0xfd, 0xf7, 0x0a, 0x54, 0x2f, 0x5c, 0xd7, 0xeb, 0xcf, 0xb9, 0xc1, 0x5e, 0xcf, 0x59, 0x61, 0xfa, 0x3f, 0x15, 0xa8, 0x5e, 0xb8, 0xae, 0xd7, 0x5f, 0x70, 0x83, 0x7d, 0xb7, 0x60, 0x01,
0xc0, 0x89, 0x06, 0x39, 0x6a, 0xf3, 0x7a, 0x66, 0x3f, 0x73, 0x90, 0x33, 0xc4, 0x4f, 0x42, 0x40, 0x27, 0x1a, 0xe4, 0xa8, 0xcd, 0xeb, 0x99, 0xc3, 0xcc, 0x51, 0xce, 0xc0, 0x7f, 0x09, 0x01, 0x65,
0x99, 0xb0, 0x80, 0xd7, 0xb3, 0xfb, 0x99, 0x83, 0x92, 0x81, 0xbf, 0x49, 0x13, 0x9e, 0xda, 0xf4, 0xc2, 0x02, 0x5e, 0xcf, 0x1e, 0x66, 0x8e, 0x4a, 0x86, 0xf8, 0x9f, 0x3c, 0x85, 0x32, 0xfe, 0x35,
0xce, 0x0c, 0xde, 0x52, 0xcf, 0xf4, 0xdd, 0x39, 0xb7, 0x9c, 0x5b, 0xf3, 0x86, 0xb1, 0x7a, 0x0e, 0xa9, 0xed, 0x2e, 0x1c, 0x5e, 0xaf, 0x09, 0x34, 0x20, 0xa9, 0x25, 0x28, 0x08, 0x18, 0xcf, 0xa8,
0x8f, 0x6d, 0xda, 0xf4, 0x6e, 0xf8, 0x96, 0x7a, 0x86, 0xe4, 0x9c, 0x32, 0x46, 0x3e, 0x87, 0x6d, 0x33, 0x65, 0x26, 0x9d, 0x4c, 0xfc, 0xba, 0x26, 0xce, 0x82, 0x24, 0xb5, 0x26, 0x13, 0x9f, 0x34,
0x71, 0xc0, 0xf3, 0x99, 0x47, 0xef, 0x53, 0x47, 0x14, 0x3c, 0xb2, 0x65, 0xd3, 0xbb, 0x01, 0x32, 0xe1, 0xb1, 0x4d, 0x6f, 0xcd, 0xe0, 0x2d, 0xf5, 0x4c, 0xdf, 0x5d, 0x70, 0xcb, 0x99, 0x9a, 0x37,
0x13, 0x87, 0xf6, 0x41, 0x8d, 0x6f, 0x11, 0xd0, 0x3c, 0x42, 0x21, 0x94, 0x2e, 0x10, 0x1f, 0x40, 0x8c, 0xd5, 0x73, 0x42, 0xd4, 0xb6, 0x4d, 0x6f, 0x87, 0x6f, 0xa9, 0x67, 0x48, 0xce, 0x19, 0x63,
0x35, 0x21, 0x56, 0x28, 0xbe, 0x81, 0x18, 0x35, 0x16, 0xd7, 0xb2, 0x39, 0xd1, 0xa1, 0x22, 0x50, 0xe4, 0x33, 0xd8, 0xc5, 0x03, 0x9e, 0xcf, 0x3c, 0x7a, 0x97, 0x3a, 0xa2, 0x88, 0x23, 0x3b, 0x36,
0xb6, 0xe5, 0x30, 0x1f, 0x05, 0x15, 0x10, 0x54, 0xb6, 0xe9, 0xdd, 0xa5, 0xa0, 0x09, 0x49, 0x9f, 0xbd, 0x1d, 0x08, 0x66, 0xe2, 0xd0, 0x21, 0xa8, 0xf1, 0x2d, 0x08, 0xcd, 0xcb, 0x87, 0x86, 0xd2,
0x80, 0x26, 0x6c, 0x66, 0xba, 0x73, 0x6e, 0x8e, 0xa7, 0xd4, 0x71, 0xd8, 0xac, 0x5e, 0xdc, 0xcf, 0x11, 0xf1, 0x3e, 0x54, 0x13, 0x62, 0x51, 0xf5, 0x2d, 0x81, 0x51, 0x63, 0x71, 0x2d, 0x9b, 0x13,
0x1c, 0x28, 0x2f, 0xb3, 0xf5, 0x8c, 0x51, 0x9d, 0x49, 0x2b, 0xb5, 0x25, 0x87, 0x1c, 0xc2, 0xa6, 0x1d, 0x2a, 0x88, 0xb2, 0x2d, 0x87, 0xf9, 0x42, 0x50, 0x41, 0x80, 0xca, 0x36, 0xbd, 0xbd, 0x44,
0x3b, 0xe7, 0xb7, 0xae, 0x78, 0x84, 0x40, 0x9b, 0x01, 0xe3, 0xf5, 0xf2, 0x7e, 0xee, 0x40, 0x31, 0x1a, 0x4a, 0xfa, 0x18, 0x34, 0xb4, 0xba, 0xe9, 0x2e, 0xb8, 0x89, 0x8a, 0x3a, 0x6c, 0x5e, 0x2f,
0x6a, 0x11, 0x43, 0x60, 0x87, 0x8c, 0x0b, 0x6c, 0xf0, 0x96, 0x31, 0xcf, 0x1c, 0xbb, 0xce, 0x8d, 0x1e, 0x66, 0x8e, 0x94, 0x97, 0xd9, 0x7a, 0xc6, 0xa8, 0xce, 0xa5, 0x9d, 0xdb, 0x92, 0x43, 0x8e,
0xc9, 0xa9, 0x7f, 0xcb, 0x78, 0xbd, 0xb4, 0x9f, 0x39, 0xc8, 0x1b, 0x35, 0x64, 0xb4, 0x5d, 0xe7, 0x61, 0xdb, 0x5d, 0xf0, 0xa9, 0x8b, 0x4a, 0x20, 0xda, 0x0c, 0x18, 0xaf, 0x97, 0x0f, 0x73, 0x47,
0x66, 0x84, 0x64, 0xf2, 0x29, 0x90, 0x29, 0x9f, 0x8d, 0x11, 0x6a, 0xf9, 0xb6, 0x74, 0x56, 0xbd, 0x8a, 0x51, 0x8b, 0x18, 0x88, 0x1d, 0x32, 0x8e, 0xd8, 0xe0, 0x2d, 0x63, 0x9e, 0x39, 0x76, 0x9d,
0x82, 0xe0, 0x4d, 0xc1, 0x69, 0x27, 0x19, 0xe4, 0x0b, 0xd8, 0x45, 0xe3, 0x78, 0xf3, 0xeb, 0x99, 0x1b, 0x93, 0x53, 0x7f, 0xca, 0x78, 0xbd, 0x74, 0x98, 0x39, 0xca, 0x1b, 0x35, 0xc1, 0x68, 0xbb,
0x35, 0x46, 0xa2, 0x39, 0x61, 0x74, 0x32, 0xb3, 0x1c, 0x56, 0x07, 0xa1, 0xbd, 0xb1, 0x23, 0x00, 0xce, 0xcd, 0x48, 0x90, 0xc9, 0x27, 0x40, 0x66, 0x7c, 0x3e, 0x16, 0x50, 0xcb, 0xb7, 0xa5, 0xbb,
0x83, 0x05, 0xff, 0x24, 0x64, 0x93, 0xa7, 0x90, 0x9f, 0xd1, 0x6b, 0x36, 0xab, 0xab, 0xe8, 0x57, 0xeb, 0x15, 0x01, 0xde, 0x46, 0x4e, 0x3b, 0xc9, 0x20, 0x9f, 0xc3, 0xbe, 0x30, 0x8e, 0xb7, 0xb8,
0xf9, 0x41, 0xf6, 0xa0, 0x64, 0x39, 0x16, 0xb7, 0x28, 0x77, 0xfd, 0x7a, 0x15, 0x39, 0x0b, 0x82, 0x9e, 0x5b, 0x63, 0x41, 0x34, 0x27, 0x8c, 0x4e, 0xe6, 0x96, 0xc3, 0xea, 0x80, 0xaf, 0x37, 0xf6,
0xfe, 0xeb, 0x2c, 0x54, 0x44, 0xbc, 0x74, 0x9d, 0xf5, 0xe1, 0xb2, 0xec, 0xb4, 0xec, 0x03, 0xa7, 0x10, 0x30, 0x58, 0xf2, 0x4f, 0x43, 0x36, 0x79, 0x0c, 0xf9, 0x39, 0xbd, 0x66, 0xf3, 0xba, 0x2a,
0x3d, 0x70, 0x47, 0xee, 0xa1, 0x3b, 0x76, 0xa1, 0x38, 0xa3, 0x01, 0x37, 0xa7, 0xae, 0x87, 0x11, 0xbc, 0x2b, 0x3f, 0xc8, 0x01, 0x94, 0x2c, 0xc7, 0xe2, 0x16, 0xe5, 0xae, 0x5f, 0xaf, 0x0a, 0xce,
0xa2, 0x1a, 0x05, 0xf1, 0x7d, 0xe6, 0x7a, 0xe4, 0x7d, 0xa8, 0xb0, 0x3b, 0xce, 0x7c, 0x87, 0xce, 0x92, 0xa0, 0xff, 0x3a, 0x0b, 0x15, 0x8c, 0xb8, 0xae, 0xb3, 0x39, 0xe0, 0x56, 0x9d, 0x96, 0xbd,
0x4c, 0x61, 0x12, 0x0c, 0x8b, 0xa2, 0xa1, 0x46, 0xc4, 0x33, 0x3e, 0x1b, 0x93, 0x03, 0xd0, 0x62, 0xe7, 0xb4, 0x7b, 0xee, 0xc8, 0xdd, 0x77, 0xc7, 0x3e, 0x14, 0xe7, 0x34, 0xe0, 0xe6, 0xcc, 0xf5,
0x43, 0x46, 0x36, 0xdf, 0x40, 0x33, 0x56, 0x23, 0x33, 0x86, 0x26, 0x8f, 0xed, 0x50, 0x58, 0x6b, 0x44, 0x84, 0xa8, 0x46, 0x01, 0xbf, 0xcf, 0x5d, 0x8f, 0xbc, 0x07, 0x15, 0x76, 0xcb, 0x99, 0xef,
0x87, 0xe2, 0xb2, 0x1d, 0xfe, 0x91, 0x01, 0x15, 0x03, 0x9c, 0x05, 0x9e, 0xeb, 0x04, 0x8c, 0x10, 0xd0, 0xb9, 0x89, 0x26, 0x11, 0x61, 0x51, 0x34, 0xd4, 0x88, 0x78, 0xce, 0xe7, 0x63, 0x72, 0x04,
0xc8, 0x5a, 0x13, 0xb4, 0x42, 0x09, 0xe3, 0x25, 0x6b, 0x4d, 0xc4, 0x13, 0xac, 0x89, 0x79, 0x7d, 0x5a, 0x6c, 0xc8, 0xc8, 0xe6, 0x5b, 0xc2, 0x8c, 0xd5, 0xc8, 0x8c, 0xa1, 0xc9, 0x63, 0x3b, 0x14,
0xcf, 0x59, 0x80, 0x2f, 0x54, 0x8d, 0x82, 0x35, 0x79, 0x29, 0x3e, 0xc9, 0x0b, 0x50, 0x51, 0x3b, 0x36, 0xda, 0xa1, 0xb8, 0x6a, 0x87, 0xbf, 0x67, 0x40, 0x15, 0x01, 0xce, 0x02, 0xcf, 0x75, 0x02,
0x3a, 0x99, 0xf8, 0x2c, 0x08, 0x64, 0x6a, 0xe1, 0xc1, 0xb2, 0xa0, 0xb7, 0x24, 0x99, 0x1c, 0xc1, 0x46, 0x08, 0x64, 0xad, 0x89, 0xb0, 0x42, 0x49, 0xc4, 0x4b, 0xd6, 0x9a, 0xa0, 0x0a, 0xd6, 0xc4,
0x56, 0x12, 0x66, 0x3a, 0xde, 0xf1, 0xdb, 0x60, 0x8a, 0xf6, 0x28, 0xc9, 0x70, 0x08, 0x91, 0x3d, 0xbc, 0xbe, 0xe3, 0x2c, 0x10, 0x1a, 0xaa, 0x46, 0xc1, 0x9a, 0xbc, 0xc4, 0x4f, 0xf2, 0x1c, 0x54,
0x64, 0x90, 0x4f, 0xc2, 0xe8, 0x89, 0xf0, 0x12, 0x9e, 0x47, 0xb8, 0x96, 0x80, 0x0f, 0x10, 0xfd, 0xf1, 0x3a, 0xcc, 0x2e, 0x16, 0x04, 0x32, 0x39, 0xc5, 0xc1, 0x32, 0xd2, 0x5b, 0x92, 0x4c, 0x5e,
0x02, 0xaa, 0x01, 0xf3, 0xdf, 0x30, 0xdf, 0xb4, 0x59, 0x10, 0xd0, 0x5b, 0x86, 0x06, 0x2a, 0x19, 0xc0, 0x4e, 0x12, 0x66, 0x3a, 0xde, 0xc9, 0xdb, 0x60, 0x26, 0xec, 0x51, 0x92, 0xe1, 0x10, 0x22,
0x15, 0x49, 0xbd, 0x94, 0x44, 0x5d, 0x83, 0xea, 0xa5, 0xeb, 0x58, 0xdc, 0xf5, 0x43, 0x9f, 0xeb, 0x7b, 0x82, 0x41, 0x3e, 0x0e, 0xa3, 0x27, 0xc2, 0x4b, 0x78, 0x5e, 0xc0, 0xb5, 0x04, 0x7c, 0x20,
0x7f, 0x50, 0x00, 0xc4, 0xeb, 0x87, 0x9c, 0xf2, 0x79, 0xb0, 0xb2, 0x62, 0x08, 0x6b, 0x64, 0xd7, 0xd0, 0xcf, 0xa1, 0x1a, 0x30, 0xff, 0x0d, 0xf3, 0x4d, 0x9b, 0x05, 0x01, 0x9d, 0x32, 0x61, 0xa0,
0x5a, 0xa3, 0xbc, 0x6c, 0x0d, 0x85, 0xdf, 0x7b, 0x32, 0x0c, 0xaa, 0xc7, 0x9b, 0x47, 0x61, 0xed, 0x92, 0x51, 0x91, 0xd4, 0x4b, 0x49, 0xd4, 0x35, 0xa8, 0x5e, 0xba, 0x8e, 0xc5, 0x5d, 0x3f, 0xf4,
0x3a, 0x12, 0x77, 0x8c, 0xee, 0x3d, 0x66, 0x20, 0x9b, 0x1c, 0x40, 0x3e, 0xe0, 0x94, 0xcb, 0x8a, 0xb9, 0xfe, 0x3b, 0x05, 0x00, 0xb5, 0x1f, 0x72, 0xca, 0x17, 0xc1, 0xda, 0x9a, 0x83, 0xd6, 0xc8,
0x51, 0x3d, 0x26, 0x29, 0x9c, 0xd0, 0x85, 0x19, 0x12, 0x40, 0xbe, 0x82, 0xea, 0x0d, 0xb5, 0x66, 0x6e, 0xb4, 0x46, 0x79, 0xd5, 0x1a, 0x0a, 0xbf, 0xf3, 0x64, 0x18, 0x54, 0x4f, 0xb6, 0x5f, 0x84,
0x73, 0x9f, 0x99, 0x3e, 0xa3, 0x81, 0xeb, 0x60, 0x24, 0x57, 0x8f, 0xb7, 0xe3, 0x23, 0xa7, 0x92, 0xd5, 0xef, 0x05, 0xde, 0x31, 0xba, 0xf3, 0x98, 0x21, 0xd8, 0xe4, 0x08, 0xf2, 0x01, 0xa7, 0x5c,
0x6d, 0x20, 0xd7, 0xa8, 0xdc, 0x24, 0x3f, 0xc9, 0x87, 0x50, 0x0b, 0x5d, 0x2d, 0xf2, 0x89, 0x5b, 0x56, 0x8c, 0xea, 0x09, 0x49, 0xe1, 0xf0, 0x2d, 0xcc, 0x90, 0x00, 0xf2, 0x25, 0x54, 0x6f, 0xa8,
0x76, 0x54, 0x79, 0xaa, 0x0b, 0xf2, 0xc8, 0xb2, 0x85, 0x46, 0x1a, 0x06, 0xe9, 0xdc, 0x9b, 0x50, 0x35, 0x5f, 0xf8, 0xcc, 0xf4, 0x19, 0x0d, 0x5c, 0x47, 0x44, 0x72, 0xf5, 0x64, 0x37, 0x3e, 0x72,
0xce, 0x24, 0x52, 0xd6, 0x9f, 0xaa, 0xa0, 0x5f, 0x21, 0x19, 0x91, 0xcb, 0x0e, 0x2f, 0xac, 0x76, 0x26, 0xd9, 0x86, 0xe0, 0x1a, 0x95, 0x9b, 0xe4, 0x27, 0xf9, 0x00, 0x6a, 0xa1, 0xab, 0x31, 0x9f,
0xf8, 0x6a, 0x07, 0xaa, 0x6b, 0x1c, 0xb8, 0x26, 0x3c, 0x2a, 0xeb, 0xc2, 0xe3, 0x3d, 0x28, 0x8f, 0xb8, 0x65, 0x47, 0x95, 0xa7, 0xba, 0x24, 0x8f, 0x2c, 0x1b, 0x5f, 0xa4, 0x89, 0x20, 0x5d, 0x78,
0xdd, 0x80, 0x9b, 0xd2, 0xbf, 0x18, 0xd5, 0x39, 0x03, 0x04, 0x69, 0x88, 0x14, 0xf2, 0x1c, 0x54, 0x13, 0xca, 0x99, 0x44, 0xca, 0xfa, 0x53, 0x45, 0xfa, 0x95, 0x20, 0x0b, 0xe4, 0xaa, 0xc3, 0x0b,
0x04, 0xb8, 0xce, 0x78, 0x4a, 0x2d, 0x07, 0x8b, 0x54, 0xce, 0xc0, 0x43, 0x7d, 0x49, 0x12, 0xc9, 0xeb, 0x1d, 0xbe, 0xde, 0x81, 0xea, 0x06, 0x07, 0x6e, 0x08, 0x8f, 0xca, 0xa6, 0xf0, 0xc0, 0xaa,
0x27, 0x21, 0x37, 0x37, 0x12, 0x03, 0xb2, 0xde, 0x22, 0x26, 0xa4, 0x2d, 0x52, 0xaa, 0x96, 0x48, 0xee, 0x06, 0xdc, 0x94, 0xfe, 0x15, 0x51, 0x9d, 0x33, 0x00, 0x49, 0x43, 0x41, 0x21, 0xcf, 0x40,
0x29, 0x9d, 0x80, 0x76, 0x61, 0x05, 0x5c, 0x78, 0x2b, 0x88, 0x42, 0xe9, 0x47, 0xb0, 0x99, 0xa0, 0x15, 0x00, 0xd7, 0x19, 0xcf, 0xa8, 0xe5, 0x88, 0x22, 0x95, 0x33, 0xc4, 0xa1, 0xbe, 0x24, 0x61,
0x85, 0xc9, 0xf4, 0x11, 0xe4, 0x45, 0xf5, 0x08, 0xea, 0x99, 0xfd, 0xdc, 0x41, 0xf9, 0x78, 0xeb, 0xf2, 0x49, 0xc8, 0xcd, 0x8d, 0xc4, 0x80, 0xac, 0xb7, 0x02, 0x13, 0xd2, 0x96, 0x29, 0x55, 0x4b,
0x81, 0xa3, 0xe7, 0x81, 0x21, 0x11, 0xfa, 0x73, 0xa8, 0x09, 0x62, 0xd7, 0xb9, 0x71, 0xa3, 0x8a, 0xa4, 0x94, 0x4e, 0x40, 0xbb, 0xb0, 0x02, 0x8e, 0xde, 0x0a, 0xa2, 0x50, 0xfa, 0x11, 0x6c, 0x27,
0x54, 0x8d, 0x53, 0x51, 0x15, 0x81, 0xa7, 0x57, 0x41, 0x1d, 0x31, 0xdf, 0x8e, 0xaf, 0xfc, 0x25, 0x68, 0x61, 0x32, 0x7d, 0x08, 0x79, 0xac, 0x1e, 0x41, 0x3d, 0x73, 0x98, 0x3b, 0x2a, 0x9f, 0xec,
0xd4, 0xba, 0x4e, 0x48, 0x09, 0x2f, 0xfc, 0x3f, 0xa8, 0xd9, 0x96, 0x23, 0x4b, 0x16, 0xb5, 0xdd, 0xdc, 0x73, 0xf4, 0x22, 0x30, 0x24, 0x42, 0x7f, 0x06, 0x35, 0x24, 0x76, 0x9d, 0x1b, 0x37, 0xaa,
0xb9, 0xc3, 0x43, 0x87, 0x57, 0x6c, 0xcb, 0x11, 0xf2, 0x5b, 0x48, 0x44, 0x5c, 0x54, 0xda, 0x42, 0x48, 0xd5, 0x38, 0x15, 0x55, 0x0c, 0x3c, 0xbd, 0x0a, 0xea, 0x88, 0xf9, 0x76, 0x7c, 0xe5, 0x2f,
0xdc, 0x46, 0x88, 0x93, 0xd5, 0x4d, 0xe2, 0xce, 0x95, 0x62, 0x46, 0xcb, 0x9e, 0x2b, 0xc5, 0xac, 0xa1, 0xd6, 0x75, 0x42, 0x4a, 0x78, 0xe1, 0xff, 0x41, 0xcd, 0xb6, 0x1c, 0x59, 0xb2, 0xc2, 0x9e,
0x96, 0x3b, 0x57, 0x8a, 0x39, 0x4d, 0x39, 0x57, 0x8a, 0x8a, 0x96, 0x3f, 0x57, 0x8a, 0x05, 0xad, 0x28, 0x1d, 0x5e, 0xb1, 0x2d, 0x07, 0xe5, 0x87, 0x6d, 0x11, 0x71, 0x51, 0x69, 0x0b, 0x71, 0x5b,
0xa8, 0xff, 0x39, 0x03, 0x5a, 0x7f, 0xce, 0xff, 0xa7, 0x2a, 0x60, 0x63, 0xb4, 0x1c, 0x73, 0x3c, 0x21, 0x4e, 0x56, 0x37, 0x89, 0x7b, 0xa5, 0x14, 0x33, 0x5a, 0xf6, 0x95, 0x52, 0xcc, 0x6a, 0xb9,
0xe3, 0x6f, 0xcc, 0x09, 0x9b, 0x71, 0x8a, 0xee, 0xce, 0x1b, 0xaa, 0x6d, 0x39, 0xed, 0x19, 0x7f, 0x57, 0x4a, 0x31, 0xa7, 0x29, 0xaf, 0x94, 0xa2, 0xa2, 0xe5, 0x5f, 0x29, 0xc5, 0x82, 0x56, 0xd4,
0x73, 0x22, 0x68, 0x51, 0xfb, 0x4c, 0xa0, 0x4a, 0x21, 0x8a, 0xde, 0xc5, 0xa8, 0xff, 0xf0, 0x9c, 0xff, 0x94, 0x01, 0xad, 0xbf, 0xe0, 0xff, 0xd3, 0x27, 0x88, 0xc6, 0x68, 0x39, 0xe6, 0x78, 0xce,
0xdf, 0x65, 0x40, 0xfd, 0xc9, 0xdc, 0xe5, 0x6c, 0x7d, 0x4b, 0xc0, 0xc0, 0x5b, 0xd4, 0xe1, 0x2c, 0xdf, 0x98, 0x13, 0x36, 0xe7, 0x54, 0xb8, 0x3b, 0x6f, 0xa8, 0xb6, 0xe5, 0xb4, 0xe7, 0xfc, 0xcd,
0xde, 0x01, 0xe3, 0x45, 0x0d, 0x7e, 0x50, 0xd2, 0x73, 0x2b, 0x4a, 0xfa, 0xa3, 0xcd, 0x4e, 0x79, 0x29, 0xd2, 0xa2, 0xf6, 0x99, 0x40, 0x95, 0x42, 0x14, 0xbd, 0x8d, 0x51, 0xff, 0x41, 0x9d, 0x3f,
0xb4, 0xd9, 0xe9, 0xbf, 0xc9, 0x08, 0xaf, 0x87, 0x6a, 0x86, 0x26, 0xdf, 0x07, 0x35, 0x6a, 0x52, 0x66, 0x40, 0xfd, 0xc9, 0xc2, 0xe5, 0x6c, 0x73, 0x4b, 0x78, 0x0a, 0xe5, 0xb7, 0x16, 0x9f, 0x99,
0x66, 0x40, 0x23, 0x85, 0x21, 0x90, 0x5d, 0x6a, 0x48, 0x71, 0xca, 0xc1, 0x04, 0xc3, 0x1b, 0x83, 0x72, 0x80, 0x08, 0xeb, 0x35, 0x20, 0xa9, 0x2d, 0x28, 0x32, 0x32, 0x97, 0x85, 0x3a, 0x2b, 0x1e,
0x69, 0x8c, 0x0c, 0xa7, 0x1c, 0xc1, 0x1b, 0x48, 0x56, 0x78, 0xe0, 0x5d, 0x80, 0x84, 0x2d, 0xf3, 0x01, 0xe3, 0x65, 0x91, 0xbe, 0x57, 0xf3, 0x73, 0x6b, 0x6a, 0xfe, 0x83, 0xdd, 0x50, 0x79, 0xb0,
0xf8, 0xce, 0xd2, 0x38, 0x61, 0x48, 0x69, 0x42, 0x45, 0xcb, 0xeb, 0x7f, 0x91, 0x51, 0xf0, 0xdf, 0x1b, 0xea, 0xbf, 0xc9, 0x60, 0x58, 0x84, 0x7a, 0x84, 0x3e, 0x39, 0x04, 0x35, 0xea, 0x62, 0x66,
0xaa, 0xf4, 0x01, 0x54, 0x17, 0xc3, 0x0e, 0x62, 0x64, 0x7f, 0x55, 0xbd, 0x68, 0xda, 0x11, 0xa8, 0x40, 0x23, 0x8d, 0x20, 0x90, 0x6d, 0x6c, 0x48, 0x39, 0x8e, 0x41, 0x22, 0x03, 0xc5, 0x8d, 0xc1,
0x8f, 0xc3, 0x3a, 0x22, 0xe7, 0x8e, 0xb4, 0xda, 0x35, 0xc1, 0x19, 0x0a, 0x46, 0x28, 0x12, 0xe7, 0x2c, 0x46, 0x86, 0x63, 0x10, 0xf2, 0x06, 0x92, 0x15, 0x1e, 0x78, 0x17, 0x20, 0x61, 0xec, 0xbc,
0x13, 0x61, 0x57, 0x7a, 0x6f, 0x33, 0x87, 0x9b, 0x38, 0xec, 0xc9, 0x9e, 0x5b, 0x43, 0x7b, 0x4a, 0xd0, 0xb3, 0x34, 0x4e, 0x58, 0x5a, 0xda, 0x58, 0xd1, 0xf2, 0xfa, 0x9f, 0x65, 0x98, 0xfc, 0xb7,
0xfa, 0x89, 0xf0, 0xed, 0xe3, 0x0f, 0xd4, 0x6b, 0x50, 0x19, 0xb9, 0xdf, 0x31, 0x27, 0x4e, 0xb6, 0x4f, 0x7a, 0x1f, 0xaa, 0xcb, 0x69, 0x48, 0x60, 0x64, 0x03, 0x56, 0xbd, 0x68, 0x1c, 0x42, 0xd4,
0x2f, 0xa1, 0x1a, 0x11, 0xc2, 0x27, 0x1e, 0xc2, 0x06, 0x47, 0x4a, 0x98, 0xdd, 0x8b, 0x32, 0x7e, 0x47, 0x61, 0xa1, 0x91, 0x83, 0x49, 0xfa, 0xd9, 0x35, 0xe4, 0x0c, 0x91, 0x11, 0x8a, 0x14, 0x03,
0x11, 0x50, 0x8e, 0x60, 0x23, 0x44, 0xe8, 0x7f, 0xcc, 0x42, 0x29, 0xa6, 0x8a, 0x20, 0xb9, 0xa6, 0x0c, 0xda, 0x95, 0xde, 0xd9, 0xcc, 0xe1, 0xa6, 0x98, 0x27, 0x65, 0x53, 0xae, 0x09, 0x7b, 0x4a,
0x01, 0x33, 0x6d, 0x3a, 0xa6, 0xbe, 0xeb, 0x3a, 0x61, 0x8e, 0xab, 0x82, 0x78, 0x19, 0xd2, 0x44, 0xfa, 0x29, 0x3a, 0xff, 0x61, 0x05, 0xf5, 0x1a, 0x54, 0x46, 0xee, 0xb7, 0xcc, 0x89, 0xb3, 0xf1,
0x09, 0x8b, 0xde, 0x31, 0xa5, 0xc1, 0x14, 0xad, 0xa3, 0x1a, 0xe5, 0x90, 0x76, 0x46, 0x83, 0x29, 0x0b, 0xa8, 0x46, 0x84, 0x50, 0xc5, 0x63, 0xd8, 0xe2, 0x82, 0x12, 0xa6, 0xff, 0xb2, 0xce, 0x5f,
0xf9, 0x08, 0xb4, 0x08, 0xe2, 0xf9, 0xcc, 0xb2, 0x45, 0xe7, 0x93, 0xfd, 0xb9, 0x16, 0xd2, 0x07, 0x04, 0x94, 0x0b, 0xb0, 0x11, 0x22, 0xf4, 0xdf, 0x67, 0xa1, 0x14, 0x53, 0x31, 0x48, 0xae, 0x69,
0x21, 0x59, 0x14, 0x78, 0x99, 0x64, 0xa6, 0x47, 0xad, 0x89, 0x69, 0x0b, 0x2b, 0xca, 0x79, 0xb5, 0xc0, 0x4c, 0x9b, 0x8e, 0xa9, 0xef, 0xba, 0x4e, 0x58, 0x04, 0x54, 0x24, 0x5e, 0x86, 0x34, 0xac,
0x2a, 0xe9, 0x03, 0x6a, 0x4d, 0x2e, 0x03, 0xca, 0xc9, 0x67, 0xf0, 0x2c, 0x31, 0xd4, 0x26, 0xe0, 0x71, 0x91, 0x1e, 0x33, 0x1a, 0xcc, 0x84, 0x75, 0x54, 0xa3, 0x1c, 0xd2, 0xce, 0x69, 0x30, 0x23,
0x32, 0x8b, 0x89, 0x1f, 0x4f, 0xb5, 0xf1, 0x91, 0xe7, 0xa0, 0x8a, 0x8e, 0x61, 0x8e, 0x7d, 0x46, 0x1f, 0x82, 0x16, 0x41, 0x3c, 0x9f, 0x59, 0x36, 0xb6, 0x46, 0xd9, 0xc0, 0x6b, 0x21, 0x7d, 0x10,
0x39, 0x9b, 0x84, 0x79, 0x5c, 0x16, 0xb4, 0xb6, 0x24, 0x91, 0x3a, 0x14, 0xd8, 0x9d, 0x67, 0xf9, 0x92, 0xb1, 0x03, 0xc8, 0x2c, 0x34, 0x3d, 0x6a, 0x4d, 0x4c, 0x1b, 0xad, 0x28, 0x07, 0xda, 0xaa,
0x6c, 0x82, 0x1d, 0xa3, 0x68, 0x44, 0x9f, 0xe2, 0x70, 0xc0, 0x5d, 0x9f, 0xde, 0x32, 0xd3, 0xa1, 0xa4, 0x0f, 0xa8, 0x35, 0xb9, 0x0c, 0x28, 0x27, 0x9f, 0xc2, 0x93, 0xc4, 0xd4, 0x9b, 0x80, 0xcb,
0x36, 0x0b, 0x47, 0x94, 0x72, 0x48, 0xeb, 0x51, 0x9b, 0xe9, 0xef, 0xc0, 0xee, 0xd7, 0x8c, 0x5f, 0x34, 0x27, 0x7e, 0x3c, 0xf6, 0xc6, 0x47, 0x9e, 0x81, 0x8a, 0x2d, 0xc5, 0x1c, 0xfb, 0x8c, 0x72,
0x58, 0xaf, 0xe7, 0xd6, 0xc4, 0xe2, 0xf7, 0x03, 0xea, 0xd3, 0x45, 0x15, 0xfc, 0x97, 0x02, 0x5b, 0x36, 0x09, 0x13, 0xbd, 0x8c, 0xb4, 0xb6, 0x24, 0x91, 0x3a, 0x14, 0xd8, 0xad, 0x67, 0xf9, 0x6c,
0x69, 0x16, 0xe3, 0xcc, 0x17, 0x1d, 0x28, 0xef, 0xcf, 0x67, 0x2c, 0xf2, 0xce, 0xa2, 0x63, 0xc6, 0x22, 0x5a, 0x4a, 0xd1, 0x88, 0x3e, 0xf1, 0x70, 0xc0, 0x5d, 0x9f, 0x4e, 0x99, 0xe9, 0x50, 0x9b,
0x60, 0x63, 0x3e, 0x63, 0x86, 0x04, 0x91, 0xaf, 0x60, 0x6f, 0x11, 0x62, 0xbe, 0xe8, 0x81, 0x01, 0x85, 0x33, 0x4c, 0x39, 0xa4, 0xf5, 0xa8, 0xcd, 0xf4, 0x77, 0x60, 0xff, 0x2b, 0xc6, 0x2f, 0xac,
0xe5, 0xa6, 0xc7, 0x7c, 0xf3, 0x8d, 0xe8, 0xf4, 0x68, 0x7d, 0xcc, 0x4a, 0x19, 0x6d, 0x06, 0xe5, 0xef, 0x16, 0xd6, 0xc4, 0xe2, 0x77, 0x03, 0xea, 0xd3, 0x65, 0x99, 0xfc, 0x97, 0x02, 0x3b, 0x69,
0x22, 0xe2, 0x06, 0xcc, 0x7f, 0x25, 0xd8, 0xe4, 0x43, 0xd0, 0x92, 0xa3, 0xa2, 0xe9, 0x79, 0x36, 0x16, 0xe3, 0xcc, 0xc7, 0x16, 0x95, 0xf7, 0x17, 0x73, 0x16, 0x79, 0x67, 0xd9, 0x52, 0x63, 0xb0,
0x7a, 0x42, 0x89, 0xab, 0x99, 0xb0, 0x97, 0x67, 0x93, 0x4f, 0x41, 0xec, 0x07, 0x66, 0xca, 0xc2, 0xb1, 0x98, 0x33, 0x43, 0x82, 0xc8, 0x97, 0x70, 0xb0, 0x0c, 0x31, 0x1f, 0x9b, 0x64, 0x40, 0xb9,
0x9e, 0x1d, 0x26, 0xbd, 0x90, 0xb1, 0x58, 0x1a, 0x04, 0xfc, 0x0b, 0x68, 0xac, 0x5e, 0x36, 0xf0, 0xe9, 0x31, 0xdf, 0x7c, 0x83, 0xa3, 0x80, 0xb0, 0xbe, 0xc8, 0x4a, 0x19, 0x6d, 0x06, 0xe5, 0x18,
0x54, 0x1e, 0x4f, 0x6d, 0xaf, 0x58, 0x38, 0xc4, 0xd9, 0xf4, 0x46, 0x21, 0x3c, 0xb8, 0x81, 0xf8, 0x71, 0x03, 0xe6, 0xbf, 0x46, 0x36, 0xf9, 0x00, 0xb4, 0xe4, 0x2c, 0x69, 0x7a, 0x9e, 0x2d, 0x3c,
0xc5, 0x46, 0x21, 0x72, 0xe6, 0x23, 0xd8, 0x4c, 0x8d, 0xb0, 0x08, 0x2c, 0x20, 0xb0, 0x9a, 0x18, 0xa1, 0xc4, 0xe5, 0x0e, 0xed, 0xe5, 0xd9, 0xe4, 0x13, 0xc0, 0x05, 0xc2, 0x4c, 0x59, 0xd8, 0xb3,
0x63, 0xe3, 0xf4, 0x5a, 0x1e, 0xff, 0x8b, 0xab, 0xc7, 0xff, 0x23, 0xd8, 0x8a, 0x06, 0x97, 0x6b, 0xc3, 0xa4, 0x47, 0x19, 0xcb, 0xad, 0x02, 0xe1, 0x9f, 0x43, 0x63, 0xfd, 0x36, 0x22, 0x4e, 0xe5,
0x3a, 0xfe, 0xce, 0xbd, 0xb9, 0x31, 0x03, 0x36, 0xc6, 0xa2, 0xac, 0x18, 0x9b, 0x21, 0xeb, 0xa5, 0xc5, 0xa9, 0xdd, 0x35, 0x1b, 0x09, 0x9e, 0x4d, 0xaf, 0x1c, 0xe8, 0xc1, 0x2d, 0x81, 0x5f, 0xae,
0xe4, 0x0c, 0xd9, 0x58, 0x4c, 0xd2, 0x74, 0xce, 0x5d, 0x33, 0xda, 0x5c, 0xb0, 0x1b, 0x17, 0x8d, 0x1c, 0x98, 0x33, 0x1f, 0xc2, 0x76, 0x6a, 0xc6, 0x15, 0xc0, 0x82, 0x00, 0x56, 0x13, 0x73, 0x6e,
0xb2, 0x20, 0x86, 0x7b, 0x9d, 0xb0, 0x1d, 0x62, 0xc4, 0x62, 0x73, 0x3d, 0x9f, 0xdc, 0x32, 0x59, 0x9c, 0x5e, 0xab, 0xfb, 0x41, 0x71, 0xfd, 0x7e, 0xf0, 0x02, 0x76, 0xa2, 0xc9, 0xe6, 0x9a, 0x8e,
0x36, 0xca, 0xd2, 0x76, 0x82, 0xd5, 0x9f, 0xf3, 0x97, 0xc8, 0x10, 0xea, 0xfe, 0x00, 0x76, 0x1f, 0xbf, 0x75, 0x6f, 0x6e, 0xcc, 0x80, 0x8d, 0x45, 0xd5, 0x56, 0x8c, 0xed, 0x90, 0xf5, 0x52, 0x72,
0xc0, 0x39, 0xf5, 0x39, 0x2a, 0xa2, 0xe2, 0xa1, 0x67, 0xe9, 0x43, 0x82, 0x2b, 0x94, 0xf9, 0x18, 0x86, 0x6c, 0x8c, 0xa3, 0x36, 0x5d, 0x70, 0xd7, 0x8c, 0x56, 0x1b, 0xd1, 0xae, 0x8b, 0x46, 0x19,
0x08, 0x9e, 0x14, 0x86, 0xb1, 0x1c, 0xf3, 0x66, 0x66, 0xdd, 0x4e, 0x39, 0x4e, 0x23, 0x8a, 0x51, 0x89, 0xe1, 0xea, 0x88, 0xb6, 0x13, 0x18, 0xdc, 0x7c, 0xae, 0x17, 0x93, 0x29, 0x93, 0x65, 0xa3,
0x13, 0x9c, 0x4b, 0x7a, 0xd7, 0x75, 0x4e, 0x91, 0xac, 0xff, 0x29, 0x03, 0x95, 0x54, 0x48, 0x61, 0x2c, 0x6d, 0x87, 0xac, 0xfe, 0x82, 0xbf, 0x14, 0x0c, 0x7c, 0xee, 0x0f, 0x60, 0xff, 0x1e, 0x9c,
0x69, 0x91, 0xdb, 0x95, 0x19, 0xf6, 0x6f, 0xc5, 0x28, 0x85, 0x94, 0xee, 0x84, 0x1c, 0x85, 0x43, 0x53, 0x9f, 0x8b, 0x87, 0xa8, 0xe2, 0xd0, 0x93, 0xf4, 0x21, 0xe4, 0xe2, 0x63, 0x3e, 0x02, 0x22,
0x62, 0x16, 0x27, 0xb9, 0xc6, 0xea, 0xb8, 0x4c, 0x4c, 0x8b, 0x9f, 0x02, 0xb1, 0x9c, 0xb1, 0x6b, 0x4e, 0xa2, 0x61, 0x2c, 0xc7, 0xbc, 0x99, 0x5b, 0xd3, 0x19, 0x17, 0xe3, 0x8a, 0x62, 0xd4, 0x90,
0x0b, 0xcf, 0xf3, 0xa9, 0xcf, 0x82, 0xa9, 0x3b, 0x9b, 0x60, 0x74, 0x55, 0x8c, 0xcd, 0x88, 0x33, 0x73, 0x49, 0x6f, 0xbb, 0xce, 0x99, 0x20, 0xeb, 0x7f, 0xc8, 0x40, 0x25, 0x15, 0x52, 0xa2, 0xb4,
0x8a, 0x18, 0x02, 0x1e, 0x2f, 0x74, 0x0b, 0xb8, 0x22, 0xe1, 0x11, 0x27, 0x86, 0xeb, 0xdf, 0xc2, 0xc8, 0xf5, 0xcb, 0x0c, 0x1b, 0xbc, 0x62, 0x94, 0x42, 0x4a, 0x77, 0x42, 0x5e, 0x84, 0x53, 0x64,
0xee, 0x70, 0x5d, 0x6e, 0x91, 0x2f, 0x01, 0xbc, 0x38, 0xa3, 0xf0, 0x25, 0xe5, 0xe3, 0xbd, 0x87, 0x56, 0x8c, 0x7a, 0x8d, 0xf5, 0x71, 0x99, 0x18, 0x27, 0x3f, 0x01, 0x62, 0x39, 0x63, 0xd7, 0x46,
0x0a, 0x2f, 0xb2, 0xce, 0x48, 0xe0, 0xf5, 0x3d, 0x68, 0xac, 0x12, 0x2d, 0xcb, 0xa7, 0xfe, 0x0c, 0xcf, 0xf3, 0x99, 0xcf, 0x82, 0x99, 0x3b, 0x9f, 0x88, 0xe8, 0xaa, 0x18, 0xdb, 0x11, 0x67, 0x14,
0xb6, 0x86, 0xf3, 0xdb, 0x5b, 0xb6, 0x34, 0x47, 0x9d, 0xc3, 0xd3, 0x34, 0x39, 0xac, 0xb6, 0xc7, 0x31, 0x10, 0x1e, 0x6f, 0x7c, 0x4b, 0xb8, 0x22, 0xe1, 0x11, 0x27, 0x86, 0xeb, 0xdf, 0xc0, 0xfe,
0x50, 0x8c, 0x63, 0x43, 0x66, 0xf4, 0xce, 0x42, 0x91, 0xd4, 0xe2, 0x6f, 0x14, 0xc2, 0x15, 0xf7, 0x70, 0x53, 0x6e, 0x91, 0x2f, 0x00, 0xbc, 0x38, 0xa3, 0x84, 0x26, 0xe5, 0x93, 0x83, 0xfb, 0x0f,
0xf0, 0x05, 0x14, 0xa3, 0xc9, 0x9b, 0xa8, 0x50, 0xbc, 0xe8, 0xf7, 0x07, 0x66, 0xff, 0x6a, 0xa4, 0x5e, 0x66, 0x9d, 0x91, 0xc0, 0xeb, 0x07, 0xd0, 0x58, 0x27, 0x5a, 0x96, 0x4f, 0xfd, 0x09, 0xec,
0x3d, 0x21, 0x65, 0x28, 0xe0, 0x57, 0xb7, 0xa7, 0x65, 0x0e, 0x03, 0x28, 0xc5, 0x83, 0x37, 0xa9, 0x0c, 0x17, 0xd3, 0x29, 0x5b, 0x19, 0xb4, 0x5e, 0xc1, 0xe3, 0x34, 0x39, 0xac, 0xb6, 0x27, 0x50,
0x40, 0xa9, 0xdb, 0xeb, 0x8e, 0xba, 0xad, 0x51, 0xe7, 0x44, 0x7b, 0x42, 0x9e, 0xc1, 0xe6, 0xc0, 0x8c, 0x63, 0x43, 0x66, 0xf4, 0xde, 0xf2, 0x21, 0xa9, 0xdf, 0x16, 0x8c, 0x42, 0xb8, 0x03, 0x1f,
0xe8, 0x74, 0x2f, 0x5b, 0x5f, 0x77, 0x4c, 0xa3, 0xf3, 0xaa, 0xd3, 0xba, 0xe8, 0x9c, 0x68, 0x19, 0x3f, 0x87, 0x62, 0x34, 0x9a, 0x13, 0x15, 0x8a, 0x17, 0xfd, 0xfe, 0xc0, 0xec, 0x5f, 0x8d, 0xb4,
0x42, 0xa0, 0x7a, 0x36, 0xba, 0x68, 0x9b, 0x83, 0xab, 0x97, 0x17, 0xdd, 0xe1, 0x59, 0xe7, 0x44, 0x47, 0xa4, 0x0c, 0x05, 0xf1, 0xd5, 0xed, 0x69, 0x99, 0xe3, 0x00, 0x4a, 0xf1, 0x64, 0x4e, 0x2a,
0xcb, 0x0a, 0x99, 0xc3, 0xab, 0x76, 0xbb, 0x33, 0x1c, 0x6a, 0x39, 0x02, 0xb0, 0x71, 0xda, 0xea, 0x50, 0xea, 0xf6, 0xba, 0xa3, 0x6e, 0x6b, 0xd4, 0x39, 0xd5, 0x1e, 0x91, 0x27, 0xb0, 0x3d, 0x30,
0x0a, 0xb0, 0x42, 0xb6, 0xa0, 0xd6, 0xed, 0xbd, 0xea, 0x77, 0xdb, 0x1d, 0x73, 0xd8, 0x19, 0x8d, 0x3a, 0xdd, 0xcb, 0xd6, 0x57, 0x1d, 0xd3, 0xe8, 0xbc, 0xee, 0xb4, 0x2e, 0x3a, 0xa7, 0x5a, 0x86,
0x04, 0x31, 0x7f, 0xf8, 0xcf, 0x0c, 0x54, 0x52, 0xb3, 0x3b, 0xd9, 0x81, 0x2d, 0x71, 0xe4, 0xca, 0x10, 0xa8, 0x9e, 0x8f, 0x2e, 0xda, 0xe6, 0xe0, 0xea, 0xe5, 0x45, 0x77, 0x78, 0xde, 0x39, 0xd5,
0x10, 0x37, 0xb5, 0x86, 0xfd, 0x9e, 0xd9, 0xeb, 0xf7, 0x3a, 0xda, 0x13, 0xf2, 0x0e, 0xec, 0x2c, 0xb2, 0x28, 0x73, 0x78, 0xd5, 0x6e, 0x77, 0x86, 0x43, 0x2d, 0x47, 0x00, 0xb6, 0xce, 0x5a, 0x5d,
0x31, 0xfa, 0xa7, 0xa7, 0xed, 0xb3, 0x96, 0x50, 0x9e, 0x34, 0x60, 0x7b, 0x89, 0x39, 0xea, 0x5e, 0x04, 0x2b, 0x64, 0x07, 0x6a, 0xdd, 0xde, 0xeb, 0x7e, 0xb7, 0xdd, 0x31, 0x87, 0x9d, 0xd1, 0x08,
0x76, 0xc4, 0x2b, 0xb3, 0x64, 0x1f, 0xf6, 0x96, 0x78, 0xc3, 0x6f, 0x3a, 0x9d, 0x41, 0x8c, 0xc8, 0x89, 0xf9, 0xe3, 0x7f, 0x64, 0xa0, 0x92, 0x1a, 0xee, 0xc9, 0x1e, 0xec, 0xe0, 0x91, 0x2b, 0x03,
0x91, 0x17, 0xf0, 0x7c, 0x09, 0xd1, 0xed, 0x0d, 0xaf, 0x4e, 0x4f, 0xbb, 0xed, 0x6e, 0xa7, 0x37, 0x6f, 0x6a, 0x0d, 0xfb, 0x3d, 0xb3, 0xd7, 0xef, 0x75, 0xb4, 0x47, 0xe4, 0x1d, 0xd8, 0x5b, 0x61,
0x32, 0x5f, 0xb5, 0x2e, 0xae, 0x3a, 0x9a, 0x42, 0xf6, 0xa0, 0xbe, 0x7c, 0x49, 0xe7, 0x72, 0xd0, 0xf4, 0xcf, 0xce, 0xda, 0xe7, 0x2d, 0x7c, 0x3c, 0x69, 0xc0, 0xee, 0x0a, 0x73, 0xd4, 0xbd, 0xec,
0x37, 0x5a, 0xc6, 0xb7, 0x5a, 0x9e, 0xbc, 0x0f, 0xef, 0x3d, 0x10, 0xd2, 0xee, 0x1b, 0x46, 0xa7, 0xa0, 0x96, 0x59, 0x72, 0x08, 0x07, 0x2b, 0xbc, 0xe1, 0xd7, 0x9d, 0xce, 0x20, 0x46, 0xe4, 0xc8,
0x3d, 0x32, 0x5b, 0x97, 0xfd, 0xab, 0xde, 0x48, 0xdb, 0x38, 0x6c, 0x8a, 0xf9, 0x78, 0x29, 0xc0, 0x73, 0x78, 0xb6, 0x82, 0xe8, 0xf6, 0x86, 0x57, 0x67, 0x67, 0xdd, 0x76, 0xb7, 0xd3, 0x1b, 0x99,
0x85, 0xc9, 0xae, 0x7a, 0x3f, 0xee, 0xf5, 0xbf, 0xe9, 0x69, 0x4f, 0x84, 0xe5, 0x47, 0x67, 0x46, 0xaf, 0x5b, 0x17, 0x57, 0x1d, 0x4d, 0x21, 0x07, 0x50, 0x5f, 0xbd, 0xa4, 0x73, 0x39, 0xe8, 0x1b,
0x67, 0x78, 0xd6, 0xbf, 0x38, 0xd1, 0x32, 0xc7, 0x7f, 0x2b, 0xc9, 0xdd, 0xac, 0x8d, 0xff, 0x06, 0x2d, 0xe3, 0x1b, 0x2d, 0x4f, 0xde, 0x83, 0xa7, 0xf7, 0x84, 0xb4, 0xfb, 0x86, 0xd1, 0x69, 0x8f,
0x11, 0x03, 0x0a, 0x51, 0x1d, 0x58, 0xe7, 0xf8, 0xc6, 0xb3, 0xd4, 0x7c, 0x1d, 0x47, 0xda, 0xce, 0xcc, 0xd6, 0x65, 0xff, 0xaa, 0x37, 0xd2, 0xb6, 0x8e, 0x9b, 0x38, 0x40, 0xaf, 0x04, 0x38, 0x9a,
0xaf, 0xfe, 0xfa, 0xf7, 0xdf, 0x66, 0x37, 0x75, 0xb5, 0xf9, 0xe6, 0xb3, 0xa6, 0x40, 0x34, 0xdd, 0xec, 0xaa, 0xf7, 0xe3, 0x5e, 0xff, 0xeb, 0x9e, 0xf6, 0x08, 0x2d, 0x3f, 0x3a, 0x37, 0x3a, 0xc3,
0x39, 0xff, 0x22, 0x73, 0x48, 0xfa, 0xb0, 0x21, 0xff, 0x03, 0x20, 0xdb, 0x29, 0x91, 0xf1, 0x9f, 0xf3, 0xfe, 0xc5, 0xa9, 0x96, 0x39, 0xf9, 0x6b, 0x49, 0x2e, 0x6f, 0x6d, 0xf1, 0x83, 0x13, 0x31,
0x02, 0xeb, 0x24, 0x6e, 0xa3, 0x44, 0x4d, 0x2f, 0xc7, 0x12, 0x2d, 0x47, 0x08, 0xfc, 0x21, 0x14, 0xa0, 0x10, 0xd5, 0x81, 0x4d, 0x8e, 0x6f, 0x3c, 0x49, 0x0d, 0xe0, 0x71, 0xa4, 0xed, 0xfd, 0xea,
0xc2, 0x0d, 0x33, 0xa1, 0x64, 0x7a, 0xe7, 0x6c, 0xac, 0x5a, 0x02, 0xfe, 0x3f, 0x43, 0x7e, 0x0a, 0x2f, 0x7f, 0xfb, 0x6d, 0x76, 0x5b, 0x57, 0x9b, 0x6f, 0x3e, 0x6d, 0x22, 0xa2, 0xe9, 0x2e, 0xf8,
0xa5, 0x78, 0x7f, 0x20, 0xbb, 0x89, 0x1c, 0x4b, 0xe7, 0x47, 0xa3, 0xb1, 0x8a, 0x95, 0x56, 0x8b, 0xe7, 0x99, 0x63, 0xd2, 0x87, 0x2d, 0xf9, 0x23, 0x01, 0xd9, 0x4d, 0x89, 0x8c, 0x7f, 0x35, 0xd8,
0x54, 0x63, 0xb5, 0x70, 0xb7, 0x20, 0x57, 0x32, 0x0f, 0xc4, 0x6e, 0x41, 0xea, 0xa9, 0xeb, 0x13, 0x24, 0x71, 0x57, 0x48, 0xd4, 0xf4, 0x72, 0x2c, 0xd1, 0x72, 0x50, 0xe0, 0x0f, 0xa1, 0x10, 0xae,
0xeb, 0xc6, 0x4a, 0xc5, 0xf4, 0x06, 0x8a, 0x7c, 0x4a, 0x48, 0x4a, 0x64, 0xf3, 0x7b, 0x6b, 0xf2, 0xa0, 0x89, 0x47, 0xa6, 0x97, 0xd2, 0xc6, 0xba, 0x2d, 0xe1, 0xff, 0x33, 0xe4, 0xa7, 0x50, 0x8a,
0x73, 0xf2, 0x33, 0x50, 0x43, 0x07, 0xe0, 0x06, 0x40, 0x16, 0xc6, 0x4a, 0xae, 0x29, 0x8d, 0xc5, 0x17, 0x0c, 0xb2, 0x9f, 0xc8, 0xb1, 0x74, 0x7e, 0x34, 0x1a, 0xeb, 0x58, 0xe9, 0x67, 0x91, 0x6a,
0x63, 0x96, 0x77, 0x85, 0x15, 0xd2, 0xdd, 0x39, 0x6f, 0x72, 0x94, 0x76, 0x1d, 0x4b, 0xc7, 0xc9, 0xfc, 0x2c, 0xb1, 0x7c, 0x90, 0x2b, 0x99, 0x07, 0xb8, 0x7c, 0x90, 0x7a, 0xea, 0xfa, 0xc4, 0x3e,
0x32, 0x21, 0x3d, 0x39, 0xa3, 0xa7, 0xa5, 0xa7, 0x66, 0x50, 0x7d, 0x1f, 0xa5, 0x37, 0x48, 0x3d, 0xb2, 0xf6, 0x61, 0x7a, 0x43, 0x88, 0x7c, 0x4c, 0x48, 0x4a, 0x64, 0xf3, 0x7b, 0x6b, 0xf2, 0x73,
0x25, 0xfd, 0xb5, 0xc0, 0x34, 0xbf, 0xa7, 0x36, 0x17, 0x2f, 0xa8, 0x8a, 0xc1, 0x02, 0x5d, 0xfe, 0xf2, 0x33, 0x50, 0x43, 0x07, 0x88, 0x15, 0x81, 0x2c, 0x8d, 0x95, 0xdc, 0x63, 0x1a, 0x4b, 0x65,
0xe8, 0x1b, 0x16, 0x56, 0x5b, 0xda, 0xb8, 0xf4, 0x5d, 0xbc, 0x64, 0x8b, 0x6c, 0x26, 0x42, 0x21, 0x56, 0x97, 0x89, 0x35, 0xd2, 0xdd, 0x05, 0x6f, 0x72, 0x21, 0xed, 0x3a, 0x96, 0x2e, 0x26, 0xcb,
0x7e, 0xc1, 0x42, 0xfa, 0xa3, 0x6f, 0x48, 0x4a, 0x4f, 0x3f, 0xe1, 0x3d, 0x94, 0xbe, 0x4b, 0x76, 0x84, 0xf4, 0xe4, 0x10, 0x9f, 0x96, 0x9e, 0x9a, 0x41, 0xf5, 0x43, 0x21, 0xbd, 0x41, 0xea, 0x29,
0x92, 0xd2, 0x93, 0x2f, 0xf8, 0x16, 0x2a, 0xe2, 0x8e, 0x68, 0xb4, 0x0c, 0x12, 0x91, 0x9c, 0x9a, 0xe9, 0xdf, 0x21, 0xa6, 0xf9, 0x3d, 0xb5, 0x39, 0x6a, 0x50, 0xc5, 0xc1, 0x42, 0xb8, 0xfc, 0x41,
0x5f, 0x1b, 0x3b, 0x0f, 0xe8, 0xe9, 0xec, 0x20, 0x35, 0xbc, 0x22, 0xa0, 0xbc, 0x29, 0x67, 0x56, 0x1d, 0x96, 0x56, 0x5b, 0x59, 0xc9, 0xf4, 0x7d, 0x71, 0xc9, 0x0e, 0xd9, 0x4e, 0x84, 0x42, 0xac,
0xc2, 0x81, 0x3c, 0x9c, 0xba, 0x88, 0x1e, 0xcb, 0x59, 0x3b, 0x92, 0x35, 0x1e, 0x6d, 0x11, 0xfa, 0xc1, 0x52, 0xfa, 0x83, 0x3a, 0x24, 0xa5, 0xa7, 0x55, 0x78, 0x2a, 0xa4, 0xef, 0x93, 0xbd, 0xa4,
0x1e, 0x5e, 0xb8, 0x4d, 0x9e, 0xe2, 0x85, 0x11, 0xa0, 0xe9, 0x49, 0xf9, 0xbf, 0x00, 0x32, 0x7c, 0xf4, 0xa4, 0x06, 0xdf, 0x40, 0x05, 0xef, 0x88, 0x46, 0xcb, 0x20, 0x11, 0xc9, 0xa9, 0xf9, 0xb5,
0xec, 0xd6, 0xb5, 0xcd, 0xaa, 0xf1, 0xfe, 0xa3, 0x98, 0xb4, 0x41, 0xf5, 0x95, 0x97, 0x8b, 0x14, 0xb1, 0x77, 0x8f, 0x9e, 0xce, 0x0e, 0x52, 0x13, 0x57, 0x04, 0x94, 0x37, 0xe5, 0xcc, 0x4a, 0x38,
0x66, 0xa0, 0x26, 0xfb, 0x0f, 0x59, 0xbc, 0x65, 0x45, 0xb7, 0x6a, 0xbc, 0xbb, 0x86, 0x1b, 0xde, 0x90, 0xfb, 0x53, 0x17, 0xd1, 0x63, 0x39, 0x1b, 0x47, 0xb2, 0xc6, 0x83, 0x2d, 0x42, 0x3f, 0x10,
0x56, 0xc7, 0xdb, 0x08, 0xd1, 0xc4, 0x6d, 0x62, 0x70, 0x68, 0x06, 0x12, 0x76, 0xbd, 0x81, 0x7f, 0x17, 0xee, 0x92, 0xc7, 0xe2, 0xc2, 0x08, 0xd0, 0xf4, 0xa4, 0xfc, 0x5f, 0x00, 0x19, 0x3e, 0x74,
0x5b, 0x7f, 0xfe, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x45, 0xeb, 0x2a, 0xdc, 0xed, 0x16, 0x00, 0xeb, 0xc6, 0x66, 0xd5, 0x78, 0xef, 0x41, 0x4c, 0xda, 0xa0, 0xfa, 0xda, 0xcb, 0x31, 0x85, 0x19,
0x00, 0xa8, 0xc9, 0xfe, 0x43, 0x96, 0xba, 0xac, 0xe9, 0x56, 0x8d, 0x77, 0x37, 0x70, 0xc3, 0xdb, 0xea,
0xe2, 0x36, 0x42, 0x34, 0xbc, 0x0d, 0x07, 0x87, 0x66, 0x20, 0x61, 0xd7, 0x5b, 0xe2, 0x97, 0xf1,
0xcf, 0xfe, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x75, 0x93, 0x1d, 0x50, 0x17, 0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

@ -153,6 +153,16 @@ message LoopOutRequest {
*/ */
string dest = 2; string dest = 2;
/*
Exact amount in satoshis to send to 'dest'. Optional.
*/
int64 dest_amount = 15;
/*
Base58 encoded change address for the swap.
*/
string change_addr = 16;
/* /*
Maximum off-chain fee in sat that may be paid for swap payment to the server. Maximum off-chain fee in sat that may be paid for swap payment to the server.
This limit is applied during path finding. Typically this value is taken This limit is applied during path finding. Typically this value is taken
@ -589,6 +599,11 @@ message QuoteRequest {
*/ */
int64 amt = 1; int64 amt = 1;
/*
If change output will be added. This is needed to send exact amount.
*/
bool with_change = 5;
/* /*
The confirmation target that should be used either for the sweep of the The confirmation target that should be used either for the sweep of the
on-chain HTLC broadcast by the swap server in the case of a Loop Out, or for on-chain HTLC broadcast by the swap server in the case of a Loop Out, or for

@ -148,6 +148,14 @@
"type": "string", "type": "string",
"format": "int64" "format": "int64"
}, },
{
"name": "with_change",
"description": "If change output will be added. This is needed to send exact amount.",
"in": "query",
"required": false,
"type": "boolean",
"format": "boolean"
},
{ {
"name": "conf_target", "name": "conf_target",
"description": "The confirmation target that should be used either for the sweep of the\non-chain HTLC broadcast by the swap server in the case of a Loop Out, or for\nthe confirmation of the on-chain HTLC broadcast by the swap client in the\ncase of a Loop In.", "description": "The confirmation target that should be used either for the sweep of the\non-chain HTLC broadcast by the swap server in the case of a Loop Out, or for\nthe confirmation of the on-chain HTLC broadcast by the swap client in the\ncase of a Loop In.",
@ -261,6 +269,14 @@
"type": "string", "type": "string",
"format": "int64" "format": "int64"
}, },
{
"name": "with_change",
"description": "If change output will be added. This is needed to send exact amount.",
"in": "query",
"required": false,
"type": "boolean",
"format": "boolean"
},
{ {
"name": "conf_target", "name": "conf_target",
"description": "The confirmation target that should be used either for the sweep of the\non-chain HTLC broadcast by the swap server in the case of a Loop Out, or for\nthe confirmation of the on-chain HTLC broadcast by the swap client in the\ncase of a Loop In.", "description": "The confirmation target that should be used either for the sweep of the\non-chain HTLC broadcast by the swap server in the case of a Loop Out, or for\nthe confirmation of the on-chain HTLC broadcast by the swap client in the\ncase of a Loop In.",
@ -615,6 +631,15 @@
"type": "string", "type": "string",
"description": "Base58 encoded destination address for the swap." "description": "Base58 encoded destination address for the swap."
}, },
"dest_amount": {
"type": "string",
"format": "int64",
"description": "Exact amount in satoshis to send to 'dest'. Optional."
},
"change_addr": {
"type": "string",
"description": "Base58 encoded change address for the swap."
},
"max_swap_routing_fee": { "max_swap_routing_fee": {
"type": "string", "type": "string",
"format": "int64", "format": "int64",

@ -16,6 +16,12 @@ This file tracks release notes for the loop client.
#### NewFeatures #### NewFeatures
* When making `loop out`, users can request exact amount to be delivered
to destination address. The user provides options `--dest_amt` and `--change_addr`
for that. In this case exactly `--dest_amt` is delivered to `--addr` and the change
amount goes to `--change_addr`. The following must be true: `amt >= max_miner_fee + dest_amt`.
This mode of operation is useful to pay to merchants expecting an exact amount for a purchase.
#### Breaking Changes #### Breaking Changes
#### Bug Fixes #### Bug Fixes

@ -25,8 +25,9 @@ func (s *Sweeper) CreateSweepTx(
htlc *swap.Htlc, htlcOutpoint wire.OutPoint, htlc *swap.Htlc, htlcOutpoint wire.OutPoint,
keyBytes [33]byte, keyBytes [33]byte,
witnessFunc func(sig []byte) (wire.TxWitness, error), witnessFunc func(sig []byte) (wire.TxWitness, error),
amount, fee btcutil.Amount, amount, destAmount, feeOnlyDest, feeOnlyChange, feeBoth btcutil.Amount,
destAddr btcutil.Address) (*wire.MsgTx, error) { destAddr, changeAddr btcutil.Address,
warnf func(message string, args ...interface{})) (*wire.MsgTx, error) {
// Compose tx. // Compose tx.
sweepTx := wire.NewMsgTx(2) sweepTx := wire.NewMsgTx(2)
@ -40,16 +41,28 @@ func (s *Sweeper) CreateSweepTx(
Sequence: sequence, Sequence: sequence,
}) })
// Add output for the destination address. destinations, err := deduceDestinations(amount, destAmount,
sweepPkScript, err := txscript.PayToAddrScript(destAddr) feeOnlyDest, feeOnlyChange, feeBoth,
destAddr, changeAddr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(destinations) == 1 && destinations[0].addr == changeAddr {
warnf("Not sufficient coin size to send to destAddr, so sending to changeAddr. amount=%s, destination=destAmount, fee=%s. This must be a bug.", amount, destAmount, feeOnlyDest)
}
sweepTx.AddTxOut(&wire.TxOut{ // Add outputs.
PkScript: sweepPkScript, for _, dst := range destinations {
Value: int64(amount - fee), sweepPkScript, err := txscript.PayToAddrScript(dst.addr)
}) if err != nil {
return nil, err
}
sweepTx.AddTxOut(&wire.TxOut{
PkScript: sweepPkScript,
Value: int64(dst.amount),
})
}
// Generate a signature for the swap htlc transaction. // Generate a signature for the swap htlc transaction.
@ -92,18 +105,49 @@ func (s *Sweeper) CreateSweepTx(
// estimator. // estimator.
func (s *Sweeper) GetSweepFee(ctx context.Context, func (s *Sweeper) GetSweepFee(ctx context.Context,
addInputEstimate func(*input.TxWeightEstimator), addInputEstimate func(*input.TxWeightEstimator),
destAddr btcutil.Address, sweepConfTarget int32) ( destAddr, changeAddr btcutil.Address, sweepConfTarget int32) (
btcutil.Amount, error) { feeOnlyDest, feeOnlyChange, feeBoth btcutil.Amount, err error) {
// Get fee estimate from lnd. // Get fee estimate from lnd.
feeRate, err := s.Lnd.WalletKit.EstimateFee(ctx, sweepConfTarget) feeRate, err := s.Lnd.WalletKit.EstimateFee(ctx, sweepConfTarget)
if err != nil { if err != nil {
return 0, fmt.Errorf("estimate fee: %v", err) return 0, 0, 0, fmt.Errorf("estimate fee: %v", err)
} }
// Calculate weight for this tx. // Calculate feeOnlyDest.
var weightEstimate input.TxWeightEstimator var estOnlyDest input.TxWeightEstimator
switch destAddr.(type) { if err := addOutput(&estOnlyDest, destAddr); err != nil {
return 0, 0, 0, err
}
addInputEstimate(&estOnlyDest)
feeOnlyDest = feeRate.FeeForWeight(int64(estOnlyDest.Weight()))
if changeAddr != nil {
// Calculate feeOnlyChange.
var estOnlyChange input.TxWeightEstimator
if err := addOutput(&estOnlyChange, changeAddr); err != nil {
return 0, 0, 0, err
}
addInputEstimate(&estOnlyChange)
feeOnlyChange = feeRate.FeeForWeight(int64(estOnlyChange.Weight()))
// Calculate feeBoth.
var estBoth input.TxWeightEstimator
if err := addOutput(&estBoth, destAddr); err != nil {
return 0, 0, 0, err
}
if err := addOutput(&estBoth, changeAddr); err != nil {
return 0, 0, 0, err
}
addInputEstimate(&estBoth)
feeBoth = feeRate.FeeForWeight(int64(estBoth.Weight()))
}
return feeOnlyDest, feeOnlyChange, feeBoth, nil
}
func addOutput(weightEstimate *input.TxWeightEstimator, addr btcutil.Address) error {
switch addr.(type) {
case *btcutil.AddressWitnessScriptHash: case *btcutil.AddressWitnessScriptHash:
weightEstimate.AddP2WSHOutput() weightEstimate.AddP2WSHOutput()
case *btcutil.AddressWitnessPubKeyHash: case *btcutil.AddressWitnessPubKeyHash:
@ -113,11 +157,78 @@ func (s *Sweeper) GetSweepFee(ctx context.Context,
case *btcutil.AddressPubKeyHash: case *btcutil.AddressPubKeyHash:
weightEstimate.AddP2PKHOutput() weightEstimate.AddP2PKHOutput()
default: default:
return 0, fmt.Errorf("unknown address type %T", destAddr) return fmt.Errorf("unknown address type %T", addr)
} }
return nil
}
const dustOutput = 2020 // FIXME: find the actual value.
addInputEstimate(&weightEstimate) type destination struct {
weight := weightEstimate.Weight() addr btcutil.Address
amount btcutil.Amount
}
func deduceDestinations(amount, destAmount, feeOnlyDest, feeOnlyChange, feeBoth btcutil.Amount,
destAddr, changeAddr btcutil.Address) ([]destination, error) {
return feeRate.FeeForWeight(int64(weight)), nil if (destAmount != 0) != (changeAddr != nil) {
return nil, fmt.Errorf("provide either both destAmount and changeAddr or none of them")
}
if (feeOnlyChange != 0) != (changeAddr != nil) {
return nil, fmt.Errorf("provide either both feeOnlyChange and changeAddr or none of them")
}
if (feeBoth != 0) != (changeAddr != nil) {
return nil, fmt.Errorf("provide either both feeBoth and changeAddr or none of them")
}
if changeAddr == nil {
// No change. Just put everything on the main destination address.
return []destination{
{
addr: destAddr,
amount: amount - feeOnlyDest,
},
}, nil
}
changeAmount := amount - destAmount - feeBoth
if changeAmount > dustOutput {
// Change is large enough. Return tx with 2 outputs.
return []destination{
{
addr: destAddr,
amount: destAmount,
},
{
addr: changeAddr,
amount: changeAmount,
},
}, nil
}
// If we are here, then changeAmount is below dustOutput so we drop the change.
if amount-destAmount >= feeOnlyDest {
// We still can send destAmount to destAddr.
return []destination{
{
addr: destAddr,
amount: destAmount,
},
}, nil
}
// We can not send destAmount to destAddr. This must be a bug, because we
// checked in swapClientServer.LoopOut that amt >= max_miner_fee + dest_amt.
// However it is better to send everything to change address than to crash.
// The warning is logged by CreateSweepTx.
return []destination{
{
addr: changeAddr,
amount: amount - feeOnlyChange,
},
}, nil
} }

@ -0,0 +1,184 @@
package sweep
import (
"reflect"
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
)
func TestDeduceDestinations(t *testing.T) {
wpkh := [20]byte{}
addr1, err := btcutil.NewAddressWitnessPubKeyHash(wpkh[:], &chaincfg.MainNetParams)
if err != nil {
t.Fatal(err)
}
wsh := [32]byte{}
addr2, err := btcutil.NewAddressWitnessScriptHash(wsh[:], &chaincfg.MainNetParams)
if err != nil {
t.Fatal(err)
}
testCases := []struct {
name string
amount, destAmount, feeOnlyDest, feeOnlyChange, feeBoth btcutil.Amount
destAddr, changeAddr btcutil.Address
wantedDestinations []destination
wantError bool
}{
{
name: "missing changeAddr",
amount: 1000000,
destAmount: 950000,
feeOnlyDest: 150,
feeOnlyChange: 150,
feeBoth: 200,
destAddr: addr1,
changeAddr: nil, // Error!
wantError: true,
},
{
name: "missing feeOnlyChange",
amount: 1000000,
destAmount: 950000,
feeOnlyDest: 150,
feeOnlyChange: 0, // Error!
feeBoth: 200,
destAddr: addr1,
changeAddr: addr2,
wantError: true,
},
{
name: "missing feeBoth",
amount: 1000000,
destAmount: 950000,
feeOnlyDest: 150,
feeOnlyChange: 150,
feeBoth: 0, // Error!
destAddr: addr1,
changeAddr: addr2,
wantError: true,
},
{
name: "exact amount was not requested",
amount: 1000000,
feeOnlyDest: 150,
destAddr: addr1,
wantedDestinations: []destination{
{
addr: addr1,
amount: 1000000 - 150,
},
},
},
{
name: "exact amount and change",
amount: 1000000,
destAmount: 950000,
feeOnlyDest: 150,
feeOnlyChange: 160,
feeBoth: 200,
destAddr: addr1,
changeAddr: addr2,
wantedDestinations: []destination{
{
addr: addr1,
amount: 950000,
},
{
addr: addr2,
amount: 1000000 - 950000 - 200,
},
},
},
{
name: "fee for both outputs is too large, but can send to destAddr",
amount: 1000000,
destAmount: 950000,
feeOnlyDest: 50000,
feeOnlyChange: 51000,
feeBoth: 70000,
destAddr: addr1,
changeAddr: addr2,
wantedDestinations: []destination{
{
addr: addr1,
amount: 950000,
},
},
},
{
name: "fee for both outputs does not left room for change output",
amount: 1000000,
destAmount: 950000,
feeOnlyDest: 45000,
feeOnlyChange: 46000,
feeBoth: 50000,
destAddr: addr1,
changeAddr: addr2,
wantedDestinations: []destination{
{
addr: addr1,
amount: 950000,
},
},
},
{
name: "fee for both outputs makes change output below dust",
amount: 1000000,
destAmount: 950000,
feeOnlyDest: 45000,
feeOnlyChange: 46000,
feeBoth: 49990,
destAddr: addr1,
changeAddr: addr2,
wantedDestinations: []destination{
{
addr: addr1,
amount: 950000,
},
},
},
{
name: "fee for both and for dest is too large, send everything to change address",
amount: 1000000,
destAmount: 950000,
feeOnlyDest: 51000,
feeOnlyChange: 52000,
feeBoth: 53000,
destAddr: addr1,
changeAddr: addr2,
wantedDestinations: []destination{
{
addr: addr2,
amount: 948000,
},
},
},
}
for _, tc := range testCases {
gotDestinations, err := deduceDestinations(
tc.amount, tc.destAmount, tc.feeOnlyDest, tc.feeOnlyChange, tc.feeBoth,
tc.destAddr, tc.changeAddr,
)
if tc.wantError {
if err == nil {
t.Errorf("case %q: wanted error, but there is no error", tc.name)
}
continue
}
if err != nil {
t.Errorf("case %q: failed: %v", tc.name, err)
continue
}
if !reflect.DeepEqual(tc.wantedDestinations, gotDestinations) {
t.Errorf("case %q: wanted %v, got %v", tc.name, tc.wantedDestinations, gotDestinations)
}
}
}
Loading…
Cancel
Save