diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..4eb2a43343a9c76a7ff475789759efbed4b3c270
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1400"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "MyContactFeature"
+               BuildableName = "MyContactFeature"
+               BlueprintName = "MyContactFeature"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      codeCoverageEnabled = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "MyContactFeatureTests"
+               BuildableName = "MyContactFeatureTests"
+               BlueprintName = "MyContactFeatureTests"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "MyContactFeature"
+            BuildableName = "MyContactFeature"
+            BlueprintName = "MyContactFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index c5bcba6a09c4d828a7506a531d429f9d9ab8a92d..c0a4f9054a911c82a801544c2ff17e4629c0ecfb 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -26,6 +26,7 @@ let package = Package(
     .library(name: "ContactFeature", targets: ["ContactFeature"]),
     .library(name: "ContactsFeature", targets: ["ContactsFeature"]),
     .library(name: "HomeFeature", targets: ["HomeFeature"]),
+    .library(name: "MyContactFeature", targets: ["MyContactFeature"]),
     .library(name: "RegisterFeature", targets: ["RegisterFeature"]),
     .library(name: "RestoreFeature", targets: ["RestoreFeature"]),
     .library(name: "SendRequestFeature", targets: ["SendRequestFeature"]),
@@ -39,7 +40,7 @@ let package = Package(
     ),
     .package(
       url: "https://github.com/pointfreeco/swift-composable-architecture.git",
-      .upToNextMajor(from: "0.40.1")
+      .upToNextMajor(from: "0.40.2")
     ),
     .package(
       url: "https://git.xx.network/elixxir/client-ios-db.git",
@@ -51,7 +52,11 @@ let package = Package(
     ),
     .package(
       url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
-      .upToNextMajor(from: "0.4.0")
+      .upToNextMajor(from: "0.4.1")
+    ),
+    .package(
+      url: "https://github.com/pointfreeco/swift-custom-dump.git",
+      .upToNextMajor(from: "0.5.2")
     ),
   ],
   targets: [
@@ -69,7 +74,8 @@ let package = Package(
     .testTarget(
       name: "AppCoreTests",
       dependencies: [
-        .target(name: "AppCore")
+        .target(name: "AppCore"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -83,6 +89,7 @@ let package = Package(
         .target(name: "ContactFeature"),
         .target(name: "ContactsFeature"),
         .target(name: "HomeFeature"),
+        .target(name: "MyContactFeature"),
         .target(name: "RegisterFeature"),
         .target(name: "RestoreFeature"),
         .target(name: "SendRequestFeature"),
@@ -100,6 +107,7 @@ let package = Package(
       name: "AppFeatureTests",
       dependencies: [
         .target(name: "AppFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -118,6 +126,7 @@ let package = Package(
       name: "ChatFeatureTests",
       dependencies: [
         .target(name: "ChatFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -135,6 +144,7 @@ let package = Package(
       name: "CheckContactAuthFeatureTests",
       dependencies: [
         .target(name: "CheckContactAuthFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ]
     ),
     .target(
@@ -151,6 +161,7 @@ let package = Package(
       name: "ConfirmRequestFeatureTests",
       dependencies: [
         .target(name: "ConfirmRequestFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ]
     ),
     .target(
@@ -173,6 +184,7 @@ let package = Package(
       name: "ContactFeatureTests",
       dependencies: [
         .target(name: "ContactFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -181,6 +193,7 @@ let package = Package(
       dependencies: [
         .target(name: "AppCore"),
         .target(name: "ContactFeature"),
+        .target(name: "MyContactFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
@@ -193,6 +206,7 @@ let package = Package(
       name: "ContactsFeatureTests",
       dependencies: [
         .target(name: "ContactsFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -213,6 +227,26 @@ let package = Package(
       name: "HomeFeatureTests",
       dependencies: [
         .target(name: "HomeFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .target(
+      name: "MyContactFeature",
+      dependencies: [
+        .target(name: "AppCore"),
+        .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXModels", package: "client-ios-db"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .testTarget(
+      name: "MyContactFeatureTests",
+      dependencies: [
+        .target(name: "MyContactFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -231,6 +265,7 @@ let package = Package(
       name: "RegisterFeatureTests",
       dependencies: [
         .target(name: "RegisterFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -245,6 +280,7 @@ let package = Package(
       name: "RestoreFeatureTests",
       dependencies: [
         .target(name: "RestoreFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -263,6 +299,7 @@ let package = Package(
       name: "SendRequestFeatureTests",
       dependencies: [
         .target(name: "SendRequestFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -282,6 +319,7 @@ let package = Package(
       name: "UserSearchFeatureTests",
       dependencies: [
         .target(name: "UserSearchFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -299,6 +337,7 @@ let package = Package(
       name: "VerifyContactFeatureTests",
       dependencies: [
         .target(name: "VerifyContactFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ]
     ),
     .target(
@@ -313,6 +352,7 @@ let package = Package(
       name: "WelcomeFeatureTests",
       dependencies: [
         .target(name: "WelcomeFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
index 65400dff681f98714be5c8db74bc82b6558dfe17..aa97ea7d2a0f1ce2fded6d13e874f8f8caf21769 100644
--- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
+++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
@@ -109,6 +109,16 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "MyContactFeatureTests"
+               BuildableName = "MyContactFeatureTests"
+               BlueprintName = "MyContactFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 611123e0334c6078250bf0e782153975d6891799..d824d6d400b402270d341bb43fbd5f363c84a879 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -6,6 +6,7 @@ import ContactFeature
 import ContactsFeature
 import Foundation
 import HomeFeature
+import MyContactFeature
 import RegisterFeature
 import RestoreFeature
 import SendRequestFeature
@@ -122,7 +123,15 @@ extension AppEnvironment {
               db: dbManager.getDB,
               mainQueue: mainQueue,
               bgQueue: bgQueue,
-              contact: { contactEnvironment }
+              contact: { contactEnvironment },
+              myContact: {
+                MyContactEnvironment(
+                  messenger: messenger,
+                  db: dbManager.getDB,
+                  mainQueue: mainQueue,
+                  bgQueue: bgQueue
+                )
+              }
             )
           },
           userSearch: {
diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift
index 1ded89de4cbf9dd44014395e3e79bf6ff2fdcc20..680a231ec8cc9e6e13487a849a65941bda7a7428 100644
--- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift
+++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift
@@ -3,6 +3,7 @@ import ComposableArchitecture
 import ComposablePresentation
 import ContactFeature
 import Foundation
+import MyContactFeature
 import XCTestDynamicOverlay
 import XXClient
 import XXMessengerClient
@@ -12,16 +13,19 @@ public struct ContactsState: Equatable {
   public init(
     myId: Data? = nil,
     contacts: IdentifiedArrayOf<XXModels.Contact> = [],
-    contact: ContactState? = nil
+    contact: ContactState? = nil,
+    myContact: MyContactState? = nil
   ) {
     self.myId = myId
     self.contacts = contacts
     self.contact = contact
+    self.myContact = myContact
   }
 
   public var myId: Data?
   public var contacts: IdentifiedArrayOf<XXModels.Contact>
   public var contact: ContactState?
+  public var myContact: MyContactState?
 }
 
 public enum ContactsAction: Equatable {
@@ -30,6 +34,9 @@ public enum ContactsAction: Equatable {
   case contactSelected(XXModels.Contact)
   case contactDismissed
   case contact(ContactAction)
+  case myContactSelected
+  case myContactDismissed
+  case myContact(MyContactAction)
 }
 
 public struct ContactsEnvironment {
@@ -38,13 +45,15 @@ public struct ContactsEnvironment {
     db: DBManagerGetDB,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
-    contact: @escaping () -> ContactEnvironment
+    contact: @escaping () -> ContactEnvironment,
+    myContact: @escaping () -> MyContactEnvironment
   ) {
     self.messenger = messenger
     self.db = db
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
     self.contact = contact
+    self.myContact = myContact
   }
 
   public var messenger: Messenger
@@ -52,6 +61,7 @@ public struct ContactsEnvironment {
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
   public var contact: () -> ContactEnvironment
+  public var myContact: () -> MyContactEnvironment
 }
 
 #if DEBUG
@@ -61,7 +71,8 @@ extension ContactsEnvironment {
     db: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
-    contact: { .unimplemented }
+    contact: { .unimplemented },
+    myContact: { .unimplemented }
   )
 }
 #endif
@@ -96,7 +107,15 @@ public let contactsReducer = Reducer<ContactsState, ContactsAction, ContactsEnvi
     state.contact = nil
     return .none
 
-  case .contact(_):
+  case .myContactSelected:
+    state.myContact = MyContactState()
+    return .none
+
+  case .myContactDismissed:
+    state.myContact = nil
+    return .none
+
+  case .contact(_), .myContact(_):
     return .none
   }
 }
@@ -107,3 +126,10 @@ public let contactsReducer = Reducer<ContactsState, ContactsAction, ContactsEnvi
   action: /ContactsAction.contact,
   environment: { $0.contact() }
 )
+.presenting(
+  myContactReducer,
+  state: .keyPath(\.myContact),
+  id: .notNil(),
+  action: /ContactsAction.myContact,
+  environment: { $0.myContact() }
+)
diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift
index ce811f9d7bab2d22cb530cc6f2725be004a603e4..e09725d92c84feeb7f8cb7da5dbc38cf9d998087 100644
--- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift
+++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift
@@ -2,6 +2,7 @@ import AppCore
 import ComposableArchitecture
 import ComposablePresentation
 import ContactFeature
+import MyContactFeature
 import SwiftUI
 import XXModels
 
@@ -28,13 +29,21 @@ public struct ContactsView: View {
         ForEach(viewStore.contacts) { contact in
           if contact.id == viewStore.myId {
             Section {
-              VStack(alignment: .leading, spacing: 8) {
-                Label(contact.username ?? "", systemImage: "person")
-                Label(contact.email ?? "", systemImage: "envelope")
-                Label(contact.phone ?? "", systemImage: "phone")
+              Button {
+                viewStore.send(.myContactSelected)
+              } label: {
+                HStack {
+                  VStack(alignment: .leading, spacing: 8) {
+                    Label(contact.username ?? "", systemImage: "person")
+                    Label(contact.email ?? "", systemImage: "envelope")
+                    Label(contact.phone ?? "", systemImage: "phone")
+                  }
+                  .font(.callout)
+                  .tint(Color.primary)
+                  Spacer()
+                  Image(systemName: "chevron.forward")
+                }
               }
-              .font(.callout)
-              .tint(Color.primary)
             } header: {
               Text("My contact")
             }
@@ -70,6 +79,14 @@ public struct ContactsView: View {
         onDeactivate: { viewStore.send(.contactDismissed) },
         destination: ContactView.init(store:)
       ))
+      .background(NavigationLinkWithStore(
+        store.scope(
+          state: \.myContact,
+          action: ContactsAction.myContact
+        ),
+        onDeactivate: { viewStore.send(.myContactDismissed) },
+        destination: MyContactView.init(store:)
+      ))
     }
   }
 }
diff --git a/Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift b/Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift
new file mode 100644
index 0000000000000000000000000000000000000000..321139aec18ce7ae51b9be8097878a9133798236
--- /dev/null
+++ b/Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift
@@ -0,0 +1,11 @@
+import ComposableArchitecture
+
+extension AlertState {
+  public static func error(_ message: String) -> AlertState<MyContactAction> {
+    AlertState<MyContactAction>(
+      title: TextState("Error"),
+      message: TextState(message),
+      buttons: []
+    )
+  }
+}
diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift
new file mode 100644
index 0000000000000000000000000000000000000000..b25d021257e34cbc8b23cafa4f4ef1c44815dbff
--- /dev/null
+++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift
@@ -0,0 +1,315 @@
+import AppCore
+import Combine
+import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+
+public struct MyContactState: Equatable {
+  public enum Field: String, Hashable {
+    case email
+    case emailCode
+    case phone
+    case phoneCode
+  }
+
+  public init(
+    contact: XXModels.Contact? = nil,
+    focusedField: Field? = nil,
+    email: String = "",
+    emailConfirmationID: String? = nil,
+    emailConfirmationCode: String = "",
+    isRegisteringEmail: Bool = false,
+    isConfirmingEmail: Bool = false,
+    isUnregisteringEmail: Bool = false,
+    phone: String = "",
+    phoneConfirmationID: String? = nil,
+    phoneConfirmationCode: String = "",
+    isRegisteringPhone: Bool = false,
+    isConfirmingPhone: Bool = false,
+    isUnregisteringPhone: Bool = false,
+    isLoadingFacts: Bool = false,
+    alert: AlertState<MyContactAction>? = nil
+  ) {
+    self.contact = contact
+    self.focusedField = focusedField
+    self.email = email
+    self.emailConfirmationID = emailConfirmationID
+    self.emailConfirmationCode = emailConfirmationCode
+    self.isRegisteringEmail = isRegisteringEmail
+    self.isConfirmingEmail = isConfirmingEmail
+    self.isUnregisteringEmail = isUnregisteringEmail
+    self.phone = phone
+    self.phoneConfirmationID = phoneConfirmationID
+    self.phoneConfirmationCode = phoneConfirmationCode
+    self.isRegisteringPhone = isRegisteringPhone
+    self.isConfirmingPhone = isConfirmingPhone
+    self.isUnregisteringPhone = isUnregisteringPhone
+    self.isLoadingFacts = isLoadingFacts
+    self.alert = alert
+  }
+
+  public var contact: XXModels.Contact?
+  @BindableState public var focusedField: Field?
+  @BindableState public var email: String
+  @BindableState public var emailConfirmationID: String?
+  @BindableState public var emailConfirmationCode: String
+  @BindableState public var isRegisteringEmail: Bool
+  @BindableState public var isConfirmingEmail: Bool
+  @BindableState public var isUnregisteringEmail: Bool
+  @BindableState public var phone: String
+  @BindableState public var phoneConfirmationID: String?
+  @BindableState public var phoneConfirmationCode: String
+  @BindableState public var isRegisteringPhone: Bool
+  @BindableState public var isConfirmingPhone: Bool
+  @BindableState public var isUnregisteringPhone: Bool
+  @BindableState public var isLoadingFacts: Bool
+  public var alert: AlertState<MyContactAction>?
+}
+
+public enum MyContactAction: Equatable, BindableAction {
+  case start
+  case contactFetched(XXModels.Contact?)
+  case registerEmailTapped
+  case confirmEmailTapped
+  case unregisterEmailTapped
+  case registerPhoneTapped
+  case confirmPhoneTapped
+  case unregisterPhoneTapped
+  case loadFactsTapped
+  case didFail(String)
+  case alertDismissed
+  case binding(BindingAction<MyContactState>)
+}
+
+public struct MyContactEnvironment {
+  public init(
+    messenger: Messenger,
+    db: DBManagerGetDB,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>
+  ) {
+    self.messenger = messenger
+    self.db = db
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+  }
+
+  public var messenger: Messenger
+  public var db: DBManagerGetDB
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
+}
+
+#if DEBUG
+extension MyContactEnvironment {
+  public static let unimplemented = MyContactEnvironment(
+    messenger: .unimplemented,
+    db: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
+}
+#endif
+
+public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContactEnvironment>
+{ state, action, env in
+  enum DBFetchEffectID {}
+
+  switch action {
+  case .start:
+    return Effect
+      .catching { try env.messenger.e2e.tryGet().getContact().getId() }
+      .tryMap { try env.db().fetchContactsPublisher(.init(id: [$0])) }
+      .flatMap { $0 }
+      .assertNoFailure()
+      .map(\.first)
+      .map(MyContactAction.contactFetched)
+      .subscribe(on: env.bgQueue)
+      .receive(on: env.mainQueue)
+      .eraseToEffect()
+      .cancellable(id: DBFetchEffectID.self, cancelInFlight: true)
+
+  case .contactFetched(let contact):
+    state.contact = contact
+    return .none
+
+  case .registerEmailTapped:
+    state.focusedField = nil
+    state.isRegisteringEmail = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud = try env.messenger.ud.tryGet()
+        let fact = Fact(type: .email, value: state.email)
+        let confirmationID = try ud.sendRegisterFact(fact)
+        subscriber.send(.set(\.$emailConfirmationID, confirmationID))
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isRegisteringEmail, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .confirmEmailTapped:
+    guard let confirmationID = state.emailConfirmationID else { return .none }
+    state.focusedField = nil
+    state.isConfirmingEmail = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud = try env.messenger.ud.tryGet()
+        try ud.confirmFact(confirmationId: confirmationID, code: state.emailConfirmationCode)
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          dbContact.email = state.email
+          try env.db().saveContact(dbContact)
+        }
+        subscriber.send(.set(\.$email, ""))
+        subscriber.send(.set(\.$emailConfirmationID, nil))
+        subscriber.send(.set(\.$emailConfirmationCode, ""))
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isConfirmingEmail, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .unregisterEmailTapped:
+    guard let email = state.contact?.email else { return .none }
+    state.isUnregisteringEmail = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud: UserDiscovery = try env.messenger.ud.tryGet()
+        let fact = Fact(type: .email, value: email)
+        try ud.removeFact(fact)
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          dbContact.email = nil
+          try env.db().saveContact(dbContact)
+        }
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isUnregisteringEmail, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .registerPhoneTapped:
+    state.focusedField = nil
+    state.isRegisteringPhone = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud = try env.messenger.ud.tryGet()
+        let fact = Fact(type: .phone, value: state.phone)
+        let confirmationID = try ud.sendRegisterFact(fact)
+        subscriber.send(.set(\.$phoneConfirmationID, confirmationID))
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isRegisteringPhone, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .confirmPhoneTapped:
+    guard let confirmationID = state.phoneConfirmationID else { return .none }
+    state.focusedField = nil
+    state.isConfirmingPhone = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud = try env.messenger.ud.tryGet()
+        try ud.confirmFact(confirmationId: confirmationID, code: state.phoneConfirmationCode)
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          dbContact.phone = state.phone
+          try env.db().saveContact(dbContact)
+        }
+        subscriber.send(.set(\.$phone, ""))
+        subscriber.send(.set(\.$phoneConfirmationID, nil))
+        subscriber.send(.set(\.$phoneConfirmationCode, ""))
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isConfirmingPhone, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .unregisterPhoneTapped:
+    guard let phone = state.contact?.phone else { return .none }
+    state.isUnregisteringPhone = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud: UserDiscovery = try env.messenger.ud.tryGet()
+        let fact = Fact(type: .phone, value: phone)
+        try ud.removeFact(fact)
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          dbContact.phone = nil
+          try env.db().saveContact(dbContact)
+        }
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isUnregisteringPhone, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .loadFactsTapped:
+    state.isLoadingFacts = true
+    return Effect.run { subscriber in
+      do {
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          let facts = try env.messenger.ud.tryGet().getFacts()
+          dbContact.email = facts.get(.email)?.value
+          dbContact.phone = facts.get(.phone)?.value
+          try env.db().saveContact(dbContact)
+        }
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isLoadingFacts, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .didFail(let failure):
+    state.alert = .error(failure)
+    return .none
+
+  case .alertDismissed:
+    state.alert = nil
+    return .none
+
+  case .binding(_):
+    return .none
+  }
+}
+.binding()
diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f32af24270f762b6a11f5a43602f013709e14344
--- /dev/null
+++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift
@@ -0,0 +1,248 @@
+import ComposableArchitecture
+import SwiftUI
+import XXModels
+
+public struct MyContactView: View {
+  public init(store: Store<MyContactState, MyContactAction>) {
+    self.store = store
+  }
+
+  let store: Store<MyContactState, MyContactAction>
+  @FocusState var focusedField: MyContactState.Field?
+
+  struct ViewState: Equatable {
+    init(state: MyContactState) {
+      contact = state.contact
+      focusedField = state.focusedField
+      email = state.email
+      emailConfirmation = state.emailConfirmationID != nil
+      emailCode = state.emailConfirmationCode
+      isRegisteringEmail = state.isRegisteringEmail
+      isConfirmingEmail = state.isConfirmingEmail
+      isUnregisteringEmail = state.isUnregisteringEmail
+      phone = state.phone
+      phoneConfirmation = state.phoneConfirmationID != nil
+      phoneCode = state.phoneConfirmationCode
+      isRegisteringPhone = state.isRegisteringPhone
+      isConfirmingPhone = state.isConfirmingPhone
+      isUnregisteringPhone = state.isUnregisteringPhone
+      isLoadingFacts = state.isLoadingFacts
+    }
+
+    var contact: XXModels.Contact?
+    var focusedField: MyContactState.Field?
+    var email: String
+    var emailConfirmation: Bool
+    var emailCode: String
+    var isRegisteringEmail: Bool
+    var isConfirmingEmail: Bool
+    var isUnregisteringEmail: Bool
+    var phone: String
+    var phoneConfirmation: Bool
+    var phoneCode: String
+    var isRegisteringPhone: Bool
+    var isConfirmingPhone: Bool
+    var isUnregisteringPhone: Bool
+    var isLoadingFacts: Bool
+  }
+
+  public var body: some View {
+    WithViewStore(store, observe: ViewState.init) { viewStore in
+      Form {
+        Section {
+          Text(viewStore.contact?.username ?? "")
+        } header: {
+          Label("Username", systemImage: "person")
+        }
+
+        Section {
+          if let contact = viewStore.contact {
+            if let email = contact.email {
+              Text(email)
+              Button(role: .destructive) {
+                viewStore.send(.unregisterEmailTapped)
+              } label: {
+                HStack {
+                  Text("Unregister")
+                  Spacer()
+                  if viewStore.isUnregisteringEmail {
+                    ProgressView()
+                  }
+                }
+              }
+              .disabled(viewStore.isUnregisteringEmail)
+            } else {
+              TextField(
+                text: viewStore.binding(
+                  get: \.email,
+                  send: { MyContactAction.set(\.$email, $0) }
+                ),
+                prompt: Text("Enter email"),
+                label: { Text("Email") }
+              )
+              .focused($focusedField, equals: .email)
+              .textInputAutocapitalization(.never)
+              .disableAutocorrection(true)
+              .disabled(viewStore.isRegisteringEmail || viewStore.emailConfirmation)
+              if viewStore.emailConfirmation {
+                TextField(
+                  text: viewStore.binding(
+                    get: \.emailCode,
+                    send: { MyContactAction.set(\.$emailConfirmationCode, $0) }
+                  ),
+                  prompt: Text("Enter confirmation code"),
+                  label: { Text("Confirmation code") }
+                )
+                .focused($focusedField, equals: .emailCode)
+                .textInputAutocapitalization(.never)
+                .disableAutocorrection(true)
+                .disabled(viewStore.isConfirmingEmail)
+                Button {
+                  viewStore.send(.confirmEmailTapped)
+                } label: {
+                  HStack {
+                    Text("Confirm")
+                    Spacer()
+                    if viewStore.isConfirmingEmail {
+                      ProgressView()
+                    }
+                  }
+                }
+                .disabled(viewStore.isConfirmingEmail)
+              } else {
+                Button {
+                  viewStore.send(.registerEmailTapped)
+                } label: {
+                  HStack {
+                    Text("Register")
+                    Spacer()
+                    if viewStore.isRegisteringEmail {
+                      ProgressView()
+                    }
+                  }
+                }
+                .disabled(viewStore.isRegisteringEmail)
+              }
+            }
+          } else {
+            Text("")
+          }
+        } header: {
+          Label("Email", systemImage: "envelope")
+        }
+
+        Section {
+          if let contact = viewStore.contact {
+            if let phone = contact.phone {
+              Text(phone)
+              Button(role: .destructive) {
+                viewStore.send(.unregisterPhoneTapped)
+              } label: {
+                HStack {
+                  Text("Unregister")
+                  Spacer()
+                  if viewStore.isUnregisteringPhone {
+                    ProgressView()
+                  }
+                }
+              }
+              .disabled(viewStore.isUnregisteringPhone)
+            } else {
+              TextField(
+                text: viewStore.binding(
+                  get: \.phone,
+                  send: { MyContactAction.set(\.$phone, $0) }
+                ),
+                prompt: Text("Enter phone"),
+                label: { Text("Phone") }
+              )
+              .focused($focusedField, equals: .phone)
+              .textInputAutocapitalization(.never)
+              .disableAutocorrection(true)
+              .disabled(viewStore.isRegisteringPhone || viewStore.phoneConfirmation)
+              if viewStore.phoneConfirmation {
+                TextField(
+                  text: viewStore.binding(
+                    get: \.phoneCode,
+                    send: { MyContactAction.set(\.$phoneConfirmationCode, $0) }
+                  ),
+                  prompt: Text("Enter confirmation code"),
+                  label: { Text("Confirmation code") }
+                )
+                .focused($focusedField, equals: .phoneCode)
+                .textInputAutocapitalization(.never)
+                .disableAutocorrection(true)
+                .disabled(viewStore.isConfirmingPhone)
+                Button {
+                  viewStore.send(.confirmPhoneTapped)
+                } label: {
+                  HStack {
+                    Text("Confirm")
+                    Spacer()
+                    if viewStore.isConfirmingPhone {
+                      ProgressView()
+                    }
+                  }
+                }
+                .disabled(viewStore.isConfirmingPhone)
+              } else {
+                Button {
+                  viewStore.send(.registerPhoneTapped)
+                } label: {
+                  HStack {
+                    Text("Register")
+                    Spacer()
+                    if viewStore.isRegisteringPhone {
+                      ProgressView()
+                    }
+                  }
+                }
+                .disabled(viewStore.isRegisteringPhone)
+              }
+            }
+          } else {
+            Text("")
+          }
+        } header: {
+          Label("Phone", systemImage: "phone")
+        }
+
+        Section {
+          Button {
+            viewStore.send(.loadFactsTapped)
+          } label: {
+            HStack {
+              Text("Reload facts")
+              Spacer()
+              if viewStore.isLoadingFacts {
+                ProgressView()
+              }
+            }
+          }
+          .disabled(viewStore.isLoadingFacts)
+        } header: {
+          Text("Actions")
+        }
+      }
+      .navigationTitle("My Contact")
+      .task { viewStore.send(.start) }
+      .onChange(of: viewStore.focusedField) { focusedField = $0 }
+      .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) }
+      .alert(store.scope(state: \.alert), dismiss: .alertDismissed)
+    }
+  }
+}
+
+#if DEBUG
+public struct MyContactView_Previews: PreviewProvider {
+  public static var previews: some View {
+    NavigationView {
+      MyContactView(store: Store(
+        initialState: MyContactState(),
+        reducer: .empty,
+        environment: ()
+      ))
+    }
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
index 5a013b28b16126b422ef3d26b19407d0af2bff5c..fc58fe4eaf2ec4fa8be79df08205638c640d656b 100644
--- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
@@ -1,4 +1,5 @@
 import ComposableArchitecture
+import CustomDump
 import HomeFeature
 import RestoreFeature
 import WelcomeFeature
diff --git a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift
index a0c0291ef79865fa6c1f3d1ffeb25235bd520acf..ffd3eafee2f6d7216fd3a48bb3c2a63097725147 100644
--- a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift
@@ -2,6 +2,7 @@ import Combine
 import ComposableArchitecture
 import ContactFeature
 import CustomDump
+import MyContactFeature
 import XCTest
 import XXClient
 import XXMessengerClient
@@ -94,4 +95,30 @@ final class ContactsFeatureTests: XCTestCase {
       $0.contact = nil
     }
   }
+
+  func testSelectMyContact() {
+    let store = TestStore(
+      initialState: ContactsState(),
+      reducer: contactsReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.myContactSelected) {
+      $0.myContact = MyContactState()
+    }
+  }
+
+  func testDismissMyContact() {
+    let store = TestStore(
+      initialState: ContactsState(
+        myContact: MyContactState()
+      ),
+      reducer: contactsReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.myContactDismissed) {
+      $0.myContact = nil
+    }
+  }
 }
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index fc671b1ec7406823bd903d07307c8146e948ed39..d922d4f9f9e141ddaef8b577cdaccf6a0e24e922 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -1,6 +1,7 @@
 import AppCore
 import ComposableArchitecture
 import ContactsFeature
+import CustomDump
 import RegisterFeature
 import UserSearchFeature
 import XCTest
diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8d5fac94338bfa3e9f6e9b413ef2c016304269fb
--- /dev/null
+++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift
@@ -0,0 +1,761 @@
+import Combine
+import ComposableArchitecture
+import CustomDump
+import XCTest
+import XXClient
+import XXMessengerClient
+import XXModels
+@testable import MyContactFeature
+
+final class MyContactFeatureTests: XCTestCase {
+  func testStart() {
+    let contactId = "contact-id".data(using: .utf8)!
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    var dbDidFetchContacts: [XXModels.Contact.Query] = []
+    let dbContactsPublisher = PassthroughSubject<[XXModels.Contact], Error>()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactId }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.fetchContactsPublisher.run = { query in
+        dbDidFetchContacts.append(query)
+        return dbContactsPublisher.eraseToAnyPublisher()
+      }
+      return db
+    }
+
+    store.send(.start)
+
+    XCTAssertNoDifference(dbDidFetchContacts, [.init(id: [contactId])])
+
+    dbContactsPublisher.send([])
+
+    store.receive(.contactFetched(nil))
+
+    let contact = XXModels.Contact(id: contactId)
+    dbContactsPublisher.send([contact])
+
+    store.receive(.contactFetched(contact)) {
+      $0.contact = contact
+    }
+
+    dbContactsPublisher.send(completion: .finished)
+  }
+
+  func testRegisterEmail() {
+    let email = "test@email.com"
+    let confirmationID = "123"
+
+    var didSendRegisterFact: [Fact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.sendRegisterFact.run = { fact in
+        didSendRegisterFact.append(fact)
+        return confirmationID
+      }
+      return ud
+    }
+
+    store.send(.set(\.$focusedField, .email)) {
+      $0.focusedField = .email
+    }
+
+    store.send(.set(\.$email, email)) {
+      $0.email = email
+    }
+
+    store.send(.registerEmailTapped) {
+      $0.focusedField = nil
+      $0.isRegisteringEmail = true
+    }
+
+    XCTAssertNoDifference(didSendRegisterFact, [.init(type: .email, value: email)])
+
+    store.receive(.set(\.$emailConfirmationID, confirmationID)) {
+      $0.emailConfirmationID = confirmationID
+    }
+
+    store.receive(.set(\.$isRegisteringEmail, false)) {
+      $0.isRegisteringEmail = false
+    }
+  }
+
+  func testRegisterEmailFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.sendRegisterFact.run = { _ in throw failure }
+      return ud
+    }
+
+    store.send(.registerEmailTapped) {
+      $0.isRegisteringEmail = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isRegisteringEmail, false)) {
+      $0.isRegisteringEmail = false
+    }
+  }
+
+  func testConfirmEmail() {
+    let contactID = "contact-id".data(using: .utf8)!
+    let email = "test@email.com"
+    let confirmationID = "123"
+    let confirmationCode = "321"
+    let dbContact = XXModels.Contact(id: contactID)
+
+    var didConfirmWithID: [String] = []
+    var didConfirmWithCode: [String] = []
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(
+        email: email,
+        emailConfirmationID: confirmationID
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.confirmFact.run = { id, code in
+        didConfirmWithID.append(id)
+        didConfirmWithCode.append(code)
+      }
+      return ud
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactID }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.set(\.$focusedField, .emailCode)) {
+      $0.focusedField = .emailCode
+    }
+
+    store.send(.set(\.$emailConfirmationCode, confirmationCode)) {
+      $0.emailConfirmationCode = confirmationCode
+    }
+
+    store.send(.confirmEmailTapped) {
+      $0.focusedField = nil
+      $0.isConfirmingEmail = true
+    }
+
+    XCTAssertNoDifference(didConfirmWithID, [confirmationID])
+    XCTAssertNoDifference(didConfirmWithCode, [confirmationCode])
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.email = email
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$email, "")) {
+      $0.email = ""
+    }
+    store.receive(.set(\.$emailConfirmationID, nil)) {
+      $0.emailConfirmationID = nil
+    }
+    store.receive(.set(\.$emailConfirmationCode, "")) {
+      $0.emailConfirmationCode = ""
+    }
+    store.receive(.set(\.$isConfirmingEmail, false)) {
+      $0.isConfirmingEmail = false
+    }
+  }
+
+  func testConfirmEmailFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(
+        emailConfirmationID: "123"
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.confirmFact.run = { _, _ in throw failure }
+      return ud
+    }
+
+    store.send(.confirmEmailTapped) {
+      $0.isConfirmingEmail = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isConfirmingEmail, false)) {
+      $0.isConfirmingEmail = false
+    }
+  }
+
+  func testUnregisterEmail() {
+    let contactID = "contact-id".data(using: .utf8)!
+    let email = "test@email.com"
+    let dbContact = XXModels.Contact(id: contactID, email: email)
+
+    var didRemoveFact: [Fact] = []
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(
+        contact: dbContact
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.removeFact.run = { didRemoveFact.append($0) }
+      return ud
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactID }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.unregisterEmailTapped) {
+      $0.isUnregisteringEmail = true
+    }
+
+    XCTAssertNoDifference(didRemoveFact, [.init(type: .email, value: email)])
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.email = nil
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$isUnregisteringEmail, false)) {
+      $0.isUnregisteringEmail = false
+    }
+  }
+
+  func testUnregisterEmailFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(
+        contact: .init(id: Data(), email: "test@email.com")
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.removeFact.run = { _ in throw failure }
+      return ud
+    }
+
+    store.send(.unregisterEmailTapped) {
+      $0.isUnregisteringEmail = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isUnregisteringEmail, false)) {
+      $0.isUnregisteringEmail = false
+    }
+  }
+
+  func testRegisterPhone() {
+    let phone = "+123456789"
+    let confirmationID = "123"
+
+    var didSendRegisterFact: [Fact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.sendRegisterFact.run = { fact in
+        didSendRegisterFact.append(fact)
+        return confirmationID
+      }
+      return ud
+    }
+
+    store.send(.set(\.$focusedField, .phone)) {
+      $0.focusedField = .phone
+    }
+
+    store.send(.set(\.$phone, phone)) {
+      $0.phone = phone
+    }
+
+    store.send(.registerPhoneTapped) {
+      $0.focusedField = nil
+      $0.isRegisteringPhone = true
+    }
+
+    XCTAssertNoDifference(didSendRegisterFact, [.init(type: .phone, value: phone)])
+
+    store.receive(.set(\.$phoneConfirmationID, confirmationID)) {
+      $0.phoneConfirmationID = confirmationID
+    }
+
+    store.receive(.set(\.$isRegisteringPhone, false)) {
+      $0.isRegisteringPhone = false
+    }
+  }
+
+  func testRegisterPhoneFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.sendRegisterFact.run = { _ in throw failure }
+      return ud
+    }
+
+    store.send(.registerPhoneTapped) {
+      $0.isRegisteringPhone = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isRegisteringPhone, false)) {
+      $0.isRegisteringPhone = false
+    }
+  }
+
+  func testConfirmPhone() {
+    let contactID = "contact-id".data(using: .utf8)!
+    let phone = "+123456789"
+    let confirmationID = "123"
+    let confirmationCode = "321"
+    let dbContact = XXModels.Contact(id: contactID)
+
+    var didConfirmWithID: [String] = []
+    var didConfirmWithCode: [String] = []
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(
+        phone: phone,
+        phoneConfirmationID: confirmationID
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.confirmFact.run = { id, code in
+        didConfirmWithID.append(id)
+        didConfirmWithCode.append(code)
+      }
+      return ud
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactID }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.set(\.$focusedField, .phoneCode)) {
+      $0.focusedField = .phoneCode
+    }
+
+    store.send(.set(\.$phoneConfirmationCode, confirmationCode)) {
+      $0.phoneConfirmationCode = confirmationCode
+    }
+
+    store.send(.confirmPhoneTapped) {
+      $0.focusedField = nil
+      $0.isConfirmingPhone = true
+    }
+
+    XCTAssertNoDifference(didConfirmWithID, [confirmationID])
+    XCTAssertNoDifference(didConfirmWithCode, [confirmationCode])
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.phone = phone
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$phone, "")) {
+      $0.phone = ""
+    }
+    store.receive(.set(\.$phoneConfirmationID, nil)) {
+      $0.phoneConfirmationID = nil
+    }
+    store.receive(.set(\.$phoneConfirmationCode, "")) {
+      $0.phoneConfirmationCode = ""
+    }
+    store.receive(.set(\.$isConfirmingPhone, false)) {
+      $0.isConfirmingPhone = false
+    }
+  }
+
+  func testConfirmPhoneFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(
+        phoneConfirmationID: "123"
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.confirmFact.run = { _, _ in throw failure }
+      return ud
+    }
+
+    store.send(.confirmPhoneTapped) {
+      $0.isConfirmingPhone = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isConfirmingPhone, false)) {
+      $0.isConfirmingPhone = false
+    }
+  }
+  
+  func testUnregisterPhone() {
+    let contactID = "contact-id".data(using: .utf8)!
+    let phone = "+123456789"
+    let dbContact = XXModels.Contact(id: contactID, phone: phone)
+
+    var didRemoveFact: [Fact] = []
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(
+        contact: dbContact
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.removeFact.run = { didRemoveFact.append($0) }
+      return ud
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactID }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.unregisterPhoneTapped) {
+      $0.isUnregisteringPhone = true
+    }
+
+    XCTAssertNoDifference(didRemoveFact, [.init(type: .phone, value: phone)])
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.phone = nil
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$isUnregisteringPhone, false)) {
+      $0.isUnregisteringPhone = false
+    }
+  }
+
+  func testUnregisterPhoneFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(
+        contact: .init(id: Data(), phone: "+123456789")
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.removeFact.run = { _ in throw failure }
+      return ud
+    }
+
+    store.send(.unregisterPhoneTapped) {
+      $0.isUnregisteringPhone = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isUnregisteringPhone, false)) {
+      $0.isUnregisteringPhone = false
+    }
+  }
+
+  func testLoadFactsFromClient() {
+    let contactId = "contact-id".data(using: .utf8)!
+    let dbContact = XXModels.Contact(id: contactId)
+    let email = "test@email.com"
+    let phone = "123456789"
+
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactId }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getFacts.run = {
+        [
+          Fact(type: .email, value: email),
+          Fact(type: .phone, value: phone),
+        ]
+      }
+      return ud
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.loadFactsTapped) {
+      $0.isLoadingFacts = true
+    }
+
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactId])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.email = email
+    expectedSavedContact.phone = phone
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$isLoadingFacts, false)) {
+      $0.isLoadingFacts = false
+    }
+  }
+
+  func testLoadFactsFromClientFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in throw failure }
+        return contact
+      }
+      return e2e
+    }
+
+    store.send(.loadFactsTapped) {
+      $0.isLoadingFacts = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isLoadingFacts, false)) {
+      $0.isLoadingFacts = false
+    }
+  }
+
+  func testErrorAlert() {
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    let failure = "Something went wrong"
+
+    store.send(.didFail(failure)) {
+      $0.alert = .error(failure)
+    }
+
+    store.send(.alertDismissed) {
+      $0.alert = nil
+    }
+  }
+}
diff --git a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
index 6c802874d64bbd0dcf7e0ac5b9fee0de7e136d8f..8da1e1f15eb79dccd3f05c0a9ea656ede51d4a35 100644
--- a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
@@ -1,4 +1,5 @@
 import ComposableArchitecture
