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