decred.org/dcrdex@v1.0.3/client/asset/zec/zec.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 zec
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"crypto/sha256"
    10  	"encoding/binary"
    11  	"encoding/hex"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"math"
    16  	"os"
    17  	"path/filepath"
    18  	"regexp"
    19  	"sort"
    20  	"strings"
    21  	"sync"
    22  	"sync/atomic"
    23  	"time"
    24  
    25  	"decred.org/dcrdex/client/asset"
    26  	"decred.org/dcrdex/client/asset/btc"
    27  	"decred.org/dcrdex/dex"
    28  	"decred.org/dcrdex/dex/config"
    29  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    30  	dexzec "decred.org/dcrdex/dex/networks/zec"
    31  	"github.com/btcsuite/btcd/btcec/v2"
    32  	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
    33  	"github.com/btcsuite/btcd/btcjson"
    34  	"github.com/btcsuite/btcd/btcutil"
    35  	"github.com/btcsuite/btcd/chaincfg"
    36  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    37  	"github.com/btcsuite/btcd/txscript"
    38  	"github.com/btcsuite/btcd/wire"
    39  	"github.com/decred/dcrd/rpcclient/v8"
    40  )
    41  
    42  const (
    43  	version = 0
    44  	BipID   = 133
    45  	// The default fee is passed to the user as part of the asset.WalletInfo
    46  	// structure.
    47  	defaultFee          = 10
    48  	defaultFeeRateLimit = 1000
    49  	minNetworkVersion   = 5090150 // v5.9.1
    50  	walletTypeRPC       = "zcashdRPC"
    51  
    52  	// defaultConfTarget is the amount of confirmations required to consider
    53  	// a transaction is confirmed for tx history.
    54  	defaultConfTarget = 1
    55  
    56  	// transparentAcctNumber = 0
    57  	shieldedAcctNumber = 0
    58  
    59  	transparentAddressType = "p2pkh"
    60  	orchardAddressType     = "orchard"
    61  	saplingAddressType     = "sapling"
    62  	unifiedAddressType     = "unified"
    63  
    64  	minOrchardConfs = 1
    65  	// nActionsOrchardEstimate is used for tx fee estimation. Scanning 1000
    66  	// previous blocks, only found 1 with a tx with > 6 nActionsOrchard. Most
    67  	// are 2.
    68  	nActionsOrchardEstimate = 6
    69  
    70  	blockTicker     = time.Second
    71  	peerCountTicker = 5 * time.Second
    72  
    73  	// requiredRedeemConfirms is the amount of confirms a redeem transaction
    74  	// needs before the trade is considered confirmed. The redeem is
    75  	// monitored until this number of confirms is reached.
    76  	requiredRedeemConfirms = 1
    77  
    78  	depositAddrPrefix = "unified:"
    79  )
    80  
    81  var (
    82  	configOpts = []*asset.ConfigOption{
    83  		{
    84  			Key:         "rpcuser",
    85  			DisplayName: "JSON-RPC Username",
    86  			Description: "Zcash's 'rpcuser' setting",
    87  		},
    88  		{
    89  			Key:         "rpcpassword",
    90  			DisplayName: "JSON-RPC Password",
    91  			Description: "Zcash's 'rpcpassword' setting",
    92  			NoEcho:      true,
    93  		},
    94  		{
    95  			Key:         "rpcbind",
    96  			DisplayName: "JSON-RPC Address",
    97  			Description: "<addr> or <addr>:<port> (default 'localhost')",
    98  		},
    99  		{
   100  			Key:         "rpcport",
   101  			DisplayName: "JSON-RPC Port",
   102  			Description: "Port for RPC connections (if not set in Address)",
   103  		},
   104  		{
   105  			Key:          "fallbackfee",
   106  			DisplayName:  "Fallback fee rate",
   107  			Description:  "Zcash's 'fallbackfee' rate. Units: ZEC/kB",
   108  			DefaultValue: defaultFee * 1000 / 1e8,
   109  		},
   110  		{
   111  			Key:         "feeratelimit",
   112  			DisplayName: "Highest acceptable fee rate",
   113  			Description: "This is the highest network fee rate you are willing to " +
   114  				"pay on swap transactions. If feeratelimit is lower than a market's " +
   115  				"maxfeerate, you will not be able to trade on that market with this " +
   116  				"wallet.  Units: BTC/kB",
   117  			DefaultValue: defaultFeeRateLimit * 1000 / 1e8,
   118  		},
   119  		{
   120  			Key:         "txsplit",
   121  			DisplayName: "Pre-split funding inputs",
   122  			Description: "When placing an order, create a \"split\" transaction to fund the order without locking more of the wallet balance than " +
   123  				"necessary. Otherwise, excess funds may be reserved to fund the order until the first swap contract is broadcast " +
   124  				"during match settlement, or the order is canceled. This an extra transaction for which network mining fees are paid. " +
   125  				"Used only for standing-type orders, e.g. limit orders without immediate time-in-force.",
   126  			IsBoolean: true,
   127  		},
   128  	}
   129  	// WalletInfo defines some general information about a Zcash wallet.
   130  	WalletInfo = &asset.WalletInfo{
   131  		Name:              "Zcash",
   132  		SupportedVersions: []uint32{version},
   133  		UnitInfo:          dexzec.UnitInfo,
   134  		AvailableWallets: []*asset.WalletDefinition{{
   135  			Type:              walletTypeRPC,
   136  			Tab:               "External",
   137  			Description:       "Connect to zcashd",
   138  			DefaultConfigPath: dexbtc.SystemConfigPath("zcash"),
   139  			ConfigOpts:        configOpts,
   140  			NoAuth:            true,
   141  		}},
   142  	}
   143  
   144  	feeReservesPerLot = dexzec.TxFeesZIP317(dexbtc.RedeemP2PKHInputSize+1, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
   145  )
   146  
   147  func init() {
   148  	asset.Register(BipID, &Driver{})
   149  }
   150  
   151  // Driver implements asset.Driver.
   152  type Driver struct{}
   153  
   154  // Open creates the ZEC exchange wallet. Start the wallet with its Run method.
   155  func (d *Driver) Open(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (asset.Wallet, error) {
   156  	return NewWallet(cfg, logger, network)
   157  }
   158  
   159  // DecodeCoinID creates a human-readable representation of a coin ID for
   160  // Zcash.
   161  func (d *Driver) DecodeCoinID(coinID []byte) (string, error) {
   162  	// Zcash shielded transactions don't have transparent outputs, so the coinID
   163  	// will just be the tx hash.
   164  	if len(coinID) == chainhash.HashSize {
   165  		var txHash chainhash.Hash
   166  		copy(txHash[:], coinID)
   167  		return txHash.String(), nil
   168  	}
   169  	// For transparent transactions, Zcash and Bitcoin have the same tx hash
   170  	// and output format.
   171  	return (&btc.Driver{}).DecodeCoinID(coinID)
   172  }
   173  
   174  // Info returns basic information about the wallet and asset.
   175  func (d *Driver) Info() *asset.WalletInfo {
   176  	return WalletInfo
   177  }
   178  
   179  // MinLotSize calculates the minimum bond size for a given fee rate that avoids
   180  // dust outputs on the swap and refund txs, assuming the maxFeeRate doesn't
   181  // change.
   182  func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 {
   183  	return dexzec.MinLotSize(maxFeeRate)
   184  }
   185  
   186  // WalletConfig are wallet-level configuration settings.
   187  type WalletConfig struct {
   188  	UseSplitTx       bool   `ini:"txsplit"`
   189  	RedeemConfTarget uint64 `ini:"redeemconftarget"`
   190  	ActivelyUsed     bool   `ini:"special_activelyUsed"` // injected by core
   191  }
   192  
   193  func newRPCConnection(cfg *dexbtc.RPCConfig) (*rpcclient.Client, error) {
   194  	return rpcclient.New(&rpcclient.ConnConfig{
   195  		HTTPPostMode: true,
   196  		DisableTLS:   true,
   197  		Host:         cfg.RPCBind,
   198  		User:         cfg.RPCUser,
   199  		Pass:         cfg.RPCPass,
   200  	}, nil)
   201  }
   202  
   203  // NewWallet is the exported constructor by which the DEX will import the
   204  // exchange wallet. The wallet will shut down when the provided context is
   205  // canceled. The configPath can be an empty string, in which case the standard
   206  // system location of the zcashd config file is assumed.
   207  func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (asset.Wallet, error) {
   208  	var btcParams *chaincfg.Params
   209  	var addrParams *dexzec.AddressParams
   210  	switch net {
   211  	case dex.Mainnet:
   212  		btcParams = dexzec.MainNetParams
   213  		addrParams = dexzec.MainNetAddressParams
   214  	case dex.Testnet:
   215  		btcParams = dexzec.TestNet4Params
   216  		addrParams = dexzec.TestNet4AddressParams
   217  	case dex.Regtest:
   218  		btcParams = dexzec.RegressionNetParams
   219  		addrParams = dexzec.RegressionNetAddressParams
   220  	default:
   221  		return nil, fmt.Errorf("unknown network ID %v", net)
   222  	}
   223  
   224  	// Designate the clone ports. These will be overwritten by any explicit
   225  	// settings in the configuration file.
   226  	ports := dexbtc.NetPorts{
   227  		Mainnet: "8232",
   228  		Testnet: "18232",
   229  		Simnet:  "18232",
   230  	}
   231  
   232  	var walletCfg WalletConfig
   233  	err := config.Unmapify(cfg.Settings, &walletCfg)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  
   238  	if err := os.MkdirAll(cfg.DataDir, 0700); err != nil {
   239  		return nil, fmt.Errorf("error creating data directory at %q: %w", cfg.DataDir, err)
   240  	}
   241  
   242  	ar, err := btc.NewAddressRecycler(filepath.Join(cfg.DataDir, "recycled-addrs.txt"), logger)
   243  	if err != nil {
   244  		return nil, fmt.Errorf("error creating address recycler: %w", err)
   245  	}
   246  
   247  	var rpcCfg dexbtc.RPCConfig
   248  	if err := config.Unmapify(cfg.Settings, &rpcCfg); err != nil {
   249  		return nil, fmt.Errorf("error reading settings: %w", err)
   250  	}
   251  
   252  	if err := dexbtc.CheckRPCConfig(&rpcCfg, "Zcash", net, ports); err != nil {
   253  		return nil, fmt.Errorf("rpc config error: %v", err)
   254  	}
   255  
   256  	cl, err := newRPCConnection(&rpcCfg)
   257  	if err != nil {
   258  		return nil, fmt.Errorf("error constructing rpc client: %w", err)
   259  	}
   260  
   261  	zw := &zecWallet{
   262  		peersChange: cfg.PeersChange,
   263  		emit:        cfg.Emit,
   264  		log:         logger,
   265  		net:         net,
   266  		btcParams:   btcParams,
   267  		addrParams:  addrParams,
   268  		decodeAddr: func(addr string, net *chaincfg.Params) (btcutil.Address, error) {
   269  			return dexzec.DecodeAddress(addr, addrParams, btcParams)
   270  		},
   271  		ar:         ar,
   272  		node:       cl,
   273  		walletDir:  cfg.DataDir,
   274  		pendingTxs: make(map[chainhash.Hash]*btc.ExtendedWalletTx),
   275  	}
   276  	zw.walletCfg.Store(&walletCfg)
   277  	zw.prepareCoinManager()
   278  	zw.prepareRedemptionFinder()
   279  	return zw, nil
   280  }
   281  
   282  type rpcCaller interface {
   283  	CallRPC(method string, args []any, thing any) error
   284  }
   285  
   286  // zecWallet is an asset.Wallet for Zcash. zecWallet is a shielded-first wallet.
   287  // This has a number of implications.
   288  //  1. zecWallet will, by default, keep funds in the shielded pool.
   289  //  2. If you send funds with z_sendmany, the change will go to the shielded pool.
   290  //     This means that we cannot maintain a transparent pool at all, since this
   291  //     behavior is unavoidable in the Zcash API, so sending funds to ourselves,
   292  //     for instance, would probably result in sending more into shielded than
   293  //     we wanted. This does not apply to fully transparent transactions such as
   294  //     swaps and redemptions.
   295  //  3. When funding is requested for an order, we will generally generate a
   296  //     "split transaction", but one that moves funds from the shielded pool to
   297  //     a transparent receiver. This will be default behavior for Zcash now, and
   298  //     cannot be disabled via configuration.
   299  //  4. ...
   300  type zecWallet struct {
   301  	ctx           context.Context
   302  	log           dex.Logger
   303  	net           dex.Network
   304  	lastAddress   atomic.Value // "string"
   305  	btcParams     *chaincfg.Params
   306  	addrParams    *dexzec.AddressParams
   307  	node          btc.RawRequester
   308  	ar            *btc.AddressRecycler
   309  	rf            *btc.RedemptionFinder
   310  	decodeAddr    dexbtc.AddressDecoder
   311  	lastPeerCount uint32
   312  	peersChange   func(uint32, error)
   313  	emit          *asset.WalletEmitter
   314  	walletCfg     atomic.Value // *WalletConfig
   315  	walletDir     string
   316  
   317  	// Coins returned by Fund are cached for quick reference.
   318  	cm *btc.CoinManager
   319  
   320  	tipAtConnect atomic.Uint64
   321  
   322  	tipMtx     sync.RWMutex
   323  	currentTip *btc.BlockVector
   324  
   325  	reserves atomic.Uint64
   326  
   327  	pendingTxsMtx sync.RWMutex
   328  	pendingTxs    map[chainhash.Hash]*btc.ExtendedWalletTx
   329  
   330  	receiveTxLastQuery atomic.Uint64
   331  
   332  	txHistoryDB      atomic.Value // *btc.BadgerTxDB
   333  	syncingTxHistory atomic.Bool
   334  }
   335  
   336  var _ asset.FeeRater = (*zecWallet)(nil)
   337  var _ asset.Wallet = (*zecWallet)(nil)
   338  var _ asset.WalletHistorian = (*zecWallet)(nil)
   339  
   340  // TODO: Implement LiveReconfigurer
   341  // var _ asset.LiveReconfigurer = (*zecWallet)(nil)
   342  
   343  func (w *zecWallet) CallRPC(method string, args []any, thing any) error {
   344  	return btc.Call(w.ctx, w.node, method, args, thing)
   345  }
   346  
   347  // FeeRate returns the asset standard fee rate for Zcash.
   348  func (w *zecWallet) FeeRate() uint64 {
   349  	return 5000 // per logical action
   350  }
   351  
   352  func (w *zecWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) {
   353  	w.ctx = ctx
   354  
   355  	if err := w.connectRPC(ctx); err != nil {
   356  		return nil, err
   357  	}
   358  
   359  	fullTBalance, err := getBalance(w)
   360  	if err != nil {
   361  		return nil, fmt.Errorf("error getting account-wide t-balance: %w", err)
   362  	}
   363  
   364  	const minConfs = 0
   365  	acctBal, err := zGetBalanceForAccount(w, shieldedAcctNumber, minConfs)
   366  	if err != nil {
   367  		return nil, fmt.Errorf("error getting account balance: %w", err)
   368  	}
   369  
   370  	locked, err := w.lockedZats()
   371  	if err != nil {
   372  		return nil, fmt.Errorf("error getting locked zats: %w", err)
   373  	}
   374  
   375  	if acctBal.Transparent+locked != fullTBalance {
   376  		return nil, errors.New(
   377  			"there appears to be some transparent balance that is not in the zeroth account. " +
   378  				"To operate correctly, all balance must be in the zeroth account. " +
   379  				"Move all balance to the zeroth account to use the Zcash wallet",
   380  		)
   381  	}
   382  
   383  	// Initialize the best block.
   384  	bestBlockHdr, err := getBestBlockHeader(w)
   385  	if err != nil {
   386  		return nil, fmt.Errorf("error initializing best block: %w", err)
   387  	}
   388  	bestBlockHash, err := chainhash.NewHashFromStr(bestBlockHdr.Hash)
   389  	if err != nil {
   390  		return nil, fmt.Errorf("invalid best block hash from node: %v", err)
   391  	}
   392  
   393  	bestBlock := &btc.BlockVector{Height: bestBlockHdr.Height, Hash: *bestBlockHash}
   394  	w.log.Infof("Connected wallet with current best block %v (%d)", bestBlock.Hash, bestBlock.Height)
   395  	w.tipMtx.Lock()
   396  	w.currentTip = bestBlock
   397  	w.tipMtx.Unlock()
   398  	w.tipAtConnect.Store(uint64(w.currentTip.Height))
   399  
   400  	wg, err := w.startTxHistoryDB(ctx)
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  
   405  	wg.Add(1)
   406  	go func() {
   407  		defer wg.Done()
   408  		<-ctx.Done()
   409  		w.ar.WriteRecycledAddrsToFile()
   410  	}()
   411  
   412  	wg.Add(1)
   413  	go func() {
   414  		defer wg.Done()
   415  		w.watchBlocks(ctx)
   416  		w.rf.CancelRedemptionSearches()
   417  	}()
   418  	wg.Add(1)
   419  	go func() {
   420  		defer wg.Done()
   421  		w.monitorPeers(ctx)
   422  	}()
   423  
   424  	wg.Add(1)
   425  	go func() {
   426  		defer wg.Done()
   427  		w.syncTxHistory(uint64(w.currentTip.Height))
   428  	}()
   429  
   430  	return wg, nil
   431  }
   432  
   433  func (w *zecWallet) monitorPeers(ctx context.Context) {
   434  	ticker := time.NewTicker(peerCountTicker)
   435  	defer ticker.Stop()
   436  	for {
   437  		w.checkPeers()
   438  
   439  		select {
   440  		case <-ticker.C:
   441  		case <-ctx.Done():
   442  			return
   443  		}
   444  	}
   445  }
   446  
   447  func (w *zecWallet) checkPeers() {
   448  	numPeers, err := peerCount(w)
   449  	if err != nil {
   450  		prevPeer := atomic.SwapUint32(&w.lastPeerCount, 0)
   451  		if prevPeer != 0 {
   452  			w.log.Errorf("Failed to get peer count: %v", err)
   453  			w.peersChange(0, err)
   454  		}
   455  		return
   456  	}
   457  	prevPeer := atomic.SwapUint32(&w.lastPeerCount, numPeers)
   458  	if prevPeer != numPeers {
   459  		w.peersChange(numPeers, nil)
   460  	}
   461  }
   462  
   463  func (w *zecWallet) prepareCoinManager() {
   464  	w.cm = btc.NewCoinManager(
   465  		w.log,
   466  		w.btcParams,
   467  		func(val, lots, maxFeeRate uint64, reportChange bool) btc.EnoughFunc { // orderEnough
   468  			return func(inputCount, inputsSize, sum uint64) (bool, uint64) {
   469  				req := dexzec.RequiredOrderFunds(val, inputCount, inputsSize, lots)
   470  				if sum >= req {
   471  					excess := sum - req
   472  					if !reportChange || isDust(val, dexbtc.P2SHOutputSize) {
   473  						excess = 0
   474  					}
   475  					return true, excess
   476  				}
   477  				return false, 0
   478  			}
   479  		},
   480  		func() ([]*btc.ListUnspentResult, error) { // list
   481  			return listUnspent(w)
   482  		},
   483  		func(unlock bool, ops []*btc.Output) error { // lock
   484  			return lockUnspent(w, unlock, ops)
   485  		},
   486  		func() ([]*btc.RPCOutpoint, error) { // listLocked
   487  			return listLockUnspent(w, w.log)
   488  		},
   489  		func(txHash *chainhash.Hash, vout uint32) (*wire.TxOut, error) {
   490  			walletTx, err := getWalletTransaction(w, txHash)
   491  			if err != nil {
   492  				return nil, err
   493  			}
   494  			tx, err := dexzec.DeserializeTx(walletTx.Bytes)
   495  			if err != nil {
   496  				w.log.Warnf("Invalid transaction %v (%x): %v", txHash, walletTx.Bytes, err)
   497  				return nil, nil
   498  			}
   499  			if vout >= uint32(len(tx.TxOut)) {
   500  				w.log.Warnf("Invalid vout %d for %v", vout, txHash)
   501  				return nil, nil
   502  			}
   503  			return tx.TxOut[vout], nil
   504  		},
   505  		func(addr btcutil.Address) (string, error) {
   506  			return dexzec.EncodeAddress(addr, w.addrParams)
   507  		},
   508  	)
   509  }
   510  
   511  func (w *zecWallet) prepareRedemptionFinder() {
   512  	w.rf = btc.NewRedemptionFinder(
   513  		w.log,
   514  		func(h *chainhash.Hash) (*btc.GetTransactionResult, error) {
   515  			tx, err := getWalletTransaction(w, h)
   516  			if err != nil {
   517  				return nil, err
   518  			}
   519  			if tx.Confirmations < 0 {
   520  				tx.Confirmations = 0
   521  			}
   522  			return &btc.GetTransactionResult{
   523  				Confirmations: uint64(tx.Confirmations),
   524  				BlockHash:     tx.BlockHash,
   525  				BlockTime:     tx.BlockTime,
   526  				TxID:          tx.TxID,
   527  				Time:          tx.Time,
   528  				TimeReceived:  tx.TimeReceived,
   529  				Bytes:         tx.Bytes,
   530  			}, nil
   531  		},
   532  		func(h *chainhash.Hash) (int32, error) {
   533  			return getBlockHeight(w, h)
   534  		},
   535  		func(h chainhash.Hash) (*wire.MsgBlock, error) {
   536  			blk, err := getBlock(w, h)
   537  			if err != nil {
   538  				return nil, err
   539  			}
   540  			return &blk.MsgBlock, nil
   541  		},
   542  		func(h *chainhash.Hash) (hdr *btc.BlockHeader, mainchain bool, err error) {
   543  			return getVerboseBlockHeader(w, h)
   544  		},
   545  		hashTx,
   546  		deserializeTx,
   547  		func() (int32, error) {
   548  			return getBestBlockHeight(w)
   549  		},
   550  		func(ctx context.Context, reqs map[btc.OutPoint]*btc.FindRedemptionReq, blockHash chainhash.Hash) (discovered map[btc.OutPoint]*btc.FindRedemptionResult) {
   551  			blk, err := getBlock(w, blockHash)
   552  			if err != nil {
   553  				w.log.Errorf("RPC GetBlock error: %v", err)
   554  				return
   555  			}
   556  			return btc.SearchBlockForRedemptions(ctx, reqs, &blk.MsgBlock, false, hashTx, w.btcParams)
   557  		},
   558  		func(h int64) (*chainhash.Hash, error) {
   559  			return getBlockHash(w, h)
   560  		},
   561  		func(ctx context.Context, reqs map[btc.OutPoint]*btc.FindRedemptionReq) (discovered map[btc.OutPoint]*btc.FindRedemptionResult) {
   562  			getRawMempool := func() ([]*chainhash.Hash, error) {
   563  				return getRawMempool(w)
   564  			}
   565  			getMsgTx := func(txHash *chainhash.Hash) (*wire.MsgTx, error) {
   566  				tx, err := getZecTransaction(w, txHash)
   567  				if err != nil {
   568  					return nil, err
   569  				}
   570  				return tx.MsgTx, nil
   571  			}
   572  			return btc.FindRedemptionsInMempool(ctx, w.log, reqs, getRawMempool, getMsgTx, false, hashTx, w.btcParams)
   573  		},
   574  	)
   575  }
   576  
   577  func (w *zecWallet) connectRPC(ctx context.Context) error {
   578  	netVer, _, err := getVersion(w)
   579  	if err != nil {
   580  		return fmt.Errorf("error getting version: %w", err)
   581  	}
   582  	if netVer < minNetworkVersion {
   583  		return fmt.Errorf("reported node version %d is less than minimum %d", netVer, minNetworkVersion)
   584  	}
   585  	chainInfo, err := getBlockchainInfo(w)
   586  	if err != nil {
   587  		return fmt.Errorf("getblockchaininfo error: %w", err)
   588  	}
   589  	if !btc.ChainOK(w.net, chainInfo.Chain) {
   590  		return errors.New("wrong net")
   591  	}
   592  	// Make sure we have zeroth and first account or are able to create them.
   593  	accts, err := zListAccounts(w)
   594  	if err != nil {
   595  		return fmt.Errorf("error listing Zcash accounts: %w", err)
   596  	}
   597  
   598  	createAccount := func(n uint32) error {
   599  		for _, acct := range accts {
   600  			if acct.Number == n {
   601  				return nil
   602  			}
   603  		}
   604  		acctNumber, err := zGetNewAccount(w)
   605  		if err != nil {
   606  			if strings.Contains(err.Error(), "zcashd-wallet-tool") {
   607  				return fmt.Errorf("account %d does not exist and cannot be created because wallet seed backup has not been acknowledged with the zcashd-wallet-tool utility", n)
   608  			}
   609  			return fmt.Errorf("error creating account %d: %w", n, err)
   610  		}
   611  		if acctNumber != n {
   612  			return fmt.Errorf("no account %d found and newly created account has unexpected account number %d", n, acctNumber)
   613  		}
   614  		return nil
   615  	}
   616  	if err := createAccount(shieldedAcctNumber); err != nil {
   617  		return err
   618  	}
   619  	return nil
   620  }
   621  
   622  // watchBlocks pings for new blocks and runs the tipChange callback function
   623  // when the block changes.
   624  func (w *zecWallet) watchBlocks(ctx context.Context) {
   625  	ticker := time.NewTicker(blockTicker)
   626  	defer ticker.Stop()
   627  
   628  	for {
   629  		select {
   630  
   631  		// Poll for the block. If the wallet offers tip reports, delay reporting
   632  		// the tip to give the wallet a moment to request and scan block data.
   633  		case <-ticker.C:
   634  			newTipHdr, err := getBestBlockHeader(w)
   635  			if err != nil {
   636  				w.log.Errorf("failed to get best block header from node: %v", err)
   637  				continue
   638  			}
   639  			newTipHash, err := chainhash.NewHashFromStr(newTipHdr.Hash)
   640  			if err != nil {
   641  				w.log.Errorf("invalid best block hash from node: %v", err)
   642  				continue
   643  			}
   644  
   645  			w.tipMtx.RLock()
   646  			sameTip := w.currentTip.Hash == *newTipHash
   647  			w.tipMtx.RUnlock()
   648  			if sameTip {
   649  				continue
   650  			}
   651  
   652  			newTip := &btc.BlockVector{Height: newTipHdr.Height, Hash: *newTipHash}
   653  			w.reportNewTip(ctx, newTip)
   654  
   655  		case <-ctx.Done():
   656  			return
   657  		}
   658  
   659  		// Ensure context cancellation takes priority before the next iteration.
   660  		if ctx.Err() != nil {
   661  			return
   662  		}
   663  	}
   664  }
   665  
   666  // reportNewTip sets the currentTip. The tipChange callback function is invoked
   667  // and a goroutine is started to check if any contracts in the
   668  // findRedemptionQueue are redeemed in the new blocks.
   669  func (w *zecWallet) reportNewTip(ctx context.Context, newTip *btc.BlockVector) {
   670  	w.tipMtx.Lock()
   671  	defer w.tipMtx.Unlock()
   672  
   673  	prevTip := w.currentTip
   674  	w.currentTip = newTip
   675  	w.log.Tracef("tip change: %d (%s) => %d (%s)", prevTip.Height, prevTip.Hash, newTip.Height, newTip.Hash)
   676  	w.emit.TipChange(uint64(newTip.Height))
   677  
   678  	w.rf.ReportNewTip(ctx, prevTip, newTip)
   679  
   680  	w.syncTxHistory(uint64(newTip.Height))
   681  }
   682  
   683  type swapOptions struct {
   684  	Split *bool `ini:"swapsplit"`
   685  	// DRAFT TODO: Strip swapfeebump from PreSwap results.
   686  	// FeeBump *float64 `ini:"swapfeebump"`
   687  }
   688  
   689  func (w *zecWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uint64, error) {
   690  	ordValStr := btcutil.Amount(ord.Value).String()
   691  	w.log.Debugf("Attempting to fund Zcash order, maxFeeRate = %d, max swaps = %d",
   692  		ord.MaxFeeRate, ord.MaxSwapCount)
   693  
   694  	if ord.Value == 0 {
   695  		return nil, nil, 0, newError(errNoFundsRequested, "cannot fund value = 0")
   696  	}
   697  	if ord.MaxSwapCount == 0 {
   698  		return nil, nil, 0, fmt.Errorf("cannot fund a zero-lot order")
   699  	}
   700  
   701  	var customCfg swapOptions
   702  	err := config.Unmapify(ord.Options, &customCfg)
   703  	if err != nil {
   704  		return nil, nil, 0, fmt.Errorf("error parsing swap options: %w", err)
   705  	}
   706  
   707  	useSplit := w.useSplitTx()
   708  	if customCfg.Split != nil {
   709  		useSplit = *customCfg.Split
   710  	}
   711  
   712  	bals, err := w.balances()
   713  	if err != nil {
   714  		return nil, nil, 0, fmt.Errorf("balances error: %w", err)
   715  	}
   716  
   717  	utxos, _, _, err := w.cm.SpendableUTXOs(0)
   718  	if err != nil {
   719  		return nil, nil, 0, newError(errFunding, "error listing utxos: %w", err)
   720  	}
   721  
   722  	sum, size, shieldedSplitNeeded, shieldedSplitFees, coins, fundingCoins, redeemScripts, spents, err := w.fund(
   723  		ord.Value, ord.MaxSwapCount, utxos, bals.orchard,
   724  	)
   725  	if err != nil {
   726  		return nil, nil, 0, err
   727  	}
   728  
   729  	inputsSize := size + uint64(wire.VarIntSerializeSize(uint64(len(coins))))
   730  	var transparentSplitFees uint64
   731  
   732  	if shieldedSplitNeeded > 0 {
   733  		acctAddr, err := w.lastShieldedAddress()
   734  		if err != nil {
   735  			return nil, nil, 0, fmt.Errorf("lastShieldedAddress error: %w", err)
   736  		}
   737  
   738  		toAddrBTC, err := transparentAddress(w, w.addrParams, w.btcParams)
   739  		if err != nil {
   740  			return nil, nil, 0, fmt.Errorf("DepositAddress error: %w", err)
   741  		}
   742  
   743  		toAddrStr, err := dexzec.EncodeAddress(toAddrBTC, w.addrParams)
   744  		if err != nil {
   745  			return nil, nil, 0, fmt.Errorf("EncodeAddress error: %w", err)
   746  		}
   747  
   748  		pkScript, err := txscript.PayToAddrScript(toAddrBTC)
   749  		if err != nil {
   750  			return nil, nil, 0, fmt.Errorf("PayToAddrScript error: %w", err)
   751  		}
   752  
   753  		txHash, err := w.sendOne(w.ctx, acctAddr, toAddrStr, shieldedSplitNeeded, AllowRevealedRecipients)
   754  		if err != nil {
   755  			return nil, nil, 0, fmt.Errorf("error sending shielded split tx: %w", err)
   756  		}
   757  
   758  		tx, err := getTransaction(w, txHash)
   759  		if err != nil {
   760  			return nil, nil, 0, fmt.Errorf("error retreiving split transaction %s: %w", txHash, err)
   761  		}
   762  
   763  		var splitOutput *wire.TxOut
   764  		var splitOutputIndex int
   765  		for vout, txOut := range tx.TxOut {
   766  			if txOut.Value >= int64(shieldedSplitNeeded) && bytes.Equal(txOut.PkScript, pkScript) {
   767  				splitOutput = txOut
   768  				splitOutputIndex = vout
   769  				break
   770  			}
   771  		}
   772  		if splitOutput == nil {
   773  			return nil, nil, 0, fmt.Errorf("split output of size %d not found in transaction %s", shieldedSplitNeeded, txHash)
   774  		}
   775  
   776  		op := btc.NewOutput(txHash, uint32(splitOutputIndex), uint64(splitOutput.Value))
   777  		fundingCoins = map[btc.OutPoint]*btc.UTxO{op.Pt: {
   778  			TxHash:  &op.Pt.TxHash,
   779  			Vout:    op.Pt.Vout,
   780  			Address: toAddrStr,
   781  			Amount:  shieldedSplitNeeded,
   782  		}}
   783  		coins = []asset.Coin{op}
   784  		redeemScripts = []dex.Bytes{nil}
   785  		spents = []*btc.Output{op}
   786  	} else if useSplit {
   787  		// No shielded split needed. Should we do a split to avoid overlock.
   788  		splitTxFees := dexzec.TxFeesZIP317(inputsSize, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
   789  		requiredForOrderWithoutSplit := dexzec.RequiredOrderFunds(ord.Value, uint64(len(coins)), inputsSize, ord.MaxSwapCount)
   790  		excessWithoutSplit := sum - requiredForOrderWithoutSplit
   791  		if splitTxFees >= excessWithoutSplit {
   792  			w.log.Debugf("Skipping split transaction because cost is greater than potential over-lock. "+
   793  				"%s > %s", btcutil.Amount(splitTxFees), btcutil.Amount(excessWithoutSplit))
   794  		} else {
   795  			splitOutputVal := dexzec.RequiredOrderFunds(ord.Value, 1, dexbtc.RedeemP2PKHInputSize, ord.MaxSwapCount)
   796  			transparentSplitFees = splitTxFees
   797  			baseTx, _, _, err := w.fundedTx(spents)
   798  			if err != nil {
   799  				return nil, nil, 0, fmt.Errorf("fundedTx error: %w", err)
   800  			}
   801  
   802  			splitOutputAddr, err := transparentAddress(w, w.addrParams, w.btcParams)
   803  			if err != nil {
   804  				return nil, nil, 0, fmt.Errorf("transparentAddress (0) error: %w", err)
   805  			}
   806  			splitOutputScript, err := txscript.PayToAddrScript(splitOutputAddr)
   807  			if err != nil {
   808  				return nil, nil, 0, fmt.Errorf("split output addr PayToAddrScript error: %w", err)
   809  			}
   810  			baseTx.AddTxOut(wire.NewTxOut(int64(splitOutputVal), splitOutputScript))
   811  
   812  			changeAddr, err := transparentAddress(w, w.addrParams, w.btcParams)
   813  			if err != nil {
   814  				return nil, nil, 0, fmt.Errorf("transparentAddress (1) error: %w", err)
   815  			}
   816  
   817  			splitTx, _, err := w.signTxAndAddChange(baseTx, changeAddr, sum, splitOutputVal, transparentSplitFees)
   818  			if err != nil {
   819  				return nil, nil, 0, fmt.Errorf("signTxAndAddChange error: %v", err)
   820  			}
   821  
   822  			splitTxHash, err := sendRawTransaction(w, splitTx)
   823  			if err != nil {
   824  				return nil, nil, 0, fmt.Errorf("sendRawTransaction error: %w", err)
   825  			}
   826  
   827  			if *splitTxHash != splitTx.TxHash() {
   828  				return nil, nil, 0, errors.New("split tx had unexpected hash")
   829  			}
   830  
   831  			w.log.Debugf("Sent split tx spending %d outputs with a sum value of %d to get a sized output of value %d",
   832  				len(coins), sum, splitOutputVal)
   833  
   834  			op := btc.NewOutput(splitTxHash, 0, splitOutputVal)
   835  
   836  			addrStr, err := dexzec.EncodeAddress(splitOutputAddr, w.addrParams)
   837  			if err != nil {
   838  				return nil, nil, 0, fmt.Errorf("error stringing address %q: %w", splitOutputAddr, err)
   839  			}
   840  
   841  			fundingCoins = map[btc.OutPoint]*btc.UTxO{op.Pt: {
   842  				TxHash:  &op.Pt.TxHash,
   843  				Vout:    op.Pt.Vout,
   844  				Address: addrStr,
   845  				Amount:  splitOutputVal,
   846  			}}
   847  			coins = []asset.Coin{op}
   848  			redeemScripts = []dex.Bytes{nil}
   849  			spents = []*btc.Output{op}
   850  		}
   851  	}
   852  
   853  	w.log.Debugf("Funding %s ZEC order with coins %v worth %s", ordValStr, coins, btcutil.Amount(sum))
   854  
   855  	w.cm.LockOutputsMap(fundingCoins)
   856  	err = lockUnspent(w, false, spents)
   857  	if err != nil {
   858  		return nil, nil, 0, newError(errLockUnspent, "LockUnspent error: %w", err)
   859  	}
   860  	return coins, redeemScripts, shieldedSplitFees + transparentSplitFees, nil
   861  }
   862  
   863  // Redeem sends the redemption transaction, completing the atomic swap.
   864  func (w *zecWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, uint64, error) {
   865  	// Create a transaction that spends the referenced contract.
   866  	tx := dexzec.NewTxFromMsgTx(wire.NewMsgTx(dexzec.VersionNU5), dexzec.MaxExpiryHeight)
   867  	var totalIn uint64
   868  	contracts := make([][]byte, 0, len(form.Redemptions))
   869  	prevScripts := make([][]byte, 0, len(form.Redemptions))
   870  	addresses := make([]btcutil.Address, 0, len(form.Redemptions))
   871  	values := make([]int64, 0, len(form.Redemptions))
   872  	var txInsSize uint64
   873  	for _, r := range form.Redemptions {
   874  		if r.Spends == nil {
   875  			return nil, nil, 0, fmt.Errorf("no audit info")
   876  		}
   877  
   878  		cinfo, err := btc.ConvertAuditInfo(r.Spends, w.decodeAddr, w.btcParams)
   879  		if err != nil {
   880  			return nil, nil, 0, fmt.Errorf("ConvertAuditInfo error: %w", err)
   881  		}
   882  
   883  		// Extract the swap contract recipient and secret hash and check the secret
   884  		// hash against the hash of the provided secret.
   885  		contract := cinfo.Contract()
   886  		_, receiver, _, secretHash, err := dexbtc.ExtractSwapDetails(contract, false /* segwit */, w.btcParams)
   887  		if err != nil {
   888  			return nil, nil, 0, fmt.Errorf("error extracting swap addresses: %w", err)
   889  		}
   890  		checkSecretHash := sha256.Sum256(r.Secret)
   891  		if !bytes.Equal(checkSecretHash[:], secretHash) {
   892  			return nil, nil, 0, fmt.Errorf("secret hash mismatch")
   893  		}
   894  		pkScript, err := w.scriptHashScript(contract)
   895  		if err != nil {
   896  			return nil, nil, 0, fmt.Errorf("error constructs p2sh script: %v", err)
   897  		}
   898  		prevScripts = append(prevScripts, pkScript)
   899  		addresses = append(addresses, receiver)
   900  		contracts = append(contracts, contract)
   901  		txIn := wire.NewTxIn(cinfo.Output.WireOutPoint(), nil, nil)
   902  		tx.AddTxIn(txIn)
   903  		values = append(values, int64(cinfo.Output.Val))
   904  		totalIn += cinfo.Output.Val
   905  		txInsSize = tx.SerializeSize()
   906  	}
   907  
   908  	txInsSize += uint64(wire.VarIntSerializeSize(uint64(len(tx.TxIn))))
   909  	txOutsSize := uint64(1 + dexbtc.P2PKHOutputSize)
   910  	fee := dexzec.TxFeesZIP317(txInsSize, txOutsSize, 0, 0, 0, 0)
   911  
   912  	// Send the funds back to the exchange wallet.
   913  	redeemAddr, err := transparentAddress(w, w.addrParams, w.btcParams)
   914  	if err != nil {
   915  		return nil, nil, 0, fmt.Errorf("error getting new address from the wallet: %w", err)
   916  	}
   917  	pkScript, err := txscript.PayToAddrScript(redeemAddr)
   918  	if err != nil {
   919  		return nil, nil, 0, fmt.Errorf("error creating change script: %w", err)
   920  	}
   921  	val := totalIn - fee
   922  	txOut := wire.NewTxOut(int64(val), pkScript)
   923  	// One last check for dust.
   924  	if isDust(val, dexbtc.P2PKHOutputSize) {
   925  		return nil, nil, 0, fmt.Errorf("redeem output is dust")
   926  	}
   927  	tx.AddTxOut(txOut)
   928  
   929  	for i, r := range form.Redemptions {
   930  		contract := contracts[i]
   931  		addr := addresses[i]
   932  
   933  		addrStr, err := dexzec.EncodeAddress(addr, w.addrParams)
   934  		if err != nil {
   935  			return nil, nil, 0, fmt.Errorf("EncodeAddress error: %w", err)
   936  		}
   937  
   938  		privKey, err := dumpPrivKey(w, addrStr)
   939  		if err != nil {
   940  			return nil, nil, 0, fmt.Errorf("dumpPrivKey error: %w", err)
   941  		}
   942  		defer privKey.Zero()
   943  
   944  		redeemSig, err := signTx(tx.MsgTx, i, contract, txscript.SigHashAll, privKey, values, prevScripts)
   945  		if err != nil {
   946  			return nil, nil, 0, fmt.Errorf("tx signing error: %w", err)
   947  		}
   948  		redeemPubKey := privKey.PubKey().SerializeCompressed()
   949  
   950  		tx.TxIn[i].SignatureScript, err = dexbtc.RedeemP2SHContract(contract, redeemSig, redeemPubKey, r.Secret)
   951  		if err != nil {
   952  			return nil, nil, 0, fmt.Errorf("RedeemP2SHContract error: %w", err)
   953  		}
   954  	}
   955  
   956  	// Send the transaction.
   957  	txHash, err := sendRawTransaction(w, tx)
   958  	if err != nil {
   959  		return nil, nil, 0, fmt.Errorf("error sending tx: %w", err)
   960  	}
   961  
   962  	w.addTxToHistory(&asset.WalletTransaction{
   963  		Type:   asset.Redeem,
   964  		ID:     txHash.String(),
   965  		Amount: totalIn,
   966  		Fees:   fee,
   967  	}, txHash, true)
   968  
   969  	// Log the change output.
   970  	coinIDs := make([]dex.Bytes, 0, len(form.Redemptions))
   971  	for i := range form.Redemptions {
   972  		coinIDs = append(coinIDs, btc.ToCoinID(txHash, uint32(i)))
   973  	}
   974  	return coinIDs, btc.NewOutput(txHash, 0, uint64(txOut.Value)), fee, nil
   975  }
   976  
   977  // scriptHashAddress returns a new p2sh address.
   978  func (w *zecWallet) scriptHashAddress(contract []byte) (btcutil.Address, error) {
   979  	return btcutil.NewAddressScriptHash(contract, w.btcParams)
   980  }
   981  
   982  func (w *zecWallet) scriptHashScript(contract []byte) ([]byte, error) {
   983  	addr, err := w.scriptHashAddress(contract)
   984  	if err != nil {
   985  		return nil, err
   986  	}
   987  	return txscript.PayToAddrScript(addr)
   988  }
   989  
   990  func (w *zecWallet) ReturnCoins(unspents asset.Coins) error {
   991  	return w.cm.ReturnCoins(unspents)
   992  }
   993  
   994  func (w *zecWallet) MaxOrder(ord *asset.MaxOrderForm) (*asset.SwapEstimate, error) {
   995  	_, _, maxEst, err := w.maxOrder(ord.LotSize, ord.FeeSuggestion, ord.MaxFeeRate)
   996  	return maxEst, err
   997  }
   998  
   999  func (w *zecWallet) maxOrder(lotSize, feeSuggestion, maxFeeRate uint64) (utxos []*btc.CompositeUTXO, bals *balances, est *asset.SwapEstimate, err error) {
  1000  	if lotSize == 0 {
  1001  		return nil, nil, nil, errors.New("cannot divide by lotSize zero")
  1002  	}
  1003  
  1004  	utxos, _, avail, err := w.cm.SpendableUTXOs(0)
  1005  	if err != nil {
  1006  		return nil, nil, nil, fmt.Errorf("error parsing unspent outputs: %w", err)
  1007  	}
  1008  
  1009  	bals, err = w.balances()
  1010  	if err != nil {
  1011  		return nil, nil, nil, fmt.Errorf("error getting current balance: %w", err)
  1012  	}
  1013  
  1014  	avail += bals.orchard.avail
  1015  
  1016  	// Find the max lots we can fund.
  1017  	maxLotsInt := int(avail / lotSize)
  1018  	oneLotTooMany := sort.Search(maxLotsInt+1, func(lots int) bool {
  1019  		_, _, _, err = w.estimateSwap(uint64(lots), lotSize, feeSuggestion, maxFeeRate, utxos, bals.orchard, true)
  1020  		// The only failure mode of estimateSwap -> zec.fund is when there is
  1021  		// not enough funds.
  1022  		return err != nil
  1023  	})
  1024  
  1025  	maxLots := uint64(oneLotTooMany - 1)
  1026  	if oneLotTooMany == 0 {
  1027  		maxLots = 0
  1028  	}
  1029  
  1030  	if maxLots > 0 {
  1031  		est, _, _, err = w.estimateSwap(maxLots, lotSize, feeSuggestion, maxFeeRate, utxos, bals.orchard, true)
  1032  		return utxos, bals, est, err
  1033  	}
  1034  
  1035  	return utxos, bals, &asset.SwapEstimate{FeeReservesPerLot: feeReservesPerLot}, nil
  1036  }
  1037  
  1038  // estimateSwap prepares an *asset.SwapEstimate.
  1039  func (w *zecWallet) estimateSwap(
  1040  	lots, lotSize, feeSuggestion, maxFeeRate uint64,
  1041  	utxos []*btc.CompositeUTXO,
  1042  	orchardBal *balanceBreakdown,
  1043  	trySplit bool,
  1044  ) (*asset.SwapEstimate, bool /*split used*/, uint64 /*amt locked*/, error) {
  1045  
  1046  	var avail uint64
  1047  	for _, utxo := range utxos {
  1048  		avail += utxo.Amount
  1049  	}
  1050  	val := lots * lotSize
  1051  	sum, inputsSize, shieldedSplitNeeded, shieldedSplitFees, coins, _, _, _, err := w.fund(val, lots, utxos, orchardBal)
  1052  	if err != nil {
  1053  		return nil, false, 0, fmt.Errorf("error funding swap value %s: %w", btcutil.Amount(val), err)
  1054  	}
  1055  
  1056  	if shieldedSplitNeeded > 0 {
  1057  		// DRAFT TODO: Do we need to "lock" anything here?
  1058  		const splitLocked = 0
  1059  		return &asset.SwapEstimate{
  1060  			Lots:               lots,
  1061  			Value:              val,
  1062  			MaxFees:            shieldedSplitFees,
  1063  			RealisticBestCase:  shieldedSplitFees,
  1064  			RealisticWorstCase: shieldedSplitFees,
  1065  			FeeReservesPerLot:  feeReservesPerLot,
  1066  		}, true, splitLocked, nil
  1067  	}
  1068  
  1069  	digestInputs := func(inputsSize uint64, withSplit bool) (reqFunds, maxFees, estHighFees, estLowFees uint64) {
  1070  		n := uint64(len(coins))
  1071  
  1072  		splitFees := shieldedSplitFees
  1073  		if withSplit {
  1074  			splitInputsSize := inputsSize + uint64(wire.VarIntSerializeSize(n))
  1075  			splitOutputsSize := uint64(2*dexbtc.P2PKHOutputSize + 1)
  1076  			splitFees = dexzec.TxFeesZIP317(splitInputsSize, splitOutputsSize, 0, 0, 0, 0)
  1077  			inputsSize = dexbtc.RedeemP2PKHInputSize
  1078  			n = 1
  1079  		}
  1080  
  1081  		firstSwapInputsSize := inputsSize + uint64(wire.VarIntSerializeSize(n))
  1082  		singleOutputSize := uint64(dexbtc.P2SHOutputSize+dexbtc.P2PKHOutputSize) + 1
  1083  		estLowFees = dexzec.TxFeesZIP317(firstSwapInputsSize, singleOutputSize, 0, 0, 0, 0)
  1084  
  1085  		req := dexzec.RequiredOrderFunds(val, n, inputsSize, lots)
  1086  		maxFees = req - val + splitFees
  1087  		estHighFees = maxFees
  1088  		return
  1089  	}
  1090  
  1091  	// Math for split transactions is a little different.
  1092  	if trySplit {
  1093  		baggage := dexzec.TxFeesZIP317(inputsSize, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
  1094  		excess := sum - dexzec.RequiredOrderFunds(val, uint64(len(coins)), inputsSize, lots)
  1095  		if baggage >= excess {
  1096  			reqFunds, maxFees, estHighFees, estLowFees := digestInputs(inputsSize, true)
  1097  			return &asset.SwapEstimate{
  1098  				Lots:               lots,
  1099  				Value:              val,
  1100  				MaxFees:            maxFees,
  1101  				RealisticBestCase:  estLowFees,
  1102  				RealisticWorstCase: estHighFees,
  1103  				FeeReservesPerLot:  feeReservesPerLot,
  1104  			}, true, reqFunds, nil
  1105  		}
  1106  	}
  1107  
  1108  	_, maxFees, estHighFees, estLowFees := digestInputs(inputsSize, false)
  1109  	return &asset.SwapEstimate{
  1110  		Lots:               lots,
  1111  		Value:              val,
  1112  		MaxFees:            maxFees,
  1113  		RealisticBestCase:  estLowFees,
  1114  		RealisticWorstCase: estHighFees,
  1115  		FeeReservesPerLot:  feeReservesPerLot,
  1116  	}, false, sum, nil
  1117  }
  1118  
  1119  // PreSwap get order estimates and order options based on the available funds
  1120  // and user-selected options.
  1121  func (w *zecWallet) PreSwap(req *asset.PreSwapForm) (*asset.PreSwap, error) {
  1122  	// Start with the maxOrder at the default configuration. This gets us the
  1123  	// utxo set, the network fee rate, and the wallet's maximum order size. The
  1124  	// utxo set can then be used repeatedly in estimateSwap at virtually zero
  1125  	// cost since there are no more RPC calls.
  1126  	utxos, bals, maxEst, err := w.maxOrder(req.LotSize, req.FeeSuggestion, req.MaxFeeRate)
  1127  	if err != nil {
  1128  		return nil, err
  1129  	}
  1130  	if maxEst.Lots < req.Lots {
  1131  		return nil, fmt.Errorf("%d lots available for %d-lot order", maxEst.Lots, req.Lots)
  1132  	}
  1133  
  1134  	// Load the user's selected order-time options.
  1135  	customCfg := new(swapOptions)
  1136  	err = config.Unmapify(req.SelectedOptions, customCfg)
  1137  	if err != nil {
  1138  		return nil, fmt.Errorf("error parsing selected swap options: %w", err)
  1139  	}
  1140  
  1141  	// Parse the configured split transaction.
  1142  	useSplit := w.useSplitTx()
  1143  	if customCfg.Split != nil {
  1144  		useSplit = *customCfg.Split
  1145  	}
  1146  
  1147  	// Always offer the split option, even for non-standing orders since
  1148  	// immediately spendable change many be desirable regardless.
  1149  	opts := []*asset.OrderOption{w.splitOption(req, utxos, bals.orchard)}
  1150  
  1151  	est, _, _, err := w.estimateSwap(req.Lots, req.LotSize, req.FeeSuggestion, req.MaxFeeRate, utxos, bals.orchard, useSplit)
  1152  	if err != nil {
  1153  		return nil, err
  1154  	}
  1155  	return &asset.PreSwap{
  1156  		Estimate: est, // may be nil so we can present options, which in turn affect estimate feasibility
  1157  		Options:  opts,
  1158  	}, nil
  1159  }
  1160  
  1161  // splitOption constructs an *asset.OrderOption with customized text based on the
  1162  // difference in fees between the configured and test split condition.
  1163  func (w *zecWallet) splitOption(req *asset.PreSwapForm, utxos []*btc.CompositeUTXO, orchardBal *balanceBreakdown) *asset.OrderOption {
  1164  	opt := &asset.OrderOption{
  1165  		ConfigOption: asset.ConfigOption{
  1166  			Key:           "swapsplit",
  1167  			DisplayName:   "Pre-size Funds",
  1168  			IsBoolean:     true,
  1169  			DefaultValue:  w.useSplitTx(), // not nil interface
  1170  			ShowByDefault: true,
  1171  		},
  1172  		Boolean: &asset.BooleanConfig{},
  1173  	}
  1174  
  1175  	noSplitEst, _, noSplitLocked, err := w.estimateSwap(req.Lots, req.LotSize,
  1176  		req.FeeSuggestion, req.MaxFeeRate, utxos, orchardBal, false)
  1177  	if err != nil {
  1178  		w.log.Errorf("estimateSwap (no split) error: %v", err)
  1179  		opt.Boolean.Reason = fmt.Sprintf("estimate without a split failed with \"%v\"", err)
  1180  		return opt // utility and overlock report unavailable, but show the option
  1181  	}
  1182  	splitEst, splitUsed, splitLocked, err := w.estimateSwap(req.Lots, req.LotSize,
  1183  		req.FeeSuggestion, req.MaxFeeRate, utxos, orchardBal, true)
  1184  	if err != nil {
  1185  		w.log.Errorf("estimateSwap (with split) error: %v", err)
  1186  		opt.Boolean.Reason = fmt.Sprintf("estimate with a split failed with \"%v\"", err)
  1187  		return opt // utility and overlock report unavailable, but show the option
  1188  	}
  1189  
  1190  	if !splitUsed || splitLocked >= noSplitLocked { // locked check should be redundant
  1191  		opt.Boolean.Reason = "avoids no ZEC overlock for this order (ignored)"
  1192  		opt.Description = "A split transaction for this order avoids no ZEC overlock, " +
  1193  			"but adds additional fees."
  1194  		opt.DefaultValue = false
  1195  		return opt // not enabled by default, but explain why
  1196  	}
  1197  
  1198  	overlock := noSplitLocked - splitLocked
  1199  	pctChange := (float64(splitEst.RealisticWorstCase)/float64(noSplitEst.RealisticWorstCase) - 1) * 100
  1200  	if pctChange > 1 {
  1201  		opt.Boolean.Reason = fmt.Sprintf("+%d%% fees, avoids %s ZEC overlock", int(math.Round(pctChange)), btcutil.Amount(overlock).String())
  1202  	} else {
  1203  		opt.Boolean.Reason = fmt.Sprintf("+%.1f%% fees, avoids %s ZEC overlock", pctChange, btcutil.Amount(overlock).String())
  1204  	}
  1205  
  1206  	xtraFees := splitEst.RealisticWorstCase - noSplitEst.RealisticWorstCase
  1207  	opt.Description = fmt.Sprintf("Using a split transaction to prevent temporary overlock of %s ZEC, but for additional fees of %s ZEC",
  1208  		btcutil.Amount(overlock).String(), btcutil.Amount(xtraFees).String())
  1209  
  1210  	return opt
  1211  }
  1212  
  1213  // SingleLotSwapRefundFees returns the fees for a swap and refund transaction
  1214  // for a single lot.
  1215  func (w *zecWallet) SingleLotSwapRefundFees(_ uint32, feeSuggestion uint64, useSafeTxSize bool) (swapFees uint64, refundFees uint64, err error) {
  1216  	var numInputs uint64
  1217  	if useSafeTxSize {
  1218  		numInputs = 12
  1219  	} else {
  1220  		numInputs = 2
  1221  	}
  1222  
  1223  	inputsSize := numInputs*dexbtc.RedeemP2PKHInputSize + 1
  1224  	outputsSize := uint64(dexbtc.P2PKHOutputSize + 1)
  1225  	swapFees = dexzec.TxFeesZIP317(inputsSize, outputsSize, 0, 0, 0, 0)
  1226  	refundFees = dexzec.TxFeesZIP317(dexbtc.RefundSigScriptSize+1, dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
  1227  
  1228  	return swapFees, refundFees, nil
  1229  }
  1230  
  1231  func (w *zecWallet) PreRedeem(form *asset.PreRedeemForm) (*asset.PreRedeem, error) {
  1232  	return w.preRedeem(form.Lots, form.FeeSuggestion, form.SelectedOptions)
  1233  }
  1234  
  1235  func (w *zecWallet) preRedeem(numLots, _ uint64, options map[string]string) (*asset.PreRedeem, error) {
  1236  	singleInputsSize := uint64(dexbtc.TxInOverhead + dexbtc.RedeemSwapSigScriptSize + 1)
  1237  	singleOutputsSize := uint64(dexbtc.P2PKHOutputSize + 1)
  1238  	singleMatchFees := dexzec.TxFeesZIP317(singleInputsSize, singleOutputsSize, 0, 0, 0, 0)
  1239  	return &asset.PreRedeem{
  1240  		Estimate: &asset.RedeemEstimate{
  1241  			RealisticWorstCase: singleMatchFees * numLots,
  1242  			RealisticBestCase:  singleMatchFees,
  1243  		},
  1244  	}, nil
  1245  }
  1246  
  1247  func (w *zecWallet) SingleLotRedeemFees(_ uint32, feeSuggestion uint64) (uint64, error) {
  1248  	singleInputsSize := uint64(dexbtc.TxInOverhead + dexbtc.RedeemSwapSigScriptSize + 1)
  1249  	singleOutputsSize := uint64(dexbtc.P2PKHOutputSize + 1)
  1250  	return dexzec.TxFeesZIP317(singleInputsSize, singleOutputsSize, 0, 0, 0, 0), nil
  1251  }
  1252  
  1253  // FundingCoins gets funding coins for the coin IDs. The coins are locked. This
  1254  // method might be called to reinitialize an order from data stored externally.
  1255  // This method will only return funding coins, e.g. unspent transaction outputs.
  1256  func (w *zecWallet) FundingCoins(ids []dex.Bytes) (asset.Coins, error) {
  1257  	return w.cm.FundingCoins(ids)
  1258  }
  1259  
  1260  func decodeCoinID(coinID dex.Bytes) (*chainhash.Hash, uint32, error) {
  1261  	if len(coinID) != 36 {
  1262  		return nil, 0, fmt.Errorf("coin ID wrong length. expected 36, got %d", len(coinID))
  1263  	}
  1264  	var txHash chainhash.Hash
  1265  	copy(txHash[:], coinID[:32])
  1266  	return &txHash, binary.BigEndian.Uint32(coinID[32:]), nil
  1267  }
  1268  
  1269  // fundedTx creates and returns a new MsgTx with the provided coins as inputs.
  1270  func (w *zecWallet) fundedTx(coins []*btc.Output) (*dexzec.Tx, uint64, []btc.OutPoint, error) {
  1271  	baseTx := zecTx(wire.NewMsgTx(dexzec.VersionNU5))
  1272  	totalIn, pts, err := w.addInputsToTx(baseTx, coins)
  1273  	if err != nil {
  1274  		return nil, 0, nil, err
  1275  	}
  1276  	return baseTx, totalIn, pts, nil
  1277  }
  1278  
  1279  func (w *zecWallet) addInputsToTx(tx *dexzec.Tx, coins []*btc.Output) (uint64, []btc.OutPoint, error) {
  1280  	var totalIn uint64
  1281  	// Add the funding utxos.
  1282  	pts := make([]btc.OutPoint, 0, len(coins))
  1283  	for _, op := range coins {
  1284  		totalIn += op.Val
  1285  		txIn := wire.NewTxIn(op.WireOutPoint(), []byte{}, nil)
  1286  		tx.AddTxIn(txIn)
  1287  		pts = append(pts, op.Pt)
  1288  	}
  1289  	return totalIn, pts, nil
  1290  }
  1291  
  1292  // signTxAndAddChange signs the passed tx and adds a change output if the change
  1293  // wouldn't be dust. Returns but does NOT broadcast the signed tx.
  1294  func (w *zecWallet) signTxAndAddChange(baseTx *dexzec.Tx, addr btcutil.Address, totalIn, totalOut, fees uint64) (*dexzec.Tx, *btc.Output, error) {
  1295  
  1296  	makeErr := func(s string, a ...any) (*dexzec.Tx, *btc.Output, error) {
  1297  		return nil, nil, fmt.Errorf(s, a...)
  1298  	}
  1299  
  1300  	// Sign the transaction to get an initial size estimate and calculate whether
  1301  	// a change output would be dust.
  1302  	remaining := totalIn - totalOut
  1303  	if fees > remaining {
  1304  		b, _ := baseTx.Bytes()
  1305  		return makeErr("not enough funds to cover minimum fee rate. %s < %s, raw tx: %x",
  1306  			btcutil.Amount(totalIn), btcutil.Amount(fees+totalOut), b)
  1307  	}
  1308  
  1309  	// Create a change output.
  1310  	changeScript, err := txscript.PayToAddrScript(addr)
  1311  	if err != nil {
  1312  		return makeErr("error creating change script: %v", err)
  1313  	}
  1314  
  1315  	changeIdx := len(baseTx.TxOut)
  1316  	changeValue := remaining - fees
  1317  	changeOutput := wire.NewTxOut(int64(changeValue), changeScript)
  1318  
  1319  	// If the change is not dust, recompute the signed txn size and iterate on
  1320  	// the fees vs. change amount.
  1321  	changeAdded := !isDust(changeValue, dexbtc.P2PKHOutputSize)
  1322  	if changeAdded {
  1323  		// Add the change output.
  1324  		baseTx.AddTxOut(changeOutput)
  1325  	} else {
  1326  		w.log.Debugf("Foregoing change worth up to %v because it is dust", changeOutput.Value)
  1327  	}
  1328  
  1329  	msgTx, err := signTxByRPC(w, baseTx)
  1330  	if err != nil {
  1331  		b, _ := baseTx.Bytes()
  1332  		return makeErr("signing error: %v, raw tx: %x", err, b)
  1333  	}
  1334  
  1335  	txHash := msgTx.TxHash()
  1336  	var change *btc.Output
  1337  	if changeAdded {
  1338  		change = btc.NewOutput(&txHash, uint32(changeIdx), uint64(changeOutput.Value))
  1339  	}
  1340  
  1341  	return msgTx, change, nil
  1342  }
  1343  
  1344  type balanceBreakdown struct {
  1345  	avail     uint64
  1346  	maturing  uint64
  1347  	noteCount uint32
  1348  }
  1349  
  1350  type balances struct {
  1351  	orchard     *balanceBreakdown
  1352  	sapling     uint64
  1353  	transparent *balanceBreakdown
  1354  }
  1355  
  1356  func (b *balances) available() uint64 {
  1357  	return b.orchard.avail + b.transparent.avail
  1358  }
  1359  
  1360  func (w *zecWallet) balances() (*balances, error) {
  1361  	zeroConf, err := zGetBalanceForAccount(w, shieldedAcctNumber, 0)
  1362  	if err != nil {
  1363  		return nil, fmt.Errorf("z_getbalanceforaccount (0) error: %w", err)
  1364  	}
  1365  	mature, err := zGetBalanceForAccount(w, shieldedAcctNumber, minOrchardConfs)
  1366  	if err != nil {
  1367  		return nil, fmt.Errorf("z_getbalanceforaccount (3) error: %w", err)
  1368  	}
  1369  	noteCounts, err := zGetNotesCount(w)
  1370  	if err != nil {
  1371  		return nil, fmt.Errorf("z_getnotescount error: %w", err)
  1372  	}
  1373  	return &balances{
  1374  		orchard: &balanceBreakdown{
  1375  			avail:     mature.Orchard,
  1376  			maturing:  zeroConf.Orchard - mature.Orchard,
  1377  			noteCount: noteCounts.Orchard,
  1378  		},
  1379  		sapling: zeroConf.Sapling,
  1380  		transparent: &balanceBreakdown{
  1381  			avail:    mature.Transparent,
  1382  			maturing: zeroConf.Transparent - mature.Transparent,
  1383  		},
  1384  	}, nil
  1385  }
  1386  
  1387  func (w *zecWallet) fund(
  1388  	val, maxSwapCount uint64,
  1389  	utxos []*btc.CompositeUTXO,
  1390  	orchardBal *balanceBreakdown,
  1391  ) (
  1392  	sum, size, shieldedSplitNeeded, shieldedSplitFees uint64,
  1393  	coins asset.Coins,
  1394  	fundingCoins map[btc.OutPoint]*btc.UTxO,
  1395  	redeemScripts []dex.Bytes,
  1396  	spents []*btc.Output,
  1397  	err error,
  1398  ) {
  1399  
  1400  	var shieldedAvail uint64
  1401  	reserves := w.reserves.Load()
  1402  	nActionsOrchard := uint64(orchardBal.noteCount)
  1403  	enough := func(inputCount, inputsSize, sum uint64) (bool, uint64) {
  1404  		req := dexzec.RequiredOrderFunds(val, inputCount, inputsSize, maxSwapCount)
  1405  		if sum >= req {
  1406  			return true, sum - req + shieldedAvail
  1407  		}
  1408  		if shieldedAvail == 0 {
  1409  			return false, 0
  1410  		}
  1411  		shieldedFees := dexzec.TxFeesZIP317(inputsSize+uint64(wire.VarIntSerializeSize(inputCount)), dexbtc.P2PKHOutputSize+1, 0, 0, 0, nActionsOrchard)
  1412  		req = dexzec.RequiredOrderFunds(val, 1, dexbtc.RedeemP2PKHInputSize, maxSwapCount)
  1413  		if sum+shieldedAvail >= req+shieldedFees {
  1414  			return true, sum + shieldedAvail - (req + shieldedFees)
  1415  		}
  1416  		return false, 0
  1417  	}
  1418  
  1419  	coins, fundingCoins, spents, redeemScripts, size, sum, err = w.cm.FundWithUTXOs(utxos, reserves, false, enough)
  1420  	if err == nil {
  1421  		return
  1422  	}
  1423  
  1424  	shieldedAvail = orchardBal.avail
  1425  
  1426  	if shieldedAvail >= reserves {
  1427  		shieldedAvail -= reserves
  1428  		reserves = 0
  1429  	} else {
  1430  		reserves -= shieldedAvail
  1431  		shieldedAvail = 0
  1432  	}
  1433  
  1434  	if shieldedAvail == 0 {
  1435  		err = codedError(errFunding, err)
  1436  		return
  1437  	}
  1438  
  1439  	shieldedSplitNeeded = dexzec.RequiredOrderFunds(val, 1, dexbtc.RedeemP2PKHInputSize, maxSwapCount)
  1440  
  1441  	// If we don't have any utxos see if a straight shielded split will get us there.
  1442  	if len(utxos) == 0 {
  1443  		// Can we do it with just the shielded balance?
  1444  		shieldedSplitFees = dexzec.TxFeesZIP317(0, dexbtc.P2SHOutputSize+1, 0, 0, 0, nActionsOrchard)
  1445  		req := val + shieldedSplitFees
  1446  		if shieldedAvail < req {
  1447  			// err is still the error from the last call to FundWithUTXOs
  1448  			err = codedError(errShieldedFunding, err)
  1449  		} else {
  1450  			err = nil
  1451  		}
  1452  		return
  1453  	}
  1454  
  1455  	// Check with both transparent and orchard funds. (shieldedAvail has been
  1456  	// set, which changes the behavior of enough.
  1457  	coins, fundingCoins, spents, redeemScripts, size, sum, err = w.cm.FundWithUTXOs(utxos, reserves, false, enough)
  1458  	if err != nil {
  1459  		err = codedError(errFunding, err)
  1460  		return
  1461  	}
  1462  
  1463  	req := dexzec.RequiredOrderFunds(val, uint64(len(coins)), size, maxSwapCount)
  1464  	if req > sum {
  1465  		txOutsSize := uint64(dexbtc.P2PKHOutputSize + 1) // 1 for varint
  1466  		shieldedSplitFees = dexzec.TxFeesZIP317(size+uint64(wire.VarIntSerializeSize(uint64(len(coins)))), txOutsSize, 0, 0, 0, nActionsOrchard)
  1467  	}
  1468  
  1469  	if shieldedAvail+sum < shieldedSplitNeeded+shieldedSplitFees {
  1470  		err = newError(errInsufficientBalance, "not enough to cover requested funds. "+
  1471  			"%d available in %d UTXOs, %d available from shielded (after bond reserves), total avail = %d, total needed = %d",
  1472  			sum, len(coins), shieldedAvail, shieldedAvail+sum, shieldedSplitNeeded+shieldedSplitFees)
  1473  		return
  1474  	}
  1475  
  1476  	return
  1477  }
  1478  
  1479  func (w *zecWallet) SetBondReserves(reserves uint64) {
  1480  	w.reserves.Store(reserves)
  1481  }
  1482  
  1483  func (w *zecWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroadcast bool) (*asset.AuditInfo, error) {
  1484  	txHash, vout, err := decodeCoinID(coinID)
  1485  	if err != nil {
  1486  		return nil, err
  1487  	}
  1488  	// Get the receiving address.
  1489  	_, receiver, stamp, secretHash, err := dexbtc.ExtractSwapDetails(contract, false /* segwit */, w.btcParams)
  1490  	if err != nil {
  1491  		return nil, fmt.Errorf("error extracting swap addresses: %w", err)
  1492  	}
  1493  
  1494  	// If no tx data is provided, attempt to get the required data (the txOut)
  1495  	// from the wallet. If this is a full node wallet, a simple gettxout RPC is
  1496  	// sufficient with no pkScript or "since" time. If this is an SPV wallet,
  1497  	// only a confirmed counterparty contract can be located, and only one
  1498  	// within ContractSearchLimit. As such, this mode of operation is not
  1499  	// intended for normal server-coordinated operation.
  1500  	var tx *dexzec.Tx
  1501  	var txOut *wire.TxOut
  1502  	if len(txData) == 0 {
  1503  		// Fall back to gettxout, but we won't have the tx to rebroadcast.
  1504  		txOut, _, err = getTxOut(w, txHash, vout)
  1505  		if err != nil || txOut == nil {
  1506  			return nil, fmt.Errorf("error finding unspent contract: %s:%d : %w", txHash, vout, err)
  1507  		}
  1508  	} else {
  1509  		tx, err = dexzec.DeserializeTx(txData)
  1510  		if err != nil {
  1511  			return nil, fmt.Errorf("coin not found, and error encountered decoding tx data: %v", err)
  1512  		}
  1513  		if len(tx.TxOut) <= int(vout) {
  1514  			return nil, fmt.Errorf("specified output %d not found in decoded tx %s", vout, txHash)
  1515  		}
  1516  		txOut = tx.TxOut[vout]
  1517  	}
  1518  
  1519  	// Check for standard P2SH. NOTE: btc.scriptHashScript(contract) should
  1520  	// equal txOut.PkScript. All we really get from the TxOut is the *value*.
  1521  	scriptClass, addrs, numReq, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams)
  1522  	if err != nil {
  1523  		return nil, fmt.Errorf("error extracting script addresses from '%x': %w", txOut.PkScript, err)
  1524  	}
  1525  	if scriptClass != txscript.ScriptHashTy {
  1526  		return nil, fmt.Errorf("unexpected script class. expected %s, got %s", txscript.ScriptHashTy, scriptClass)
  1527  	}
  1528  	// Compare the contract hash to the P2SH address.
  1529  	contractHash := btcutil.Hash160(contract)
  1530  	// These last two checks are probably overkill.
  1531  	if numReq != 1 {
  1532  		return nil, fmt.Errorf("unexpected number of signatures expected for P2SH script: %d", numReq)
  1533  	}
  1534  	if len(addrs) != 1 {
  1535  		return nil, fmt.Errorf("unexpected number of addresses for P2SH script: %d", len(addrs))
  1536  	}
  1537  
  1538  	addr := addrs[0]
  1539  	if !bytes.Equal(contractHash, addr.ScriptAddress()) {
  1540  		return nil, fmt.Errorf("contract hash doesn't match script address. %x != %x",
  1541  			contractHash, addr.ScriptAddress())
  1542  	}
  1543  
  1544  	// Broadcast the transaction, but do not block because this is not required
  1545  	// and does not affect the audit result.
  1546  	if rebroadcast && tx != nil {
  1547  		go func() {
  1548  			if hashSent, err := sendRawTransaction(w, tx); err != nil {
  1549  				w.log.Debugf("Rebroadcasting counterparty contract %v (THIS MAY BE NORMAL): %v", txHash, err)
  1550  			} else if !hashSent.IsEqual(txHash) {
  1551  				w.log.Errorf("Counterparty contract %v was rebroadcast as %v!", txHash, hashSent)
  1552  			}
  1553  		}()
  1554  	}
  1555  
  1556  	addrStr, err := dexzec.EncodeAddress(receiver, w.addrParams)
  1557  	if err != nil {
  1558  		w.log.Errorf("Failed to stringify receiver address %v (default): %v", receiver, err)
  1559  	}
  1560  
  1561  	return &asset.AuditInfo{
  1562  		Coin:       btc.NewOutput(txHash, vout, uint64(txOut.Value)),
  1563  		Recipient:  addrStr,
  1564  		Contract:   contract,
  1565  		SecretHash: secretHash,
  1566  		Expiration: time.Unix(int64(stamp), 0).UTC(),
  1567  	}, nil
  1568  }
  1569  
  1570  func (w *zecWallet) ConfirmRedemption(coinID dex.Bytes, redemption *asset.Redemption, feeSuggestion uint64) (*asset.ConfirmRedemptionStatus, error) {
  1571  	txHash, _, err := decodeCoinID(coinID)
  1572  	if err != nil {
  1573  		return nil, err
  1574  	}
  1575  
  1576  	tx, err := getWalletTransaction(w, txHash)
  1577  	// redemption transaction found, return its confirms.
  1578  	//
  1579  	// TODO: Investigate the case where this redeem has been sitting in the
  1580  	// mempool for a long amount of time, possibly requiring some action by
  1581  	// us to get it unstuck.
  1582  	if err == nil {
  1583  		if tx.Confirmations < 0 {
  1584  			tx.Confirmations = 0
  1585  		}
  1586  		return &asset.ConfirmRedemptionStatus{
  1587  			Confs:  uint64(tx.Confirmations),
  1588  			Req:    requiredRedeemConfirms,
  1589  			CoinID: coinID,
  1590  		}, nil
  1591  	}
  1592  
  1593  	// Redemption transaction is missing from the point of view of our node!
  1594  	// Unlikely, but possible it was redeemed by another transaction. Check
  1595  	// if the contract is still an unspent output.
  1596  
  1597  	swapHash, vout, err := decodeCoinID(redemption.Spends.Coin.ID())
  1598  	if err != nil {
  1599  		return nil, err
  1600  	}
  1601  
  1602  	utxo, _, err := getTxOut(w, swapHash, vout)
  1603  	if err != nil {
  1604  		return nil, newError(errNoTx, "error finding unspent contract %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err)
  1605  	}
  1606  	if utxo == nil {
  1607  		// TODO: Spent, but by who. Find the spending tx.
  1608  		w.log.Warnf("Contract coin %v with swap hash %v vout %d spent by someone but not sure who.", redemption.Spends.Coin.ID(), swapHash, vout)
  1609  		// Incorrect, but we will be in a loop of erroring if we don't
  1610  		// return something.
  1611  		return &asset.ConfirmRedemptionStatus{
  1612  			Confs:  requiredRedeemConfirms,
  1613  			Req:    requiredRedeemConfirms,
  1614  			CoinID: coinID,
  1615  		}, nil
  1616  	}
  1617  
  1618  	// The contract has not yet been redeemed, but it seems the redeeming
  1619  	// tx has disappeared. Assume the fee was too low at the time and it
  1620  	// was eventually purged from the mempool. Attempt to redeem again with
  1621  	// a currently reasonable fee.
  1622  
  1623  	form := &asset.RedeemForm{
  1624  		Redemptions: []*asset.Redemption{redemption},
  1625  	}
  1626  	_, coin, _, err := w.Redeem(form)
  1627  	if err != nil {
  1628  		return nil, fmt.Errorf("unable to re-redeem %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err)
  1629  	}
  1630  	return &asset.ConfirmRedemptionStatus{
  1631  		Confs:  0,
  1632  		Req:    requiredRedeemConfirms,
  1633  		CoinID: coin.ID(),
  1634  	}, nil
  1635  }
  1636  
  1637  func (w *zecWallet) ContractLockTimeExpired(ctx context.Context, contract dex.Bytes) (bool, time.Time, error) {
  1638  	_, _, locktime, _, err := dexbtc.ExtractSwapDetails(contract, false /* segwit */, w.btcParams)
  1639  	if err != nil {
  1640  		return false, time.Time{}, fmt.Errorf("error extracting contract locktime: %w", err)
  1641  	}
  1642  	contractExpiry := time.Unix(int64(locktime), 0).UTC()
  1643  	expired, err := w.LockTimeExpired(ctx, contractExpiry)
  1644  	if err != nil {
  1645  		return false, time.Time{}, err
  1646  	}
  1647  	return expired, contractExpiry, nil
  1648  }
  1649  
  1650  type depositAddressJSON struct {
  1651  	Unified     string `json:"unified"`
  1652  	Transparent string `json:"transparent"`
  1653  	Orchard     string `json:"orchard"`
  1654  	Sapling     string `json:"sapling,omitempty"`
  1655  }
  1656  
  1657  func (w *zecWallet) DepositAddress() (string, error) {
  1658  	addrRes, err := zGetAddressForAccount(w, shieldedAcctNumber, []string{transparentAddressType, orchardAddressType})
  1659  	if err != nil {
  1660  		return "", err
  1661  	}
  1662  	receivers, err := zGetUnifiedReceivers(w, addrRes.Address)
  1663  	if err != nil {
  1664  		return "", err
  1665  	}
  1666  	b, err := json.Marshal(&depositAddressJSON{
  1667  		Unified:     addrRes.Address,
  1668  		Transparent: receivers.Transparent,
  1669  		Orchard:     receivers.Orchard,
  1670  		Sapling:     receivers.Sapling,
  1671  	})
  1672  	if err != nil {
  1673  		return "", fmt.Errorf("error encoding address JSON: %w", err)
  1674  	}
  1675  	return depositAddrPrefix + string(b), nil
  1676  
  1677  }
  1678  
  1679  func (w *zecWallet) NewAddress() (string, error) {
  1680  	return w.DepositAddress()
  1681  }
  1682  
  1683  func (w *zecWallet) FindRedemption(ctx context.Context, coinID, contract dex.Bytes) (redemptionCoin, secret dex.Bytes, err error) {
  1684  	return w.rf.FindRedemption(ctx, coinID)
  1685  }
  1686  
  1687  func (w *zecWallet) FundMultiOrder(mo *asset.MultiOrder, maxLock uint64) (coins []asset.Coins, redeemScripts [][]dex.Bytes, fundingFees uint64, err error) {
  1688  	w.log.Debugf("Attempting to fund a multi-order for ZEC")
  1689  
  1690  	var totalRequiredForOrders uint64
  1691  	var swapInputSize uint64 = dexbtc.RedeemP2PKHInputSize
  1692  	for _, v := range mo.Values {
  1693  		if v.Value == 0 {
  1694  			return nil, nil, 0, newError(errBadInput, "cannot fund value = 0")
  1695  		}
  1696  		if v.MaxSwapCount == 0 {
  1697  			return nil, nil, 0, fmt.Errorf("cannot fund zero-lot order")
  1698  		}
  1699  		req := dexzec.RequiredOrderFunds(v.Value, 1, swapInputSize, v.MaxSwapCount)
  1700  		totalRequiredForOrders += req
  1701  	}
  1702  
  1703  	if maxLock < totalRequiredForOrders && maxLock != 0 {
  1704  		return nil, nil, 0, newError(errMaxLock, "maxLock < totalRequiredForOrders (%d < %d)", maxLock, totalRequiredForOrders)
  1705  	}
  1706  
  1707  	bal, err := w.Balance()
  1708  	if err != nil {
  1709  		return nil, nil, 0, newError(errFunding, "error getting wallet balance: %w", err)
  1710  	}
  1711  	if bal.Available < totalRequiredForOrders {
  1712  		return nil, nil, 0, newError(errInsufficientBalance, "insufficient funds. %d < %d", bal.Available, totalRequiredForOrders)
  1713  	}
  1714  
  1715  	reserves := w.reserves.Load()
  1716  
  1717  	const multiSplitAllowed = true
  1718  
  1719  	coins, redeemScripts, fundingCoins, spents, err := w.cm.FundMultiBestEffort(reserves, maxLock, mo.Values, mo.MaxFeeRate, multiSplitAllowed)
  1720  	if err != nil {
  1721  		return nil, nil, 0, codedError(errFunding, err)
  1722  	}
  1723  	if len(coins) == len(mo.Values) {
  1724  		w.cm.LockOutputsMap(fundingCoins)
  1725  		lockUnspent(w, false, spents)
  1726  		return coins, redeemScripts, 0, nil
  1727  	}
  1728  
  1729  	recips := make([]*zSendManyRecipient, len(mo.Values))
  1730  	addrs := make([]string, len(mo.Values))
  1731  	orderReqs := make([]uint64, len(mo.Values))
  1732  	var txWasBroadcast bool
  1733  	defer func() {
  1734  		if txWasBroadcast || len(addrs) == 0 {
  1735  			return
  1736  		}
  1737  		w.ar.ReturnAddresses(addrs)
  1738  	}()
  1739  	for i, v := range mo.Values {
  1740  		addr, err := w.recyclableAddress()
  1741  		if err != nil {
  1742  			return nil, nil, 0, fmt.Errorf("error getting address for split tx: %v", err)
  1743  		}
  1744  		orderReqs[i] = dexzec.RequiredOrderFunds(v.Value, 1, dexbtc.RedeemP2PKHInputSize, v.MaxSwapCount)
  1745  		addrs[i] = addr
  1746  		recips[i] = &zSendManyRecipient{Address: addr, Amount: btcutil.Amount(orderReqs[i]).ToBTC()}
  1747  	}
  1748  
  1749  	txHash, err := w.sendManyShielded(recips)
  1750  	if err != nil {
  1751  		return nil, nil, 0, fmt.Errorf("sendManyShielded error: %w", err)
  1752  	}
  1753  
  1754  	tx, err := getTransaction(w, txHash)
  1755  	if err != nil {
  1756  		return nil, nil, 0, fmt.Errorf("error retreiving split transaction %s: %w", txHash, err)
  1757  	}
  1758  
  1759  	txWasBroadcast = true
  1760  
  1761  	fundingFees = tx.RequiredTxFeesZIP317()
  1762  
  1763  	txOuts := make(map[uint32]*wire.TxOut, len(mo.Values))
  1764  	for vout, txOut := range tx.TxOut {
  1765  		txOuts[uint32(vout)] = txOut
  1766  	}
  1767  
  1768  	coins = make([]asset.Coins, len(mo.Values))
  1769  	utxos := make([]*btc.UTxO, len(mo.Values))
  1770  	ops := make([]*btc.Output, len(mo.Values))
  1771  	redeemScripts = make([][]dex.Bytes, len(mo.Values))
  1772  next:
  1773  	for i, v := range mo.Values {
  1774  		orderReq := orderReqs[i]
  1775  		for vout, txOut := range txOuts {
  1776  			if uint64(txOut.Value) == orderReq {
  1777  				_, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams)
  1778  				if err != nil {
  1779  					return nil, nil, 0, fmt.Errorf("error extracting addresses error: %w", err)
  1780  				}
  1781  				if len(addrs) != 1 {
  1782  					return nil, nil, 0, fmt.Errorf("unexpected multi-sig (%d)", len(addrs))
  1783  				}
  1784  				addr := addrs[0]
  1785  				addrStr, err := dexzec.EncodeAddress(addr, w.addrParams)
  1786  				if err != nil {
  1787  					return nil, nil, 0, fmt.Errorf("error encoding Zcash transparent address: %w", err)
  1788  				}
  1789  				utxos[i] = &btc.UTxO{
  1790  					TxHash:  txHash,
  1791  					Vout:    vout,
  1792  					Address: addrStr,
  1793  					Amount:  orderReq,
  1794  				}
  1795  				ops[i] = btc.NewOutput(txHash, vout, orderReq)
  1796  				coins[i] = asset.Coins{ops[i]}
  1797  				redeemScripts[i] = []dex.Bytes{nil}
  1798  				delete(txOuts, vout)
  1799  				continue next
  1800  			}
  1801  		}
  1802  		return nil, nil, 0, fmt.Errorf("failed to find output coin for multisplit value %s at index %d", btcutil.Amount(v.Value), i)
  1803  	}
  1804  	w.cm.LockUTXOs(utxos)
  1805  	if err := lockUnspent(w, false, ops); err != nil {
  1806  		return nil, nil, 0, fmt.Errorf("error locking unspents: %w", err)
  1807  	}
  1808  
  1809  	return coins, redeemScripts, fundingFees, nil
  1810  }
  1811  
  1812  func (w *zecWallet) sendManyShielded(recips []*zSendManyRecipient) (*chainhash.Hash, error) {
  1813  	lastAddr, err := w.lastShieldedAddress()
  1814  	if err != nil {
  1815  		return nil, err
  1816  	}
  1817  
  1818  	operationID, err := zSendMany(w, lastAddr, recips, NoPrivacy)
  1819  	if err != nil {
  1820  		return nil, fmt.Errorf("z_sendmany error: %w", err)
  1821  	}
  1822  
  1823  	return w.awaitSendManyOperation(w.ctx, w, operationID)
  1824  }
  1825  
  1826  func (w *zecWallet) Info() *asset.WalletInfo {
  1827  	return WalletInfo
  1828  }
  1829  
  1830  func (w *zecWallet) LockTimeExpired(_ context.Context, lockTime time.Time) (bool, error) {
  1831  	chainStamper := func(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) {
  1832  		hdr, err := getBlockHeader(w, blockHash)
  1833  		if err != nil {
  1834  			return
  1835  		}
  1836  		return hdr.Timestamp, &hdr.PrevBlock, nil
  1837  	}
  1838  
  1839  	w.tipMtx.RLock()
  1840  	tip := w.currentTip
  1841  	w.tipMtx.RUnlock()
  1842  
  1843  	medianTime, err := btc.CalcMedianTime(chainStamper, &tip.Hash) // TODO: pass ctx
  1844  	if err != nil {
  1845  		return false, fmt.Errorf("error getting median time: %w", err)
  1846  	}
  1847  	return medianTime.After(lockTime), nil
  1848  }
  1849  
  1850  // fundMultiOptions are the possible order options when calling FundMultiOrder.
  1851  type fundMultiOptions struct {
  1852  	// Split, if true, and multi-order cannot be funded with the existing UTXOs
  1853  	// in the wallet without going over the maxLock limit, a split transaction
  1854  	// will be created with one output per order.
  1855  	//
  1856  	// Use the multiSplitKey const defined above in the options map to set this option.
  1857  	Split bool `ini:"multisplit"`
  1858  }
  1859  
  1860  func decodeFundMultiSettings(settings map[string]string) (*fundMultiOptions, error) {
  1861  	opts := new(fundMultiOptions)
  1862  	return opts, config.Unmapify(settings, opts)
  1863  }
  1864  
  1865  func (w *zecWallet) MaxFundingFees(numTrades uint32, feeRate uint64, settings map[string]string) uint64 {
  1866  	customCfg, err := decodeFundMultiSettings(settings)
  1867  	if err != nil {
  1868  		w.log.Errorf("Error decoding multi-fund settings: %v", err)
  1869  		return 0
  1870  	}
  1871  
  1872  	// Assume a split from shielded
  1873  	txOutsSize := uint64(numTrades*dexbtc.P2PKHOutputSize + 1) // 1 for varint
  1874  	shieldedSplitFees := dexzec.TxFeesZIP317(0, txOutsSize, 0, 0, 0, nActionsOrchardEstimate)
  1875  
  1876  	if !customCfg.Split {
  1877  		return shieldedSplitFees
  1878  	}
  1879  
  1880  	return shieldedSplitFees + dexzec.TxFeesZIP317(1, uint64(numTrades+1)*dexbtc.P2PKHOutputSize, 0, 0, 0, 0)
  1881  }
  1882  
  1883  func (w *zecWallet) OwnsDepositAddress(addrStr string) (bool, error) {
  1884  	if strings.HasPrefix(addrStr, depositAddrPrefix) {
  1885  		var addrs depositAddressJSON
  1886  		if err := json.Unmarshal([]byte(addrStr[len(depositAddrPrefix):]), &addrs); err != nil {
  1887  			return false, fmt.Errorf("error decoding unified address info: %w", err)
  1888  		}
  1889  		addrStr = addrs.Unified
  1890  	}
  1891  	res, err := zValidateAddress(w, addrStr)
  1892  	if err != nil {
  1893  		return false, fmt.Errorf("error validating address: %w", err)
  1894  	}
  1895  	return res.IsMine, nil
  1896  }
  1897  
  1898  func (w *zecWallet) RedemptionAddress() (string, error) {
  1899  	return w.recyclableAddress()
  1900  }
  1901  
  1902  // A recyclable address is a redemption or refund address that may be recycled
  1903  // if unused. If already recycled addresses are available, one will be returned.
  1904  func (w *zecWallet) recyclableAddress() (string, error) {
  1905  	var returns []string
  1906  	defer w.ar.ReturnAddresses(returns)
  1907  	for {
  1908  		addr := w.ar.Address()
  1909  		if addr == "" {
  1910  			break
  1911  		}
  1912  		if owns, err := w.OwnsDepositAddress(addr); owns {
  1913  			return addr, nil
  1914  		} else if err != nil {
  1915  			w.log.Errorf("Error checking ownership of recycled address %q: %v", addr, err)
  1916  			returns = append(returns, addr)
  1917  		}
  1918  	}
  1919  	return transparentAddressString(w)
  1920  }
  1921  
  1922  func (w *zecWallet) Refund(coinID, contract dex.Bytes, feeRate uint64) (dex.Bytes, error) {
  1923  	txHash, vout, err := decodeCoinID(coinID)
  1924  	if err != nil {
  1925  		return nil, err
  1926  	}
  1927  
  1928  	// TODO: I'd recommend not passing a pkScript without a limited startTime
  1929  	// to prevent potentially long searches. In this case though, the output
  1930  	// will be found in the wallet and won't need to be searched for, only
  1931  	// the spender search will be conducted using the pkScript starting from
  1932  	// the block containing the original tx. The script can be gotten from
  1933  	// the wallet tx though and used for the spender search, while not passing
  1934  	// a script here to ensure no attempt is made to find the output without
  1935  	// a limited startTime.
  1936  	utxo, _, err := getTxOut(w, txHash, vout)
  1937  	if err != nil {
  1938  		return nil, fmt.Errorf("error finding unspent contract: %w", err)
  1939  	}
  1940  	if utxo == nil {
  1941  		return nil, asset.CoinNotFoundError // spent
  1942  	}
  1943  	msgTx, err := w.refundTx(txHash, vout, contract, uint64(utxo.Value), nil, feeRate)
  1944  	if err != nil {
  1945  		return nil, fmt.Errorf("error creating refund tx: %w", err)
  1946  	}
  1947  
  1948  	refundHash, err := w.broadcastTx(dexzec.NewTxFromMsgTx(msgTx, dexzec.MaxExpiryHeight))
  1949  	if err != nil {
  1950  		return nil, fmt.Errorf("broadcastTx: %w", err)
  1951  	}
  1952  
  1953  	tx := zecTx(msgTx)
  1954  	w.addTxToHistory(&asset.WalletTransaction{
  1955  		Type:   asset.Refund,
  1956  		ID:     refundHash.String(),
  1957  		Amount: uint64(utxo.Value),
  1958  		Fees:   tx.RequiredTxFeesZIP317(),
  1959  	}, refundHash, true)
  1960  
  1961  	return btc.ToCoinID(refundHash, 0), nil
  1962  }
  1963  
  1964  func (w *zecWallet) broadcastTx(tx *dexzec.Tx) (*chainhash.Hash, error) {
  1965  	rawTx := func() string {
  1966  		b, err := tx.Bytes()
  1967  		if err != nil {
  1968  			return "serialization error: " + err.Error()
  1969  		}
  1970  		return dex.Bytes(b).String()
  1971  	}
  1972  	txHash, err := sendRawTransaction(w, tx)
  1973  	if err != nil {
  1974  		return nil, fmt.Errorf("sendrawtx error: %v: %s", err, rawTx())
  1975  	}
  1976  	checkHash := tx.TxHash()
  1977  	if *txHash != checkHash {
  1978  		return nil, fmt.Errorf("transaction sent, but received unexpected transaction ID back from RPC server. "+
  1979  			"expected %s, got %s. raw tx: %s", checkHash, txHash, rawTx())
  1980  	}
  1981  	return txHash, nil
  1982  }
  1983  
  1984  func (w *zecWallet) refundTx(txHash *chainhash.Hash, vout uint32, contract dex.Bytes, val uint64, refundAddr btcutil.Address, fees uint64) (*wire.MsgTx, error) {
  1985  	sender, _, lockTime, _, err := dexbtc.ExtractSwapDetails(contract, false, w.btcParams)
  1986  	if err != nil {
  1987  		return nil, fmt.Errorf("error extracting swap addresses: %w", err)
  1988  	}
  1989  
  1990  	// Create the transaction that spends the contract.
  1991  	msgTx := wire.NewMsgTx(dexzec.VersionNU5)
  1992  	msgTx.LockTime = uint32(lockTime)
  1993  	prevOut := wire.NewOutPoint(txHash, vout)
  1994  	txIn := wire.NewTxIn(prevOut, []byte{}, nil)
  1995  	// Enable the OP_CHECKLOCKTIMEVERIFY opcode to be used.
  1996  	//
  1997  	// https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#Spending_wallet_policy
  1998  	txIn.Sequence = wire.MaxTxInSequenceNum - 1
  1999  	msgTx.AddTxIn(txIn)
  2000  	// Calculate fees and add the change output.
  2001  
  2002  	if refundAddr == nil {
  2003  		refundAddr, err = transparentAddress(w, w.addrParams, w.btcParams)
  2004  		if err != nil {
  2005  			return nil, fmt.Errorf("error getting new address from the wallet: %w", err)
  2006  		}
  2007  	}
  2008  	pkScript, err := txscript.PayToAddrScript(refundAddr)
  2009  	if err != nil {
  2010  		return nil, fmt.Errorf("error creating change script: %w", err)
  2011  	}
  2012  	txOut := wire.NewTxOut(int64(val-fees), pkScript)
  2013  	// One last check for dust.
  2014  	if isDust(uint64(txOut.Value), uint64(txOut.SerializeSize())) {
  2015  		return nil, fmt.Errorf("refund output is dust. value = %d, size = %d", txOut.Value, txOut.SerializeSize())
  2016  	}
  2017  	msgTx.AddTxOut(txOut)
  2018  
  2019  	prevScript, err := w.scriptHashScript(contract)
  2020  	if err != nil {
  2021  		return nil, fmt.Errorf("error constructing p2sh script: %w", err)
  2022  	}
  2023  
  2024  	refundSig, refundPubKey, err := w.createSig(msgTx, 0, contract, sender, []int64{int64(val)}, [][]byte{prevScript})
  2025  	if err != nil {
  2026  		return nil, fmt.Errorf("createSig: %w", err)
  2027  	}
  2028  	txIn.SignatureScript, err = dexbtc.RefundP2SHContract(contract, refundSig, refundPubKey)
  2029  	if err != nil {
  2030  		return nil, fmt.Errorf("RefundP2SHContract: %w", err)
  2031  	}
  2032  	return msgTx, nil
  2033  }
  2034  
  2035  func (w *zecWallet) createSig(msgTx *wire.MsgTx, idx int, pkScript []byte, addr btcutil.Address, vals []int64, prevScripts [][]byte) (sig, pubkey []byte, err error) {
  2036  	addrStr, err := dexzec.EncodeAddress(addr, w.addrParams)
  2037  	if err != nil {
  2038  		return nil, nil, fmt.Errorf("error encoding address: %w", err)
  2039  	}
  2040  	privKey, err := dumpPrivKey(w, addrStr)
  2041  	if err != nil {
  2042  		return nil, nil, fmt.Errorf("dumpPrivKey error: %w", err)
  2043  	}
  2044  	defer privKey.Zero()
  2045  
  2046  	sig, err = signTx(msgTx, idx, pkScript, txscript.SigHashAll, privKey, vals, prevScripts)
  2047  	if err != nil {
  2048  		return nil, nil, err
  2049  	}
  2050  
  2051  	return sig, privKey.PubKey().SerializeCompressed(), nil
  2052  }
  2053  
  2054  func (w *zecWallet) RegFeeConfirmations(_ context.Context, id dex.Bytes) (confs uint32, err error) {
  2055  	return 0, errors.New("legacy registration not supported")
  2056  }
  2057  
  2058  func sendEnough(val uint64) btc.EnoughFunc {
  2059  	return func(inputCount, inputsSize, sum uint64) (bool, uint64) {
  2060  		fees := dexzec.TxFeesZIP317(inputCount*dexbtc.RedeemP2PKHInputSize+1, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
  2061  		total := fees + val
  2062  		if sum >= total {
  2063  			return true, sum - total
  2064  		}
  2065  		return false, 0
  2066  	}
  2067  }
  2068  
  2069  // fundOrchard checks whether a send of the specified amount can be funded from
  2070  // the Orchard pool alone.
  2071  func (w *zecWallet) fundOrchard(amt uint64) (sum, fees uint64, n int, funded bool, err error) {
  2072  	unfiltered, err := zListUnspent(w)
  2073  	if err != nil {
  2074  		return 0, 0, 0, false, err
  2075  	}
  2076  	unspents := make([]*zListUnspentResult, 0, len(unfiltered))
  2077  	for _, u := range unfiltered {
  2078  		if u.Account == shieldedAcctNumber && u.Confirmations >= minOrchardConfs {
  2079  			unspents = append(unspents, u)
  2080  		}
  2081  	}
  2082  	sort.Slice(unspents, func(i, j int) bool {
  2083  		return unspents[i].Amount < unspents[j].Amount
  2084  	})
  2085  	var u *zListUnspentResult
  2086  	for n, u = range unspents {
  2087  		sum += toZats(u.Amount)
  2088  		fees = dexzec.TxFeesZIP317(0, 0, 0, 0, 0, uint64(n))
  2089  		if sum > amt+fees {
  2090  			funded = true
  2091  			return
  2092  		}
  2093  	}
  2094  	return
  2095  }
  2096  
  2097  func (w *zecWallet) EstimateSendTxFee(
  2098  	addrStr string, amt, _ /* feeRate*/ uint64, _ /* subtract */, maxWithdraw bool,
  2099  ) (
  2100  	fees uint64, isValidAddress bool, err error,
  2101  ) {
  2102  
  2103  	res, err := zValidateAddress(w, addrStr)
  2104  	isValidAddress = err == nil && res.IsValid
  2105  
  2106  	if maxWithdraw {
  2107  		addrType := transparentAddressType
  2108  		if isValidAddress {
  2109  			addrType = res.AddressType
  2110  		}
  2111  		fees, err = w.maxWithdrawFees(addrType)
  2112  		return
  2113  	}
  2114  
  2115  	orchardSum, orchardFees, orchardN, orchardFunded, err := w.fundOrchard(amt)
  2116  	if err != nil {
  2117  		return 0, false, fmt.Errorf("error checking orchard funding: %w", err)
  2118  	}
  2119  	if orchardFunded {
  2120  		return orchardFees, isValidAddress, nil
  2121  	}
  2122  
  2123  	bal, err := zGetBalanceForAccount(w, shieldedAcctNumber, 0)
  2124  	if err != nil {
  2125  		return 0, false, fmt.Errorf("z_getbalanceforaccount (0) error: %w", err)
  2126  	}
  2127  
  2128  	remain := amt - orchardSum - bal.Sapling /* sapling fees not accounted for */
  2129  
  2130  	const minConfs = 0
  2131  	_, _, spents, _, inputsSize, _, err := w.cm.Fund(w.reserves.Load(), minConfs, false, sendEnough(remain))
  2132  	if err != nil {
  2133  		return 0, false, fmt.Errorf("error funding value %d with %d transparent and %d orchard balances: %w",
  2134  			amt, bal.Transparent, orchardSum, err)
  2135  	}
  2136  
  2137  	fees = dexzec.TxFeesZIP317(inputsSize+uint64(wire.VarIntSerializeSize(uint64(len(spents)))), 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, uint64(orchardN))
  2138  	return
  2139  }
  2140  
  2141  func (w *zecWallet) maxWithdrawFees(addrType string) (uint64, error) {
  2142  	unfiltered, err := listUnspent(w)
  2143  	if err != nil {
  2144  		return 0, fmt.Errorf("listunspent error: %w", err)
  2145  	}
  2146  	numInputs := uint64(len(unfiltered))
  2147  	noteCounts, err := zGetNotesCount(w)
  2148  	if err != nil {
  2149  		return 0, fmt.Errorf("balances error: %w", err)
  2150  	}
  2151  
  2152  	txInsSize := numInputs*dexbtc.RedeemP2PKHInputSize + uint64(wire.VarIntSerializeSize(numInputs))
  2153  	nActionsOrchard := uint64(noteCounts.Orchard)
  2154  	var txOutsSize, nOutputsSapling uint64
  2155  	switch addrType {
  2156  	case unifiedAddressType, orchardAddressType:
  2157  		nActionsOrchard++
  2158  	case saplingAddressType:
  2159  		nOutputsSapling = 1
  2160  	default: // transparent
  2161  		txOutsSize = dexbtc.P2PKHOutputSize + 1
  2162  	}
  2163  	// TODO: Do we get nJoinSplit from noteCounts.Sprout somehow?
  2164  	return dexzec.TxFeesZIP317(txInsSize, txOutsSize, uint64(noteCounts.Sapling), nOutputsSapling, 0, nActionsOrchard), nil
  2165  }
  2166  
  2167  type txCoin struct {
  2168  	txHash *chainhash.Hash
  2169  	v      uint64
  2170  }
  2171  
  2172  var _ asset.Coin = (*txCoin)(nil)
  2173  
  2174  // ID is a unique identifier for this coin.
  2175  func (c *txCoin) ID() dex.Bytes {
  2176  	return c.txHash[:]
  2177  }
  2178  
  2179  // String is a string representation of the coin.
  2180  func (c *txCoin) String() string {
  2181  	return c.txHash.String()
  2182  }
  2183  
  2184  // Value is the available quantity, in atoms/satoshi.
  2185  func (c *txCoin) Value() uint64 {
  2186  	return c.v
  2187  }
  2188  
  2189  // TxID is the ID of the transaction that created the coin.
  2190  func (c *txCoin) TxID() string {
  2191  	return c.txHash.String()
  2192  }
  2193  
  2194  func (w *zecWallet) Send(addr string, value, feeRate uint64) (asset.Coin, error) {
  2195  	txHash, err := w.sendShielded(w.ctx, addr, value)
  2196  	if err != nil {
  2197  		return nil, err
  2198  	}
  2199  
  2200  	selfSend, err := w.OwnsDepositAddress(addr)
  2201  	if err != nil {
  2202  		w.log.Errorf("error checking if address %q is owned: %v", addr, err)
  2203  	}
  2204  	txType := asset.Send
  2205  	if selfSend {
  2206  		txType = asset.SelfSend
  2207  	}
  2208  
  2209  	tx, err := getTransaction(w, txHash)
  2210  	if err != nil {
  2211  		return nil, fmt.Errorf("unable to find tx after send %s: %v", txHash, err)
  2212  	}
  2213  
  2214  	w.addTxToHistory(&asset.WalletTransaction{
  2215  		Type:   txType,
  2216  		ID:     txHash.String(),
  2217  		Amount: value,
  2218  		Fees:   tx.RequiredTxFeesZIP317(),
  2219  	}, txHash, true)
  2220  
  2221  	return &txCoin{
  2222  		txHash: txHash,
  2223  		v:      value,
  2224  	}, nil
  2225  }
  2226  
  2227  // StandardSendFees returns the fees for a simple send tx with one input and two
  2228  // outputs.
  2229  func (w *zecWallet) StandardSendFee(feeRate uint64) uint64 {
  2230  	return dexzec.TxFeesZIP317(dexbtc.RedeemP2PKHInputSize+1, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
  2231  }
  2232  
  2233  // TransactionConfirmations gets the number of confirmations for the specified
  2234  // transaction.
  2235  func (w *zecWallet) TransactionConfirmations(ctx context.Context, txID string) (confs uint32, err error) {
  2236  	txHash, err := chainhash.NewHashFromStr(txID)
  2237  	if err != nil {
  2238  		return 0, fmt.Errorf("error decoding txid %q: %w", txID, err)
  2239  	}
  2240  	tx, err := getWalletTransaction(w, txHash)
  2241  	if err != nil {
  2242  		return 0, err
  2243  	}
  2244  	if tx.Confirmations < 0 {
  2245  		tx.Confirmations = 0
  2246  	}
  2247  
  2248  	return uint32(tx.Confirmations), nil
  2249  }
  2250  
  2251  // send the value to the address, with the given fee rate. If subtract is true,
  2252  // the fees will be subtracted from the value. If false, the fees are in
  2253  // addition to the value. feeRate is in units of sats/byte.
  2254  func (w *zecWallet) send(addrStr string, val uint64, subtract bool) (*chainhash.Hash, uint32, uint64, error) {
  2255  	addr, err := dexzec.DecodeAddress(addrStr, w.addrParams, w.btcParams)
  2256  	if err != nil {
  2257  		return nil, 0, 0, fmt.Errorf("invalid address: %s", addrStr)
  2258  	}
  2259  	pay2script, err := txscript.PayToAddrScript(addr)
  2260  	if err != nil {
  2261  		return nil, 0, 0, fmt.Errorf("PayToAddrScript error: %w", err)
  2262  	}
  2263  
  2264  	const minConfs = 0
  2265  	_, _, spents, _, inputsSize, _, err := w.cm.Fund(w.reserves.Load(), minConfs, false, sendEnough(val))
  2266  	if err != nil {
  2267  		return nil, 0, 0, newError(errFunding, "error funding transaction: %w", err)
  2268  	}
  2269  
  2270  	fundedTx, totalIn, _, err := w.fundedTx(spents)
  2271  	if err != nil {
  2272  		return nil, 0, 0, fmt.Errorf("error adding inputs to transaction: %w", err)
  2273  	}
  2274  
  2275  	fees := dexzec.TxFeesZIP317(inputsSize, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
  2276  	var toSend uint64
  2277  	if subtract {
  2278  		toSend = val - fees
  2279  	} else {
  2280  		toSend = val
  2281  	}
  2282  	fundedTx.AddTxOut(wire.NewTxOut(int64(toSend), pay2script))
  2283  
  2284  	changeAddr, err := transparentAddress(w, w.addrParams, w.btcParams)
  2285  	if err != nil {
  2286  		return nil, 0, 0, fmt.Errorf("error creating change address: %w", err)
  2287  	}
  2288  
  2289  	signedTx, _, err := w.signTxAndAddChange(fundedTx, changeAddr, totalIn, val, fees)
  2290  	if err != nil {
  2291  		return nil, 0, 0, fmt.Errorf("signTxAndAddChange error: %v", err)
  2292  	}
  2293  
  2294  	txHash, err := w.broadcastTx(signedTx)
  2295  	if err != nil {
  2296  		return nil, 0, 0, err
  2297  	}
  2298  
  2299  	return txHash, 0, toSend, nil
  2300  }
  2301  
  2302  func (w *zecWallet) sendWithReturn(baseTx *dexzec.Tx, addr btcutil.Address, totalIn, totalOut uint64) (*dexzec.Tx, error) {
  2303  	txInsSize := uint64(len(baseTx.TxIn))*dexbtc.RedeemP2PKHInputSize + 1
  2304  	var txOutsSize uint64 = uint64(wire.VarIntSerializeSize(uint64(len(baseTx.TxOut) + 1)))
  2305  	for _, txOut := range baseTx.TxOut {
  2306  		txOutsSize += uint64(txOut.SerializeSize())
  2307  	}
  2308  
  2309  	fees := dexzec.TxFeesZIP317(txInsSize, txOutsSize, 0, 0, 0, 0)
  2310  
  2311  	signedTx, _, err := w.signTxAndAddChange(baseTx, addr, totalIn, totalOut, fees)
  2312  	if err != nil {
  2313  		return nil, err
  2314  	}
  2315  
  2316  	_, err = w.broadcastTx(signedTx)
  2317  	return signedTx, err
  2318  }
  2319  
  2320  func (w *zecWallet) SignMessage(coin asset.Coin, msg dex.Bytes) (pubkeys, sigs []dex.Bytes, err error) {
  2321  
  2322  	op, err := btc.ConvertCoin(coin)
  2323  	if err != nil {
  2324  		return nil, nil, fmt.Errorf("error converting coin: %w", err)
  2325  	}
  2326  	utxo := w.cm.LockedOutput(op.Pt)
  2327  
  2328  	if utxo == nil {
  2329  		return nil, nil, fmt.Errorf("no utxo found for %s", op)
  2330  	}
  2331  	privKey, err := dumpPrivKey(w, utxo.Address)
  2332  	if err != nil {
  2333  		return nil, nil, err
  2334  	}
  2335  	defer privKey.Zero()
  2336  	pk := privKey.PubKey()
  2337  	hash := chainhash.HashB(msg) // legacy servers will not accept this signature!
  2338  	sig := ecdsa.Sign(privKey, hash)
  2339  	pubkeys = append(pubkeys, pk.SerializeCompressed())
  2340  	sigs = append(sigs, sig.Serialize()) // DER format serialization
  2341  	return
  2342  }
  2343  
  2344  func (w *zecWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint64, error) {
  2345  	contracts := make([][]byte, 0, len(swaps.Contracts))
  2346  	var totalOut uint64
  2347  	// Start with an empty MsgTx.
  2348  	coins := make([]*btc.Output, len(swaps.Inputs))
  2349  	for i, coin := range swaps.Inputs {
  2350  		c, err := btc.ConvertCoin(coin)
  2351  		if err != nil {
  2352  			return nil, nil, 0, fmt.Errorf("error converting coin ID: %w", err)
  2353  		}
  2354  		coins[i] = c
  2355  	}
  2356  	baseTx, totalIn, pts, err := w.fundedTx(coins)
  2357  	if err != nil {
  2358  		return nil, nil, 0, err
  2359  	}
  2360  
  2361  	var customCfg swapOptions
  2362  	err = config.Unmapify(swaps.Options, &customCfg)
  2363  	if err != nil {
  2364  		return nil, nil, 0, fmt.Errorf("error parsing swap options: %w", err)
  2365  	}
  2366  
  2367  	refundAddrs := make([]btcutil.Address, 0, len(swaps.Contracts))
  2368  
  2369  	// Add the contract outputs.
  2370  	// TODO: Make P2WSH contract and P2WPKH change outputs instead of
  2371  	// legacy/non-segwit swap contracts pkScripts.
  2372  	var swapOutsSize uint64
  2373  	for _, contract := range swaps.Contracts {
  2374  		totalOut += contract.Value
  2375  		// revokeAddr is the address belonging to the key that may be used to
  2376  		// sign and refund a swap past its encoded refund locktime.
  2377  		revokeAddrStr, err := w.recyclableAddress()
  2378  		if err != nil {
  2379  			return nil, nil, 0, fmt.Errorf("error creating revocation address: %w", err)
  2380  		}
  2381  		revokeAddr, err := dexzec.DecodeAddress(revokeAddrStr, w.addrParams, w.btcParams)
  2382  		if err != nil {
  2383  			return nil, nil, 0, fmt.Errorf("refund address decode error: %v", err)
  2384  		}
  2385  		refundAddrs = append(refundAddrs, revokeAddr)
  2386  
  2387  		contractAddr, err := dexzec.DecodeAddress(contract.Address, w.addrParams, w.btcParams)
  2388  		if err != nil {
  2389  			return nil, nil, 0, fmt.Errorf("contract address decode error: %v", err)
  2390  		}
  2391  
  2392  		// Create the contract, a P2SH redeem script.
  2393  		contractScript, err := dexbtc.MakeContract(contractAddr, revokeAddr,
  2394  			contract.SecretHash, int64(contract.LockTime), false, w.btcParams)
  2395  		if err != nil {
  2396  			return nil, nil, 0, fmt.Errorf("unable to create pubkey script for address %s: %w", contract.Address, err)
  2397  		}
  2398  		contracts = append(contracts, contractScript)
  2399  
  2400  		// Make the P2SH address and pubkey script.
  2401  		scriptAddr, err := w.scriptHashAddress(contractScript)
  2402  		if err != nil {
  2403  			return nil, nil, 0, fmt.Errorf("error encoding script address: %w", err)
  2404  		}
  2405  
  2406  		pkScript, err := txscript.PayToAddrScript(scriptAddr)
  2407  		if err != nil {
  2408  			return nil, nil, 0, fmt.Errorf("error creating pubkey script: %w", err)
  2409  		}
  2410  
  2411  		// Add the transaction output.
  2412  		txOut := wire.NewTxOut(int64(contract.Value), pkScript)
  2413  		swapOutsSize += uint64(txOut.SerializeSize())
  2414  		baseTx.AddTxOut(txOut)
  2415  	}
  2416  	if totalIn < totalOut {
  2417  		return nil, nil, 0, newError(errInsufficientBalance, "unfunded contract. %d < %d", totalIn, totalOut)
  2418  	}
  2419  
  2420  	// Ensure we have enough outputs before broadcasting.
  2421  	swapCount := len(swaps.Contracts)
  2422  	if len(baseTx.TxOut) < swapCount {
  2423  		return nil, nil, 0, fmt.Errorf("fewer outputs than swaps. %d < %d", len(baseTx.TxOut), swapCount)
  2424  	}
  2425  
  2426  	// Grab a change address.
  2427  	changeAddr, err := transparentAddress(w, w.addrParams, w.btcParams)
  2428  	if err != nil {
  2429  		return nil, nil, 0, fmt.Errorf("error creating change address: %w", err)
  2430  	}
  2431  
  2432  	txInsSize := uint64(len(coins))*dexbtc.RedeemP2PKHInputSize + 1
  2433  	txOutsSize := swapOutsSize + dexbtc.P2PKHOutputSize
  2434  	fees := dexzec.TxFeesZIP317(txInsSize, txOutsSize, 0, 0, 0, 0)
  2435  
  2436  	// Sign, add change, but don't send the transaction yet until
  2437  	// the individual swap refund txs are prepared and signed.
  2438  	msgTx, change, err := w.signTxAndAddChange(baseTx, changeAddr, totalIn, totalOut, fees)
  2439  	if err != nil {
  2440  		return nil, nil, 0, err
  2441  	}
  2442  	txHash := msgTx.TxHash()
  2443  
  2444  	// Prepare the receipts.
  2445  	receipts := make([]asset.Receipt, 0, swapCount)
  2446  	for i, contract := range swaps.Contracts {
  2447  		output := btc.NewOutput(&txHash, uint32(i), contract.Value)
  2448  		refundAddr := refundAddrs[i]
  2449  		signedRefundTx, err := w.refundTx(&output.Pt.TxHash, output.Pt.Vout, contracts[i], contract.Value, refundAddr, fees)
  2450  		if err != nil {
  2451  			return nil, nil, 0, fmt.Errorf("error creating refund tx: %w", err)
  2452  		}
  2453  		refundBuff := new(bytes.Buffer)
  2454  		err = signedRefundTx.Serialize(refundBuff)
  2455  		if err != nil {
  2456  			return nil, nil, 0, fmt.Errorf("error serializing refund tx: %w", err)
  2457  		}
  2458  		receipts = append(receipts, &btc.SwapReceipt{
  2459  			Output:            output,
  2460  			SwapContract:      contracts[i],
  2461  			ExpirationTime:    time.Unix(int64(contract.LockTime), 0).UTC(),
  2462  			SignedRefundBytes: refundBuff.Bytes(),
  2463  		})
  2464  	}
  2465  
  2466  	// Refund txs prepared and signed. Can now broadcast the swap(s).
  2467  	_, err = w.broadcastTx(msgTx)
  2468  	if err != nil {
  2469  		return nil, nil, 0, err
  2470  	}
  2471  
  2472  	w.addTxToHistory(&asset.WalletTransaction{
  2473  		Type:   asset.Swap,
  2474  		ID:     txHash.String(),
  2475  		Amount: totalOut,
  2476  		Fees:   msgTx.RequiredTxFeesZIP317(),
  2477  	}, &txHash, true)
  2478  
  2479  	// If change is nil, return a nil asset.Coin.
  2480  	var changeCoin asset.Coin
  2481  	if change != nil {
  2482  		changeCoin = change
  2483  	}
  2484  
  2485  	if swaps.LockChange && change != nil {
  2486  		// Lock the change output
  2487  		w.log.Debugf("locking change coin %s", change)
  2488  		err = lockUnspent(w, false, []*btc.Output{change})
  2489  		if err != nil {
  2490  			// The swap transaction is already broadcasted, so don't fail now.
  2491  			w.log.Errorf("failed to lock change output: %v", err)
  2492  		}
  2493  
  2494  		addrStr, err := dexzec.EncodeAddress(changeAddr, w.addrParams)
  2495  		if err != nil {
  2496  			w.log.Errorf("Failed to stringify address %v (default encoding): %v", changeAddr, err)
  2497  			addrStr = changeAddr.String() // may or may not be able to retrieve the private keys for the next swap!
  2498  		}
  2499  		w.cm.LockUTXOs([]*btc.UTxO{{
  2500  			TxHash:  &change.Pt.TxHash,
  2501  			Vout:    change.Pt.Vout,
  2502  			Address: addrStr,
  2503  			Amount:  change.Val,
  2504  		}})
  2505  	}
  2506  
  2507  	w.cm.UnlockOutPoints(pts)
  2508  
  2509  	return receipts, changeCoin, fees, nil
  2510  }
  2511  
  2512  func (w *zecWallet) SwapConfirmations(_ context.Context, id dex.Bytes, contract dex.Bytes, startTime time.Time) (uint32, bool, error) {
  2513  	txHash, vout, err := decodeCoinID(id)
  2514  	if err != nil {
  2515  		return 0, false, err
  2516  	}
  2517  	// Check for an unspent output.
  2518  	txOut, err := getTxOutput(w, txHash, vout)
  2519  	if err == nil && txOut != nil {
  2520  		return uint32(txOut.Confirmations), false, nil
  2521  	}
  2522  	// Check wallet transactions.
  2523  	tx, err := getWalletTransaction(w, txHash)
  2524  	if err != nil {
  2525  		if errors.Is(err, asset.CoinNotFoundError) {
  2526  			return 0, false, asset.CoinNotFoundError
  2527  		}
  2528  		return 0, false, newError(errNoTx, "gettransaction error; %w", err)
  2529  	}
  2530  	if tx.Confirmations < 0 {
  2531  		tx.Confirmations = 0
  2532  	}
  2533  	return uint32(tx.Confirmations), true, nil
  2534  }
  2535  
  2536  func (w *zecWallet) SyncStatus() (*asset.SyncStatus, error) {
  2537  	ss, err := syncStatus(w)
  2538  	if err != nil {
  2539  		return nil, err
  2540  	}
  2541  	ss.StartingBlocks = w.tipAtConnect.Load()
  2542  
  2543  	if ss.TargetHeight == 0 { // do not say progress = 1
  2544  		return &asset.SyncStatus{}, nil
  2545  	}
  2546  	if ss.Synced {
  2547  		numPeers, err := peerCount(w)
  2548  		if err != nil {
  2549  			return nil, err
  2550  		}
  2551  		ss.Synced = numPeers > 0
  2552  	}
  2553  	return ss, nil
  2554  }
  2555  
  2556  func (w *zecWallet) ValidateAddress(addr string) bool {
  2557  	res, err := zValidateAddress(w, addr)
  2558  	if err != nil {
  2559  		w.log.Errorf("z_validateaddress error for address %q: %w", addr, err)
  2560  		return false
  2561  	}
  2562  	return res.IsValid
  2563  }
  2564  
  2565  func (w *zecWallet) ValidateSecret(secret, secretHash []byte) bool {
  2566  	h := sha256.Sum256(secret)
  2567  	return bytes.Equal(h[:], secretHash)
  2568  }
  2569  
  2570  func (w *zecWallet) useSplitTx() bool {
  2571  	return w.walletCfg.Load().(*WalletConfig).UseSplitTx
  2572  }
  2573  
  2574  func transparentAddressString(c rpcCaller) (string, error) {
  2575  	// One of the address types MUST be shielded.
  2576  	addrRes, err := zGetAddressForAccount(c, shieldedAcctNumber, []string{transparentAddressType, orchardAddressType})
  2577  	if err != nil {
  2578  		return "", err
  2579  	}
  2580  	receivers, err := zGetUnifiedReceivers(c, addrRes.Address)
  2581  	if err != nil {
  2582  		return "", err
  2583  	}
  2584  	return receivers.Transparent, nil
  2585  }
  2586  
  2587  func transparentAddress(c rpcCaller, addrParams *dexzec.AddressParams, btcParams *chaincfg.Params) (btcutil.Address, error) {
  2588  	addrStr, err := transparentAddressString(c)
  2589  	if err != nil {
  2590  		return nil, err
  2591  	}
  2592  	return dexzec.DecodeAddress(addrStr, addrParams, btcParams)
  2593  }
  2594  
  2595  func (w *zecWallet) lastShieldedAddress() (addr string, err error) {
  2596  	if addrPtr := w.lastAddress.Load(); addrPtr != nil {
  2597  		return addrPtr.(string), nil
  2598  	}
  2599  	accts, err := zListAccounts(w)
  2600  	if err != nil {
  2601  		return "", err
  2602  	}
  2603  	for _, acct := range accts {
  2604  		if acct.Number != shieldedAcctNumber {
  2605  			continue
  2606  		}
  2607  		if len(acct.Addresses) == 0 {
  2608  			break // generate first address
  2609  		}
  2610  		lastAddr := acct.Addresses[len(acct.Addresses)-1].UnifiedAddr
  2611  		w.lastAddress.Store(lastAddr)
  2612  		return lastAddr, nil // Orchard = Unified for account 1
  2613  	}
  2614  	return w.newShieldedAddress()
  2615  }
  2616  
  2617  // Balance adds a sum of shielded pool balances to the transparent balance info.
  2618  //
  2619  // Since v5.4.0, the getnewaddress RPC is deprecated if favor of using unified
  2620  // addresses from accounts generated with z_getnewaccount. Addresses are
  2621  // generated from account 0. Any addresses previously generated using
  2622  // getnewaddress belong to a legacy account that is not listed with
  2623  // z_listaccount, nor addressable with e.g. z_getbalanceforaccount. For
  2624  // transparent addresses, we still use the getbalance RPC, which combines
  2625  // transparent balance from both legacy and generated accounts. This matches the
  2626  // behavior of the listunspent RPC, and conveniently makes upgrading simple. So
  2627  // even though we ONLY use account 0 to generate t-addresses, any account's
  2628  // transparent outputs are eligible for trading. To minimize confusion, we don't
  2629  // add transparent receivers to addresses generated from the shielded account.
  2630  // This doesn't preclude a user doing something silly with zcash-cli.
  2631  func (w *zecWallet) Balance() (*asset.Balance, error) {
  2632  
  2633  	bals, err := w.balances()
  2634  	if err != nil {
  2635  		return nil, codedError(errBalanceRetrieval, err)
  2636  	}
  2637  	locked, err := w.lockedZats()
  2638  	if err != nil {
  2639  		return nil, err
  2640  	}
  2641  
  2642  	bal := &asset.Balance{
  2643  		Available: bals.orchard.avail + bals.transparent.avail,
  2644  		Immature:  bals.orchard.maturing + bals.transparent.maturing,
  2645  		Locked:    locked,
  2646  		Other:     make(map[asset.BalanceCategory]asset.CustomBalance),
  2647  	}
  2648  
  2649  	bal.Other[asset.BalanceCategoryShielded] = asset.CustomBalance{
  2650  		Amount: bals.orchard.avail, // + bals.orchard.maturing,
  2651  	}
  2652  
  2653  	reserves := w.reserves.Load()
  2654  	if reserves > bal.Available {
  2655  		w.log.Warnf("Available balance is below configured reserves: %s < %s",
  2656  			btcutil.Amount(bal.Available), btcutil.Amount(reserves))
  2657  		bal.ReservesDeficit = reserves - bal.Available
  2658  		reserves = bal.Available
  2659  	}
  2660  
  2661  	bal.BondReserves = reserves
  2662  	bal.Available -= reserves
  2663  	bal.Locked += reserves
  2664  
  2665  	return bal, nil
  2666  }
  2667  
  2668  // lockedSats is the total value of locked outputs, as locked with LockUnspent.
  2669  func (w *zecWallet) lockedZats() (uint64, error) {
  2670  	lockedOutpoints, err := listLockUnspent(w, w.log)
  2671  	if err != nil {
  2672  		return 0, err
  2673  	}
  2674  	var sum uint64
  2675  	for _, rpcOP := range lockedOutpoints {
  2676  		txHash, err := chainhash.NewHashFromStr(rpcOP.TxID)
  2677  		if err != nil {
  2678  			return 0, err
  2679  		}
  2680  		pt := btc.NewOutPoint(txHash, rpcOP.Vout)
  2681  		utxo := w.cm.LockedOutput(pt)
  2682  		if utxo != nil {
  2683  			sum += utxo.Amount
  2684  			continue
  2685  		}
  2686  		tx, err := getWalletTransaction(w, txHash)
  2687  		if err != nil {
  2688  			return 0, err
  2689  		}
  2690  		txOut, err := btc.TxOutFromTxBytes(tx.Bytes, rpcOP.Vout, deserializeTx, hashTx)
  2691  		if err != nil {
  2692  			return 0, err
  2693  		}
  2694  		sum += uint64(txOut.Value)
  2695  	}
  2696  	return sum, nil
  2697  }
  2698  
  2699  // newShieldedAddress creates a new shielded address.
  2700  func (w *zecWallet) newShieldedAddress() (string, error) {
  2701  	// An orchard address is the same as a unified address with only an orchard
  2702  	// receiver.
  2703  	addrRes, err := zGetAddressForAccount(w, shieldedAcctNumber, []string{orchardAddressType})
  2704  	if err != nil {
  2705  		return "", err
  2706  	}
  2707  	w.lastAddress.Store(addrRes.Address)
  2708  	return addrRes.Address, nil
  2709  }
  2710  
  2711  // sendOne is a helper function for doing a z_sendmany with a single recipient.
  2712  func (w *zecWallet) sendOne(ctx context.Context, fromAddr, toAddr string, amt uint64, priv privacyPolicy) (*chainhash.Hash, error) {
  2713  	recip := singleSendManyRecipient(toAddr, amt)
  2714  
  2715  	operationID, err := zSendMany(w, fromAddr, recip, priv)
  2716  	if err != nil {
  2717  		return nil, fmt.Errorf("z_sendmany error: %w", err)
  2718  	}
  2719  
  2720  	return w.awaitSendManyOperation(ctx, w, operationID)
  2721  }
  2722  
  2723  // awaitSendManyOperation waits for the asynchronous result from a z_sendmany
  2724  // operation.
  2725  func (w *zecWallet) awaitSendManyOperation(ctx context.Context, c rpcCaller, operationID string) (*chainhash.Hash, error) {
  2726  	for {
  2727  		res, err := zGetOperationResult(c, operationID)
  2728  		if err != nil && !errors.Is(err, ErrEmptyOpResults) {
  2729  			return nil, fmt.Errorf("error getting operation result: %w", err)
  2730  		}
  2731  		if res != nil {
  2732  			switch res.Status {
  2733  			case "failed":
  2734  				return nil, fmt.Errorf("z_sendmany operation failed: %s", res.Error.Message)
  2735  
  2736  			case "success":
  2737  				if res.Result == nil {
  2738  					return nil, errors.New("async operation result = 'success' but no Result field")
  2739  				}
  2740  				txHash, err := chainhash.NewHashFromStr(res.Result.TxID)
  2741  				if err != nil {
  2742  					return nil, fmt.Errorf("error decoding txid: %w", err)
  2743  				}
  2744  				return txHash, nil
  2745  			default:
  2746  				w.log.Warnf("unexpected z_getoperationresult status %q: %+v", res.Status)
  2747  			}
  2748  
  2749  		}
  2750  		select {
  2751  		case <-ctx.Done():
  2752  			return nil, ctx.Err()
  2753  		case <-time.After(time.Second):
  2754  		}
  2755  	}
  2756  }
  2757  
  2758  func (w *zecWallet) sendOneShielded(ctx context.Context, toAddr string, amt uint64, priv privacyPolicy) (*chainhash.Hash, error) {
  2759  	lastAddr, err := w.lastShieldedAddress()
  2760  	if err != nil {
  2761  		return nil, err
  2762  	}
  2763  	return w.sendOne(ctx, lastAddr, toAddr, amt, priv)
  2764  }
  2765  
  2766  func (w *zecWallet) sendShielded(ctx context.Context, toAddr string, amt uint64) (*chainhash.Hash, error) {
  2767  	res, err := zValidateAddress(w, toAddr)
  2768  	if err != nil {
  2769  		return nil, fmt.Errorf("error validating address: %w", err)
  2770  	}
  2771  
  2772  	if !res.IsValid {
  2773  		return nil, fmt.Errorf("invalid address %q", toAddr)
  2774  	}
  2775  
  2776  	// TODO: We're using NoPrivacy for everything except orchard-to-orchard
  2777  	// sends. This can potentially be tightened up, but why? The RPC provides
  2778  	// no insight into calculating what privacy level is needed, so we would
  2779  	// have to do a bunch of calculations regarding sources of funds and
  2780  	// potential for change outputs. In the end, it changes nothing, and zcashd
  2781  	// will optimize privacy the best it can.
  2782  	priv := NoPrivacy
  2783  	if res.AddressType == unifiedAddressType {
  2784  		r, err := zGetUnifiedReceivers(w, toAddr)
  2785  		if err != nil {
  2786  			return nil, fmt.Errorf("error getting unified receivers: %w", err)
  2787  		}
  2788  		if r.Orchard != "" {
  2789  			if _, _, _, isFunded, err := w.fundOrchard(amt); err != nil {
  2790  				return nil, fmt.Errorf("error checking orchard funding: %w", err)
  2791  			} else if isFunded {
  2792  				priv = FullPrivacy
  2793  			}
  2794  		}
  2795  	}
  2796  
  2797  	txHash, err := w.sendOneShielded(ctx, toAddr, amt, priv)
  2798  	if err != nil {
  2799  		return nil, err
  2800  	}
  2801  
  2802  	return txHash, nil
  2803  }
  2804  
  2805  func zecTx(tx *wire.MsgTx) *dexzec.Tx {
  2806  	return dexzec.NewTxFromMsgTx(tx, dexzec.MaxExpiryHeight)
  2807  }
  2808  
  2809  // signTx signs the transaction input with Zcash's BLAKE-2B sighash digest.
  2810  // Won't work with shielded or blended transactions.
  2811  func signTx(btcTx *wire.MsgTx, idx int, pkScript []byte, hashType txscript.SigHashType,
  2812  	key *btcec.PrivateKey, amts []int64, prevScripts [][]byte) ([]byte, error) {
  2813  
  2814  	tx := zecTx(btcTx)
  2815  
  2816  	sigHash, err := tx.SignatureDigest(idx, hashType, pkScript, amts, prevScripts)
  2817  	if err != nil {
  2818  		return nil, fmt.Errorf("sighash calculation error: %v", err)
  2819  	}
  2820  
  2821  	return append(ecdsa.Sign(key, sigHash[:]).Serialize(), byte(hashType)), nil
  2822  }
  2823  
  2824  // isDust returns true if the output will be rejected as dust.
  2825  func isDust(val, outputSize uint64) bool {
  2826  	// See https://github.com/zcash/zcash/blob/5066efbb98bc2af5eed201212d27c77993950cee/src/primitives/transaction.h#L630
  2827  	// https://github.com/zcash/zcash/blob/5066efbb98bc2af5eed201212d27c77993950cee/src/primitives/transaction.cpp#L127
  2828  	// Also see informative comments hinting towards future changes at
  2829  	// https://github.com/zcash/zcash/blob/master/src/policy/policy.h
  2830  	sz := outputSize + 148                        // 148 accounts for an input on spending tx
  2831  	const oneThirdDustThresholdRate = 100         // zats / kB
  2832  	nFee := oneThirdDustThresholdRate * sz / 1000 // This is different from BTC
  2833  	if nFee == 0 {
  2834  		nFee = oneThirdDustThresholdRate
  2835  	}
  2836  
  2837  	return val < 3*nFee
  2838  }
  2839  
  2840  // Convert the ZEC value to satoshi.
  2841  func toZats(v float64) uint64 {
  2842  	return uint64(math.Round(v * 1e8))
  2843  }
  2844  
  2845  func hashTx(tx *wire.MsgTx) *chainhash.Hash {
  2846  	h := zecTx(tx).TxHash()
  2847  	return &h
  2848  }
  2849  
  2850  func deserializeTx(b []byte) (*wire.MsgTx, error) {
  2851  	tx, err := dexzec.DeserializeTx(b)
  2852  	if err != nil {
  2853  		return nil, err
  2854  	}
  2855  	return tx.MsgTx, nil
  2856  }
  2857  
  2858  func (w *zecWallet) txDB() *btc.BadgerTxDB {
  2859  	db := w.txHistoryDB.Load()
  2860  	if db == nil {
  2861  		return nil
  2862  	}
  2863  	return db.(*btc.BadgerTxDB)
  2864  }
  2865  
  2866  func (w *zecWallet) listSinceBlock(start int64) ([]btcjson.ListTransactionsResult, error) {
  2867  	hash, err := getBlockHash(w, start)
  2868  	if err != nil {
  2869  		return nil, err
  2870  	}
  2871  
  2872  	res, err := listSinceBlock(w, hash)
  2873  	if err != nil {
  2874  		return nil, err
  2875  	}
  2876  
  2877  	return res, nil
  2878  }
  2879  
  2880  func (w *zecWallet) addTxToHistory(wt *asset.WalletTransaction, txHash *chainhash.Hash, submitted bool, skipNotes ...bool) {
  2881  	txHistoryDB := w.txDB()
  2882  	if txHistoryDB == nil {
  2883  		return
  2884  	}
  2885  
  2886  	ewt := &btc.ExtendedWalletTx{
  2887  		WalletTransaction: wt,
  2888  		Submitted:         submitted,
  2889  	}
  2890  
  2891  	if wt.BlockNumber == 0 {
  2892  		w.pendingTxsMtx.Lock()
  2893  		w.pendingTxs[*txHash] = ewt
  2894  		w.pendingTxsMtx.Unlock()
  2895  	}
  2896  
  2897  	err := txHistoryDB.StoreTx(ewt)
  2898  	if err != nil {
  2899  		w.log.Errorf("failed to store tx in tx history db: %v", err)
  2900  	}
  2901  
  2902  	skipNote := len(skipNotes) > 0 && skipNotes[0]
  2903  	if submitted && !skipNote {
  2904  		w.emit.TransactionNote(wt, true)
  2905  	}
  2906  }
  2907  
  2908  func (w *zecWallet) txHistoryDBPath(walletID string) string {
  2909  	return filepath.Join(w.walletDir, fmt.Sprintf("txhistorydb-%s", walletID))
  2910  }
  2911  
  2912  // findExistingAddressBasedTxHistoryDB finds the path of a tx history db that
  2913  // was created using an address controlled by the wallet. This should only be
  2914  // used for RPC wallets, as SPV wallets are able to get the first address
  2915  // generated by the wallet.
  2916  func (w *zecWallet) findExistingAddressBasedTxHistoryDB(encSeed string) (string, error) {
  2917  	dir, err := os.Open(w.walletDir)
  2918  	if err != nil {
  2919  		return "", fmt.Errorf("error opening wallet directory: %w", err)
  2920  	}
  2921  	defer dir.Close()
  2922  
  2923  	entries, err := dir.Readdir(0)
  2924  	if err != nil {
  2925  		return "", fmt.Errorf("error reading wallet directory: %w", err)
  2926  	}
  2927  
  2928  	pattern := regexp.MustCompile(`^txhistorydb-(.+)$`)
  2929  
  2930  	for _, entry := range entries {
  2931  		if !entry.IsDir() {
  2932  			continue
  2933  		}
  2934  
  2935  		match := pattern.FindStringSubmatch(entry.Name())
  2936  		if match == nil {
  2937  			continue
  2938  		}
  2939  
  2940  		name := match[1]
  2941  		if name == encSeed {
  2942  			return filepath.Join(w.walletDir, entry.Name()), nil
  2943  		}
  2944  	}
  2945  
  2946  	return "", nil
  2947  }
  2948  
  2949  func (w *zecWallet) startTxHistoryDB(ctx context.Context) (*sync.WaitGroup, error) {
  2950  	wInfo, err := walletInfo(w)
  2951  	if err != nil {
  2952  		return nil, err
  2953  	}
  2954  	encSeed := wInfo.MnemonicSeedfp
  2955  	name, err := w.findExistingAddressBasedTxHistoryDB(encSeed)
  2956  	if err != nil {
  2957  		return nil, err
  2958  	}
  2959  	dbPath := name
  2960  
  2961  	if dbPath == "" {
  2962  		dbPath = w.txHistoryDBPath(encSeed)
  2963  	}
  2964  
  2965  	w.log.Debugf("Using tx history db at %s", dbPath)
  2966  
  2967  	db := btc.NewBadgerTxDB(dbPath, w.log)
  2968  	w.txHistoryDB.Store(db)
  2969  
  2970  	wg, err := db.Connect(ctx)
  2971  	if err != nil {
  2972  		return nil, fmt.Errorf("error connecting to tx history db: %w", err)
  2973  	}
  2974  
  2975  	pendingTxs, err := db.GetPendingTxs()
  2976  	if err != nil {
  2977  		return nil, fmt.Errorf("failed to load unconfirmed txs: %v", err)
  2978  	}
  2979  
  2980  	w.pendingTxsMtx.Lock()
  2981  	for _, tx := range pendingTxs {
  2982  		txHash, err := chainhash.NewHashFromStr(tx.ID)
  2983  		if err != nil {
  2984  			w.log.Errorf("Invalid txid %v from tx history db: %v", tx.ID, err)
  2985  			continue
  2986  		}
  2987  		w.pendingTxs[*txHash] = tx
  2988  	}
  2989  	w.pendingTxsMtx.Unlock()
  2990  
  2991  	lastQuery, err := db.GetLastReceiveTxQuery()
  2992  	if errors.Is(err, btc.ErrNeverQueried) {
  2993  		lastQuery = 0
  2994  	} else if err != nil {
  2995  		return nil, fmt.Errorf("failed to load last query time: %v", err)
  2996  	}
  2997  
  2998  	w.receiveTxLastQuery.Store(lastQuery)
  2999  
  3000  	return wg, nil
  3001  }
  3002  
  3003  // WalletTransaction returns a transaction that either the wallet has made or
  3004  // one in which the wallet has received funds.
  3005  func (w *zecWallet) WalletTransaction(_ context.Context, txID string) (*asset.WalletTransaction, error) {
  3006  	coinID, err := hex.DecodeString(txID)
  3007  	if err == nil {
  3008  		txHash, _, err := decodeCoinID(coinID)
  3009  		if err == nil {
  3010  			txID = txHash.String()
  3011  		}
  3012  	}
  3013  
  3014  	txHistoryDB := w.txDB()
  3015  	tx, err := txHistoryDB.GetTx(txID)
  3016  	if err != nil {
  3017  		return nil, err
  3018  	}
  3019  
  3020  	if tx == nil {
  3021  		txHash, err := chainhash.NewHashFromStr(txID)
  3022  		if err != nil {
  3023  			return nil, fmt.Errorf("error decoding txid %s: %w", txID, err)
  3024  		}
  3025  
  3026  		gtr, err := getTransaction(w, txHash)
  3027  		if err != nil {
  3028  			return nil, fmt.Errorf("error getting transaction %s: %w", txID, err)
  3029  		}
  3030  
  3031  		var (
  3032  			blockHeight int32
  3033  			blockTime   int64
  3034  		)
  3035  		if gtr.blockHash != nil {
  3036  			block, _, err := getVerboseBlockHeader(w, gtr.blockHash)
  3037  			if err != nil {
  3038  				return nil, fmt.Errorf("error getting block height for %s: %v", gtr.blockHash, err)
  3039  			}
  3040  			blockHeight = int32(block.Height)
  3041  			blockTime = block.Time
  3042  		}
  3043  
  3044  		tx, err = w.idUnknownTx(&btcjson.ListTransactionsResult{
  3045  			BlockHeight: &blockHeight,
  3046  			BlockTime:   blockTime,
  3047  			TxID:        txID,
  3048  		})
  3049  		if err != nil {
  3050  			return nil, fmt.Errorf("error identifying transaction: %v", err)
  3051  		}
  3052  
  3053  		tx.BlockNumber = uint64(blockHeight)
  3054  		tx.Timestamp = uint64(blockTime)
  3055  		tx.Confirmed = true
  3056  		w.addTxToHistory(tx, txHash, true, false)
  3057  	}
  3058  
  3059  	// If the wallet knows about the transaction, it will be part of the
  3060  	// available balance, so we always return Confirmed = true.
  3061  	tx.Confirmed = true
  3062  
  3063  	return tx, nil
  3064  }
  3065  
  3066  // TxHistory returns all the transactions the wallet has made. If refID is nil,
  3067  // then transactions starting from the most recent are returned (past is ignored).
  3068  // If past is true, the transactions prior to the refID are returned, otherwise
  3069  // the transactions after the refID are returned. n is the number of
  3070  // transactions to return. If n is <= 0, all the transactions will be returned.
  3071  func (w *zecWallet) TxHistory(n int, refID *string, past bool) ([]*asset.WalletTransaction, error) {
  3072  	txHistoryDB := w.txDB()
  3073  	if txHistoryDB == nil {
  3074  		return nil, fmt.Errorf("tx database not initialized")
  3075  	}
  3076  
  3077  	return txHistoryDB.GetTxs(n, refID, past)
  3078  }
  3079  
  3080  const sendCategory = "send"
  3081  
  3082  // idUnknownTx identifies the type and details of a transaction either made
  3083  // or recieved by the wallet.
  3084  func (w *zecWallet) idUnknownTx(tx *btcjson.ListTransactionsResult) (*asset.WalletTransaction, error) {
  3085  	txHash, err := chainhash.NewHashFromStr(tx.TxID)
  3086  	if err != nil {
  3087  		return nil, fmt.Errorf("error decoding tx hash %s: %v", tx.TxID, err)
  3088  	}
  3089  	msgTx, err := getTransaction(w, txHash)
  3090  	if err != nil {
  3091  		return nil, err
  3092  	}
  3093  
  3094  	var totalOut uint64
  3095  	for _, txOut := range msgTx.TxOut {
  3096  		totalOut += uint64(txOut.Value)
  3097  	}
  3098  
  3099  	fee := msgTx.RequiredTxFeesZIP317()
  3100  
  3101  	txIsBond := func(msgTx *zTx) (bool, *asset.BondTxInfo) {
  3102  		if len(msgTx.TxOut) < 2 {
  3103  			return false, nil
  3104  		}
  3105  		const scriptVer = 0
  3106  		acctID, lockTime, pkHash, err := dexbtc.ExtractBondCommitDataV0(scriptVer, msgTx.TxOut[1].PkScript)
  3107  		if err != nil {
  3108  			return false, nil
  3109  		}
  3110  		return true, &asset.BondTxInfo{
  3111  			AccountID: acctID[:],
  3112  			LockTime:  uint64(lockTime),
  3113  			BondID:    pkHash[:],
  3114  		}
  3115  	}
  3116  	if isBond, bondInfo := txIsBond(msgTx); isBond {
  3117  		return &asset.WalletTransaction{
  3118  			Type:     asset.CreateBond,
  3119  			ID:       tx.TxID,
  3120  			Amount:   uint64(msgTx.TxOut[0].Value),
  3121  			Fees:     fee,
  3122  			BondInfo: bondInfo,
  3123  		}, nil
  3124  	}
  3125  
  3126  	// Any other P2SH may be a swap or a send. We cannot determine unless we
  3127  	// look up the transaction that spends this UTXO.
  3128  	txPaysToScriptHash := func(msgTx *zTx) (v uint64) {
  3129  		for _, txOut := range msgTx.TxOut {
  3130  			if txscript.IsPayToScriptHash(txOut.PkScript) {
  3131  				v += uint64(txOut.Value)
  3132  			}
  3133  		}
  3134  		return
  3135  	}
  3136  	if v := txPaysToScriptHash(msgTx); v > 0 {
  3137  		return &asset.WalletTransaction{
  3138  			Type:   asset.SwapOrSend,
  3139  			ID:     tx.TxID,
  3140  			Amount: v,
  3141  			Fees:   fee,
  3142  		}, nil
  3143  	}
  3144  
  3145  	// Helper function will help us identify inputs that spend P2SH contracts.
  3146  	containsContractAtPushIndex := func(msgTx *zTx, idx int, isContract func(contract []byte) bool) bool {
  3147  	txinloop:
  3148  		for _, txIn := range msgTx.TxIn {
  3149  			// not segwit
  3150  			const scriptVer = 0
  3151  			tokenizer := txscript.MakeScriptTokenizer(scriptVer, txIn.SignatureScript)
  3152  			for i := 0; i <= idx; i++ { // contract is 5th item item in redemption and 4th in refund
  3153  				if !tokenizer.Next() {
  3154  					continue txinloop
  3155  				}
  3156  			}
  3157  			if isContract(tokenizer.Data()) {
  3158  				return true
  3159  			}
  3160  		}
  3161  		return false
  3162  	}
  3163  
  3164  	// Swap redemptions and refunds
  3165  	contractIsSwap := func(contract []byte) bool {
  3166  		_, _, _, _, err := dexbtc.ExtractSwapDetails(contract, false /* segwit */, w.btcParams)
  3167  		return err == nil
  3168  	}
  3169  	redeemsSwap := func(msgTx *zTx) bool {
  3170  		return containsContractAtPushIndex(msgTx, 4, contractIsSwap)
  3171  	}
  3172  	if redeemsSwap(msgTx) {
  3173  		return &asset.WalletTransaction{
  3174  			Type:   asset.Redeem,
  3175  			ID:     tx.TxID,
  3176  			Amount: totalOut + fee,
  3177  			Fees:   fee,
  3178  		}, nil
  3179  	}
  3180  	refundsSwap := func(msgTx *zTx) bool {
  3181  		return containsContractAtPushIndex(msgTx, 3, contractIsSwap)
  3182  	}
  3183  	if refundsSwap(msgTx) {
  3184  		return &asset.WalletTransaction{
  3185  			Type:   asset.Refund,
  3186  			ID:     tx.TxID,
  3187  			Amount: totalOut + fee,
  3188  			Fees:   fee,
  3189  		}, nil
  3190  	}
  3191  
  3192  	// Bond refunds
  3193  	redeemsBond := func(msgTx *zTx) (bool, *asset.BondTxInfo) {
  3194  		var bondInfo *asset.BondTxInfo
  3195  		isBond := func(contract []byte) bool {
  3196  			const scriptVer = 0
  3197  			lockTime, pkHash, err := dexbtc.ExtractBondDetailsV0(scriptVer, contract)
  3198  			if err != nil {
  3199  				return false
  3200  			}
  3201  			bondInfo = &asset.BondTxInfo{
  3202  				AccountID: []byte{}, // Could look for the bond tx to get this, I guess.
  3203  				LockTime:  uint64(lockTime),
  3204  				BondID:    pkHash[:],
  3205  			}
  3206  			return true
  3207  		}
  3208  		return containsContractAtPushIndex(msgTx, 2, isBond), bondInfo
  3209  	}
  3210  	if isBondRedemption, bondInfo := redeemsBond(msgTx); isBondRedemption {
  3211  		return &asset.WalletTransaction{
  3212  			Type:     asset.RedeemBond,
  3213  			ID:       tx.TxID,
  3214  			Amount:   totalOut,
  3215  			Fees:     fee,
  3216  			BondInfo: bondInfo,
  3217  		}, nil
  3218  	}
  3219  
  3220  	const scriptVersion = 0
  3221  
  3222  	allOutputsPayUs := func(msgTx *zTx) bool {
  3223  		for _, txOut := range msgTx.TxOut {
  3224  			_, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams)
  3225  			if err != nil {
  3226  				w.log.Errorf("ExtractAddrs error: %w", err)
  3227  				return false
  3228  			}
  3229  			if len(addrs) != 1 { // sanity check
  3230  				return false
  3231  			}
  3232  
  3233  			addr, err := dexzec.EncodeAddress(addrs[0], w.addrParams)
  3234  			if err != nil {
  3235  				w.log.Errorf("unable to encode address: %w", err)
  3236  				return false
  3237  			}
  3238  			owns, err := w.OwnsDepositAddress(addr)
  3239  			if err != nil {
  3240  				w.log.Errorf("w.OwnsDepositAddress error: %w", err)
  3241  				return false
  3242  			}
  3243  			if !owns {
  3244  				return false
  3245  			}
  3246  		}
  3247  
  3248  		return true
  3249  	}
  3250  
  3251  	if tx.Category == sendCategory && allOutputsPayUs(msgTx) && len(msgTx.TxIn) == 1 {
  3252  		return &asset.WalletTransaction{
  3253  			Type: asset.Split,
  3254  			ID:   tx.TxID,
  3255  			Fees: fee,
  3256  		}, nil
  3257  	}
  3258  
  3259  	txOutDirection := func(msgTx *zTx) (in, out uint64) {
  3260  		for _, txOut := range msgTx.TxOut {
  3261  			_, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams)
  3262  			if err != nil {
  3263  				w.log.Errorf("ExtractAddrs error: %w", err)
  3264  				continue
  3265  			}
  3266  			if len(addrs) != 1 { // sanity check
  3267  				continue
  3268  			}
  3269  
  3270  			addr, err := dexzec.EncodeAddress(addrs[0], w.addrParams)
  3271  			if err != nil {
  3272  				w.log.Errorf("unable to encode address: %w", err)
  3273  				continue
  3274  			}
  3275  			owns, err := w.OwnsDepositAddress(addr)
  3276  			if err != nil {
  3277  				w.log.Errorf("w.OwnsDepositAddress error: %w", err)
  3278  				continue
  3279  			}
  3280  			if owns {
  3281  				in += uint64(txOut.Value)
  3282  			} else {
  3283  				out += uint64(txOut.Value)
  3284  			}
  3285  		}
  3286  		return
  3287  	}
  3288  
  3289  	in, out := txOutDirection(msgTx)
  3290  
  3291  	getRecipient := func(msgTx *zTx, receive bool) *string {
  3292  		for _, txOut := range msgTx.TxOut {
  3293  			_, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams)
  3294  			if err != nil {
  3295  				w.log.Errorf("ExtractAddrs error: %w", err)
  3296  				continue
  3297  			}
  3298  			if len(addrs) != 1 { // sanity check
  3299  				continue
  3300  			}
  3301  
  3302  			addr, err := dexzec.EncodeAddress(addrs[0], w.addrParams)
  3303  			if err != nil {
  3304  				w.log.Errorf("unable to encode address: %w", err)
  3305  				continue
  3306  			}
  3307  			owns, err := w.OwnsDepositAddress(addr)
  3308  			if err != nil {
  3309  				w.log.Errorf("w.OwnsDepositAddress error: %w", err)
  3310  				continue
  3311  			}
  3312  
  3313  			if receive == owns {
  3314  				return &addr
  3315  			}
  3316  		}
  3317  		return nil
  3318  	}
  3319  
  3320  	if tx.Category == sendCategory {
  3321  		txType := asset.Send
  3322  		if allOutputsPayUs(msgTx) {
  3323  			txType = asset.SelfSend
  3324  		}
  3325  		return &asset.WalletTransaction{
  3326  			Type:      txType,
  3327  			ID:        tx.TxID,
  3328  			Amount:    out,
  3329  			Fees:      fee,
  3330  			Recipient: getRecipient(msgTx, false),
  3331  		}, nil
  3332  	}
  3333  
  3334  	return &asset.WalletTransaction{
  3335  		Type:      asset.Receive,
  3336  		ID:        tx.TxID,
  3337  		Amount:    in,
  3338  		Fees:      fee,
  3339  		Recipient: getRecipient(msgTx, true),
  3340  	}, nil
  3341  }
  3342  
  3343  // addUnknownTransactionsToHistory checks for any transactions the wallet has
  3344  // made or recieved that are not part of the transaction history. It scans
  3345  // from the last point to which it had previously scanned to the current tip.
  3346  func (w *zecWallet) addUnknownTransactionsToHistory(tip uint64) {
  3347  	txHistoryDB := w.txDB()
  3348  
  3349  	// Zcash has a maximum reorg length of 100 blocks.
  3350  	const blockQueryBuffer = 100
  3351  	var blockToQuery uint64
  3352  	lastQuery := w.receiveTxLastQuery.Load()
  3353  	if lastQuery == 0 {
  3354  		// TODO: use wallet birthday instead of block 0.
  3355  		// blockToQuery = 0
  3356  	} else if lastQuery < tip-blockQueryBuffer {
  3357  		blockToQuery = lastQuery - blockQueryBuffer
  3358  	} else {
  3359  		blockToQuery = tip - blockQueryBuffer
  3360  	}
  3361  
  3362  	txs, err := w.listSinceBlock(int64(blockToQuery))
  3363  	if err != nil {
  3364  		w.log.Errorf("Error listing transactions since block %d: %v", blockToQuery, err)
  3365  		return
  3366  	}
  3367  
  3368  	for _, tx := range txs {
  3369  		if w.ctx.Err() != nil {
  3370  			return
  3371  		}
  3372  		txHash, err := chainhash.NewHashFromStr(tx.TxID)
  3373  		if err != nil {
  3374  			w.log.Errorf("Error decoding tx hash %s: %v", tx.TxID, err)
  3375  			continue
  3376  		}
  3377  		_, err = txHistoryDB.GetTx(txHash.String())
  3378  		if err == nil {
  3379  			continue
  3380  		}
  3381  		if !errors.Is(err, asset.CoinNotFoundError) {
  3382  			w.log.Errorf("Error getting tx %s: %v", txHash.String(), err)
  3383  			continue
  3384  		}
  3385  		wt, err := w.idUnknownTx(&tx)
  3386  		if err != nil {
  3387  			w.log.Errorf("error identifying transaction: %v", err)
  3388  			continue
  3389  		}
  3390  
  3391  		if tx.BlockIndex != nil && *tx.BlockIndex > 0 && *tx.BlockIndex < int64(tip-blockQueryBuffer) {
  3392  			wt.BlockNumber = uint64(*tx.BlockIndex)
  3393  			wt.Timestamp = uint64(tx.BlockTime)
  3394  		}
  3395  
  3396  		// Don't send notifications for the initial sync to avoid spamming the
  3397  		// front end. A notification is sent at the end of the initial sync.
  3398  		w.addTxToHistory(wt, txHash, true, blockToQuery == 0)
  3399  	}
  3400  
  3401  	w.receiveTxLastQuery.Store(tip)
  3402  	err = txHistoryDB.SetLastReceiveTxQuery(tip)
  3403  	if err != nil {
  3404  		w.log.Errorf("Error setting last query to %d: %v", tip, err)
  3405  	}
  3406  
  3407  	if blockToQuery == 0 {
  3408  		w.emit.TransactionHistorySyncedNote()
  3409  	}
  3410  }
  3411  
  3412  // syncTxHistory checks to see if there are any transactions which the wallet
  3413  // has made or recieved that are not part of the transaction history, then
  3414  // identifies and adds them. It also checks all the pending transactions to see
  3415  // if they have been mined into a block, and if so, updates the transaction
  3416  // history to reflect the block height.
  3417  func (w *zecWallet) syncTxHistory(tip uint64) {
  3418  	if !w.syncingTxHistory.CompareAndSwap(false, true) {
  3419  		return
  3420  	}
  3421  	defer w.syncingTxHistory.Store(false)
  3422  
  3423  	txHistoryDB := w.txDB()
  3424  	if txHistoryDB == nil {
  3425  		// It's actually impossible to get here, because we error and return
  3426  		// early in Connect if startTxHistoryDB returns an error, but we'll
  3427  		// log this for good measure anyway.
  3428  		w.log.Error("Transaction history database was not initialized")
  3429  		return
  3430  	}
  3431  
  3432  	ss, err := w.SyncStatus()
  3433  	if err != nil {
  3434  		w.log.Errorf("Error getting sync status: %v", err)
  3435  		return
  3436  	}
  3437  	if !ss.Synced {
  3438  		return
  3439  	}
  3440  
  3441  	w.addUnknownTransactionsToHistory(tip)
  3442  
  3443  	pendingTxsCopy := make(map[chainhash.Hash]btc.ExtendedWalletTx, len(w.pendingTxs))
  3444  	w.pendingTxsMtx.RLock()
  3445  	for hash, tx := range w.pendingTxs {
  3446  		pendingTxsCopy[hash] = *tx
  3447  	}
  3448  	w.pendingTxsMtx.RUnlock()
  3449  
  3450  	handlePendingTx := func(txHash chainhash.Hash, tx *btc.ExtendedWalletTx) {
  3451  		if !tx.Submitted {
  3452  			return
  3453  		}
  3454  
  3455  		gtr, err := getTransaction(w, &txHash)
  3456  		if errors.Is(err, asset.CoinNotFoundError) {
  3457  			err = txHistoryDB.RemoveTx(txHash.String())
  3458  			if err == nil {
  3459  				w.pendingTxsMtx.Lock()
  3460  				delete(w.pendingTxs, txHash)
  3461  				w.pendingTxsMtx.Unlock()
  3462  			} else {
  3463  				// Leave it in the pendingPendingTxs and attempt to remove it
  3464  				// again next time.
  3465  				w.log.Errorf("Error removing tx %s from the history store: %v", txHash.String(), err)
  3466  			}
  3467  			return
  3468  		}
  3469  		if err != nil {
  3470  			if w.ctx.Err() != nil {
  3471  				return
  3472  			}
  3473  			w.log.Errorf("Error getting transaction %s: %v", txHash, err)
  3474  			return
  3475  		}
  3476  
  3477  		var updated bool
  3478  		if gtr.blockHash != nil && *gtr.blockHash != (chainhash.Hash{}) {
  3479  			block, _, err := getVerboseBlockHeader(w, gtr.blockHash)
  3480  			if err != nil {
  3481  				w.log.Errorf("Error getting block height for %s: %v", gtr.blockHash, err)
  3482  				return
  3483  			}
  3484  			blockHeight := block.Height
  3485  			if tx.BlockNumber != uint64(blockHeight) || tx.Timestamp != uint64(block.Time) {
  3486  				tx.BlockNumber = uint64(blockHeight)
  3487  				tx.Timestamp = uint64(block.Time)
  3488  				updated = true
  3489  			}
  3490  		} else if gtr.blockHash == nil && tx.BlockNumber != 0 {
  3491  			tx.BlockNumber = 0
  3492  			tx.Timestamp = 0
  3493  			updated = true
  3494  		}
  3495  
  3496  		var confs uint64
  3497  		if tx.BlockNumber > 0 && tip >= tx.BlockNumber {
  3498  			confs = tip - tx.BlockNumber + 1
  3499  		}
  3500  		if confs >= defaultConfTarget {
  3501  			tx.Confirmed = true
  3502  			updated = true
  3503  		}
  3504  
  3505  		if updated {
  3506  			err = txHistoryDB.StoreTx(tx)
  3507  			if err != nil {
  3508  				w.log.Errorf("Error updating tx %s: %v", txHash, err)
  3509  				return
  3510  			}
  3511  
  3512  			w.pendingTxsMtx.Lock()
  3513  			if tx.Confirmed {
  3514  				delete(w.pendingTxs, txHash)
  3515  			} else {
  3516  				w.pendingTxs[txHash] = tx
  3517  			}
  3518  			w.pendingTxsMtx.Unlock()
  3519  
  3520  			w.emit.TransactionNote(tx.WalletTransaction, false)
  3521  		}
  3522  	}
  3523  
  3524  	for hash, tx := range pendingTxsCopy {
  3525  		handlePendingTx(hash, &tx)
  3526  	}
  3527  }