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
}
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,

@ -43,6 +43,18 @@ 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; 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{
Name: "htlc_confs",
Usage: "the number of of confirmations, in blocks " +
@ -128,6 +140,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("change_addr")
}
client, cleanup, err := getClient(ctx)
if err != nil {
return err
@ -152,6 +177,7 @@ func loopOut(ctx *cli.Context) error {
Amt: int64(amt),
ConfTarget: sweepConfTarget,
SwapPublicationDeadline: uint64(swapDeadline.Unix()),
WithChange: changeAddr != "",
}
quote, err := client.LoopOutQuote(context.Background(), quoteReq)
if err != nil {
@ -185,6 +211,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),

@ -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 " +
@ -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()),
}

@ -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
@ -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

@ -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.
if err := labels.Validate(in.Label); err != nil {
return nil, err
@ -89,6 +111,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),
@ -410,6 +434,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,

@ -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
@ -205,6 +213,21 @@ func deserializeLoopOutContract(value []byte, chainParams *chaincfg.Params) (
}
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
}
@ -294,5 +317,16 @@ func serializeLoopOutContract(swap *LoopOutContract) (
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
}

@ -37,6 +37,7 @@ var (
migrateSwapPublicationDeadline,
migrateLastHop,
migrateUpdates,
migrateExactAmount,
}
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
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 {
@ -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, s.log.Warnf,
)
if err != nil {
return err

@ -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,
@ -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
@ -936,7 +942,10 @@ 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,
s.log.Warnf,
)
if err != nil {
return err
@ -955,6 +964,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())
@ -975,6 +989,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

