diff --git a/src/Makefile.am b/src/Makefile.am
index ac31044c3e95efba5e77f81787ccefa487a609b6..560e6bb2bf2cd124c672491496e83912f6762741 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -49,6 +49,7 @@ BITCOIN_CORE_H = \
   main.h \
   miner.h \
   mruset.h \
+  names.h \
   netbase.h \
   net.h \
   noui.h \
@@ -98,11 +99,13 @@ libbitcoin_server_a_SOURCES = \
   leveldbwrapper.cpp \
   main.cpp \
   miner.cpp \
+  names.cpp \
   net.cpp \
   noui.cpp \
   rpcblockchain.cpp \
   rpcmining.cpp \
   rpcmisc.cpp \
+  rpcnames.cpp \
   rpcnet.cpp \
   rpcrawtransaction.cpp \
   rpcserver.cpp \
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index b9e0f46afe7dec3fadad87a5a28b3077cfb4b41c..37e100ce32ea78a3f244888737f45f04ca98895b 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -39,6 +39,8 @@ public:
         bnProofOfWorkLimit = CBigNum(~uint256(0) >> 32);
         nSubsidyHalvingInterval = 2100000;
         nAuxpowStartHeight = 453273;
+        /* TODO: Decide about fork height.  */
+        namesForkHeight = 1000000;
 
         // Build the genesis block. Note that the output of the genesis coinbase cannot
         // be spent as it did not originally exist in the database.
@@ -123,6 +125,8 @@ public:
         nRPCPort = 19341;
         nAuxpowStartHeight = 453273;
         strDataDir = "testnet3";
+        /* TODO: Decide about fork height.  */
+        namesForkHeight = 1000000;
 
         // Modify the testnet genesis block so the timestamp is valid for a later start.
         genesis.nTime = 1405338325;
@@ -165,11 +169,17 @@ public:
         nDefaultPort = 19444;
         nRPCPort = 19445;
         nAuxpowStartHeight = 250;
+        namesForkHeight = 250;
         strDataDir = "regtest";
 
         hashGenesisBlock = genesis.GetHash();
         assert(hashGenesisBlock == uint256("0x231de73ec08234a4adff3c71e57271a13fa73f5ae1ca6b0ded89275e557a6207"));
 
+        /* Fork height for names:  The regtest framework constructs initial
+           chains of height 200.  Use 250 here so that we can test both
+           before and after the fork.  */
+        namesForkHeight = 250;
+
         vSeeds.clear();  // Regtest mode doesn't have any DNS seeds.
     }
 
diff --git a/src/chainparams.h b/src/chainparams.h
index 43890e60b8ca0e9c5c38f3e21b8b028059c7bd23..e854847d5361f170c0d41a935e09ab1acc316236 100644
--- a/src/chainparams.h
+++ b/src/chainparams.h
@@ -68,6 +68,14 @@ public:
     int RPCPort() const { return nRPCPort; }
     int AuxpowChainId() const { return 20; }
     int AuxpowStartHeight() const { return nAuxpowStartHeight; }
+
+    /* Height at which names are enabled as a softfork.  */
+    inline int
+    GetNamesForkHeight() const
+    {
+        return namesForkHeight;
+    }
+
 protected:
     CChainParams() {}
 
@@ -83,6 +91,7 @@ protected:
     string strDataDir;
     vector<CDNSSeedData> vSeeds;
     std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
+    int namesForkHeight;
 };
 
 /**
diff --git a/src/coins.cpp b/src/coins.cpp
index 86b2a6ef178b4c809370aa630511e2cf65888d71..daac765aa3014426727a617f3947b8f52d98b856 100644
--- a/src/coins.cpp
+++ b/src/coins.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2013 The Bitcoin developers
+// Copyright (c) 2012-2014 The Bitcoin developers
 // Distributed under the MIT/X11 software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
@@ -55,7 +55,10 @@ bool CCoinsView::SetCoins(const uint256 &txid, const CCoins &coins) { return fal
 bool CCoinsView::HaveCoins(const uint256 &txid) { return false; }
 uint256 CCoinsView::GetBestBlock() { return uint256(0); }
 bool CCoinsView::SetBestBlock(const uint256 &hashBlock) { return false; }
-bool CCoinsView::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock) { return false; }
+bool CCoinsView::GetName (const CName& name, CNameData& data) const { return false; }
+bool CCoinsView::SetName (const CName& name, const CNameData& data) { return false; }
+bool CCoinsView::DeleteName (const CName& name) { return false; }
+bool CCoinsView::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names) { return false; }
 bool CCoinsView::GetStats(CCoinsStats &stats) { return false; }
 
 
@@ -65,8 +68,11 @@ bool CCoinsViewBacked::SetCoins(const uint256 &txid, const CCoins &coins) { retu
 bool CCoinsViewBacked::HaveCoins(const uint256 &txid) { return base->HaveCoins(txid); }
 uint256 CCoinsViewBacked::GetBestBlock() { return base->GetBestBlock(); }
 bool CCoinsViewBacked::SetBestBlock(const uint256 &hashBlock) { return base->SetBestBlock(hashBlock); }
+bool CCoinsViewBacked::GetName (const CName& name, CNameData& data) const { return base->GetName (name, data); }
+bool CCoinsViewBacked::SetName (const CName& name, const CNameData& data) { return base->SetName (name, data); }
+bool CCoinsViewBacked::DeleteName (const CName& name) { return base->DeleteName (name); }
 void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
-bool CCoinsViewBacked::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
+bool CCoinsViewBacked::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names) { return base->BatchWrite(mapCoins, hashBlock, names); }
 bool CCoinsViewBacked::GetStats(CCoinsStats &stats) { return base->GetStats(stats); }
 
 CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), hashBlock(0) { }
@@ -121,21 +127,48 @@ bool CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
     return true;
 }
 
-bool CCoinsViewCache::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlockIn) {
+bool CCoinsViewCache::GetName (const CName& name, CNameData& data) const {
+    if (cacheNames.IsDeleted (name))
+        return false;
+    if (cacheNames.Get (name, data))
+        return true;
+
+    /* Note: This does not attempt to cache name queries.  The cache
+       only keeps track of changes!  */
+
+    return base->GetName (name, data);
+}
+
+bool CCoinsViewCache::SetName (const CName& name, const CNameData& data) {
+    cacheNames.Set (name, data);
+    return true;
+}
+
+bool CCoinsViewCache::DeleteName (const CName& name) {
+    cacheNames.Delete (name);
+    return true;
+}
+
+bool CCoinsViewCache::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlockIn, const CNameCache& names) {
     for (std::map<uint256, CCoins>::const_iterator it = mapCoins.begin(); it != mapCoins.end(); it++)
         cacheCoins[it->first] = it->second;
     hashBlock = hashBlockIn;
+    cacheNames.Apply (names);
     return true;
 }
 
 bool CCoinsViewCache::Flush() {
-    bool fOk = base->BatchWrite(cacheCoins, hashBlock);
+    bool fOk = base->BatchWrite(cacheCoins, hashBlock, cacheNames);
     if (fOk)
+    {
         cacheCoins.clear();
+        cacheNames.Clear();
+    }
     return fOk;
 }
 
 unsigned int CCoinsViewCache::GetCacheSize() {
+    // Do not take name operations into account here.
     return cacheCoins.size();
 }
 
diff --git a/src/coins.h b/src/coins.h
index 1ee342313c46b525698b88d9c94085afbcb8e20a..930381e7acc52c3a38210bf5c3754fea0b66e6ea 100644
--- a/src/coins.h
+++ b/src/coins.h
@@ -1,11 +1,12 @@
 // Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2013 The Bitcoin developers
+// Copyright (c) 2009-2014 The Bitcoin developers
 // Distributed under the MIT/X11 software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 #ifndef BITCOIN_COINS_H
 #define BITCOIN_COINS_H
 
 #include "core.h"
+#include "names.h"
 #include "serialize.h"
 #include "uint256.h"
 
@@ -274,8 +275,16 @@ public:
     // Modify the currently active block hash
     virtual bool SetBestBlock(const uint256 &hashBlock);
 
