decred.org/dcrdex@v1.0.3/client/asset/btc/btc.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package btc
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"crypto/sha256"
    10  	"encoding/binary"
    11  	"encoding/hex"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"math"
    16  	"os"
    17  	"path/filepath"
    18  	"regexp"
    19  	"sort"
    20  	"strconv"
    21  	"strings"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"decred.org/dcrdex/client/asset"
    27  	"decred.org/dcrdex/dex"
    28  	"decred.org/dcrdex/dex/calc"
    29  	"decred.org/dcrdex/dex/config"
    30  	"decred.org/dcrdex/dex/dexnet"
    31  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    32  	"github.com/btcsuite/btcd/btcec/v2"
    33  	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
    34  	"github.com/btcsuite/btcd/btcjson"
    35  	"github.com/btcsuite/btcd/btcutil"
    36  	"github.com/btcsuite/btcd/chaincfg"
    37  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    38  	"github.com/btcsuite/btcd/txscript"
    39  	"github.com/btcsuite/btcd/wire"
    40  	"github.com/btcsuite/btcwallet/wallet"
    41  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    42  	"github.com/decred/dcrd/rpcclient/v8"
    43  )
    44  
    45  const (
    46  	version = 0
    47  
    48  	// BipID is the BIP-0044 asset ID.
    49  	BipID = 0
    50  
    51  	// The default fee is passed to the user as part of the asset.WalletInfo
    52  	// structure.
    53  	defaultFee = 100
    54  	// defaultFeeRateLimit is the default value for the feeratelimit.
    55  	defaultFeeRateLimit = 1400
    56  	// defaultRedeemConfTarget is the default redeem transaction confirmation
    57  	// target in blocks used by estimatesmartfee to get the optimal fee for a
    58  	// redeem transaction.
    59  	defaultRedeemConfTarget = 2
    60  
    61  	minNetworkVersion  = 270000
    62  	minProtocolVersion = 70015
    63  	// version which descriptor wallets have been introduced.
    64  	minDescriptorVersion = 220000
    65  
    66  	// splitTxBaggage is the total number of additional bytes associated with
    67  	// using a split transaction to fund a swap.
    68  	splitTxBaggage = dexbtc.MinimumTxOverhead + dexbtc.RedeemP2PKHInputSize + 2*dexbtc.P2PKHOutputSize
    69  	// splitTxBaggageSegwit it the analogue of splitTxBaggage for segwit.
    70  	// We include the 2 bytes for marker and flag.
    71  	splitTxBaggageSegwit = dexbtc.MinimumTxOverhead + 2*dexbtc.P2WPKHOutputSize +
    72  		dexbtc.RedeemP2WPKHInputSize + ((dexbtc.RedeemP2WPKHInputWitnessWeight + dexbtc.SegwitMarkerAndFlagWeight + 3) / 4)
    73  
    74  	walletTypeLegacy   = ""
    75  	walletTypeRPC      = "bitcoindRPC"
    76  	walletTypeSPV      = "SPV"
    77  	walletTypeElectrum = "electrumRPC"
    78  
    79  	swapFeeBumpKey      = "swapfeebump"
    80  	splitKey            = "swapsplit"
    81  	multiSplitKey       = "multisplit"
    82  	multiSplitBufferKey = "multisplitbuffer"
    83  	redeemFeeBumpFee    = "redeemfeebump"
    84  
    85  	// requiredRedeemConfirms is the amount of confirms a redeem transaction
    86  	// needs before the trade is considered confirmed. The redeem is
    87  	// monitored until this number of confirms is reached.
    88  	requiredRedeemConfirms = 1
    89  )
    90  
    91  const (
    92  	minTimeBeforeAcceleration uint64 = 3600 // 1 hour
    93  )
    94  
    95  var (
    96  	// ContractSearchLimit is how far back in time AuditContract in SPV mode
    97  	// will search for a contract if no txData is provided. This should be a
    98  	// positive duration.
    99  	ContractSearchLimit = 48 * time.Hour
   100  
   101  	// blockTicker is the delay between calls to check for new blocks.
   102  	blockTicker                  = time.Second
   103  	peerCountTicker              = 5 * time.Second
   104  	walletBlockAllowance         = time.Second * 10
   105  	conventionalConversionFactor = float64(dexbtc.UnitInfo.Conventional.ConversionFactor)
   106  
   107  	ElectrumConfigOpts = []*asset.ConfigOption{
   108  		{
   109  			Key:         "rpcuser",
   110  			DisplayName: "JSON-RPC Username",
   111  			Description: "Electrum's 'rpcuser' setting",
   112  		},
   113  		{
   114  			Key:         "rpcpassword",
   115  			DisplayName: "JSON-RPC Password",
   116  			Description: "Electrum's 'rpcpassword' setting",
   117  			NoEcho:      true,
   118  		},
   119  		{
   120  			Key:         "rpcport",
   121  			DisplayName: "JSON-RPC Port",
   122  			Description: "Electrum's 'rpcport' (if not set with rpcbind)",
   123  		},
   124  		{
   125  			Key:          "rpcbind", // match RPCConfig struct field tags
   126  			DisplayName:  "JSON-RPC Address",
   127  			Description:  "Electrum's 'rpchost' <addr> or <addr>:<port>",
   128  			DefaultValue: "127.0.0.1",
   129  		},
   130  		{
   131  			Key:          "walletname", // match RPCConfig struct field tags
   132  			DisplayName:  "Wallet File",
   133  			Description:  "Full path to the wallet file (empty is default_wallet)",
   134  			DefaultValue: "", // empty string, not a nil interface
   135  		},
   136  	}
   137  
   138  	// 02 Jun 21 21:12 CDT
   139  	defaultWalletBirthdayUnix = 1622668320
   140  	DefaultWalletBirthday     = time.Unix(int64(defaultWalletBirthdayUnix), 0)
   141  
   142  	MultiFundingOpts = []*asset.OrderOption{
   143  		{
   144  			ConfigOption: asset.ConfigOption{
   145  				Key:         multiSplitKey,
   146  				DisplayName: "Allow multi split",
   147  				Description: "Allow split funding transactions that pre-size outputs to " +
   148  					"prevent excessive overlock.",
   149  				IsBoolean:    true,
   150  				DefaultValue: true,
   151  			},
   152  		},
   153  		{
   154  			ConfigOption: asset.ConfigOption{
   155  				Key:         multiSplitBufferKey,
   156  				DisplayName: "Multi split buffer",
   157  				Description: "Add an integer percent buffer to split output amounts to " +
   158  					"facilitate output reuse. This is only required for quote assets.",
   159  				DefaultValue: 5,
   160  				DependsOn:    multiSplitKey,
   161  			},
   162  			QuoteAssetOnly: true,
   163  			XYRange: &asset.XYRange{
   164  				Start: asset.XYRangePoint{
   165  					Label: "0%",
   166  					X:     0,
   167  					Y:     0,
   168  				},
   169  				End: asset.XYRangePoint{
   170  					Label: "100%",
   171  					X:     100,
   172  					Y:     100,
   173  				},
   174  				XUnit:  "%",
   175  				YUnit:  "%",
   176  				RoundX: true,
   177  				RoundY: true,
   178  			},
   179  		},
   180  	}
   181  
   182  	rpcWalletDefinition = &asset.WalletDefinition{
   183  		Type:              walletTypeRPC,
   184  		Tab:               "External",
   185  		Description:       "Connect to bitcoind",
   186  		DefaultConfigPath: dexbtc.SystemConfigPath("bitcoin"),
   187  		ConfigOpts:        append(RPCConfigOpts("Bitcoin", "8332"), CommonConfigOpts("BTC", false)...),
   188  		MultiFundingOpts:  MultiFundingOpts,
   189  	}
   190  	spvWalletDefinition = &asset.WalletDefinition{
   191  		Type:             walletTypeSPV,
   192  		Tab:              "Native",
   193  		Description:      "Use the built-in SPV wallet",
   194  		ConfigOpts:       CommonConfigOpts("BTC", true),
   195  		Seeded:           true,
   196  		MultiFundingOpts: MultiFundingOpts,
   197  	}
   198  
   199  	electrumWalletDefinition = &asset.WalletDefinition{
   200  		Type:        walletTypeElectrum,
   201  		Tab:         "Electrum (external)",
   202  		Description: "Use an external Electrum Wallet",
   203  		// json: DefaultConfigPath: filepath.Join(btcutil.AppDataDir("electrum", false), "config"), // e.g. ~/.electrum/config
   204  		ConfigOpts:       append(ElectrumConfigOpts, CommonConfigOpts("BTC", false)...),
   205  		MultiFundingOpts: MultiFundingOpts,
   206  	}
   207  
   208  	// WalletInfo defines some general information about a Bitcoin wallet.
   209  	WalletInfo = &asset.WalletInfo{
   210  		Name:              "Bitcoin",
   211  		SupportedVersions: []uint32{version},
   212  		UnitInfo:          dexbtc.UnitInfo,
   213  		AvailableWallets: []*asset.WalletDefinition{
   214  			spvWalletDefinition,
   215  			rpcWalletDefinition,
   216  			electrumWalletDefinition,
   217  		},
   218  		LegacyWalletIndex: 1,
   219  	}
   220  )
   221  
   222  func apiFallbackOpt(defaultV bool) *asset.ConfigOption {
   223  	return &asset.ConfigOption{
   224  		Key:         "apifeefallback",
   225  		DisplayName: "External fee rate estimates",
   226  		Description: "Allow fee rate estimation from a block explorer API. " +
   227  			"This is useful as a fallback for SPV wallets and RPC wallets " +
   228  			"that have recently been started.",
   229  		IsBoolean:    true,
   230  		DefaultValue: defaultV,
   231  	}
   232  }
   233  
   234  // CommonConfigOpts are the common options that the Wallets recognize.
   235  func CommonConfigOpts(symbol string /* upper-case */, withApiFallback bool) []*asset.ConfigOption {
   236  	opts := []*asset.ConfigOption{
   237  		{
   238  			Key:         "fallbackfee",
   239  			DisplayName: "Fallback fee rate",
   240  			Description: fmt.Sprintf("The fee rate to use for sending or withdrawing funds and fee payment when"+
   241  				" estimatesmartfee is not available. Units: %s/kB", symbol),
   242  			DefaultValue: defaultFee * 1000 / 1e8,
   243  		},
   244  		{
   245  			Key:         "feeratelimit",
   246  			DisplayName: "Highest acceptable fee rate",
   247  			Description: fmt.Sprintf("This is the highest network fee rate you are willing to "+
   248  				"pay on swap transactions. If feeratelimit is lower than a market's "+
   249  				"maxfeerate, you will not be able to trade on that market with this "+
   250  				"wallet.  Units: %s/kB", symbol),
   251  			DefaultValue: defaultFeeRateLimit * 1000 / 1e8,
   252  		},
   253  		{
   254  			Key:         "redeemconftarget",
   255  			DisplayName: "Redeem confirmation target",
   256  			Description: "The target number of blocks for the redeem transaction " +
   257  				"to be mined. Used to set the transaction's fee rate. " +
   258  				"(default: 2 blocks)",
   259  			DefaultValue: defaultRedeemConfTarget,
   260  		},
   261  		{
   262  			Key:         "txsplit",
   263  			DisplayName: "Pre-size funding inputs",
   264  			Description: "When placing an order, create a \"split\" transaction to " +
   265  				"fund the order without locking more of the wallet balance than " +
   266  				"necessary. Otherwise, excess funds may be reserved to fund the order " +
   267  				"until the first swap contract is broadcast during match settlement, " +
   268  				"or the order is canceled. This an extra transaction for which network " +
   269  				"mining fees are paid.",
   270  			IsBoolean:    true,
   271  			DefaultValue: false,
   272  		},
   273  	}
   274  
   275  	if withApiFallback {
   276  		opts = append(opts, apiFallbackOpt(true))
   277  	}
   278  	return opts
   279  }
   280  
   281  // RPCConfigOpts are the settings that are used to connect to an external RPC
   282  // wallet.
   283  func RPCConfigOpts(name, rpcPort string) []*asset.ConfigOption {
   284  	return []*asset.ConfigOption{
   285  		{
   286  			Key:         "walletname",
   287  			DisplayName: "Wallet Name",
   288  			Description: "The wallet name",
   289  		},
   290  		{
   291  			Key:         "rpcuser",
   292  			DisplayName: "JSON-RPC Username",
   293  			Description: fmt.Sprintf("%s's 'rpcuser' setting", name),
   294  		},
   295  		{
   296  			Key:         "rpcpassword",
   297  			DisplayName: "JSON-RPC Password",
   298  			Description: fmt.Sprintf("%s's 'rpcpassword' setting", name),
   299  			NoEcho:      true,
   300  		},
   301  		{
   302  			Key:          "rpcbind",
   303  			DisplayName:  "JSON-RPC Address",
   304  			Description:  "<addr> or <addr>:<port> (default 'localhost')",
   305  			DefaultValue: "127.0.0.1",
   306  		},
   307  		{
   308  			Key:          "rpcport",
   309  			DisplayName:  "JSON-RPC Port",
   310  			Description:  "Port for RPC connections (if not set in rpcbind)",
   311  			DefaultValue: rpcPort,
   312  		},
   313  	}
   314  }
   315  
   316  // TxInSigner is a transaction input signer. In addition to the standard Bitcoin
   317  // arguments, TxInSigner receives all values and pubkey scripts for previous
   318  // outpoints spent in this transaction.
   319  type TxInSigner func(tx *wire.MsgTx, idx int, subScript []byte, hashType txscript.SigHashType,
   320  	key *btcec.PrivateKey, vals []int64, prevScripts [][]byte) ([]byte, error)
   321  
   322  // BTCCloneCFG holds clone specific parameters.
   323  type BTCCloneCFG struct {
   324  	WalletCFG          *asset.WalletConfig
   325  	MinNetworkVersion  uint64
   326  	MinElectrumVersion dex.Semver
   327  	WalletInfo         *asset.WalletInfo
   328  	Symbol             string
   329  	Logger             dex.Logger
   330  	Network            dex.Network
   331  	ChainParams        *chaincfg.Params
   332  	// Ports is the default wallet RPC tcp ports used when undefined in
   333  	// WalletConfig.
   334  	Ports               dexbtc.NetPorts
   335  	DefaultFallbackFee  uint64 // sats/byte
   336  	DefaultFeeRateLimit uint64 // sats/byte
   337  	// LegacyBalance is for clones that don't yet support the 'getbalances' RPC
   338  	// call.
   339  	LegacyBalance bool
   340  	// BalanceFunc is a custom function for getting the wallet's balance.
   341  	// BalanceFunc precludes any other methods of balance retrieval.
   342  	BalanceFunc func(ctx context.Context, locked uint64) (*asset.Balance, error)
   343  	// If segwit is false, legacy addresses and contracts will be used. This
   344  	// setting must match the configuration of the server's asset backend.
   345  	Segwit bool
   346  	// LegacyRawFeeLimit can be true if the RPC only supports the boolean
   347  	// allowHighFees argument to the sendrawtransaction RPC.
   348  	LegacyRawFeeLimit bool
   349  	// InitTxSize is the size of a swap initiation transaction with a single
   350  	// input i.e. chained swaps.
   351  	InitTxSize uint32
   352  	// InitTxSizeBase is the size of a swap initiation transaction with no
   353  	// inputs. This is used to accurately determine the size of the first swap
   354  	// in a chain when considered with the actual inputs.
   355  	InitTxSizeBase uint32
   356  	// PrivKeyFunc is an optional function to get a private key for an address
   357  	// from the wallet. If not given the usual dumpprivkey RPC will be used.
   358  	PrivKeyFunc func(addr string) (*btcec.PrivateKey, error)
   359  	// AddressDecoder is an optional argument that can decode an address string
   360  	// into btcutil.Address. If AddressDecoder is not supplied,
   361  	// btcutil.DecodeAddress will be used.
   362  	AddressDecoder dexbtc.AddressDecoder // string => btcutil.Address
   363  	// AddressStringer is an optional argument that can encode a btcutil.Address
   364  	// into an address string. If AddressStringer is not supplied, the
   365  	// (btcutil.Address).String method will be used.
   366  	AddressStringer dexbtc.AddressStringer // btcutil.Address => string, may be an override or just the String method
   367  	// BlockDeserializer can be used in place of (*wire.MsgBlock).Deserialize.
   368  	BlockDeserializer func([]byte) (*wire.MsgBlock, error)
   369  	// ArglessChangeAddrRPC can be true if the getrawchangeaddress takes no
   370  	// address-type argument.
   371  	ArglessChangeAddrRPC bool
   372  	// NonSegwitSigner can be true if the transaction signature hash data is not
   373  	// the standard for non-segwit Bitcoin. If nil, txscript.
   374  	NonSegwitSigner TxInSigner
   375  	// ConnectFunc, if provided, is called by the RPC client at the end of the
   376  	// (*rpcClient).connect method. Errors returned by ConnectFunc will preclude
   377  	// the starting of goroutines associated with block and peer monitoring.
   378  	ConnectFunc func() error
   379  	// FeeEstimator provides a way to get fees given an RawRequest-enabled
   380  	// client and a confirmation target.
   381  	FeeEstimator func(context.Context, RawRequester, uint64) (uint64, error)
   382  	// ExternalFeeEstimator should be supplied if the clone provides the
   383  	// apifeefallback ConfigOpt. TODO: confTarget uint64
   384  	ExternalFeeEstimator func(context.Context, dex.Network) (uint64, error)
   385  	// ExternalFeeShelfLife can be set to adjust the time to staleness of
   386  	// external fee rates. Default is 5 minutes.
   387  	ExternalFeeShelfLife time.Duration
   388  	// OmitAddressType causes the address type (bech32, legacy) to be omitted
   389  	// from calls to getnewaddress.
   390  	OmitAddressType bool
   391  	// LegacySignTxRPC causes the RPC client to use the signrawtransaction
   392  	// endpoint instead of the signrawtransactionwithwallet endpoint.
   393  	LegacySignTxRPC bool
   394  	// BooleanGetBlockRPC causes the RPC client to use a boolean second argument
   395  	// for the getblock endpoint, instead of Bitcoin's numeric.
   396  	BooleanGetBlockRPC bool
   397  	// LegacyValidateAddressRPC uses the validateaddress endpoint instead of
   398  	// getaddressinfo in order to discover ownership of an address.
   399  	LegacyValidateAddressRPC bool
   400  	// SingularWallet signals that the node software supports only one wallet,
   401  	// so the RPC endpoint does not have a /wallet/{walletname} path.
   402  	SingularWallet bool
   403  	// UnlockSpends manually unlocks outputs as they are spent. Most assets will
   404  	// unlock wallet outputs automatically as they are spent.
   405  	UnlockSpends bool
   406  	// TxDeserializer is an optional function used to deserialize a transaction.
   407  	TxDeserializer func([]byte) (*wire.MsgTx, error)
   408  	// TxSerializer is an optional function used to serialize a transaction.
   409  	TxSerializer func(*wire.MsgTx) ([]byte, error)
   410  	// TxHasher is a function that generates a tx hash from a MsgTx.
   411  	TxHasher func(*wire.MsgTx) *chainhash.Hash
   412  	// TxSizeCalculator is an optional function that will be used to calculate
   413  	// the size of a transaction.
   414  	TxSizeCalculator func(*wire.MsgTx) uint64
   415  	// NumericGetRawRPC uses a numeric boolean indicator for the
   416  	// getrawtransaction RPC.
   417  	NumericGetRawRPC bool
   418  	// TxVersion is an optional function that returns a version to use for
   419  	// new transactions.
   420  	TxVersion func() int32
   421  	// ManualMedianTime causes the median time to be calculated manually.
   422  	ManualMedianTime bool
   423  	// ConstantDustLimit is used if an asset enforces a dust limit (minimum
   424  	// output value) that doesn't depend on the serialized size of the output.
   425  	// If ConstantDustLimit is zero, dexbtc.IsDust is used.
   426  	ConstantDustLimit uint64
   427  	// OmitRPCOptionsArg is for clones that don't take an options argument.
   428  	OmitRPCOptionsArg bool
   429  	// AssetID is the asset ID of the clone.
   430  	AssetID uint32
   431  }
   432  
   433  // PaymentScripter can be implemented to make non-standard payment scripts.
   434  type PaymentScripter interface {
   435  	PaymentScript() ([]byte, error)
   436  }
   437  
   438  // RPCConfig adds a wallet name to the basic configuration.
   439  type RPCConfig struct {
   440  	dexbtc.RPCConfig `ini:",extends"`
   441  	WalletName       string `ini:"walletname"`
   442  }
   443  
   444  // RPCWalletConfig is a combination of RPCConfig and WalletConfig. Used for a
   445  // wallet based on a bitcoind-like RPC API.
   446  type RPCWalletConfig struct {
   447  	RPCConfig    `ini:",extends"`
   448  	WalletConfig `ini:",extends"`
   449  }
   450  
   451  // WalletConfig are wallet-level configuration settings.
   452  type WalletConfig struct {
   453  	UseSplitTx       bool    `ini:"txsplit"`
   454  	FallbackFeeRate  float64 `ini:"fallbackfee"`
   455  	FeeRateLimit     float64 `ini:"feeratelimit"`
   456  	RedeemConfTarget uint64  `ini:"redeemconftarget"`
   457  	ActivelyUsed     bool    `ini:"special_activelyUsed"` // injected by core
   458  	ApiFeeFallback   bool    `ini:"apifeefallback"`
   459  }
   460  
   461  func readBaseWalletConfig(walletCfg *WalletConfig) (*baseWalletConfig, error) {
   462  	cfg := &baseWalletConfig{}
   463  	// if values not specified, use defaults. As they are validated as BTC/KB,
   464  	// we need to convert first.
   465  	if walletCfg.FallbackFeeRate == 0 {
   466  		walletCfg.FallbackFeeRate = float64(defaultFee) * 1000 / 1e8
   467  	}
   468  	if walletCfg.FeeRateLimit == 0 {
   469  		walletCfg.FeeRateLimit = float64(defaultFeeRateLimit) * 1000 / 1e8
   470  	}
   471  	if walletCfg.RedeemConfTarget == 0 {
   472  		walletCfg.RedeemConfTarget = defaultRedeemConfTarget
   473  	}
   474  	// If set in the user config, the fallback fee will be in conventional units
   475  	// per kB, e.g. BTC/kB. Translate that to sats/byte.
   476  	cfg.fallbackFeeRate = toSatoshi(walletCfg.FallbackFeeRate / 1000)
   477  	if cfg.fallbackFeeRate == 0 {
   478  		return nil, fmt.Errorf("fallback fee rate limit is smaller than the minimum 1000 sats/byte: %v",
   479  			walletCfg.FallbackFeeRate)
   480  	}
   481  	// If set in the user config, the fee rate limit will be in units of BTC/KB.
   482  	// Convert to sats/byte & error if value is smaller than smallest unit.
   483  	cfg.feeRateLimit = toSatoshi(walletCfg.FeeRateLimit / 1000)
   484  	if cfg.feeRateLimit == 0 {
   485  		return nil, fmt.Errorf("fee rate limit is smaller than the minimum 1000 sats/byte: %v",
   486  			walletCfg.FeeRateLimit)
   487  	}
   488  
   489  	cfg.redeemConfTarget = walletCfg.RedeemConfTarget
   490  	cfg.useSplitTx = walletCfg.UseSplitTx
   491  	cfg.apiFeeFallback = walletCfg.ApiFeeFallback
   492  
   493  	return cfg, nil
   494  }
   495  
   496  // readRPCWalletConfig parses the settings map into a *RPCWalletConfig.
   497  func readRPCWalletConfig(settings map[string]string, symbol string, net dex.Network, ports dexbtc.NetPorts) (cfg *RPCWalletConfig, err error) {
   498  	cfg = new(RPCWalletConfig)
   499  	err = config.Unmapify(settings, cfg)
   500  	if err != nil {
   501  		return nil, fmt.Errorf("error parsing rpc wallet config: %w", err)
   502  	}
   503  	err = dexbtc.CheckRPCConfig(&cfg.RPCConfig.RPCConfig, symbol, net, ports)
   504  	return
   505  }
   506  
   507  // parseRPCWalletConfig parses a *RPCWalletConfig from the settings map and
   508  // creates the unconnected *rpcclient.Client.
   509  func parseRPCWalletConfig(settings map[string]string, symbol string, net dex.Network,
   510  	ports dexbtc.NetPorts, singularWallet bool) (*RPCWalletConfig, *rpcclient.Client, error) {
   511  	cfg, err := readRPCWalletConfig(settings, symbol, net, ports)
   512  	if err != nil {
   513  		return nil, nil, err
   514  	}
   515  
   516  	// For BTC, external fee rates are the default because of the instability
   517  	// of estimatesmartfee.
   518  	if symbol == "btc" {
   519  		cfg.ApiFeeFallback = true
   520  	}
   521  
   522  	cl, err := newRPCConnection(cfg, singularWallet)
   523  	if err != nil {
   524  		return nil, nil, err
   525  	}
   526  
   527  	return cfg, cl, nil
   528  }
   529  
   530  // newRPCConnection creates a new RPC client.
   531  func newRPCConnection(cfg *RPCWalletConfig, singularWallet bool) (*rpcclient.Client, error) {
   532  	endpoint := cfg.RPCBind
   533  	if !singularWallet {
   534  		endpoint += "/wallet/" + cfg.WalletName
   535  	}
   536  
   537  	return rpcclient.New(&rpcclient.ConnConfig{
   538  		HTTPPostMode: true,
   539  		DisableTLS:   true,
   540  		Host:         endpoint,
   541  		User:         cfg.RPCUser,
   542  		Pass:         cfg.RPCPass,
   543  	}, nil)
   544  }
   545  
   546  // Driver implements asset.Driver.
   547  type Driver struct{}
   548  
   549  // Check that Driver implements Driver and Creator.
   550  var _ asset.Driver = (*Driver)(nil)
   551  var _ asset.Creator = (*Driver)(nil)
   552  
   553  // Exists checks the existence of the wallet. Part of the Creator interface, so
   554  // only used for wallets with WalletDefinition.Seeded = true.
   555  func (d *Driver) Exists(walletType, dataDir string, settings map[string]string, net dex.Network) (bool, error) {
   556  	if walletType != walletTypeSPV {
   557  		return false, fmt.Errorf("no Bitcoin wallet of type %q available", walletType)
   558  	}
   559  
   560  	chainParams, err := parseChainParams(net)
   561  	if err != nil {
   562  		return false, err
   563  	}
   564  
   565  	dir := filepath.Join(dataDir, chainParams.Name)
   566  	return walletExists(dir, chainParams)
   567  }
   568  
   569  // walletExists checks the existence of the wallet.
   570  func walletExists(dir string, chainParams *chaincfg.Params) (bool, error) {
   571  	// timeout and recoverWindow arguments borrowed from btcwallet directly.
   572  	loader := wallet.NewLoader(chainParams, dir, true, dbTimeout, 250)
   573  	return loader.WalletExists()
   574  }
   575  
   576  // createConfig combines the configuration settings used for wallet creation.
   577  type createConfig struct {
   578  	WalletConfig `ini:",extends"`
   579  	RecoveryCfg  `ini:",extends"`
   580  }
   581  
   582  // Create creates a new SPV wallet.
   583  func (d *Driver) Create(params *asset.CreateWalletParams) error {
   584  	if params.Type != walletTypeSPV {
   585  		return fmt.Errorf("SPV is the only seeded wallet type. required = %q, requested = %q", walletTypeSPV, params.Type)
   586  	}
   587  	if len(params.Seed) == 0 {
   588  		return errors.New("wallet seed cannot be empty")
   589  	}
   590  	if len(params.DataDir) == 0 {
   591  		return errors.New("must specify wallet data directory")
   592  	}
   593  	chainParams, err := parseChainParams(params.Net)
   594  	if err != nil {
   595  		return fmt.Errorf("error parsing chain: %w", err)
   596  	}
   597  
   598  	cfg := new(createConfig)
   599  	err = config.Unmapify(params.Settings, cfg)
   600  	if err != nil {
   601  		return err
   602  	}
   603  
   604  	_, err = readBaseWalletConfig(&cfg.WalletConfig)
   605  	if err != nil {
   606  		return err
   607  	}
   608  
   609  	bday := DefaultWalletBirthday
   610  	if params.Birthday != 0 {
   611  		bday = time.Unix(int64(params.Birthday), 0)
   612  	}
   613  
   614  	dir := filepath.Join(params.DataDir, chainParams.Name)
   615  	return createSPVWallet(params.Pass, params.Seed, bday, dir,
   616  		params.Logger, cfg.NumExternalAddresses, cfg.NumInternalAddresses, chainParams)
   617  }
   618  
   619  // Open opens or connects to the BTC exchange wallet. Start the wallet with its
   620  // Run method.
   621  func (d *Driver) Open(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (asset.Wallet, error) {
   622  	return NewWallet(cfg, logger, network)
   623  }
   624  
   625  // DecodeCoinID creates a human-readable representation of a coin ID for
   626  // Bitcoin.
   627  func (d *Driver) DecodeCoinID(coinID []byte) (string, error) {
   628  	txid, vout, err := decodeCoinID(coinID)
   629  	if err != nil {
   630  		return "<invalid>", err
   631  	}
   632  	return fmt.Sprintf("%v:%d", txid, vout), err
   633  }
   634  
   635  // Info returns basic information about the wallet and asset.
   636  func (d *Driver) Info() *asset.WalletInfo {
   637  	return WalletInfo
   638  }
   639  
   640  // MinLotSize calculates the minimum bond size for a given fee rate that avoids
   641  // dust outputs on the swap and refund txs, assuming the maxFeeRate doesn't
   642  // change.
   643  func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 {
   644  	return dexbtc.MinLotSize(maxFeeRate, true)
   645  }
   646  
   647  type CustomSPVWalletConstructor func(settings map[string]string, params *chaincfg.Params) (BTCWallet, error)
   648  
   649  // customSPVWalletConstructors are functions for setting up custom
   650  // implementations of the BTCWallet interface that may be used by the
   651  // ExchangeWalletSPV instead of the default spv implementation.
   652  var customSPVWalletConstructors = map[string]CustomSPVWalletConstructor{}
   653  
   654  // RegisterCustomSPVWallet registers a function that should be used in creating
   655  // a BTCWallet implementation that the ExchangeWalletSPV will use in place of
   656  // the default spv wallet implementation. External consumers can use this
   657  // function to provide alternative BTCWallet implementations, and must do so
   658  // before attempting to create an ExchangeWalletSPV instance of this type. It'll
   659  // panic if callers try to register a wallet twice.
   660  func RegisterCustomSPVWallet(constructor CustomSPVWalletConstructor, def *asset.WalletDefinition) {
   661  	for _, availableWallets := range WalletInfo.AvailableWallets {
   662  		if def.Type == availableWallets.Type {
   663  			panic(fmt.Sprintf("wallet type (%q) is already registered", def.Type))
   664  		}
   665  	}
   666  	customSPVWalletConstructors[def.Type] = constructor
   667  	WalletInfo.AvailableWallets = append(WalletInfo.AvailableWallets, def)
   668  }
   669  
   670  // swapOptions captures the available Swap options. Tagged to be used with
   671  // config.Unmapify to decode e.g. asset.Order.Options.
   672  type swapOptions struct {
   673  	Split   *bool    `ini:"swapsplit"`
   674  	FeeBump *float64 `ini:"swapfeebump"`
   675  }
   676  
   677  func (s *swapOptions) feeBump() (float64, error) {
   678  	bump := 1.0
   679  	if s.FeeBump != nil {
   680  		bump = *s.FeeBump
   681  		if bump > 2.0 {
   682  			return 0, fmt.Errorf("fee bump %f is higher than the 2.0 limit", bump)
   683  		}
   684  		if bump < 1.0 {
   685  			return 0, fmt.Errorf("fee bump %f is lower than 1", bump)
   686  		}
   687  	}
   688  	return bump, nil
   689  }
   690  
   691  // fundMultiOptions are the possible order options when calling FundMultiOrder.
   692  type fundMultiOptions struct {
   693  	// Split, if true, and multi-order cannot be funded with the existing UTXOs
   694  	// in the wallet without going over the maxLock limit, a split transaction
   695  	// will be created with one output per order.
   696  	//
   697  	// Use the multiSplitKey const defined above in the options map to set this option.
   698  	Split bool `ini:"multisplit"`
   699  	// SplitBuffer, if set, will instruct the wallet to add a buffer onto each
   700  	// output of the multi-order split transaction (if the split is needed).
   701  	// SplitBuffer is defined as a percentage of the output. If a .1 BTC output
   702  	// is required for an order and SplitBuffer is set to 5, a .105 BTC output
   703  	// will be created.
   704  	//
   705  	// The motivation for this is to assist market makers in having to do the
   706  	// least amount of splits as possible. It is useful when BTC is the quote
   707  	// asset on a market, and the price is increasing. During a market maker's
   708  	// operation, it will frequently have to cancel and replace orders as the
   709  	// rate moves. If BTC is the quote asset on a market, and the rate has
   710  	// lightly increased, the market maker will need to lock slightly more of
   711  	// the quote asset for the same amount of lots of the base asset. If there
   712  	// is no split buffer, this may necessitate a new split transaction.
   713  	//
   714  	// Use the multiSplitBufferKey const defined above in the options map to set this.
   715  	SplitBuffer float64 `ini:"multisplitbuffer"`
   716  }
   717  
   718  func decodeFundMultiOptions(options map[string]string) (*fundMultiOptions, error) {
   719  	opts := new(fundMultiOptions)
   720  	return opts, config.Unmapify(options, opts)
   721  }
   722  
   723  // redeemOptions are order options that apply to redemptions.
   724  type redeemOptions struct {
   725  	FeeBump *float64 `ini:"redeemfeebump"`
   726  }
   727  
   728  func init() {
   729  	asset.Register(BipID, &Driver{})
   730  }
   731  
   732  // baseWalletConfig is the validated, unit-converted, user-configurable wallet
   733  // settings.
   734  type baseWalletConfig struct {
   735  	fallbackFeeRate  uint64 // atoms/byte
   736  	feeRateLimit     uint64 // atoms/byte
   737  	redeemConfTarget uint64
   738  	useSplitTx       bool
   739  	apiFeeFallback   bool
   740  }
   741  
   742  // feeRateCache wraps a ExternalFeeEstimator function and caches results.
   743  type feeRateCache struct {
   744  	f         func(context.Context, dex.Network) (uint64, error)
   745  	shelfLife time.Duration
   746  
   747  	mtx        sync.Mutex
   748  	fetchStamp time.Time
   749  	lastRate   uint64
   750  	errorStamp time.Time
   751  	lastError  error
   752  }
   753  
   754  func (c *feeRateCache) rate(ctx context.Context, net dex.Network) (uint64, error) {
   755  	c.mtx.Lock()
   756  	defer c.mtx.Unlock()
   757  	const defaultShelfLife = time.Minute * 5
   758  	shelfLife := defaultShelfLife
   759  	if c.shelfLife > 0 {
   760  		shelfLife = c.shelfLife
   761  	}
   762  	if time.Since(c.fetchStamp) < shelfLife {
   763  		return c.lastRate, nil
   764  	}
   765  	const errorDelay = time.Minute
   766  	if time.Since(c.errorStamp) < errorDelay {
   767  		return 0, c.lastError
   768  	}
   769  	feeRate, err := c.f(ctx, net)
   770  	if err != nil {
   771  		c.errorStamp = time.Now()
   772  		c.lastError = err
   773  		return 0, err
   774  	}
   775  	c.fetchStamp = time.Now()
   776  	c.lastRate = feeRate
   777  	return feeRate, nil
   778  }
   779  
   780  // baseWallet is a wallet backend for Bitcoin. The backend is how the DEX
   781  // client app communicates with the BTC blockchain and wallet. baseWallet
   782  // satisfies the dex.Wallet interface.
   783  type baseWallet struct {
   784  	// 64-bit atomic variables first. See
   785  	// https://golang.org/pkg/sync/atomic/#pkg-note-BUG
   786  	tipAtConnect int64
   787  
   788  	cfgV              atomic.Value // *baseWalletConfig
   789  	node              Wallet
   790  	walletInfo        *asset.WalletInfo
   791  	cloneParams       *BTCCloneCFG
   792  	chainParams       *chaincfg.Params
   793  	log               dex.Logger
   794  	symbol            string
   795  	emit              *asset.WalletEmitter
   796  	lastPeerCount     uint32
   797  	peersChange       func(uint32, error)
   798  	minNetworkVersion uint64
   799  	dustLimit         uint64
   800  	initTxSize        uint64
   801  	initTxSizeBase    uint64
   802  	useLegacyBalance  bool
   803  	balanceFunc       func(ctx context.Context, locked uint64) (*asset.Balance, error)
   804  	segwit            bool
   805  	signNonSegwit     TxInSigner
   806  	localFeeRate      func(context.Context, RawRequester, uint64) (uint64, error)
   807  	feeCache          *feeRateCache
   808  	decodeAddr        dexbtc.AddressDecoder
   809  	walletDir         string
   810  
   811  	deserializeTx func([]byte) (*wire.MsgTx, error)
   812  	serializeTx   func(*wire.MsgTx) ([]byte, error)
   813  	calcTxSize    func(*wire.MsgTx) uint64
   814  	hashTx        func(*wire.MsgTx) *chainhash.Hash
   815  
   816  	stringAddr dexbtc.AddressStringer
   817  
   818  	txVersion func() int32
   819  
   820  	Network dex.Network
   821  	ctx     context.Context // the asset subsystem starts with Connect(ctx)
   822  
   823  	// TODO: remove currentTip and the mutex, and make it local to the
   824  	// watchBlocks->reportNewTip call stack. The tests are reliant on current
   825  	// internals, so this will take a little work.
   826  	tipMtx     sync.RWMutex
   827  	currentTip *BlockVector
   828  
   829  	cm *CoinManager
   830  
   831  	rf *RedemptionFinder
   832  
   833  	bondReserves atomic.Uint64
   834  
   835  	pendingTxsMtx sync.RWMutex
   836  	pendingTxs    map[chainhash.Hash]ExtendedWalletTx
   837  
   838  	// receiveTxLastQuery stores the last block height at which the wallet
   839  	// was queried for recieve transactions. This is also stored in the
   840  	// txHistoryDB.
   841  	receiveTxLastQuery atomic.Uint64
   842  
   843  	txHistoryDB atomic.Value // *BadgerTxDB
   844  
   845  	ar *AddressRecycler
   846  }
   847  
   848  func (w *baseWallet) fallbackFeeRate() uint64 {
   849  	return w.cfgV.Load().(*baseWalletConfig).fallbackFeeRate
   850  }
   851  
   852  func (w *baseWallet) feeRateLimit() uint64 {
   853  	return w.cfgV.Load().(*baseWalletConfig).feeRateLimit
   854  }
   855  
   856  func (w *baseWallet) redeemConfTarget() uint64 {
   857  	return w.cfgV.Load().(*baseWalletConfig).redeemConfTarget
   858  }
   859  
   860  func (w *baseWallet) useSplitTx() bool {
   861  	return w.cfgV.Load().(*baseWalletConfig).useSplitTx
   862  }
   863  
   864  func (w *baseWallet) UseSplitTx() bool {
   865  	return w.useSplitTx()
   866  }
   867  
   868  func (w *baseWallet) apiFeeFallback() bool {
   869  	return w.cfgV.Load().(*baseWalletConfig).apiFeeFallback
   870  }
   871  
   872  type intermediaryWallet struct {
   873  	*baseWallet
   874  	txFeeEstimator txFeeEstimator
   875  	tipRedeemer    tipRedemptionWallet
   876  
   877  	syncingTxHistory atomic.Bool
   878  }
   879  
   880  // ExchangeWalletSPV embeds a ExchangeWallet, but also provides the Rescan
   881  // method to implement asset.Rescanner.
   882  type ExchangeWalletSPV struct {
   883  	*intermediaryWallet
   884  	*authAddOn
   885  
   886  	spvNode *spvWallet
   887  }
   888  
   889  // ExchangeWalletFullNode implements Wallet and adds the FeeRate method.
   890  type ExchangeWalletFullNode struct {
   891  	*intermediaryWallet
   892  	*authAddOn
   893  }
   894  
   895  type ExchangeWalletNoAuth struct {
   896  	*intermediaryWallet
   897  }
   898  
   899  // ExchangeWalletAccelerator implements the Accelerator interface on an
   900  // ExchangeWalletFullNode.
   901  type ExchangeWalletAccelerator struct {
   902  	*ExchangeWalletFullNode
   903  }
   904  
   905  // Check that wallets satisfy their supported interfaces.
   906  var _ asset.Wallet = (*intermediaryWallet)(nil)
   907  var _ asset.Accelerator = (*ExchangeWalletAccelerator)(nil)
   908  var _ asset.Accelerator = (*ExchangeWalletSPV)(nil)
   909  var _ asset.Withdrawer = (*baseWallet)(nil)
   910  var _ asset.FeeRater = (*baseWallet)(nil)
   911  var _ asset.Rescanner = (*ExchangeWalletSPV)(nil)
   912  var _ asset.LogFiler = (*ExchangeWalletSPV)(nil)
   913  var _ asset.Recoverer = (*ExchangeWalletSPV)(nil)
   914  var _ asset.PeerManager = (*ExchangeWalletSPV)(nil)
   915  var _ asset.TxFeeEstimator = (*intermediaryWallet)(nil)
   916  var _ asset.Bonder = (*baseWallet)(nil)
   917  var _ asset.Authenticator = (*ExchangeWalletSPV)(nil)
   918  var _ asset.Authenticator = (*ExchangeWalletFullNode)(nil)
   919  var _ asset.Authenticator = (*ExchangeWalletAccelerator)(nil)
   920  var _ asset.AddressReturner = (*baseWallet)(nil)
   921  var _ asset.WalletHistorian = (*ExchangeWalletSPV)(nil)
   922  
   923  // RecoveryCfg is the information that is transferred from the old wallet
   924  // to the new one when the wallet is recovered.
   925  type RecoveryCfg struct {
   926  	NumExternalAddresses uint32 `ini:"numexternaladdr"`
   927  	NumInternalAddresses uint32 `ini:"numinternaladdr"`
   928  }
   929  
   930  // GetRecoveryCfg returns information that will help the wallet get
   931  // back to its previous state after it is recreated. Part of the
   932  // Recoverer interface.
   933  func (btc *ExchangeWalletSPV) GetRecoveryCfg() (map[string]string, error) {
   934  	internal, external, err := btc.spvNode.numDerivedAddresses()
   935  	if err != nil {
   936  		return nil, err
   937  	}
   938  
   939  	reCfg := &RecoveryCfg{
   940  		NumInternalAddresses: internal,
   941  		NumExternalAddresses: external,
   942  	}
   943  	cfg, err := config.Mapify(reCfg)
   944  	if err != nil {
   945  		return nil, err
   946  	}
   947  
   948  	return cfg, nil
   949  }
   950  
   951  // Destroy will delete all the wallet files so the wallet can be recreated.
   952  // Part of the Recoverer interface.
   953  func (btc *ExchangeWalletSPV) Move(backupDir string) error {
   954  	err := btc.spvNode.moveWalletData(backupDir)
   955  	if err != nil {
   956  		return fmt.Errorf("unable to move wallet data: %w", err)
   957  	}
   958  
   959  	return nil
   960  }
   961  
   962  // Rescan satisfies the asset.Rescanner interface, and issues a rescan wallet
   963  // command if the backend is an SPV wallet.
   964  func (btc *ExchangeWalletSPV) Rescan(_ context.Context, _ /* bday already stored internally */ uint64) error {
   965  	atomic.StoreInt64(&btc.tipAtConnect, 0) // for progress
   966  	// Caller should start calling SyncStatus on a ticker.
   967  	if err := btc.spvNode.wallet.RescanAsync(); err != nil {
   968  		return err
   969  	}
   970  	btc.receiveTxLastQuery.Store(0)
   971  	// Rescan is occuring asynchronously, so there's probably no point in
   972  	// running checkPendingTxs.
   973  	return nil
   974  }
   975  
   976  // Peers returns a list of peers that the wallet is connected to.
   977  func (btc *ExchangeWalletSPV) Peers() ([]*asset.WalletPeer, error) {
   978  	return btc.spvNode.peers()
   979  }
   980  
   981  // AddPeer connects the wallet to a new peer. The peer's address will be
   982  // persisted and connected to each time the wallet is started up.
   983  func (btc *ExchangeWalletSPV) AddPeer(addr string) error {
   984  	return btc.spvNode.addPeer(addr)
   985  }
   986  
   987  // RemovePeer will remove a peer that was added by AddPeer. This peer may
   988  // still be connected to by the wallet if it discovers it on it's own.
   989  func (btc *ExchangeWalletSPV) RemovePeer(addr string) error {
   990  	return btc.spvNode.removePeer(addr)
   991  }
   992  
   993  var _ asset.FeeRater = (*ExchangeWalletFullNode)(nil)
   994  var _ asset.FeeRater = (*ExchangeWalletNoAuth)(nil)
   995  
   996  // FeeRate satisfies asset.FeeRater.
   997  func (btc *baseWallet) FeeRate() uint64 {
   998  	rate, err := btc.feeRate(1)
   999  	if err != nil {
  1000  		btc.log.Tracef("Failed to get fee rate: %v", err)
  1001  		return 0
  1002  	}
  1003  	return rate
  1004  }
  1005  
  1006  // LogFilePath returns the path to the neutrino log file.
  1007  func (btc *ExchangeWalletSPV) LogFilePath() string {
  1008  	return btc.spvNode.logFilePath()
  1009  }
  1010  
  1011  // WithdrawTx generates a transaction that withdraws all funds to the specified
  1012  // address.
  1013  func (btc *ExchangeWalletSPV) WithdrawTx(ctx context.Context, walletPW []byte, addr btcutil.Address) (_ *wire.MsgTx, err error) {
  1014  	btc.ctx = ctx
  1015  	spvw := btc.node.(*spvWallet)
  1016  	spvw.cl, err = spvw.wallet.Start()
  1017  	if err != nil {
  1018  		return nil, fmt.Errorf("error starting wallet")
  1019  	}
  1020  
  1021  	defer spvw.wallet.Stop()
  1022  
  1023  	if err := spvw.Unlock(walletPW); err != nil {
  1024  		return nil, fmt.Errorf("error unlocking wallet: %w", err)
  1025  	}
  1026  
  1027  	feeRate := btc.FeeRate()
  1028  	if feeRate == 0 {
  1029  		return nil, errors.New("no fee rate")
  1030  	}
  1031  	utxos, _, _, err := btc.cm.SpendableUTXOs(0)
  1032  	if err != nil {
  1033  		return nil, err
  1034  	}
  1035  	var inputsSize uint64
  1036  	coins := make(asset.Coins, 0, len(utxos))
  1037  	for _, utxo := range utxos {
  1038  		op := NewOutput(utxo.TxHash, utxo.Vout, utxo.Amount)
  1039  		coins = append(coins, op)
  1040  		inputsSize += uint64(utxo.Input.VBytes())
  1041  	}
  1042  
  1043  	var baseSize uint64 = dexbtc.MinimumTxOverhead
  1044  	if btc.segwit {
  1045  		baseSize += dexbtc.P2WPKHOutputSize * 2
  1046  	} else {
  1047  		baseSize += dexbtc.P2PKHOutputSize * 2
  1048  	}
  1049  
  1050  	fundedTx, totalIn, _, err := btc.fundedTx(coins)
  1051  	if err != nil {
  1052  		return nil, fmt.Errorf("error adding inputs to transaction: %w", err)
  1053  	}
  1054  
  1055  	fees := feeRate * (inputsSize + baseSize)
  1056  	toSend := totalIn - fees
  1057  
  1058  	signedTx, _, _, err := btc.signTxAndAddChange(fundedTx, addr, toSend, 0, feeRate)
  1059  	if err != nil {
  1060  		return nil, err
  1061  	}
  1062  
  1063  	return signedTx, nil
  1064  }
  1065  
  1066  func parseChainParams(net dex.Network) (*chaincfg.Params, error) {
  1067  	switch net {
  1068  	case dex.Mainnet:
  1069  		return &chaincfg.MainNetParams, nil
  1070  	case dex.Testnet:
  1071  		return &chaincfg.TestNet3Params, nil
  1072  	case dex.Regtest:
  1073  		return &chaincfg.RegressionNetParams, nil
  1074  	}
  1075  	return nil, fmt.Errorf("unknown network ID %v", net)
  1076  }
  1077  
  1078  // NewWallet is the exported constructor by which the DEX will import the
  1079  // exchange wallet.
  1080  func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (asset.Wallet, error) {
  1081  	params, err := parseChainParams(net)
  1082  	if err != nil {
  1083  		return nil, err
  1084  	}
  1085  
  1086  	cloneCFG := &BTCCloneCFG{
  1087  		WalletCFG:           cfg,
  1088  		MinNetworkVersion:   minNetworkVersion,
  1089  		WalletInfo:          WalletInfo,
  1090  		Symbol:              "btc",
  1091  		Logger:              logger,
  1092  		Network:             net,
  1093  		ChainParams:         params,
  1094  		Ports:               dexbtc.RPCPorts,
  1095  		DefaultFallbackFee:  defaultFee,
  1096  		DefaultFeeRateLimit: defaultFeeRateLimit,
  1097  		Segwit:              true,
  1098  		// FeeEstimator must default to rpcFeeRate if not set, but set a
  1099  		// specific external estimator:
  1100  		ExternalFeeEstimator: externalFeeRate,
  1101  		AssetID:              BipID,
  1102  	}
  1103  
  1104  	switch cfg.Type {
  1105  	case walletTypeSPV:
  1106  		return OpenSPVWallet(cloneCFG, openSPVWallet)
  1107  	case walletTypeRPC, walletTypeLegacy:
  1108  		rpcWallet, err := BTCCloneWallet(cloneCFG)
  1109  		if err != nil {
  1110  			return nil, err
  1111  		}
  1112  		return &ExchangeWalletAccelerator{rpcWallet}, nil
  1113  	case walletTypeElectrum:
  1114  		cloneCFG.Ports = dexbtc.NetPorts{} // no default ports
  1115  		ver, err := dex.SemverFromString(needElectrumVersion)
  1116  		if err != nil {
  1117  			return nil, err
  1118  		}
  1119  		cloneCFG.MinElectrumVersion = *ver
  1120  		return ElectrumWallet(cloneCFG)
  1121  	default:
  1122  		makeCustomWallet, ok := customSPVWalletConstructors[cfg.Type]
  1123  		if !ok {
  1124  			return nil, fmt.Errorf("unknown wallet type %q", cfg.Type)
  1125  		}
  1126  
  1127  		// Create custom wallet first and return early if we encounter any
  1128  		// error.
  1129  		btcWallet, err := makeCustomWallet(cfg.Settings, cloneCFG.ChainParams)
  1130  		if err != nil {
  1131  			return nil, fmt.Errorf("btc custom wallet setup error: %v", err)
  1132  		}
  1133  
  1134  		walletConstructor := func(_ string, _ *WalletConfig, _ *chaincfg.Params, _ dex.Logger) BTCWallet {
  1135  			return btcWallet
  1136  		}
  1137  		return OpenSPVWallet(cloneCFG, walletConstructor)
  1138  	}
  1139  }
  1140  
  1141  // BTCCloneWallet creates a wallet backend for a set of network parameters and
  1142  // default network ports. A BTC clone can use this method, possibly in
  1143  // conjunction with ReadCloneParams, to create a ExchangeWallet for other assets
  1144  // with minimal coding.
  1145  func BTCCloneWallet(cfg *BTCCloneCFG) (*ExchangeWalletFullNode, error) {
  1146  	iw, err := btcCloneWallet(cfg)
  1147  	if err != nil {
  1148  		return nil, err
  1149  	}
  1150  	return &ExchangeWalletFullNode{iw, &authAddOn{iw.node}}, nil
  1151  }
  1152  
  1153  // BTCCloneWalletNoAuth is like BTCCloneWallet but the wallet created does not
  1154  // implement asset.Authenticator.
  1155  func BTCCloneWalletNoAuth(cfg *BTCCloneCFG) (*ExchangeWalletNoAuth, error) {
  1156  	iw, err := btcCloneWallet(cfg)
  1157  	if err != nil {
  1158  		return nil, err
  1159  	}
  1160  	return &ExchangeWalletNoAuth{iw}, nil
  1161  }
  1162  
  1163  // btcCloneWallet creates a wallet backend for a set of network parameters and
  1164  // default network ports.
  1165  func btcCloneWallet(cfg *BTCCloneCFG) (*intermediaryWallet, error) {
  1166  	clientCfg, client, err := parseRPCWalletConfig(cfg.WalletCFG.Settings, cfg.Symbol, cfg.Network, cfg.Ports, cfg.SingularWallet)
  1167  	if err != nil {
  1168  		return nil, err
  1169  	}
  1170  
  1171  	iw, err := newRPCWallet(client, cfg, clientCfg)
  1172  	if err != nil {
  1173  		return nil, fmt.Errorf("error creating %s exchange wallet: %v", cfg.Symbol,
  1174  			err)
  1175  	}
  1176  
  1177  	return iw, nil
  1178  }
  1179  
  1180  // newRPCWallet creates the ExchangeWallet and starts the block monitor.
  1181  func newRPCWallet(requester RawRequester, cfg *BTCCloneCFG, parsedCfg *RPCWalletConfig) (*intermediaryWallet, error) {
  1182  	btc, err := newUnconnectedWallet(cfg, &parsedCfg.WalletConfig)
  1183  	if err != nil {
  1184  		return nil, err
  1185  	}
  1186  
  1187  	blockDeserializer := cfg.BlockDeserializer
  1188  	if blockDeserializer == nil {
  1189  		blockDeserializer = deserializeBlock
  1190  	}
  1191  
  1192  	core := &rpcCore{
  1193  		rpcConfig:         &parsedCfg.RPCConfig,
  1194  		cloneParams:       cfg,
  1195  		segwit:            cfg.Segwit,
  1196  		decodeAddr:        btc.decodeAddr,
  1197  		stringAddr:        btc.stringAddr,
  1198  		deserializeBlock:  blockDeserializer,
  1199  		legacyRawSends:    cfg.LegacyRawFeeLimit,
  1200  		minNetworkVersion: cfg.MinNetworkVersion,
  1201  		log:               cfg.Logger.SubLogger("RPC"),
  1202  		chainParams:       cfg.ChainParams,
  1203  		omitAddressType:   cfg.OmitAddressType,
  1204  		legacySignTx:      cfg.LegacySignTxRPC,
  1205  		booleanGetBlock:   cfg.BooleanGetBlockRPC,
  1206  		unlockSpends:      cfg.UnlockSpends,
  1207  
  1208  		deserializeTx:      btc.deserializeTx,
  1209  		serializeTx:        btc.serializeTx,
  1210  		hashTx:             btc.hashTx,
  1211  		numericGetRawTxRPC: cfg.NumericGetRawRPC,
  1212  		manualMedianTime:   cfg.ManualMedianTime,
  1213  
  1214  		legacyValidateAddressRPC: cfg.LegacyValidateAddressRPC,
  1215  		omitRPCOptionsArg:        cfg.OmitRPCOptionsArg,
  1216  		privKeyFunc:              cfg.PrivKeyFunc,
  1217  	}
  1218  	core.requesterV.Store(requester)
  1219  	node := newRPCClient(core)
  1220  	btc.setNode(node)
  1221  	w := &intermediaryWallet{
  1222  		baseWallet:     btc,
  1223  		txFeeEstimator: node,
  1224  		tipRedeemer:    node,
  1225  	}
  1226  
  1227  	w.prepareRedemptionFinder()
  1228  	return w, nil
  1229  }
  1230  
  1231  func decodeAddress(addr string, params *chaincfg.Params) (btcutil.Address, error) {
  1232  	a, err := btcutil.DecodeAddress(addr, params)
  1233  	if err != nil {
  1234  		return nil, err
  1235  	}
  1236  	if !a.IsForNet(params) {
  1237  		return nil, errors.New("wrong network")
  1238  	}
  1239  	return a, nil
  1240  }
  1241  
  1242  func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWallet, error) {
  1243  	// Make sure we can use the specified wallet directory.
  1244  	walletDir := filepath.Join(cfg.WalletCFG.DataDir, cfg.ChainParams.Name)
  1245  	if err := os.MkdirAll(walletDir, 0744); err != nil {
  1246  		return nil, fmt.Errorf("error creating wallet directory: %w", err)
  1247  	}
  1248  
  1249  	baseCfg, err := readBaseWalletConfig(walletCfg)
  1250  	if err != nil {
  1251  		return nil, err
  1252  	}
  1253  
  1254  	addrDecoder := decodeAddress
  1255  	if cfg.AddressDecoder != nil {
  1256  		addrDecoder = cfg.AddressDecoder
  1257  	}
  1258  
  1259  	nonSegwitSigner := rawTxInSig
  1260  	if cfg.NonSegwitSigner != nil {
  1261  		nonSegwitSigner = cfg.NonSegwitSigner
  1262  	}
  1263  
  1264  	initTxSize := uint64(cfg.InitTxSize)
  1265  	if initTxSize == 0 {
  1266  		if cfg.Segwit {
  1267  			initTxSize = dexbtc.InitTxSizeSegwit
  1268  		} else {
  1269  			initTxSize = dexbtc.InitTxSize
  1270  		}
  1271  	}
  1272  
  1273  	initTxSizeBase := uint64(cfg.InitTxSizeBase)
  1274  	if initTxSizeBase == 0 {
  1275  		if cfg.Segwit {
  1276  			initTxSizeBase = dexbtc.InitTxSizeBaseSegwit
  1277  		} else {
  1278  			initTxSizeBase = dexbtc.InitTxSizeBase
  1279  		}
  1280  	}
  1281  
  1282  	txDeserializer := cfg.TxDeserializer
  1283  	if txDeserializer == nil {
  1284  		txDeserializer = msgTxFromBytes
  1285  	}
  1286  
  1287  	txSerializer := cfg.TxSerializer
  1288  	if txSerializer == nil {
  1289  		txSerializer = serializeMsgTx
  1290  	}
  1291  
  1292  	txSizeCalculator := cfg.TxSizeCalculator
  1293  	if txSizeCalculator == nil {
  1294  		txSizeCalculator = dexbtc.MsgTxVBytes
  1295  	}
  1296  
  1297  	txHasher := cfg.TxHasher
  1298  	if txHasher == nil {
  1299  		txHasher = hashTx
  1300  	}
  1301  
  1302  	addrStringer := cfg.AddressStringer
  1303  	if addrStringer == nil {
  1304  		addrStringer = stringifyAddress
  1305  	}
  1306  
  1307  	txVersion := cfg.TxVersion
  1308  	if txVersion == nil {
  1309  		txVersion = func() int32 { return wire.TxVersion }
  1310  	}
  1311  
  1312  	addressRecyler, err := NewAddressRecycler(filepath.Join(walletDir, "recycled-addrs.txt"), cfg.Logger)
  1313  	if err != nil {
  1314  		return nil, err
  1315  	}
  1316  
  1317  	var feeCache *feeRateCache
  1318  	if cfg.ExternalFeeEstimator != nil {
  1319  		feeCache = &feeRateCache{
  1320  			f:         cfg.ExternalFeeEstimator,
  1321  			shelfLife: cfg.ExternalFeeShelfLife,
  1322  		}
  1323  	}
  1324  
  1325  	w := &baseWallet{
  1326  		symbol:            cfg.Symbol,
  1327  		chainParams:       cfg.ChainParams,
  1328  		cloneParams:       cfg,
  1329  		log:               cfg.Logger,
  1330  		emit:              cfg.WalletCFG.Emit,
  1331  		peersChange:       cfg.WalletCFG.PeersChange,
  1332  		minNetworkVersion: cfg.MinNetworkVersion,
  1333  		dustLimit:         cfg.ConstantDustLimit,
  1334  		useLegacyBalance:  cfg.LegacyBalance,
  1335  		balanceFunc:       cfg.BalanceFunc,
  1336  		segwit:            cfg.Segwit,
  1337  		initTxSize:        initTxSize,
  1338  		initTxSizeBase:    initTxSizeBase,
  1339  		signNonSegwit:     nonSegwitSigner,
  1340  		localFeeRate:      cfg.FeeEstimator,
  1341  		feeCache:          feeCache,
  1342  		decodeAddr:        addrDecoder,
  1343  		stringAddr:        addrStringer,
  1344  		walletInfo:        cfg.WalletInfo,
  1345  		deserializeTx:     txDeserializer,
  1346  		serializeTx:       txSerializer,
  1347  		hashTx:            txHasher,
  1348  		calcTxSize:        txSizeCalculator,
  1349  		txVersion:         txVersion,
  1350  		Network:           cfg.Network,
  1351  		pendingTxs:        make(map[chainhash.Hash]ExtendedWalletTx),
  1352  		walletDir:         walletDir,
  1353  		ar:                addressRecyler,
  1354  	}
  1355  	w.cfgV.Store(baseCfg)
  1356  
  1357  	// Default to the BTC RPC estimator (see LTC). Consumers can use
  1358  	// noLocalFeeRate or a similar dummy function to power feeRate() requests
  1359  	// with only an external fee rate source available. Otherwise, all method
  1360  	// calls must provide a rate or accept the configured fallback.
  1361  	if w.localFeeRate == nil {
  1362  		w.localFeeRate = rpcFeeRate
  1363  	}
  1364  
  1365  	return w, nil
  1366  }
  1367  
  1368  // noLocalFeeRate is a dummy function for BTCCloneCFG.FeeEstimator for a wallet
  1369  // instance that cannot support a local fee rate estimate but has an external
  1370  // fee rate source.
  1371  func noLocalFeeRate(ctx context.Context, rr RawRequester, u uint64) (uint64, error) {
  1372  	return 0, errors.New("no local fee rate estimate possible")
  1373  }
  1374  
  1375  // OpenSPVWallet opens the previously created native SPV wallet.
  1376  func OpenSPVWallet(cfg *BTCCloneCFG, walletConstructor BTCWalletConstructor) (*ExchangeWalletSPV, error) {
  1377  	walletCfg := new(WalletConfig)
  1378  	err := config.Unmapify(cfg.WalletCFG.Settings, walletCfg)
  1379  	if err != nil {
  1380  		return nil, err
  1381  	}
  1382  
  1383  	// SPV wallets without a FeeEstimator will default to any enabled external
  1384  	// fee estimator.
  1385  	if cfg.FeeEstimator == nil {
  1386  		cfg.FeeEstimator = noLocalFeeRate
  1387  	}
  1388  
  1389  	btc, err := newUnconnectedWallet(cfg, walletCfg)
  1390  	if err != nil {
  1391  		return nil, err
  1392  	}
  1393  
  1394  	spvw := &spvWallet{
  1395  		chainParams: cfg.ChainParams,
  1396  		cfg:         walletCfg,
  1397  		dir:         filepath.Join(cfg.WalletCFG.DataDir, cfg.ChainParams.Name),
  1398  		log:         cfg.Logger.SubLogger("SPV"),
  1399  		tipChan:     make(chan *BlockVector, 8),
  1400  		decodeAddr:  btc.decodeAddr,
  1401  	}
  1402  
  1403  	spvw.BlockFiltersScanner = NewBlockFiltersScanner(spvw, spvw.log)
  1404  	spvw.wallet = walletConstructor(spvw.dir, spvw.cfg, spvw.chainParams, spvw.log)
  1405  	btc.setNode(spvw)
  1406  
  1407  	// Set account number for easy reference.
  1408  	spvw.acctNum = spvw.wallet.AccountInfo().AccountNumber
  1409  
  1410  	w := &ExchangeWalletSPV{
  1411  		intermediaryWallet: &intermediaryWallet{
  1412  			baseWallet:     btc,
  1413  			txFeeEstimator: spvw,
  1414  			tipRedeemer:    spvw,
  1415  		},
  1416  		authAddOn: &authAddOn{spvw},
  1417  		spvNode:   spvw,
  1418  	}
  1419  	w.prepareRedemptionFinder()
  1420  	return w, nil
  1421  }
  1422  
  1423  func (btc *baseWallet) setNode(node Wallet) {
  1424  	btc.node = node
  1425  	btc.cm = NewCoinManager(
  1426  		btc.log,
  1427  		btc.chainParams,
  1428  		func(val, lots, maxFeeRate uint64, reportChange bool) EnoughFunc {
  1429  			return orderEnough(val, lots, maxFeeRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, reportChange)
  1430  		},
  1431  		func() ([]*ListUnspentResult, error) { // list
  1432  			return node.listUnspent()
  1433  		},
  1434  		func(unlock bool, ops []*Output) error { // lock
  1435  			return node.lockUnspent(unlock, ops)
  1436  		},
  1437  		func() ([]*RPCOutpoint, error) { // listLocked
  1438  			return node.listLockUnspent()
  1439  		},
  1440  		func(txHash *chainhash.Hash, vout uint32) (*wire.TxOut, error) {
  1441  			txRaw, _, err := btc.rawWalletTx(txHash)
  1442  			if err != nil {
  1443  				return nil, err
  1444  			}
  1445  			msgTx, err := btc.deserializeTx(txRaw)
  1446  			if err != nil {
  1447  				btc.log.Warnf("Invalid transaction %v (%x): %v", txHash, txRaw, err)
  1448  				return nil, nil
  1449  			}
  1450  			if vout >= uint32(len(msgTx.TxOut)) {
  1451  				btc.log.Warnf("Invalid vout %d for %v", vout, txHash)
  1452  				return nil, nil
  1453  			}
  1454  			return msgTx.TxOut[vout], nil
  1455  		},
  1456  		func(addr btcutil.Address) (string, error) {
  1457  			return btc.stringAddr(addr, btc.chainParams)
  1458  		},
  1459  	)
  1460  }
  1461  
  1462  func (btc *intermediaryWallet) prepareRedemptionFinder() {
  1463  	btc.rf = NewRedemptionFinder(
  1464  		btc.log,
  1465  		btc.tipRedeemer.getWalletTransaction,
  1466  		btc.tipRedeemer.getBlockHeight,
  1467  		btc.tipRedeemer.getBlock,
  1468  		btc.tipRedeemer.getBlockHeader,
  1469  		btc.hashTx,
  1470  		btc.deserializeTx,
  1471  		btc.tipRedeemer.getBestBlockHeight,
  1472  		btc.tipRedeemer.searchBlockForRedemptions,
  1473  		btc.tipRedeemer.getBlockHash,
  1474  		btc.tipRedeemer.findRedemptionsInMempool,
  1475  	)
  1476  }
  1477  
  1478  // Info returns basic information about the wallet and asset.
  1479  func (btc *baseWallet) Info() *asset.WalletInfo {
  1480  	return btc.walletInfo
  1481  }
  1482  
  1483  func (btc *baseWallet) txHistoryDBPath(walletID string) string {
  1484  	return filepath.Join(btc.walletDir, fmt.Sprintf("txhistorydb-%s", walletID))
  1485  }
  1486  
  1487  // findExistingAddressBasedTxHistoryDB finds the path of a tx history db that
  1488  // was created using an address controlled by the wallet. This should only be
  1489  // used for wallets that are unable to generate a fingerprint.
  1490  func (btc *baseWallet) findExistingAddressBasedTxHistoryDB() (string, error) {
  1491  	dir, err := os.Open(btc.walletDir)
  1492  	if err != nil {
  1493  		return "", fmt.Errorf("error opening wallet directory: %w", err)
  1494  	}
  1495  	defer dir.Close()
  1496  
  1497  	entries, err := dir.Readdir(0)
  1498  	if err != nil {
  1499  		return "", fmt.Errorf("error reading wallet directory: %w", err)
  1500  	}
  1501  
  1502  	pattern := regexp.MustCompile(`^txhistorydb-(.+)$`)
  1503  
  1504  	for _, entry := range entries {
  1505  		if !entry.IsDir() {
  1506  			continue
  1507  		}
  1508  
  1509  		match := pattern.FindStringSubmatch(entry.Name())
  1510  		if match == nil {
  1511  			continue
  1512  		}
  1513  
  1514  		address := match[1]
  1515  		owns, err := btc.OwnsDepositAddress(address)
  1516  		if err != nil {
  1517  			continue
  1518  		}
  1519  		if owns {
  1520  			return filepath.Join(btc.walletDir, entry.Name()), nil
  1521  		}
  1522  	}
  1523  
  1524  	return "", nil
  1525  }
  1526  
  1527  func (btc *baseWallet) startTxHistoryDB(ctx context.Context) (*sync.WaitGroup, error) {
  1528  	var dbPath string
  1529  	fingerPrint, err := btc.node.fingerprint()
  1530  	if err == nil && fingerPrint != "" {
  1531  		dbPath = btc.txHistoryDBPath(fingerPrint)
  1532  	}
  1533  
  1534  	if dbPath == "" {
  1535  		addressPath, err := btc.findExistingAddressBasedTxHistoryDB()
  1536  		if err != nil {
  1537  			return nil, err
  1538  		}
  1539  		if addressPath != "" {
  1540  			dbPath = addressPath
  1541  		}
  1542  	}
  1543  
  1544  	if dbPath == "" {
  1545  		depositAddr, err := btc.DepositAddress()
  1546  		if err != nil {
  1547  			return nil, fmt.Errorf("error getting deposit address: %w", err)
  1548  		}
  1549  		dbPath = btc.txHistoryDBPath(depositAddr)
  1550  	}
  1551  
  1552  	btc.log.Debugf("Using tx history db at %s", dbPath)
  1553  
  1554  	db := NewBadgerTxDB(dbPath, btc.log)
  1555  	btc.txHistoryDB.Store(db)
  1556  
  1557  	wg, err := db.Connect(ctx)
  1558  	if err != nil {
  1559  		return nil, err
  1560  	}
  1561  
  1562  	pendingTxs, err := db.GetPendingTxs()
  1563  	if err != nil {
  1564  		return nil, fmt.Errorf("failed to load unconfirmed txs: %v", err)
  1565  	}
  1566  
  1567  	btc.pendingTxsMtx.Lock()
  1568  	for _, tx := range pendingTxs {
  1569  		txHash, err := chainhash.NewHashFromStr(tx.ID)
  1570  		if err != nil {
  1571  			btc.log.Errorf("Invalid txid %v from tx history db: %v", tx.ID, err)
  1572  			continue
  1573  		}
  1574  		btc.pendingTxs[*txHash] = *tx
  1575  	}
  1576  	btc.pendingTxsMtx.Unlock()
  1577  
  1578  	lastQuery, err := db.GetLastReceiveTxQuery()
  1579  	if errors.Is(err, ErrNeverQueried) {
  1580  		lastQuery = 0
  1581  	} else if err != nil {
  1582  		return nil, fmt.Errorf("failed to load last query time: %v", err)
  1583  	}
  1584  
  1585  	btc.receiveTxLastQuery.Store(lastQuery)
  1586  
  1587  	return wg, nil
  1588  }
  1589  
  1590  // connect is shared between Wallet implementations that may have different
  1591  // monitoring goroutines or other configuration set after connect. For example
  1592  // an asset.Wallet implementation that embeds baseWallet may override Connect to
  1593  // perform monitoring differently, but still use this connect method to start up
  1594  // the btc.Wallet (the btc.node field).
  1595  func (btc *baseWallet) connect(ctx context.Context) (*sync.WaitGroup, error) {
  1596  	btc.ctx = ctx
  1597  	var wg sync.WaitGroup
  1598  	if err := btc.node.connect(ctx, &wg); err != nil {
  1599  		return nil, err
  1600  	}
  1601  
  1602  	// Initialize the best block.
  1603  	bestBlockHdr, err := btc.node.getBestBlockHeader()
  1604  	if err != nil {
  1605  		return nil, fmt.Errorf("error initializing best block for %s: %w", btc.symbol, err)
  1606  	}
  1607  	bestBlockHash, err := chainhash.NewHashFromStr(bestBlockHdr.Hash)
  1608  	if err != nil {
  1609  		return nil, fmt.Errorf("invalid best block hash from %s node: %v", btc.symbol, err)
  1610  	}
  1611  	// Check for method unknown error for feeRate method.
  1612  	_, err = btc.feeRate(1)
  1613  	if isMethodNotFoundErr(err) {
  1614  		return nil, fmt.Errorf("fee estimation method not found. Are you configured for the correct RPC?")
  1615  	}
  1616  
  1617  	bestBlock := &BlockVector{bestBlockHdr.Height, *bestBlockHash}
  1618  	btc.log.Infof("Connected wallet with current best block %v (%d)", bestBlock.Hash, bestBlock.Height)
  1619  	btc.tipMtx.Lock()
  1620  	btc.currentTip = bestBlock
  1621  	btc.tipMtx.Unlock()
  1622  	atomic.StoreInt64(&btc.tipAtConnect, btc.currentTip.Height)
  1623  
  1624  	wg.Add(1)
  1625  	go func() {
  1626  		defer wg.Done()
  1627  		<-ctx.Done()
  1628  		btc.ar.WriteRecycledAddrsToFile()
  1629  	}()
  1630  
  1631  	return &wg, nil
  1632  }
  1633  
  1634  // Connect connects the wallet to the btc.Wallet backend and starts monitoring
  1635  // blocks and peers. Satisfies the dex.Connector interface.
  1636  func (btc *intermediaryWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) {
  1637  	wg, err := btc.connect(ctx)
  1638  	if err != nil {
  1639  		return nil, err
  1640  	}
  1641  
  1642  	dbWG, err := btc.startTxHistoryDB(ctx)
  1643  	if err != nil {
  1644  		return nil, err
  1645  	}
  1646  
  1647  	wg.Add(1)
  1648  	go func() {
  1649  		defer wg.Done()
  1650  		dbWG.Wait()
  1651  	}()
  1652  
  1653  	wg.Add(1)
  1654  	go func() {
  1655  		defer wg.Done()
  1656  		btc.watchBlocks(ctx)
  1657  		btc.rf.CancelRedemptionSearches()
  1658  	}()
  1659  
  1660  	wg.Add(1)
  1661  	go func() {
  1662  		defer wg.Done()
  1663  		btc.monitorPeers(ctx)
  1664  	}()
  1665  
  1666  	wg.Add(1)
  1667  	func() {
  1668  		defer wg.Done()
  1669  		btc.tipMtx.RLock()
  1670  		tip := btc.currentTip
  1671  		btc.tipMtx.RUnlock()
  1672  		go btc.syncTxHistory(uint64(tip.Height))
  1673  	}()
  1674  
  1675  	return wg, nil
  1676  }
  1677  
  1678  // Reconfigure attempts to reconfigure the wallet.
  1679  func (btc *baseWallet) Reconfigure(ctx context.Context, cfg *asset.WalletConfig, currentAddress string) (restart bool, err error) {
  1680  	// See what the node says.
  1681  	restart, err = btc.node.reconfigure(cfg, currentAddress)
  1682  	if err != nil {
  1683  		return false, err
  1684  	}
  1685  
  1686  	parsedCfg := new(RPCWalletConfig)
  1687  	if err = config.Unmapify(cfg.Settings, parsedCfg); err != nil {
  1688  		return false, err
  1689  	}
  1690  	walletCfg := &parsedCfg.WalletConfig
  1691  
  1692  	// Make sure the configuration parameters are valid. If restart is required,
  1693  	// this validates the configuration parameters, preventing an ugly surprise
  1694  	// when the caller attempts to open the wallet. If no restart is required,
  1695  	// we'll swap out the configuration parameters right away.
  1696  	newCfg, err := readBaseWalletConfig(walletCfg)
  1697  	if err != nil {
  1698  		return false, err
  1699  	}
  1700  	btc.cfgV.Store(newCfg) // probably won't matter if restart/reinit required
  1701  
  1702  	return restart, nil
  1703  }
  1704  
  1705  // IsDust checks if the tx output's value is dust. If the dustLimit is set, it
  1706  // is compared against that, otherwise the formula in dexbtc.IsDust is used.
  1707  func (btc *baseWallet) IsDust(txOut *wire.TxOut, minRelayTxFee uint64) bool {
  1708  	if btc.dustLimit > 0 {
  1709  		return txOut.Value < int64(btc.dustLimit)
  1710  	}
  1711  	return dexbtc.IsDust(txOut, minRelayTxFee)
  1712  }
  1713  
  1714  // GetBlockchainInfoResult models the data returned from the getblockchaininfo
  1715  // command.
  1716  type GetBlockchainInfoResult struct {
  1717  	Chain         string `json:"chain"`
  1718  	Blocks        int64  `json:"blocks"`
  1719  	Headers       int64  `json:"headers"`
  1720  	BestBlockHash string `json:"bestblockhash"`
  1721  	// InitialBlockDownload will be true if the node is still in the initial
  1722  	// block download mode.
  1723  	InitialBlockDownload *bool `json:"initialblockdownload"`
  1724  	// InitialBlockDownloadComplete will be true if this node has completed its
  1725  	// initial block download and is expected to be synced to the network.
  1726  	// Zcash uses this terminology instead of initialblockdownload.
  1727  	InitialBlockDownloadComplete *bool `json:"initial_block_download_complete"`
  1728  }
  1729  
  1730  func (r *GetBlockchainInfoResult) Syncing() bool {
  1731  	if r.InitialBlockDownloadComplete != nil && *r.InitialBlockDownloadComplete {
  1732  		return false
  1733  	}
  1734  	if r.InitialBlockDownload != nil && *r.InitialBlockDownload {
  1735  		return true
  1736  	}
  1737  	return r.Headers-r.Blocks > 1
  1738  }
  1739  
  1740  // SyncStatus is information about the blockchain sync status.
  1741  func (btc *baseWallet) SyncStatus() (*asset.SyncStatus, error) {
  1742  	ss, err := btc.node.syncStatus()
  1743  	if err != nil {
  1744  		return nil, err
  1745  	}
  1746  	ss.StartingBlocks = uint64(atomic.LoadInt64(&btc.tipAtConnect))
  1747  	if ss.Synced {
  1748  		numPeers, err := btc.node.peerCount()
  1749  		if err != nil {
  1750  			return nil, err
  1751  		}
  1752  		ss.Synced = numPeers > 0
  1753  	}
  1754  	return ss, nil
  1755  }
  1756  
  1757  // OwnsDepositAddress indicates if the provided address can be used
  1758  // to deposit funds into the wallet.
  1759  func (btc *baseWallet) OwnsDepositAddress(address string) (bool, error) {
  1760  	addr, err := btc.decodeAddr(address, btc.chainParams) // maybe move into the ownsAddress impls
  1761  	if err != nil {
  1762  		return false, err
  1763  	}
  1764  	return btc.node.ownsAddress(addr)
  1765  }
  1766  
  1767  func (btc *baseWallet) balance() (*asset.Balance, error) {
  1768  	if btc.balanceFunc != nil {
  1769  		locked, err := btc.lockedSats()
  1770  		if err != nil {
  1771  			return nil, fmt.Errorf("(legacy) lockedSats error: %w", err)
  1772  		}
  1773  		return btc.balanceFunc(btc.ctx, locked)
  1774  	}
  1775  	if btc.useLegacyBalance {
  1776  		return btc.legacyBalance()
  1777  	}
  1778  	balances, err := btc.node.balances()
  1779  	if err != nil {
  1780  		return nil, err
  1781  	}
  1782  	locked, err := btc.lockedSats()
  1783  	if err != nil {
  1784  		return nil, err
  1785  	}
  1786  
  1787  	return &asset.Balance{
  1788  		Available: toSatoshi(balances.Mine.Trusted) - locked,
  1789  		Immature:  toSatoshi(balances.Mine.Immature + balances.Mine.Untrusted),
  1790  		Locked:    locked,
  1791  		Other:     make(map[asset.BalanceCategory]asset.CustomBalance),
  1792  	}, nil
  1793  }
  1794  
  1795  // Balance should return the total available funds in the wallet.
  1796  func (btc *baseWallet) Balance() (*asset.Balance, error) {
  1797  	bal, err := btc.balance()
  1798  	if err != nil {
  1799  		return nil, err
  1800  	}
  1801  
  1802  	reserves := btc.bondReserves.Load()
  1803  	if reserves > bal.Available {
  1804  		btc.log.Warnf("Available balance is below configured reserves: %f < %f",
  1805  			toBTC(bal.Available), toBTC(reserves))
  1806  		bal.ReservesDeficit = reserves - bal.Available
  1807  		reserves = bal.Available
  1808  	}
  1809  
  1810  	bal.BondReserves = reserves
  1811  	bal.Available -= reserves
  1812  	bal.Locked += reserves
  1813  
  1814  	return bal, nil
  1815  }
  1816  
  1817  func bondsFeeBuffer(segwit bool, highFeeRate uint64) uint64 {
  1818  	const inputCount uint64 = 8 // plan for lots of inputs
  1819  	var largeBondTxSize uint64
  1820  	if segwit {
  1821  		largeBondTxSize = dexbtc.MinimumTxOverhead + dexbtc.P2WSHOutputSize + 1 + dexbtc.BondPushDataSize +
  1822  			dexbtc.P2WPKHOutputSize + inputCount*dexbtc.RedeemP2WPKHInputSize
  1823  	} else {
  1824  		largeBondTxSize = dexbtc.MinimumTxOverhead + dexbtc.P2SHOutputSize + 1 + dexbtc.BondPushDataSize +
  1825  			dexbtc.P2PKHOutputSize + inputCount*dexbtc.RedeemP2PKHInputSize
  1826  	}
  1827  
  1828  	// Normally we can plan on just 2 parallel "tracks" (single bond overlap
  1829  	// when bonds are expired and waiting to refund) but that may increase
  1830  	// temporarily if target tier is adjusted up.
  1831  	const parallelTracks uint64 = 4
  1832  	return parallelTracks * largeBondTxSize * highFeeRate
  1833  }
  1834  
  1835  // legacyBalance is used for clones that are < node version 0.18 and so don't
  1836  // have 'getbalances'.
  1837  func (btc *baseWallet) legacyBalance() (*asset.Balance, error) {
  1838  	cl, ok := btc.node.(*rpcClient)
  1839  	if !ok {
  1840  		return nil, fmt.Errorf("legacyBalance unimplemented for spv clients")
  1841  	}
  1842  
  1843  	locked, err := btc.lockedSats()
  1844  	if err != nil {
  1845  		return nil, fmt.Errorf("(legacy) lockedSats error: %w", err)
  1846  	}
  1847  
  1848  	walletInfo, err := cl.GetWalletInfo()
  1849  	if err != nil {
  1850  		return nil, fmt.Errorf("(legacy) GetWalletInfo error: %w", err)
  1851  	}
  1852  
  1853  	return &asset.Balance{
  1854  		Available: toSatoshi(walletInfo.Balance+walletInfo.UnconfirmedBalance) - locked,
  1855  		Immature:  toSatoshi(walletInfo.ImmatureBalance),
  1856  		Locked:    locked,
  1857  		Other:     make(map[asset.BalanceCategory]asset.CustomBalance),
  1858  	}, nil
  1859  }
  1860  
  1861  // feeRate returns the current optimal fee rate in sat / byte using the
  1862  // estimatesmartfee RPC or an external API if configured and enabled.
  1863  func (btc *baseWallet) feeRate(confTarget uint64) (feeRate uint64, err error) {
  1864  	allowExternalFeeRate := btc.apiFeeFallback()
  1865  	// Because of the problems Bitcoin's unstable estimatesmartfee has caused,
  1866  	// we won't use it.
  1867  	if btc.symbol != "btc" || !allowExternalFeeRate {
  1868  		feeRate, err := btc.localFeeRate(btc.ctx, btc.node, confTarget) // e.g. rpcFeeRate
  1869  		if err == nil {
  1870  			return feeRate, nil
  1871  		} else if !allowExternalFeeRate {
  1872  			return 0, fmt.Errorf("error getting local rate and external rates are disabled: %w", err)
  1873  		}
  1874  	}
  1875  
  1876  	if btc.feeCache == nil {
  1877  		return 0, fmt.Errorf("external fee rate fetcher not configured")
  1878  	}
  1879  
  1880  	// External estimate fallback. Error if it exceeds our limit, and the caller
  1881  	// may use btc.fallbackFeeRate(), as in targetFeeRateWithFallback.
  1882  	feeRate, err = btc.feeCache.rate(btc.ctx, btc.Network) // e.g. externalFeeRate
  1883  	if err != nil {
  1884  		btc.log.Meter("feeRate.rate.fail", time.Hour).Errorf("Failed to get fee rate from external API: %v", err)
  1885  		return 0, nil
  1886  	}
  1887  	if feeRate <= 0 || feeRate > btc.feeRateLimit() { // but fetcher shouldn't return <= 0 without error
  1888  		return 0, fmt.Errorf("external fee rate %v exceeds configured limit", feeRate)
  1889  	}
  1890  	btc.log.Tracef("Retrieved fee rate from external API: %v", feeRate)
  1891  	return feeRate, nil
  1892  }
  1893  
  1894  func rpcFeeRate(ctx context.Context, rr RawRequester, confTarget uint64) (uint64, error) {
  1895  	feeResult, err := estimateSmartFee(ctx, rr, confTarget, &btcjson.EstimateModeEconomical)
  1896  	if err != nil {
  1897  		return 0, err
  1898  	}
  1899  
  1900  	if len(feeResult.Errors) > 0 {
  1901  		return 0, errors.New(strings.Join(feeResult.Errors, "; "))
  1902  	}
  1903  	if feeResult.FeeRate == nil {
  1904  		return 0, fmt.Errorf("no fee rate available")
  1905  	}
  1906  	satPerKB, err := btcutil.NewAmount(*feeResult.FeeRate) // satPerKB is 0 when err != nil
  1907  	if err != nil {
  1908  		return 0, err
  1909  	}
  1910  	if satPerKB <= 0 {
  1911  		return 0, errors.New("zero or negative fee rate")
  1912  	}
  1913  	return uint64(dex.IntDivUp(int64(satPerKB), 1000)), nil
  1914  }
  1915  
  1916  // externalFeeRate gets the fee rate from the external API and returns it
  1917  // in sats/vByte.
  1918  func externalFeeRate(ctx context.Context, net dex.Network) (uint64, error) {
  1919  	// https://mempool.space/docs/api
  1920  	var uri string
  1921  	if net == dex.Testnet {
  1922  		uri = "https://mempool.space/testnet/api/v1/fees/recommended"
  1923  	} else {
  1924  		uri = "https://mempool.space/api/v1/fees/recommended"
  1925  	}
  1926  	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
  1927  	defer cancel()
  1928  	var resp struct {
  1929  		Fastest  uint64 `json:"fastestFee"`
  1930  		HalfHour uint64 `json:"halfHourFee"`
  1931  		Hour     uint64 `json:"hourFee"`
  1932  		Economy  uint64 `json:"economyFee"`
  1933  		Minimum  uint64 `json:"minimumFee"`
  1934  	}
  1935  	if err := dexnet.Get(ctx, uri, &resp, dexnet.WithSizeLimit(1<<14)); err != nil {
  1936  		return 0, err
  1937  	}
  1938  	if resp.Fastest == 0 {
  1939  		return 0, errors.New("no fee rate found")
  1940  	}
  1941  	return resp.Fastest, nil
  1942  }
  1943  
  1944  type amount uint64
  1945  
  1946  func (a amount) String() string {
  1947  	return strconv.FormatFloat(btcutil.Amount(a).ToBTC(), 'f', -1, 64) // dec, but no trailing zeros
  1948  }
  1949  
  1950  // targetFeeRateWithFallback attempts to get a fresh fee rate for the target
  1951  // number of confirmations, but falls back to the suggestion or fallbackFeeRate
  1952  // via feeRateWithFallback.
  1953  func (btc *baseWallet) targetFeeRateWithFallback(confTarget, feeSuggestion uint64) uint64 {
  1954  	feeRate, err := btc.feeRate(confTarget)
  1955  	if err == nil && feeRate > 0 {
  1956  		btc.log.Tracef("Obtained estimate for %d-conf fee rate, %d", confTarget, feeRate)
  1957  		return feeRate
  1958  	}
  1959  	btc.log.Tracef("no %d-conf feeRate available: %v", confTarget, err)
  1960  	return btc.feeRateWithFallback(feeSuggestion)
  1961  }
  1962  
  1963  // feeRateWithFallback filters the suggested fee rate by ensuring it is within
  1964  // limits. If not, the configured fallbackFeeRate is returned and a warning
  1965  // logged.
  1966  func (btc *baseWallet) feeRateWithFallback(feeSuggestion uint64) uint64 {
  1967  	if feeSuggestion > 0 && feeSuggestion < btc.feeRateLimit() {
  1968  		btc.log.Tracef("feeRateWithFallback using caller's suggestion for fee rate, %d.",
  1969  			feeSuggestion)
  1970  		return feeSuggestion
  1971  	}
  1972  	btc.log.Warnf("Unable to get optimal fee rate, using fallback of %d", btc.fallbackFeeRate)
  1973  	return btc.fallbackFeeRate()
  1974  }
  1975  
  1976  // MaxOrder generates information about the maximum order size and associated
  1977  // fees that the wallet can support for the given DEX configuration. The fees are an
  1978  // estimate based on current network conditions, and will be <= the fees
  1979  // associated with nfo.MaxFeeRate. For quote assets, the caller will have to
  1980  // calculate lotSize based on a rate conversion from the base asset's lot size.
  1981  // lotSize must not be zero and will cause a panic if so.
  1982  func (btc *baseWallet) MaxOrder(ord *asset.MaxOrderForm) (*asset.SwapEstimate, error) {
  1983  	_, maxEst, err := btc.maxOrder(ord.LotSize, ord.FeeSuggestion, ord.MaxFeeRate)
  1984  	return maxEst, err
  1985  }
  1986  
  1987  // maxOrder gets the estimate for MaxOrder, and also returns the
  1988  // []*CompositeUTXO to be used for further order estimation without additional
  1989  // calls to listunspent.
  1990  func (btc *baseWallet) maxOrder(lotSize, feeSuggestion, maxFeeRate uint64) (utxos []*CompositeUTXO, est *asset.SwapEstimate, err error) {
  1991  	if lotSize == 0 {
  1992  		return nil, nil, errors.New("cannot divide by lotSize zero")
  1993  	}
  1994  
  1995  	utxos, _, avail, err := btc.cm.SpendableUTXOs(0)
  1996  	if err != nil {
  1997  		return nil, nil, fmt.Errorf("error parsing unspent outputs: %w", err)
  1998  	}
  1999  
  2000  	// Start by attempting max lots with a basic fee.
  2001  	basicFee := btc.initTxSize * maxFeeRate
  2002  	maxLotsInt := int(avail / (lotSize + basicFee))
  2003  	oneLotTooMany := sort.Search(maxLotsInt+1, func(lots int) bool {
  2004  		_, _, _, err = btc.estimateSwap(uint64(lots), lotSize, feeSuggestion, maxFeeRate, utxos, true, 1.0)
  2005  		// The only failure mode of estimateSwap -> btc.fund is when there is
  2006  		// not enough funds.
  2007  		return err != nil
  2008  	})
  2009  
  2010  	maxLots := uint64(oneLotTooMany - 1)
  2011  	if oneLotTooMany == 0 {
  2012  		maxLots = 0
  2013  	}
  2014  
  2015  	if maxLots > 0 {
  2016  		est, _, _, err = btc.estimateSwap(maxLots, lotSize, feeSuggestion, maxFeeRate, utxos, true, 1.0)
  2017  		return utxos, est, err
  2018  	}
  2019  
  2020  	return utxos, &asset.SwapEstimate{
  2021  		FeeReservesPerLot: basicFee,
  2022  	}, nil
  2023  }
  2024  
  2025  // sizeUnit returns the short form of the unit used to measure size, either
  2026  // vB if segwit, else B.
  2027  func (btc *baseWallet) sizeUnit() string {
  2028  	if btc.segwit {
  2029  		return "vB"
  2030  	}
  2031  	return "B"
  2032  }
  2033  
  2034  // PreSwap get order estimates and order options based on the available funds
  2035  // and user-selected options.
  2036  func (btc *baseWallet) PreSwap(req *asset.PreSwapForm) (*asset.PreSwap, error) {
  2037  	// Start with the maxOrder at the default configuration. This gets us the
  2038  	// utxo set, the network fee rate, and the wallet's maximum order size. The
  2039  	// utxo set can then be used repeatedly in estimateSwap at virtually zero
  2040  	// cost since there are no more RPC calls.
  2041  	utxos, maxEst, err := btc.maxOrder(req.LotSize, req.FeeSuggestion, req.MaxFeeRate)
  2042  	if err != nil {
  2043  		return nil, err
  2044  	}
  2045  	if maxEst.Lots < req.Lots {
  2046  		return nil, fmt.Errorf("%d lots available for %d-lot order", maxEst.Lots, req.Lots)
  2047  	}
  2048  
  2049  	// Load the user's selected order-time options.
  2050  	customCfg := new(swapOptions)
  2051  	err = config.Unmapify(req.SelectedOptions, customCfg)
  2052  	if err != nil {
  2053  		return nil, fmt.Errorf("error parsing selected swap options: %w", err)
  2054  	}
  2055  
  2056  	// Parse the configured split transaction.
  2057  	split := btc.useSplitTx()
  2058  	if customCfg.Split != nil {
  2059  		split = *customCfg.Split
  2060  	}
  2061  
  2062  	// Parse the configured fee bump.
  2063  	bump, err := customCfg.feeBump()
  2064  	if err != nil {
  2065  		return nil, err
  2066  	}
  2067  
  2068  	// Get the estimate using the current configuration.
  2069  	est, _, _, err := btc.estimateSwap(req.Lots, req.LotSize, req.FeeSuggestion,
  2070  		req.MaxFeeRate, utxos, split, bump)
  2071  	if err != nil {
  2072  		btc.log.Warnf("estimateSwap failure: %v", err)
  2073  	}
  2074  
  2075  	// Always offer the split option, even for non-standing orders since
  2076  	// immediately spendable change many be desirable regardless.
  2077  	opts := []*asset.OrderOption{btc.splitOption(req, utxos, bump)}
  2078  
  2079  	// Figure out what our maximum available fee bump is, within our 2x hard
  2080  	// limit.
  2081  	var maxBump float64
  2082  	var maxBumpEst *asset.SwapEstimate
  2083  	for maxBump = 2.0; maxBump > 1.01; maxBump -= 0.1 {
  2084  		if est == nil {
  2085  			break
  2086  		}
  2087  		tryEst, splitUsed, _, err := btc.estimateSwap(req.Lots, req.LotSize,
  2088  			req.FeeSuggestion, req.MaxFeeRate, utxos, split, maxBump)
  2089  		// If the split used wasn't the configured value, this option is not
  2090  		// available.
  2091  		if err == nil && split == splitUsed {
  2092  			maxBumpEst = tryEst
  2093  			break
  2094  		}
  2095  	}
  2096  
  2097  	if maxBumpEst != nil {
  2098  		noBumpEst, _, _, err := btc.estimateSwap(req.Lots, req.LotSize, req.FeeSuggestion,
  2099  			req.MaxFeeRate, utxos, split, 1.0)
  2100  		if err != nil {
  2101  			// shouldn't be possible, since we already succeeded with a higher bump.
  2102  			return nil, fmt.Errorf("error getting no-bump estimate: %w", err)
  2103  		}
  2104  
  2105  		bumpLabel := "2X"
  2106  		if maxBump < 2.0 {
  2107  			bumpLabel = strconv.FormatFloat(maxBump, 'f', 1, 64) + "X"
  2108  		}
  2109  
  2110  		extraFees := maxBumpEst.RealisticWorstCase - noBumpEst.RealisticWorstCase
  2111  		desc := fmt.Sprintf("Add a fee multiplier up to %.1fx (up to ~%s %s more) for faster settlement when %s network traffic is high.",
  2112  			maxBump, prettyBTC(extraFees), btc.symbol, btc.walletInfo.Name)
  2113  
  2114  		opts = append(opts, &asset.OrderOption{
  2115  			ConfigOption: asset.ConfigOption{
  2116  				Key:          swapFeeBumpKey,
  2117  				DisplayName:  "Faster Swaps",
  2118  				Description:  desc,
  2119  				DefaultValue: 1.0,
  2120  			},
  2121  			XYRange: &asset.XYRange{
  2122  				Start: asset.XYRangePoint{
  2123  					Label: "1X",
  2124  					X:     1.0,
  2125  					Y:     float64(req.FeeSuggestion),
  2126  				},
  2127  				End: asset.XYRangePoint{
  2128  					Label: bumpLabel,
  2129  					X:     maxBump,
  2130  					Y:     float64(req.FeeSuggestion) * maxBump,
  2131  				},
  2132  				XUnit: "X",
  2133  				YUnit: btc.walletInfo.UnitInfo.AtomicUnit + "/" + btc.sizeUnit(),
  2134  			},
  2135  		})
  2136  	}
  2137  
  2138  	return &asset.PreSwap{
  2139  		Estimate: est, // may be nil so we can present options, which in turn affect estimate feasibility
  2140  		Options:  opts,
  2141  	}, nil
  2142  }
  2143  
  2144  // SingleLotSwapRefundFees returns the fees for a swap and refund transaction
  2145  // for a single lot.
  2146  func (btc *baseWallet) SingleLotSwapRefundFees(_ uint32, feeSuggestion uint64, useSafeTxSize bool) (swapFees uint64, refundFees uint64, err error) {
  2147  	var numInputs uint64
  2148  	if useSafeTxSize {
  2149  		numInputs = 12
  2150  	} else {
  2151  		numInputs = 2
  2152  	}
  2153  
  2154  	// TODO: The following is not correct for all BTC clones. e.g. Zcash has
  2155  	// a different MinimumTxOverhead (29).
  2156  
  2157  	var swapTxSize uint64
  2158  	if btc.segwit {
  2159  		inputSize := dexbtc.RedeemP2WPKHInputSize + uint64((dexbtc.RedeemP2PKSigScriptSize+2+3)/4)
  2160  		swapTxSize = dexbtc.MinimumTxOverhead + (numInputs * inputSize) + dexbtc.P2WSHOutputSize + dexbtc.P2WPKHOutputSize
  2161  	} else {
  2162  		swapTxSize = dexbtc.MinimumTxOverhead + (numInputs * dexbtc.RedeemP2PKHInputSize) + dexbtc.P2SHOutputSize + dexbtc.P2PKHOutputSize
  2163  	}
  2164  
  2165  	var refundTxSize uint64
  2166  	if btc.segwit {
  2167  		witnessVBytes := uint64((dexbtc.RefundSigScriptSize + 2 + 3) / 4)
  2168  		refundTxSize = dexbtc.MinimumTxOverhead + dexbtc.TxInOverhead + witnessVBytes + dexbtc.P2WPKHOutputSize
  2169  	} else {
  2170  		inputSize := uint64(dexbtc.TxInOverhead + dexbtc.RefundSigScriptSize)
  2171  		refundTxSize = dexbtc.MinimumTxOverhead + inputSize + dexbtc.P2PKHOutputSize
  2172  	}
  2173  
  2174  	return swapTxSize * feeSuggestion, refundTxSize * feeSuggestion, nil
  2175  }
  2176  
  2177  // splitOption constructs an *asset.OrderOption with customized text based on the
  2178  // difference in fees between the configured and test split condition.
  2179  func (btc *baseWallet) splitOption(req *asset.PreSwapForm, utxos []*CompositeUTXO, bump float64) *asset.OrderOption {
  2180  	opt := &asset.OrderOption{
  2181  		ConfigOption: asset.ConfigOption{
  2182  			Key:           splitKey,
  2183  			DisplayName:   "Pre-size Funds",
  2184  			IsBoolean:     true,
  2185  			DefaultValue:  btc.useSplitTx(), // not nil interface
  2186  			ShowByDefault: true,
  2187  		},
  2188  		Boolean: &asset.BooleanConfig{},
  2189  	}
  2190  
  2191  	noSplitEst, _, noSplitLocked, err := btc.estimateSwap(req.Lots, req.LotSize,
  2192  		req.FeeSuggestion, req.MaxFeeRate, utxos, false, bump)
  2193  	if err != nil {
  2194  		btc.log.Errorf("estimateSwap (no split) error: %v", err)
  2195  		opt.Boolean.Reason = fmt.Sprintf("estimate without a split failed with \"%v\"", err)
  2196  		return opt // utility and overlock report unavailable, but show the option
  2197  	}
  2198  	splitEst, splitUsed, splitLocked, err := btc.estimateSwap(req.Lots, req.LotSize,
  2199  		req.FeeSuggestion, req.MaxFeeRate, utxos, true, bump)
  2200  	if err != nil {
  2201  		btc.log.Errorf("estimateSwap (with split) error: %v", err)
  2202  		opt.Boolean.Reason = fmt.Sprintf("estimate with a split failed with \"%v\"", err)
  2203  		return opt // utility and overlock report unavailable, but show the option
  2204  	}
  2205  	symbol := strings.ToUpper(btc.symbol)
  2206  
  2207  	if !splitUsed || splitLocked >= noSplitLocked { // locked check should be redundant
  2208  		opt.Boolean.Reason = fmt.Sprintf("avoids no %s overlock for this order (ignored)", symbol)
  2209  		opt.Description = fmt.Sprintf("A split transaction for this order avoids no %s overlock, "+
  2210  			"but adds additional fees.", symbol)
  2211  		opt.DefaultValue = false
  2212  		return opt // not enabled by default, but explain why
  2213  	}
  2214  
  2215  	overlock := noSplitLocked - splitLocked
  2216  	pctChange := (float64(splitEst.RealisticWorstCase)/float64(noSplitEst.RealisticWorstCase) - 1) * 100
  2217  	if pctChange > 1 {
  2218  		opt.Boolean.Reason = fmt.Sprintf("+%d%% fees, avoids %s %s overlock", int(math.Round(pctChange)), prettyBTC(overlock), symbol)
  2219  	} else {
  2220  		opt.Boolean.Reason = fmt.Sprintf("+%.1f%% fees, avoids %s %s overlock", pctChange, prettyBTC(overlock), symbol)
  2221  	}
  2222  
  2223  	xtraFees := splitEst.RealisticWorstCase - noSplitEst.RealisticWorstCase
  2224  	opt.Description = fmt.Sprintf("Using a split transaction to prevent temporary overlock of %s %s, but for additional fees of %s %s",
  2225  		prettyBTC(overlock), symbol, prettyBTC(xtraFees), symbol)
  2226  
  2227  	return opt
  2228  }
  2229  
  2230  // estimateSwap prepares an *asset.SwapEstimate.
  2231  func (btc *baseWallet) estimateSwap(lots, lotSize, feeSuggestion, maxFeeRate uint64, utxos []*CompositeUTXO,
  2232  	trySplit bool, feeBump float64) (*asset.SwapEstimate, bool /*split used*/, uint64 /*amt locked*/, error) {
  2233  
  2234  	var avail uint64
  2235  	for _, utxo := range utxos {
  2236  		avail += utxo.Amount
  2237  	}
  2238  	reserves := btc.bondReserves.Load()
  2239  
  2240  	// If there is a fee bump, the networkFeeRate can be higher than the
  2241  	// MaxFeeRate
  2242  	bumpedMaxRate := maxFeeRate
  2243  	bumpedNetRate := feeSuggestion
  2244  	if feeBump > 1 {
  2245  		bumpedMaxRate = uint64(math.Ceil(float64(bumpedMaxRate) * feeBump))
  2246  		bumpedNetRate = uint64(math.Ceil(float64(bumpedNetRate) * feeBump))
  2247  	}
  2248  
  2249  	feeReservesPerLot := bumpedMaxRate * btc.initTxSize
  2250  
  2251  	val := lots * lotSize
  2252  	// The orderEnough func does not account for a split transaction at the start,
  2253  	// so it is possible that funding for trySplit would actually choose more
  2254  	// UTXOs. Actual order funding accounts for this. For this estimate, we will
  2255  	// just not use a split tx if the split-adjusted required funds exceeds the
  2256  	// total value of the UTXO selected with this enough closure.
  2257  	sum, _, inputsSize, _, _, _, _, err := TryFund(utxos,
  2258  		orderEnough(val, lots, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, trySplit))
  2259  	if err != nil {
  2260  		return nil, false, 0, fmt.Errorf("error funding swap value %s: %w", amount(val), err)
  2261  	}
  2262  
  2263  	digestInputs := func(inputsSize uint64) (reqFunds, maxFees, estHighFees, estLowFees uint64) {
  2264  		reqFunds = calc.RequiredOrderFunds(val, inputsSize, lots,
  2265  			btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate) // same as in enough func
  2266  		maxFees = reqFunds - val
  2267  
  2268  		estHighFunds := calc.RequiredOrderFunds(val, inputsSize, lots,
  2269  			btc.initTxSizeBase, btc.initTxSize, bumpedNetRate)
  2270  		estHighFees = estHighFunds - val
  2271  
  2272  		estLowFunds := calc.RequiredOrderFunds(val, inputsSize, 1,
  2273  			btc.initTxSizeBase, btc.initTxSize, bumpedNetRate) // best means single multi-lot match, even better than batch
  2274  		estLowFees = estLowFunds - val
  2275  		return
  2276  	}
  2277  
  2278  	reqFunds, maxFees, estHighFees, estLowFees := digestInputs(inputsSize)
  2279  
  2280  	// Math for split transactions is a little different.
  2281  	if trySplit {
  2282  		_, splitMaxFees := btc.splitBaggageFees(bumpedMaxRate, false)
  2283  		_, splitFees := btc.splitBaggageFees(bumpedNetRate, false)
  2284  		reqTotal := reqFunds + splitMaxFees // ~ rather than actually fund()ing again
  2285  		if reqTotal <= sum && sum-reqTotal >= reserves {
  2286  			return &asset.SwapEstimate{
  2287  				Lots:               lots,
  2288  				Value:              val,
  2289  				MaxFees:            maxFees + splitMaxFees,
  2290  				RealisticBestCase:  estLowFees + splitFees,
  2291  				RealisticWorstCase: estHighFees + splitFees,
  2292  				FeeReservesPerLot:  feeReservesPerLot,
  2293  			}, true, reqFunds, nil // requires reqTotal, but locks reqFunds in the split output
  2294  		}
  2295  	}
  2296  
  2297  	if sum > avail-reserves {
  2298  		if trySplit {
  2299  			return nil, false, 0, errors.New("balance too low to both fund order and maintain bond reserves")
  2300  		}
  2301  		kept := leastOverFund(reserveEnough(reserves), utxos)
  2302  		utxos := UTxOSetDiff(utxos, kept)
  2303  		sum, _, inputsSize, _, _, _, _, err = TryFund(utxos, orderEnough(val, lots, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, false))
  2304  		if err != nil {
  2305  			return nil, false, 0, fmt.Errorf("error funding swap value %s: %w", amount(val), err)
  2306  		}
  2307  		_, maxFees, estHighFees, estLowFees = digestInputs(inputsSize)
  2308  	}
  2309  
  2310  	return &asset.SwapEstimate{
  2311  		Lots:               lots,
  2312  		Value:              val,
  2313  		MaxFees:            maxFees,
  2314  		RealisticBestCase:  estLowFees,
  2315  		RealisticWorstCase: estHighFees,
  2316  		FeeReservesPerLot:  feeReservesPerLot,
  2317  	}, false, sum, nil
  2318  }
  2319  
  2320  // PreRedeem generates an estimate of the range of redemption fees that could
  2321  // be assessed.
  2322  func (btc *baseWallet) preRedeem(numLots, feeSuggestion uint64, options map[string]string) (*asset.PreRedeem, error) {
  2323  	feeRate := feeSuggestion
  2324  	if feeRate == 0 {
  2325  		feeRate = btc.targetFeeRateWithFallback(btc.redeemConfTarget(), 0)
  2326  	}
  2327  	// Best is one transaction with req.Lots inputs and 1 output.
  2328  	var best uint64 = dexbtc.MinimumTxOverhead
  2329  	// Worst is req.Lots transactions, each with one input and one output.
  2330  	var worst uint64 = dexbtc.MinimumTxOverhead * numLots
  2331  	var inputSize, outputSize uint64
  2332  	if btc.segwit {
  2333  		// Add the marker and flag weight here.
  2334  		inputSize = dexbtc.TxInOverhead + (dexbtc.RedeemSwapSigScriptSize+2+3)/4
  2335  		outputSize = dexbtc.P2WPKHOutputSize
  2336  
  2337  	} else {
  2338  		inputSize = dexbtc.TxInOverhead + dexbtc.RedeemSwapSigScriptSize
  2339  		outputSize = dexbtc.P2PKHOutputSize
  2340  	}
  2341  	best += inputSize*numLots + outputSize
  2342  	worst += (inputSize + outputSize) * numLots
  2343  
  2344  	// Read the order options.
  2345  	customCfg := new(redeemOptions)
  2346  	err := config.Unmapify(options, customCfg)
  2347  	if err != nil {
  2348  		return nil, fmt.Errorf("error parsing selected options: %w", err)
  2349  	}
  2350  
  2351  	// Parse the configured fee bump.
  2352  	currentBump := 1.0
  2353  	if customCfg.FeeBump != nil {
  2354  		bump := *customCfg.FeeBump
  2355  		if bump < 1.0 || bump > 2.0 {
  2356  			return nil, fmt.Errorf("invalid fee bump: %f", bump)
  2357  		}
  2358  		currentBump = bump
  2359  	}
  2360  
  2361  	opts := []*asset.OrderOption{{
  2362  		ConfigOption: asset.ConfigOption{
  2363  			Key:          redeemFeeBumpFee,
  2364  			DisplayName:  "Change Redemption Fees",
  2365  			Description:  "Bump the redemption transaction fees up to 2x for faster confirmation of your redemption transaction.",
  2366  			DefaultValue: 1.0,
  2367  		},
  2368  		XYRange: &asset.XYRange{
  2369  			Start: asset.XYRangePoint{
  2370  				Label: "1X",
  2371  				X:     1.0,
  2372  				Y:     float64(feeRate),
  2373  			},
  2374  			End: asset.XYRangePoint{
  2375  				Label: "2X",
  2376  				X:     2.0,
  2377  				Y:     float64(feeRate * 2),
  2378  			},
  2379  			YUnit: btc.walletInfo.UnitInfo.AtomicUnit + "/" + btc.sizeUnit(),
  2380  			XUnit: "X",
  2381  		},
  2382  	}}
  2383  
  2384  	return &asset.PreRedeem{
  2385  		Estimate: &asset.RedeemEstimate{
  2386  			RealisticWorstCase: uint64(math.Round(float64(worst*feeRate) * currentBump)),
  2387  			RealisticBestCase:  uint64(math.Round(float64(best*feeRate) * currentBump)),
  2388  		},
  2389  		Options: opts,
  2390  	}, nil
  2391  }
  2392  
  2393  // PreRedeem generates an estimate of the range of redemption fees that could
  2394  // be assessed.
  2395  func (btc *baseWallet) PreRedeem(form *asset.PreRedeemForm) (*asset.PreRedeem, error) {
  2396  	return btc.preRedeem(form.Lots, form.FeeSuggestion, form.SelectedOptions)
  2397  }
  2398  
  2399  // SingleLotRedeemFees returns the fees for a redeem transaction for a single lot.
  2400  func (btc *baseWallet) SingleLotRedeemFees(_ uint32, feeSuggestion uint64) (uint64, error) {
  2401  	preRedeem, err := btc.preRedeem(1, feeSuggestion, nil)
  2402  	if err != nil {
  2403  		return 0, err
  2404  	}
  2405  	return preRedeem.Estimate.RealisticWorstCase, nil
  2406  }
  2407  
  2408  // FundOrder selects coins for use in an order. The coins will be locked, and
  2409  // will not be returned in subsequent calls to FundOrder or calculated in calls
  2410  // to Available, unless they are unlocked with ReturnCoins.
  2411  // The returned []dex.Bytes contains the redeem scripts for the selected coins.
  2412  // Equal number of coins and redeemed scripts must be returned. A nil or empty
  2413  // dex.Bytes should be appended to the redeem scripts collection for coins with
  2414  // no redeem script.
  2415  func (btc *baseWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uint64, error) {
  2416  	ordValStr := amount(ord.Value).String()
  2417  	btc.log.Debugf("Attempting to fund order for %s %s, maxFeeRate = %d, max swaps = %d",
  2418  		ordValStr, btc.symbol, ord.MaxFeeRate, ord.MaxSwapCount)
  2419  
  2420  	if ord.Value == 0 {
  2421  		return nil, nil, 0, fmt.Errorf("cannot fund value = 0")
  2422  	}
  2423  	if ord.MaxSwapCount == 0 {
  2424  		return nil, nil, 0, fmt.Errorf("cannot fund a zero-lot order")
  2425  	}
  2426  	if ord.FeeSuggestion > ord.MaxFeeRate {
  2427  		return nil, nil, 0, fmt.Errorf("fee suggestion %d > max fee rate %d", ord.FeeSuggestion, ord.MaxFeeRate)
  2428  	}
  2429  	// Check wallets fee rate limit against server's max fee rate
  2430  	if btc.feeRateLimit() < ord.MaxFeeRate {
  2431  		return nil, nil, 0, fmt.Errorf(
  2432  			"%v: server's max fee rate %v higher than configued fee rate limit %v",
  2433  			dex.BipIDSymbol(BipID), ord.MaxFeeRate, btc.feeRateLimit())
  2434  	}
  2435  
  2436  	customCfg := new(swapOptions)
  2437  	err := config.Unmapify(ord.Options, customCfg)
  2438  	if err != nil {
  2439  		return nil, nil, 0, fmt.Errorf("error parsing swap options: %w", err)
  2440  	}
  2441  
  2442  	bumpedMaxRate, err := calcBumpedRate(ord.MaxFeeRate, customCfg.FeeBump)
  2443  	if err != nil {
  2444  		btc.log.Errorf("calcBumpRate error: %v", err)
  2445  	}
  2446  
  2447  	// If a split is not requested, but is forced, create an extra output from
  2448  	// the split tx to help avoid a forced split in subsequent orders.
  2449  	var extraSplitOutput uint64
  2450  	useSplit := btc.useSplitTx()
  2451  	if customCfg.Split != nil {
  2452  		useSplit = *customCfg.Split
  2453  	}
  2454  
  2455  	reserves := btc.bondReserves.Load()
  2456  	minConfs := uint32(0)
  2457  	coins, fundingCoins, spents, redeemScripts, inputsSize, sum, err := btc.cm.Fund(reserves, minConfs, true,
  2458  		orderEnough(ord.Value, ord.MaxSwapCount, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, useSplit))
  2459  	if err != nil {
  2460  		if !useSplit && reserves > 0 {
  2461  			// Force a split if funding failure may be due to reserves.
  2462  			btc.log.Infof("Retrying order funding with a forced split transaction to help respect reserves.")
  2463  			useSplit = true
  2464  			coins, fundingCoins, spents, redeemScripts, inputsSize, sum, err = btc.cm.Fund(reserves, minConfs, true,
  2465  				orderEnough(ord.Value, ord.MaxSwapCount, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, useSplit))
  2466  			extraSplitOutput = reserves + btc.BondsFeeBuffer(ord.FeeSuggestion)
  2467  		}
  2468  		if err != nil {
  2469  			return nil, nil, 0, fmt.Errorf("error funding swap value of %s: %w", amount(ord.Value), err)
  2470  		}
  2471  	}
  2472  
  2473  	if useSplit {
  2474  		// We apply the bumped fee rate to the split transaction when the
  2475  		// PreSwap is created, so we use that bumped rate here too.
  2476  		// But first, check that it's within bounds.
  2477  		splitFeeRate := ord.FeeSuggestion
  2478  		if splitFeeRate == 0 {
  2479  			// TODO
  2480  			// 1.0: Error when no suggestion.
  2481  			// return nil, nil, fmt.Errorf("cannot do a split transaction without a fee rate suggestion from the server")
  2482  			splitFeeRate = btc.targetFeeRateWithFallback(btc.redeemConfTarget(), 0)
  2483  			// We PreOrder checked this as <= MaxFeeRate, so use that as an
  2484  			// upper limit.
  2485  			if splitFeeRate > ord.MaxFeeRate {
  2486  				splitFeeRate = ord.MaxFeeRate
  2487  			}
  2488  		}
  2489  		splitFeeRate, err = calcBumpedRate(splitFeeRate, customCfg.FeeBump)
  2490  		if err != nil {
  2491  			btc.log.Errorf("calcBumpRate error: %v", err)
  2492  		}
  2493  
  2494  		splitCoins, split, splitFees, err := btc.split(ord.Value, ord.MaxSwapCount, spents,
  2495  			inputsSize, fundingCoins, splitFeeRate, bumpedMaxRate, extraSplitOutput)
  2496  		if err != nil {
  2497  			if err := btc.ReturnCoins(coins); err != nil {
  2498  				btc.log.Errorf("Error returning coins: %v", err)
  2499  			}
  2500  			return nil, nil, 0, err
  2501  		} else if split {
  2502  			return splitCoins, []dex.Bytes{nil}, splitFees, nil // no redeem script required for split tx output
  2503  		}
  2504  		return coins, redeemScripts, 0, nil // splitCoins == coins
  2505  	}
  2506  
  2507  	btc.log.Infof("Funding %s %s order with coins %v worth %s",
  2508  		ordValStr, btc.symbol, coins, amount(sum))
  2509  
  2510  	return coins, redeemScripts, 0, nil
  2511  }
  2512  
  2513  // fundsRequiredForMultiOrders returns an slice of the required funds for each
  2514  // of a slice of orders and the total required funds.
  2515  func (btc *baseWallet) fundsRequiredForMultiOrders(orders []*asset.MultiOrderValue, feeRate uint64, splitBuffer float64, swapInputSize uint64) ([]uint64, uint64) {
  2516  	requiredForOrders := make([]uint64, len(orders))
  2517  	var totalRequired uint64
  2518  
  2519  	for i, value := range orders {
  2520  		req := calc.RequiredOrderFunds(value.Value, swapInputSize, value.MaxSwapCount, btc.initTxSizeBase, btc.initTxSize, feeRate)
  2521  		req = uint64(math.Round(float64(req) * (100 + splitBuffer) / 100))
  2522  		requiredForOrders[i] = req
  2523  		totalRequired += req
  2524  	}
  2525  
  2526  	return requiredForOrders, totalRequired
  2527  }
  2528  
  2529  // fundMultiSplitTx uses the utxos provided and attempts to fund a multi-split
  2530  // transaction to fund each of the orders. If successful, it returns the
  2531  // funding coins and outputs.
  2532  func (btc *baseWallet) fundMultiSplitTx(
  2533  	orders []*asset.MultiOrderValue,
  2534  	utxos []*CompositeUTXO,
  2535  	splitTxFeeRate, maxFeeRate uint64,
  2536  	splitBuffer float64,
  2537  	keep, maxLock uint64,
  2538  ) (bool, asset.Coins, []*Output) {
  2539  
  2540  	var swapInputSize uint64
  2541  	if btc.segwit {
  2542  		swapInputSize = dexbtc.RedeemP2WPKHInputTotalSize
  2543  	} else {
  2544  		swapInputSize = dexbtc.RedeemP2PKHInputSize
  2545  	}
  2546  	_, totalOutputRequired := btc.fundsRequiredForMultiOrders(orders, maxFeeRate, splitBuffer, swapInputSize)
  2547  
  2548  	var splitTxSizeWithoutInputs uint64 = dexbtc.MinimumTxOverhead
  2549  	numOutputs := len(orders)
  2550  	if keep > 0 {
  2551  		numOutputs++
  2552  	}
  2553  	if btc.segwit {
  2554  		splitTxSizeWithoutInputs += uint64(dexbtc.P2WPKHOutputSize * numOutputs)
  2555  	} else {
  2556  		splitTxSizeWithoutInputs += uint64(dexbtc.P2PKHOutputSize * numOutputs)
  2557  	}
  2558  	enough := func(_, inputsSize, sum uint64) (bool, uint64) {
  2559  		splitTxFee := (splitTxSizeWithoutInputs + inputsSize) * splitTxFeeRate
  2560  		req := totalOutputRequired + splitTxFee
  2561  		return sum >= req, sum - req
  2562  	}
  2563  
  2564  	fundSplitCoins, _, spents, _, inputsSize, _, err := btc.cm.FundWithUTXOs(utxos, keep, false, enough)
  2565  	if err != nil {
  2566  		return false, nil, nil
  2567  	}
  2568  
  2569  	if maxLock > 0 {
  2570  		totalSize := inputsSize + splitTxSizeWithoutInputs
  2571  		if totalOutputRequired+(totalSize*splitTxFeeRate) > maxLock {
  2572  			return false, nil, nil
  2573  		}
  2574  	}
  2575  
  2576  	return true, fundSplitCoins, spents
  2577  }
  2578  
  2579  // submitMultiSplitTx creates a multi-split transaction using fundingCoins with
  2580  // one output for each order, and submits it to the network.
  2581  func (btc *baseWallet) submitMultiSplitTx(fundingCoins asset.Coins, spents []*Output, orders []*asset.MultiOrderValue,
  2582  	maxFeeRate, splitTxFeeRate uint64, splitBuffer float64) ([]asset.Coins, uint64, error) {
  2583  	baseTx, totalIn, _, err := btc.fundedTx(fundingCoins)
  2584  	if err != nil {
  2585  		return nil, 0, err
  2586  	}
  2587  
  2588  	btc.cm.lockUnspent(false, spents)
  2589  	var success bool
  2590  	defer func() {
  2591  		if !success {
  2592  			btc.node.lockUnspent(true, spents)
  2593  		}
  2594  	}()
  2595  
  2596  	var swapInputSize uint64
  2597  	if btc.segwit {
  2598  		swapInputSize = dexbtc.RedeemP2WPKHInputTotalSize
  2599  	} else {
  2600  		swapInputSize = dexbtc.RedeemP2PKHInputSize
  2601  	}
  2602  
  2603  	requiredForOrders, totalRequired := btc.fundsRequiredForMultiOrders(orders, maxFeeRate, splitBuffer, swapInputSize)
  2604  
  2605  	outputAddresses := make([]btcutil.Address, len(orders))
  2606  	for i, req := range requiredForOrders {
  2607  		outputAddr, err := btc.node.externalAddress()
  2608  		if err != nil {
  2609  			return nil, 0, err
  2610  		}
  2611  		outputAddresses[i] = outputAddr
  2612  		script, err := txscript.PayToAddrScript(outputAddr)
  2613  		if err != nil {
  2614  			return nil, 0, err
  2615  		}
  2616  		baseTx.AddTxOut(wire.NewTxOut(int64(req), script))
  2617  	}
  2618  
  2619  	changeAddr, err := btc.node.changeAddress()
  2620  	if err != nil {
  2621  		return nil, 0, err
  2622  	}
  2623  	tx, err := btc.sendWithReturn(baseTx, changeAddr, totalIn, totalRequired, splitTxFeeRate)
  2624  	if err != nil {
  2625  		return nil, 0, err
  2626  	}
  2627  
  2628  	txHash := btc.hashTx(tx)
  2629  	coins := make([]asset.Coins, len(orders))
  2630  	ops := make([]*Output, len(orders))
  2631  	locks := make([]*UTxO, len(coins))
  2632  	for i := range coins {
  2633  		coins[i] = asset.Coins{NewOutput(txHash, uint32(i), uint64(tx.TxOut[i].Value))}
  2634  		ops[i] = NewOutput(txHash, uint32(i), uint64(tx.TxOut[i].Value))
  2635  		locks[i] = &UTxO{
  2636  			TxHash:  txHash,
  2637  			Vout:    uint32(i),
  2638  			Amount:  uint64(tx.TxOut[i].Value),
  2639  			Address: outputAddresses[i].String(),
  2640  		}
  2641  	}
  2642  	btc.cm.LockUTXOs(locks)
  2643  	btc.node.lockUnspent(false, ops)
  2644  
  2645  	var totalOut uint64
  2646  	for _, txOut := range tx.TxOut {
  2647  		totalOut += uint64(txOut.Value)
  2648  	}
  2649  
  2650  	btc.addTxToHistory(&asset.WalletTransaction{
  2651  		Type: asset.Split,
  2652  		ID:   txHash.String(),
  2653  		Fees: totalIn - totalOut,
  2654  	}, txHash, true)
  2655  
  2656  	success = true
  2657  	return coins, totalIn - totalOut, nil
  2658  }
  2659  
  2660  // fundMultiWithSplit creates a split transaction to fund multiple orders. It
  2661  // attempts to fund as many of the orders as possible without a split transaction,
  2662  // and only creates a split transaction for the remaining orders. This is only
  2663  // called after it has been determined that all of the orders cannot be funded
  2664  // without a split transaction.
  2665  func (btc *baseWallet) fundMultiWithSplit(keep, maxLock uint64, values []*asset.MultiOrderValue,
  2666  	splitTxFeeRate, maxFeeRate uint64, splitBuffer float64) ([]asset.Coins, [][]dex.Bytes, uint64, error) {
  2667  	utxos, _, avail, err := btc.cm.SpendableUTXOs(0)
  2668  	if err != nil {
  2669  		return nil, nil, 0, fmt.Errorf("error getting spendable utxos: %w", err)
  2670  	}
  2671  
  2672  	canFund, splitCoins, splitSpents := btc.fundMultiSplitTx(values, utxos, splitTxFeeRate, maxFeeRate, splitBuffer, keep, maxLock)
  2673  	if !canFund {
  2674  		return nil, nil, 0, fmt.Errorf("cannot fund all with split")
  2675  	}
  2676  
  2677  	remainingUTXOs := utxos
  2678  	remainingOrders := values
  2679  
  2680  	// The return values must be in the same order as the values that were
  2681  	// passed in, so we keep track of the original indexes here.
  2682  	indexToFundingCoins := make(map[int][]*CompositeUTXO, len(values))
  2683  	remainingIndexes := make([]int, len(values))
  2684  	for i := range remainingIndexes {
  2685  		remainingIndexes[i] = i
  2686  	}
  2687  
  2688  	var totalFunded uint64
  2689  
  2690  	// Find each of the orders that can be funded without being included
  2691  	// in the split transaction.
  2692  	for range values {
  2693  		// First find the order the can be funded with the least overlock.
  2694  		// If there is no order that can be funded without going over the
  2695  		// maxLock limit, or not leaving enough for bond reserves, then all
  2696  		// of the remaining orders must be funded with the split transaction.
  2697  		orderIndex, fundingUTXOs := btc.cm.OrderWithLeastOverFund(maxLock-totalFunded, maxFeeRate, remainingOrders, remainingUTXOs)
  2698  		if orderIndex == -1 {
  2699  			break
  2700  		}
  2701  		totalFunded += SumUTXOs(fundingUTXOs)
  2702  		if totalFunded > avail-keep {
  2703  			break
  2704  		}
  2705  
  2706  		newRemainingOrders := make([]*asset.MultiOrderValue, 0, len(remainingOrders)-1)
  2707  		newRemainingIndexes := make([]int, 0, len(remainingOrders)-1)
  2708  		for j := range remainingOrders {
  2709  			if j != orderIndex {
  2710  				newRemainingOrders = append(newRemainingOrders, remainingOrders[j])
  2711  				newRemainingIndexes = append(newRemainingIndexes, remainingIndexes[j])
  2712  			}
  2713  		}
  2714  		remainingUTXOs = UTxOSetDiff(remainingUTXOs, fundingUTXOs)
  2715  
  2716  		// Then we make sure that a split transaction can be created for
  2717  		// any remaining orders without using the utxos returned by
  2718  		// orderWithLeastOverFund.
  2719  		if len(newRemainingOrders) > 0 {
  2720  			canFund, newSplitCoins, newSpents := btc.fundMultiSplitTx(newRemainingOrders, remainingUTXOs,
  2721  				splitTxFeeRate, maxFeeRate, splitBuffer, keep, maxLock-totalFunded)
  2722  			if !canFund {
  2723  				break
  2724  			}
  2725  			splitCoins = newSplitCoins
  2726  			splitSpents = newSpents
  2727  		}
  2728  
  2729  		indexToFundingCoins[remainingIndexes[orderIndex]] = fundingUTXOs
  2730  		remainingOrders = newRemainingOrders
  2731  		remainingIndexes = newRemainingIndexes
  2732  	}
  2733  
  2734  	var splitOutputCoins []asset.Coins
  2735  	var splitFees uint64
  2736  
  2737  	// This should always be true, otherwise this function would not have been
  2738  	// called.
  2739  	if len(remainingOrders) > 0 {
  2740  		splitOutputCoins, splitFees, err = btc.submitMultiSplitTx(splitCoins,
  2741  			splitSpents, remainingOrders, maxFeeRate, splitTxFeeRate, splitBuffer)
  2742  		if err != nil {
  2743  			return nil, nil, 0, fmt.Errorf("error creating split transaction: %w", err)
  2744  		}
  2745  	}
  2746  
  2747  	coins := make([]asset.Coins, len(values))
  2748  	redeemScripts := make([][]dex.Bytes, len(values))
  2749  	spents := make([]*Output, 0, len(values))
  2750  
  2751  	var splitIndex int
  2752  	locks := make([]*UTxO, 0)
  2753  	for i := range values {
  2754  		if fundingUTXOs, ok := indexToFundingCoins[i]; ok {
  2755  			coins[i] = make(asset.Coins, len(fundingUTXOs))
  2756  			redeemScripts[i] = make([]dex.Bytes, len(fundingUTXOs))
  2757  			for j, unspent := range fundingUTXOs {
  2758  				output := NewOutput(unspent.TxHash, unspent.Vout, unspent.Amount)
  2759  				locks = append(locks, &UTxO{
  2760  					TxHash:  unspent.TxHash,
  2761  					Vout:    unspent.Vout,
  2762  					Amount:  unspent.Amount,
  2763  					Address: unspent.Address,
  2764  				})
  2765  				coins[i][j] = output
  2766  				spents = append(spents, output)
  2767  				redeemScripts[i][j] = unspent.RedeemScript
  2768  			}
  2769  		} else {
  2770  			coins[i] = splitOutputCoins[splitIndex]
  2771  			redeemScripts[i] = []dex.Bytes{nil}
  2772  			splitIndex++
  2773  		}
  2774  	}
  2775  
  2776  	btc.cm.LockUTXOs(locks)
  2777  	btc.node.lockUnspent(false, spents)
  2778  
  2779  	return coins, redeemScripts, splitFees, nil
  2780  }
  2781  
  2782  // fundMulti first attempts to fund each of the orders with with the available
  2783  // UTXOs. If a split is not allowed, it will fund the orders that it was able
  2784  // to fund. If splitting is allowed, a split transaction will be created to fund
  2785  // all of the orders.
  2786  func (btc *baseWallet) fundMulti(maxLock uint64, values []*asset.MultiOrderValue, splitTxFeeRate, maxFeeRate uint64, allowSplit bool, splitBuffer float64) ([]asset.Coins, [][]dex.Bytes, uint64, error) {
  2787  	reserves := btc.bondReserves.Load()
  2788  
  2789  	coins, redeemScripts, fundingCoins, spents, err := btc.cm.FundMultiBestEffort(reserves, maxLock, values, maxFeeRate, allowSplit)
  2790  	if err != nil {
  2791  		return nil, nil, 0, err
  2792  	}
  2793  	if len(coins) == len(values) || !allowSplit {
  2794  		btc.cm.LockOutputsMap(fundingCoins)
  2795  		btc.node.lockUnspent(false, spents)
  2796  		return coins, redeemScripts, 0, nil
  2797  	}
  2798  
  2799  	return btc.fundMultiWithSplit(reserves, maxLock, values, splitTxFeeRate, maxFeeRate, splitBuffer)
  2800  }
  2801  
  2802  // split will send a split transaction and return the sized output. If the
  2803  // split transaction is determined to be un-economical, it will not be sent,
  2804  // there is no error, and the input coins will be returned unmodified, but an
  2805  // info message will be logged. The returned bool indicates if a split tx was
  2806  // sent (true) or if the original coins were returned unmodified (false).
  2807  //
  2808  // A split transaction nets additional network bytes consisting of
  2809  //   - overhead from 1 transaction
  2810  //   - 1 extra signed p2wpkh-spending input. The split tx has the fundingCoins as
  2811  //     inputs now, but we'll add the input that spends the sized coin that will go
  2812  //     into the first swap if the split tx does not add excess baggage
  2813  //   - 2 additional p2wpkh outputs for the split tx sized output and change
  2814  //
  2815  // If the fees associated with this extra baggage are more than the excess
  2816  // amount that would be locked if a split transaction were not used, then the
  2817  // split transaction is pointless. This might be common, for instance, if an
  2818  // order is canceled partially filled, and then the remainder resubmitted. We
  2819  // would already have an output of just the right size, and that would be
  2820  // recognized here.
  2821  func (btc *baseWallet) split(value uint64, lots uint64, outputs []*Output, inputsSize uint64,
  2822  	fundingCoins map[OutPoint]*UTxO, suggestedFeeRate, bumpedMaxRate, extraOutput uint64) (asset.Coins, bool, uint64, error) {
  2823  
  2824  	var err error
  2825  	defer func() {
  2826  		if err != nil {
  2827  			return
  2828  		}
  2829  		btc.cm.LockOutputsMap(fundingCoins)
  2830  		err = btc.node.lockUnspent(false, outputs)
  2831  		if err != nil {
  2832  			btc.log.Errorf("error locking unspent outputs: %v", err)
  2833  		}
  2834  	}()
  2835  
  2836  	// Calculate the extra fees associated with the additional inputs, outputs,
  2837  	// and transaction overhead, and compare to the excess that would be locked.
  2838  	swapInputSize, baggage := btc.splitBaggageFees(bumpedMaxRate, extraOutput > 0)
  2839  
  2840  	var coinSum uint64
  2841  	coins := make(asset.Coins, 0, len(outputs))
  2842  	for _, op := range outputs {
  2843  		coins = append(coins, op)
  2844  		coinSum += op.Val
  2845  	}
  2846  
  2847  	valueStr := amount(value).String()
  2848  
  2849  	excess := coinSum - calc.RequiredOrderFunds(value, inputsSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate)
  2850  	if baggage > excess {
  2851  		btc.log.Debugf("Skipping split transaction because cost is greater than potential over-lock. "+
  2852  			"%s > %s", amount(baggage), amount(excess))
  2853  		btc.log.Infof("Funding %s %s order with coins %v worth %s",
  2854  			valueStr, btc.symbol, coins, amount(coinSum))
  2855  		return coins, false, 0, nil // err==nil records and locks the provided fundingCoins in defer
  2856  	}
  2857  
  2858  	addr, err := btc.node.externalAddress()
  2859  	if err != nil {
  2860  		return nil, false, 0, fmt.Errorf("error creating split transaction address: %w", err)
  2861  	}
  2862  	addrStr, err := btc.stringAddr(addr, btc.chainParams)
  2863  	if err != nil {
  2864  		return nil, false, 0, fmt.Errorf("failed to stringify the change address: %w", err)
  2865  	}
  2866  
  2867  	reqFunds := calc.RequiredOrderFunds(value, swapInputSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate)
  2868  
  2869  	baseTx, _, _, err := btc.fundedTx(coins)
  2870  	splitScript, err := txscript.PayToAddrScript(addr)
  2871  	if err != nil {
  2872  		return nil, false, 0, fmt.Errorf("error creating split tx script: %w", err)
  2873  	}
  2874  	baseTx.AddTxOut(wire.NewTxOut(int64(reqFunds), splitScript))
  2875  
  2876  	if extraOutput > 0 {
  2877  		addr, err := btc.node.changeAddress()
  2878  		if err != nil {
  2879  			return nil, false, 0, fmt.Errorf("error creating split transaction address: %w", err)
  2880  		}
  2881  		splitScript, err := txscript.PayToAddrScript(addr)
  2882  		if err != nil {
  2883  			return nil, false, 0, fmt.Errorf("error creating split tx script: %w", err)
  2884  		}
  2885  		baseTx.AddTxOut(wire.NewTxOut(int64(extraOutput), splitScript))
  2886  	}
  2887  
  2888  	// Grab a change address.
  2889  	changeAddr, err := btc.node.changeAddress()
  2890  	if err != nil {
  2891  		return nil, false, 0, fmt.Errorf("error creating change address: %w", err)
  2892  	}
  2893  
  2894  	// Sign, add change, and send the transaction.
  2895  	msgTx, err := btc.sendWithReturn(baseTx, changeAddr, coinSum, reqFunds+extraOutput, suggestedFeeRate)
  2896  	if err != nil {
  2897  		return nil, false, 0, fmt.Errorf("error sending tx: %w", err)
  2898  	}
  2899  
  2900  	txHash := btc.hashTx(msgTx)
  2901  	op := NewOutput(txHash, 0, reqFunds)
  2902  
  2903  	totalOut := reqFunds
  2904  	for i := 1; i < len(msgTx.TxOut); i++ {
  2905  		totalOut += uint64(msgTx.TxOut[i].Value)
  2906  	}
  2907  
  2908  	btc.addTxToHistory(&asset.WalletTransaction{
  2909  		Type: asset.Split,
  2910  		ID:   txHash.String(),
  2911  		Fees: coinSum - totalOut,
  2912  	}, txHash, true)
  2913  
  2914  	fundingCoins = map[OutPoint]*UTxO{op.Pt: {
  2915  		TxHash:  op.txHash(),
  2916  		Vout:    op.vout(),
  2917  		Address: addrStr,
  2918  		Amount:  reqFunds,
  2919  	}}
  2920  
  2921  	// Unlock spent coins
  2922  	returnErr := btc.ReturnCoins(coins)
  2923  	if returnErr != nil {
  2924  		btc.log.Errorf("error unlocking spent coins: %v", err)
  2925  	}
  2926  
  2927  	btc.log.Infof("Funding %s %s order with split output coin %v from original coins %v",
  2928  		valueStr, btc.symbol, op, coins)
  2929  	btc.log.Infof("Sent split transaction %s to accommodate swap of size %s %s + fees = %s",
  2930  		op.txHash(), valueStr, btc.symbol, amount(reqFunds))
  2931  
  2932  	// Assign to coins so the deferred function will lock the output.
  2933  	outputs = []*Output{op}
  2934  	return asset.Coins{op}, true, coinSum - totalOut, nil
  2935  }
  2936  
  2937  // splitBaggageFees is the fees associated with adding a split transaction.
  2938  func (btc *baseWallet) splitBaggageFees(maxFeeRate uint64, extraOutput bool) (swapInputSize, baggage uint64) {
  2939  	if btc.segwit {
  2940  		baggage = maxFeeRate * splitTxBaggageSegwit
  2941  		if extraOutput {
  2942  			baggage += maxFeeRate * dexbtc.P2WPKHOutputSize
  2943  		}
  2944  		swapInputSize = dexbtc.RedeemP2WPKHInputTotalSize
  2945  		return
  2946  	}
  2947  	baggage = maxFeeRate * splitTxBaggage
  2948  	if extraOutput {
  2949  		baggage += maxFeeRate * dexbtc.P2PKHOutputSize
  2950  	}
  2951  	swapInputSize = dexbtc.RedeemP2PKHInputSize
  2952  	return
  2953  }
  2954  
  2955  // ReturnCoins unlocks coins. This would be used in the case of a canceled or
  2956  // partially filled order. Part of the asset.Wallet interface.
  2957  func (btc *baseWallet) ReturnCoins(unspents asset.Coins) error {
  2958  	return btc.cm.ReturnCoins(unspents)
  2959  }
  2960  
  2961  // rawWalletTx gets the raw bytes of a transaction and the number of
  2962  // confirmations. This is a wrapper for checkWalletTx (if node is a
  2963  // walletTxChecker), with a fallback to getWalletTransaction.
  2964  func (btc *baseWallet) rawWalletTx(hash *chainhash.Hash) ([]byte, uint32, error) {
  2965  	if fast, ok := btc.node.(walletTxChecker); ok {
  2966  		txRaw, confs, err := fast.checkWalletTx(hash.String())
  2967  		if err == nil {
  2968  			return txRaw, confs, nil
  2969  		}
  2970  		btc.log.Warnf("checkWalletTx: %v", err)
  2971  		// fallback to getWalletTransaction
  2972  	}
  2973  
  2974  	tx, err := btc.node.getWalletTransaction(hash)
  2975  	if err != nil {
  2976  		return nil, 0, err
  2977  	}
  2978  	return tx.Bytes, uint32(tx.Confirmations), nil
  2979  }
  2980  
  2981  // FundingCoins gets funding coins for the coin IDs. The coins are locked. This
  2982  // method might be called to reinitialize an order from data stored externally.
  2983  // This method will only return funding coins, e.g. unspent transaction outputs.
  2984  func (btc *baseWallet) FundingCoins(ids []dex.Bytes) (asset.Coins, error) {
  2985  	return btc.cm.FundingCoins(ids)
  2986  }
  2987  
  2988  // authAddOn implements the asset.Authenticator.
  2989  type authAddOn struct {
  2990  	w Wallet
  2991  }
  2992  
  2993  // Unlock unlocks the underlying wallet. The pw supplied should be the same as
  2994  // the password for the underlying bitcoind wallet which will also be unlocked.
  2995  // It implements asset.authenticator.
  2996  func (a *authAddOn) Unlock(pw []byte) error {
  2997  	return a.w.walletUnlock(pw)
  2998  }
  2999  
  3000  // Lock locks the underlying bitcoind wallet. It implements asset.authenticator.
  3001  func (a *authAddOn) Lock() error {
  3002  	return a.w.walletLock()
  3003  }
  3004  
  3005  // Locked will be true if the wallet is currently locked. It implements
  3006  // asset.authenticator.
  3007  func (a *authAddOn) Locked() bool {
  3008  	return a.w.locked()
  3009  }
  3010  
  3011  func (btc *baseWallet) addInputsToTx(tx *wire.MsgTx, coins asset.Coins) (uint64, []OutPoint, error) {
  3012  	var totalIn uint64
  3013  	// Add the funding utxos.
  3014  	pts := make([]OutPoint, 0, len(coins))
  3015  	for _, coin := range coins {
  3016  		op, err := ConvertCoin(coin)
  3017  		if err != nil {
  3018  			return 0, nil, fmt.Errorf("error converting coin: %w", err)
  3019  		}
  3020  		if op.Val == 0 {
  3021  			return 0, nil, fmt.Errorf("zero-valued output detected for %s:%d", op.txHash(), op.vout())
  3022  		}
  3023  		totalIn += op.Val
  3024  		txIn := wire.NewTxIn(op.WireOutPoint(), []byte{}, nil)
  3025  		tx.AddTxIn(txIn)
  3026  		pts = append(pts, op.Pt)
  3027  	}
  3028  	return totalIn, pts, nil
  3029  }
  3030  
  3031  // fundedTx creates and returns a new MsgTx with the provided coins as inputs.
  3032  func (btc *baseWallet) fundedTx(coins asset.Coins) (*wire.MsgTx, uint64, []OutPoint, error) {
  3033  	baseTx := wire.NewMsgTx(btc.txVersion())
  3034  	totalIn, pts, err := btc.addInputsToTx(baseTx, coins)
  3035  	if err != nil {
  3036  		return nil, 0, nil, err
  3037  	}
  3038  	return baseTx, totalIn, pts, nil
  3039  }
  3040  
  3041  // lookupWalletTxOutput looks up the value of a transaction output that is
  3042  // spandable by this wallet, and creates an output.
  3043  func (btc *baseWallet) lookupWalletTxOutput(txHash *chainhash.Hash, vout uint32) (*Output, error) {
  3044  	getTxResult, err := btc.node.getWalletTransaction(txHash)
  3045  	if err != nil {
  3046  		return nil, err
  3047  	}
  3048  
  3049  	tx, err := btc.deserializeTx(getTxResult.Bytes)
  3050  	if err != nil {
  3051  		return nil, err
  3052  	}
  3053  	if len(tx.TxOut) <= int(vout) {
  3054  		return nil, fmt.Errorf("txId %s only has %d outputs. tried to access index %d",
  3055  			txHash, len(tx.TxOut), vout)
  3056  	}
  3057  
  3058  	value := tx.TxOut[vout].Value
  3059  	return NewOutput(txHash, vout, uint64(value)), nil
  3060  }
  3061  
  3062  // getTransactions retrieves the transactions that created coins. The
  3063  // returned slice will be in the same order as the argument.
  3064  func (btc *baseWallet) getTransactions(coins []dex.Bytes) ([]*GetTransactionResult, error) {
  3065  	txs := make([]*GetTransactionResult, 0, len(coins))
  3066  
  3067  	for _, coinID := range coins {
  3068  		txHash, _, err := decodeCoinID(coinID)
  3069  		if err != nil {
  3070  			return nil, err
  3071  		}
  3072  		getTxRes, err := btc.node.getWalletTransaction(txHash)
  3073  		if err != nil {
  3074  			return nil, err
  3075  		}
  3076  		txs = append(txs, getTxRes)
  3077  	}
  3078  
  3079  	return txs, nil
  3080  }
  3081  
  3082  func (btc *baseWallet) getTxFee(tx *wire.MsgTx) (uint64, error) {
  3083  	var in, out uint64
  3084  
  3085  	for _, txOut := range tx.TxOut {
  3086  		out += uint64(txOut.Value)
  3087  	}
  3088  
  3089  	for _, txIn := range tx.TxIn {
  3090  		prevTx, err := btc.node.getWalletTransaction(&txIn.PreviousOutPoint.Hash)
  3091  		if err != nil {
  3092  			return 0, err
  3093  		}
  3094  		prevMsgTx, err := btc.deserializeTx(prevTx.Bytes)
  3095  		if err != nil {
  3096  			return 0, err
  3097  		}
  3098  		if len(prevMsgTx.TxOut) <= int(txIn.PreviousOutPoint.Index) {
  3099  			return 0, fmt.Errorf("tx %x references index %d output of %x, but it only has %d outputs",
  3100  				btc.hashTx(tx), txIn.PreviousOutPoint.Index, prevMsgTx.TxHash(), len(prevMsgTx.TxOut))
  3101  		}
  3102  		in += uint64(prevMsgTx.TxOut[int(txIn.PreviousOutPoint.Index)].Value)
  3103  	}
  3104  
  3105  	if in < out {
  3106  		return 0, fmt.Errorf("tx %x has value of inputs %d < value of outputs %d",
  3107  			btc.hashTx(tx), in, out)
  3108  	}
  3109  
  3110  	return in - out, nil
  3111  }
  3112  
  3113  // sizeAndFeesOfUnconfirmedTxs returns the total size in vBytes and the total
  3114  // fees spent by the unconfirmed transactions in txs.
  3115  func (btc *baseWallet) sizeAndFeesOfUnconfirmedTxs(txs []*GetTransactionResult) (size uint64, fees uint64, err error) {
  3116  	for _, tx := range txs {
  3117  		if tx.Confirmations > 0 {
  3118  			continue
  3119  		}
  3120  
  3121  		msgTx, err := btc.deserializeTx(tx.Bytes)
  3122  		if err != nil {
  3123  			return 0, 0, err
  3124  		}
  3125  
  3126  		fee, err := btc.getTxFee(msgTx)
  3127  		if err != nil {
  3128  			return 0, 0, err
  3129  		}
  3130  
  3131  		fees += fee
  3132  		size += btc.calcTxSize(msgTx)
  3133  	}
  3134  
  3135  	return size, fees, nil
  3136  }
  3137  
  3138  // additionalFeesRequired calculates the additional satoshis that need to be
  3139  // sent to miners in order to increase the average fee rate of unconfirmed
  3140  // transactions to newFeeRate. An error is returned if no additional fees
  3141  // are required.
  3142  func (btc *baseWallet) additionalFeesRequired(txs []*GetTransactionResult, newFeeRate uint64) (uint64, error) {
  3143  	size, fees, err := btc.sizeAndFeesOfUnconfirmedTxs(txs)
  3144  	if err != nil {
  3145  		return 0, err
  3146  	}
  3147  
  3148  	if fees >= size*newFeeRate {
  3149  		return 0, fmt.Errorf("extra fees are not needed. %d would be needed "+
  3150  			"for a fee rate of %d, but %d was already paid",
  3151  			size*newFeeRate, newFeeRate, fees)
  3152  	}
  3153  
  3154  	return size*newFeeRate - fees, nil
  3155  }
  3156  
  3157  // changeCanBeAccelerated returns nil if the change can be accelerated,
  3158  // otherwise it returns an error containing the reason why it cannot.
  3159  func (btc *baseWallet) changeCanBeAccelerated(change *Output, remainingSwaps bool) error {
  3160  	lockedUtxos, err := btc.node.listLockUnspent()
  3161  	if err != nil {
  3162  		return err
  3163  	}
  3164  
  3165  	changeTxHash := change.Pt.TxHash.String()
  3166  	for _, utxo := range lockedUtxos {
  3167  		if utxo.TxID == changeTxHash && utxo.Vout == change.Pt.Vout {
  3168  			if !remainingSwaps {
  3169  				return errors.New("change locked by another order")
  3170  			}
  3171  			// change is locked by this order
  3172  			return nil
  3173  		}
  3174  	}
  3175  
  3176  	utxos, err := btc.node.listUnspent()
  3177  	if err != nil {
  3178  		return err
  3179  	}
  3180  	for _, utxo := range utxos {
  3181  		if utxo.TxID == changeTxHash && utxo.Vout == change.Pt.Vout {
  3182  			return nil
  3183  		}
  3184  	}
  3185  
  3186  	return errors.New("change already spent")
  3187  }
  3188  
  3189  // signedAccelerationTx returns a signed transaction that sends funds to a
  3190  // change address controlled by this wallet. This new transaction will have
  3191  // a fee high enough to make the average fee of the unmined previousTxs to
  3192  // the newFeeRate. orderChange latest change in the order, and must be spent
  3193  // by this new transaction in order to accelerate the order.
  3194  // requiredForRemainingSwaps is the amount of funds that are still required
  3195  // to complete the order, so the change of the acceleration transaction must
  3196  // contain at least that amount.
  3197  func (btc *baseWallet) signedAccelerationTx(previousTxs []*GetTransactionResult, orderChange *Output, requiredForRemainingSwaps, newFeeRate uint64) (*wire.MsgTx, *Output, uint64, error) {
  3198  	makeError := func(err error) (*wire.MsgTx, *Output, uint64, error) {
  3199  		return nil, nil, 0, err
  3200  	}
  3201  
  3202  	err := btc.changeCanBeAccelerated(orderChange, requiredForRemainingSwaps > 0)
  3203  	if err != nil {
  3204  		return makeError(err)
  3205  	}
  3206  
  3207  	additionalFeesRequired, err := btc.additionalFeesRequired(previousTxs, newFeeRate)
  3208  	if err != nil {
  3209  		return makeError(err)
  3210  	}
  3211  
  3212  	// Figure out how much funds we need to increase the fee to the requested
  3213  	// amount.
  3214  	txSize := uint64(dexbtc.MinimumTxOverhead)
  3215  	// Add the size of using the order change as an input
  3216  	if btc.segwit {
  3217  		txSize += dexbtc.RedeemP2WPKHInputTotalSize
  3218  	} else {
  3219  		txSize += dexbtc.RedeemP2PKHInputSize
  3220  	}
  3221  	// We need an output if funds are still required for additional swaps in
  3222  	// the order.
  3223  	if requiredForRemainingSwaps > 0 {
  3224  		if btc.segwit {
  3225  			txSize += dexbtc.P2WPKHOutputSize
  3226  		} else {
  3227  			txSize += dexbtc.P2PKHOutputSize
  3228  		}
  3229  	}
  3230  	fundsRequired := additionalFeesRequired + requiredForRemainingSwaps + txSize*newFeeRate
  3231  
  3232  	var additionalInputs asset.Coins
  3233  	if fundsRequired > orderChange.Val {
  3234  		// If change not enough, need to use other UTXOs.
  3235  		enough := func(_, inputSize, inputsVal uint64) (bool, uint64) {
  3236  			txSize := dexbtc.MinimumTxOverhead + inputSize
  3237  
  3238  			// add the order change as an input
  3239  			if btc.segwit {
  3240  				txSize += dexbtc.RedeemP2WPKHInputTotalSize
  3241  			} else {
  3242  				txSize += dexbtc.RedeemP2PKHInputSize
  3243  			}
  3244  
  3245  			if requiredForRemainingSwaps > 0 {
  3246  				if btc.segwit {
  3247  					txSize += dexbtc.P2WPKHOutputSize
  3248  				} else {
  3249  					txSize += dexbtc.P2PKHOutputSize
  3250  				}
  3251  			}
  3252  
  3253  			totalFees := additionalFeesRequired + txSize*newFeeRate
  3254  			totalReq := requiredForRemainingSwaps + totalFees
  3255  			totalVal := inputsVal + orderChange.Val
  3256  			return totalReq <= totalVal, totalVal - totalReq
  3257  		}
  3258  		minConfs := uint32(1)
  3259  		additionalInputs, _, _, _, _, _, err = btc.cm.Fund(btc.bondReserves.Load(), minConfs, false, enough)
  3260  		if err != nil {
  3261  			return makeError(fmt.Errorf("failed to fund acceleration tx: %w", err))
  3262  		}
  3263  	}
  3264  
  3265  	baseTx, totalIn, _, err := btc.fundedTx(append(additionalInputs, orderChange))
  3266  	if err != nil {
  3267  		return makeError(err)
  3268  	}
  3269  
  3270  	addr, err := btc.node.externalAddress()
  3271  	if err != nil {
  3272  		return makeError(fmt.Errorf("error creating change address: %w", err))
  3273  	}
  3274  
  3275  	tx, output, txFee, err := btc.signTxAndAddChange(baseTx, addr, totalIn, additionalFeesRequired, newFeeRate)
  3276  	if err != nil {
  3277  		return makeError(err)
  3278  	}
  3279  
  3280  	return tx, output, txFee + additionalFeesRequired, nil
  3281  }
  3282  
  3283  // FeesForRemainingSwaps returns the fees for a certain number of swaps at a given
  3284  // feeRate. This is only accurate if each swap has a single input. Accurate
  3285  // estimates should use PreSwap or FundOrder.
  3286  func (btc *intermediaryWallet) FeesForRemainingSwaps(n, feeRate uint64) uint64 {
  3287  	return btc.initTxSize * n * feeRate
  3288  }
  3289  
  3290  // AccelerateOrder uses the Child-Pays-For-Parent technique to accelerate a
  3291  // chain of swap transactions and previous accelerations. It broadcasts a new
  3292  // transaction with a fee high enough so that the average fee of all the
  3293  // unconfirmed transactions in the chain and the new transaction will have
  3294  // an average fee rate of newFeeRate. The changeCoin argument is the latest
  3295  // change in the order. It must be the input in the acceleration transaction
  3296  // in order for the order to be accelerated. requiredForRemainingSwaps is the
  3297  // amount of funds required to complete the rest of the swaps in the order.
  3298  // The change output of the acceleration transaction will have at least
  3299  // this amount.
  3300  //
  3301  // The returned change coin may be nil, and should be checked before use.
  3302  func (btc *ExchangeWalletAccelerator) AccelerateOrder(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (asset.Coin, string, error) {
  3303  	return accelerateOrder(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, newFeeRate)
  3304  }
  3305  
  3306  // AccelerateOrder uses the Child-Pays-For-Parent technique to accelerate a
  3307  // chain of swap transactions and previous accelerations. It broadcasts a new
  3308  // transaction with a fee high enough so that the average fee of all the
  3309  // unconfirmed transactions in the chain and the new transaction will have
  3310  // an average fee rate of newFeeRate. The changeCoin argument is the latest
  3311  // change in the order. It must be the input in the acceleration transaction
  3312  // in order for the order to be accelerated. requiredForRemainingSwaps is the
  3313  // amount of funds required to complete the rest of the swaps in the order.
  3314  // The change output of the acceleration transaction will have at least
  3315  // this amount.
  3316  //
  3317  // The returned change coin may be nil, and should be checked before use.
  3318  func (btc *ExchangeWalletSPV) AccelerateOrder(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (asset.Coin, string, error) {
  3319  	return accelerateOrder(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, newFeeRate)
  3320  }
  3321  
  3322  func accelerateOrder(btc *baseWallet, swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (asset.Coin, string, error) {
  3323  	changeTxHash, changeVout, err := decodeCoinID(changeCoin)
  3324  	if err != nil {
  3325  		return nil, "", err
  3326  	}
  3327  	changeOutput, err := btc.lookupWalletTxOutput(changeTxHash, changeVout)
  3328  	if err != nil {
  3329  		return nil, "", err
  3330  	}
  3331  	previousTxs, err := btc.getTransactions(append(swapCoins, accelerationCoins...))
  3332  	if err != nil {
  3333  		return nil, "", err
  3334  	}
  3335  	signedTx, newChange, fees, err :=
  3336  		btc.signedAccelerationTx(previousTxs, changeOutput, requiredForRemainingSwaps, newFeeRate)
  3337  	if err != nil {
  3338  		return nil, "", err
  3339  	}
  3340  
  3341  	_, err = btc.broadcastTx(signedTx)
  3342  	if err != nil {
  3343  		return nil, "", err
  3344  	}
  3345  
  3346  	txHash := btc.hashTx(signedTx)
  3347  	btc.addTxToHistory(&asset.WalletTransaction{
  3348  		Type: asset.Acceleration,
  3349  		ID:   txHash.String(),
  3350  		Fees: fees,
  3351  	}, txHash, true)
  3352  
  3353  	// Delete the old change from the cache
  3354  	btc.cm.ReturnOutPoint(NewOutPoint(changeTxHash, changeVout))
  3355  
  3356  	if newChange == nil {
  3357  		return nil, txHash.String(), nil
  3358  	}
  3359  
  3360  	// Add the new change to the cache if needed. We check if
  3361  	// required for remaining swaps > 0 because this ensures if the
  3362  	// previous change was locked, this one will also be locked. If
  3363  	// requiredForRemainingSwaps = 0, but the change was locked,
  3364  	// changeCanBeAccelerated would have returned an error since this means
  3365  	// that the change was locked by another order.
  3366  	if requiredForRemainingSwaps > 0 {
  3367  		err = btc.node.lockUnspent(false, []*Output{newChange})
  3368  		if err != nil {
  3369  			// The transaction is already broadcasted, so don't fail now.
  3370  			btc.log.Errorf("failed to lock change output: %v", err)
  3371  		}
  3372  
  3373  		// Log it as a fundingCoin, since it is expected that this will be
  3374  		// chained into further matches.
  3375  		btc.cm.LockUTXOs([]*UTxO{{
  3376  			TxHash:  newChange.txHash(),
  3377  			Vout:    newChange.vout(),
  3378  			Address: newChange.String(),
  3379  			Amount:  newChange.Val,
  3380  		}})
  3381  	}
  3382  
  3383  	// return nil error since tx is already broadcast, and core needs to update
  3384  	// the change coin
  3385  	return newChange, txHash.String(), nil
  3386  }
  3387  
  3388  // AccelerationEstimate takes the same parameters as AccelerateOrder, but
  3389  // instead of broadcasting the acceleration transaction, it just returns
  3390  // the amount of funds that will need to be spent in order to increase the
  3391  // average fee rate to the desired amount.
  3392  func (btc *ExchangeWalletAccelerator) AccelerationEstimate(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (uint64, error) {
  3393  	return accelerationEstimate(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, newFeeRate)
  3394  }
  3395  
  3396  // AccelerationEstimate takes the same parameters as AccelerateOrder, but
  3397  // instead of broadcasting the acceleration transaction, it just returns
  3398  // the amount of funds that will need to be spent in order to increase the
  3399  // average fee rate to the desired amount.
  3400  func (btc *ExchangeWalletSPV) AccelerationEstimate(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (uint64, error) {
  3401  	return accelerationEstimate(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, newFeeRate)
  3402  }
  3403  
  3404  func accelerationEstimate(btc *baseWallet, swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (uint64, error) {
  3405  	previousTxs, err := btc.getTransactions(append(swapCoins, accelerationCoins...))
  3406  	if err != nil {
  3407  		return 0, fmt.Errorf("failed to get transactions: %w", err)
  3408  	}
  3409  
  3410  	changeTxHash, changeVout, err := decodeCoinID(changeCoin)
  3411  	if err != nil {
  3412  		return 0, err
  3413  	}
  3414  	changeOutput, err := btc.lookupWalletTxOutput(changeTxHash, changeVout)
  3415  	if err != nil {
  3416  		return 0, err
  3417  	}
  3418  
  3419  	_, _, fee, err := btc.signedAccelerationTx(previousTxs, changeOutput, requiredForRemainingSwaps, newFeeRate)
  3420  	if err != nil {
  3421  		return 0, err
  3422  	}
  3423  
  3424  	return fee, nil
  3425  }
  3426  
  3427  // tooEarlyToAccelerate returns an asset.EarlyAcceleration if
  3428  // minTimeBeforeAcceleration has not passed since either the earliest
  3429  // unconfirmed swap transaction, or the latest acceleration transaction.
  3430  func tooEarlyToAccelerate(swapTxs []*GetTransactionResult, accelerationTxs []*GetTransactionResult) (*asset.EarlyAcceleration, error) {
  3431  	accelerationTxLookup := make(map[string]bool, len(accelerationTxs))
  3432  	for _, accelerationCoin := range accelerationTxs {
  3433  		accelerationTxLookup[accelerationCoin.TxID] = true
  3434  	}
  3435  
  3436  	var latestAcceleration, earliestUnconfirmed uint64 = 0, math.MaxUint64
  3437  	for _, tx := range swapTxs {
  3438  		if tx.Confirmations > 0 {
  3439  			continue
  3440  		}
  3441  		if tx.Time < earliestUnconfirmed {
  3442  			earliestUnconfirmed = tx.Time
  3443  		}
  3444  	}
  3445  	for _, tx := range accelerationTxs {
  3446  		if tx.Confirmations > 0 {
  3447  			continue
  3448  		}
  3449  		if tx.Time > latestAcceleration {
  3450  			latestAcceleration = tx.Time
  3451  		}
  3452  	}
  3453  
  3454  	var actionTime uint64
  3455  	var wasAccelerated bool
  3456  	if latestAcceleration == 0 && earliestUnconfirmed == math.MaxUint64 {
  3457  		return nil, fmt.Errorf("no need to accelerate because all tx are confirmed")
  3458  	} else if earliestUnconfirmed > latestAcceleration && earliestUnconfirmed < math.MaxUint64 {
  3459  		actionTime = earliestUnconfirmed
  3460  	} else {
  3461  		actionTime = latestAcceleration
  3462  		wasAccelerated = true
  3463  	}
  3464  
  3465  	currentTime := uint64(time.Now().Unix())
  3466  	if actionTime+minTimeBeforeAcceleration > currentTime {
  3467  		return &asset.EarlyAcceleration{
  3468  			TimePast:       currentTime - actionTime,
  3469  			WasAccelerated: wasAccelerated,
  3470  		}, nil
  3471  	}
  3472  
  3473  	return nil, nil
  3474  }
  3475  
  3476  // PreAccelerate returns the current average fee rate of the unmined swap
  3477  // initiation and acceleration transactions, and also returns a suggested
  3478  // range that the fee rate should be increased to in order to expedite mining.
  3479  // The feeSuggestion argument is the current prevailing network rate. It is
  3480  // used to help determine the suggestedRange, which is a range meant to give
  3481  // the user a good amount of flexibility in determining the post acceleration
  3482  // effective fee rate, but still not allowing them to pick something
  3483  // outrageously high.
  3484  func (btc *ExchangeWalletAccelerator) PreAccelerate(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, feeSuggestion uint64) (uint64, *asset.XYRange, *asset.EarlyAcceleration, error) {
  3485  	return preAccelerate(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, feeSuggestion)
  3486  }
  3487  
  3488  // PreAccelerate returns the current average fee rate of the unmined swap
  3489  // initiation and acceleration transactions, and also returns a suggested
  3490  // range that the fee rate should be increased to in order to expedite mining.
  3491  // The feeSuggestion argument is the current prevailing network rate. It is
  3492  // used to help determine the suggestedRange, which is a range meant to give
  3493  // the user a good amount of flexibility in determining the post acceleration
  3494  // effective fee rate, but still not allowing them to pick something
  3495  // outrageously high.
  3496  func (btc *ExchangeWalletSPV) PreAccelerate(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, feeSuggestion uint64) (uint64, *asset.XYRange, *asset.EarlyAcceleration, error) {
  3497  	return preAccelerate(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, feeSuggestion)
  3498  }
  3499  
  3500  // maxAccelerationRate returns the max rate to which an order can be
  3501  // accelerated, if the max rate is less than rateNeeded. If the max rate is
  3502  // greater than rateNeeded, rateNeeded is returned.
  3503  func (btc *baseWallet) maxAccelerationRate(changeVal, feesAlreadyPaid, orderTxVBytes, requiredForRemainingSwaps, rateNeeded uint64) (uint64, error) {
  3504  	var txSize, witnessSize, additionalUtxosVal uint64
  3505  
  3506  	// First, add all the elements that will definitely be part of the
  3507  	// acceleration transaction, without any additional inputs.
  3508  	txSize += dexbtc.MinimumTxOverhead
  3509  	if btc.segwit {
  3510  		txSize += dexbtc.RedeemP2WPKHInputSize
  3511  		witnessSize += dexbtc.RedeemP2WPKHInputWitnessWeight
  3512  	} else {
  3513  		txSize += dexbtc.RedeemP2PKHInputSize
  3514  	}
  3515  	if requiredForRemainingSwaps > 0 {
  3516  		if btc.segwit {
  3517  			txSize += dexbtc.P2WPKHOutputSize
  3518  		} else {
  3519  			txSize += dexbtc.P2PKHOutputSize
  3520  		}
  3521  	}
  3522  
  3523  	calcFeeRate := func() uint64 {
  3524  		accelerationTxVBytes := txSize + (witnessSize+3)/4
  3525  		totalValue := changeVal + feesAlreadyPaid + additionalUtxosVal
  3526  		if totalValue < requiredForRemainingSwaps {
  3527  			return 0
  3528  		}
  3529  		totalValue -= requiredForRemainingSwaps
  3530  		totalSize := accelerationTxVBytes + orderTxVBytes
  3531  		return totalValue / totalSize
  3532  	}
  3533  
  3534  	if calcFeeRate() >= rateNeeded {
  3535  		return rateNeeded, nil
  3536  	}
  3537  
  3538  	// If necessary, use as many additional utxos as needed
  3539  	utxos, _, _, err := btc.cm.SpendableUTXOs(1)
  3540  	if err != nil {
  3541  		return 0, err
  3542  	}
  3543  
  3544  	for _, utxo := range utxos {
  3545  		if utxo.Input.NonStandardScript {
  3546  			continue
  3547  		}
  3548  		txSize += dexbtc.TxInOverhead +
  3549  			uint64(wire.VarIntSerializeSize(uint64(utxo.Input.SigScriptSize))) +
  3550  			uint64(utxo.Input.SigScriptSize)
  3551  		witnessSize += uint64(utxo.Input.WitnessSize)
  3552  		additionalUtxosVal += utxo.Amount
  3553  		if calcFeeRate() >= rateNeeded {
  3554  			return rateNeeded, nil
  3555  		}
  3556  	}
  3557  
  3558  	return calcFeeRate(), nil
  3559  }
  3560  
  3561  func preAccelerate(btc *baseWallet, swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, feeSuggestion uint64) (uint64, *asset.XYRange, *asset.EarlyAcceleration, error) {
  3562  	makeError := func(err error) (uint64, *asset.XYRange, *asset.EarlyAcceleration, error) {
  3563  		return 0, &asset.XYRange{}, nil, err
  3564  	}
  3565  
  3566  	changeTxHash, changeVout, err := decodeCoinID(changeCoin)
  3567  	if err != nil {
  3568  		return makeError(err)
  3569  	}
  3570  	changeOutput, err := btc.lookupWalletTxOutput(changeTxHash, changeVout)
  3571  	if err != nil {
  3572  		return makeError(err)
  3573  	}
  3574  
  3575  	err = btc.changeCanBeAccelerated(changeOutput, requiredForRemainingSwaps > 0)
  3576  	if err != nil {
  3577  		return makeError(err)
  3578  	}
  3579  
  3580  	txs, err := btc.getTransactions(append(swapCoins, accelerationCoins...))
  3581  	if err != nil {
  3582  		return makeError(fmt.Errorf("failed to get transactions: %w", err))
  3583  	}
  3584  
  3585  	existingTxSize, feesAlreadyPaid, err := btc.sizeAndFeesOfUnconfirmedTxs(txs)
  3586  	if err != nil {
  3587  		return makeError(err)
  3588  	}
  3589  	// Is it safe to assume that transactions will all have some fee?
  3590  	if feesAlreadyPaid == 0 {
  3591  		return makeError(fmt.Errorf("all transactions are already confirmed, no need to accelerate"))
  3592  	}
  3593  
  3594  	earlyAcceleration, err := tooEarlyToAccelerate(txs[:len(swapCoins)], txs[len(swapCoins):])
  3595  	if err != nil {
  3596  		return makeError(err)
  3597  	}
  3598  
  3599  	// The suggested range will be the min and max of the slider that is
  3600  	// displayed on the UI. The minimum of the range is 1 higher than the
  3601  	// current effective range of the swap transactions. The max of the range
  3602  	// will be the maximum of 5x the current effective rate, or 5x the current
  3603  	// prevailing network rate. This is a completely arbitrary choice, but in
  3604  	// this way the user will definitely be able to accelerate at least 5x the
  3605  	// original rate, and even if the prevailing network rate is much higher
  3606  	// than the current effective rate, they will still have a comformtable
  3607  	// buffer above the prevailing network rate.
  3608  	const scalingFactor = 5
  3609  	currentEffectiveRate := feesAlreadyPaid / existingTxSize
  3610  	maxSuggestion := currentEffectiveRate * scalingFactor
  3611  	if feeSuggestion > currentEffectiveRate {
  3612  		maxSuggestion = feeSuggestion * scalingFactor
  3613  	}
  3614  
  3615  	// We must make sure that the wallet can fund an acceleration at least
  3616  	// the max suggestion, and if not, lower the max suggestion to the max
  3617  	// rate that the wallet can fund.
  3618  	maxRate, err := btc.maxAccelerationRate(changeOutput.Val, feesAlreadyPaid, existingTxSize, requiredForRemainingSwaps, maxSuggestion)
  3619  	if err != nil {
  3620  		return makeError(err)
  3621  	}
  3622  	if maxRate <= currentEffectiveRate {
  3623  		return makeError(fmt.Errorf("cannot accelerate, max rate %v <= current rate %v", maxRate, currentEffectiveRate))
  3624  	}
  3625  	if maxRate < maxSuggestion {
  3626  		maxSuggestion = maxRate
  3627  	}
  3628  
  3629  	suggestedRange := asset.XYRange{
  3630  		Start: asset.XYRangePoint{
  3631  			Label: "Min",
  3632  			X:     float64(currentEffectiveRate+1) / float64(currentEffectiveRate),
  3633  			Y:     float64(currentEffectiveRate + 1),
  3634  		},
  3635  		End: asset.XYRangePoint{
  3636  			Label: "Max",
  3637  			X:     float64(maxSuggestion) / float64(currentEffectiveRate),
  3638  			Y:     float64(maxSuggestion),
  3639  		},
  3640  		XUnit: "X",
  3641  		YUnit: btc.walletInfo.UnitInfo.AtomicUnit + "/" + btc.sizeUnit(),
  3642  	}
  3643  
  3644  	return currentEffectiveRate, &suggestedRange, earlyAcceleration, nil
  3645  }
  3646  
  3647  func (btc *baseWallet) txDB() *BadgerTxDB {
  3648  	dbi := btc.txHistoryDB.Load()
  3649  	if dbi == nil {
  3650  		return nil
  3651  	}
  3652  	return dbi.(*BadgerTxDB)
  3653  }
  3654  
  3655  func (btc *baseWallet) markTxAsSubmitted(txHash *chainhash.Hash) {
  3656  	txHistoryDB := btc.txDB()
  3657  	if txHistoryDB == nil {
  3658  		return
  3659  	}
  3660  
  3661  	err := txHistoryDB.MarkTxAsSubmitted(txHash.String())
  3662  	if err != nil {
  3663  		btc.log.Errorf("failed to mark tx as submitted in tx history db: %v", err)
  3664  	}
  3665  
  3666  	btc.pendingTxsMtx.RLock()
  3667  	wt, found := btc.pendingTxs[*txHash]
  3668  	btc.pendingTxsMtx.RUnlock()
  3669  
  3670  	if !found {
  3671  		btc.log.Errorf("tx %s not found in pending txs", txHash)
  3672  		return
  3673  	}
  3674  
  3675  	wt.Submitted = true
  3676  
  3677  	btc.pendingTxsMtx.Lock()
  3678  	btc.pendingTxs[*txHash] = wt
  3679  	btc.pendingTxsMtx.Unlock()
  3680  
  3681  	btc.emit.TransactionNote(wt.WalletTransaction, true)
  3682  }
  3683  
  3684  func (btc *baseWallet) removeTxFromHistory(txHash *chainhash.Hash) {
  3685  	txHistoryDB := btc.txDB()
  3686  	if txHistoryDB == nil {
  3687  		return
  3688  	}
  3689  
  3690  	err := txHistoryDB.RemoveTx(txHash.String())
  3691  	if err != nil {
  3692  		btc.log.Errorf("failed to remove tx from tx history db: %v", err)
  3693  	}
  3694  
  3695  	btc.pendingTxsMtx.Lock()
  3696  	delete(btc.pendingTxs, *txHash)
  3697  	btc.pendingTxsMtx.Unlock()
  3698  }
  3699  
  3700  func (btc *baseWallet) addTxToHistory(wt *asset.WalletTransaction, txHash *chainhash.Hash, submitted bool, skipNotes ...bool) {
  3701  	txHistoryDB := btc.txDB()
  3702  	if txHistoryDB == nil {
  3703  		return
  3704  	}
  3705  
  3706  	ewt := &ExtendedWalletTx{
  3707  		WalletTransaction: wt,
  3708  		Submitted:         submitted,
  3709  	}
  3710  
  3711  	if wt.BlockNumber == 0 {
  3712  		btc.pendingTxsMtx.Lock()
  3713  		btc.pendingTxs[*txHash] = *ewt
  3714  		btc.pendingTxsMtx.Unlock()
  3715  	}
  3716  
  3717  	err := txHistoryDB.StoreTx(ewt)
  3718  	if err != nil {
  3719  		btc.log.Errorf("failed to store tx in tx history db: %v", err)
  3720  	}
  3721  
  3722  	skipNote := len(skipNotes) > 0 && skipNotes[0]
  3723  	if submitted && !skipNote {
  3724  		btc.emit.TransactionNote(wt, true)
  3725  	}
  3726  }
  3727  
  3728  // Swap sends the swaps in a single transaction and prepares the receipts. The
  3729  // Receipts returned can be used to refund a failed transaction. The Input coins
  3730  // are NOT manually unlocked because they're auto-unlocked when the transaction
  3731  // is broadcasted.
  3732  func (btc *baseWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint64, error) {
  3733  	if swaps.FeeRate == 0 {
  3734  		return nil, nil, 0, fmt.Errorf("cannot send swap with with zero fee rate")
  3735  	}
  3736  
  3737  	contracts := make([][]byte, 0, len(swaps.Contracts))
  3738  	var totalOut uint64
  3739  	// Start with an empty MsgTx.
  3740  	baseTx, totalIn, pts, err := btc.fundedTx(swaps.Inputs)
  3741  	if err != nil {
  3742  		return nil, nil, 0, err
  3743  	}
  3744  
  3745  	customCfg := new(swapOptions)
  3746  	err = config.Unmapify(swaps.Options, customCfg)
  3747  	if err != nil {
  3748  		return nil, nil, 0, fmt.Errorf("error parsing swap options: %w", err)
  3749  	}
  3750  
  3751  	refundAddrs := make([]btcutil.Address, 0, len(swaps.Contracts))
  3752  
  3753  	// Add the contract outputs.
  3754  	// TODO: Make P2WSH contract and P2WPKH change outputs instead of
  3755  	// legacy/non-segwit swap contracts pkScripts.
  3756  	for _, contract := range swaps.Contracts {
  3757  		totalOut += contract.Value
  3758  		// revokeAddr is the address belonging to the key that may be used to
  3759  		// sign and refund a swap past its encoded refund locktime.
  3760  		revokeAddrStr, err := btc.recyclableAddress()
  3761  		if err != nil {
  3762  			return nil, nil, 0, fmt.Errorf("error creating revocation address: %w", err)
  3763  		}
  3764  		revokeAddr, err := btc.decodeAddr(revokeAddrStr, btc.chainParams)
  3765  		if err != nil {
  3766  			return nil, nil, 0, fmt.Errorf("refund address decode error: %v", err)
  3767  		}
  3768  		refundAddrs = append(refundAddrs, revokeAddr)
  3769  
  3770  		contractAddr, err := btc.decodeAddr(contract.Address, btc.chainParams)
  3771  		if err != nil {
  3772  			return nil, nil, 0, fmt.Errorf("contract address decode error: %v", err)
  3773  		}
  3774  
  3775  		// Create the contract, a P2SH redeem script.
  3776  		contractScript, err := dexbtc.MakeContract(contractAddr, revokeAddr,
  3777  			contract.SecretHash, int64(contract.LockTime), btc.segwit, btc.chainParams)
  3778  		if err != nil {
  3779  			return nil, nil, 0, fmt.Errorf("unable to create pubkey script for address %s: %w", contract.Address, err)
  3780  		}
  3781  		contracts = append(contracts, contractScript)
  3782  
  3783  		// Make the P2SH address and pubkey script.
  3784  		scriptAddr, err := btc.scriptHashAddress(contractScript)
  3785  		if err != nil {
  3786  			return nil, nil, 0, fmt.Errorf("error encoding script address: %w", err)
  3787  		}
  3788  
  3789  		pkScript, err := txscript.PayToAddrScript(scriptAddr)
  3790  		if err != nil {
  3791  			return nil, nil, 0, fmt.Errorf("error creating pubkey script: %w", err)
  3792  		}
  3793  
  3794  		// Add the transaction output.
  3795  		txOut := wire.NewTxOut(int64(contract.Value), pkScript)
  3796  		baseTx.AddTxOut(txOut)
  3797  	}
  3798  	if totalIn < totalOut {
  3799  		return nil, nil, 0, fmt.Errorf("unfunded contract. %d < %d", totalIn, totalOut)
  3800  	}
  3801  
  3802  	// Ensure we have enough outputs before broadcasting.
  3803  	swapCount := len(swaps.Contracts)
  3804  	if len(baseTx.TxOut) < swapCount {
  3805  		return nil, nil, 0, fmt.Errorf("fewer outputs than swaps. %d < %d", len(baseTx.TxOut), swapCount)
  3806  	}
  3807  
  3808  	// Grab a change address.
  3809  	changeAddr, err := btc.node.changeAddress()
  3810  	if err != nil {
  3811  		return nil, nil, 0, fmt.Errorf("error creating change address: %w", err)
  3812  	}
  3813  
  3814  	feeRate, err := calcBumpedRate(swaps.FeeRate, customCfg.FeeBump)
  3815  	if err != nil {
  3816  		btc.log.Errorf("ignoring invalid fee bump factor, %s: %v", float64PtrStr(customCfg.FeeBump), err)
  3817  	}
  3818  
  3819  	// Sign, add change, but don't send the transaction yet until
  3820  	// the individual swap refund txs are prepared and signed.
  3821  	msgTx, change, fees, err := btc.signTxAndAddChange(baseTx, changeAddr, totalIn, totalOut, feeRate)
  3822  	if err != nil {
  3823  		return nil, nil, 0, err
  3824  	}
  3825  	txHash := btc.hashTx(msgTx)
  3826  
  3827  	// Prepare the receipts.
  3828  	receipts := make([]asset.Receipt, 0, swapCount)
  3829  	for i, contract := range swaps.Contracts {
  3830  		output := NewOutput(txHash, uint32(i), contract.Value)
  3831  		refundAddr := refundAddrs[i]
  3832  		signedRefundTx, err := btc.refundTx(output.txHash(), output.vout(), contracts[i],
  3833  			contract.Value, refundAddr, swaps.FeeRate)
  3834  		if err != nil {
  3835  			return nil, nil, 0, fmt.Errorf("error creating refund tx: %w", err)
  3836  		}
  3837  		refundBuff := new(bytes.Buffer)
  3838  		err = signedRefundTx.Serialize(refundBuff)
  3839  		if err != nil {
  3840  			return nil, nil, 0, fmt.Errorf("error serializing refund tx: %w", err)
  3841  		}
  3842  		receipts = append(receipts, &SwapReceipt{
  3843  			Output:            output,
  3844  			SwapContract:      contracts[i],
  3845  			ExpirationTime:    time.Unix(int64(contract.LockTime), 0).UTC(),
  3846  			SignedRefundBytes: refundBuff.Bytes(),
  3847  		})
  3848  	}
  3849  
  3850  	// Refund txs prepared and signed. Can now broadcast the swap(s).
  3851  	_, err = btc.broadcastTx(msgTx)
  3852  	if err != nil {
  3853  		return nil, nil, 0, err
  3854  	}
  3855  
  3856  	btc.addTxToHistory(&asset.WalletTransaction{
  3857  		Type:   asset.Swap,
  3858  		ID:     txHash.String(),
  3859  		Amount: totalOut,
  3860  		Fees:   fees,
  3861  	}, txHash, true)
  3862  
  3863  	// If change is nil, return a nil asset.Coin.
  3864  	var changeCoin asset.Coin
  3865  	if change != nil {
  3866  		changeCoin = change
  3867  	}
  3868  
  3869  	var locks []*UTxO
  3870  	if change != nil && swaps.LockChange {
  3871  		// Lock the change output
  3872  		btc.log.Debugf("locking change coin %s", change)
  3873  		err = btc.node.lockUnspent(false, []*Output{change})
  3874  		if err != nil {
  3875  			// The swap transaction is already broadcasted, so don't fail now.
  3876  			btc.log.Errorf("failed to lock change output: %v", err)
  3877  		}
  3878  
  3879  		addrStr, err := btc.stringAddr(changeAddr, btc.chainParams)
  3880  		if err != nil {
  3881  			btc.log.Errorf("Failed to stringify address %v (default encoding): %v", changeAddr, err)
  3882  			addrStr = changeAddr.String() // may or may not be able to retrieve the private keys for the next swap!
  3883  		}
  3884  
  3885  		// Log it as a fundingCoin, since it is expected that this will be
  3886  		// chained into further matches.
  3887  		locks = append(locks, &UTxO{
  3888  			TxHash:  change.txHash(),
  3889  			Vout:    change.vout(),
  3890  			Address: addrStr,
  3891  			Amount:  change.Val,
  3892  		})
  3893  	}
  3894  
  3895  	btc.cm.LockUTXOs(locks)
  3896  	btc.cm.UnlockOutPoints(pts)
  3897  
  3898  	return receipts, changeCoin, fees, nil
  3899  }
  3900  
  3901  // Redeem sends the redemption transaction, completing the atomic swap.
  3902  func (btc *baseWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, uint64, error) {
  3903  	// Create a transaction that spends the referenced contract.
  3904  	msgTx := wire.NewMsgTx(btc.txVersion())
  3905  	var totalIn uint64
  3906  	contracts := make([][]byte, 0, len(form.Redemptions))
  3907  	prevScripts := make([][]byte, 0, len(form.Redemptions))
  3908  	addresses := make([]btcutil.Address, 0, len(form.Redemptions))
  3909  	values := make([]int64, 0, len(form.Redemptions))
  3910  	for _, r := range form.Redemptions {
  3911  		if r.Spends == nil {
  3912  			return nil, nil, 0, fmt.Errorf("no audit info")
  3913  		}
  3914  
  3915  		cinfo, err := ConvertAuditInfo(r.Spends, btc.decodeAddr, btc.chainParams)
  3916  		if err != nil {
  3917  			return nil, nil, 0, err
  3918  		}
  3919  
  3920  		// Extract the swap contract recipient and secret hash and check the secret
  3921  		// hash against the hash of the provided secret.
  3922  		contract := cinfo.contract
  3923  		_, receiver, _, secretHash, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams)
  3924  		if err != nil {
  3925  			return nil, nil, 0, fmt.Errorf("error extracting swap addresses: %w", err)
  3926  		}
  3927  		checkSecretHash := sha256.Sum256(r.Secret)
  3928  		if !bytes.Equal(checkSecretHash[:], secretHash) {
  3929  			return nil, nil, 0, fmt.Errorf("secret hash mismatch")
  3930  		}
  3931  		pkScript, err := btc.scriptHashScript(contract)
  3932  		if err != nil {
  3933  			return nil, nil, 0, fmt.Errorf("error constructs p2sh script: %v", err)
  3934  		}
  3935  		prevScripts = append(prevScripts, pkScript)
  3936  		addresses = append(addresses, receiver)
  3937  		contracts = append(contracts, contract)
  3938  		txIn := wire.NewTxIn(cinfo.Output.WireOutPoint(), nil, nil)
  3939  		msgTx.AddTxIn(txIn)
  3940  		values = append(values, int64(cinfo.Output.Val))
  3941  		totalIn += cinfo.Output.Val
  3942  	}
  3943  
  3944  	// Calculate the size and the fees.
  3945  	size := btc.calcTxSize(msgTx)
  3946  	if btc.segwit {
  3947  		// Add the marker and flag weight here.
  3948  		witnessVBytes := (dexbtc.RedeemSwapSigScriptSize*uint64(len(form.Redemptions)) + 2 + 3) / 4
  3949  		size += witnessVBytes + dexbtc.P2WPKHOutputSize
  3950  	} else {
  3951  		size += dexbtc.RedeemSwapSigScriptSize*uint64(len(form.Redemptions)) + dexbtc.P2PKHOutputSize
  3952  	}
  3953  
  3954  	customCfg := new(redeemOptions)
  3955  	err := config.Unmapify(form.Options, customCfg)
  3956  	if err != nil {
  3957  		return nil, nil, 0, fmt.Errorf("error parsing selected swap options: %w", err)
  3958  	}
  3959  
  3960  	rawFeeRate := btc.targetFeeRateWithFallback(btc.redeemConfTarget(), form.FeeSuggestion)
  3961  	feeRate, err := calcBumpedRate(rawFeeRate, customCfg.FeeBump)
  3962  	if err != nil {
  3963  		btc.log.Errorf("calcBumpRate error: %v", err)
  3964  	}
  3965  	fee := feeRate * size
  3966  	if fee > totalIn {
  3967  		// Double check that the fee bump isn't the issue.
  3968  		feeRate = rawFeeRate
  3969  		fee = feeRate * size
  3970  		if fee > totalIn {
  3971  			return nil, nil, 0, fmt.Errorf("redeem tx not worth the fees")
  3972  		}
  3973  		btc.log.Warnf("Ignoring fee bump (%s) resulting in fees > redemption", float64PtrStr(customCfg.FeeBump))
  3974  	}
  3975  
  3976  	// Send the funds back to the exchange wallet.
  3977  	redeemAddr, err := btc.node.externalAddress()
  3978  	if err != nil {
  3979  		return nil, nil, 0, fmt.Errorf("error getting new address from the wallet: %w", err)
  3980  	}
  3981  	pkScript, err := txscript.PayToAddrScript(redeemAddr)
  3982  	if err != nil {
  3983  		return nil, nil, 0, fmt.Errorf("error creating change script: %w", err)
  3984  	}
  3985  	txOut := wire.NewTxOut(int64(totalIn-fee), pkScript)
  3986  	// One last check for dust.
  3987  	if btc.IsDust(txOut, feeRate) {
  3988  		return nil, nil, 0, fmt.Errorf("swap redeem output is dust")
  3989  	}
  3990  	msgTx.AddTxOut(txOut)
  3991  
  3992  	if btc.segwit {
  3993  		// NewTxSigHashes uses the PrevOutFetcher only for detecting a taproot
  3994  		// output, so we can provide a dummy that always returns a wire.TxOut
  3995  		// with a nil pkScript that so IsPayToTaproot returns false.
  3996  		sigHashes := txscript.NewTxSigHashes(msgTx, new(txscript.CannedPrevOutputFetcher))
  3997  		for i, r := range form.Redemptions {
  3998  			contract := contracts[i]
  3999  			redeemSig, redeemPubKey, err := btc.createWitnessSig(msgTx, i, contract, addresses[i], values[i], sigHashes)
  4000  			if err != nil {
  4001  				return nil, nil, 0, err
  4002  			}
  4003  			msgTx.TxIn[i].Witness = dexbtc.RedeemP2WSHContract(contract, redeemSig, redeemPubKey, r.Secret)
  4004  		}
  4005  	} else {
  4006  		for i, r := range form.Redemptions {
  4007  			contract := contracts[i]
  4008  			redeemSig, redeemPubKey, err := btc.createSig(msgTx, i, contract, addresses[i], values, prevScripts)
  4009  			if err != nil {
  4010  				return nil, nil, 0, err
  4011  			}
  4012  			msgTx.TxIn[i].SignatureScript, err = dexbtc.RedeemP2SHContract(contract, redeemSig, redeemPubKey, r.Secret)
  4013  			if err != nil {
  4014  				return nil, nil, 0, err
  4015  			}
  4016  		}
  4017  	}
  4018  
  4019  	// Send the transaction.
  4020  	txHash, err := btc.broadcastTx(msgTx)
  4021  	if err != nil {
  4022  		return nil, nil, 0, err
  4023  	}
  4024  
  4025  	btc.addTxToHistory(&asset.WalletTransaction{
  4026  		Type:   asset.Redeem,
  4027  		ID:     txHash.String(),
  4028  		Amount: totalIn,
  4029  		Fees:   fee,
  4030  	}, txHash, true)
  4031  
  4032  	// Log the change output.
  4033  	coinIDs := make([]dex.Bytes, 0, len(form.Redemptions))
  4034  	for i := range form.Redemptions {
  4035  		coinIDs = append(coinIDs, ToCoinID(txHash, uint32(i)))
  4036  	}
  4037  	return coinIDs, NewOutput(txHash, 0, uint64(txOut.Value)), fee, nil
  4038  }
  4039  
  4040  // ConvertAuditInfo converts from the common *asset.AuditInfo type to our
  4041  // internal *auditInfo type.
  4042  func ConvertAuditInfo(ai *asset.AuditInfo, decodeAddr dexbtc.AddressDecoder, chainParams *chaincfg.Params) (*AuditInfo, error) {
  4043  	if ai.Coin == nil {
  4044  		return nil, fmt.Errorf("no coin")
  4045  	}
  4046  
  4047  	txHash, vout, err := decodeCoinID(ai.Coin.ID())
  4048  	if err != nil {
  4049  		return nil, err
  4050  	}
  4051  
  4052  	recip, err := decodeAddr(ai.Recipient, chainParams)
  4053  	if err != nil {
  4054  		return nil, err
  4055  	}
  4056  
  4057  	return &AuditInfo{
  4058  		Output:     NewOutput(txHash, vout, ai.Coin.Value()), // *Output
  4059  		Recipient:  recip,                                    // btcutil.Address
  4060  		contract:   ai.Contract,                              // []byte
  4061  		secretHash: ai.SecretHash,                            // []byte
  4062  		expiration: ai.Expiration,                            // time.Time
  4063  	}, nil
  4064  }
  4065  
  4066  // SignMessage signs the message with the private key associated with the
  4067  // specified unspent coin. A slice of pubkeys required to spend the coin and a
  4068  // signature for each pubkey are returned.
  4069  func (btc *baseWallet) SignMessage(coin asset.Coin, msg dex.Bytes) (pubkeys, sigs []dex.Bytes, err error) {
  4070  	op, err := ConvertCoin(coin)
  4071  	if err != nil {
  4072  		return nil, nil, fmt.Errorf("error converting coin: %w", err)
  4073  	}
  4074  	utxo := btc.cm.LockedOutput(op.Pt)
  4075  
  4076  	if utxo == nil {
  4077  		return nil, nil, fmt.Errorf("no utxo found for %s", op)
  4078  	}
  4079  	privKey, err := btc.node.privKeyForAddress(utxo.Address)
  4080  	if err != nil {
  4081  		return nil, nil, err
  4082  	}
  4083  	defer privKey.Zero()
  4084  	pk := privKey.PubKey()
  4085  	hash := chainhash.HashB(msg) // legacy servers will not accept this signature!
  4086  	sig := ecdsa.Sign(privKey, hash)
  4087  	pubkeys = append(pubkeys, pk.SerializeCompressed())
  4088  	sigs = append(sigs, sig.Serialize()) // DER format serialization
  4089  	return
  4090  }
  4091  
  4092  // AuditContract retrieves information about a swap contract from the provided
  4093  // txData. The extracted information would be used to audit the counter-party's
  4094  // contract during a swap. The txData may be empty to attempt retrieval of the
  4095  // transaction output from the network, but it is only ensured to succeed for a
  4096  // full node or, if the tx is confirmed, an SPV wallet. Normally the server
  4097  // should communicate this txData, and the caller can decide to require it. The
  4098  // ability to work with an empty txData is a convenience for recovery tools and
  4099  // testing, and it may change in the future if a GetTxData method is added for
  4100  // this purpose.
  4101  func (btc *baseWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroadcast bool) (*asset.AuditInfo, error) {
  4102  	txHash, vout, err := decodeCoinID(coinID)
  4103  	if err != nil {
  4104  		return nil, err
  4105  	}
  4106  	// Get the receiving address.
  4107  	_, receiver, stamp, secretHash, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams)
  4108  	if err != nil {
  4109  		return nil, fmt.Errorf("error extracting swap addresses: %w", err)
  4110  	}
  4111  
  4112  	// If no tx data is provided, attempt to get the required data (the txOut)
  4113  	// from the wallet. If this is a full node wallet, a simple gettxout RPC is
  4114  	// sufficient with no pkScript or "since" time. If this is an SPV wallet,
  4115  	// only a confirmed counterparty contract can be located, and only one
  4116  	// within ContractSearchLimit. As such, this mode of operation is not
  4117  	// intended for normal server-coordinated operation.
  4118  	var tx *wire.MsgTx
  4119  	var txOut *wire.TxOut
  4120  	if len(txData) == 0 {
  4121  		// Fall back to gettxout, but we won't have the tx to rebroadcast.
  4122  		pkScript, _ := btc.scriptHashScript(contract) // pkScript and since time are unused if full node
  4123  		txOut, _, err = btc.node.getTxOut(txHash, vout, pkScript, time.Now().Add(-ContractSearchLimit))
  4124  		if err != nil || txOut == nil {
  4125  			return nil, fmt.Errorf("error finding unspent contract: %s:%d : %w", txHash, vout, err)
  4126  		}
  4127  	} else {
  4128  		tx, err = btc.deserializeTx(txData)
  4129  		if err != nil {
  4130  			return nil, fmt.Errorf("coin not found, and error encountered decoding tx data: %v", err)
  4131  		}
  4132  		if len(tx.TxOut) <= int(vout) {
  4133  			return nil, fmt.Errorf("specified output %d not found in decoded tx %s", vout, txHash)
  4134  		}
  4135  		txOut = tx.TxOut[vout]
  4136  	}
  4137  
  4138  	// Check for standard P2SH. NOTE: btc.scriptHashScript(contract) should
  4139  	// equal txOut.PkScript. All we really get from the TxOut is the *value*.
  4140  	scriptClass, addrs, numReq, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, btc.chainParams)
  4141  	if err != nil {
  4142  		return nil, fmt.Errorf("error extracting script addresses from '%x': %w", txOut.PkScript, err)
  4143  	}
  4144  	var contractHash []byte
  4145  	if btc.segwit {
  4146  		if scriptClass != txscript.WitnessV0ScriptHashTy {
  4147  			return nil, fmt.Errorf("unexpected script class. expected %s, got %s",
  4148  				txscript.WitnessV0ScriptHashTy, scriptClass)
  4149  		}
  4150  		h := sha256.Sum256(contract)
  4151  		contractHash = h[:]
  4152  	} else {
  4153  		if scriptClass != txscript.ScriptHashTy {
  4154  			return nil, fmt.Errorf("unexpected script class. expected %s, got %s",
  4155  				txscript.ScriptHashTy, scriptClass)
  4156  		}
  4157  		// Compare the contract hash to the P2SH address.
  4158  		contractHash = btcutil.Hash160(contract)
  4159  	}
  4160  	// These last two checks are probably overkill.
  4161  	if numReq != 1 {
  4162  		return nil, fmt.Errorf("unexpected number of signatures expected for P2SH script: %d", numReq)
  4163  	}
  4164  	if len(addrs) != 1 {
  4165  		return nil, fmt.Errorf("unexpected number of addresses for P2SH script: %d", len(addrs))
  4166  	}
  4167  
  4168  	addr := addrs[0]
  4169  	if !bytes.Equal(contractHash, addr.ScriptAddress()) {
  4170  		return nil, fmt.Errorf("contract hash doesn't match script address. %x != %x",
  4171  			contractHash, addr.ScriptAddress())
  4172  	}
  4173  
  4174  	// Broadcast the transaction, but do not block because this is not required
  4175  	// and does not affect the audit result.
  4176  	if rebroadcast && tx != nil {
  4177  		go func() {
  4178  			if hashSent, err := btc.node.sendRawTransaction(tx); err != nil {
  4179  				btc.log.Debugf("Rebroadcasting counterparty contract %v (THIS MAY BE NORMAL): %v", txHash, err)
  4180  			} else if !hashSent.IsEqual(txHash) {
  4181  				btc.log.Errorf("Counterparty contract %v was rebroadcast as %v!", txHash, hashSent)
  4182  			}
  4183  		}()
  4184  	}
  4185  
  4186  	addrStr, err := btc.stringAddr(receiver, btc.chainParams)
  4187  	if err != nil {
  4188  		btc.log.Errorf("Failed to stringify receiver address %v (default): %v", receiver, err)
  4189  		addrStr = receiver.String() // potentially misleading AuditInfo.Recipient
  4190  	}
  4191  
  4192  	return &asset.AuditInfo{
  4193  		Coin:       NewOutput(txHash, vout, uint64(txOut.Value)),
  4194  		Recipient:  addrStr,
  4195  		Contract:   contract,
  4196  		SecretHash: secretHash,
  4197  		Expiration: time.Unix(int64(stamp), 0).UTC(),
  4198  	}, nil
  4199  }
  4200  
  4201  // LockTimeExpired returns true if the specified locktime has expired, making it
  4202  // possible to refund the locked coins.
  4203  func (btc *baseWallet) LockTimeExpired(_ context.Context, lockTime time.Time) (bool, error) {
  4204  	medianTime, err := btc.node.medianTime() // TODO: pass ctx
  4205  	if err != nil {
  4206  		return false, fmt.Errorf("error getting median time: %w", err)
  4207  	}
  4208  	return medianTime.After(lockTime), nil
  4209  }
  4210  
  4211  // ContractLockTimeExpired returns true if the specified contract's locktime has
  4212  // expired, making it possible to issue a Refund.
  4213  func (btc *baseWallet) ContractLockTimeExpired(ctx context.Context, contract dex.Bytes) (bool, time.Time, error) {
  4214  	_, _, locktime, _, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams)
  4215  	if err != nil {
  4216  		return false, time.Time{}, fmt.Errorf("error extracting contract locktime: %w", err)
  4217  	}
  4218  	contractExpiry := time.Unix(int64(locktime), 0).UTC()
  4219  	expired, err := btc.LockTimeExpired(ctx, contractExpiry)
  4220  	if err != nil {
  4221  		return false, time.Time{}, err
  4222  	}
  4223  	return expired, contractExpiry, nil
  4224  }
  4225  
  4226  // FindRedemption watches for the input that spends the specified contract
  4227  // coin, and returns the spending input and the contract's secret key when it
  4228  // finds a spender.
  4229  //
  4230  // This method blocks until the redemption is found, an error occurs or the
  4231  // provided context is canceled.
  4232  func (btc *intermediaryWallet) FindRedemption(ctx context.Context, coinID, _ dex.Bytes) (redemptionCoin, secret dex.Bytes, err error) {
  4233  	return btc.rf.FindRedemption(ctx, coinID)
  4234  }
  4235  
  4236  // Refund revokes a contract. This can only be used after the time lock has
  4237  // expired. This MUST return an asset.CoinNotFoundError error if the coin is
  4238  // spent.
  4239  // NOTE: The contract cannot be retrieved from the unspent coin info as the
  4240  // wallet does not store it, even though it was known when the init transaction
  4241  // was created. The client should store this information for persistence across
  4242  // sessions.
  4243  func (btc *baseWallet) Refund(coinID, contract dex.Bytes, feeRate uint64) (dex.Bytes, error) {
  4244  	txHash, vout, err := decodeCoinID(coinID)
  4245  	if err != nil {
  4246  		return nil, err
  4247  	}
  4248  
  4249  	pkScript, err := btc.scriptHashScript(contract)
  4250  	if err != nil {
  4251  		return nil, fmt.Errorf("error parsing pubkey script: %w", err)
  4252  	}
  4253  
  4254  	if feeRate == 0 {
  4255  		feeRate = btc.targetFeeRateWithFallback(2, 0)
  4256  	}
  4257  
  4258  	// TODO: I'd recommend not passing a pkScript without a limited startTime
  4259  	// to prevent potentially long searches. In this case though, the output
  4260  	// will be found in the wallet and won't need to be searched for, only
  4261  	// the spender search will be conducted using the pkScript starting from
  4262  	// the block containing the original tx. The script can be gotten from
  4263  	// the wallet tx though and used for the spender search, while not passing
  4264  	// a script here to ensure no attempt is made to find the output without
  4265  	// a limited startTime.
  4266  	utxo, _, err := btc.node.getTxOut(txHash, vout, pkScript, time.Time{})
  4267  	if err != nil {
  4268  		return nil, fmt.Errorf("error finding unspent contract: %w", err)
  4269  	}
  4270  	if utxo == nil {
  4271  		return nil, asset.CoinNotFoundError // spent
  4272  	}
  4273  	msgTx, err := btc.refundTx(txHash, vout, contract, uint64(utxo.Value), nil, feeRate)
  4274  	if err != nil {
  4275  		return nil, fmt.Errorf("error creating refund tx: %w", err)
  4276  	}
  4277  
  4278  	refundHash, err := btc.broadcastTx(msgTx)
  4279  	if err != nil {
  4280  		return nil, fmt.Errorf("broadcastTx: %w", err)
  4281  	}
  4282  
  4283  	var fee uint64
  4284  	if len(msgTx.TxOut) > 0 { // something went very wrong if not true
  4285  		fee = uint64(utxo.Value - msgTx.TxOut[0].Value)
  4286  	}
  4287  	btc.addTxToHistory(&asset.WalletTransaction{
  4288  		Type:   asset.Refund,
  4289  		ID:     refundHash.String(),
  4290  		Amount: uint64(utxo.Value),
  4291  		Fees:   fee,
  4292  	}, refundHash, true)
  4293  
  4294  	return ToCoinID(refundHash, 0), nil
  4295  }
  4296  
  4297  // refundTx creates and signs a contract`s refund transaction. If refundAddr is
  4298  // not supplied, one will be requested from the wallet.
  4299  func (btc *baseWallet) refundTx(txHash *chainhash.Hash, vout uint32, contract dex.Bytes, val uint64, refundAddr btcutil.Address, feeRate uint64) (*wire.MsgTx, error) {
  4300  	sender, _, lockTime, _, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams)
  4301  	if err != nil {
  4302  		return nil, fmt.Errorf("error extracting swap addresses: %w", err)
  4303  	}
  4304  
  4305  	// Create the transaction that spends the contract.
  4306  	msgTx := wire.NewMsgTx(btc.txVersion())
  4307  	msgTx.LockTime = uint32(lockTime)
  4308  	prevOut := wire.NewOutPoint(txHash, vout)
  4309  	txIn := wire.NewTxIn(prevOut, []byte{}, nil)
  4310  	// Enable the OP_CHECKLOCKTIMEVERIFY opcode to be used.
  4311  	//
  4312  	// https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#Spending_wallet_policy
  4313  	txIn.Sequence = wire.MaxTxInSequenceNum - 1
  4314  	msgTx.AddTxIn(txIn)
  4315  	// Calculate fees and add the change output.
  4316  
  4317  	size := btc.calcTxSize(msgTx)
  4318  
  4319  	if btc.segwit {
  4320  		// Add the marker and flag weight too.
  4321  		witnessVBytes := uint64((dexbtc.RefundSigScriptSize + 2 + 3) / 4)
  4322  		size += witnessVBytes + dexbtc.P2WPKHOutputSize
  4323  	} else {
  4324  		size += dexbtc.RefundSigScriptSize + dexbtc.P2PKHOutputSize
  4325  	}
  4326  
  4327  	fee := feeRate * size // TODO: use btc.FeeRate in caller and fallback to nfo.MaxFeeRate
  4328  	if fee > val {
  4329  		return nil, fmt.Errorf("refund tx not worth the fees")
  4330  	}
  4331  	if refundAddr == nil {
  4332  		refundAddr, err = btc.node.externalAddress()
  4333  		if err != nil {
  4334  			return nil, fmt.Errorf("error getting new address from the wallet: %w", err)
  4335  		}
  4336  	}
  4337  	pkScript, err := txscript.PayToAddrScript(refundAddr)
  4338  	if err != nil {
  4339  		return nil, fmt.Errorf("error creating change script: %w", err)
  4340  	}
  4341  	txOut := wire.NewTxOut(int64(val-fee), pkScript)
  4342  	// One last check for dust.
  4343  	if btc.IsDust(txOut, feeRate) {
  4344  		return nil, fmt.Errorf("refund output is dust")
  4345  	}
  4346  	msgTx.AddTxOut(txOut)
  4347  
  4348  	if btc.segwit {
  4349  		sigHashes := txscript.NewTxSigHashes(msgTx, new(txscript.CannedPrevOutputFetcher))
  4350  		refundSig, refundPubKey, err := btc.createWitnessSig(msgTx, 0, contract, sender, int64(val), sigHashes)
  4351  		if err != nil {
  4352  			return nil, fmt.Errorf("createWitnessSig: %w", err)
  4353  		}
  4354  		txIn.Witness = dexbtc.RefundP2WSHContract(contract, refundSig, refundPubKey)
  4355  
  4356  	} else {
  4357  		prevScript, err := btc.scriptHashScript(contract)
  4358  		if err != nil {
  4359  			return nil, fmt.Errorf("error constructing p2sh script: %w", err)
  4360  		}
  4361  
  4362  		refundSig, refundPubKey, err := btc.createSig(msgTx, 0, contract, sender, []int64{int64(val)}, [][]byte{prevScript})
  4363  		if err != nil {
  4364  			return nil, fmt.Errorf("createSig: %w", err)
  4365  		}
  4366  		txIn.SignatureScript, err = dexbtc.RefundP2SHContract(contract, refundSig, refundPubKey)
  4367  		if err != nil {
  4368  			return nil, fmt.Errorf("RefundP2SHContract: %w", err)
  4369  		}
  4370  	}
  4371  	return msgTx, nil
  4372  }
  4373  
  4374  // DepositAddress returns an address for depositing funds into the
  4375  // exchange wallet.
  4376  func (btc *baseWallet) DepositAddress() (string, error) {
  4377  	addr, err := btc.node.externalAddress()
  4378  	if err != nil {
  4379  		return "", err
  4380  	}
  4381  	addrStr, err := btc.stringAddr(addr, btc.chainParams)
  4382  	if err != nil {
  4383  		return "", err
  4384  	}
  4385  	if btc.node.locked() {
  4386  		return addrStr, nil
  4387  	}
  4388  
  4389  	// If the wallet is unlocked, be extra cautious and ensure the wallet gave
  4390  	// us an address for which we can retrieve the private keys, regardless of
  4391  	// what ownsAddress would say.
  4392  	priv, err := btc.node.privKeyForAddress(addrStr)
  4393  	if err != nil {
  4394  		return "", fmt.Errorf("private key unavailable for address %v: %w", addrStr, err)
  4395  	}
  4396  	priv.Zero()
  4397  	return addrStr, nil
  4398  }
  4399  
  4400  // RedemptionAddress gets an address for use in redeeming the counterparty's
  4401  // swap. This would be included in their swap initialization.
  4402  func (btc *baseWallet) RedemptionAddress() (string, error) {
  4403  	return btc.recyclableAddress()
  4404  }
  4405  
  4406  // A recyclable address is a redemption or refund address that may be recycled
  4407  // if unused. If already recycled addresses are available, one will be returned.
  4408  func (btc *baseWallet) recyclableAddress() (string, error) {
  4409  	var returns []string
  4410  	defer btc.ar.ReturnAddresses(returns)
  4411  	for {
  4412  		addr := btc.ar.Address()
  4413  		if addr == "" {
  4414  			break
  4415  		}
  4416  		if owns, err := btc.OwnsDepositAddress(addr); owns {
  4417  			return addr, nil
  4418  		} else if err != nil {
  4419  			btc.log.Errorf("Error checking ownership of recycled address %q: %v", addr, err)
  4420  			returns = append(returns, addr)
  4421  		}
  4422  	}
  4423  	return btc.DepositAddress()
  4424  }
  4425  
  4426  // ReturnRefundContracts should be called with the Receipt.Contract() data for
  4427  // any swaps that will not be refunded.
  4428  func (btc *baseWallet) ReturnRefundContracts(contracts [][]byte) {
  4429  	addrs := make([]string, 0, len(contracts))
  4430  	for _, c := range contracts {
  4431  		sender, _, _, _, err := dexbtc.ExtractSwapDetails(c, btc.segwit, btc.chainParams)
  4432  		if err != nil {
  4433  			btc.log.Errorf("Error extracting refund address from contract '%x': %v", c, err)
  4434  			continue
  4435  		}
  4436  		addr, err := btc.stringAddr(sender, btc.chainParams)
  4437  		if err != nil {
  4438  			btc.log.Errorf("Error stringifying address %q: %v", addr, err)
  4439  			continue
  4440  		}
  4441  		addrs = append(addrs, addr)
  4442  	}
  4443  	if len(addrs) > 0 {
  4444  		btc.ar.ReturnAddresses(addrs)
  4445  	}
  4446  }
  4447  
  4448  // ReturnRedemptionAddress accepts a Wallet.RedemptionAddress() if the address
  4449  // will not be used.
  4450  func (btc *baseWallet) ReturnRedemptionAddress(addr string) {
  4451  	btc.ar.ReturnAddresses([]string{addr})
  4452  }
  4453  
  4454  // NewAddress returns a new address from the wallet. This satisfies the
  4455  // NewAddresser interface.
  4456  func (btc *baseWallet) NewAddress() (string, error) {
  4457  	return btc.DepositAddress()
  4458  }
  4459  
  4460  // Withdraw withdraws funds to the specified address. Fees are subtracted from
  4461  // the value. feeRate is in units of sats/byte.
  4462  // Withdraw satisfies asset.Withdrawer.
  4463  func (btc *baseWallet) Withdraw(address string, value, feeRate uint64) (asset.Coin, error) {
  4464  	txHash, vout, sent, err := btc.send(address, value, btc.feeRateWithFallback(feeRate), true)
  4465  	if err != nil {
  4466  		return nil, err
  4467  	}
  4468  	return NewOutput(txHash, vout, sent), nil
  4469  }
  4470  
  4471  // Send sends the exact value to the specified address. This is different from
  4472  // Withdraw, which subtracts the tx fees from the amount sent. feeRate is in
  4473  // units of sats/byte.
  4474  func (btc *baseWallet) Send(address string, value, feeRate uint64) (asset.Coin, error) {
  4475  	txHash, vout, sent, err := btc.send(address, value, btc.feeRateWithFallback(feeRate), false)
  4476  	if err != nil {
  4477  		return nil, err
  4478  	}
  4479  	return NewOutput(txHash, vout, sent), nil
  4480  }
  4481  
  4482  // SendTransaction broadcasts a valid fully-signed transaction.
  4483  func (btc *baseWallet) SendTransaction(rawTx []byte) ([]byte, error) {
  4484  	msgTx, err := btc.deserializeTx(rawTx)
  4485  	if err != nil {
  4486  		return nil, err
  4487  	}
  4488  
  4489  	txHash, err := btc.node.sendRawTransaction(msgTx)
  4490  	if err != nil {
  4491  		return nil, err
  4492  	}
  4493  
  4494  	btc.markTxAsSubmitted(txHash)
  4495  
  4496  	return ToCoinID(txHash, 0), nil
  4497  }
  4498  
  4499  // ValidateSecret checks that the secret satisfies the contract.
  4500  func (btc *baseWallet) ValidateSecret(secret, secretHash []byte) bool {
  4501  	h := sha256.Sum256(secret)
  4502  	return bytes.Equal(h[:], secretHash)
  4503  }
  4504  
  4505  // send the value to the address, with the given fee rate. If subtract is true,
  4506  // the fees will be subtracted from the value. If false, the fees are in
  4507  // addition to the value. feeRate is in units of sats/byte.
  4508  func (btc *baseWallet) send(address string, val uint64, feeRate uint64, subtract bool) (*chainhash.Hash, uint32, uint64, error) {
  4509  	addr, err := btc.decodeAddr(address, btc.chainParams)
  4510  	if err != nil {
  4511  		return nil, 0, 0, fmt.Errorf("invalid address: %s", address)
  4512  	}
  4513  	var pay2script []byte
  4514  	if scripter, is := addr.(PaymentScripter); is {
  4515  		pay2script, err = scripter.PaymentScript()
  4516  	} else {
  4517  		pay2script, err = txscript.PayToAddrScript(addr)
  4518  	}
  4519  	if err != nil {
  4520  		return nil, 0, 0, fmt.Errorf("PayToAddrScript error: %w", err)
  4521  	}
  4522  
  4523  	baseSize := dexbtc.MinimumTxOverhead
  4524  	if btc.segwit {
  4525  		baseSize += dexbtc.P2WPKHOutputSize * 2
  4526  	} else {
  4527  		baseSize += dexbtc.P2PKHOutputSize * 2
  4528  	}
  4529  
  4530  	enough := sendEnough(val, feeRate, subtract, uint64(baseSize), btc.segwit, true)
  4531  	minConfs := uint32(0)
  4532  	coins, _, _, _, inputsSize, _, err := btc.cm.Fund(btc.bondReserves.Load(), minConfs, false, enough)
  4533  	if err != nil {
  4534  		return nil, 0, 0, fmt.Errorf("error funding transaction: %w", err)
  4535  	}
  4536  
  4537  	fundedTx, totalIn, _, err := btc.fundedTx(coins)
  4538  	if err != nil {
  4539  		return nil, 0, 0, fmt.Errorf("error adding inputs to transaction: %w", err)
  4540  	}
  4541  
  4542  	fees := feeRate * (inputsSize + uint64(baseSize))
  4543  	var toSend uint64
  4544  	if subtract {
  4545  		toSend = val - fees
  4546  	} else {
  4547  		toSend = val
  4548  	}
  4549  	fundedTx.AddTxOut(wire.NewTxOut(int64(toSend), pay2script))
  4550  
  4551  	changeAddr, err := btc.node.changeAddress()
  4552  	if err != nil {
  4553  		return nil, 0, 0, fmt.Errorf("error creating change address: %w", err)
  4554  	}
  4555  
  4556  	msgTx, err := btc.sendWithReturn(fundedTx, changeAddr, totalIn, toSend, feeRate)
  4557  	if err != nil {
  4558  		return nil, 0, 0, err
  4559  	}
  4560  
  4561  	txHash := btc.hashTx(msgTx)
  4562  
  4563  	var totalOut uint64
  4564  	for _, txOut := range msgTx.TxOut {
  4565  		totalOut += uint64(txOut.Value)
  4566  	}
  4567  
  4568  	selfSend, err := btc.OwnsDepositAddress(address)
  4569  	if err != nil {
  4570  		return nil, 0, 0, fmt.Errorf("error checking address ownership: %w", err)
  4571  	}
  4572  	txType := asset.Send
  4573  	if selfSend {
  4574  		txType = asset.SelfSend
  4575  	}
  4576  
  4577  	btc.addTxToHistory(&asset.WalletTransaction{
  4578  		Type:      txType,
  4579  		ID:        txHash.String(),
  4580  		Amount:    toSend,
  4581  		Fees:      totalIn - totalOut,
  4582  		Recipient: &address,
  4583  	}, txHash, true)
  4584  
  4585  	return txHash, 0, toSend, nil
  4586  }
  4587  
  4588  // SwapConfirmations gets the number of confirmations for the specified swap
  4589  // by first checking for a unspent output, and if not found, searching indexed
  4590  // wallet transactions.
  4591  func (btc *baseWallet) SwapConfirmations(_ context.Context, id dex.Bytes, contract dex.Bytes, startTime time.Time) (uint32, bool, error) {
  4592  	txHash, vout, err := decodeCoinID(id)
  4593  	if err != nil {
  4594  		return 0, false, err
  4595  	}
  4596  	pkScript, err := btc.scriptHashScript(contract)
  4597  	if err != nil {
  4598  		return 0, false, err
  4599  	}
  4600  	return btc.node.swapConfirmations(txHash, vout, pkScript, startTime)
  4601  }
  4602  
  4603  // RegFeeConfirmations gets the number of confirmations for the specified output
  4604  // by first checking for a unspent output, and if not found, searching indexed
  4605  // wallet transactions.
  4606  func (btc *baseWallet) RegFeeConfirmations(_ context.Context, id dex.Bytes) (confs uint32, err error) {
  4607  	txHash, _, err := decodeCoinID(id)
  4608  	if err != nil {
  4609  		return 0, err
  4610  	}
  4611  	_, confs, err = btc.rawWalletTx(txHash)
  4612  	return
  4613  }
  4614  
  4615  func (btc *baseWallet) checkPeers() {
  4616  	numPeers, err := btc.node.peerCount()
  4617  	if err != nil {
  4618  		prevPeer := atomic.SwapUint32(&btc.lastPeerCount, 0)
  4619  		if prevPeer != 0 {
  4620  			btc.log.Errorf("Failed to get peer count: %v", err)
  4621  			btc.peersChange(0, err)
  4622  		}
  4623  		return
  4624  	}
  4625  	prevPeer := atomic.SwapUint32(&btc.lastPeerCount, numPeers)
  4626  	if prevPeer != numPeers {
  4627  		btc.peersChange(numPeers, nil)
  4628  	}
  4629  }
  4630  
  4631  func (btc *baseWallet) monitorPeers(ctx context.Context) {
  4632  	ticker := time.NewTicker(peerCountTicker)
  4633  	defer ticker.Stop()
  4634  	for {
  4635  		btc.checkPeers()
  4636  
  4637  		select {
  4638  		case <-ticker.C:
  4639  		case <-ctx.Done():
  4640  			return
  4641  		}
  4642  	}
  4643  }
  4644  
  4645  // watchBlocks pings for new blocks and runs the tipChange callback function
  4646  // when the block changes.
  4647  func (btc *intermediaryWallet) watchBlocks(ctx context.Context) {
  4648  	ticker := time.NewTicker(blockTicker)
  4649  	defer ticker.Stop()
  4650  
  4651  	var walletBlock <-chan *BlockVector
  4652  	if notifier, isNotifier := btc.node.(tipNotifier); isNotifier {
  4653  		walletBlock = notifier.tipFeed()
  4654  	}
  4655  
  4656  	// A polledBlock is a block found during polling, but whose broadcast has
  4657  	// been queued in anticipation of a wallet notification.
  4658  	type polledBlock struct {
  4659  		*BlockVector
  4660  		queue *time.Timer
  4661  	}
  4662  
  4663  	// queuedBlock is the currently queued, polling-discovered block that will
  4664  	// be broadcast after a timeout if the wallet doesn't send the matching
  4665  	// notification.
  4666  	var queuedBlock *polledBlock
  4667  
  4668  	for {
  4669  		select {
  4670  
  4671  		// Poll for the block. If the wallet offers tip reports, delay reporting
  4672  		// the tip to give the wallet a moment to request and scan block data.
  4673  		case <-ticker.C:
  4674  			newTipHdr, err := btc.node.getBestBlockHeader()
  4675  			if err != nil {
  4676  				btc.log.Errorf("failed to get best block header from %s node: %v", btc.symbol, err)
  4677  				continue
  4678  			}
  4679  			newTipHash, err := chainhash.NewHashFromStr(newTipHdr.Hash)
  4680  			if err != nil {
  4681  				btc.log.Errorf("invalid best block hash from %s node: %v", btc.symbol, err)
  4682  				continue
  4683  			}
  4684  
  4685  			if queuedBlock != nil && *newTipHash == queuedBlock.BlockVector.Hash {
  4686  				continue
  4687  			}
  4688  
  4689  			btc.tipMtx.RLock()
  4690  			sameTip := btc.currentTip.Hash == *newTipHash
  4691  			btc.tipMtx.RUnlock()
  4692  			if sameTip {
  4693  				continue
  4694  			}
  4695  
  4696  			newTip := &BlockVector{newTipHdr.Height, *newTipHash}
  4697  
  4698  			// If the wallet is not offering tip reports, send this one right
  4699  			// away.
  4700  			if walletBlock == nil {
  4701  				btc.reportNewTip(ctx, newTip)
  4702  			} else {
  4703  				// Queue it for reporting, but don't send it right away. Give the
  4704  				// wallet a chance to provide their block update. SPV wallet may
  4705  				// need more time after storing the block header to fetch and
  4706  				// scan filters and issue the FilteredBlockConnected report.
  4707  				if queuedBlock != nil {
  4708  					queuedBlock.queue.Stop()
  4709  				}
  4710  				blockAllowance := walletBlockAllowance
  4711  				syncStatus, err := btc.node.syncStatus()
  4712  				if err != nil {
  4713  					btc.log.Errorf("Error retrieving sync status before queuing polled block: %v", err)
  4714  				} else if !syncStatus.Synced {
  4715  					blockAllowance *= 10
  4716  				}
  4717  				queuedBlock = &polledBlock{
  4718  					BlockVector: newTip,
  4719  					queue: time.AfterFunc(blockAllowance, func() {
  4720  						if ss, _ := btc.SyncStatus(); ss != nil && ss.Synced {
  4721  							btc.log.Warnf("Reporting a block found in polling that the wallet apparently "+
  4722  								"never reported: %d %s. If you see this message repeatedly, it may indicate "+
  4723  								"an issue with the wallet.", newTip.Height, newTip.Hash)
  4724  						}
  4725  						btc.reportNewTip(ctx, newTip)
  4726  					}),
  4727  				}
  4728  			}
  4729  
  4730  		// Tip reports from the wallet are always sent, and we'll clear any
  4731  		// queued polled block that would appear to be superceded by this one.
  4732  		case walletTip := <-walletBlock:
  4733  			if queuedBlock != nil && walletTip.Height >= queuedBlock.Height {
  4734  				if !queuedBlock.queue.Stop() && walletTip.Hash == queuedBlock.Hash {
  4735  					continue
  4736  				}
  4737  				queuedBlock = nil
  4738  			}
  4739  			btc.reportNewTip(ctx, walletTip)
  4740  
  4741  		case <-ctx.Done():
  4742  			return
  4743  		}
  4744  
  4745  		// Ensure context cancellation takes priority before the next iteration.
  4746  		if ctx.Err() != nil {
  4747  			return
  4748  		}
  4749  	}
  4750  }
  4751  
  4752  // reportNewTip sets the currentTip. The tipChange callback function is invoked
  4753  // and RedemptionFinder is informed of the new block.
  4754  func (btc *intermediaryWallet) reportNewTip(ctx context.Context, newTip *BlockVector) {
  4755  	btc.tipMtx.Lock()
  4756  	defer btc.tipMtx.Unlock()
  4757  
  4758  	prevTip := btc.currentTip
  4759  	btc.currentTip = newTip
  4760  	btc.log.Tracef("tip change: %d (%s) => %d (%s)", prevTip.Height, prevTip.Hash, newTip.Height, newTip.Hash)
  4761  	btc.emit.TipChange(uint64(newTip.Height))
  4762  
  4763  	go btc.syncTxHistory(uint64(newTip.Height))
  4764  
  4765  	btc.rf.ReportNewTip(ctx, prevTip, newTip)
  4766  }
  4767  
  4768  // sendWithReturn sends the unsigned transaction with an added output (unless
  4769  // dust) for the change.
  4770  func (btc *baseWallet) sendWithReturn(baseTx *wire.MsgTx, addr btcutil.Address,
  4771  	totalIn, totalOut, feeRate uint64) (*wire.MsgTx, error) {
  4772  
  4773  	signedTx, _, _, err := btc.signTxAndAddChange(baseTx, addr, totalIn, totalOut, feeRate)
  4774  	if err != nil {
  4775  		return nil, err
  4776  	}
  4777  
  4778  	_, err = btc.broadcastTx(signedTx)
  4779  	return signedTx, err
  4780  }
  4781  
  4782  // signTxAndAddChange signs the passed tx and adds a change output if the change
  4783  // wouldn't be dust. Returns but does NOT broadcast the signed tx.
  4784  func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Address,
  4785  	totalIn, totalOut, feeRate uint64) (*wire.MsgTx, *Output, uint64, error) {
  4786  
  4787  	makeErr := func(s string, a ...any) (*wire.MsgTx, *Output, uint64, error) {
  4788  		return nil, nil, 0, fmt.Errorf(s, a...)
  4789  	}
  4790  
  4791  	// Sign the transaction to get an initial size estimate and calculate whether
  4792  	// a change output would be dust.
  4793  	sigCycles := 1
  4794  	msgTx, err := btc.node.signTx(baseTx)
  4795  	if err != nil {
  4796  		return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx))
  4797  	}
  4798  	vSize := btc.calcTxSize(msgTx)
  4799  	minFee := feeRate * vSize
  4800  	remaining := totalIn - totalOut
  4801  	if minFee > remaining {
  4802  		return makeErr("not enough funds to cover minimum fee rate. %.8f < %.8f, raw tx: %x",
  4803  			toBTC(totalIn), toBTC(minFee+totalOut), btc.wireBytes(baseTx))
  4804  	}
  4805  
  4806  	// Create a change output.
  4807  	changeScript, err := txscript.PayToAddrScript(addr)
  4808  	if err != nil {
  4809  		return makeErr("error creating change script: %v", err)
  4810  	}
  4811  	changeFees := dexbtc.P2PKHOutputSize * feeRate
  4812  	if btc.segwit {
  4813  		changeFees = dexbtc.P2WPKHOutputSize * feeRate
  4814  	}
  4815  	changeIdx := len(baseTx.TxOut)
  4816  	changeOutput := wire.NewTxOut(int64(remaining-minFee-changeFees), changeScript)
  4817  	if changeFees+minFee > remaining { // Prevent underflow
  4818  		changeOutput.Value = 0
  4819  	}
  4820  	// If the change is not dust, recompute the signed txn size and iterate on
  4821  	// the fees vs. change amount.
  4822  	changeAdded := !btc.IsDust(changeOutput, feeRate)
  4823  	if changeAdded {
  4824  		// Add the change output.
  4825  		vSize0 := btc.calcTxSize(baseTx)
  4826  		baseTx.AddTxOut(changeOutput)
  4827  		changeSize := btc.calcTxSize(baseTx) - vSize0       // may be dexbtc.P2WPKHOutputSize
  4828  		addrStr, _ := btc.stringAddr(addr, btc.chainParams) // just for logging
  4829  		btc.log.Tracef("Change output size = %d, addr = %s", changeSize, addrStr)
  4830  
  4831  		vSize += changeSize
  4832  		fee := feeRate * vSize
  4833  		changeOutput.Value = int64(remaining - fee)
  4834  		// Find the best fee rate by closing in on it in a loop.
  4835  		tried := map[uint64]bool{}
  4836  		for {
  4837  			// Sign the transaction with the change output and compute new size.
  4838  			sigCycles++
  4839  			msgTx, err = btc.node.signTx(baseTx)
  4840  			if err != nil {
  4841  				return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx))
  4842  			}
  4843  			vSize = btc.calcTxSize(msgTx) // recompute the size with new tx signature
  4844  			reqFee := feeRate * vSize
  4845  			if reqFee > remaining {
  4846  				// I can't imagine a scenario where this condition would be true, but
  4847  				// I'd hate to be wrong.
  4848  				btc.log.Errorf("reached the impossible place. in = %.8f, out = %.8f, reqFee = %.8f, lastFee = %.8f, raw tx = %x, vSize = %d, feeRate = %d",
  4849  					toBTC(totalIn), toBTC(totalOut), toBTC(reqFee), toBTC(fee), btc.wireBytes(msgTx), vSize, feeRate)
  4850  				return makeErr("change error")
  4851  			}
  4852  			if fee == reqFee || (fee > reqFee && tried[reqFee]) {
  4853  				// If a lower fee appears available, but it's already been attempted and
  4854  				// had a longer serialized size, the current fee is likely as good as
  4855  				// it gets.
  4856  				break
  4857  			}
  4858  
  4859  			// We must have some room for improvement.
  4860  			tried[fee] = true
  4861  			fee = reqFee
  4862  			changeOutput.Value = int64(remaining - fee)
  4863  			if btc.IsDust(changeOutput, feeRate) {
  4864  				// Another condition that should be impossible, but check anyway in case
  4865  				// the maximum fee was underestimated causing the first check to be
  4866  				// missed.
  4867  				btc.log.Errorf("reached the impossible place. in = %.8f, out = %.8f, reqFee = %.8f, lastFee = %.8f, raw tx = %x",
  4868  					toBTC(totalIn), toBTC(totalOut), toBTC(reqFee), toBTC(fee), btc.wireBytes(msgTx))
  4869  				return makeErr("dust error")
  4870  			}
  4871  			continue
  4872  		}
  4873  
  4874  		totalOut += uint64(changeOutput.Value)
  4875  	} else {
  4876  		btc.log.Debugf("Foregoing change worth up to %v in tx %v because it is dust",
  4877  			changeOutput.Value, btc.hashTx(msgTx))
  4878  	}
  4879  
  4880  	txHash := btc.hashTx(msgTx)
  4881  
  4882  	fee := totalIn - totalOut
  4883  	actualFeeRate := fee / vSize
  4884  	btc.log.Debugf("%d signature cycles to converge on fees for tx %s: "+
  4885  		"min rate = %d, actual fee rate = %d (%v for %v bytes), change = %v",
  4886  		sigCycles, txHash, feeRate, actualFeeRate, fee, vSize, changeAdded)
  4887  
  4888  	var change *Output
  4889  	if changeAdded {
  4890  		change = NewOutput(txHash, uint32(changeIdx), uint64(changeOutput.Value))
  4891  	}
  4892  
  4893  	return msgTx, change, fee, nil
  4894  }
  4895  
  4896  func (btc *baseWallet) broadcastTx(signedTx *wire.MsgTx) (*chainhash.Hash, error) {
  4897  	txHash, err := btc.node.sendRawTransaction(signedTx)
  4898  	if err != nil {
  4899  		return nil, fmt.Errorf("sendrawtx error: %v, raw tx: %x", err, btc.wireBytes(signedTx))
  4900  	}
  4901  	checkHash := btc.hashTx(signedTx)
  4902  	if *txHash != *checkHash {
  4903  		return nil, fmt.Errorf("transaction sent, but received unexpected transaction ID back from RPC server. "+
  4904  			"expected %s, got %s. raw tx: %x", checkHash, *txHash, btc.wireBytes(signedTx))
  4905  	}
  4906  	return txHash, nil
  4907  }
  4908  
  4909  // createSig creates and returns the serialized raw signature and compressed
  4910  // pubkey for a transaction input signature.
  4911  func (btc *baseWallet) createSig(tx *wire.MsgTx, idx int, pkScript []byte, addr btcutil.Address, vals []int64, pkScripts [][]byte) (sig, pubkey []byte, err error) {
  4912  	addrStr, err := btc.stringAddr(addr, btc.chainParams)
  4913  	if err != nil {
  4914  		return nil, nil, err
  4915  	}
  4916  
  4917  	privKey, err := btc.node.privKeyForAddress(addrStr)
  4918  	if err != nil {
  4919  		return nil, nil, err
  4920  	}
  4921  	defer privKey.Zero()
  4922  
  4923  	sig, err = btc.signNonSegwit(tx, idx, pkScript, txscript.SigHashAll, privKey, vals, pkScripts)
  4924  	if err != nil {
  4925  		return nil, nil, err
  4926  	}
  4927  
  4928  	return sig, privKey.PubKey().SerializeCompressed(), nil
  4929  }
  4930  
  4931  // createWitnessSig creates and returns a signature for the witness of a segwit
  4932  // input and the pubkey associated with the address.
  4933  func (btc *baseWallet) createWitnessSig(tx *wire.MsgTx, idx int, pkScript []byte,
  4934  	addr btcutil.Address, val int64, sigHashes *txscript.TxSigHashes) (sig, pubkey []byte, err error) {
  4935  	addrStr, err := btc.stringAddr(addr, btc.chainParams)
  4936  	if err != nil {
  4937  		return nil, nil, err
  4938  	}
  4939  	privKey, err := btc.node.privKeyForAddress(addrStr)
  4940  	if err != nil {
  4941  		return nil, nil, err
  4942  	}
  4943  	defer privKey.Zero()
  4944  	sig, err = txscript.RawTxInWitnessSignature(tx, sigHashes, idx, val,
  4945  		pkScript, txscript.SigHashAll, privKey)
  4946  
  4947  	if err != nil {
  4948  		return nil, nil, err
  4949  	}
  4950  	return sig, privKey.PubKey().SerializeCompressed(), nil
  4951  }
  4952  
  4953  // ValidateAddress checks that the provided address is valid.
  4954  func (btc *baseWallet) ValidateAddress(address string) bool {
  4955  	_, err := btc.decodeAddr(address, btc.chainParams)
  4956  	return err == nil
  4957  }
  4958  
  4959  // dummyP2PKHScript only has to be a valid 25-byte pay-to-pubkey-hash pkScript
  4960  // for EstimateSendTxFee when an empty or invalid address is provided.
  4961  var dummyP2PKHScript = []byte{0x76, 0xa9, 0x14, 0xe4, 0x28, 0x61, 0xa,
  4962  	0xfc, 0xd0, 0x4e, 0x21, 0x94, 0xf7, 0xe2, 0xcc, 0xf8,
  4963  	0x58, 0x7a, 0xc9, 0xe7, 0x2c, 0x79, 0x7b, 0x88, 0xac,
  4964  }
  4965  
  4966  // EstimateSendTxFee returns a tx fee estimate for sending or withdrawing the
  4967  // provided amount using the provided feeRate.
  4968  func (btc *intermediaryWallet) EstimateSendTxFee(address string, sendAmount, feeRate uint64, subtract, _ bool) (fee uint64, isValidAddress bool, err error) {
  4969  	if sendAmount == 0 {
  4970  		return 0, false, fmt.Errorf("cannot check fee: send amount = 0")
  4971  	}
  4972  
  4973  	var pkScript []byte
  4974  	if addr, err := btc.decodeAddr(address, btc.chainParams); err == nil {
  4975  		pkScript, err = txscript.PayToAddrScript(addr)
  4976  		if err != nil {
  4977  			return 0, false, fmt.Errorf("error generating pubkey script: %w", err)
  4978  		}
  4979  		isValidAddress = true
  4980  	} else {
  4981  		// use a dummy 25-byte p2pkh script
  4982  		pkScript = dummyP2PKHScript
  4983  	}
  4984  
  4985  	wireOP := wire.NewTxOut(int64(sendAmount), pkScript)
  4986  	if dexbtc.IsDust(wireOP, feeRate) {
  4987  		return 0, false, errors.New("output value is dust")
  4988  	}
  4989  
  4990  	tx := wire.NewMsgTx(btc.txVersion())
  4991  	tx.AddTxOut(wireOP)
  4992  	fee, err = btc.txFeeEstimator.estimateSendTxFee(tx, btc.feeRateWithFallback(feeRate), subtract)
  4993  	if err != nil {
  4994  		return 0, false, err
  4995  	}
  4996  	return fee, isValidAddress, nil
  4997  }
  4998  
  4999  // StandardSendFees returns the fees for a simple send tx with one input and two
  5000  // outputs.
  5001  func (btc *baseWallet) StandardSendFee(feeRate uint64) uint64 {
  5002  	var sz uint64 = dexbtc.MinimumTxOverhead
  5003  	if btc.segwit {
  5004  		inputSize := dexbtc.RedeemP2WPKHInputSize + uint64((dexbtc.RedeemP2PKSigScriptSize+2+3)/4)
  5005  		sz += inputSize + dexbtc.P2WPKHOutputSize*2
  5006  	} else {
  5007  		sz += dexbtc.RedeemP2PKHInputSize + dexbtc.P2PKHOutputSize*2
  5008  	}
  5009  	return feeRate * sz
  5010  }
  5011  
  5012  func (btc *baseWallet) SetBondReserves(reserves uint64) {
  5013  	btc.bondReserves.Store(reserves)
  5014  }
  5015  
  5016  func bondPushData(ver uint16, acctID []byte, lockTimeSec int64, pkh []byte) []byte {
  5017  	pushData := make([]byte, 2+len(acctID)+4+20)
  5018  	var offset int
  5019  	binary.BigEndian.PutUint16(pushData[offset:], ver)
  5020  	offset += 2
  5021  	copy(pushData[offset:], acctID[:])
  5022  	offset += len(acctID)
  5023  	binary.BigEndian.PutUint32(pushData[offset:], uint32(lockTimeSec))
  5024  	offset += 4
  5025  	copy(pushData[offset:], pkh)
  5026  	return pushData
  5027  }
  5028  
  5029  func bondPushDataScript(ver uint16, acctID []byte, lockTimeSec int64, pkh []byte) ([]byte, error) {
  5030  	return txscript.NewScriptBuilder().
  5031  		AddOp(txscript.OP_RETURN).
  5032  		AddData(bondPushData(ver, acctID, lockTimeSec, pkh)).
  5033  		Script()
  5034  }
  5035  
  5036  // MakeBondTx creates a time-locked fidelity bond transaction. The V0
  5037  // transaction has two required outputs:
  5038  //
  5039  // Output 0 is a the time-locked bond output of type P2SH with the provided
  5040  // value. The redeem script looks similar to the refund path of an atomic swap
  5041  // script, but with a pubkey hash:
  5042  //
  5043  //	<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubkeyhash[20]> OP_EQUALVERIFY OP_CHECKSIG
  5044  //
  5045  // The pubkey referenced by the script is provided by the caller.
  5046  //
  5047  // Output 1 is a DEX Account commitment. This is an OP_RETURN output that
  5048  // references the provided account ID.
  5049  //
  5050  //	OP_RETURN <2-byte version> <32-byte account ID> <4-byte locktime> <20-byte pubkey hash>
  5051  //
  5052  // Having the account ID in the raw allows the txn alone to identify the account
  5053  // without the bond output's redeem script.
  5054  //
  5055  // Output 2 is change, if any.
  5056  //
  5057  // The bond output's redeem script, which is needed to spend the bond output, is
  5058  // returned as the Data field of the Bond. The bond output pays to a pubkeyhash
  5059  // script for a wallet address. Bond.RedeemTx is a backup transaction that
  5060  // spends the bond output after lockTime passes, paying to an address for the
  5061  // current underlying wallet; the bond private key should normally be used to
  5062  // author a new transaction paying to a new address instead.
  5063  func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time.Time, bondKey *secp256k1.PrivateKey, acctID []byte) (*asset.Bond, func(), error) {
  5064  	if ver != 0 {
  5065  		return nil, nil, errors.New("only version 0 bonds supported")
  5066  	}
  5067  	if until := time.Until(lockTime); until >= 365*12*time.Hour /* ~6 months */ {
  5068  		return nil, nil, fmt.Errorf("that lock time is nuts: %v", lockTime)
  5069  	} else if until < 0 {
  5070  		return nil, nil, fmt.Errorf("that lock time is already passed: %v", lockTime)
  5071  	}
  5072  
  5073  	pk := bondKey.PubKey().SerializeCompressed()
  5074  	pkh := btcutil.Hash160(pk)
  5075  
  5076  	feeRate = btc.feeRateWithFallback(feeRate)
  5077  	baseTx := wire.NewMsgTx(btc.txVersion())
  5078  
  5079  	// TL output.
  5080  	lockTimeSec := lockTime.Unix()
  5081  	if lockTimeSec >= dexbtc.MaxCLTVScriptNum || lockTimeSec <= 0 {
  5082  		return nil, nil, fmt.Errorf("invalid lock time %v", lockTime)
  5083  	}
  5084  	bondScript, err := dexbtc.MakeBondScript(ver, uint32(lockTimeSec), pkh)
  5085  	if err != nil {
  5086  		return nil, nil, fmt.Errorf("failed to build bond output redeem script: %w", err)
  5087  	}
  5088  	pkScript, err := btc.scriptHashScript(bondScript)
  5089  	if err != nil {
  5090  		return nil, nil, fmt.Errorf("error constructing p2sh script: %v", err)
  5091  	}
  5092  	txOut := wire.NewTxOut(int64(amt), pkScript)
  5093  	if btc.IsDust(txOut, feeRate) {
  5094  		return nil, nil, fmt.Errorf("bond output value of %d (fee rate %d) is dust", amt, feeRate)
  5095  	}
  5096  	baseTx.AddTxOut(txOut)
  5097  
  5098  	// Acct ID commitment and bond details output, v0. The integers are encoded
  5099  	// with big-endian byte order and a fixed number of bytes, unlike in Script,
  5100  	// for natural visual inspection of the version and lock time.
  5101  	commitPkScript, err := bondPushDataScript(ver, acctID, lockTimeSec, pkh)
  5102  	if err != nil {
  5103  		return nil, nil, fmt.Errorf("failed to build acct commit output script: %w", err)
  5104  	}
  5105  	acctOut := wire.NewTxOut(0, commitPkScript) // value zero
  5106  	baseTx.AddTxOut(acctOut)
  5107  
  5108  	baseSize := uint32(baseTx.SerializeSize())
  5109  	if btc.segwit {
  5110  		baseSize += dexbtc.P2WPKHOutputSize
  5111  	} else {
  5112  		baseSize += dexbtc.P2PKHOutputSize
  5113  	}
  5114  
  5115  	const subtract = false
  5116  	coins, _, _, _, _, _, err := btc.cm.Fund(0, 0, true, sendEnough(amt, feeRate, subtract, uint64(baseSize), btc.segwit, true))
  5117  	if err != nil {
  5118  		return nil, nil, fmt.Errorf("failed to fund bond tx: %w", err)
  5119  	}
  5120  
  5121  	var txIDToRemoveFromHistory *chainhash.Hash // will be non-nil if tx was added to history
  5122  
  5123  	abandon := func() { // if caller does not broadcast, or we fail in this method
  5124  		err := btc.ReturnCoins(coins)
  5125  		if err != nil {
  5126  			btc.log.Errorf("error returning coins for unused bond tx: %v", coins)
  5127  		}
  5128  		if txIDToRemoveFromHistory != nil {
  5129  			btc.removeTxFromHistory(txIDToRemoveFromHistory)
  5130  		}
  5131  	}
  5132  
  5133  	var success bool
  5134  	defer func() {
  5135  		if !success {
  5136  			abandon()
  5137  		}
  5138  	}()
  5139  
  5140  	totalIn, _, err := btc.addInputsToTx(baseTx, coins)
  5141  	if err != nil {
  5142  		return nil, nil, fmt.Errorf("failed to add inputs to bond tx: %w", err)
  5143  	}
  5144  
  5145  	changeAddr, err := btc.node.changeAddress()
  5146  	if err != nil {
  5147  		return nil, nil, fmt.Errorf("error creating change address: %w", err)
  5148  	}
  5149  	signedTx, _, fee, err := btc.signTxAndAddChange(baseTx, changeAddr, totalIn, amt, feeRate)
  5150  	if err != nil {
  5151  		return nil, nil, fmt.Errorf("failed to sign bond tx: %w", err)
  5152  	}
  5153  
  5154  	txid := btc.hashTx(signedTx)
  5155  
  5156  	signedTxBytes, err := btc.serializeTx(signedTx)
  5157  	if err != nil {
  5158  		return nil, nil, err
  5159  	}
  5160  	unsignedTxBytes, err := btc.serializeTx(baseTx)
  5161  	if err != nil {
  5162  		return nil, nil, err
  5163  	}
  5164  
  5165  	// Prep the redeem / refund tx.
  5166  	redeemMsgTx, err := btc.makeBondRefundTxV0(txid, 0, amt, bondScript, bondKey, feeRate)
  5167  	if err != nil {
  5168  		return nil, nil, fmt.Errorf("unable to create bond redemption tx: %w", err)
  5169  	}
  5170  	redeemTx, err := btc.serializeTx(redeemMsgTx)
  5171  	if err != nil {
  5172  		return nil, nil, fmt.Errorf("failed to serialize bond redemption tx: %w", err)
  5173  	}
  5174  
  5175  	bond := &asset.Bond{
  5176  		Version:    ver,
  5177  		AssetID:    btc.cloneParams.AssetID,
  5178  		Amount:     amt,
  5179  		CoinID:     ToCoinID(txid, 0),
  5180  		Data:       bondScript,
  5181  		SignedTx:   signedTxBytes,
  5182  		UnsignedTx: unsignedTxBytes,
  5183  		RedeemTx:   redeemTx,
  5184  	}
  5185  	success = true
  5186  
  5187  	btc.addTxToHistory(&asset.WalletTransaction{
  5188  		Type:   asset.CreateBond,
  5189  		ID:     txid.String(),
  5190  		Amount: amt,
  5191  		Fees:   fee,
  5192  		BondInfo: &asset.BondTxInfo{
  5193  			AccountID: acctID,
  5194  			LockTime:  uint64(lockTimeSec),
  5195  			BondID:    pkh,
  5196  		},
  5197  	}, txid, false)
  5198  
  5199  	txIDToRemoveFromHistory = txid
  5200  
  5201  	return bond, abandon, nil
  5202  }
  5203  
  5204  func (btc *baseWallet) makeBondRefundTxV0(txid *chainhash.Hash, vout uint32, amt uint64,
  5205  	script []byte, priv *secp256k1.PrivateKey, feeRate uint64) (*wire.MsgTx, error) {
  5206  	lockTime, pkhPush, err := dexbtc.ExtractBondDetailsV0(0, script)
  5207  	if err != nil {
  5208  		return nil, err
  5209  	}
  5210  
  5211  	pk := priv.PubKey().SerializeCompressed()
  5212  	pkh := btcutil.Hash160(pk)
  5213  	if !bytes.Equal(pkh, pkhPush) {
  5214  		return nil, fmt.Errorf("incorrect private key to spend the bond output")
  5215  	}
  5216  
  5217  	msgTx := wire.NewMsgTx(btc.txVersion())
  5218  	// Transaction LockTime must be <= spend time, and >= the CLTV lockTime, so
  5219  	// we use exactly the CLTV's value. This limits the CLTV value to 32-bits.
  5220  	msgTx.LockTime = lockTime
  5221  	bondPrevOut := wire.NewOutPoint(txid, vout)
  5222  	txIn := wire.NewTxIn(bondPrevOut, []byte{}, nil)
  5223  	txIn.Sequence = wire.MaxTxInSequenceNum - 1 // not finalized, do not disable cltv
  5224  	msgTx.AddTxIn(txIn)
  5225  
  5226  	// Calculate fees and add the refund output.
  5227  	size := btc.calcTxSize(msgTx)
  5228  	if btc.segwit {
  5229  		witnessVBytes := (dexbtc.RedeemBondSigScriptSize + 2 + 3) / 4
  5230  		size += uint64(witnessVBytes) + dexbtc.P2WPKHOutputSize
  5231  	} else {
  5232  		size += dexbtc.RedeemBondSigScriptSize + dexbtc.P2PKHOutputSize
  5233  	}
  5234  	fee := feeRate * size
  5235  	if fee > amt {
  5236  		return nil, fmt.Errorf("irredeemable bond at fee rate %d atoms/byte", feeRate)
  5237  	}
  5238  
  5239  	// Add the refund output.
  5240  	redeemAddr, err := btc.node.externalAddress()
  5241  	if err != nil {
  5242  		return nil, fmt.Errorf("error creating change address: %w", err)
  5243  	}
  5244  	redeemPkScript, err := txscript.PayToAddrScript(redeemAddr)
  5245  	if err != nil {
  5246  		return nil, fmt.Errorf("error creating pubkey script: %w", err)
  5247  	}
  5248  	redeemTxOut := wire.NewTxOut(int64(amt-fee), redeemPkScript)
  5249  	if btc.IsDust(redeemTxOut, feeRate) { // hard to imagine
  5250  		return nil, fmt.Errorf("bond redeem output (amt = %d, feeRate = %d, outputSize = %d) is dust", amt, feeRate, redeemTxOut.SerializeSize())
  5251  	}
  5252  	msgTx.AddTxOut(redeemTxOut)
  5253  
  5254  	if btc.segwit {
  5255  		sigHashes := txscript.NewTxSigHashes(msgTx, new(txscript.CannedPrevOutputFetcher))
  5256  		sig, err := txscript.RawTxInWitnessSignature(msgTx, sigHashes, 0, int64(amt),
  5257  			script, txscript.SigHashAll, priv)
  5258  		if err != nil {
  5259  			return nil, err
  5260  		}
  5261  		txIn.Witness = dexbtc.RefundBondScriptSegwit(script, sig, pk)
  5262  	} else {
  5263  		prevPkScript, err := btc.scriptHashScript(script) // P2SH: OP_HASH160 <script hash> OP_EQUAL
  5264  		if err != nil {
  5265  			return nil, fmt.Errorf("error constructing p2sh script: %w", err)
  5266  		}
  5267  		sig, err := btc.signNonSegwit(msgTx, 0, script, txscript.SigHashAll, priv, []int64{int64(amt)}, [][]byte{prevPkScript})
  5268  		if err != nil {
  5269  			return nil, err
  5270  		}
  5271  		txIn.SignatureScript, err = dexbtc.RefundBondScript(script, sig, pk)
  5272  		if err != nil {
  5273  			return nil, fmt.Errorf("RefundBondScript: %w", err)
  5274  		}
  5275  	}
  5276  
  5277  	return msgTx, nil
  5278  }
  5279  
  5280  // RefundBond refunds a bond output to a new wallet address given the redeem
  5281  // script and private key. After broadcasting, the output paying to the wallet
  5282  // is returned.
  5283  func (btc *baseWallet) RefundBond(ctx context.Context, ver uint16, coinID, script []byte, amt uint64, privKey *secp256k1.PrivateKey) (asset.Coin, error) {
  5284  	if ver != 0 {
  5285  		return nil, errors.New("only version 0 bonds supported")
  5286  	}
  5287  	lockTime, pkhPush, err := dexbtc.ExtractBondDetailsV0(0, script)
  5288  	if err != nil {
  5289  		return nil, err
  5290  	}
  5291  	txHash, vout, err := decodeCoinID(coinID)
  5292  	if err != nil {
  5293  		return nil, err
  5294  	}
  5295  	feeRate := btc.targetFeeRateWithFallback(2, 0)
  5296  
  5297  	msgTx, err := btc.makeBondRefundTxV0(txHash, vout, amt, script, privKey, feeRate)
  5298  	if err != nil {
  5299  		return nil, err
  5300  	}
  5301  
  5302  	_, err = btc.node.sendRawTransaction(msgTx)
  5303  	if err != nil {
  5304  		return nil, fmt.Errorf("error sending refund bond transaction: %w", err)
  5305  	}
  5306  
  5307  	txID := btc.hashTx(msgTx)
  5308  	var fees uint64
  5309  	if len(msgTx.TxOut) == 1 {
  5310  		fees = amt - uint64(msgTx.TxOut[0].Value)
  5311  	}
  5312  	btc.addTxToHistory(&asset.WalletTransaction{
  5313  		Type:   asset.RedeemBond,
  5314  		ID:     txID.String(),
  5315  		Amount: amt,
  5316  		Fees:   fees,
  5317  		BondInfo: &asset.BondTxInfo{
  5318  			LockTime: uint64(lockTime),
  5319  			BondID:   pkhPush,
  5320  		},
  5321  	}, txID, true)
  5322  
  5323  	return NewOutput(txHash, 0, uint64(msgTx.TxOut[0].Value)), nil
  5324  }
  5325  
  5326  func (btc *baseWallet) decodeV0BondTx(msgTx *wire.MsgTx, txHash *chainhash.Hash, coinID []byte) (*asset.BondDetails, error) {
  5327  	if len(msgTx.TxOut) < 2 {
  5328  		return nil, fmt.Errorf("tx %s is not a v0 bond transaction: too few outputs", txHash)
  5329  	}
  5330  	_, lockTime, pkh, err := dexbtc.ExtractBondCommitDataV0(0, msgTx.TxOut[1].PkScript)
  5331  	if err != nil {
  5332  		return nil, fmt.Errorf("unable to extract bond commitment details from output 1 of %s: %v", txHash, err)
  5333  	}
  5334  	// Sanity check.
  5335  	bondScript, err := dexbtc.MakeBondScript(0, lockTime, pkh[:])
  5336  	if err != nil {
  5337  		return nil, fmt.Errorf("failed to build bond output redeem script: %w", err)
  5338  	}
  5339  	pkScript, err := btc.scriptHashScript(bondScript)
  5340  	if err != nil {
  5341  		return nil, fmt.Errorf("error constructing p2sh script: %v", err)
  5342  	}
  5343  	if !bytes.Equal(pkScript, msgTx.TxOut[0].PkScript) {
  5344  		return nil, fmt.Errorf("bond script does not match commit data for %s: %x != %x",
  5345  			txHash, bondScript, msgTx.TxOut[0].PkScript)
  5346  	}
  5347  	return &asset.BondDetails{
  5348  		Bond: &asset.Bond{
  5349  			Version: 0,
  5350  			AssetID: btc.cloneParams.AssetID,
  5351  			Amount:  uint64(msgTx.TxOut[0].Value),
  5352  			CoinID:  coinID,
  5353  			Data:    bondScript,
  5354  			//
  5355  			// SignedTx and UnsignedTx not populated because this is
  5356  			// an already posted bond and these fields are no longer used.
  5357  			// SignedTx, UnsignedTx []byte
  5358  			//
  5359  			// RedeemTx cannot be populated because we do not have
  5360  			// the private key that only core knows. Core will need
  5361  			// the BondPKH to determine what the private key was.
  5362  			// RedeemTx []byte
  5363  		},
  5364  		LockTime: time.Unix(int64(lockTime), 0),
  5365  		CheckPrivKey: func(bondKey *secp256k1.PrivateKey) bool {
  5366  			pk := bondKey.PubKey().SerializeCompressed()
  5367  			pkhB := btcutil.Hash160(pk)
  5368  			return bytes.Equal(pkh[:], pkhB)
  5369  		},
  5370  	}, nil
  5371  }
  5372  
  5373  // FindBond finds the bond with coinID and returns the values used to create it.
  5374  func (btc *baseWallet) FindBond(_ context.Context, coinID []byte, _ time.Time) (bond *asset.BondDetails, err error) {
  5375  	txHash, vout, err := decodeCoinID(coinID)
  5376  	if err != nil {
  5377  		return nil, err
  5378  	}
  5379  
  5380  	// If the bond was funded by this wallet or had a change output paying
  5381  	// to this wallet, it should be found here.
  5382  	tx, err := btc.node.getWalletTransaction(txHash)
  5383  	if err != nil {
  5384  		return nil, fmt.Errorf("did not find the bond output %v:%d", txHash, vout)
  5385  	}
  5386  	msgTx, err := btc.deserializeTx(tx.Bytes)
  5387  	if err != nil {
  5388  		return nil, fmt.Errorf("invalid hex for tx %s: %v", txHash, err)
  5389  	}
  5390  	return btc.decodeV0BondTx(msgTx, txHash, coinID)
  5391  }
  5392  
  5393  // FindBond finds the bond with coinID and returns the values used to create it.
  5394  // The intermediate wallet is able to brute force finding blocks.
  5395  func (btc *intermediaryWallet) FindBond(
  5396  	ctx context.Context,
  5397  	coinID []byte,
  5398  	searchUntil time.Time,
  5399  ) (bond *asset.BondDetails, err error) {
  5400  
  5401  	txHash, vout, err := decodeCoinID(coinID)
  5402  	if err != nil {
  5403  		return nil, err
  5404  	}
  5405  
  5406  	// If the bond was funded by this wallet or had a change output paying
  5407  	// to this wallet, it should be found here.
  5408  	tx, err := btc.node.getWalletTransaction(txHash)
  5409  	if err == nil {
  5410  		msgTx, err := btc.deserializeTx(tx.Bytes)
  5411  		if err != nil {
  5412  			return nil, fmt.Errorf("invalid hex for tx %s: %v", txHash, err)
  5413  		}
  5414  		return btc.decodeV0BondTx(msgTx, txHash, coinID)
  5415  	}
  5416  	if !errors.Is(err, asset.CoinNotFoundError) {
  5417  		btc.log.Warnf("Unexpected error looking up bond output %v:%d", txHash, vout)
  5418  	}
  5419  
  5420  	// The bond was not funded by this wallet or had no change output when
  5421  	// restored from seed. This is not a problem. However, we are unable to
  5422  	// use filters because we don't know any output scripts. Brute force
  5423  	// finding the transaction.
  5424  	bestBlockHdr, err := btc.node.getBestBlockHeader()
  5425  	if err != nil {
  5426  		return nil, fmt.Errorf("unable to get best hash: %v", err)
  5427  	}
  5428  	blockHash, err := chainhash.NewHashFromStr(bestBlockHdr.Hash)
  5429  	if err != nil {
  5430  		return nil, fmt.Errorf("invalid best block hash from %s node: %v", btc.symbol, err)
  5431  	}
  5432  	var (
  5433  		blk   *wire.MsgBlock
  5434  		msgTx *wire.MsgTx
  5435  	)
  5436  out:
  5437  	for {
  5438  		if err := ctx.Err(); err != nil {
  5439  			return nil, fmt.Errorf("bond search stopped: %w", err)
  5440  		}
  5441  		blk, err = btc.tipRedeemer.getBlock(*blockHash)
  5442  		if err != nil {
  5443  			return nil, fmt.Errorf("error retrieving block %s: %w", blockHash, err)
  5444  		}
  5445  		if blk.Header.Timestamp.Before(searchUntil) {
  5446  			return nil, fmt.Errorf("searched blocks until %v but did not find the bond tx %s", searchUntil, txHash)
  5447  		}
  5448  		for _, tx := range blk.Transactions {
  5449  			if tx.TxHash() == *txHash {
  5450  				btc.log.Debugf("Found mined tx %s in block %s.", txHash, blk.BlockHash())
  5451  				msgTx = tx
  5452  				break out
  5453  			}
  5454  		}
  5455  		blockHash = &blk.Header.PrevBlock
  5456  		if blockHash == nil {
  5457  			return nil, fmt.Errorf("did not find the bond output %v:%d", txHash, vout)
  5458  		}
  5459  	}
  5460  	return btc.decodeV0BondTx(msgTx, txHash, coinID)
  5461  }
  5462  
  5463  // BondsFeeBuffer suggests how much extra may be required for the transaction
  5464  // fees part of required bond reserves when bond rotation is enabled. The
  5465  // provided fee rate may be zero, in which case the wallet will use it's own
  5466  // estimate or fallback value.
  5467  func (btc *baseWallet) BondsFeeBuffer(feeRate uint64) uint64 {
  5468  	if feeRate == 0 {
  5469  		feeRate = btc.targetFeeRateWithFallback(1, 0)
  5470  	}
  5471  	feeRate *= 2 // double the current fee rate estimate so this fee buffer does not get stale too quickly
  5472  	return bondsFeeBuffer(btc.segwit, feeRate)
  5473  }
  5474  
  5475  // FundMultiOrder funds multiple orders in one shot. MaxLock is the maximum
  5476  // amount that the wallet can lock for these orders. If maxLock == 0, then
  5477  // there is no limit. An error is returned if the wallet does not have enough
  5478  // available balance to fund each of the orders, however, if splitting is
  5479  // not enabled and all of the orders cannot be funded due to mismatches in
  5480  // UTXO sizes, the orders that can be funded are funded. It will fail on the
  5481  // first order that cannot be funded. The returned values will always be in
  5482  // the same order as the Values in the parameter. If the length of the returned
  5483  // orders is shorter than what was passed in, it means that the orders at the
  5484  // end of the list were unable to be funded.
  5485  func (btc *baseWallet) FundMultiOrder(mo *asset.MultiOrder, maxLock uint64) ([]asset.Coins, [][]dex.Bytes, uint64, error) {
  5486  	btc.log.Debugf("Attempting to fund a multi-order for %s, maxFeeRate = %d", btc.symbol, mo.MaxFeeRate)
  5487  
  5488  	var totalRequiredForOrders uint64
  5489  	var swapInputSize uint64
  5490  	if btc.segwit {
  5491  		swapInputSize = dexbtc.RedeemP2WPKHInputTotalSize
  5492  	} else {
  5493  		swapInputSize = dexbtc.RedeemP2PKHInputSize
  5494  	}
  5495  	for _, value := range mo.Values {
  5496  		if value.Value == 0 {
  5497  			return nil, nil, 0, fmt.Errorf("cannot fund value = 0")
  5498  		}
  5499  		if value.MaxSwapCount == 0 {
  5500  			return nil, nil, 0, fmt.Errorf("cannot fund zero-lot order")
  5501  		}
  5502  		req := calc.RequiredOrderFunds(value.Value, swapInputSize, value.MaxSwapCount,
  5503  			btc.initTxSizeBase, btc.initTxSize, mo.MaxFeeRate)
  5504  		totalRequiredForOrders += req
  5505  	}
  5506  
  5507  	if maxLock < totalRequiredForOrders && maxLock != 0 {
  5508  		return nil, nil, 0, fmt.Errorf("maxLock < totalRequiredForOrders (%d < %d)", maxLock, totalRequiredForOrders)
  5509  	}
  5510  
  5511  	if mo.FeeSuggestion > mo.MaxFeeRate {
  5512  		return nil, nil, 0, fmt.Errorf("fee suggestion %d > max fee rate %d", mo.FeeSuggestion, mo.MaxFeeRate)
  5513  	}
  5514  	// Check wallets fee rate limit against server's max fee rate
  5515  	if btc.feeRateLimit() < mo.MaxFeeRate {
  5516  		return nil, nil, 0, fmt.Errorf(
  5517  			"%v: server's max fee rate %v higher than configued fee rate limit %v",
  5518  			dex.BipIDSymbol(BipID), mo.MaxFeeRate, btc.feeRateLimit())
  5519  	}
  5520  
  5521  	bal, err := btc.Balance()
  5522  	if err != nil {
  5523  		return nil, nil, 0, fmt.Errorf("error getting wallet balance: %w", err)
  5524  	}
  5525  	if bal.Available < totalRequiredForOrders {
  5526  		return nil, nil, 0, fmt.Errorf("insufficient funds. %d < %d",
  5527  			bal.Available, totalRequiredForOrders)
  5528  	}
  5529  
  5530  	customCfg, err := decodeFundMultiOptions(mo.Options)
  5531  	if err != nil {
  5532  		return nil, nil, 0, fmt.Errorf("error decoding options: %w", err)
  5533  	}
  5534  
  5535  	return btc.fundMulti(maxLock, mo.Values, mo.FeeSuggestion, mo.MaxFeeRate, customCfg.Split, customCfg.SplitBuffer)
  5536  }
  5537  
  5538  // MaxFundingFees returns the maximum funding fees for an order/multi-order.
  5539  func (btc *baseWallet) MaxFundingFees(numTrades uint32, feeRate uint64, options map[string]string) uint64 {
  5540  	customCfg, err := decodeFundMultiOptions(options)
  5541  	if err != nil {
  5542  		btc.log.Errorf("Error decoding multi-fund settings: %v", err)
  5543  		return 0
  5544  	}
  5545  
  5546  	if !customCfg.Split {
  5547  		return 0
  5548  	}
  5549  
  5550  	var inputSize, outputSize uint64
  5551  	if btc.segwit {
  5552  		inputSize = dexbtc.RedeemP2WPKHInputTotalSize
  5553  		outputSize = dexbtc.P2WPKHOutputSize
  5554  	} else {
  5555  		inputSize = dexbtc.RedeemP2PKHInputSize
  5556  		outputSize = dexbtc.P2PKHOutputSize
  5557  	}
  5558  
  5559  	const numInputs = 12 // plan for lots of inputs to get a safe estimate
  5560  
  5561  	txSize := dexbtc.MinimumTxOverhead + numInputs*inputSize + uint64(numTrades+1)*outputSize
  5562  	return feeRate * txSize
  5563  }
  5564  
  5565  func rpcTxFee(tx *ListTransactionsResult) uint64 {
  5566  	if tx.Fee != nil {
  5567  		// Fee always seems to be negative in btcwallet, but just
  5568  		// in case.
  5569  		if *tx.Fee < 0 {
  5570  			return toSatoshi(-*tx.Fee)
  5571  		}
  5572  		return toSatoshi(*tx.Fee)
  5573  	}
  5574  	return 0
  5575  }
  5576  
  5577  // idUnknownTx identifies the type and details of a transaction either made
  5578  // or recieved by the wallet.
  5579  func (btc *baseWallet) idUnknownTx(tx *ListTransactionsResult) (*asset.WalletTransaction, error) {
  5580  	txHash, err := chainhash.NewHashFromStr(tx.TxID)
  5581  	if err != nil {
  5582  		return nil, fmt.Errorf("error decoding tx hash %s: %v", tx.TxID, err)
  5583  	}
  5584  	txRaw, _, err := btc.rawWalletTx(txHash)
  5585  	if err != nil {
  5586  		return nil, err
  5587  	}
  5588  	msgTx, err := btc.deserializeTx(txRaw)
  5589  	if err != nil {
  5590  		return nil, fmt.Errorf("error deserializing tx: %v", err)
  5591  	}
  5592  
  5593  	fee := rpcTxFee(tx)
  5594  
  5595  	var totalOut uint64
  5596  	for _, txOut := range msgTx.TxOut {
  5597  		totalOut += uint64(txOut.Value)
  5598  	}
  5599  
  5600  	txIsBond := func(msgTx *wire.MsgTx) (bool, *asset.BondTxInfo) {
  5601  		if len(msgTx.TxOut) < 2 {
  5602  			return false, nil
  5603  		}
  5604  		const scriptVer = 0
  5605  		acctID, lockTime, pkHash, err := dexbtc.ExtractBondCommitDataV0(scriptVer, msgTx.TxOut[1].PkScript)
  5606  		if err != nil {
  5607  			return false, nil
  5608  		}
  5609  		return true, &asset.BondTxInfo{
  5610  			AccountID: acctID[:],
  5611  			LockTime:  uint64(lockTime),
  5612  			BondID:    pkHash[:],
  5613  		}
  5614  	}
  5615  	if isBond, bondInfo := txIsBond(msgTx); isBond {
  5616  		return &asset.WalletTransaction{
  5617  			ID:       tx.TxID,
  5618  			Type:     asset.CreateBond,
  5619  			Amount:   uint64(msgTx.TxOut[0].Value),
  5620  			Fees:     fee,
  5621  			BondInfo: bondInfo,
  5622  		}, nil
  5623  	}
  5624  
  5625  	// Any other P2SH may be a swap or a send. We cannot determine unless we
  5626  	// look up the transaction that spends this UTXO.
  5627  	txPaysToScriptHash := func(msgTx *wire.MsgTx) (v uint64) {
  5628  		for _, txOut := range msgTx.TxOut {
  5629  			scriptClass := txscript.GetScriptClass(txOut.PkScript)
  5630  			if scriptClass == txscript.WitnessV0ScriptHashTy || scriptClass == txscript.ScriptHashTy {
  5631  				v += uint64(txOut.Value)
  5632  			}
  5633  		}
  5634  		return
  5635  	}
  5636  	if v := txPaysToScriptHash(msgTx); tx.Send && v > 0 {
  5637  		return &asset.WalletTransaction{
  5638  			ID:     tx.TxID,
  5639  			Type:   asset.SwapOrSend,
  5640  			Amount: v,
  5641  			Fees:   fee,
  5642  		}, nil
  5643  	}
  5644  
  5645  	// Helper function will help us identify inputs that spend P2SH contracts.
  5646  	containsContractAtPushIndex := func(msgTx *wire.MsgTx, idx int, isContract func(segwit bool, contract []byte) bool) bool {
  5647  	txinloop:
  5648  		for _, txIn := range msgTx.TxIn {
  5649  			if len(txIn.Witness) > 0 {
  5650  				// segwit
  5651  				if len(txIn.Witness) < idx+1 {
  5652  					continue
  5653  				}
  5654  				contract := txIn.Witness[idx]
  5655  				if isContract(true, contract) {
  5656  					return true
  5657  				}
  5658  			} else {
  5659  				// not segwit
  5660  				const scriptVer = 0
  5661  				tokenizer := txscript.MakeScriptTokenizer(scriptVer, txIn.SignatureScript)
  5662  				for i := 0; i <= idx; i++ { // contract is 5th item item in redemption and 4th in refund
  5663  					if !tokenizer.Next() {
  5664  						continue txinloop
  5665  					}
  5666  				}
  5667  				if isContract(false, tokenizer.Data()) {
  5668  					return true
  5669  				}
  5670  			}
  5671  		}
  5672  		return false
  5673  	}
  5674  
  5675  	// Swap redemptions and refunds
  5676  	contractIsSwap := func(segwit bool, contract []byte) bool {
  5677  		_, _, _, _, err := dexbtc.ExtractSwapDetails(contract, segwit, btc.chainParams)
  5678  		return err == nil
  5679  	}
  5680  	redeemsSwap := func(msgTx *wire.MsgTx) bool {
  5681  		return containsContractAtPushIndex(msgTx, 4, contractIsSwap)
  5682  	}
  5683  	if redeemsSwap(msgTx) {
  5684  		return &asset.WalletTransaction{
  5685  			ID:     tx.TxID,
  5686  			Type:   asset.Redeem,
  5687  			Amount: totalOut + fee,
  5688  			Fees:   fee,
  5689  		}, nil
  5690  	}
  5691  	refundsSwap := func(msgTx *wire.MsgTx) bool {
  5692  		return containsContractAtPushIndex(msgTx, 3, contractIsSwap)
  5693  	}
  5694  	if refundsSwap(msgTx) {
  5695  		return &asset.WalletTransaction{
  5696  			ID:     tx.TxID,
  5697  			Type:   asset.Refund,
  5698  			Amount: totalOut + fee,
  5699  			Fees:   fee,
  5700  		}, nil
  5701  	}
  5702  
  5703  	// Bond refunds
  5704  	redeemsBond := func(msgTx *wire.MsgTx) (bool, *asset.BondTxInfo) {
  5705  		var bondInfo *asset.BondTxInfo
  5706  		isBond := func(segwit bool, contract []byte) bool {
  5707  			const scriptVer = 0
  5708  			lockTime, pkHash, err := dexbtc.ExtractBondDetailsV0(scriptVer, contract)
  5709  			if err != nil {
  5710  				return false
  5711  			}
  5712  			bondInfo = &asset.BondTxInfo{
  5713  				AccountID: []byte{}, // Could look for the bond tx to get this, I guess.
  5714  				LockTime:  uint64(lockTime),
  5715  				BondID:    pkHash[:],
  5716  			}
  5717  			return true
  5718  		}
  5719  		return containsContractAtPushIndex(msgTx, 2, isBond), bondInfo
  5720  	}
  5721  	if isBondRedemption, bondInfo := redeemsBond(msgTx); isBondRedemption {
  5722  		return &asset.WalletTransaction{
  5723  			ID:       tx.TxID,
  5724  			Type:     asset.RedeemBond,
  5725  			Amount:   totalOut,
  5726  			Fees:     fee,
  5727  			BondInfo: bondInfo,
  5728  		}, nil
  5729  	}
  5730  
  5731  	allOutputsPayUs := func(msgTx *wire.MsgTx) bool {
  5732  		for _, txOut := range msgTx.TxOut {
  5733  			scriptClass, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, btc.chainParams)
  5734  			if err != nil {
  5735  				btc.log.Errorf("ExtractPkScriptAddrs error: %w", err)
  5736  				return false
  5737  			}
  5738  			switch scriptClass {
  5739  			case txscript.PubKeyHashTy, txscript.WitnessV0PubKeyHashTy:
  5740  			default:
  5741  				return false
  5742  			}
  5743  			if len(addrs) != 1 { // sanity check
  5744  				return false
  5745  			}
  5746  
  5747  			addr := addrs[0]
  5748  			owns, err := btc.node.ownsAddress(addr)
  5749  			if err != nil {
  5750  				btc.log.Errorf("ownsAddress error: %w", err)
  5751  				return false
  5752  			}
  5753  			if !owns {
  5754  				return false
  5755  			}
  5756  		}
  5757  
  5758  		return true
  5759  	}
  5760  
  5761  	if tx.Send && allOutputsPayUs(msgTx) {
  5762  		if len(msgTx.TxOut) == 1 {
  5763  			return &asset.WalletTransaction{
  5764  				ID:     tx.TxID,
  5765  				Type:   asset.Acceleration,
  5766  				Amount: 0,
  5767  				Fees:   fee,
  5768  			}, nil
  5769  
  5770  		}
  5771  		return &asset.WalletTransaction{
  5772  			ID:     tx.TxID,
  5773  			Type:   asset.Split,
  5774  			Amount: 0,
  5775  			Fees:   fee,
  5776  		}, nil
  5777  	}
  5778  
  5779  	txOutDirection := func(msgTx *wire.MsgTx) (in, out uint64) {
  5780  		for _, txOut := range msgTx.TxOut {
  5781  			_, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, btc.chainParams)
  5782  			if err != nil {
  5783  				btc.log.Errorf("ExtractPkScriptAddrs error: %w", err)
  5784  				continue
  5785  			}
  5786  
  5787  			if len(addrs) == 0 { // sanity check
  5788  				continue
  5789  			}
  5790  
  5791  			addr := addrs[0]
  5792  			owns, err := btc.node.ownsAddress(addr)
  5793  			if err != nil {
  5794  				btc.log.Errorf("ownsAddress error: %w", err)
  5795  				continue
  5796  			}
  5797  			if owns {
  5798  				in += uint64(txOut.Value)
  5799  			} else {
  5800  				out += uint64(txOut.Value)
  5801  			}
  5802  		}
  5803  		return
  5804  	}
  5805  	in, out := txOutDirection(msgTx)
  5806  
  5807  	getRecipient := func(msgTx *wire.MsgTx, receive bool) *string {
  5808  		for _, txOut := range msgTx.TxOut {
  5809  			_, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, btc.chainParams)
  5810  			if err != nil {
  5811  				btc.log.Errorf("ExtractPkScriptAddrs error: %w", err)
  5812  				continue
  5813  			}
  5814  
  5815  			if len(addrs) == 0 { // sanity check
  5816  				continue
  5817  			}
  5818  
  5819  			addr := addrs[0]
  5820  			owns, err := btc.node.ownsAddress(addr)
  5821  			if err != nil {
  5822  				btc.log.Errorf("ownsAddress error: %w", err)
  5823  				continue
  5824  			}
  5825  
  5826  			if receive == owns {
  5827  				str := addr.String()
  5828  				return &str
  5829  			}
  5830  		}
  5831  		return nil
  5832  	}
  5833  
  5834  	if tx.Send {
  5835  		return &asset.WalletTransaction{
  5836  			ID:        tx.TxID,
  5837  			Type:      asset.Send,
  5838  			Amount:    out,
  5839  			Fees:      fee,
  5840  			Recipient: getRecipient(msgTx, false),
  5841  		}, nil
  5842  	}
  5843  
  5844  	return &asset.WalletTransaction{
  5845  		ID:        tx.TxID,
  5846  		Type:      asset.Receive,
  5847  		Amount:    in,
  5848  		Fees:      fee,
  5849  		Recipient: getRecipient(msgTx, true),
  5850  	}, nil
  5851  }
  5852  
  5853  // addUnknownTransactionsToHistory checks for any transactions the wallet has
  5854  // made or recieved that are not part of the transaction history. It scans
  5855  // from the last point to which it had previously scanned to the current tip.
  5856  func (btc *baseWallet) addUnknownTransactionsToHistory(tip uint64) {
  5857  	txHistoryDB := btc.txDB()
  5858  	if txHistoryDB == nil {
  5859  		return
  5860  	}
  5861  
  5862  	const blockQueryBuffer = 3
  5863  	var blockToQuery uint64
  5864  	lastQuery := btc.receiveTxLastQuery.Load()
  5865  	if lastQuery == 0 {
  5866  		// TODO: use wallet birthday instead of block 0.
  5867  		// blockToQuery = 0
  5868  	} else if lastQuery < tip-blockQueryBuffer {
  5869  		blockToQuery = lastQuery - blockQueryBuffer
  5870  	} else {
  5871  		blockToQuery = tip - blockQueryBuffer
  5872  	}
  5873  
  5874  	txs, err := btc.node.listTransactionsSinceBlock(int32(blockToQuery))
  5875  	if err != nil {
  5876  		btc.log.Errorf("Error listing transactions since block %d: %v", blockToQuery, err)
  5877  		return
  5878  	}
  5879  
  5880  	for _, tx := range txs {
  5881  		if btc.ctx.Err() != nil {
  5882  			return
  5883  		}
  5884  		txHash, err := chainhash.NewHashFromStr(tx.TxID)
  5885  		if err != nil {
  5886  			btc.log.Errorf("Error decoding tx hash %s: %v", tx.TxID, err)
  5887  			continue
  5888  		}
  5889  		_, err = txHistoryDB.GetTx(txHash.String())
  5890  		if err == nil {
  5891  			continue
  5892  		}
  5893  		if !errors.Is(err, asset.CoinNotFoundError) {
  5894  			btc.log.Errorf("Error getting tx %s: %v", txHash.String(), err)
  5895  			continue
  5896  		}
  5897  		wt, err := btc.idUnknownTx(tx)
  5898  		if err != nil {
  5899  			btc.log.Errorf("error identifying transaction: %v", err)
  5900  			continue
  5901  		}
  5902  		if tx.BlockHeight > 0 && tx.BlockHeight < uint32(tip-blockQueryBuffer) {
  5903  			wt.BlockNumber = uint64(tx.BlockHeight)
  5904  			wt.Timestamp = tx.BlockTime
  5905  		}
  5906  
  5907  		// Don't send notifications for the initial sync to avoid spamming the
  5908  		// front end. A notification is sent at the end of the initial sync.
  5909  		btc.addTxToHistory(wt, txHash, true, blockToQuery == 0)
  5910  	}
  5911  
  5912  	btc.receiveTxLastQuery.Store(tip)
  5913  	err = txHistoryDB.SetLastReceiveTxQuery(tip)
  5914  	if err != nil {
  5915  		btc.log.Errorf("Error setting last query to %d: %v", tip, err)
  5916  	}
  5917  
  5918  	if blockToQuery == 0 {
  5919  		btc.emit.TransactionHistorySyncedNote()
  5920  	}
  5921  }
  5922  
  5923  // syncTxHistory checks to see if there are any transactions which the wallet
  5924  // has made or recieved that are not part of the transaction history, then
  5925  // identifies and adds them. It also checks all the pending transactions to see
  5926  // if they have been mined into a block, and if so, updates the transaction
  5927  // history to reflect the block height.
  5928  func (btc *intermediaryWallet) syncTxHistory(tip uint64) {
  5929  	if !btc.syncingTxHistory.CompareAndSwap(false, true) {
  5930  		return
  5931  	}
  5932  	defer btc.syncingTxHistory.Store(false)
  5933  
  5934  	txHistoryDB := btc.txDB()
  5935  	if txHistoryDB == nil {
  5936  		return
  5937  	}
  5938  
  5939  	ss, err := btc.SyncStatus()
  5940  	if err != nil {
  5941  		btc.log.Errorf("Error getting sync status: %v", err)
  5942  		return
  5943  	}
  5944  	if !ss.Synced {
  5945  		return
  5946  	}
  5947  
  5948  	btc.addUnknownTransactionsToHistory(tip)
  5949  
  5950  	pendingTxsCopy := make(map[chainhash.Hash]ExtendedWalletTx, len(btc.pendingTxs))
  5951  	btc.pendingTxsMtx.RLock()
  5952  	for hash, tx := range btc.pendingTxs {
  5953  		pendingTxsCopy[hash] = tx
  5954  	}
  5955  	btc.pendingTxsMtx.RUnlock()
  5956  
  5957  	handlePendingTx := func(txHash chainhash.Hash, tx *ExtendedWalletTx) {
  5958  		if !tx.Submitted {
  5959  			return
  5960  		}
  5961  
  5962  		gtr, err := btc.node.getWalletTransaction(&txHash)
  5963  		if errors.Is(err, asset.CoinNotFoundError) {
  5964  			err = txHistoryDB.RemoveTx(txHash.String())
  5965  			if err == nil || errors.Is(err, asset.CoinNotFoundError) {
  5966  				btc.pendingTxsMtx.Lock()
  5967  				delete(btc.pendingTxs, txHash)
  5968  				btc.pendingTxsMtx.Unlock()
  5969  			} else {
  5970  				// Leave it in the pendingPendingTxs and attempt to remove it
  5971  				// again next time.
  5972  				btc.log.Errorf("Error removing tx %s from the history store: %v", txHash.String(), err)
  5973  			}
  5974  			return
  5975  		}
  5976  		if err != nil {
  5977  			btc.log.Errorf("Error getting transaction %s: %v", txHash.String(), err)
  5978  			return
  5979  		}
  5980  
  5981  		var updated bool
  5982  		if gtr.BlockHash != "" {
  5983  			blockHash, err := chainhash.NewHashFromStr(gtr.BlockHash)
  5984  			if err != nil {
  5985  				btc.log.Errorf("Error decoding block hash %s: %v", gtr.BlockHash, err)
  5986  				return
  5987  			}
  5988  			blockHeight, err := btc.tipRedeemer.getBlockHeight(blockHash)
  5989  			if err != nil {
  5990  				btc.log.Errorf("Error getting block height for %s: %v", blockHash, err)
  5991  				return
  5992  			}
  5993  			if tx.BlockNumber != uint64(blockHeight) || tx.Timestamp != gtr.BlockTime {
  5994  				tx.BlockNumber = uint64(blockHeight)
  5995  				tx.Timestamp = gtr.BlockTime
  5996  				updated = true
  5997  			}
  5998  		} else if gtr.BlockHash == "" && tx.BlockNumber != 0 {
  5999  			tx.BlockNumber = 0
  6000  			tx.Timestamp = 0
  6001  			updated = true
  6002  		}
  6003  
  6004  		var confs uint64
  6005  		if tx.BlockNumber > 0 && tip >= tx.BlockNumber {
  6006  			confs = tip - tx.BlockNumber + 1
  6007  		}
  6008  		if confs >= requiredRedeemConfirms {
  6009  			tx.Confirmed = true
  6010  			updated = true
  6011  		}
  6012  
  6013  		if updated {
  6014  			err = txHistoryDB.StoreTx(tx)
  6015  			if err != nil {
  6016  				btc.log.Errorf("Error updating tx %s: %v", txHash, err)
  6017  				return
  6018  			}
  6019  
  6020  			btc.pendingTxsMtx.Lock()
  6021  			if tx.Confirmed {
  6022  				delete(btc.pendingTxs, txHash)
  6023  			} else {
  6024  				btc.pendingTxs[txHash] = *tx
  6025  			}
  6026  			btc.pendingTxsMtx.Unlock()
  6027  
  6028  			btc.emit.TransactionNote(tx.WalletTransaction, false)
  6029  		}
  6030  	}
  6031  
  6032  	for hash, tx := range pendingTxsCopy {
  6033  		if btc.ctx.Err() != nil {
  6034  			return
  6035  		}
  6036  		handlePendingTx(hash, &tx)
  6037  	}
  6038  }
  6039  
  6040  // WalletTransaction returns a transaction that either the wallet has made or
  6041  // one in which the wallet has received funds. The txID can be either a byte
  6042  // reversed tx hash or a hex encoded coin ID.
  6043  func (btc *intermediaryWallet) WalletTransaction(ctx context.Context, txID string) (*asset.WalletTransaction, error) {
  6044  	coinID, err := hex.DecodeString(txID)
  6045  	if err == nil {
  6046  		txHash, _, err := decodeCoinID(coinID)
  6047  		if err == nil {
  6048  			txID = txHash.String()
  6049  		}
  6050  	}
  6051  
  6052  	txHistoryDB := btc.txDB()
  6053  	if txHistoryDB == nil {
  6054  		return nil, fmt.Errorf("tx database not initialized")
  6055  	}
  6056  
  6057  	tx, err := txHistoryDB.GetTx(txID)
  6058  	if err != nil && !errors.Is(err, asset.CoinNotFoundError) {
  6059  		return nil, err
  6060  	}
  6061  	if tx != nil && tx.Confirmed {
  6062  		return tx, nil
  6063  	}
  6064  
  6065  	txHash, err := chainhash.NewHashFromStr(txID)
  6066  	if err != nil {
  6067  		return nil, fmt.Errorf("error decoding txid %s: %w", txID, err)
  6068  	}
  6069  	gtr, err := btc.node.getWalletTransaction(txHash)
  6070  	if err != nil {
  6071  		return nil, fmt.Errorf("error getting transaction %s: %w", txID, err)
  6072  	}
  6073  
  6074  	var blockHeight uint32
  6075  	if gtr.BlockHash != "" {
  6076  		blockHash, err := chainhash.NewHashFromStr(gtr.BlockHash)
  6077  		if err != nil {
  6078  			return nil, fmt.Errorf("error decoding block hash %s: %w", gtr.BlockHash, err)
  6079  		}
  6080  		height, err := btc.tipRedeemer.getBlockHeight(blockHash)
  6081  		if err != nil {
  6082  			return nil, fmt.Errorf("error getting block height for %s: %w", blockHash, err)
  6083  		}
  6084  		blockHeight = uint32(height)
  6085  	}
  6086  
  6087  	updated := tx == nil
  6088  	if tx == nil {
  6089  		tx, err = btc.idUnknownTx(&ListTransactionsResult{
  6090  			BlockHeight: blockHeight,
  6091  			BlockTime:   gtr.BlockTime,
  6092  			TxID:        txID,
  6093  		})
  6094  		if err != nil {
  6095  			return nil, fmt.Errorf("error identifying transaction: %v", err)
  6096  		}
  6097  	}
  6098  
  6099  	if tx.BlockNumber != uint64(blockHeight) || tx.Timestamp != gtr.BlockTime {
  6100  		tx.BlockNumber = uint64(blockHeight)
  6101  		tx.Timestamp = gtr.BlockTime
  6102  		tx.Confirmed = blockHeight > 0
  6103  		updated = true
  6104  	}
  6105  
  6106  	if updated {
  6107  		btc.addTxToHistory(tx, txHash, true, false)
  6108  	}
  6109  
  6110  	return tx, nil
  6111  }
  6112  
  6113  // TxHistory returns all the transactions the wallet has made. If refID is nil,
  6114  // then transactions starting from the most recent are returned (past is ignored).
  6115  // If past is true, the transactions prior to the refID are returned, otherwise
  6116  // the transactions after the refID are returned. n is the number of
  6117  // transactions to return. If n is <= 0, all the transactions will be returned.
  6118  func (btc *intermediaryWallet) TxHistory(n int, refID *string, past bool) ([]*asset.WalletTransaction, error) {
  6119  	txHistoryDB := btc.txDB()
  6120  	if txHistoryDB == nil {
  6121  		return nil, fmt.Errorf("tx database not initialized")
  6122  	}
  6123  	return txHistoryDB.GetTxs(n, refID, past)
  6124  }
  6125  
  6126  // lockedSats is the total value of locked outputs, as locked with LockUnspent.
  6127  func (btc *baseWallet) lockedSats() (uint64, error) {
  6128  	lockedOutpoints, err := btc.node.listLockUnspent()
  6129  	if err != nil {
  6130  		return 0, err
  6131  	}
  6132  	var sum uint64
  6133  	for _, rpcOP := range lockedOutpoints {
  6134  		txHash, err := chainhash.NewHashFromStr(rpcOP.TxID)
  6135  		if err != nil {
  6136  			return 0, err
  6137  		}
  6138  		pt := NewOutPoint(txHash, rpcOP.Vout)
  6139  		utxo := btc.cm.LockedOutput(pt)
  6140  		if utxo != nil {
  6141  			sum += utxo.Amount
  6142  			continue
  6143  		}
  6144  		tx, err := btc.node.getWalletTransaction(txHash)
  6145  		if err != nil {
  6146  			return 0, err
  6147  		}
  6148  		txOut, err := TxOutFromTxBytes(tx.Bytes, rpcOP.Vout, btc.deserializeTx, btc.hashTx)
  6149  		if err != nil {
  6150  			return 0, err
  6151  		}
  6152  		sum += uint64(txOut.Value)
  6153  	}
  6154  	return sum, nil
  6155  }
  6156  
  6157  // wireBytes dumps the serialized transaction bytes.
  6158  func (btc *baseWallet) wireBytes(tx *wire.MsgTx) []byte {
  6159  	b, err := btc.serializeTx(tx)
  6160  	// wireBytes is just used for logging, and a serialization error is
  6161  	// extremely unlikely, so just log the error and return the nil bytes.
  6162  	if err != nil {
  6163  		btc.log.Errorf("error serializing %s transaction: %v", btc.symbol, err)
  6164  		return nil
  6165  	}
  6166  	return b
  6167  }
  6168  
  6169  // GetBestBlockHeight is exported for use by clone wallets. Not part of the
  6170  // asset.Wallet interface.
  6171  func (btc *baseWallet) GetBestBlockHeight() (int32, error) {
  6172  	return btc.node.getBestBlockHeight()
  6173  }
  6174  
  6175  // Convert the BTC value to satoshi.
  6176  func toSatoshi(v float64) uint64 {
  6177  	return uint64(math.Round(v * conventionalConversionFactor))
  6178  }
  6179  
  6180  // BlockHeader is a partial btcjson.GetBlockHeaderVerboseResult with mediantime
  6181  // included.
  6182  type BlockHeader struct {
  6183  	Hash              string `json:"hash"`
  6184  	Confirmations     int64  `json:"confirmations"`
  6185  	Height            int64  `json:"height"`
  6186  	Time              int64  `json:"time"`
  6187  	PreviousBlockHash string `json:"previousblockhash"`
  6188  	MedianTime        int64  `json:"mediantime"`
  6189  }
  6190  
  6191  // hashContract hashes the contract for use in a p2sh or p2wsh pubkey script.
  6192  // The hash function used depends on whether the wallet is configured for
  6193  // segwit. Non-segwit uses Hash160, segwit uses SHA256.
  6194  func (btc *baseWallet) hashContract(contract []byte) []byte {
  6195  	return hashContract(btc.segwit, contract)
  6196  }
  6197  
  6198  func hashContract(segwit bool, contract []byte) []byte {
  6199  	if segwit {
  6200  		h := sha256.Sum256(contract) // BIP141
  6201  		return h[:]
  6202  	}
  6203  	return btcutil.Hash160(contract) // BIP16
  6204  }
  6205  
  6206  // scriptHashAddress returns a new p2sh or p2wsh address, depending on whether
  6207  // the wallet is configured for segwit.
  6208  func (btc *baseWallet) scriptHashAddress(contract []byte) (btcutil.Address, error) {
  6209  	return scriptHashAddress(btc.segwit, contract, btc.chainParams)
  6210  }
  6211  
  6212  func (btc *baseWallet) scriptHashScript(contract []byte) ([]byte, error) {
  6213  	addr, err := btc.scriptHashAddress(contract)
  6214  	if err != nil {
  6215  		return nil, err
  6216  	}
  6217  	return txscript.PayToAddrScript(addr)
  6218  }
  6219  
  6220  // CallRPC is a method for making RPC calls directly on an underlying RPC
  6221  // client. CallRPC is not part of the wallet interface. Its intended use is for
  6222  // clone wallets to implement custom functionality.
  6223  func (btc *baseWallet) CallRPC(method string, args []any, thing any) error {
  6224  	rpcCl, is := btc.node.(*rpcClient)
  6225  	if !is {
  6226  		return errors.New("wallet is not RPC")
  6227  	}
  6228  	return rpcCl.call(method, args, thing)
  6229  }
  6230  
  6231  func scriptHashAddress(segwit bool, contract []byte, chainParams *chaincfg.Params) (btcutil.Address, error) {
  6232  	if segwit {
  6233  		return btcutil.NewAddressWitnessScriptHash(hashContract(segwit, contract), chainParams)
  6234  	}
  6235  	return btcutil.NewAddressScriptHash(contract, chainParams)
  6236  }
  6237  
  6238  // ToCoinID converts the tx hash and vout to a coin ID, as a []byte.
  6239  func ToCoinID(txHash *chainhash.Hash, vout uint32) []byte {
  6240  	coinID := make([]byte, chainhash.HashSize+4)
  6241  	copy(coinID[:chainhash.HashSize], txHash[:])
  6242  	binary.BigEndian.PutUint32(coinID[chainhash.HashSize:], vout)
  6243  	return coinID
  6244  }
  6245  
  6246  // decodeCoinID decodes the coin ID into a tx hash and a vout.
  6247  func decodeCoinID(coinID dex.Bytes) (*chainhash.Hash, uint32, error) {
  6248  	if len(coinID) != 36 {
  6249  		return nil, 0, fmt.Errorf("coin ID wrong length. expected 36, got %d", len(coinID))
  6250  	}
  6251  	var txHash chainhash.Hash
  6252  	copy(txHash[:], coinID[:32])
  6253  	return &txHash, binary.BigEndian.Uint32(coinID[32:]), nil
  6254  }
  6255  
  6256  // toBTC returns a float representation in conventional units for the sats.
  6257  func toBTC[V uint64 | int64](v V) float64 {
  6258  	return btcutil.Amount(v).ToBTC()
  6259  }
  6260  
  6261  // rawTxInSig signs the transaction in input using the standard bitcoin
  6262  // signature hash and ECDSA algorithm.
  6263  func rawTxInSig(tx *wire.MsgTx, idx int, pkScript []byte, hashType txscript.SigHashType,
  6264  	key *btcec.PrivateKey, _ []int64, _ [][]byte) ([]byte, error) {
  6265  
  6266  	return txscript.RawTxInSignature(tx, idx, pkScript, txscript.SigHashAll, key)
  6267  }
  6268  
  6269  // prettyBTC prints a value as a float with up to 8 digits of precision, but
  6270  // with trailing zeros and decimal points removed.
  6271  func prettyBTC(v uint64) string {
  6272  	return strings.TrimRight(strings.TrimRight(strconv.FormatFloat(float64(v)/1e8, 'f', 8, 64), "0"), ".")
  6273  }
  6274  
  6275  // calcBumpedRate calculated a bump on the baseRate. If bump is nil, the
  6276  // baseRate is returned directly. In the case of an error (nil or out-of-range),
  6277  // the baseRate is returned unchanged.
  6278  func calcBumpedRate(baseRate uint64, bump *float64) (uint64, error) {
  6279  	if bump == nil {
  6280  		return baseRate, nil
  6281  	}
  6282  	userBump := *bump
  6283  	if userBump > 2.0 {
  6284  		return baseRate, fmt.Errorf("fee bump %f is higher than the 2.0 limit", userBump)
  6285  	}
  6286  	if userBump < 1.0 {
  6287  		return baseRate, fmt.Errorf("fee bump %f is lower than 1", userBump)
  6288  	}
  6289  	return uint64(math.Round(float64(baseRate) * userBump)), nil
  6290  }
  6291  
  6292  func float64PtrStr(v *float64) string {
  6293  	if v == nil {
  6294  		return "nil"
  6295  	}
  6296  	return strconv.FormatFloat(*v, 'f', 8, 64)
  6297  }
  6298  
  6299  func hashTx(tx *wire.MsgTx) *chainhash.Hash {
  6300  	h := tx.TxHash()
  6301  	return &h
  6302  }
  6303  
  6304  func stringifyAddress(addr btcutil.Address, _ *chaincfg.Params) (string, error) {
  6305  	return addr.String(), nil
  6306  }
  6307  
  6308  func deserializeBlock(b []byte) (*wire.MsgBlock, error) {
  6309  	msgBlock := &wire.MsgBlock{}
  6310  	return msgBlock, msgBlock.Deserialize(bytes.NewReader(b))
  6311  }
  6312  
  6313  // serializeMsgTx serializes the wire.MsgTx.
  6314  func serializeMsgTx(msgTx *wire.MsgTx) ([]byte, error) {
  6315  	buf := bytes.NewBuffer(make([]byte, 0, msgTx.SerializeSize()))
  6316  	err := msgTx.Serialize(buf)
  6317  	if err != nil {
  6318  		return nil, err
  6319  	}
  6320  	return buf.Bytes(), nil
  6321  }
  6322  
  6323  // deserializeMsgTx creates a wire.MsgTx by deserializing data from the Reader.
  6324  func deserializeMsgTx(r io.Reader) (*wire.MsgTx, error) {
  6325  	msgTx := new(wire.MsgTx)
  6326  	err := msgTx.Deserialize(r)
  6327  	if err != nil {
  6328  		return nil, err
  6329  	}
  6330  	return msgTx, nil
  6331  }
  6332  
  6333  // msgTxFromBytes creates a wire.MsgTx by deserializing the transaction.
  6334  func msgTxFromBytes(txB []byte) (*wire.MsgTx, error) {
  6335  	return deserializeMsgTx(bytes.NewReader(txB))
  6336  }
  6337  
  6338  // ConfirmRedemption returns how many confirmations a redemption has. Normally
  6339  // this is very straightforward. However, with fluxuating fees, there's the
  6340  // possibility that the tx is never mined and eventually purged from the
  6341  // mempool. In that case we use the provided fee suggestion to create and send
  6342  // a new redeem transaction, returning the new transactions hash.
  6343  func (btc *baseWallet) ConfirmRedemption(coinID dex.Bytes, redemption *asset.Redemption, feeSuggestion uint64) (*asset.ConfirmRedemptionStatus, error) {
  6344  	txHash, _, err := decodeCoinID(coinID)
  6345  	if err != nil {
  6346  		return nil, err
  6347  	}
  6348  
  6349  	_, confs, err := btc.rawWalletTx(txHash)
  6350  	// redemption transaction found, return its confirms.
  6351  	//
  6352  	// TODO: Investigate the case where this redeem has been sitting in the
  6353  	// mempool for a long amount of time, possibly requiring some action by
  6354  	// us to get it unstuck.
  6355  	if err == nil {
  6356  		return &asset.ConfirmRedemptionStatus{
  6357  			Confs:  uint64(confs),
  6358  			Req:    requiredRedeemConfirms,
  6359  			CoinID: coinID,
  6360  		}, nil
  6361  	}
  6362  
  6363  	if !errors.Is(err, WalletTransactionNotFound) {
  6364  		return nil, fmt.Errorf("problem searching for redemption transaction %s: %w", txHash, err)
  6365  	}
  6366  
  6367  	// Redemption transaction is missing from the point of view of our node!
  6368  	// Unlikely, but possible it was redeemed by another transaction. Check
  6369  	// if the contract is still an unspent output.
  6370  
  6371  	pkScript, err := btc.scriptHashScript(redemption.Spends.Contract)
  6372  	if err != nil {
  6373  		return nil, fmt.Errorf("error creating contract script: %w", err)
  6374  	}
  6375  
  6376  	swapHash, vout, err := decodeCoinID(redemption.Spends.Coin.ID())
  6377  	if err != nil {
  6378  		return nil, err
  6379  	}
  6380  
  6381  	utxo, _, err := btc.node.getTxOut(swapHash, vout, pkScript, time.Now().Add(-ContractSearchLimit))
  6382  	if err != nil {
  6383  		return nil, fmt.Errorf("error finding unspent contract %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err)
  6384  	}
  6385  	if utxo == nil {
  6386  		// TODO: Spent, but by who. Find the spending tx.
  6387  		btc.log.Warnf("Contract coin %v with swap hash %v vout %d spent by someone but not sure who.", redemption.Spends.Coin.ID(), swapHash, vout)
  6388  		// Incorrect, but we will be in a loop of erroring if we don't
  6389  		// return something.
  6390  		return &asset.ConfirmRedemptionStatus{
  6391  			Confs:  requiredRedeemConfirms,
  6392  			Req:    requiredRedeemConfirms,
  6393  			CoinID: coinID,
  6394  		}, nil
  6395  	}
  6396  
  6397  	// The contract has not yet been redeemed, but it seems the redeeming
  6398  	// tx has disappeared. Assume the fee was too low at the time and it
  6399  	// was eventually purged from the mempool. Attempt to redeem again with
  6400  	// a currently reasonable fee.
  6401  
  6402  	form := &asset.RedeemForm{
  6403  		Redemptions:   []*asset.Redemption{redemption},
  6404  		FeeSuggestion: feeSuggestion,
  6405  	}
  6406  	_, coin, _, err := btc.Redeem(form)
  6407  	if err != nil {
  6408  		return nil, fmt.Errorf("unable to re-redeem %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err)
  6409  	}
  6410  	return &asset.ConfirmRedemptionStatus{
  6411  		Confs:  0,
  6412  		Req:    requiredRedeemConfirms,
  6413  		CoinID: coin.ID(),
  6414  	}, nil
  6415  }
  6416  
  6417  type AddressRecycler struct {
  6418  	recyclePath string
  6419  	log         dex.Logger
  6420  
  6421  	mtx   sync.Mutex
  6422  	addrs map[string]struct{}
  6423  }
  6424  
  6425  func NewAddressRecycler(recyclePath string, log dex.Logger) (*AddressRecycler, error) {
  6426  	// Try to load any cached unused redemption addresses.
  6427  	b, err := os.ReadFile(recyclePath)
  6428  	if err != nil && !errors.Is(err, os.ErrNotExist) {
  6429  		return nil, fmt.Errorf("error looking for recycled address file: %w", err)
  6430  	}
  6431  	addrs := strings.Split(string(b), "\n")
  6432  	recycledAddrs := make(map[string]struct{}, len(addrs))
  6433  	for _, addr := range addrs {
  6434  		if addr == "" {
  6435  			continue
  6436  		}
  6437  		recycledAddrs[addr] = struct{}{}
  6438  	}
  6439  	return &AddressRecycler{
  6440  		recyclePath: recyclePath,
  6441  		log:         log,
  6442  		addrs:       recycledAddrs,
  6443  	}, nil
  6444  }
  6445  
  6446  // WriteRecycledAddrsToFile writes the recycled address cache to file.
  6447  func (a *AddressRecycler) WriteRecycledAddrsToFile() {
  6448  	a.mtx.Lock()
  6449  	addrs := make([]string, 0, len(a.addrs))
  6450  	for addr := range a.addrs {
  6451  		addrs = append(addrs, addr)
  6452  	}
  6453  	a.mtx.Unlock()
  6454  	contents := []byte(strings.Join(addrs, "\n"))
  6455  	if err := os.WriteFile(a.recyclePath, contents, 0600); err != nil {
  6456  		a.log.Errorf("Error writing recycled address file: %v", err)
  6457  	}
  6458  }
  6459  
  6460  func (a *AddressRecycler) Address() string {
  6461  	a.mtx.Lock()
  6462  	defer a.mtx.Unlock()
  6463  	for addr := range a.addrs {
  6464  		delete(a.addrs, addr)
  6465  		return addr
  6466  	}
  6467  	return ""
  6468  }
  6469  
  6470  func (a *AddressRecycler) ReturnAddresses(addrs []string) {
  6471  	a.mtx.Lock()
  6472  	defer a.mtx.Unlock()
  6473  	for _, addr := range addrs {
  6474  		if _, exists := a.addrs[addr]; exists {
  6475  			a.log.Errorf("Returned address %q was already indexed", addr)
  6476  			continue
  6477  		}
  6478  		a.addrs[addr] = struct{}{}
  6479  	}
  6480  }
  6481  
  6482  // BitcoreRateFetcher generates a rate fetching function for the bitcore.io API.
  6483  func BitcoreRateFetcher(ticker string) func(ctx context.Context, net dex.Network) (uint64, error) {
  6484  	const uriTemplate = "https://api.bitcore.io/api/%s/%s/fee/1"
  6485  	mainnetURI, testnetURI := fmt.Sprintf(uriTemplate, ticker, "mainnet"), fmt.Sprintf(uriTemplate, ticker, "testnet")
  6486  
  6487  	return func(ctx context.Context, net dex.Network) (uint64, error) {
  6488  		var uri string
  6489  		if net == dex.Testnet {
  6490  			uri = testnetURI
  6491  		} else {
  6492  			uri = mainnetURI
  6493  		}
  6494  		ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
  6495  		defer cancel()
  6496  		var resp struct {
  6497  			RatePerKB float64 `json:"feerate"`
  6498  		}
  6499  		if err := dexnet.Get(ctx, uri, &resp); err != nil {
  6500  			return 0, err
  6501  		}
  6502  		if resp.RatePerKB <= 0 {
  6503  			return 0, fmt.Errorf("zero or negative fee rate")
  6504  		}
  6505  		return uint64(math.Round(resp.RatePerKB * 1e5)), nil // 1/kB => 1/B
  6506  	}
  6507  }