+import CustomDump
 import XCTest
 import XXClient
 import XXMessengerClient
diff --git a/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift
index cec3587e845624edc68ef537b00604b8a577c13c..3f35d3dfb2a6488ae2281eff7d1e468d0d0d8f8a 100644
--- a/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift
@@ -1,5 +1,6 @@
 import Combine
 import ComposableArchitecture
+import CustomDump
 import XCTest
 import XXClient
 import XXModels
diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
index 44731c1f16d41e0b95ab3ff1675f654d1ab2fcd5..33f1edb9d61d7186503b63f90c1f73f11251185f 100644
--- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
@@ -1,5 +1,6 @@
 import ComposableArchitecture
 import ContactFeature
+import CustomDump
 import XCTest
 import XXClient
 import XXMessengerClient
@@ -64,6 +65,8 @@ final class UserSearchFeatureTests: XCTestCase {
       $0.failure = nil
     }
 
+    XCTAssertNoDifference(didSearchWithQuery, [.init(username: "Username")])
+
     store.receive(.didSucceed(contacts)) {
       $0.isSearching = false
       $0.failure = nil
diff --git a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 45de0ce85881cbcd30f418b3ac71b0983257de07..42a8b2ba71e97e1f4f1a04cdf740a1fb59c1242d 100644
--- a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -59,8 +59,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/swift-composable-architecture.git",
       "state" : {
-        "revision" : "cbe013b42b3c368957f8f882c960b93845e1589d",
-        "version" : "0.40.1"
+        "revision" : "9ea8c763061287052a68d5e6723fed45e898b7d9",
+        "version" : "0.40.2"
       }
     },
     {
@@ -75,10 +75,10 @@
     {
       "identity" : "swift-custom-dump",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/pointfreeco/swift-custom-dump",
+      "location" : "https://github.com/pointfreeco/swift-custom-dump.git",
       "state" : {
-        "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7",
-        "version" : "0.5.0"
+        "revision" : "c9b6b940d95c0a925c63f6858943415714d8a981",
+        "version" : "0.5.2"
       }
     },
     {
@@ -95,8 +95,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
       "state" : {
-        "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260",
-        "version" : "0.4.0"
+        "revision" : "30314f1ece684dd60679d598a9b89107557b67d9",
+        "version" : "0.4.1"
       }
     }
   ],
