diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -258,11 +258,6 @@
      * >0 : is a coinbase transaction which matures in this many blocks
      */
     int GetBlocksToMaturity() const;
-    /**
-     * Pass this transaction to the mempool. Fails if absolute fee exceeds
-     * absurd fee.
-     */
-    bool AcceptToMemoryPool(const Amount nAbsurdFee, CValidationState &state);
     bool hashUnset() const {
         return (hashBlock.IsNull() || hashBlock == ABANDON_HASH);
     }
@@ -319,6 +314,7 @@
     mutable bool fImmatureWatchCreditCached;
     mutable bool fAvailableWatchCreditCached;
     mutable bool fChangeCached;
+    mutable bool fInMempool;
     mutable Amount nDebitCached;
     mutable Amount nCreditCached;
     mutable Amount nImmatureCreditCached;
@@ -354,6 +350,7 @@
         fImmatureWatchCreditCached = false;
         fAvailableWatchCreditCached = false;
         fChangeCached = false;
+        fInMempool = false;
         nDebitCached = Amount::zero();
         nCreditCached = Amount::zero();
         nImmatureCreditCached = Amount::zero();
@@ -458,6 +455,12 @@
     // RelayWalletTransaction may only be called if fBroadcastTransactions!
     bool RelayWalletTransaction(CConnman *connman);
 
+    /**
+     * Pass this transaction to the mempool. Fails if absolute fee exceeds
+     * absurd fee.
+     */
+    bool AcceptToMemoryPool(const Amount nAbsurdFee, CValidationState &state);
+
     std::set<TxId> GetConflicts() const;
 };
 
@@ -922,6 +925,7 @@
     CBlockIndex *ScanForWalletTransactions(CBlockIndex *pindexStart,
                                            CBlockIndex *pindexStop,
                                            bool fUpdate = false);
+    void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
     void ReacceptWalletTransactions();
     void ResendWalletTransactions(int64_t nBestBlockTime,
                                   CConnman *connman) override;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1265,6 +1265,19 @@
 void CWallet::TransactionAddedToMempool(const CTransactionRef &ptx) {
     LOCK2(cs_main, cs_wallet);
     SyncTransaction(ptx);
+
+    auto it = mapWallet.find(ptx->GetId());
+    if (it != mapWallet.end()) {
+        it->second.fInMempool = true;
+    }
+}
+
+void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
+    LOCK(cs_wallet);
+    auto it = mapWallet.find(ptx->GetId());
+    if (it != mapWallet.end()) {
+        it->second.fInMempool = false;
+    }
 }
 
 void CWallet::BlockConnected(
@@ -1281,10 +1294,12 @@
     // that the conflicted transaction was evicted.
     for (const CTransactionRef &ptx : vtxConflicted) {
         SyncTransaction(ptx);
+        TransactionRemovedFromMempool(ptx);
     }
 
     for (size_t i = 0; i < pblock->vtx.size(); i++) {
         SyncTransaction(pblock->vtx[i], pindex, i);
+        TransactionRemovedFromMempool(pblock->vtx[i]);
     }
 
     m_last_block_processed = pindex;
@@ -1993,12 +2008,7 @@
 }
 
 bool CWalletTx::InMempool() const {
-    LOCK(g_mempool.cs);
-    if (g_mempool.exists(GetId())) {
-        return true;
-    }
-
-    return false;
+    return fInMempool;
 }
 
 bool CWalletTx::IsTrusted() const {
@@ -3123,16 +3133,20 @@
     // Track how many getdata requests our transaction gets.
     mapRequestCount[wtxNew.GetId()] = 0;
 
+    // Get the inserted-CWalletTx from mapWallet so that the
+    // fInMempool flag is cached properly
+    CWalletTx &wtx = mapWallet[wtxNew.GetId()];
+
     if (fBroadcastTransactions) {
         // Broadcast
-        if (!wtxNew.AcceptToMemoryPool(maxTxFee, state)) {
-            LogPrintf("CommitTransaction(): Transaction cannot be "
-                      "broadcast immediately, %s\n",
+        if (!wtx.AcceptToMemoryPool(maxTxFee, state)) {
+            LogPrintf("CommitTransaction(): Transaction cannot be broadcast "
+                      "immediately, %s\n",
                       state.GetRejectReason());
             // TODO: if we expect the failure to be long term or permanent,
             // instead delete wtx from the wallet and return failure.
         } else {
-            wtxNew.RelayWalletTransaction(connman);
+            wtx.RelayWalletTransaction(connman);
         }
     }
 
@@ -4289,8 +4303,17 @@
     return GetBlocksToMaturity() > 0;
 }
 
-bool CMerkleTx::AcceptToMemoryPool(const Amount nAbsurdFee,
+bool CWalletTx::AcceptToMemoryPool(const Amount nAbsurdFee,
                                    CValidationState &state) {
-    return ::AcceptToMemoryPool(GetConfig(), g_mempool, state, tx, true,
-                                nullptr, false, nAbsurdFee);
+    // We must set fInMempool here - while it will be re-set to true by the
+    // entered-mempool callback, if we did not there would be a race where a
+    // user could call sendmoney in a loop and hit spurious out of funds errors
+    // because we think that the transaction they just generated's change is
+    // unavailable as we're not yet aware its in mempool.
+    bool ret = ::AcceptToMemoryPool(
+        GetConfig(), g_mempool, state, tx, true /* fLimitFree */,
+        nullptr /* pfMissingInputs */, false /* fOverrideMempoolLimit */,
+        nAbsurdFee);
+    fInMempool = ret;
+    return ret;
 }