diff --git a/.gitignore b/.gitignore
index 30f29f0e733db9a0ae86702d463517e7be394e29..9f03361f7129fca9258961dd53bc90023155fd01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,6 @@ localdev_*
 *.class
 *.aar
 *.jar
+# Ignore test output
+.ekv*
+.*test*
diff --git a/api/client.go b/api/client.go
index 7901d1fef5d4d30f8a265d50e145dbad36fe0c65..b45e8cf77ebbb7ea072d4fb89019e7f2e9a21091 100644
--- a/api/client.go
+++ b/api/client.go
@@ -439,30 +439,28 @@ type SearchCallback interface {
 func (cl *Client) SearchForUser(emailAddress string,
 	cb SearchCallback, timeout time.Duration) {
 	//see if the user has been searched before, if it has, return it
-	uid, pk := cl.session.GetContactByValue(emailAddress)
+	contact, err := io.SessionV2.GetContact(emailAddress)
 
-	if uid != nil {
-		cb.Callback(uid.Bytes(), pk, nil)
+	// if we successfully got the contact, return it.
+	// errors can include the email address not existing,
+	// so errors from the GetContact call are ignored
+	if contact != nil && err == nil {
+		cb.Callback(contact.Id.Bytes(), contact.PublicKey, nil)
+		return
 	}
 
 	valueType := "EMAIL"
 	go func() {
-		uid, pubKey, err := bots.Search(valueType, emailAddress, cl.opStatus, timeout)
-		if err == nil && uid != nil && pubKey != nil {
+		contact, err := bots.Search(valueType, emailAddress, cl.opStatus, timeout)
+		if err == nil && contact.Id != nil && contact.PublicKey != nil {
 			cl.opStatus(globals.UDB_SEARCH_BUILD_CREDS)
-			err = cl.registerUserE2E(uid, pubKey)
+			err = cl.registerUserE2E(contact)
 			if err != nil {
-				cb.Callback(uid[:], pubKey, err)
+				cb.Callback(contact.Id.Bytes(), contact.PublicKey, err)
 				return
 			}
 			//store the user so future lookups can find it
-			cl.session.StoreContactByValue(emailAddress, uid, pubKey)
-
-			err = cl.session.StoreSession()
-			if err != nil {
-				cb.Callback(uid[:], pubKey, err)
-				return
-			}
+			io.SessionV2.SetContact(emailAddress, contact)
 
 			// If there is something in the channel then send it; otherwise,
 			// skip over it
@@ -471,7 +469,7 @@ func (cl *Client) SearchForUser(emailAddress string,
 			default:
 			}
 
-			cb.Callback(uid[:], pubKey, err)
+			cb.Callback(contact.Id.Bytes(), contact.PublicKey, err)
 
 		} else {
 			if err == nil {
diff --git a/api/client_test.go b/api/client_test.go
index 190efcefc6096cd7dbf4b7e3085418a0eb546a0a..2db50011e98f42ed1ab0774969e90a5cb4810761 100644
--- a/api/client_test.go
+++ b/api/client_test.go
@@ -13,6 +13,7 @@ import (
 	"gitlab.com/elixxir/client/io"
 	"gitlab.com/elixxir/client/keyStore"
 	"gitlab.com/elixxir/client/parse"
+	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/client/user"
 	"gitlab.com/elixxir/crypto/csprng"
 	"gitlab.com/elixxir/crypto/cyclic"
@@ -177,7 +178,13 @@ func TestRegisterUserE2E(t *testing.T) {
 
 	testClient.session = session
 
-	testClient.registerUserE2E(partner, partnerPubKeyCyclic.Bytes())
+	err = testClient.registerUserE2E(&storage.Contact{
+		Id:        partner,
+		PublicKey: partnerPubKeyCyclic.Bytes(),
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
 
 	// Confirm we can get all types of keys
 	km := session.GetKeyStore().GetSendManager(partner)
@@ -243,7 +250,7 @@ func TestRegisterUserE2E(t *testing.T) {
 func TestRegisterUserE2E_CheckAllKeys(t *testing.T) {
 	testClient, err := NewClient(&globals.RamStorage{}, ".ekv-testrege2e-allkeys", "", def)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	cmixGrp, e2eGrp := getGroups()
@@ -268,7 +275,13 @@ func TestRegisterUserE2E_CheckAllKeys(t *testing.T) {
 
 	testClient.session = session
 
-	testClient.registerUserE2E(partner, partnerPubKeyCyclic.Bytes())
+	err = testClient.registerUserE2E(&storage.Contact{
+		Id:        partner,
+		PublicKey: partnerPubKeyCyclic.Bytes(),
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
 
 	// Generate all keys and confirm they all match
 	keyParams := testClient.GetKeyParams()
@@ -712,8 +725,8 @@ func TestClient_LogoutTimeout(t *testing.T) {
 // Test that if we logout we can logback in.
 func TestClient_LogoutAndLoginAgain(t *testing.T) {
 	//Initialize a client
-	storage := &DummyStorage{LocationA: ".ekv-logoutlogin", StoreA: []byte{'a', 'b', 'c'}}
-	tc, err := NewClient(storage, ".ekv-logoutlogin", "", def)
+	dummyStorage := &DummyStorage{LocationA: ".ekv-logoutlogin", StoreA: []byte{'a', 'b', 'c'}}
+	tc, err := NewClient(dummyStorage, ".ekv-logoutlogin", "", def)
 	if err != nil {
 		t.Errorf("Failed to create new client: %+v", err)
 	}
@@ -750,7 +763,7 @@ func TestClient_LogoutAndLoginAgain(t *testing.T) {
 	}
 
 	//Redefine client with old session files and attempt to login.
-	tc, err = NewClient(storage, ".ekv-logoutlogin", "", def)
+	tc, err = NewClient(dummyStorage, ".ekv-logoutlogin", "", def)
 	if err != nil {
 		t.Errorf("Failed second client initialization: %+v", err)
 	}
diff --git a/api/private.go b/api/private.go
index bc76f0f36d6f02080c16698ceab5c73a6f6b7caa..55253f75c7942307bafc776eb6c20cb29dc567ed 100644
--- a/api/private.go
+++ b/api/private.go
@@ -13,6 +13,7 @@ import (
 	"gitlab.com/elixxir/client/globals"
 	"gitlab.com/elixxir/client/io"
 	"gitlab.com/elixxir/client/keyStore"
+	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/client/user"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/crypto/csprng"
@@ -208,16 +209,15 @@ func (cl *Client) confirmNonce(UID, nonce []byte,
 	return nil
 }
 
-func (cl *Client) registerUserE2E(partnerID *id.ID,
-	partnerPubKey []byte) error {
+func (cl *Client) registerUserE2E(partner *storage.Contact) error {
 
 	// Check that the returned user is valid
-	if partnerKeyStore := cl.session.GetKeyStore().GetSendManager(partnerID); partnerKeyStore != nil {
+	if partnerKeyStore := cl.session.GetKeyStore().GetSendManager(partner.Id); partnerKeyStore != nil {
 		return errors.New(fmt.Sprintf("UDB searched failed for %v because user has "+
-			"been searched for before", partnerID))
+			"been searched for before", partner.Id))
 	}
 
-	if cl.session.GetCurrentUser().User.Cmp(partnerID) {
+	if cl.session.GetCurrentUser().User.Cmp(partner.Id) {
 		return errors.New("cannot search for yourself on UDB")
 	}
 
@@ -228,11 +228,11 @@ func (cl *Client) registerUserE2E(partnerID *id.ID,
 	// Create user private key and partner public key
 	// in the group
 	privKeyCyclic := cl.session.GetE2EDHPrivateKey()
-	partnerPubKeyCyclic := grp.NewIntFromBytes(partnerPubKey)
+	publicKeyCyclic := grp.NewIntFromBytes(partner.PublicKey)
 
 	// Generate baseKey
 	baseKey, _ := diffieHellman.CreateDHSessionKey(
-		partnerPubKeyCyclic,
+		publicKeyCyclic,
 		privKeyCyclic,
 		grp)
 
@@ -243,7 +243,7 @@ func (cl *Client) registerUserE2E(partnerID *id.ID,
 
 	// Create Send KeyManager
 	km := keyStore.NewManager(baseKey, privKeyCyclic,
-		partnerPubKeyCyclic, partnerID, true,
+		publicKeyCyclic, partner.Id, true,
 		numKeys, keysTTL, params.NumRekeys)
 
 	// Generate Send Keys
@@ -252,7 +252,7 @@ func (cl *Client) registerUserE2E(partnerID *id.ID,
 
 	// Create Receive KeyManager
 	km = keyStore.NewManager(baseKey, privKeyCyclic,
-		partnerPubKeyCyclic, partnerID, false,
+		publicKeyCyclic, partner.Id, false,
 		numKeys, keysTTL, params.NumRekeys)
 
 	// Generate Receive Keys
@@ -265,10 +265,10 @@ func (cl *Client) registerUserE2E(partnerID *id.ID,
 
 	keys := &keyStore.RekeyKeys{
 		CurrPrivKey: privKeyCyclic,
-		CurrPubKey:  partnerPubKeyCyclic,
+		CurrPubKey:  publicKeyCyclic,
 	}
 
-	rkm.AddKeys(partnerID, keys)
+	rkm.AddKeys(partner.Id, keys)
 
 	return nil
 }
diff --git a/bots/bots_test.go b/bots/bots_test.go
index 6f0c7edba4e04504b780cf8039028940a7e25986..9675c5506e34c9ef77a8ca41f7a54cf8b0a986de 100644
--- a/bots/bots_test.go
+++ b/bots/bots_test.go
@@ -126,11 +126,6 @@ func TestRegister(t *testing.T) {
 // TestSearch smoke tests the search function
 func TestSearch(t *testing.T) {
 	publicKeyString := base64.StdEncoding.EncodeToString(pubKey)
-	//uid := id.NewIdFromUInt(26, id.User, t)
-	//serRetUid := base64.StdEncoding.EncodeToString(uid[:])
-	//result, _ := base64.StdEncoding.DecodeString(serRetUid)
-	//t.Fatal(serRetUid)
-	//t.Fatal(len(result))
 
 	// Send response messages from fake UDB in advance
 	searchResponseListener <- "blah@elixxir.io FOUND UR69db14ZyicpZVqJ1HFC5rk9UZ8817aV6+VHmrJpGc= AAAAAAAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD 8oKh7TYG4KxQcBAymoXPBHSD/uga9pX3Mn/jKhvcD8M="
@@ -141,16 +136,19 @@ func TestSearch(t *testing.T) {
 		return
 	}
 
-	searchedUser, _, err := Search("EMAIL", "blah@elixxir.io",
+	searchedUser, err := Search("EMAIL", "blah@elixxir.io",
 		dummySearchState, 30*time.Second)
 	if err != nil {
-		t.Errorf("Error on Search: %s", err.Error())
+		t.Fatalf("Error on Search: %s", err.Error())
 	}
-	if !searchedUser.Cmp(id.NewIdFromUInt(26, id.User, t)) {
+	if !searchedUser.Id.Cmp(id.NewIdFromUInt(26, id.User, t)) {
 		t.Errorf("Search did not return user ID 26! returned %s", searchedUser)
 	}
 	//Test the timeout capabilities
-	searchedUser, _, err = Search("EMAIL", "blah@elixxir.io", dummySearchState, 1*time.Millisecond)
+	searchedUser, err = Search("EMAIL", "blah@elixxir.io", dummySearchState, 1*time.Millisecond)
+	if err == nil {
+		t.Fatal("udb search timeout should have caused error")
+	}
 	if strings.Compare(err.Error(), "UDB search timeout exceeded on user lookup") != 0 {
 		t.Errorf("error: %v", err)
 	}
diff --git a/bots/userDiscovery.go b/bots/userDiscovery.go
index d84976c955d24e3814939419a7d3c8541dae07fc..3e51ebfb55f4e12dcd4090a0328588cbb7a4a4f4 100644
--- a/bots/userDiscovery.go
+++ b/bots/userDiscovery.go
@@ -16,6 +16,7 @@ import (
 	"gitlab.com/elixxir/client/cmixproto"
 	"gitlab.com/elixxir/client/globals"
 	"gitlab.com/elixxir/client/parse"
+	"gitlab.com/elixxir/client/storage"
 	"gitlab.com/elixxir/crypto/hash"
 	"gitlab.com/elixxir/primitives/id"
 	"strings"
@@ -38,7 +39,7 @@ func Register(valueType, value string, publicKey []byte, regStatus func(int), ti
 	if valueType == "EMAIL" {
 		value, err = hashAndEncode(strings.ToLower(value))
 		if err != nil {
-			return fmt.Errorf("Could not hash and encode email %s: %+v", value, err)
+			return fmt.Errorf("could not hash and encode email %s: %+v", value, err)
 		}
 	}
 
@@ -114,7 +115,7 @@ func Register(valueType, value string, publicKey []byte, regStatus func(int), ti
 // Search returns a userID and public key based on the search criteria
 // it accepts a valueType of EMAIL and value of an e-mail address, and
 // returns a map of userid -> public key
-func Search(valueType, value string, searchStatus func(int), timeout time.Duration) (*id.ID, []byte, error) {
+func Search(valueType, value string, searchStatus func(int), timeout time.Duration) (*storage.Contact, error) {
 	globals.Log.DEBUG.Printf("Running search for %v, %v", valueType, value)
 
 	searchTimeout := time.NewTimer(timeout)
@@ -123,7 +124,7 @@ func Search(valueType, value string, searchStatus func(int), timeout time.Durati
 	if valueType == "EMAIL" {
 		value, err = hashAndEncode(strings.ToLower(value))
 		if err != nil {
-			return nil, nil, fmt.Errorf("Could not hash and encode email %s: %+v", value, err)
+			return nil, fmt.Errorf("could not hash and encode email %s: %+v", value, err)
 		}
 	}
 
@@ -135,7 +136,7 @@ func Search(valueType, value string, searchStatus func(int), timeout time.Durati
 	})
 	err = sendCommand(&id.UDB, msgBody)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 
 	var response string
@@ -149,20 +150,20 @@ func Search(valueType, value string, searchStatus func(int), timeout time.Durati
 		case response = <-searchResponseListener:
 			empty := fmt.Sprintf("SEARCH %s NOTFOUND", value)
 			if response == empty {
-				return nil, nil, nil
+				return nil, nil
 			}
 			if strings.Contains(response, value) {
 				found = true
 			}
 		case <-searchTimeout.C:
-			return nil, nil, errors.New("UDB search timeout exceeded on user lookup")
+			return nil, errors.New("UDB search timeout exceeded on user lookup")
 		}
 	}
 
 	// While search returns more than 1 result, we only process the first
 	cMixUID, keyFP, err := parseSearch(response)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 
 	searchStatus(globals.UDB_SEARCH_GETKEY)
@@ -174,7 +175,7 @@ func Search(valueType, value string, searchStatus func(int), timeout time.Durati
 	})
 	err = sendCommand(&id.UDB, msgBody)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 
 	// wait for the response to searching for the key against the timeout.
@@ -187,13 +188,16 @@ func Search(valueType, value string, searchStatus func(int), timeout time.Durati
 				found = true
 			}
 		case <-searchTimeout.C:
-			return nil, nil, errors.New("UDB search timeout exceeded on key lookup")
+			return nil, errors.New("UDB search timeout exceeded on key lookup")
 		}
 	}
 
 	publicKey := parseGetKey(response)
 
-	return cMixUID, publicKey, nil
+	return &storage.Contact{
+		Id:        cMixUID,
+		PublicKey: publicKey,
+	}, nil
 }
 
 func hashAndEncode(s string) (string, error) {
diff --git a/storage/contact.go b/storage/contact.go
index a28095d03234547daa0ac7c6763a59b2ec8e52fb..ce3deefdec85e5cba5dbcd2beabcb2002906eccf 100644
--- a/storage/contact.go
+++ b/storage/contact.go
@@ -3,16 +3,16 @@ package storage
 import (
 	"encoding/json"
 	"gitlab.com/elixxir/client/globals"
-	"gitlab.com/elixxir/client/user"
+	"gitlab.com/elixxir/primitives/id"
 	"time"
 )
 
 const currentContactVersion = 0
 
-func (s *Session) GetContact(name string) (*user.SearchedUserRecord, error) {
+func (s *Session) GetContact(name string) (*Contact, error) {
 	// Make key
 	// If upgrading version, may need to add logic to update version number in key prefix
-	key := MakeKeyPrefix("SearchedUserRecord", currentContactVersion) + name
+	key := MakeKeyPrefix("Contact", currentContactVersion) + name
 
 	obj, err := s.Get(key)
 	if err != nil {
@@ -24,18 +24,18 @@ func (s *Session) GetContact(name string) (*user.SearchedUserRecord, error) {
 	}
 
 	// deserialize
-	var contact user.SearchedUserRecord
+	var contact Contact
 	err = json.Unmarshal(obj.Data, &contact)
 	return &contact, err
 }
 
-func (s *Session) SetContact(name string, record *user.SearchedUserRecord) error {
+func (s *Session) SetContact(name string, record *Contact) error {
 	now, err := time.Now().MarshalText()
 	if err != nil {
 		return err
 	}
 
-	key := MakeKeyPrefix("SearchedUserRecord", currentContactVersion) + name
+	key := MakeKeyPrefix("Contact", currentContactVersion) + name
 	var data []byte
 	data, err = json.Marshal(record)
 	if err != nil {
@@ -48,3 +48,8 @@ func (s *Session) SetContact(name string, record *user.SearchedUserRecord) error
 	}
 	return s.Set(key, &obj)
 }
+
+type Contact struct {
+	Id        *id.ID
+	PublicKey []byte
+}
diff --git a/storage/contact_test.go b/storage/contact_test.go
index 8fc667da5133a0a0b5621ea028ca2eedc4975634..2b8f77463fe32fc17bb647d825529530b0eb404d 100644
--- a/storage/contact_test.go
+++ b/storage/contact_test.go
@@ -1,7 +1,6 @@
 package storage
 
 import (
-	"gitlab.com/elixxir/client/user"
 	"gitlab.com/elixxir/ekv"
 	"gitlab.com/elixxir/primitives/id"
 	"reflect"
@@ -13,9 +12,9 @@ func TestSession_Contact(t *testing.T) {
 	store := make(ekv.Memstore)
 	session := &Session{NewVersionedKV(store)}
 
-	expectedRecord := &user.SearchedUserRecord{
-		Id: *id.NewIdFromUInt(24601, id.User, t),
-		Pk: []byte("not a real public key"),
+	expectedRecord := &Contact{
+		Id:        id.NewIdFromUInt(24601, id.User, t),
+		PublicKey: []byte("not a real public key"),
 	}
 
 	name := "niamh@elixxir.io"