diff --git a/Package.resolved b/Package.resolved
index 6741fe9d93c193203c994ec7bd191103eecea29b..225eb0fdb5e6eb944de29fa3bc030fed309735c0 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -14,8 +14,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/swift-custom-dump.git",
       "state" : {
-        "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7",
-        "version" : "0.5.0"
+        "revision" : "c9b6b940d95c0a925c63f6858943415714d8a981",
+        "version" : "0.5.2"
       }
     },
     {
@@ -23,8 +23,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
       "state" : {
-        "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260",
-        "version" : "0.4.0"
+        "revision" : "30314f1ece684dd60679d598a9b89107557b67d9",
+        "version" : "0.4.1"
       }
     }
   ],
diff --git a/Package.swift b/Package.swift
index 30e5c604ce0c444f011056ca371ffb100c704351..d34f37017ee095dcf806fe9fab7d6f82482b93e5 100644
--- a/Package.swift
+++ b/Package.swift
@@ -26,11 +26,11 @@ let package = Package(
   dependencies: [
     .package(
       url: "https://github.com/pointfreeco/swift-custom-dump.git",
-      .upToNextMajor(from: "0.5.0")
+      .upToNextMajor(from: "0.5.2")
     ),
     .package(
       url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
-      .upToNextMajor(from: "0.4.0")
+      .upToNextMajor(from: "0.4.1")
     ),
     .package(
       url: "https://github.com/kishikawakatsumi/KeychainAccess.git",
diff --git a/xcode-remove-caches.sh b/xcode-remove-caches.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4d24eab47daf55841f3152f75e371388188612af
--- /dev/null
+++ b/xcode-remove-caches.sh
@@ -0,0 +1,6 @@
+pkill -int com.apple.CoreSimulator.CoreSimulatorService
+killall Xcode
+rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang/ModuleCache"
+rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang.$(whoami)/ModuleCache"
+rm -rf ~/Library/Developer/Xcode/DerivedData/*
+rm -rf ~/Library/Caches/com.apple.dt.Xcode/*