diff --git a/src/init.cpp b/src/init.cpp
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -265,6 +265,7 @@
 #endif
     UnregisterAllValidationInterfaces();
     GetMainSignals().UnregisterBackgroundSignalScheduler();
+    GetMainSignals().UnregisterWithMempoolSignals(g_mempool);
     g_wallet_init_interface->Close();
     g_wallet_init_interface.reset();
     globalVerifyHandle.reset();
@@ -1785,6 +1786,7 @@
                                           "scheduler", serviceLoop));
 
     GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
+    GetMainSignals().RegisterWithMempoolSignals(g_mempool);
 
     /**
      * Start the RPC server.  It will be started in "warmup" mode and not
diff --git a/src/txmempool.h b/src/txmempool.h
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -596,6 +596,9 @@
     // to track size/count of descendant transactions. First version of
     // addUnchecked can be used to have it call CalculateMemPoolAncestors(), and
     // then invoke the second version.
+    // Note that addUnchecked is ONLY called from ATMP outside of tests
+    // and any other callers may break wallet's in-mempool tracking (due to
+    // lack of CValidationInterface::TransactionAddedToMempool callbacks).
     bool addUnchecked(const uint256 &hash, const CTxMemPoolEntry &entry,
                       bool validFeeEstimate = true);
     bool addUnchecked(const uint256 &hash, const CTxMemPoolEntry &entry,
diff --git a/src/validationinterface.h b/src/validationinterface.h
--- a/src/validationinterface.h
+++ b/src/validationinterface.h
@@ -20,6 +20,8 @@
 class CValidationState;
 class uint256;
 class CScheduler;
+class CTxMemPool;
+enum class MemPoolRemovalReason;
 
 // These functions dispatch to one or all registered wallets
 
@@ -52,6 +54,15 @@
      * Called on a background thread.
      */
     virtual void TransactionAddedToMempool(const CTransactionRef &ptxn) {}
+    /**
+     * Notifies listeners of a transaction leaving mempool.
+     *
+     * This only fires for transactions which leave mempool because of expiry,
+     * size limiting, reorg (changes in lock times/coinbase maturity), or
+     * replacement. This does not include any transactions which are included
+     * in BlockConnectedDisconnected either in block->vtx or in txnConflicted.
+     */
+    virtual void TransactionRemovedFromMempool(const CTransactionRef &ptx) {}
     /**
      * Notifies listeners of a block being connected.
      * Provides a vector of transactions evicted from the mempool as a result.
@@ -98,6 +109,8 @@
     friend void ::UnregisterValidationInterface(CValidationInterface *);
     friend void ::UnregisterAllValidationInterfaces();
 
+    void MempoolEntryRemoved(CTransactionRef tx, MemPoolRemovalReason reason);
+
 public:
     /**
      * Register a CScheduler to give callbacks which should run in the
@@ -112,6 +125,11 @@
     /** Call any remaining callbacks on the calling thread */
     void FlushBackgroundCallbacks();
 
+    /** Register with mempool to call TransactionRemovedFromMempool callbacks */
+    void RegisterWithMempoolSignals(CTxMemPool &pool);
+    /** Unregister with mempool */
+    void UnregisterWithMempoolSignals(CTxMemPool &pool);
+
     void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *,
                          bool fInitialDownload);
     void TransactionAddedToMempool(const CTransactionRef &);
diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp
--- a/src/validationinterface.cpp
+++ b/src/validationinterface.cpp
@@ -7,6 +7,7 @@
 #include "init.h"
 #include "scheduler.h"
 #include "sync.h"
+#include "txmempool.h"
 #include "util.h"
 
 #include <atomic>
@@ -26,6 +27,8 @@
         BlockConnected;
     boost::signals2::signal<void(const std::shared_ptr<const CBlock> &)>
         BlockDisconnected;
+    boost::signals2::signal<void(const CTransactionRef &)>
+        TransactionRemovedFromMempool;
     boost::signals2::signal<void(const CBlockLocator &)> SetBestChain;
     boost::signals2::signal<void(const uint256 &)> Inventory;
     boost::signals2::signal<void(int64_t nBestBlockTime, CConnman *connman)>
@@ -60,6 +63,16 @@
     m_internals->m_schedulerClient.EmptyQueue();
 }
 
+void CMainSignals::RegisterWithMempoolSignals(CTxMemPool &pool) {
+    pool.NotifyEntryRemoved.connect(
+        boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2));
+}
+
+void CMainSignals::UnregisterWithMempoolSignals(CTxMemPool &pool) {
+    pool.NotifyEntryRemoved.disconnect(
+        boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2));
+}
+
 CMainSignals &GetMainSignals() {
     return g_signals;
 }
@@ -73,6 +86,8 @@
         &CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3));
     g_signals.m_internals->BlockDisconnected.connect(
         boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1));
+    g_signals.m_internals->TransactionRemovedFromMempool.connect(boost::bind(
+        &CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1));
     g_signals.m_internals->SetBestChain.connect(
         boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
     g_signals.m_internals->Inventory.connect(
@@ -100,6 +115,8 @@
         &CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3));
     g_signals.m_internals->BlockDisconnected.disconnect(
         boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1));
+    g_signals.m_internals->TransactionRemovedFromMempool.disconnect(boost::bind(
+        &CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1));
     g_signals.m_internals->UpdatedBlockTip.disconnect(boost::bind(
         &CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3));
     g_signals.m_internals->NewPoWValidBlock.disconnect(boost::bind(
@@ -114,10 +131,19 @@
     g_signals.m_internals->TransactionAddedToMempool.disconnect_all_slots();
     g_signals.m_internals->BlockConnected.disconnect_all_slots();
     g_signals.m_internals->BlockDisconnected.disconnect_all_slots();
+    g_signals.m_internals->TransactionRemovedFromMempool.disconnect_all_slots();
     g_signals.m_internals->UpdatedBlockTip.disconnect_all_slots();
     g_signals.m_internals->NewPoWValidBlock.disconnect_all_slots();
 }
 
+void CMainSignals::MempoolEntryRemoved(CTransactionRef ptx,
+                                       MemPoolRemovalReason reason) {
+    if (reason != MemPoolRemovalReason::BLOCK &&
+        reason != MemPoolRemovalReason::CONFLICT) {
+        m_internals->TransactionRemovedFromMempool(ptx);
+    }
+}
+
 void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew,
                                    const CBlockIndex *pindexFork,
                                    bool fInitialDownload) {