@ -208,6 +208,12 @@ type LoopOutRequest struct {
//Base58 encoded destination address for the swap.
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.
//This limit is applied during path finding. Typically this value is taken
//from the response of the GetQuote call.
@ -323,6 +329,20 @@ func (m *LoopOutRequest) GetDest() string {
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 {
if m != nil {
return m.MaxSwapRoutingFee
@ -1128,6 +1148,9 @@ type QuoteRequest struct {
//The amount to swap in satoshis.
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
//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
@ -1181,6 +1204,13 @@ func (m *QuoteRequest) GetAmt() int64 {
return 0
}
func (m *QuoteRequest) GetWithChange() bool {
if m != nil {
return m.WithChange
}
return false
}
func (m *QuoteRequest) GetConfTarget() int32 {
if m != nil {
return m.ConfTarget
@ -2005,157 +2035,159 @@ func init() {
func init() { proto.RegisterFile("client.proto", fileDescriptor_014de31d7ac8c57c) }
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,
0xf5, 0x5f, 0x49, 0x94, 0x25, 0x3d, 0x51, 0x12, 0x3d, 0xde, 0xb5, 0x65, 0xc5, 0x41, 0xbc, 0x4c,
0xf6, 0x1b, 0xc7, 0x49, 0xac, 0x6f, 0x9c, 0x4b, 0x1b, 0x24, 0x05, 0xb4, 0xb2, 0x1c, 0xcb, 0xb5,
0x25, 0x95, 0x92, 0x37, 0x48, 0x51, 0x80, 0x18, 0x4b, 0x63, 0x8b, 0x88, 0xf8, 0x63, 0xc9, 0xd1,
0xae, 0x8d, 0xa0, 0x2d, 0x50, 0xa0, 0xe7, 0x1e, 0xfa, 0x1f, 0xf4, 0xde, 0x5b, 0x6f, 0xed, 0x9f,
0xd0, 0x53, 0x7b, 0xec, 0xb5, 0x97, 0x1e, 0xfa, 0x27, 0x14, 0x28, 0xe6, 0x0d, 0x49, 0x91, 0xb2,
0xe4, 0xa2, 0x87, 0xde, 0xc4, 0xf7, 0x3e, 0xf3, 0xe6, 0xcd, 0xfb, 0xfd, 0x04, 0xea, 0x78, 0x66,
0x31, 0x87, 0x1f, 0x79, 0xbe, 0xcb, 0x5d, 0x52, 0x98, 0xb9, 0xae, 0xe7, 0x7b, 0xe3, 0xc6, 0xde,
0xad, 0xeb, 0xde, 0xce, 0x58, 0x93, 0x7a, 0x56, 0x93, 0x3a, 0x8e, 0xcb, 0x29, 0xb7, 0x5c, 0x27,
0x90, 0x30, 0xfd, 0xf7, 0x0a, 0x54, 0x2f, 0x5c, 0xd7, 0xeb, 0xcf, 0xb9, 0xc1, 0x5e, 0xcf, 0x59,
0xc0, 0x89, 0x06, 0x39, 0x6a, 0xf3, 0x7a, 0x66, 0x3f, 0x73, 0x90, 0x33, 0xc4, 0x4f, 0x42, 0x40,
0x99, 0xb0, 0x80, 0xd7, 0xb3, 0xfb, 0x99, 0x83, 0x92, 0x81, 0xbf, 0x49, 0x13, 0x9e, 0xda, 0xf4,
0xce, 0x0c, 0xde, 0x52, 0xcf, 0xf4, 0xdd, 0x39, 0xb7, 0x9c, 0x5b, 0xf3, 0x86, 0xb1, 0x7a, 0x0e,
0x8f, 0x6d, 0xda, 0xf4, 0x6e, 0xf8, 0x96, 0x7a, 0x86, 0xe4, 0x9c, 0x32, 0x46, 0x3e, 0x87, 0x6d,
0x71, 0xc0, 0xf3, 0x99, 0x47, 0xef, 0x53, 0x47, 0x14, 0x3c, 0xb2, 0x65, 0xd3, 0xbb, 0x01, 0x32,
0x13, 0x87, 0xf6, 0x41, 0x8d, 0x6f, 0x11, 0xd0, 0x3c, 0x42, 0x21, 0x94, 0x2e, 0x10, 0x1f, 0x40,
0x35, 0x21, 0x56, 0x28, 0xbe, 0x81, 0x18, 0x35, 0x16, 0xd7, 0xb2, 0x39, 0xd1, 0xa1, 0x22, 0x50,
0xb6, 0xe5, 0x30, 0x1f, 0x05, 0x15, 0x10, 0x54, 0xb6, 0xe9, 0xdd, 0xa5, 0xa0, 0x09, 0x49, 0x9f,
0x80, 0x26, 0x6c, 0x66, 0xba, 0x73, 0x6e, 0x8e, 0xa7, 0xd4, 0x71, 0xd8, 0xac, 0x5e, 0xdc, 0xcf,
0x1c, 0x28, 0x2f, 0xb3, 0xf5, 0x8c, 0x51, 0x9d, 0x49, 0x2b, 0xb5, 0x25, 0x87, 0x1c, 0xc2, 0xa6,
0x3b, 0xe7, 0xb7, 0xae, 0x78, 0x84, 0x40, 0x9b, 0x01, 0xe3, 0xf5, 0xf2, 0x7e, 0xee, 0x40, 0x31,
0x6a, 0x11, 0x43, 0x60, 0x87, 0x8c, 0x0b, 0x6c, 0xf0, 0x96, 0x31, 0xcf, 0x1c, 0xbb, 0xce, 0x8d,
0xc9, 0xa9, 0x7f, 0xcb, 0x78, 0xbd, 0xb4, 0x9f, 0x39, 0xc8, 0x1b, 0x35, 0x64, 0xb4, 0x5d, 0xe7,
0x66, 0x84, 0x64, 0xf2, 0x29, 0x90, 0x29, 0x9f, 0x8d, 0x11, 0x6a, 0xf9, 0xb6, 0x74, 0x56, 0xbd,
0x82, 0xe0, 0x4d, 0xc1, 0x69, 0x27, 0x19, 0xe4, 0x0b, 0xd8, 0x45, 0xe3, 0x78, 0xf3, 0xeb, 0x99,
0x35, 0x46, 0xa2, 0x39, 0x61, 0x74, 0x32, 0xb3, 0x1c, 0x56, 0x07, 0xa1, 0xbd, 0xb1, 0x23, 0x00,
0x83, 0x05, 0xff, 0x24, 0x64, 0x93, 0xa7, 0x90, 0x9f, 0xd1, 0x6b, 0x36, 0xab, 0xab, 0xe8, 0x57,
0xf9, 0x41, 0xf6, 0xa0, 0x64, 0x39, 0x16, 0xb7, 0x28, 0x77, 0xfd, 0x7a, 0x15, 0x39, 0x0b, 0x82,
0xfe, 0xeb, 0x2c, 0x54, 0x44, 0xbc, 0x74, 0x9d, 0xf5, 0xe1, 0xb2, 0xec, 0xb4, 0xec, 0x03, 0xa7,
0x3d, 0x70, 0x47, 0xee, 0xa1, 0x3b, 0x76, 0xa1, 0x38, 0xa3, 0x01, 0x37, 0xa7, 0xae, 0x87, 0x11,
0xa2, 0x1a, 0x05, 0xf1, 0x7d, 0xe6, 0x7a, 0xe4, 0x7d, 0xa8, 0xb0, 0x3b, 0xce, 0x7c, 0x87, 0xce,
0x4c, 0x61, 0x12, 0x0c, 0x8b, 0xa2, 0xa1, 0x46, 0xc4, 0x33, 0x3e, 0x1b, 0x93, 0x03, 0xd0, 0x62,
0x43, 0x46, 0x36, 0xdf, 0x40, 0x33, 0x56, 0x23, 0x33, 0x86, 0x26, 0x8f, 0xed, 0x50, 0x58, 0x6b,
0x87, 0xe2, 0xb2, 0x1d, 0xfe, 0x91, 0x01, 0x15, 0x03, 0x9c, 0x05, 0x9e, 0xeb, 0x04, 0x8c, 0x10,
0xc8, 0x5a, 0x13, 0xb4, 0x42, 0x09, 0xe3, 0x25, 0x6b, 0x4d, 0xc4, 0x13, 0xac, 0x89, 0x79, 0x7d,
0xcf, 0x59, 0x80, 0x2f, 0x54, 0x8d, 0x82, 0x35, 0x79, 0x29, 0x3e, 0xc9, 0x0b, 0x50, 0x51, 0x3b,
0x3a, 0x99, 0xf8, 0x2c, 0x08, 0x64, 0x6a, 0xe1, 0xc1, 0xb2, 0xa0, 0xb7, 0x24, 0x99, 0x1c, 0xc1,
0x56, 0x12, 0x66, 0x3a, 0xde, 0xf1, 0xdb, 0x60, 0x8a, 0xf6, 0x28, 0xc9, 0x70, 0x08, 0x91, 0x3d,
0x64, 0x90, 0x4f, 0xc2, 0xe8, 0x89, 0xf0, 0x12, 0x9e, 0x47, 0xb8, 0x96, 0x80, 0x0f, 0x10, 0xfd,
0x02, 0xaa, 0x01, 0xf3, 0xdf, 0x30, 0xdf, 0xb4, 0x59, 0x10, 0xd0, 0x5b, 0x86, 0x06, 0x2a, 0x19,
0x15, 0x49, 0xbd, 0x94, 0x44, 0x5d, 0x83, 0xea, 0xa5, 0xeb, 0x58, 0xdc, 0xf5, 0x43, 0x9f, 0xeb,
0x7f, 0x50, 0x00, 0xc4, 0xeb, 0x87, 0x9c, 0xf2, 0x79, 0xb0, 0xb2, 0x62, 0x08, 0x6b, 0x64, 0xd7,
0x5a, 0xa3, 0xbc, 0x6c, 0x0d, 0x85, 0xdf, 0x7b, 0x32, 0x0c, 0xaa, 0xc7, 0x9b, 0x47, 0x61, 0xed,
0x3a, 0x12, 0x77, 0x8c, 0xee, 0x3d, 0x66, 0x20, 0x9b, 0x1c, 0x40, 0x3e, 0xe0, 0x94, 0xcb, 0x8a,
0x51, 0x3d, 0x26, 0x29, 0x9c, 0xd0, 0x85, 0x19, 0x12, 0x40, 0xbe, 0x82, 0xea, 0x0d, 0xb5, 0x66,
0x73, 0x9f, 0x99, 0x3e, 0xa3, 0x81, 0xeb, 0x60, 0x24, 0x57, 0x8f, 0xb7, 0xe3, 0x23, 0xa7, 0x92,
0x6d, 0x20, 0xd7, 0xa8, 0xdc, 0x24, 0x3f, 0xc9, 0x87, 0x50, 0x0b, 0x5d, 0x2d, 0xf2, 0x89, 0x5b,
0x76, 0x54, 0x79, 0xaa, 0x0b, 0xf2, 0xc8, 0xb2, 0x85, 0x46, 0x1a, 0x06, 0xe9, 0xdc, 0x9b, 0x50,
0xce, 0x24, 0x52, 0xd6, 0x9f, 0xaa, 0xa0, 0x5f, 0x21, 0x19, 0x91, 0xcb, 0x0e, 0x2f, 0xac, 0x76,
0xf8, 0x6a, 0x07, 0xaa, 0x6b, 0x1c, 0xb8, 0x26, 0x3c, 0x2a, 0xeb, 0xc2, 0xe3, 0x3d, 0x28, 0x8f,
0xdd, 0x80, 0x9b, 0xd2, 0xbf, 0x18, 0xd5, 0x39, 0x03, 0x04, 0x69, 0x88, 0x14, 0xf2, 0x1c, 0x54,
0x04, 0xb8, 0xce, 0x78, 0x4a, 0x2d, 0x07, 0x8b, 0x54, 0xce, 0xc0, 0x43, 0x7d, 0x49, 0x12, 0xc9,
0x27, 0x21, 0x37, 0x37, 0x12, 0x03, 0xb2, 0xde, 0x22, 0x26, 0xa4, 0x2d, 0x52, 0xaa, 0x96, 0x48,
0x29, 0x9d, 0x80, 0x76, 0x61, 0x05, 0x5c, 0x78, 0x2b, 0x88, 0x42, 0xe9, 0x47, 0xb0, 0x99, 0xa0,
0x85, 0xc9, 0xf4, 0x11, 0xe4, 0x45, 0xf5, 0x08, 0xea, 0x99, 0xfd, 0xdc, 0x41, 0xf9, 0x78, 0xeb,
0x81, 0xa3, 0xe7, 0x81, 0x21, 0x11, 0xfa, 0x73, 0xa8, 0x09, 0x62, 0xd7, 0xb9, 0x71, 0xa3, 0x8a,
0x54, 0x8d, 0x53, 0x51, 0x15, 0x81, 0xa7, 0x57, 0x41, 0x1d, 0x31, 0xdf, 0x8e, 0xaf, 0xfc, 0x25,
0xd4, 0xba, 0x4e, 0x48, 0x09, 0x2f, 0xfc, 0x3f, 0xa8, 0xd9, 0x96, 0x23, 0x4b, 0x16, 0xb5, 0xdd,
0xb9, 0xc3, 0x43, 0x87, 0x57, 0x6c, 0xcb, 0x11, 0xf2, 0x5b, 0x48, 0x44, 0x5c, 0x54, 0xda, 0x42,
0xdc, 0x46, 0x88, 0x93, 0xd5, 0x4d, 0xe2, 0xce, 0x95, 0x62, 0x46, 0xcb, 0x9e, 0x2b, 0xc5, 0xac,
0x96, 0x3b, 0x57, 0x8a, 0x39, 0x4d, 0x39, 0x57, 0x8a, 0x8a, 0x96, 0x3f, 0x57, 0x8a, 0x05, 0xad,
0xa8, 0xff, 0x39, 0x03, 0x5a, 0x7f, 0xce, 0xff, 0xa7, 0x2a, 0x60, 0x63, 0xb4, 0x1c, 0x73, 0x3c,
0xe3, 0x6f, 0xcc, 0x09, 0x9b, 0x71, 0x8a, 0xee, 0xce, 0x1b, 0xaa, 0x6d, 0x39, 0xed, 0x19, 0x7f,
0x73, 0x22, 0x68, 0x51, 0xfb, 0x4c, 0xa0, 0x4a, 0x21, 0x8a, 0xde, 0xc5, 0xa8, 0xff, 0xf0, 0x9c,
0xdf, 0x65, 0x40, 0xfd, 0xc9, 0xdc, 0xe5, 0x6c, 0x7d, 0x4b, 0xc0, 0xc0, 0x5b, 0xd4, 0xe1, 0x2c,
0xde, 0x01, 0xe3, 0x45, 0x0d, 0x7e, 0x50, 0xd2, 0x73, 0x2b, 0x4a, 0xfa, 0xa3, 0xcd, 0x4e, 0x79,
0xb4, 0xd9, 0xe9, 0xbf, 0xc9, 0x08, 0xaf, 0x87, 0x6a, 0x86, 0x26, 0xdf, 0x07, 0x35, 0x6a, 0x52,
0x66, 0x40, 0x23, 0x85, 0x21, 0x90, 0x5d, 0x6a, 0x48, 0x71, 0xca, 0xc1, 0x04, 0xc3, 0x1b, 0x83,
0x69, 0x8c, 0x0c, 0xa7, 0x1c, 0xc1, 0x1b, 0x48, 0x56, 0x78, 0xe0, 0x5d, 0x80, 0x84, 0x2d, 0xf3,
0xf8, 0xce, 0xd2, 0x38, 0x61, 0x48, 0x69, 0x42, 0x45, 0xcb, 0xeb, 0x7f, 0x91, 0x51, 0xf0, 0xdf,
0xaa, 0xf4, 0x01, 0x54, 0x17, 0xc3, 0x0e, 0x62, 0x64, 0x7f, 0x55, 0xbd, 0x68, 0xda, 0x11, 0xa8,
0x8f, 0xc3, 0x3a, 0x22, 0xe7, 0x8e, 0xb4, 0xda, 0x35, 0xc1, 0x19, 0x0a, 0x46, 0x28, 0x12, 0xe7,
0x13, 0x61, 0x57, 0x7a, 0x6f, 0x33, 0x87, 0x9b, 0x38, 0xec, 0xc9, 0x9e, 0x5b, 0x43, 0x7b, 0x4a,
0xfa, 0x89, 0xf0, 0xed, 0xe3, 0x0f, 0xd4, 0x6b, 0x50, 0x19, 0xb9, 0xdf, 0x31, 0x27, 0x4e, 0xb6,
0x2f, 0xa1, 0x1a, 0x11, 0xc2, 0x27, 0x1e, 0xc2, 0x06, 0x47, 0x4a, 0x98, 0xdd, 0x8b, 0x32, 0x7e,
0x11, 0x50, 0x8e, 0x60, 0x23, 0x44, 0xe8, 0x7f, 0xcc, 0x42, 0x29, 0xa6, 0x8a, 0x20, 0xb9, 0xa6,
0x01, 0x33, 0x6d, 0x3a, 0xa6, 0xbe, 0xeb, 0x3a, 0x61, 0x8e, 0xab, 0x82, 0x78, 0x19, 0xd2, 0x44,
0x09, 0x8b, 0xde, 0x31, 0xa5, 0xc1, 0x14, 0xad, 0xa3, 0x1a, 0xe5, 0x90, 0x76, 0x46, 0x83, 0x29,
0xf9, 0x08, 0xb4, 0x08, 0xe2, 0xf9, 0xcc, 0xb2, 0x45, 0xe7, 0x93, 0xfd, 0xb9, 0x16, 0xd2, 0x07,
0x21, 0x59, 0x14, 0x78, 0x99, 0x64, 0xa6, 0x47, 0xad, 0x89, 0x69, 0x0b, 0x2b, 0xca, 0x79, 0xb5,
0x2a, 0xe9, 0x03, 0x6a, 0x4d, 0x2e, 0x03, 0xca, 0xc9, 0x67, 0xf0, 0x2c, 0x31, 0xd4, 0x26, 0xe0,
0x32, 0x8b, 0x89, 0x1f, 0x4f, 0xb5, 0xf1, 0x91, 0xe7, 0xa0, 0x8a, 0x8e, 0x61, 0x8e, 0x7d, 0x46,
0x39, 0x9b, 0x84, 0x79, 0x5c, 0x16, 0xb4, 0xb6, 0x24, 0x91, 0x3a, 0x14, 0xd8, 0x9d, 0x67, 0xf9,
0x6c, 0x82, 0x1d, 0xa3, 0x68, 0x44, 0x9f, 0xe2, 0x70, 0xc0, 0x5d, 0x9f, 0xde, 0x32, 0xd3, 0xa1,
0x36, 0x0b, 0x47, 0x94, 0x72, 0x48, 0xeb, 0x51, 0x9b, 0xe9, 0xef, 0xc0, 0xee, 0xd7, 0x8c, 0x5f,
0x58, 0xaf, 0xe7, 0xd6, 0xc4, 0xe2, 0xf7, 0x03, 0xea, 0xd3, 0x45, 0x15, 0xfc, 0x97, 0x02, 0x5b,
0x69, 0x16, 0xe3, 0xcc, 0x17, 0x1d, 0x28, 0xef, 0xcf, 0x67, 0x2c, 0xf2, 0xce, 0xa2, 0x63, 0xc6,
0x60, 0x63, 0x3e, 0x63, 0x86, 0x04, 0x91, 0xaf, 0x60, 0x6f, 0x11, 0x62, 0xbe, 0xe8, 0x81, 0x01,
0xe5, 0xa6, 0xc7, 0x7c, 0xf3, 0x8d, 0xe8, 0xf4, 0x68, 0x7d, 0xcc, 0x4a, 0x19, 0x6d, 0x06, 0xe5,
0x22, 0xe2, 0x06, 0xcc, 0x7f, 0x25, 0xd8, 0xe4, 0x43, 0xd0, 0x92, 0xa3, 0xa2, 0xe9, 0x79, 0x36,
0x7a, 0x42, 0x89, 0xab, 0x99, 0xb0, 0x97, 0x67, 0x93, 0x4f, 0x41, 0xec, 0x07, 0x66, 0xca, 0xc2,
0x9e, 0x1d, 0x26, 0xbd, 0x90, 0xb1, 0x58, 0x1a, 0x04, 0xfc, 0x0b, 0x68, 0xac, 0x5e, 0x36, 0xf0,
0x54, 0x1e, 0x4f, 0x6d, 0xaf, 0x58, 0x38, 0xc4, 0xd9, 0xf4, 0x46, 0x21, 0x3c, 0xb8, 0x81, 0xf8,
0xc5, 0x46, 0x21, 0x72, 0xe6, 0x23, 0xd8, 0x4c, 0x8d, 0xb0, 0x08, 0x2c, 0x20, 0xb0, 0x9a, 0x18,
0x63, 0xe3, 0xf4, 0x5a, 0x1e, 0xff, 0x8b, 0xab, 0xc7, 0xff, 0x23, 0xd8, 0x8a, 0x06, 0x97, 0x6b,
0x3a, 0xfe, 0xce, 0xbd, 0xb9, 0x31, 0x03, 0x36, 0xc6, 0xa2, 0xac, 0x18, 0x9b, 0x21, 0xeb, 0xa5,
0xe4, 0x0c, 0xd9, 0x58, 0x4c, 0xd2, 0x74, 0xce, 0x5d, 0x33, 0xda, 0x5c, 0xb0, 0x1b, 0x17, 0x8d,
0xb2, 0x20, 0x86, 0x7b, 0x9d, 0xb0, 0x1d, 0x62, 0xc4, 0x62, 0x73, 0x3d, 0x9f, 0xdc, 0x32, 0x59,
0x36, 0xca, 0xd2, 0x76, 0x82, 0xd5, 0x9f, 0xf3, 0x97, 0xc8, 0x10, 0xea, 0xfe, 0x00, 0x76, 0x1f,
0xc0, 0x39, 0xf5, 0x39, 0x2a, 0xa2, 0xe2, 0xa1, 0x67, 0xe9, 0x43, 0x82, 0x2b, 0x94, 0xf9, 0x18,
0x08, 0x9e, 0x14, 0x86, 0xb1, 0x1c, 0xf3, 0x66, 0x66, 0xdd, 0x4e, 0x39, 0x4e, 0x23, 0x8a, 0x51,
0x13, 0x9c, 0x4b, 0x7a, 0xd7, 0x75, 0x4e, 0x91, 0xac, 0xff, 0x29, 0x03, 0x95, 0x54, 0x48, 0x61,
0x69, 0x91, 0xdb, 0x95, 0x19, 0xf6, 0x6f, 0xc5, 0x28, 0x85, 0x94, 0xee, 0x84, 0x1c, 0x85, 0x43,
0x62, 0x16, 0x27, 0xb9, 0xc6, 0xea, 0xb8, 0x4c, 0x4c, 0x8b, 0x9f, 0x02, 0xb1, 0x9c, 0xb1, 0x6b,
0x0b, 0xcf, 0xf3, 0xa9, 0xcf, 0x82, 0xa9, 0x3b, 0x9b, 0x60, 0x74, 0x55, 0x8c, 0xcd, 0x88, 0x33,
0x8a, 0x18, 0x02, 0x1e, 0x2f, 0x74, 0x0b, 0xb8, 0x22, 0xe1, 0x11, 0x27, 0x86, 0xeb, 0xdf, 0xc2,
0xee, 0x70, 0x5d, 0x6e, 0x91, 0x2f, 0x01, 0xbc, 0x38, 0xa3, 0xf0, 0x25, 0xe5, 0xe3, 0xbd, 0x87,
0x0a, 0x2f, 0xb2, 0xce, 0x48, 0xe0, 0xf5, 0x3d, 0x68, 0xac, 0x12, 0x2d, 0xcb, 0xa7, 0xfe, 0x0c,
0xb6, 0x86, 0xf3, 0xdb, 0x5b, 0xb6, 0x34, 0x47, 0x9d, 0xc3, 0xd3, 0x34, 0x39, 0xac, 0xb6, 0xc7,
0x50, 0x8c, 0x63, 0x43, 0x66, 0xf4, 0xce, 0x42, 0x91, 0xd4, 0xe2, 0x6f, 0x14, 0xc2, 0x15, 0xf7,
0xf0, 0x05, 0x14, 0xa3, 0xc9, 0x9b, 0xa8, 0x50, 0xbc, 0xe8, 0xf7, 0x07, 0x66, 0xff, 0x6a, 0xa4,
0x3d, 0x21, 0x65, 0x28, 0xe0, 0x57, 0xb7, 0xa7, 0x65, 0x0e, 0x03, 0x28, 0xc5, 0x83, 0x37, 0xa9,
0x40, 0xa9, 0xdb, 0xeb, 0x8e, 0xba, 0xad, 0x51, 0xe7, 0x44, 0x7b, 0x42, 0x9e, 0xc1, 0xe6, 0xc0,
0xe8, 0x74, 0x2f, 0x5b, 0x5f, 0x77, 0x4c, 0xa3, 0xf3, 0xaa, 0xd3, 0xba, 0xe8, 0x9c, 0x68, 0x19,
0x42, 0xa0, 0x7a, 0x36, 0xba, 0x68, 0x9b, 0x83, 0xab, 0x97, 0x17, 0xdd, 0xe1, 0x59, 0xe7, 0x44,
0xcb, 0x0a, 0x99, 0xc3, 0xab, 0x76, 0xbb, 0x33, 0x1c, 0x6a, 0x39, 0x02, 0xb0, 0x71, 0xda, 0xea,
0x0a, 0xb0, 0x42, 0xb6, 0xa0, 0xd6, 0xed, 0xbd, 0xea, 0x77, 0xdb, 0x1d, 0x73, 0xd8, 0x19, 0x8d,
0x04, 0x31, 0x7f, 0xf8, 0xcf, 0x0c, 0x54, 0x52, 0xb3, 0x3b, 0xd9, 0x81, 0x2d, 0x71, 0xe4, 0xca,
0x10, 0x37, 0xb5, 0x86, 0xfd, 0x9e, 0xd9, 0xeb, 0xf7, 0x3a, 0xda, 0x13, 0xf2, 0x0e, 0xec, 0x2c,
0x31, 0xfa, 0xa7, 0xa7, 0xed, 0xb3, 0x96, 0x50, 0x9e, 0x34, 0x60, 0x7b, 0x89, 0x39, 0xea, 0x5e,
0x76, 0xc4, 0x2b, 0xb3, 0x64, 0x1f, 0xf6, 0x96, 0x78, 0xc3, 0x6f, 0x3a, 0x9d, 0x41, 0x8c, 0xc8,
0x91, 0x17, 0xf0, 0x7c, 0x09, 0xd1, 0xed, 0x0d, 0xaf, 0x4e, 0x4f, 0xbb, 0xed, 0x6e, 0xa7, 0x37,
0x32, 0x5f, 0xb5, 0x2e, 0xae, 0x3a, 0x9a, 0x42, 0xf6, 0xa0, 0xbe, 0x7c, 0x49, 0xe7, 0x72, 0xd0,
0x37, 0x5a, 0xc6, 0xb7, 0x5a, 0x9e, 0xbc, 0x0f, 0xef, 0x3d, 0x10, 0xd2, 0xee, 0x1b, 0x46, 0xa7,
0x3d, 0x32, 0x5b, 0x97, 0xfd, 0xab, 0xde, 0x48, 0xdb, 0x38, 0x6c, 0x8a, 0xf9, 0x78, 0x29, 0xc0,
0x85, 0xc9, 0xae, 0x7a, 0x3f, 0xee, 0xf5, 0xbf, 0xe9, 0x69, 0x4f, 0x84, 0xe5, 0x47, 0x67, 0x46,
0x67, 0x78, 0xd6, 0xbf, 0x38, 0xd1, 0x32, 0xc7, 0x7f, 0x2b, 0xc9, 0xdd, 0xac, 0x8d, 0xff, 0x06,
0x11, 0x03, 0x0a, 0x51, 0x1d, 0x58, 0xe7, 0xf8, 0xc6, 0xb3, 0xd4, 0x7c, 0x1d, 0x47, 0xda, 0xce,
0xaf, 0xfe, 0xfa, 0xf7, 0xdf, 0x66, 0x37, 0x75, 0xb5, 0xf9, 0xe6, 0xb3, 0xa6, 0x40, 0x34, 0xdd,
0x39, 0xff, 0x22, 0x73, 0x48, 0xfa, 0xb0, 0x21, 0xff, 0x03, 0x20, 0xdb, 0x29, 0x91, 0xf1, 0x9f,
0x02, 0xeb, 0x24, 0x6e, 0xa3, 0x44, 0x4d, 0x2f, 0xc7, 0x12, 0x2d, 0x47, 0x08, 0xfc, 0x21, 0x14,
0xc2, 0x0d, 0x33, 0xa1, 0x64, 0x7a, 0xe7, 0x6c, 0xac, 0x5a, 0x02, 0xfe, 0x3f, 0x43, 0x7e, 0x0a,
0xa5, 0x78, 0x7f, 0x20, 0xbb, 0x89, 0x1c, 0x4b, 0xe7, 0x47, 0xa3, 0xb1, 0x8a, 0x95, 0x56, 0x8b,
0x54, 0x63, 0xb5, 0x70, 0xb7, 0x20, 0x57, 0x32, 0x0f, 0xc4, 0x6e, 0x41, 0xea, 0xa9, 0xeb, 0x13,
0xeb, 0xc6, 0x4a, 0xc5, 0xf4, 0x06, 0x8a, 0x7c, 0x4a, 0x48, 0x4a, 0x64, 0xf3, 0x7b, 0x6b, 0xf2,
0x73, 0xf2, 0x33, 0x50, 0x43, 0x07, 0xe0, 0x06, 0x40, 0x16, 0xc6, 0x4a, 0xae, 0x29, 0x8d, 0xc5,
0x63, 0x96, 0x77, 0x85, 0x15, 0xd2, 0xdd, 0x39, 0x6f, 0x72, 0x94, 0x76, 0x1d, 0x4b, 0xc7, 0xc9,
0x32, 0x21, 0x3d, 0x39, 0xa3, 0xa7, 0xa5, 0xa7, 0x66, 0x50, 0x7d, 0x1f, 0xa5, 0x37, 0x48, 0x3d,
0x25, 0xfd, 0xb5, 0xc0, 0x34, 0xbf, 0xa7, 0x36, 0x17, 0x2f, 0xa8, 0x8a, 0xc1, 0x02, 0x5d, 0xfe,
0xe8, 0x1b, 0x16, 0x56, 0x5b, 0xda, 0xb8, 0xf4, 0x5d, 0xbc, 0x64, 0x8b, 0x6c, 0x26, 0x42, 0x21,
0x7e, 0xc1, 0x42, 0xfa, 0xa3, 0x6f, 0x48, 0x4a, 0x4f, 0x3f, 0xe1, 0x3d, 0x94, 0xbe, 0x4b, 0x76,
0x92, 0xd2, 0x93, 0x2f, 0xf8, 0x16, 0x2a, 0xe2, 0x8e, 0x68, 0xb4, 0x0c, 0x12, 0x91, 0x9c, 0x9a,
0x5f, 0x1b, 0x3b, 0x0f, 0xe8, 0xe9, 0xec, 0x20, 0x35, 0xbc, 0x22, 0xa0, 0xbc, 0x29, 0x67, 0x56,
0xc2, 0x81, 0x3c, 0x9c, 0xba, 0x88, 0x1e, 0xcb, 0x59, 0x3b, 0x92, 0x35, 0x1e, 0x6d, 0x11, 0xfa,
0x1e, 0x5e, 0xb8, 0x4d, 0x9e, 0xe2, 0x85, 0x11, 0xa0, 0xe9, 0x49, 0xf9, 0xbf, 0x00, 0x32, 0x7c,
0xec, 0xd6, 0xb5, 0xcd, 0xaa, 0xf1, 0xfe, 0xa3, 0x98, 0xb4, 0x41, 0xf5, 0x95, 0x97, 0x8b, 0x14,
0x66, 0xa0, 0x26, 0xfb, 0x0f, 0x59, 0xbc, 0x65, 0x45, 0xb7, 0x6a, 0xbc, 0xbb, 0x86, 0x1b, 0xde,
0x56, 0xc7, 0xdb, 0x08, 0xd1, 0xc4, 0x6d, 0x62, 0x70, 0x68, 0x06, 0x12, 0x76, 0xbd, 0x81, 0x7f,
0x5b, 0x7f, 0xfe, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x45, 0xeb, 0x2a, 0xdc, 0xed, 0x16, 0x00,
0x00,
0xf5, 0x5f, 0x49, 0x94, 0x25, 0x3d, 0x51, 0x12, 0x3d, 0xde, 0xb5, 0x65, 0xc5, 0xc1, 0x7a, 0x99,
0xec, 0x37, 0x8e, 0x93, 0xac, 0xbe, 0x71, 0x2e, 0x6d, 0x90, 0x14, 0xd0, 0xca, 0x72, 0xac, 0xad,
0x2d, 0xa9, 0x94, 0xbc, 0x41, 0x8a, 0x02, 0xc4, 0x58, 0x1a, 0x4b, 0x44, 0xc4, 0x1f, 0x21, 0x47,
0xbb, 0x36, 0x82, 0xb6, 0x40, 0x81, 0x9e, 0x7b, 0xe8, 0xbf, 0xd2, 0x5b, 0x7b, 0xeb, 0xb5, 0xa7,
0xf6, 0xd8, 0x6b, 0x2f, 0x3d, 0x14, 0xfd, 0x0b, 0x0a, 0x14, 0x6f, 0x86, 0xa4, 0x48, 0x59, 0x72,
0xd1, 0x43, 0x4f, 0x36, 0xdf, 0xfb, 0xcc, 0x9b, 0x79, 0xbf, 0xdf, 0x13, 0xa8, 0xe3, 0xb9, 0xc5,
0x1c, 0xfe, 0xc2, 0xf3, 0x5d, 0xee, 0x92, 0xc2, 0xdc, 0x75, 0x3d, 0xdf, 0x1b, 0x37, 0x0e, 0xa6,
0xae, 0x3b, 0x9d, 0xb3, 0x26, 0xf5, 0xac, 0x26, 0x75, 0x1c, 0x97, 0x53, 0x6e, 0xb9, 0x4e, 0x20,
0x61, 0xfa, 0x3f, 0x15, 0xa8, 0x5e, 0xb8, 0xae, 0xd7, 0x5f, 0x70, 0x83, 0x7d, 0xb7, 0x60, 0x01,
0x27, 0x1a, 0xe4, 0xa8, 0xcd, 0xeb, 0x99, 0xc3, 0xcc, 0x51, 0xce, 0xc0, 0x7f, 0x09, 0x01, 0x65,
0xc2, 0x02, 0x5e, 0xcf, 0x1e, 0x66, 0x8e, 0x4a, 0x86, 0xf8, 0x9f, 0x3c, 0x85, 0x32, 0xfe, 0x35,
0xa9, 0xed, 0x2e, 0x1c, 0x5e, 0xaf, 0x09, 0x34, 0x20, 0xa9, 0x25, 0x28, 0x08, 0x18, 0xcf, 0xa8,
0x33, 0x65, 0x26, 0x9d, 0x4c, 0xfc, 0xba, 0x26, 0xce, 0x82, 0x24, 0xb5, 0x26, 0x13, 0x9f, 0x34,
0xe1, 0xb1, 0x4d, 0x6f, 0xcd, 0xe0, 0x2d, 0xf5, 0x4c, 0xdf, 0x5d, 0x70, 0xcb, 0x99, 0x9a, 0x37,
0x8c, 0xd5, 0x73, 0x42, 0xd4, 0xb6, 0x4d, 0x6f, 0x87, 0x6f, 0xa9, 0x67, 0x48, 0xce, 0x19, 0x63,
0xe4, 0x33, 0xd8, 0xc5, 0x03, 0x9e, 0xcf, 0x3c, 0x7a, 0x97, 0x3a, 0xa2, 0x88, 0x23, 0x3b, 0x36,
0xbd, 0x1d, 0x08, 0x66, 0xe2, 0xd0, 0x21, 0xa8, 0xf1, 0x2d, 0x08, 0xcd, 0xcb, 0x87, 0x86, 0xd2,
0x11, 0xf1, 0x3e, 0x54, 0x13, 0x62, 0x51, 0xf5, 0x2d, 0x81, 0x51, 0x63, 0x71, 0x2d, 0x9b, 0x13,
0x1d, 0x2a, 0x88, 0xb2, 0x2d, 0x87, 0xf9, 0x42, 0x50, 0x41, 0x80, 0xca, 0x36, 0xbd, 0xbd, 0x44,
0x1a, 0x4a, 0xfa, 0x18, 0x34, 0xb4, 0xba, 0xe9, 0x2e, 0xb8, 0x89, 0x8a, 0x3a, 0x6c, 0x5e, 0x2f,
0x1e, 0x66, 0x8e, 0x94, 0x97, 0xd9, 0x7a, 0xc6, 0xa8, 0xce, 0xa5, 0x9d, 0xdb, 0x92, 0x43, 0x8e,
0x61, 0xdb, 0x5d, 0xf0, 0xa9, 0x8b, 0x4a, 0x20, 0xda, 0x0c, 0x18, 0xaf, 0x97, 0x0f, 0x73, 0x47,
0x8a, 0x51, 0x8b, 0x18, 0x88, 0x1d, 0x32, 0x8e, 0xd8, 0xe0, 0x2d, 0x63, 0x9e, 0x39, 0x76, 0x9d,
0x1b, 0x93, 0x53, 0x7f, 0xca, 0x78, 0xbd, 0x74, 0x98, 0x39, 0xca, 0x1b, 0x35, 0xc1, 0x68, 0xbb,
0xce, 0xcd, 0x48, 0x90, 0xc9, 0x27, 0x40, 0x66, 0x7c, 0x3e, 0x16, 0x50, 0xcb, 0xb7, 0xa5, 0xbb,
0xeb, 0x15, 0x01, 0xde, 0x46, 0x4e, 0x3b, 0xc9, 0x20, 0x9f, 0xc3, 0xbe, 0x30, 0x8e, 0xb7, 0xb8,
0x9e, 0x5b, 0x63, 0x41, 0x34, 0x27, 0x8c, 0x4e, 0xe6, 0x96, 0xc3, 0xea, 0x80, 0xaf, 0x37, 0xf6,
0x10, 0x30, 0x58, 0xf2, 0x4f, 0x43, 0x36, 0x79, 0x0c, 0xf9, 0x39, 0xbd, 0x66, 0xf3, 0xba, 0x2a,
0xbc, 0x2b, 0x3f, 0xc8, 0x01, 0x94, 0x2c, 0xc7, 0xe2, 0x16, 0xe5, 0xae, 0x5f, 0xaf, 0x0a, 0xce,
0x92, 0xa0, 0xff, 0x3a, 0x0b, 0x15, 0x8c, 0xb8, 0xae, 0xb3, 0x39, 0xe0, 0x56, 0x9d, 0x96, 0xbd,
0xe7, 0xb4, 0x7b, 0xee, 0xc8, 0xdd, 0x77, 0xc7, 0x3e, 0x14, 0xe7, 0x34, 0xe0, 0xe6, 0xcc, 0xf5,
0x44, 0x84, 0xa8, 0x46, 0x01, 0xbf, 0xcf, 0x5d, 0x8f, 0xbc, 0x07, 0x15, 0x76, 0xcb, 0x99, 0xef,
0xd0, 0xb9, 0x89, 0x26, 0x11, 0x61, 0x51, 0x34, 0xd4, 0x88, 0x78, 0xce, 0xe7, 0x63, 0x72, 0x04,
0x5a, 0x6c, 0xc8, 0xc8, 0xe6, 0x5b, 0xc2, 0x8c, 0xd5, 0xc8, 0x8c, 0xa1, 0xc9, 0x63, 0x3b, 0x14,
0x36, 0xda, 0xa1, 0xb8, 0x6a, 0x87, 0xbf, 0x67, 0x40, 0x15, 0x01, 0xce, 0x02, 0xcf, 0x75, 0x02,
0x46, 0x08, 0x64, 0xad, 0x89, 0xb0, 0x42, 0x49, 0xc4, 0x4b, 0xd6, 0x9a, 0xa0, 0x0a, 0xd6, 0xc4,
0xbc, 0xbe, 0xe3, 0x2c, 0x10, 0x1a, 0xaa, 0x46, 0xc1, 0x9a, 0xbc, 0xc4, 0x4f, 0xf2, 0x1c, 0x54,
0xf1, 0x3a, 0xcc, 0x2e, 0x16, 0x04, 0x32, 0x39, 0xc5, 0xc1, 0x32, 0xd2, 0x5b, 0x92, 0x4c, 0x5e,
0xc0, 0x4e, 0x12, 0x66, 0x3a, 0xde, 0xc9, 0xdb, 0x60, 0x26, 0xec, 0x51, 0x92, 0xe1, 0x10, 0x22,
0x7b, 0x82, 0x41, 0x3e, 0x0e, 0xa3, 0x27, 0xc2, 0x4b, 0x78, 0x5e, 0xc0, 0xb5, 0x04, 0x7c, 0x20,
0xd0, 0xcf, 0xa1, 0x1a, 0x30, 0xff, 0x0d, 0xf3, 0x4d, 0x9b, 0x05, 0x01, 0x9d, 0x32, 0x61, 0xa0,
0x92, 0x51, 0x91, 0xd4, 0x4b, 0x49, 0xd4, 0x35, 0xa8, 0x5e, 0xba, 0x8e, 0xc5, 0x5d, 0x3f, 0xf4,
0xb9, 0xfe, 0x3b, 0x05, 0x00, 0xb5, 0x1f, 0x72, 0xca, 0x17, 0xc1, 0xda, 0x9a, 0x83, 0xd6, 0xc8,
0x6e, 0xb4, 0x46, 0x79, 0xd5, 0x1a, 0x0a, 0xbf, 0xf3, 0x64, 0x18, 0x54, 0x4f, 0xb6, 0x5f, 0x84,
0xd5, 0xef, 0x05, 0xde, 0x31, 0xba, 0xf3, 0x98, 0x21, 0xd8, 0xe4, 0x08, 0xf2, 0x01, 0xa7, 0x5c,
0x56, 0x8c, 0xea, 0x09, 0x49, 0xe1, 0xf0, 0x2d, 0xcc, 0x90, 0x00, 0xf2, 0x25, 0x54, 0x6f, 0xa8,
0x35, 0x5f, 0xf8, 0xcc, 0xf4, 0x19, 0x0d, 0x5c, 0x47, 0x44, 0x72, 0xf5, 0x64, 0x37, 0x3e, 0x72,
0x26, 0xd9, 0x86, 0xe0, 0x1a, 0x95, 0x9b, 0xe4, 0x27, 0xf9, 0x00, 0x6a, 0xa1, 0xab, 0x31, 0x9f,
0xb8, 0x65, 0x47, 0x95, 0xa7, 0xba, 0x24, 0x8f, 0x2c, 0x1b, 0x5f, 0xa4, 0x89, 0x20, 0x5d, 0x78,
0x13, 0xca, 0x99, 0x44, 0xca, 0xfa, 0x53, 0x45, 0xfa, 0x95, 0x20, 0x0b, 0xe4, 0xaa, 0xc3, 0x0b,
0xeb, 0x1d, 0xbe, 0xde, 0x81, 0xea, 0x06, 0x07, 0x6e, 0x08, 0x8f, 0xca, 0xa6, 0xf0, 0xc0, 0xaa,
0xee, 0x06, 0xdc, 0x94, 0xfe, 0x15, 0x51, 0x9d, 0x33, 0x00, 0x49, 0x43, 0x41, 0x21, 0xcf, 0x40,
0x15, 0x00, 0xd7, 0x19, 0xcf, 0xa8, 0xe5, 0x88, 0x22, 0x95, 0x33, 0xc4, 0xa1, 0xbe, 0x24, 0x61,
0xf2, 0x49, 0xc8, 0xcd, 0x8d, 0xc4, 0x80, 0xac, 0xb7, 0x02, 0x13, 0xd2, 0x96, 0x29, 0x55, 0x4b,
0xa4, 0x94, 0x4e, 0x40, 0xbb, 0xb0, 0x02, 0x8e, 0xde, 0x0a, 0xa2, 0x50, 0xfa, 0x11, 0x6c, 0x27,
0x68, 0x61, 0x32, 0x7d, 0x08, 0x79, 0xac, 0x1e, 0x41, 0x3d, 0x73, 0x98, 0x3b, 0x2a, 0x9f, 0xec,
0xdc, 0x73, 0xf4, 0x22, 0x30, 0x24, 0x42, 0x7f, 0x06, 0x35, 0x24, 0x76, 0x9d, 0x1b, 0x37, 0xaa,
0x48, 0xd5, 0x38, 0x15, 0x55, 0x0c, 0x3c, 0xbd, 0x0a, 0xea, 0x88, 0xf9, 0x76, 0x7c, 0xe5, 0x2f,
0xa1, 0xd6, 0x75, 0x42, 0x4a, 0x78, 0xe1, 0xff, 0x41, 0xcd, 0xb6, 0x1c, 0x59, 0xb2, 0xc2, 0x9e,
0x28, 0x1d, 0x5e, 0xb1, 0x2d, 0x07, 0xe5, 0x87, 0x6d, 0x11, 0x71, 0x51, 0x69, 0x0b, 0x71, 0x5b,
0x21, 0x4e, 0x56, 0x37, 0x89, 0x7b, 0xa5, 0x14, 0x33, 0x5a, 0xf6, 0x95, 0x52, 0xcc, 0x6a, 0xb9,
0x57, 0x4a, 0x31, 0xa7, 0x29, 0xaf, 0x94, 0xa2, 0xa2, 0xe5, 0x5f, 0x29, 0xc5, 0x82, 0x56, 0xd4,
0xff, 0x94, 0x01, 0xad, 0xbf, 0xe0, 0xff, 0xd3, 0x27, 0x88, 0xc6, 0x68, 0x39, 0xe6, 0x78, 0xce,
0xdf, 0x98, 0x13, 0x36, 0xe7, 0x54, 0xb8, 0x3b, 0x6f, 0xa8, 0xb6, 0xe5, 0xb4, 0xe7, 0xfc, 0xcd,
0x29, 0xd2, 0xa2, 0xf6, 0x99, 0x40, 0x95, 0x42, 0x14, 0xbd, 0x8d, 0x51, 0xff, 0x41, 0x9d, 0x3f,
0x66, 0x40, 0xfd, 0xc9, 0xc2, 0xe5, 0x6c, 0x73, 0x4b, 0x78, 0x0a, 0xe5, 0xb7, 0x16, 0x9f, 0x99,
0x72, 0x80, 0x08, 0xeb, 0x35, 0x20, 0xa9, 0x2d, 0x28, 0x32, 0x32, 0x97, 0x85, 0x3a, 0x2b, 0x1e,
0x01, 0xe3, 0x65, 0x91, 0xbe, 0x57, 0xf3, 0x73, 0x6b, 0x6a, 0xfe, 0x83, 0xdd, 0x50, 0x79, 0xb0,
0x1b, 0xea, 0xbf, 0xc9, 0x60, 0x58, 0x84, 0x7a, 0x84, 0x3e, 0x39, 0x04, 0x35, 0xea, 0x62, 0x66,
0x40, 0x23, 0x8d, 0x20, 0x90, 0x6d, 0x6c, 0x48, 0x39, 0x8e, 0x41, 0x22, 0x03, 0xc5, 0x8d, 0xc1,
0x2c, 0x46, 0x86, 0x63, 0x10, 0xf2, 0x06, 0x92, 0x15, 0x1e, 0x78, 0x17, 0x20, 0x61, 0xec, 0xbc,
0xd0, 0xb3, 0x34, 0x4e, 0x58, 0x5a, 0xda, 0x58, 0xd1, 0xf2, 0xfa, 0x9f, 0x65, 0x98, 0xfc, 0xb7,
0x4f, 0x7a, 0x1f, 0xaa, 0xcb, 0x69, 0x48, 0x60, 0x64, 0x03, 0x56, 0xbd, 0x68, 0x1c, 0x42, 0xd4,
0x47, 0x61, 0xa1, 0x91, 0x83, 0x49, 0xfa, 0xd9, 0x35, 0xe4, 0x0c, 0x91, 0x11, 0x8a, 0x14, 0x03,
0x0c, 0xda, 0x95, 0xde, 0xd9, 0xcc, 0xe1, 0xa6, 0x98, 0x27, 0x65, 0x53, 0xae, 0x09, 0x7b, 0x4a,
0xfa, 0x29, 0x3a, 0xff, 0x61, 0x05, 0xf5, 0x1a, 0x54, 0x46, 0xee, 0xb7, 0xcc, 0x89, 0xb3, 0xf1,
0x0b, 0xa8, 0x46, 0x84, 0x50, 0xc5, 0x63, 0xd8, 0xe2, 0x82, 0x12, 0xa6, 0xff, 0xb2, 0xce, 0x5f,
0x04, 0x94, 0x0b, 0xb0, 0x11, 0x22, 0xf4, 0xdf, 0x67, 0xa1, 0x14, 0x53, 0x31, 0x48, 0xae, 0x69,
0xc0, 0x4c, 0x9b, 0x8e, 0xa9, 0xef, 0xba, 0x4e, 0x58, 0x04, 0x54, 0x24, 0x5e, 0x86, 0x34, 0xac,
0x71, 0x91, 0x1e, 0x33, 0x1a, 0xcc, 0x84, 0x75, 0x54, 0xa3, 0x1c, 0xd2, 0xce, 0x69, 0x30, 0x23,
0x1f, 0x82, 0x16, 0x41, 0x3c, 0x9f, 0x59, 0x36, 0xb6, 0x46, 0xd9, 0xc0, 0x6b, 0x21, 0x7d, 0x10,
0x92, 0xb1, 0x03, 0xc8, 0x2c, 0x34, 0x3d, 0x6a, 0x4d, 0x4c, 0x1b, 0xad, 0x28, 0x07, 0xda, 0xaa,
0xa4, 0x0f, 0xa8, 0x35, 0xb9, 0x0c, 0x28, 0x27, 0x9f, 0xc2, 0x93, 0xc4, 0xd4, 0x9b, 0x80, 0xcb,
0x34, 0x27, 0x7e, 0x3c, 0xf6, 0xc6, 0x47, 0x9e, 0x81, 0x8a, 0x2d, 0xc5, 0x1c, 0xfb, 0x8c, 0x72,
0x36, 0x09, 0x13, 0xbd, 0x8c, 0xb4, 0xb6, 0x24, 0x91, 0x3a, 0x14, 0xd8, 0xad, 0x67, 0xf9, 0x6c,
0x22, 0x5a, 0x4a, 0xd1, 0x88, 0x3e, 0xf1, 0x70, 0xc0, 0x5d, 0x9f, 0x4e, 0x99, 0xe9, 0x50, 0x9b,
0x85, 0x33, 0x4c, 0x39, 0xa4, 0xf5, 0xa8, 0xcd, 0xf4, 0x77, 0x60, 0xff, 0x2b, 0xc6, 0x2f, 0xac,
0xef, 0x16, 0xd6, 0xc4, 0xe2, 0x77, 0x03, 0xea, 0xd3, 0x65, 0x99, 0xfc, 0x97, 0x02, 0x3b, 0x69,
0x16, 0xe3, 0xcc, 0xc7, 0x16, 0x95, 0xf7, 0x17, 0x73, 0x16, 0x79, 0x67, 0xd9, 0x52, 0x63, 0xb0,
0xb1, 0x98, 0x33, 0x43, 0x82, 0xc8, 0x97, 0x70, 0xb0, 0x0c, 0x31, 0x1f, 0x9b, 0x64, 0x40, 0xb9,
0xe9, 0x31, 0xdf, 0x7c, 0x83, 0xa3, 0x80, 0xb0, 0xbe, 0xc8, 0x4a, 0x19, 0x6d, 0x06, 0xe5, 0x18,
0x71, 0x03, 0xe6, 0xbf, 0x46, 0x36, 0xf9, 0x00, 0xb4, 0xe4, 0x2c, 0x69, 0x7a, 0x9e, 0x2d, 0x3c,
0xa1, 0xc4, 0xe5, 0x0e, 0xed, 0xe5, 0xd9, 0xe4, 0x13, 0xc0, 0x05, 0xc2, 0x4c, 0x59, 0xd8, 0xb3,
0xc3, 0xa4, 0x47, 0x19, 0xcb, 0xad, 0x02, 0xe1, 0x9f, 0x43, 0x63, 0xfd, 0x36, 0x22, 0x4e, 0xe5,
0xc5, 0xa9, 0xdd, 0x35, 0x1b, 0x09, 0x9e, 0x4d, 0xaf, 0x1c, 0xe8, 0xc1, 0x2d, 0x81, 0x5f, 0xae,
0x1c, 0x98, 0x33, 0x1f, 0xc2, 0x76, 0x6a, 0xc6, 0x15, 0xc0, 0x82, 0x00, 0x56, 0x13, 0x73, 0x6e,
0x9c, 0x5e, 0xab, 0xfb, 0x41, 0x71, 0xfd, 0x7e, 0xf0, 0x02, 0x76, 0xa2, 0xc9, 0xe6, 0x9a, 0x8e,
0xbf, 0x75, 0x6f, 0x6e, 0xcc, 0x80, 0x8d, 0x45, 0xd5, 0x56, 0x8c, 0xed, 0x90, 0xf5, 0x52, 0x72,
0x86, 0x6c, 0x8c, 0xa3, 0x36, 0x5d, 0x70, 0xd7, 0x8c, 0x56, 0x1b, 0xd1, 0xae, 0x8b, 0x46, 0x19,
0x89, 0xe1, 0xea, 0x88, 0xb6, 0x13, 0x18, 0xdc, 0x7c, 0xae, 0x17, 0x93, 0x29, 0x93, 0x65, 0xa3,
0x2c, 0x6d, 0x87, 0xac, 0xfe, 0x82, 0xbf, 0x14, 0x0c, 0x7c, 0xee, 0x0f, 0x60, 0xff, 0x1e, 0x9c,
0x53, 0x9f, 0x8b, 0x87, 0xa8, 0xe2, 0xd0, 0x93, 0xf4, 0x21, 0xe4, 0xe2, 0x63, 0x3e, 0x02, 0x22,
0x4e, 0xa2, 0x61, 0x2c, 0xc7, 0xbc, 0x99, 0x5b, 0xd3, 0x19, 0x17, 0xe3, 0x8a, 0x62, 0xd4, 0x90,
0x73, 0x49, 0x6f, 0xbb, 0xce, 0x99, 0x20, 0xeb, 0x7f, 0xc8, 0x40, 0x25, 0x15, 0x52, 0xa2, 0xb4,
0xc8, 0xf5, 0xcb, 0x0c, 0x1b, 0xbc, 0x62, 0x94, 0x42, 0x4a, 0x77, 0x42, 0x5e, 0x84, 0x53, 0x64,
0x56, 0x8c, 0x7a, 0x8d, 0xf5, 0x71, 0x99, 0x18, 0x27, 0x3f, 0x01, 0x62, 0x39, 0x63, 0xd7, 0x46,
0xcf, 0xf3, 0x99, 0xcf, 0x82, 0x99, 0x3b, 0x9f, 0x88, 0xe8, 0xaa, 0x18, 0xdb, 0x11, 0x67, 0x14,
0x31, 0x10, 0x1e, 0x6f, 0x7c, 0x4b, 0xb8, 0x22, 0xe1, 0x11, 0x27, 0x86, 0xeb, 0xdf, 0xc0, 0xfe,
0x70, 0x53, 0x6e, 0x91, 0x2f, 0x00, 0xbc, 0x38, 0xa3, 0x84, 0x26, 0xe5, 0x93, 0x83, 0xfb, 0x0f,
0x5e, 0x66, 0x9d, 0x91, 0xc0, 0xeb, 0x07, 0xd0, 0x58, 0x27, 0x5a, 0x96, 0x4f, 0xfd, 0x09, 0xec,
0x0c, 0x17, 0xd3, 0x29, 0x5b, 0x19, 0xb4, 0x5e, 0xc1, 0xe3, 0x34, 0x39, 0xac, 0xb6, 0x27, 0x50,
0x8c, 0x63, 0x43, 0x66, 0xf4, 0xde, 0xf2, 0x21, 0xa9, 0xdf, 0x16, 0x8c, 0x42, 0xb8, 0x03, 0x1f,
0x3f, 0x87, 0x62, 0x34, 0x9a, 0x13, 0x15, 0x8a, 0x17, 0xfd, 0xfe, 0xc0, 0xec, 0x5f, 0x8d, 0xb4,
0x47, 0xa4, 0x0c, 0x05, 0xf1, 0xd5, 0xed, 0x69, 0x99, 0xe3, 0x00, 0x4a, 0xf1, 0x64, 0x4e, 0x2a,
0x50, 0xea, 0xf6, 0xba, 0xa3, 0x6e, 0x6b, 0xd4, 0x39, 0xd5, 0x1e, 0x91, 0x27, 0xb0, 0x3d, 0x30,
0x3a, 0xdd, 0xcb, 0xd6, 0x57, 0x1d, 0xd3, 0xe8, 0xbc, 0xee, 0xb4, 0x2e, 0x3a, 0xa7, 0x5a, 0x86,
0x10, 0xa8, 0x9e, 0x8f, 0x2e, 0xda, 0xe6, 0xe0, 0xea, 0xe5, 0x45, 0x77, 0x78, 0xde, 0x39, 0xd5,
0xb2, 0x28, 0x73, 0x78, 0xd5, 0x6e, 0x77, 0x86, 0x43, 0x2d, 0x47, 0x00, 0xb6, 0xce, 0x5a, 0x5d,
0x04, 0x2b, 0x64, 0x07, 0x6a, 0xdd, 0xde, 0xeb, 0x7e, 0xb7, 0xdd, 0x31, 0x87, 0x9d, 0xd1, 0x08,
0x89, 0xf9, 0xe3, 0x7f, 0x64, 0xa0, 0x92, 0x1a, 0xee, 0xc9, 0x1e, 0xec, 0xe0, 0x91, 0x2b, 0x03,
0x6f, 0x6a, 0x0d, 0xfb, 0x3d, 0xb3, 0xd7, 0xef, 0x75, 0xb4, 0x47, 0xe4, 0x1d, 0xd8, 0x5b, 0x61,
0xf4, 0xcf, 0xce, 0xda, 0xe7, 0x2d, 0x7c, 0x3c, 0x69, 0xc0, 0xee, 0x0a, 0x73, 0xd4, 0xbd, 0xec,
0xa0, 0x96, 0x59, 0x72, 0x08, 0x07, 0x2b, 0xbc, 0xe1, 0xd7, 0x9d, 0xce, 0x20, 0x46, 0xe4, 0xc8,
0x73, 0x78, 0xb6, 0x82, 0xe8, 0xf6, 0x86, 0x57, 0x67, 0x67, 0xdd, 0x76, 0xb7, 0xd3, 0x1b, 0x99,
0xaf, 0x5b, 0x17, 0x57, 0x1d, 0x4d, 0x21, 0x07, 0x50, 0x5f, 0xbd, 0xa4, 0x73, 0x39, 0xe8, 0x1b,
0x2d, 0xe3, 0x1b, 0x2d, 0x4f, 0xde, 0x83, 0xa7, 0xf7, 0x84, 0xb4, 0xfb, 0x86, 0xd1, 0x69, 0x8f,
0xcc, 0xd6, 0x65, 0xff, 0xaa, 0x37, 0xd2, 0xb6, 0x8e, 0x9b, 0x38, 0x40, 0xaf, 0x04, 0x38, 0x9a,
0xec, 0xaa, 0xf7, 0xe3, 0x5e, 0xff, 0xeb, 0x9e, 0xf6, 0x08, 0x2d, 0x3f, 0x3a, 0x37, 0x3a, 0xc3,
0xf3, 0xfe, 0xc5, 0xa9, 0x96, 0x39, 0xf9, 0x6b, 0x49, 0x2e, 0x6f, 0x6d, 0xf1, 0x83, 0x13, 0x31,
0xa0, 0x10, 0xd5, 0x81, 0x4d, 0x8e, 0x6f, 0x3c, 0x49, 0x0d, 0xe0, 0x71, 0xa4, 0xed, 0xfd, 0xea,
0x2f, 0x7f, 0xfb, 0x6d, 0x76, 0x5b, 0x57, 0x9b, 0x6f, 0x3e, 0x6d, 0x22, 0xa2, 0xe9, 0x2e, 0xf8,
0xe7, 0x99, 0x63, 0xd2, 0x87, 0x2d, 0xf9, 0x23, 0x01, 0xd9, 0x4d, 0x89, 0x8c, 0x7f, 0x35, 0xd8,
0x24, 0x71, 0x57, 0x48, 0xd4, 0xf4, 0x72, 0x2c, 0xd1, 0x72, 0x50, 0xe0, 0x0f, 0xa1, 0x10, 0xae,
0xa0, 0x89, 0x47, 0xa6, 0x97, 0xd2, 0xc6, 0xba, 0x2d, 0xe1, 0xff, 0x33, 0xe4, 0xa7, 0x50, 0x8a,
0x17, 0x0c, 0xb2, 0x9f, 0xc8, 0xb1, 0x74, 0x7e, 0x34, 0x1a, 0xeb, 0x58, 0xe9, 0x67, 0x91, 0x6a,
0xfc, 0x2c, 0xb1, 0x7c, 0x90, 0x2b, 0x99, 0x07, 0xb8, 0x7c, 0x90, 0x7a, 0xea, 0xfa, 0xc4, 0x3e,
0xb2, 0xf6, 0x61, 0x7a, 0x43, 0x88, 0x7c, 0x4c, 0x48, 0x4a, 0x64, 0xf3, 0x7b, 0x6b, 0xf2, 0x73,
0xf2, 0x33, 0x50, 0x43, 0x07, 0x88, 0x15, 0x81, 0x2c, 0x8d, 0x95, 0xdc, 0x63, 0x1a, 0x4b, 0x65,
0x56, 0x97, 0x89, 0x35, 0xd2, 0xdd, 0x05, 0x6f, 0x72, 0x21, 0xed, 0x3a, 0x96, 0x2e, 0x26, 0xcb,
0x84, 0xf4, 0xe4, 0x10, 0x9f, 0x96, 0x9e, 0x9a, 0x41, 0xf5, 0x43, 0x21, 0xbd, 0x41, 0xea, 0x29,
0xe9, 0xdf, 0x21, 0xa6, 0xf9, 0x3d, 0xb5, 0x39, 0x6a, 0x50, 0xc5, 0xc1, 0x42, 0xb8, 0xfc, 0x41,
0x1d, 0x96, 0x56, 0x5b, 0x59, 0xc9, 0xf4, 0x7d, 0x71, 0xc9, 0x0e, 0xd9, 0x4e, 0x84, 0x42, 0xac,
0xc1, 0x52, 0xfa, 0x83, 0x3a, 0x24, 0xa5, 0xa7, 0x55, 0x78, 0x2a, 0xa4, 0xef, 0x93, 0xbd, 0xa4,
0xf4, 0xa4, 0x06, 0xdf, 0x40, 0x05, 0xef, 0x88, 0x46, 0xcb, 0x20, 0x11, 0xc9, 0xa9, 0xf9, 0xb5,
0xb1, 0x77, 0x8f, 0x9e, 0xce, 0x0e, 0x52, 0x13, 0x57, 0x04, 0x94, 0x37, 0xe5, 0xcc, 0x4a, 0x38,
0x90, 0xfb, 0x53, 0x17, 0xd1, 0x63, 0x39, 0x1b, 0x47, 0xb2, 0xc6, 0x83, 0x2d, 0x42, 0x3f, 0x10,
0x17, 0xee, 0x92, 0xc7, 0xe2, 0xc2, 0x08, 0xd0, 0xf4, 0xa4, 0xfc, 0x5f, 0x00, 0x19, 0x3e, 0x74,
0xeb, 0xc6, 0x66, 0xd5, 0x78, 0xef, 0x41, 0x4c, 0xda, 0xa0, 0xfa, 0xda, 0xcb, 0x31, 0x85, 0x19,
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.

@ -153,6 +153,16 @@ message LoopOutRequest {
*/
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.
This limit is applied during path finding. Typically this value is taken
@ -589,6 +599,11 @@ message QuoteRequest {
*/
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
on-chain HTLC broadcast by the swap server in the case of a Loop Out, or for

@ -148,6 +148,14 @@
"type": "string",
"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",
"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",
"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",
"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",
"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": {
"type": "string",
"format": "int64",

@ -16,6 +16,12 @@ This file tracks release notes for the loop client.
#### 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
#### Bug Fixes

@ -25,8 +25,9 @@ func (s *Sweeper) CreateSweepTx(
htlc *swap.Htlc, htlcOutpoint wire.OutPoint,
keyBytes [33]byte,
witnessFunc func(sig []byte) (wire.TxWitness, error),
amount, fee btcutil.Amount,
destAddr btcutil.Address) (*wire.MsgTx, error) {
amount, destAmount, feeOnlyDest, feeOnlyChange, feeBoth btcutil.Amount,
destAddr, changeAddr btcutil.Address,
warnf func(message string, args ...interface{})) (*wire.MsgTx, error) {
// Compose tx.
sweepTx := wire.NewMsgTx(2)
@ -40,16 +41,28 @@ func (s *Sweeper) CreateSweepTx(
Sequence: sequence,
})
// Add output for the destination address.
sweepPkScript, err := txscript.PayToAddrScript(destAddr)
destinations, err := deduceDestinations(amount, destAmount,
feeOnlyDest, feeOnlyChange, feeBoth,
destAddr, changeAddr)
if err != nil {
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{
PkScript: sweepPkScript,
Value: int64(amount - fee),
})
// Add outputs.
for _, dst := range destinations {
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.
@ -92,18 +105,49 @@ func (s *Sweeper) CreateSweepTx(
// estimator.
func (s *Sweeper) GetSweepFee(ctx context.Context,
addInputEstimate func(*input.TxWeightEstimator),
destAddr btcutil.Address, sweepConfTarget int32) (
btcutil.Amount, error) {
destAddr, changeAddr btcutil.Address, sweepConfTarget int32) (
feeOnlyDest, feeOnlyChange, feeBoth btcutil.Amount, err error) {
// Get fee estimate from lnd.
feeRate, err := s.Lnd.WalletKit.EstimateFee(ctx, sweepConfTarget)
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.
var weightEstimate input.TxWeightEstimator
switch destAddr.(type) {
// Calculate feeOnlyDest.
var estOnlyDest input.TxWeightEstimator
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:
weightEstimate.AddP2WSHOutput()
case *btcutil.AddressWitnessPubKeyHash:
@ -113,11 +157,78 @@ func (s *Sweeper) GetSweepFee(ctx context.Context,
case *btcutil.AddressPubKeyHash:
weightEstimate.AddP2PKHOutput()
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)
weight := weightEstimate.Weight()
type destination struct {
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