func (w *Watcher) expiryHandler(blockChan chan int32, errChan chan error) {
	defer w.wg.Done()
	var (
		bestHeight uint32
		expirations = make(map[[33]byte]uint32)
		expirationsPerHeight = make(map[uint32][]*btcec.PublicKey)
	)
	select {
	case newBlock := <-blockChan:
		bestHeight = uint32(newBlock)
	case err := <-errChan:
		log.Errorf("Unable to receive initial block notification: %v",
			err)
	case <-w.quit:
		return
	}
	for {
		select {
		case newBlock := <-blockChan:
			bestHeight = uint32(newBlock)
			for _, traderKey := range expirationsPerHeight[bestHeight] {
				var accountKey [33]byte
				copy(accountKey[:], traderKey.SerializeCompressed())
				curExpiry, ok := expirations[accountKey]
				if !ok || bestHeight != curExpiry {
					continue
				}
				err := w.cfg.HandleAccountExpiry(
					traderKey, bestHeight,
				)
				if err != nil {
					log.Errorf("Unable to handle "+
						"expiration of account %x: %v",
						traderKey.SerializeCompressed(),
						err)
				}
			}
			delete(expirationsPerHeight, bestHeight)
		case err := <-errChan:
			log.Errorf("Unable to receive block notification: %v",
				err)
		case req := <-w.expiryReqs:
			var accountKey [33]byte
			copy(accountKey[:], req.traderKey.SerializeCompressed())
			if req.expiry <= bestHeight {
				err := w.cfg.HandleAccountExpiry(
					req.traderKey, bestHeight,
				)
				if err != nil {
					log.Errorf("Unable to handle "+
						"expiration of account %x: %v",
						req.traderKey.SerializeCompressed(),
						err)
				}
				delete(expirations, accountKey)
				continue
			}
			expirations[accountKey] = req.expiry
			expirationsPerHeight[req.expiry] = append(
				expirationsPerHeight[req.expiry], req.traderKey,
			)
		case <-w.quit:
			return
		}
	}
}
expiryHandler receives block notifications to determine when accounts expire.

NOTE: This must be run as a goroutine.
package watcher
import (
	"context"
	"errors"
	"sync"
	"github.com/btcsuite/btcd/btcec"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightninglabs/lndclient"
	"github.com/lightningnetwork/lnd/chainntnfs"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)
type expiryReq struct {
	traderKey *btcec.PublicKey
	expiry uint32
}
type Config struct {
	ChainNotifier lndclient.ChainNotifierClient
	HandleAccountConf func(*btcec.PublicKey, *chainntnfs.TxConfirmation) error
	HandleAccountSpend func(*btcec.PublicKey, *chainntnfs.SpendDetail) error
	HandleAccountExpiry func(*btcec.PublicKey, uint32) error
}
type Watcher struct {
	started sync.Once
	stopped sync.Once
	cfg Config
	expiryReqs chan *expiryReq
	wg         sync.WaitGroup
	quit       chan struct{}
	ctxCancels []func()
	cancelMtx    sync.Mutex
	spendCancels map[[33]byte]func()
	confCancels  map[[33]byte]func()
}
func New(cfg *Config) *Watcher {
	return &Watcher{
		cfg:          *cfg,
		expiryReqs:   make(chan *expiryReq),
		quit:         make(chan struct{}),
		spendCancels: make(map[[33]byte]func()),
		confCancels:  make(map[[33]byte]func()),
	}
}
func (w *Watcher) Start() error {
	var err error
	w.started.Do(func() {
		err = w.start()
	})
	return err
}
func (w *Watcher) start() error {
	ctxc, cancel := context.WithCancel(context.Background())
	blockChan, errChan, err := w.cfg.ChainNotifier.RegisterBlockEpochNtfn(
		ctxc,
	)
	if err != nil {
		cancel()
		return err
	}
	w.ctxCancels = append(w.ctxCancels, cancel)
	w.wg.Add(1)
	go w.expiryHandler(blockChan, errChan)
	return nil
}
func (w *Watcher) Stop() {
	w.stopped.Do(func() {
		close(w.quit)
		w.wg.Wait()
		for _, cancel := range w.ctxCancels {
			cancel()
		}
		w.cancelMtx.Lock()
		for _, cancel := range w.spendCancels {
			cancel()
		}
		for _, cancel := range w.confCancels {
			cancel()
		}
		w.cancelMtx.Unlock()
	})
}