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