-    // Do a bulk modification (multiple SetCoins + one SetBestBlock)
-    virtual bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock);
+    // Get a name (if it exists)
+    virtual bool GetName (const CName& name, CNameData& data) const;
+    // Set a name (or add it if not exists)
+    virtual bool SetName (const CName& name, const CNameData& data);
+    // Delete a name.
+    virtual bool DeleteName (const CName& name);
+
+    // Do a bulk modification (multiple SetCoins, one SetBestBlock
+    // and name updates)
+    virtual bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names);
 
     // Calculate statistics about the unspent transaction output set
     virtual bool GetStats(CCoinsStats &stats);
@@ -298,8 +307,11 @@ public:
     bool HaveCoins(const uint256 &txid);
     uint256 GetBestBlock();
     bool SetBestBlock(const uint256 &hashBlock);
+    bool GetName (const CName& name, CNameData& data) const;
+    bool SetName (const CName& name, const CNameData& data);
+    bool DeleteName (const CName& name);
     void SetBackend(CCoinsView &viewIn);
-    bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock);
+    bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names);
     bool GetStats(CCoinsStats &stats);
 };
 
@@ -310,6 +322,7 @@ class CCoinsViewCache : public CCoinsViewBacked
 protected:
     uint256 hashBlock;
     std::map<uint256,CCoins> cacheCoins;
+    CNameCache cacheNames;
 
 public:
     CCoinsViewCache(CCoinsView &baseIn, bool fDummy = false);
@@ -320,7 +333,10 @@ public:
     bool HaveCoins(const uint256 &txid);
     uint256 GetBestBlock();
     bool SetBestBlock(const uint256 &hashBlock);
-    bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock);
+    bool GetName (const CName& name, CNameData& data) const;
+    bool SetName (const CName& name, const CNameData& data);
+    bool DeleteName (const CName& name);
+    bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names);
 
     // Return a modifiable reference to a CCoins. Check HaveCoins first.
     // Many methods explicitly require a CCoinsViewCache because of this method, to reduce
@@ -332,6 +348,7 @@ public:
     bool Flush();
 
     // Calculate the size of the cache (in number of transactions)
+    // This doesn't take name operations into account.
     unsigned int GetCacheSize();
 
     /** Amount of bitcoins coming in to a transaction
diff --git a/src/leveldbwrapper.h b/src/leveldbwrapper.h
index 53e9e439bdd7f90a88fe20c12489a9e78a0d2286..4f0662aa9cda85912412946ebc0e1bc545f7c8c7 100644
--- a/src/leveldbwrapper.h
+++ b/src/leveldbwrapper.h
@@ -82,7 +82,7 @@ public:
     CLevelDBWrapper(const boost::filesystem::path &path, size_t nCacheSize, bool fMemory = false, bool fWipe = false);
     ~CLevelDBWrapper();
 
-    template<typename K, typename V> bool Read(const K& key, V& value) throw(leveldb_error) {
+    template<typename K, typename V> bool Read(const K& key, V& value) const throw(leveldb_error) {
         CDataStream ssKey(SER_DISK, CLIENT_VERSION);
         ssKey.reserve(ssKey.GetSerializeSize(key));
         ssKey << key;
@@ -111,7 +111,7 @@ public:
         return WriteBatch(batch, fSync);
     }
 
-    template<typename K> bool Exists(const K& key) throw(leveldb_error) {
+    template<typename K> bool Exists(const K& key) const throw(leveldb_error) {
         CDataStream ssKey(SER_DISK, CLIENT_VERSION);
         ssKey.reserve(ssKey.GetSerializeSize(key));
         ssKey << key;
diff --git a/src/main.cpp b/src/main.cpp
index b2fa8165c5f6cf1f31c9bff49f2488c9f2ca8de2..166194fdca87202b37e14e5535232c4880744bf1 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -403,6 +403,17 @@ CBlockIndex *CChain::FindFork(const CBlockLocator &locator) const {
 CCoinsViewCache *pcoinsTip = NULL;
 CBlockTreeDB *pblocktree = NULL;
 
+//////////////////////////////////////////////////////////////////////////////
+//
+// CBlockUndo
+//
+
+CBlockUndo::CBlockUndo (const CBlockIndex* pindex)
+  : vtxundo(), names()
+{
+  supportsNames = (pindex->nHeight >= Params ().GetNamesForkHeight ());
+}
+
 //////////////////////////////////////////////////////////////////////////////
 //
 // mapOrphanTransactions
@@ -895,12 +906,19 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
                          hash.ToString(),
                          nFees, CTransaction::nMinRelayTxFee * 10000);
 
+        /* Check that there's not already a pending name operation.  */
+        if (!pool.names.checkTransaction (tx))
+            return error ("AcceptToMemoryPool: already have an operation on"
+                          " the same name");
+
         // Check against previous transactions
         // This is done last to help prevent CPU exhaustion denial-of-service attacks.
-        if (!CheckInputs(tx, state, view, true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC))
-        {
+        const unsigned checkFlags = SCRIPT_VERIFY_P2SH
+                                    | SCRIPT_VERIFY_STRICTENC
+                                    | SCRIPT_VERIFY_NAMES;
+        if (!CheckInputs (tx, state, view, true, checkFlags))
             return error("AcceptToMemoryPool: : ConnectInputs failed %s", hash.ToString());
-        }
+
         // Store transaction in memory
         pool.addUnchecked(hash, entry);
     }
@@ -1562,6 +1580,15 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, CCoinsViewCach
         }
     }
 
+    /* Check name operations if the flag is set.  */
+    if (flags & SCRIPT_VERIFY_NAMES)
+        for (std::vector<CTxOut>::const_iterator out = tx.vout.begin ();
+             out != tx.vout.end (); ++out)
+        {
+            if (!CheckNameOperation (*out, inputs, state))
+                return error ("CheckInputs: name operation is invalid");
+        }
+
     return true;
 }
 
@@ -1576,7 +1603,7 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
 
     bool fClean = true;
 
-    CBlockUndo blockUndo;
+    CBlockUndo blockUndo(pindex);
     CDiskBlockPos pos = pindex->GetUndoPos();
     if (pos.IsNull())
         return error("DisconnectBlock() : no undo data available");
@@ -1644,6 +1671,10 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
         }
     }
 
+    /* Undo name operations.  */
+    if (!blockUndo.names.applyUndo (view))
+        return error ("DisconnectBlock: failed to undo name operations");
+
     // move best block pointer to prevout block
     view.SetBestBlock(pindex->pprev->GetBlockHash());
 
@@ -1694,6 +1725,15 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C
     if (!CheckBlock(block, state, !fJustCheck, !fJustCheck))
         return false;
 
+    /* Check that no names appear multiple times in an operation
+       in the single block.  This needs the current height (since we only
+       do this check after the softfork), thus it can't be part of
+       the initial CheckBlock routine.  */
+    const int namesForkHeight = Params ().GetNamesForkHeight ();
+    const bool considerNames = (pindex->nHeight >= namesForkHeight);
+    if (considerNames && !CheckNamesInBlock (block, state))
+      return error ("CheckBlock: CheckNamesInBlock failed");
+
     // verify that the view's current state corresponds to the previous block
     uint256 hashPrevBlock = pindex->pprev == NULL ? uint256(0) : pindex->pprev->GetBlockHash();
     assert(hashPrevBlock == view.GetBestBlock());
@@ -1737,8 +1777,10 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C
 
     unsigned int flags = SCRIPT_VERIFY_NOCACHE |
                          (fStrictPayToScriptHash ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE);
+    if (considerNames)
+        flags |= SCRIPT_VERIFY_NAMES;
 
-    CBlockUndo blockundo;
+    CBlockUndo blockundo(pindex);
 
     CCheckQueueControl<CScriptCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : NULL);
 
@@ -1789,6 +1831,14 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C
         if (!tx.IsCoinBase())
             blockundo.vtxundo.push_back(txundo);
 
+        /* Update the name database for all name operations found.  */
+        if (considerNames)
+            for (std::vector<CTxOut>::const_iterator out = tx.vout.begin ();
+                 out != tx.vout.end (); ++out)
+            {
+                ApplyNameOperation (*out, view, blockundo.names, state);
+            }
+
         vPos.push_back(std::make_pair(block.GetTxHash(i), pos));
         pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
     }
