func (c *RemovedTransactionNotificationsClient) Done() {
	go func() {
		for range c.C {
		}
	}()
	go func() {
		s := c.server
		s.mu.Lock()
		clients := s.removedTransactionClients
		for i, ch := range clients {
			if c.C == ch {
				clients[i] = clients[len(clients)-1]
				s.removedTransactionClients = clients[:len(clients)-1]
				close(ch)
				break
			}
		}
		s.mu.Unlock()
	}()
}
Done deregisters the client from the server and drains any remaining
messages.  It must be called exactly once when the client is finished
receiving notifications.
package wallet
import (
	"bytes"
	"context"
	"sync"
	"decred.org/dcrwallet/v2/errors"
	"decred.org/dcrwallet/v2/internal/compat"
	"decred.org/dcrwallet/v2/wallet/udb"
	"decred.org/dcrwallet/v2/wallet/walletdb"
	"github.com/decred/dcrd/blockchain/stake/v4"
	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/hdkeychain/v3"
	"github.com/decred/dcrd/txscript/v4"
	"github.com/decred/dcrd/txscript/v4/stdaddr"
	"github.com/decred/dcrd/wire"
)
type NotificationServer struct {
	transactions []chan *TransactionNotifications
	currentTxNtfn             *TransactionNotifications
	accountClients            []chan *AccountNotification
	tipChangedClients         []chan *MainTipChangedNotification
	confClients               []*ConfirmationNotificationsClient
	removedTransactionClients []chan *RemovedTransactionNotification
	mu                        sync.Mutex  
	wallet                    *Wallet     
}
func newNotificationServer(wallet *Wallet) *NotificationServer {
	return &NotificationServer{
		wallet: wallet,
	}
}
func lookupInputAccount(dbtx walletdb.ReadTx, w *Wallet, details *udb.TxDetails, deb udb.DebitRecord) uint32 {
	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
	txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
	prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint
	prev, err := w.txStore.TxDetails(txmgrNs, &prevOP.Hash)
	if err != nil {
		log.Errorf("Cannot query previous transaction details for %v: %v", prevOP.Hash, err)
		return 0
	}
	if prev == nil {
		log.Errorf("Missing previous transaction %v", prevOP.Hash)
		return 0
	}
	prevOut := prev.MsgTx.TxOut[prevOP.Index]
	_, addrs, _, err := txscript.ExtractPkScriptAddrs(prevOut.Version, prevOut.PkScript, w.chainParams, true)  
	var inputAcct uint32
	if err == nil && len(addrs) > 0 {
		inputAcct, err = w.manager.AddrAccount(addrmgrNs, addrs[0])
	}
	if err != nil {
		log.Errorf("Cannot fetch account for previous output %v: %v", prevOP, err)
		inputAcct = 0
	}
	return inputAcct
}
func lookupOutputChain(dbtx walletdb.ReadTx, w *Wallet, details *udb.TxDetails,
	cred udb.CreditRecord) (account uint32, internal bool, address stdaddr.Address,
	amount int64, outputScript []byte) {
	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
	output := details.MsgTx.TxOut[cred.Index]
	_, addrs, _, err := txscript.ExtractPkScriptAddrs(output.Version, output.PkScript, w.chainParams, true)  
	var ma udb.ManagedAddress
	if err == nil && len(addrs) > 0 {
		ma, err = w.manager.Address(addrmgrNs, addrs[0])
	}
	if err != nil {
		log.Errorf("Cannot fetch account for wallet output: %v", err)
	} else {
		account = ma.Account()
		internal = ma.Internal()
		address = ma.Address()
		amount = output.Value
		outputScript = output.PkScript
	}
	return
}
func makeTxSummary(dbtx walletdb.ReadTx, w *Wallet, details *udb.TxDetails) TransactionSummary {
	serializedTx := details.SerializedTx
	if serializedTx == nil {
		var buf bytes.Buffer
		buf.Grow(details.MsgTx.SerializeSize())
		err := details.MsgTx.Serialize(&buf)
		if err != nil {
			log.Errorf("Transaction serialization: %v", err)
		}
		serializedTx = buf.Bytes()
	}
	var fee dcrutil.Amount
	if len(details.Debits) == len(details.MsgTx.TxIn) {
		for _, deb := range details.Debits {
			fee += deb.Amount
		}
		for _, txOut := range details.MsgTx.TxOut {
			fee -= dcrutil.Amount(txOut.Value)
		}
	}
	var inputs []TransactionSummaryInput
	if len(details.Debits) != 0 {
		inputs = make([]TransactionSummaryInput, len(details.Debits))
		for i, d := range details.Debits {
			inputs[i] = TransactionSummaryInput{
				Index:           d.Index,
				PreviousAccount: lookupInputAccount(dbtx, w, details, d),
				PreviousAmount:  d.Amount,
			}
		}
	}
	outputs := make([]TransactionSummaryOutput, 0, len(details.MsgTx.TxOut))
	for i := range details.MsgTx.TxOut {
		credIndex := len(outputs)
		mine := len(details.Credits) > credIndex && details.Credits[credIndex].Index == uint32(i)
		if !mine {
			continue
		}
		acct, internal, address, amount, outputScript := lookupOutputChain(dbtx, w, details, details.Credits[credIndex])
		output := TransactionSummaryOutput{
			Index:        uint32(i),
			Account:      acct,
			Internal:     internal,
			Amount:       dcrutil.Amount(amount),
			Address:      address,
			OutputScript: outputScript,
		}
		outputs = append(outputs, output)
	}
	var transactionType = TxTransactionType(&details.MsgTx)
	receiveTime := details.Received
	if details.Height() >= 0 && details.Block.Time.Before(receiveTime) {
		receiveTime = details.Block.Time
	}
	return TransactionSummary{
		Hash:        &details.Hash,
		Transaction: serializedTx,
		MyInputs:    inputs,
		MyOutputs:   outputs,
		Fee:         fee,
		Timestamp:   receiveTime.Unix(),
		Type:        transactionType,
	}
}
func totalBalances(dbtx walletdb.ReadTx, w *Wallet, m map[uint32]dcrutil.Amount) error {
	addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
	unspent, err := w.txStore.UnspentOutputs(dbtx)
	if err != nil {
		return err
	}
	for i := range unspent {
		output := unspent[i]
		var outputAcct uint32
		_, addrs, _, err := txscript.ExtractPkScriptAddrs(
			0, output.PkScript, w.chainParams, true)  
		if err == nil && len(addrs) > 0 {
			outputAcct, err = w.manager.AddrAccount(addrmgrNs, addrs[0])
		}
		if err == nil {
			_, ok := m[outputAcct]
			if ok {
				m[outputAcct] += output.Amount
			}
		}
	}
	return nil
}
func flattenBalanceMap(m map[uint32]dcrutil.Amount) []AccountBalance {
	s := make([]AccountBalance, 0, len(m))
	for k, v := range m {
		s = append(s, AccountBalance{Account: k, TotalBalance: v})
	}
	return s
}
func relevantAccounts(w *Wallet, m map[uint32]dcrutil.Amount, txs []TransactionSummary) {
	for _, tx := range txs {
		for _, d := range tx.MyInputs {
			m[d.PreviousAccount] = 0
		}
		for _, c := range tx.MyOutputs {
			m[c.Account] = 0
		}
	}
}
func (s *NotificationServer) notifyUnminedTransaction(dbtx walletdb.ReadTx, details *udb.TxDetails) {
	defer s.mu.Unlock()
	s.mu.Lock()
	if s.currentTxNtfn != nil {
		log.Tracef("Notifying unmined tx notification while creating notification for blocks")
	}
	clients := s.transactions
	if len(clients) == 0 {
		return
	}
	unminedTxs := []TransactionSummary{makeTxSummary(dbtx, s.wallet, details)}
	unminedHashes, err := s.wallet.txStore.UnminedTxHashes(dbtx.ReadBucket(wtxmgrNamespaceKey))
	if err != nil {
		log.Errorf("Cannot fetch unmined transaction hashes: %v", err)
		return
	}
	bals := make(map[uint32]dcrutil.Amount)
	relevantAccounts(s.wallet, bals, unminedTxs)
	err = totalBalances(dbtx, s.wallet, bals)
	if err != nil {
		log.Errorf("Cannot determine balances for relevant accounts: %v", err)
		return
	}
	n := &TransactionNotifications{
		UnminedTransactions:      unminedTxs,
		UnminedTransactionHashes: unminedHashes,
		NewBalances:              flattenBalanceMap(bals),
	}
	for _, c := range clients {
		c <- n
	}
}
func (s *NotificationServer) notifyDetachedBlock(header *wire.BlockHeader) {
	defer s.mu.Unlock()
	s.mu.Lock()
	if s.currentTxNtfn == nil {
		s.currentTxNtfn = &TransactionNotifications{}
	}
	s.currentTxNtfn.DetachedBlocks = append(s.currentTxNtfn.DetachedBlocks, header)
}
func (s *NotificationServer) notifyMinedTransaction(dbtx walletdb.ReadTx, details *udb.TxDetails, block *udb.BlockMeta) {
	defer s.mu.Unlock()
	s.mu.Lock()
	if s.currentTxNtfn == nil {
		s.currentTxNtfn = &TransactionNotifications{}
	}
	n := len(s.currentTxNtfn.AttachedBlocks)
	if n == 0 || s.currentTxNtfn.AttachedBlocks[n-1].Header.BlockHash() != block.Hash {
		return
	}
	txs := &s.currentTxNtfn.AttachedBlocks[n-1].Transactions
	*txs = append(*txs, makeTxSummary(dbtx, s.wallet, details))
}
func (s *NotificationServer) notifyAttachedBlock(dbtx walletdb.ReadTx, block *wire.BlockHeader, blockHash *chainhash.Hash) {
	defer s.mu.Unlock()
	s.mu.Lock()
	if s.currentTxNtfn == nil {
		s.currentTxNtfn = &TransactionNotifications{}
	}
	n := len(s.currentTxNtfn.AttachedBlocks)
	if n == 0 || s.currentTxNtfn.AttachedBlocks[n-1].Header.BlockHash() != *blockHash {
		s.currentTxNtfn.AttachedBlocks = append(s.currentTxNtfn.AttachedBlocks, Block{
			Header: block,
		})
	}
}
func (s *NotificationServer) sendAttachedBlockNotification(ctx context.Context) {
	s.mu.Lock()
	if len(s.transactions) == 0 {
		s.currentTxNtfn = nil
		s.mu.Unlock()
		return
	}
	currentTxNtfn := s.currentTxNtfn
	s.currentTxNtfn = nil
	s.mu.Unlock()
	var (
		w             = s.wallet
		bals          = make(map[uint32]dcrutil.Amount)
		unminedHashes []*chainhash.Hash
	)
	err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
		var err error
		unminedHashes, err = w.txStore.UnminedTxHashes(txmgrNs)
		if err != nil {
			return err
		}
		for _, b := range currentTxNtfn.AttachedBlocks {
			relevantAccounts(w, bals, b.Transactions)
		}
		return totalBalances(dbtx, w, bals)
	})
	if err != nil {
		log.Errorf("Failed to construct attached blocks notification: %v", err)
		return
	}
	currentTxNtfn.UnminedTransactionHashes = unminedHashes
	currentTxNtfn.NewBalances = flattenBalanceMap(bals)
	s.mu.Lock()
	for _, c := range s.transactions {
		c <- currentTxNtfn
	}
	s.mu.Unlock()
}
type TransactionNotifications struct {
	AttachedBlocks           []Block
	DetachedBlocks           []*wire.BlockHeader
	UnminedTransactions      []TransactionSummary
	UnminedTransactionHashes []*chainhash.Hash
	NewBalances              []AccountBalance
}
type Block struct {
	Header       *wire.BlockHeader  
	Transactions []TransactionSummary
}
type TransactionSummary struct {
	Hash        *chainhash.Hash
	Transaction []byte
	MyInputs    []TransactionSummaryInput
	MyOutputs   []TransactionSummaryOutput
	Fee         dcrutil.Amount
	Timestamp   int64
	Type        TransactionType
}
type TransactionType int8
const (
	TransactionTypeRegular TransactionType = iota
	TransactionTypeCoinbase
	TransactionTypeTicketPurchase
	TransactionTypeVote
	TransactionTypeRevocation
)
func TxTransactionType(tx *wire.MsgTx) TransactionType {
	if compat.IsEitherCoinBaseTx(tx) {
		return TransactionTypeCoinbase
	} else if stake.IsSStx(tx) {
		return TransactionTypeTicketPurchase
	} else if stake.IsSSGen(tx, true  ) {
		return TransactionTypeVote
	} else if isRevocation(tx) {
		return TransactionTypeRevocation
	} else {
		return TransactionTypeRegular
	}
}
type TransactionSummaryInput struct {
	Index           uint32
	PreviousAccount uint32
	PreviousAmount  dcrutil.Amount
}
type TransactionSummaryOutput struct {
	Index        uint32
	Account      uint32
	Internal     bool
	Amount       dcrutil.Amount
	Address      stdaddr.Address
	OutputScript []byte
}
type AccountBalance struct {
	Account      uint32
	TotalBalance dcrutil.Amount
}
type TransactionNotificationsClient struct {
	C      <-chan *TransactionNotifications
	server *NotificationServer
}
func (s *NotificationServer) TransactionNotifications() TransactionNotificationsClient {
	c := make(chan *TransactionNotifications)
	s.mu.Lock()
	s.transactions = append(s.transactions, c)
	s.mu.Unlock()
	return TransactionNotificationsClient{
		C:      c,
		server: s,
	}
}
func (c *TransactionNotificationsClient) Done() {
	go func() {
		for range c.C {
		}
	}()
	go func() {
		s := c.server
		s.mu.Lock()
		clients := s.transactions
		for i, ch := range clients {
			if c.C == ch {
				clients[i] = clients[len(clients)-1]
				s.transactions = clients[:len(clients)-1]
				close(ch)
				break
			}
		}
		s.mu.Unlock()
	}()
}
type RemovedTransactionNotification struct {
	TxHash chainhash.Hash
}
type RemovedTransactionNotificationsClient struct {
	C      chan *RemovedTransactionNotification
	server *NotificationServer
}
func (s *NotificationServer) RemovedTransactionNotifications() RemovedTransactionNotificationsClient {
	c := make(chan *RemovedTransactionNotification)
	s.mu.Lock()
	s.removedTransactionClients = append(s.removedTransactionClients, c)
	s.mu.Unlock()
	return RemovedTransactionNotificationsClient{
		C:      c,
		server: s,
	}
}