@@ -2901,7 +2951,7 @@ bool VerifyDB(int nCheckLevel, int nCheckDepth)
             return error("VerifyDB() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
         // check level 2: verify undo validity
         if (nCheckLevel >= 2 && pindex) {
-            CBlockUndo undo;
+            CBlockUndo undo(pindex);
             CDiskBlockPos pos = pindex->GetUndoPos();
             if (!pos.IsNull()) {
                 if (!undo.ReadFromDisk(pos, pindex->pprev->GetBlockHash()))
diff --git a/src/main.h b/src/main.h
index eb7ba80b90b939e59ac42d90934d8b1c7a26a3b8..ca77853d2dee21e6bab00ef1e793877e74f3ffa7 100644
--- a/src/main.h
+++ b/src/main.h
@@ -14,6 +14,7 @@
 #include "chainparams.h"
 #include "coins.h"
 #include "core.h"
+#include "names.h"
 #include "net.h"
 #include "script.h"
 #include "sync.h"
@@ -331,8 +332,27 @@ class CBlockUndo
 public:
     std::vector<CTxUndo> vtxundo; // for all but the coinbase
 
+    /**
+     * Determine whether or not to read/write the names information.
+     * This flag is set depending on whether the block this corresponds to
+     * is before or after the fork point.
+     */
+    bool supportsNames;
+    /** Undo information for names.  */
+    CNameUndo names;
+
+    /* Construct the undo object empty.  It uses the CBlockIndex object
+       (and in particular the block height) to initialise the
+       "supportsNames" field appropriately.  */
+    explicit CBlockUndo (const CBlockIndex* pindex);
+
     IMPLEMENT_SERIALIZE(
         READWRITE(vtxundo);
+
+        if (supportsNames)
+          READWRITE (names);
+        else
+          assert (names.IsNull ());
     )
 
     bool WriteToDisk(CDiskBlockPos &pos, const uint256 &hashBlock)
diff --git a/src/miner.cpp b/src/miner.cpp
index 20305ca771ab88fcd09d76afc09fac2b53d3490c..b76111d6bf4dbb79c27d77ccc9736763512c0335 100644
--- a/src/miner.cpp
+++ b/src/miner.cpp
@@ -280,7 +280,8 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
                 continue;
 
             CValidationState state;
-            if (!CheckInputs(tx, state, view, true, SCRIPT_VERIFY_P2SH))
+            if (!CheckInputs (tx, state, view, true,
+                              SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_NAMES))
                 continue;
 
             CTxUndo txundo;
diff --git a/src/names.cpp b/src/names.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..008756ce87c2c8c8d6c8af0dd3f9916361bbd392
--- /dev/null
+++ b/src/names.cpp
@@ -0,0 +1,363 @@
+// Copyright (c) 2014-2015 Daniel Kraft
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "names.h"
+
+#include "leveldbwrapper.h"
+#include "main.h"
+#include "util.h"
+
+#include <assert.h>
+
+/* Construct a name from a string.  */
+CName
+NameFromString (const std::string& str)
+{
+  const unsigned char* strPtr;
+  strPtr = reinterpret_cast<const unsigned char*> (str.c_str ());
+  return CName(strPtr, strPtr + str.size ());
+}
+
+/* Convert a name to a string.  */
+std::string
+NameToString (const CName& name)
+{
+  return std::string (name.begin (), name.end ());
+}
+
+/* Return the required (minimum) cost of a name registration.  */
+int64_t
+GetNameCost (const CName& name)
+{
+  const vchType::size_type len = name.size ();
+
+  /* TODO: Decide about real cost schedule.  */
+
+  if (len < 3)
+    return -1;
+
+  return COIN;
+}
+
+/* ************************************************************************** */
+/* CNameCache.  */
+
+/* Try to get a name's associated data.  This looks only
+   in entries, and doesn't care about deleted data.  */
+bool
+CNameCache::Get (const CName& name, CNameData& data) const
+{
+  const std::map<CName, CNameData>::const_iterator i = entries.find (name);
+  if (i == entries.end ())
+    return false;
+
+  data = i->second;
+  return true;
+}
+
+/* Insert (or update) a name.  If it is marked as "deleted", this also
+   removes the "deleted" mark.  */
+void
+CNameCache::Set (const CName& name, const CNameData& data)
+{
+  const std::set<CName>::iterator di = deleted.find (name);
+  if (di != deleted.end ())
+    deleted.erase (di);
+
+  const std::map<CName, CNameData>::iterator ei = entries.find (name);
+  if (ei != entries.end ())
+    ei->second = data;
+  else
+    entries.insert (std::make_pair (name, data));
+}
+
+/* Delete a name.  If it is in the "entries" set also, remove it there.  */
+void
+CNameCache::Delete (const CName& name)
+{
+  const std::map<CName, CNameData>::iterator ei = entries.find (name);
+  if (ei != entries.end ())
+    entries.erase (ei);
+
+  deleted.insert (name);
+}
+
+/* Apply all the changes in the passed-in record on top of this one.  */
+void
+CNameCache::Apply (const CNameCache& cache)
+{
+  for (std::map<CName, CNameData>::const_iterator i = cache.entries.begin ();
+       i != cache.entries.end (); ++i)
+    Set (i->first, i->second);
+
+  for (std::set<CName>::const_iterator i = cache.deleted.begin ();
+       i != cache.deleted.end (); ++i)
+    Delete (*i);
+}
+
+/* Write all cached changes to a database batch update object.  */
+void
+CNameCache::WriteBatch (CLevelDBBatch& batch) const
+{
+  for (std::map<CName, CNameData>::const_iterator i = entries.begin ();
+       i != entries.end (); ++i)
+    batch.Write (std::make_pair ('n', i->first), i->second);
+
+  for (std::set<CName>::const_iterator i = deleted.begin ();
+       i != deleted.end (); ++i)
+    batch.Erase (std::make_pair ('n', *i));
+}
+
+/* ************************************************************************** */
+/* CNameUndo.  */
+
+/* Undo everything in here on the given coins view.  */
+bool
+CNameUndo::applyUndo (CCoinsView& view) const
+{
+  for (std::set<CName>::const_iterator i = registrations.begin ();
+       i != registrations.end (); ++i)
+    if (!view.DeleteName (*i))
+      return error ("CNameUndo::applyUndo: failed to delete name '%s'",
+                    NameToString (*i).c_str ());
+
+  return true;
+}
+
+/* ************************************************************************** */
+/* CNameMemPool.  */
+
+/* Check if a given new transaction conflicts with the names
+   already in here.  */
+bool
+CNameMemPool::checkTransaction (const CTransaction& tx) const
+{
+  for (std::vector<CTxOut>::const_iterator out = tx.vout.begin ();
+       out != tx.vout.end (); ++out)
+    {
+      CName name;
+      if (!IsNameOperation (*out, name))
+        continue;
+
+      if (hasName (name))
+        return error ("CNameMemPool: name '%s' has a pending operation",
+                      NameToString (name).c_str ());
+    }
+
+  return true;
+}
+
+/* Add all names appearing in the given tx.  This should only be
+   called after CheckTransaction has already been fine.  */
+void
+CNameMemPool::addTransaction (const CTransaction& tx)
+{
+  for (std::vector<CTxOut>::const_iterator out = tx.vout.begin ();
+       out != tx.vout.end (); ++out)
+    {
+      CName name;
+      if (!IsNameOperation (*out, name))
+        continue;
+
+      names.insert (name);
+    }
+}
+
+/* Remove all entries for the given tx.  */
+void
+CNameMemPool::removeTransaction (const CTransaction& tx)
+{
+  for (std::vector<CTxOut>::const_iterator out = tx.vout.begin ();
+       out != tx.vout.end (); ++out)
+    {
+      CName name;
+      if (!IsNameOperation (*out, name))
+        continue;
+
+      names.erase (name);
+    }
+}
+
+/* ************************************************************************** */
+
+/* Decode a tx output script and see if it is a name operation.  This also
+   checks that the operation is well-formed.  If it looks like a name operation
+   (OP_RETURN OP_NAME_*) but isn't well-formed, it isn't accepted at all
+   (not just ignored).  In that case, fError is set to true.  */
+bool
+DecodeNameScript (const CScript& script, opcodetype& op, CName& name,
+                  std::vector<vchType>& args, bool& fError)
+{
+  CScript::const_iterator pc = script.begin ();
+
+  opcodetype cur;
+  if (!script.GetOp (pc, cur) || cur != OP_RETURN)
+    {
+      fError = false;
+      return false;
+    }
+  if (!script.GetOp (pc, op) || op != OP_NAME_REGISTER)
+    {
+      fError = false;
+      return false;
+    }
+
+  /* Get remaining data as arguments.  The name itself is also taken care of
+     as the first argument.  */
+  bool haveName = false;
+  args.clear ();
+  while (pc != script.end ())
+    {
+      vchType arg;
+      if (!script.GetOp (pc, cur, arg) || cur < 0 || cur > OP_PUSHDATA4)
+        {
+          fError = true;
+          return error ("fetching name script arguments failed");
+        }
+
+      if (haveName)
+        args.push_back (arg);
+      else
+        {
+          name = arg;
+          haveName = true;
+        }
+    }
+
+  if (!haveName)
+    {
+      fError = true;
+      return error ("no name found in name script");
+    }
+
+  /* For now, only OP_NAME_REGISTER is implemented.  Thus verify that the
+     arguments match what they should be.  */
+  if (args.size () != 1)
+    {
+      fError = true;
+      return error ("wrong argument count for OP_NAME_REGISTER");
+    }
+
+  fError = false;
+  return true;
+}
+
+/* See if a given tx output is a name operation.  */
+bool
+IsNameOperation (const CTxOut& txo, CName& name)
+{
+  opcodetype op;
+  std::vector<vchType> args;
+  bool fError;
+
+  return DecodeNameScript (txo.scriptPubKey, op, name, args, fError);
+}
+
+/* Construct a name registration script.  The passed-in script is
+   overwritten with the constructed one.  */
+void
+ConstructNameRegistration (CScript& out, const CName& name,
+                           const CNameData& data)
+{
+  out = CScript();
+  out << OP_RETURN << OP_NAME_REGISTER << name
+      << static_cast<const vchType&> (data.address);
+}
+
+/* "Hook" for basic checking of a block.  This looks through all transactions
+   in it, and verifies that each name is touched at most once by an operation
+   in the block.  This is done as a preparatory step for block validation,
+   before checking the transactions in detail.  */
+bool
+CheckNamesInBlock (const CBlock& block, CValidationState& state)
+{
+  std::set<CName> names;
+  for (std::vector<CTransaction>::const_iterator tx = block.vtx.begin ();
+       tx != block.vtx.end (); ++tx)
+    for (std::vector<CTxOut>::const_iterator out = tx->vout.begin ();
+         out != tx->vout.end (); ++out)
+      {
+        CName name;
+
+        /* Note: Actual checking of the transaction is not done here.  So
+           we don't care about fError, and we don't do anything except keeping
+           track of the names that appear.  */
+
+        if (!IsNameOperation (*out, name))
+          continue;
+          
+        if (names.count (name) != 0)
+          return state.Invalid (error ("CheckNamesInBlock: duplicate name '%s'",
+                                       NameToString (name).c_str ()));
+        names.insert (name);
+      }
+
+  return true;
+}
+
+/* Check a tx output from the name point-of-view.  If it looks like
+   a name operation, verify that it is valid (taking also the
+   chain state in coins into account).  */
+bool
+CheckNameOperation (const CTxOut& txo, const CCoinsView& coins,
+                    CValidationState& state)
+{
+  opcodetype op;
+  CName name;
+  std::vector<vchType> args;
+  bool fError;
+  if (!DecodeNameScript (txo.scriptPubKey, op, name, args, fError))
+    {
+      if (fError)
+        return state.Invalid (error ("CheckNameOperation: decoding of name"
+                                     " script returned an error flag"));
+      return true;
+    }
+
+  /* Currently, only OP_NAME_REGISTER is implemented.  */
+  assert (op == OP_NAME_REGISTER && args.size () == 1);
+
+  CNameData data;
+  if (coins.GetName (name, data))
+    return state.Invalid (error ("CheckNameOperation: name '%s' exists already",
+                                 NameToString (name).c_str ()));
+
+  const int64_t cost = GetNameCost (name);
+  if (cost == -1)
+    return state.Invalid (error ("CheckNameOperation: name '%s' is invalid",
+                                 NameToString (name).c_str ()));
+
+  assert (cost >= 0);
+  if (txo.nValue < GetNameCost (name))
+    return state.Invalid (error ("CheckNameOperation: not enough coins paid"
+                                 " for '%s'",
+                                 NameToString (name).c_str ()));
+
+  return true;
+}
+
+/* If the tx output is a name operation, apply it to the coin view.  */
+bool
+ApplyNameOperation (const CTxOut& txo, CCoinsView& coins, CNameUndo& undo,
+                    CValidationState& state)
+{
+  opcodetype op;
+  CName name;
+  std::vector<vchType> args;
+  bool fError;
+  if (!DecodeNameScript (txo.scriptPubKey, op, name, args, fError))
+    return true;
+
+  /* Currently, only OP_NAME_REGISTER is implemented.  */
+  assert (op == OP_NAME_REGISTER && args.size () == 1);
+
+  CNameData data;
+  data.address = CScript(args[0].begin (), args[0].end ());
+  if (!coins.SetName (name, data))
+    return state.Abort ("ApplyNameOperation: failed to write name");
+
+  undo.registrations.insert (name);
+
+  return true;
+}
diff --git a/src/names.h b/src/names.h
new file mode 100644
index 0000000000000000000000000000000000000000..16ed16e8015feaa6771df10664f450bbaf1f389b
--- /dev/null
+++ b/src/names.h
@@ -0,0 +1,252 @@
+// Copyright (c) 2014-2015 Daniel Kraft
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#ifndef BITCOIN_NAMES_H
+#define BITCOIN_NAMES_H
+
+#include "core.h"
+#include "serialize.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+class CCoinsView;
+class CValidationState;
+
+/* Format of name scripts:
+
+OP_NAME_REGISTER:
+
+  OP_RETURN OP_NAME_REGISTER <name> <script>
+
+  <name> and <script> are vchType values, where <script> is the
+  script corresponding to the name's desired address.
+
+*/
+
+class CLevelDBBatch;
+
+/** Type representing a name internally.  */
+typedef vchType CName;
+
+/** Construct a name from a string.  */
+CName NameFromString (const std::string& str);
+/** Convert a name to a string.  */
+std::string NameToString (const CName& name);
+
+/**
+ * Return the required (minimum) cost of a name registration.  It may return
+ * -1 to signify that the name is not allowed to be registered at all.
+ */
+int64_t GetNameCost (const CName& name);
+
+/**
+ * Information stored internally for a name.  For now, this is just
+ * the corresponding owner/recipient script.
+ */
+class CNameData
+{
+
+public:
+
+  /** The name's ownership / recipient script.  */
+  CScript address;
+
+  IMPLEMENT_SERIALIZE
+  (
+    READWRITE (address);
+  )
+
+  /* Implement == and != operators.  */
+  friend inline bool
+  operator== (const CNameData& a, const CNameData& b)
+  {
+    return (a.address == b.address);
+  }
+  friend inline bool
+  operator!= (const CNameData& a, const CNameData& b)
+  {
+    return !(a == b);
+  }
+
+};
+
+/**
+ * Cache / record of updates to the name database.  In addition to
+ * new names (or updates to them), this also keeps track of deleted names
+ * (when rolling back changes).
+ */
+class CNameCache
+{
+
+public:
+
+  /** New or updated names.  */
+  std::map<CName, CNameData> entries;
+  /** Deleted names.  */
+  std::set<CName> deleted;
+
+  CNameCache ()
+    : entries(), deleted()
+  {}
+
+  inline void
+  Clear ()
+  {
+    entries.clear ();
+    deleted.clear ();
+  }
+
+  /* See if the given name is marked as deleted.  */
+  inline bool
+  IsDeleted (const CName& name) const
+  {
+    return (deleted.count (name) > 0); 
+  }
+
+  /* Try to get a name's associated data.  This looks only
+     in entries, and doesn't care about deleted data.  */
+  bool Get (const CName& name, CNameData& data) const;
+
+  /* Insert (or update) a name.  If it is marked as "deleted", this also
+     removes the "deleted" mark.  */
+  void Set (const CName& name, const CNameData& data);
+
+  /* Delete a name.  If it is in the "entries" set also, remove it there.  */
+  void Delete (const CName& name);
+
+  /* Apply all the changes in the passed-in record on top of this one.  */
+  void Apply (const CNameCache& cache);
+
+  /* Write all cached changes to a database batch update object.  */
+  void WriteBatch (CLevelDBBatch& batch) const;
+
+};
+
+/**
+ * Undo object for name operations in a block.
+ */
+class CNameUndo
+{
+
+public:
+
+  /**
+   * Name registrations that have to be undone.  These names are simply
+   * removed from the database when performing the undo.
+   */
+  std::set<CName> registrations;
+
+  IMPLEMENT_SERIALIZE
+  (
+    /* For future extensions (changing of names), we store also
+       a "format version".  */
+    int nFormat = 1;
+    READWRITE (nFormat);
+    assert (nFormat == 1);
+
+    READWRITE (registrations);
+  )
+
+  /* Check if this object is "empty".  This is enforced when writing
+     the undo information before the hardfork point (so that we ensure
+     that no information is lost by *not* actually writing the object
+     before that).  */
+  inline bool
+  IsNull () const
+  {
+    return registrations.empty ();
+  }
+
+  /* Undo everything in here on the given coins view.  */
+  bool applyUndo (CCoinsView& view) const;
+
+};
+
+/**
+ * "Memory pool" for name operations.  This is used by CTxMemPool, and
+ * makes sure that for each name, only a single tx operating on it
+ * will ever be held in memory.
+ */
+class CNameMemPool
+{
+
+public:
+
+  /* The names that have pending operations in the mempool.  */
+  std::set<CName> names;
+
+  inline CNameMemPool ()
+    : names()
+  {}
+
+  /* See if a name has already a pending operation.  */
+  inline bool
+  hasName (const CName& name) const
+  {
+    return (names.count (name) != 0);
+  }
+
+  /* Check if a given new transaction conflicts with the names
+     already in here.  */
+  bool checkTransaction (const CTransaction& tx) const;
+
+  /* Add all names appearing in the given tx.  This should only be
+     called after CheckTransaction has already been fine.  */
+  void addTransaction (const CTransaction& tx);
+
+  /* Remove all entries for the given tx.  */
+  void removeTransaction (const CTransaction& tx);
+
+  /* Completely clear.  */
+  inline void
+  clear ()
+  {
+    names.clear ();
+  }
+
+  /* Return number of names in here.  This is used by the sanity checks
+     of CTxMemPool.  */
+  inline unsigned
+  size () const
+  {
+    return names.size ();
+  }
+
+};
+
+/* Decode a tx output script and see if it is a name operation.  This also
+   checks that the operation is well-formed.  If it looks like a name operation
+   (OP_RETURN OP_NAME_*) but isn't well-formed, it isn't accepted at all
+   (not just ignored).  In that case, fError is set to true.  */
+bool DecodeNameScript (const CScript& script, opcodetype& op, CName& name,
+                       std::vector<vchType>& args, bool& fError);
+/* See if a given tx output is a name operation.  */
+bool IsNameOperation (const CTxOut& txo, CName& name);
+
+/* Construct a name registration script.  The passed-in script is
+   overwritten with the constructed one.  */
+void ConstructNameRegistration (CScript& out, const CName& name,
+                                const CNameData& data);
+
+/* "Hook" for basic checking of a block.  This looks through all transactions
+   in it, and verifies that each name is touched at most once by an operation
+   in the block.  This is done as a preparatory step for block validation,
+   before checking the transactions in detail.  */
+bool CheckNamesInBlock (const CBlock& block, CValidationState& state);
+
+/* Check a tx output from the name point-of-view.  If it looks like
+   a name operation, verify that it is valid (taking also the
+   chain state in coins into account).  */
+bool CheckNameOperation (const CTxOut& txo, const CCoinsView& coins,
+                         CValidationState& state);
+/* If the tx output is a name operation, apply it to the coin view.  This also
+   fills in the appropriate undo information.  */
+bool ApplyNameOperation (const CTxOut& txo, CCoinsView& coins, CNameUndo& undo,
+                         CValidationState& state);
+
+#endif
diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp
index 8db5ea21269f13893d74064885b00891533ac310..1d32ff0fcc34e07369331a2c70b1631403c8fb63 100644
--- a/src/rpcclient.cpp
+++ b/src/rpcclient.cpp
@@ -132,6 +132,7 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector<std::stri
     if (strMethod == "getnetworkhashps"       && n > 0) ConvertTo<int64_t>(params[0]);
     if (strMethod == "getnetworkhashps"       && n > 1) ConvertTo<int64_t>(params[1]);
     if (strMethod == "sendtoaddress"          && n > 1) ConvertTo<double>(params[1]);
+    if (strMethod == "sendtoname"             && n > 1) ConvertTo<double>(params[1]);
     if (strMethod == "settxfee"               && n > 0) ConvertTo<double>(params[0]);
     if (strMethod == "getreceivedbyaddress"   && n > 1) ConvertTo<int64_t>(params[1]);
     if (strMethod == "getreceivedbyaccount"   && n > 1) ConvertTo<int64_t>(params[1]);
diff --git a/src/rpcnames.cpp b/src/rpcnames.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..76780eefbf0c55602a4229d337c34fe73b1c51e8
--- /dev/null
+++ b/src/rpcnames.cpp
@@ -0,0 +1,188 @@
+// Copyright (c) 2014-2015 Daniel Kraft
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "base58.h"
+#include "core.h"
+#include "init.h"
+#include "main.h"
+#include "names.h"
+#include "rpcserver.h"
+
+#ifdef ENABLE_WALLET
+# include "wallet.h"
+#endif /* ENABLE_WALLET?  */
+
+#include "json/json_spirit_utils.h"
+#include "json/json_spirit_value.h"
+
+#include <sstream>
+
+json_spirit::Value
+name_getaddress (const json_spirit::Array& params, bool fHelp)
+{
+  if (fHelp || params.size () != 1)
+    throw std::runtime_error (
+        "name_getaddress \"name\"\n"
+        "\nLook up the address corresponding to the given name."
+        "  It fails if the name doesn't exist or if its associated"
+        " script cannot be parsed for an address.\n"
+        "\nResult:\n"
+        "\"xxxx\"                        (string) address of the name\n"
+        "\nExamples:\n"
+        + HelpExampleCli ("name_getaddress", "\"myname\"")
+        + HelpExampleRpc ("name_getaddress", "\"myname\"")
+      );
+
+  CName name;
+  CNameData data;
+
+  name = NameFromString (params[0].get_str ());
+  if (!pcoinsTip->GetName (name, data))
+    {
+      std::ostringstream msg;
+      msg << "name not found: '" << NameToString (name) << "'";
+      throw JSONRPCError(RPC_NAME_NOT_FOUND, msg.str ());
+    }
+
+  CTxDestination dest;
+  CBitcoinAddress addr;
+  if (!ExtractDestination (data.address, dest) || !addr.Set (dest))
+    throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
+                       "destination address cannot be extracted");
+
+  return addr.ToString ();
+}
+
+#ifdef ENABLE_WALLET
+
+/* Check if there's a pending name operation and throw an appropriate
+   JSONRPCError if that is the case.  */
+static void
+CheckPendingNameOperation (const CName& name)
+{
+  if (mempool.names.hasName (name))
+    throw JSONRPCError(RPC_NAME_PENDING_OPERATION,
+                       "there is a pending operation on this name");
+}
+
+json_spirit::Value
+name_register (const json_spirit::Array& params, bool fHelp)
+{
+  if (fHelp || params.size () != 2)
+    throw std::runtime_error (
+        "name_register \"name\" \"address\"\n"
+        "\nRegister the given name as an alias for the address.\n"
+        + HelpRequiringPassphrase () +
+        "\nArguments:\n"
+        "1. \"name\"        (string) The name to register.\n"
+        "2. \"amount\"      (string) The address to which the name will resolve.\n"
+        "\nResult:\n"
+        "\"transactionid\"  (string) The transaction id. (view at https://blockchain.info/tx/[transactionid])\n"
+        "\nExamples:\n"
+        + HelpExampleCli ("name_register", "\"myname\" \"i5qPw9kNW6Ce9T2jwMn3vWaRrPWDY8C4G9\"")
+        + HelpExampleRpc ("name_register", "\"myname\" \"i5qPw9kNW6Ce9T2jwMn3vWaRrPWDY8C4G9\"")
+      );
+
+  /* Check if we already had the softfork.  */
+  if (chainActive.Height () < Params ().GetNamesForkHeight ())
+    throw JSONRPCError(RPC_MISC_ERROR,
+                       "names are not yet supported by the chain");
+
+  /* Get the name and verify that it is open for registration.  */
+  const CName name = NameFromString (params[0].get_str ());
+  CNameData data;
+  if (pcoinsTip->GetName (name, data))
+    throw JSONRPCError(RPC_NAME_NOT_AVAILABLE, "the name is already taken");
+  CheckPendingNameOperation (name);
+
+  /* Validate the target address and build up the name
+     registration data we want in the end.  */
+  data = CNameData();
+  CBitcoinAddress addr(params[1].get_str ());
+  if (!addr.IsValid ())
+    throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "invalid Crowncoin address");
+  data.address.SetDestination (addr.Get ());
+
+  /* Build up the final transaction and send it.  */
+
+  const int64_t nAmount = GetNameCost (name);
+  if (nAmount == -1)
+    throw JSONRPCError(RPC_NAME_INVALID, "this name is not allowed");
+
+  assert (nAmount >= 0);
+  CScript script;
+  ConstructNameRegistration (script, name, data);
+
+  CWalletTx wtx;
+  EnsureWalletIsUnlocked ();
+
+  std::string strError = pwalletMain->SendMoney (script, nAmount, wtx);
+  if (strError != "")
+    throw JSONRPCError(RPC_WALLET_ERROR, strError);
+
+  return wtx.GetHash ().GetHex ();
+}
+
+json_spirit::Value
+sendtoname (const json_spirit::Array& params, bool fHelp)
+{
+  if (fHelp || params.size () < 2 || params.size () > 4)
+    throw std::runtime_error (
+        "sendtoaddress \"name\" amount ( \"comment\" \"comment-to\" )\n"
+        "\nSent an amount to the address of a given name.  The amount is a"
+        " real and is rounded to the nearest 0.00000001.\n"
+        + HelpRequiringPassphrase () +
+        "\nArguments:\n"
+        "1. \"name\"        (string, required) The name to send to.\n"
+        "2. \"amount\"      (numeric, required) The amount in ISR to send. eg 100.01\n"
+        "3. \"comment\"     (string, optional) A comment used to store what the transaction is for. \n"
+        "                             This is not part of the transaction, just kept in your wallet.\n"
+        "4. \"comment-to\"  (string, optional) A comment to store the name of the person or organization \n"
+        "                             to which you're sending the transaction. This is not part of the \n"
+        "                             transaction, just kept in your wallet.\n"
+        "\nResult:\n"
+        "\"transactionid\"  (string) The transaction id. (view at https://blockchain.info/tx/[transactionid])\n"
+        "\nExamples:\n"
+        + HelpExampleCli ("sendtoname", "\"myname\" 0.1")
+        + HelpExampleCli ("sendtoname", "\"myname\" 0.1 \"donation\" \"seans outpost\"")
+        + HelpExampleRpc ("sendtoname", "\"myname\", 0.1, \"donation\", \"seans outpost\"")
+      );
+
+  /* Extract destination script from name database.  */
+
+  CName name;
+  CNameData data;
+
+  name = NameFromString (params[0].get_str ());
+  if (!pcoinsTip->GetName (name, data))
+    {
+      std::ostringstream msg;
+      msg << "name not found: '" << NameToString (name) << "'";
+      throw JSONRPCError (RPC_NAME_NOT_FOUND, msg.str ());
+    }
+
+  /* Amount and wallet comments, just as in "sendtoaddress".  */
+
+  const int64_t nAmount = AmountFromValue (params[1]);
+  
+  CWalletTx wtx;
+  if (params.size () > 2 && params[2].type () != json_spirit::null_type
+      && !params[2].get_str ().empty ())
+    wtx.mapValue["comment"] = params[2].get_str ();
+  if (params.size () > 3 && params[3].type () != json_spirit::null_type
+      && !params[3].get_str ().empty ())
+    wtx.mapValue["to"] = params[3].get_str ();
+
+  /* Perform the send.  */
+
+  EnsureWalletIsUnlocked ();
+
+  std::string strError = pwalletMain->SendMoney (data.address, nAmount, wtx);
+  if (strError != "")
+    throw JSONRPCError(RPC_WALLET_ERROR, strError);
+
+  return wtx.GetHash ().GetHex ();
+}
+
+#endif /* ENABLE_WALLET?  */
diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h
index 8b3df1962142d08fbd3bef18b2c4e6c9b3892062..79b702fac4d5633e22893697c0470d6c2771a9c2 100644
--- a/src/rpcprotocol.h
+++ b/src/rpcprotocol.h
@@ -69,6 +69,12 @@ enum RPCErrorCode
     RPC_WALLET_WRONG_ENC_STATE      = -15, // Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
     RPC_WALLET_ENCRYPTION_FAILED    = -16, // Failed to encrypt the wallet
     RPC_WALLET_ALREADY_UNLOCKED     = -17, // Wallet is already unlocked
+
+    // Name errors
+    RPC_NAME_NOT_FOUND              = -100, // Name is not in the database
+    RPC_NAME_NOT_AVAILABLE          = -101, // Name is already taken
+    RPC_NAME_PENDING_OPERATION      = -102, // There is already a pending name operation
+    RPC_NAME_INVALID                = -103, // The name is not allowed for registration
 };
 
 //
diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp
index c3e71d1ba6e41664749315f95b8d8f0fd9161e61..8d2e49b7499933d18f824ee6aa431fb2f07e6832 100644
--- a/src/rpcserver.cpp
+++ b/src/rpcserver.cpp
@@ -268,6 +268,13 @@ static const CRPCCommand vRPCCommands[] =
     { "validateaddress",        &validateaddress,        true,      false,      false }, /* uses wallet if enabled */
     { "verifymessage",          &verifymessage,          false,     false,      false },
 
+    /* Names */
+    { "name_getaddress",        &name_getaddress,        true,      false,      false },
+#ifdef ENABLE_WALLET
+    { "name_register",          &name_register,          false,     false,      true },
+    { "sendtoname",             &sendtoname,             false,     false,      true },
+#endif /* ENABLE_WALLET?  */
+
 #ifdef ENABLE_WALLET
     /* Wallet */
     { "addmultisigaddress",     &addmultisigaddress,     false,     false,      true },
diff --git a/src/rpcserver.h b/src/rpcserver.h
index 0068975e612010c44cab137a884c4942da87a4cc..c5a76e898366fa0b38e92cf4dcd7f67ae58eb915 100644
--- a/src/rpcserver.h
+++ b/src/rpcserver.h
@@ -189,4 +189,8 @@ extern json_spirit::Value gettxoutsetinfo(const json_spirit::Array& params, bool
 extern json_spirit::Value gettxout(const json_spirit::Array& params, bool fHelp);
 extern json_spirit::Value verifychain(const json_spirit::Array& params, bool fHelp);
 
+extern json_spirit::Value name_getaddress (const json_spirit::Array& params, bool fHelp);
+extern json_spirit::Value name_register (const json_spirit::Array& params, bool fHelp);
+extern json_spirit::Value sendtoname (const json_spirit::Array& params, bool fHelp);
+
 #endif
diff --git a/src/script.cpp b/src/script.cpp
index d129f7665c7f976e7afc165304eedab02756f2d3..f2e1b8d361b3a3e7a29c7eedb8031baf22a90ef5 100644
--- a/src/script.cpp
+++ b/src/script.cpp
@@ -82,6 +82,7 @@ const char* GetTxnOutputType(txnouttype t)
     case TX_SCRIPTHASH: return "scripthash";
     case TX_MULTISIG: return "multisig";
     case TX_NULL_DATA: return "nulldata";
+    case TX_NAME_OPERATION: return "nameop";
     }
     return NULL;
 }
@@ -1212,6 +1213,9 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector<vector<unsi
         // Empty, provably prunable, data-carrying output
         mTemplates.insert(make_pair(TX_NULL_DATA, CScript() << OP_RETURN << OP_SMALLDATA));
         mTemplates.insert(make_pair(TX_NULL_DATA, CScript() << OP_RETURN));
+
+        // Name operations
+        mTemplates.insert(make_pair(TX_NAME_OPERATION, CScript() << OP_RETURN << OP_NAME_REGISTER << OP_SMALLDATA << OP_SMALLDATA));
     }
 
     // Shortcut for pay-to-script-hash, which are more constrained than the other types:
@@ -1365,6 +1369,7 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash
     {
     case TX_NONSTANDARD:
     case TX_NULL_DATA:
+    case TX_NAME_OPERATION:
         return false;
     case TX_PUBKEY:
         keyID = CPubKey(vSolutions[0]).GetID();
@@ -1396,6 +1401,7 @@ int ScriptSigArgsExpected(txnouttype t, const std::vector<std::vector<unsigned c
     {
     case TX_NONSTANDARD:
     case TX_NULL_DATA:
+    case TX_NAME_OPERATION:
         return -1;
     case TX_PUBKEY:
         return 1;
@@ -1473,6 +1479,7 @@ bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
     {
     case TX_NONSTANDARD:
     case TX_NULL_DATA:
+    case TX_NAME_OPERATION:
         return false;
     case TX_PUBKEY:
         keyID = CPubKey(vSolutions[0]).GetID();
@@ -1534,7 +1541,7 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, vecto
     vector<valtype> vSolutions;
     if (!Solver(scriptPubKey, typeRet, vSolutions))
         return false;
-    if (typeRet == TX_NULL_DATA){
+    if (typeRet == TX_NULL_DATA || typeRet == TX_NAME_OPERATION){
         // This is data, not addresses
         return false;
     }
@@ -1754,6 +1761,7 @@ static CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo,
     {
     case TX_NONSTANDARD:
     case TX_NULL_DATA:
+    case TX_NAME_OPERATION:
         // Don't know anything about this, assume bigger one is correct:
         if (sigs1.size() >= sigs2.size())
             return PushAll(sigs1);
diff --git a/src/script.h b/src/script.h
index 657ac0b388bf0957f0526ecdc6a997fd73f7a960..ec4b0d3f041f5ee7c4ed5d830dbd64ab74baebd6 100644
--- a/src/script.h
+++ b/src/script.h
@@ -25,6 +25,9 @@ class CTransaction;
 static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
 static const unsigned int MAX_OP_RETURN_RELAY = 40;      // bytes
 
+/** Type used for generic data on the stack.  */
+typedef std::vector<unsigned char> vchType;
+
 /** Signature hash types/flags */
 enum
 {
@@ -42,6 +45,8 @@ enum
     SCRIPT_VERIFY_STRICTENC = (1U << 1), // enforce strict conformance to DER and SEC2 for signatures and pubkeys
     SCRIPT_VERIFY_EVEN_S    = (1U << 2), // enforce even S values in signatures (depends on STRICTENC)
     SCRIPT_VERIFY_NOCACHE   = (1U << 3), // do not store results in signature cache (but do query it)
+
+    SCRIPT_VERIFY_NAMES     = (1U << 10), // check name operations?
 };
 
 enum txnouttype
@@ -53,6 +58,7 @@ enum txnouttype
     TX_SCRIPTHASH,
     TX_MULTISIG,
     TX_NULL_DATA,
+    TX_NAME_OPERATION,
 };
 
 class CNoDestination {
@@ -84,6 +90,7 @@ enum opcodetype
     OP_RESERVED = 0x50,
     OP_1 = 0x51,
     OP_TRUE=OP_1,
+    OP_NAME_REGISTER=OP_1,
     OP_2 = 0x52,
     OP_3 = 0x53,
     OP_4 = 0x54,
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
index 6dd7a5ee3d515a9be01468df2d5f8b7b470bea39..50c32e83a0580f0f9a41d3b5a7a5d98d7d94d015 100644
--- a/src/test/Makefile.am
+++ b/src/test/Makefile.am
@@ -50,6 +50,7 @@ test_crowncoin_SOURCES = \
   miner_tests.cpp \
   mruset_tests.cpp \
   multisig_tests.cpp \
+  name_tests.cpp \
   netbase_tests.cpp \
   pmt_tests.cpp \
   rpc_tests.cpp \
diff --git a/src/test/name_tests.cpp b/src/test/name_tests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..53aaaa60fd71d9facc43509652bcc7bdaa670bb1
--- /dev/null
+++ b/src/test/name_tests.cpp
@@ -0,0 +1,144 @@
+// Copyright (c) 2014-2015 Daniel Kraft
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "base58.h"
+#include "core.h"
+#include "main.h"
+#include "names.h"
+
+#include <boost/test/unit_test.hpp>
+
+static const char* addrString = "1HbJ5rPgNLJzFMfkKDZSEoRv2ePtAZqh8q";
+
+BOOST_AUTO_TEST_SUITE (name_tests)
+
+BOOST_AUTO_TEST_CASE (name_script_parsing)
+{
+  CNameData data;
+
+  CBitcoinAddress addr(addrString);
+  BOOST_CHECK (addr.IsValid ());
+  data.address.SetDestination (addr.Get ());
+  const CName name = NameFromString ("my-cool-name");
+
+  CScript script;
+  ConstructNameRegistration (script, name, data);
+
+  opcodetype op;
+  CName name2;
+  std::vector<vchType> args;
+  bool error;
+
+  BOOST_CHECK (DecodeNameScript (script, op, name2, args, error));
+  BOOST_CHECK (!error);
+  BOOST_CHECK (op == OP_NAME_REGISTER);
+  BOOST_CHECK (name2 == name);
+  BOOST_CHECK (args.size () == 1);
+  BOOST_CHECK (args[0] == data.address);
+
+  BOOST_CHECK (!DecodeNameScript (data.address, op, name2, args, error));
+  BOOST_CHECK (!error);
+
+  script = CScript ();
+  script << OP_RETURN << OP_NAME_REGISTER;
+  BOOST_CHECK (!DecodeNameScript (script, op, name2, args, error));
+  BOOST_CHECK (error);
+
+  script << name;
+  BOOST_CHECK (!DecodeNameScript (script, op, name2, args, error));
+  BOOST_CHECK (error);
+
+  script << OP_NOP;
+  BOOST_CHECK (!DecodeNameScript (script, op, name2, args, error));
+  BOOST_CHECK (error);
+
+  ConstructNameRegistration (script, name, data);
+  script << OP_NOP;
+  BOOST_CHECK (!DecodeNameScript (script, op, name2, args, error));
+  BOOST_CHECK (error);
+}
+
+BOOST_AUTO_TEST_CASE (names_in_block)
+{
+  CBlock block;
+  CValidationState state;
+
+  CNameData data;
+  CBitcoinAddress addr(addrString);
+  BOOST_CHECK (addr.IsValid ());
+  data.address.SetDestination (addr.Get ());
+
+  const CName name = NameFromString ("my-cool-name");
+  CTxOut txo;
+  txo.nValue = GetNameCost (name);
+  ConstructNameRegistration (txo.scriptPubKey, name, data);
+
+  CTransaction tx;
+  tx.vout.push_back (txo);
+
+  block.vtx.push_back (tx);
+  BOOST_CHECK (CheckNamesInBlock (block, state));
+
+  block.vtx.push_back (tx);
+  BOOST_CHECK (!CheckNamesInBlock (block, state));
+}
+
+BOOST_AUTO_TEST_CASE (names_database)
+{
+  const CName name = NameFromString ("database-test-name");
+  CNameData data, data2;
+  CBitcoinAddress addr(addrString);
+  BOOST_CHECK (addr.IsValid ());
+  data.address.SetDestination (addr.Get ());
+
+  CCoinsViewCache& view = *pcoinsTip;
+
+  BOOST_CHECK (!view.GetName (name, data2));
+  BOOST_CHECK (view.SetName (name, data));
+  BOOST_CHECK (view.GetName (name, data2));
+  BOOST_CHECK (data == data2);
+  BOOST_CHECK (view.Flush ());
+
+  BOOST_CHECK (view.DeleteName (name));
+  BOOST_CHECK (!view.GetName (name, data2));
+  BOOST_CHECK (view.Flush ());
+}
+
+BOOST_AUTO_TEST_CASE (name_operations)
+{
+  CCoinsView dummy;
+  CCoinsViewCache view(dummy);
+  CNameUndo undo;
+
+  const CName nameInvalid = NameFromString ("ab");
+  BOOST_CHECK (GetNameCost (nameInvalid) == -1);
+
+  const CName name = NameFromString ("database-test-name");
+  CNameData data, data2;
+  CBitcoinAddress addr(addrString);
+  BOOST_CHECK (addr.IsValid ());
+  data.address.SetDestination (addr.Get ());
+
+  CValidationState state;
+  CTxOut txo;
+  ConstructNameRegistration (txo.scriptPubKey, name, data);
+
+  txo.nValue = GetNameCost (name) - 1;
+  BOOST_CHECK (!CheckNameOperation (txo, view, state));
+  BOOST_CHECK (undo.IsNull ());
+
+  txo.nValue = GetNameCost (name);
+  BOOST_CHECK (CheckNameOperation (txo, view, state));
+  BOOST_CHECK (ApplyNameOperation (txo, view, undo, state));
+  BOOST_CHECK (!undo.IsNull ());
+
+  BOOST_CHECK (view.GetName (name, data2));
+  BOOST_CHECK (data == data2);
+  BOOST_CHECK (!CheckNameOperation (txo, view, state));
+
+  undo.applyUndo (view);
+  BOOST_CHECK (CheckNameOperation (txo, view, state));
+}
+
+BOOST_AUTO_TEST_SUITE_END ()
diff --git a/src/txdb.cpp b/src/txdb.cpp
index 272e026a1afdc7273fbce2783075f969a08c6c2d..0c98b5e2a5f072e763e485a8caff5e562439b241 100644
--- a/src/txdb.cpp
+++ b/src/txdb.cpp
@@ -53,7 +53,27 @@ bool CCoinsViewDB::SetBestBlock(const uint256 &hashBlock) {
     return db.WriteBatch(batch);
 }
 
-bool CCoinsViewDB::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock) {
+bool CCoinsViewDB::GetName (const CName& name, CNameData& data) const {
+    return db.Read (std::make_pair ('n', name), data);
+}
+
+bool CCoinsViewDB::SetName (const CName& name, const CNameData& data) {
+    CLevelDBBatch batch;
+    CNameCache cache;
+    cache.Set (name, data);
+    cache.WriteBatch (batch);
+    return db.WriteBatch (batch);
+}
+
+bool CCoinsViewDB::DeleteName (const CName& name) {
+    CLevelDBBatch batch;
+    CNameCache cache;
+    cache.Delete (name);
+    cache.WriteBatch (batch);
+    return db.WriteBatch (batch);
+}
+
+bool CCoinsViewDB::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names) {
     LogPrint("coindb", "Committing %u changed transactions to coin database...\n", (unsigned int)mapCoins.size());
 
     CLevelDBBatch batch;
@@ -61,6 +81,7 @@ bool CCoinsViewDB::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const u
         BatchWriteCoins(batch, it->first, it->second);
     if (hashBlock != uint256(0))
         BatchWriteHashBestChain(batch, hashBlock);
+    names.WriteBatch (batch);
 
     return db.WriteBatch(batch);
 }
diff --git a/src/txdb.h b/src/txdb.h
index 5eb5731db3d6dd37b134b4c25876bc995ae94e77..7ca4bca15136c02aa9367e6b0211c8eba1c2a51e 100644
--- a/src/txdb.h
+++ b/src/txdb.h
@@ -8,6 +8,7 @@
 
 #include "leveldbwrapper.h"
 #include "main.h"
+#include "names.h"
 
 #include <map>
 #include <string>
@@ -38,7 +39,10 @@ public:
     bool HaveCoins(const uint256 &txid);
     uint256 GetBestBlock();
     bool SetBestBlock(const uint256 &hashBlock);
-    bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock);
+    bool GetName (const CName& name, CNameData& data) const;
+    bool SetName (const CName& name, const CNameData& data);
+    bool DeleteName (const CName& name);
+    bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names);
     bool GetStats(CCoinsStats &stats);
 };
 
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 64c9eac73dcb560f4a73ddff42a0f10b4b1f053a..813ac1bb9c0367e73d736fcd04e123c6b6c54cf3 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -81,6 +81,16 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry)
         for (unsigned int i = 0; i < tx.vin.size(); i++)
             mapNextTx[tx.vin[i].prevout] = CInPoint(&tx, i);
         nTransactionsUpdated++;
+
+        /* Names are handled here without checking the softfork height.
+           This is done because otherwise, we would need logic to add names
+           of transactions already in the mempool as soon as the chain
+           grows beyond the softfork height, which would get complicated.
+           Also, the worst that will happen, is that nodes don't relay or
+           mine transactions that look like name operations even before the
+           softfork -- since those do not really appear normally anyway,
+           that shouldn't be a problem.  */
+        names.addTransaction (tx);
     }
     return true;
 }
@@ -106,6 +116,7 @@ void CTxMemPool::remove(const CTransaction &tx, std::list<CTransaction>& removed
             BOOST_FOREACH(const CTxIn& txin, tx.vin)
                 mapNextTx.erase(txin.prevout);
             mapTx.erase(hash);
+            names.removeTransaction (tx);
             nTransactionsUpdated++;
         }
     }
@@ -133,6 +144,7 @@ void CTxMemPool::clear()
     LOCK(cs);
     mapTx.clear();
     mapNextTx.clear();
+    names.clear ();
     ++nTransactionsUpdated;
 }
 
@@ -174,6 +186,25 @@ void CTxMemPool::check(CCoinsViewCache *pcoins) const
         assert(tx.vin.size() > it->second.n);
         assert(it->first == it->second.ptx->vin[it->second.n].prevout);
     }
+
+    /* Check the names mempool.  */
+    unsigned nameCount = 0;
+    for (std::map<uint256, CTxMemPoolEntry>::const_iterator it = mapTx.begin ();
+         it != mapTx.end (); it++)
+    {
+        const CTransaction& tx = it->second.GetTx ();
+        for (std::vector<CTxOut>::const_iterator out = tx.vout.begin ();
+             out != tx.vout.end (); ++out)
+        {
+            CName name;
+            if (!IsNameOperation (*out, name))
+              continue;
+
+            ++nameCount;
+            assert (names.hasName (name));
+        }
+    }
+    assert (nameCount == names.size ());
 }
 
 void CTxMemPool::queryHashes(vector<uint256>& vtxid)
diff --git a/src/txmempool.h b/src/txmempool.h
index 4509e95778c9ed98f7bd5c9803b90e61a6e602c7..91dd6996558173d2c70a69dad1a25568dd1103b9 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -9,6 +9,7 @@
 
 #include "coins.h"
 #include "core.h"
+#include "names.h"
 #include "sync.h"
 
 /** Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) */
@@ -62,6 +63,15 @@ public:
     std::map<uint256, CTxMemPoolEntry> mapTx;
     std::map<COutPoint, CInPoint> mapNextTx;
 
+    /* Keep track of all names that are currently operated on by pending
+       transactions in the mempool.  Alternatively, we could also adapt
+       CCoinsViewMemPool to be a "full" CCoinsView also with respect
+       to names -- but this makes things more complicated.  Just checking
+       that no name appears twice in the mempool and that all name
+       operations are valid with respect to the "actual", DB-backed CCoinsView
+       should be enough for this purpose.  */
+    CNameMemPool names;
+
     CTxMemPool();
 
     /*