diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ContactFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ContactFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..f4e87fe12e26e32585ff4eb739a425467ad1d237
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ContactFeature.xcscheme
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1340"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "ContactFeature"
+               BuildableName = "ContactFeature"
+               BlueprintName = "ContactFeature"
+               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 = "ContactFeatureTests"
+               BuildableName = "ContactFeatureTests"
+               BlueprintName = "ContactFeatureTests"
+               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 = "ContactFeature"
+            BuildableName = "ContactFeature"
+            BlueprintName = "ContactFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/SendRequestFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/SendRequestFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..2f70b385a82f17839c21022e2b22b2a67a715333
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/SendRequestFeature.xcscheme
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1340"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "SendRequestFeature"
+               BuildableName = "SendRequestFeature"
+               BlueprintName = "SendRequestFeature"
+               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 = "SendRequestFeatureTests"
+               BuildableName = "SendRequestFeatureTests"
+               BlueprintName = "SendRequestFeatureTests"
+               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 = "SendRequestFeature"
+            BuildableName = "SendRequestFeature"
+            BlueprintName = "SendRequestFeature"
+            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 f9ceb75fdf29afbda5362c20139003d77ebb131f..77b97a0f38f4516e0ae9b60feeb18364dbb02247 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -20,9 +20,11 @@ let package = Package(
   products: [
     .library(name: "AppCore", targets: ["AppCore"]),
     .library(name: "AppFeature", targets: ["AppFeature"]),
+    .library(name: "ContactFeature", targets: ["ContactFeature"]),
     .library(name: "HomeFeature", targets: ["HomeFeature"]),
     .library(name: "RegisterFeature", targets: ["RegisterFeature"]),
     .library(name: "RestoreFeature", targets: ["RestoreFeature"]),
+    .library(name: "SendRequestFeature", targets: ["SendRequestFeature"]),
     .library(name: "UserSearchFeature", targets: ["UserSearchFeature"]),
     .library(name: "WelcomeFeature", targets: ["WelcomeFeature"]),
   ],
@@ -52,6 +54,7 @@ let package = Package(
       name: "AppCore",
       dependencies: [
         .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXDatabase", package: "client-ios-db"),
         .product(name: "XXModels", package: "client-ios-db"),
       ],
@@ -68,9 +71,11 @@ let package = Package(
       name: "AppFeature",
       dependencies: [
         .target(name: "AppCore"),
+        .target(name: "ContactFeature"),
         .target(name: "HomeFeature"),
         .target(name: "RegisterFeature"),
         .target(name: "RestoreFeature"),
+        .target(name: "SendRequestFeature"),
         .target(name: "UserSearchFeature"),
         .target(name: "WelcomeFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
@@ -87,6 +92,25 @@ let package = Package(
       ],
       swiftSettings: swiftSettings
     ),
+    .target(
+      name: "ContactFeature",
+      dependencies: [
+        .target(name: "AppCore"),
+        .target(name: "SendRequestFeature"),
+        .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXModels", package: "client-ios-db"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .testTarget(
+      name: "ContactFeatureTests",
+      dependencies: [
+        .target(name: "ContactFeature"),
+      ],
+      swiftSettings: swiftSettings
+    ),
     .target(
       name: "HomeFeature",
       dependencies: [
@@ -111,6 +135,7 @@ let package = Package(
       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"),
       ],
@@ -137,12 +162,33 @@ let package = Package(
       ],
       swiftSettings: swiftSettings
     ),
+    .target(
+      name: "SendRequestFeature",
+      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: "SendRequestFeatureTests",
+      dependencies: [
+        .target(name: "SendRequestFeature"),
+      ],
+      swiftSettings: swiftSettings
+    ),
     .target(
       name: "UserSearchFeature",
       dependencies: [
+        .target(name: "ContactFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
         .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
     ),
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 041cf3f70c88c188af478b1c3ec2bb88d5a7e4f1..0d7cb106a1c962bb00f93bd1668ae0e80dbcc173 100644
--- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
+++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
@@ -49,6 +49,16 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "ContactFeatureTests"
+               BuildableName = "ContactFeatureTests"
+               BlueprintName = "ContactFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
@@ -79,6 +89,16 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "SendRequestFeatureTests"
+               BuildableName = "SendRequestFeatureTests"
+               BlueprintName = "SendRequestFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
diff --git a/Examples/xx-messenger/Sources/AppCore/DBManager/DBManager.swift b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManager.swift
index 178e1078e56aaddaf6ef8170834ea830b944aac5..f591dc1c2d37c3f14cf1a47905ccee2e3379fad1 100644
--- a/Examples/xx-messenger/Sources/AppCore/DBManager/DBManager.swift
+++ b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManager.swift
@@ -4,6 +4,7 @@ public struct DBManager {
   public var hasDB: DBManagerHasDB
   public var makeDB: DBManagerMakeDB
   public var getDB: DBManagerGetDB
+  public var removeDB: DBManagerRemoveDB
 }
 
 extension DBManager {
@@ -17,7 +18,8 @@ extension DBManager {
     return DBManager(
       hasDB: .init { container.db != nil },
       makeDB: .live(setDB: { container.db = $0 }),
-      getDB: .live(getDB: { container.db })
+      getDB: .live(getDB: { container.db }),
+      removeDB: .live(getDB: { container.db }, unsetDB: { container.db = nil })
     )
   }
 }
@@ -26,6 +28,7 @@ extension DBManager {
   public static let unimplemented = DBManager(
     hasDB: .unimplemented,
     makeDB: .unimplemented,
-    getDB: .unimplemented
+    getDB: .unimplemented,
+    removeDB: .unimplemented
   )
 }
diff --git a/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerRemoveDB.swift b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerRemoveDB.swift
new file mode 100644
index 0000000000000000000000000000000000000000..69ab6e020d1ae782b048c82d75fb667eb3d7b985
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerRemoveDB.swift
@@ -0,0 +1,30 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXDatabase
+import XXModels
+
+public struct DBManagerRemoveDB {
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws -> Void {
+    try run()
+  }
+}
+
+extension DBManagerRemoveDB {
+  public static func live(
+    getDB: @escaping () -> Database?,
+    unsetDB: @escaping () -> Void
+  ) -> DBManagerRemoveDB {
+    DBManagerRemoveDB {
+      try getDB()?.drop()
+      unsetDB()
+    }
+  }
+}
+
+extension DBManagerRemoveDB {
+  public static let unimplemented = DBManagerRemoveDB(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 5d7777802ed09dcfb2e36b14ba2a5b55f1fe906e..298cb9f6e78b7d3bea1c0e5ec35f1fac235519ea 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -1,8 +1,10 @@
 import AppCore
+import ContactFeature
 import Foundation
 import HomeFeature
 import RegisterFeature
 import RestoreFeature
+import SendRequestFeature
 import UserSearchFeature
 import WelcomeFeature
 import XXMessengerClient
@@ -34,7 +36,7 @@ extension AppEnvironment {
       home: {
         HomeEnvironment(
           messenger: messenger,
-          db: dbManager.getDB,
+          dbManager: dbManager,
           mainQueue: mainQueue,
           bgQueue: bgQueue,
           register: {
@@ -50,7 +52,26 @@ extension AppEnvironment {
             UserSearchEnvironment(
               messenger: messenger,
               mainQueue: mainQueue,
-              bgQueue: bgQueue
+              bgQueue: bgQueue,
+              result: {
+                UserSearchResultEnvironment()
+              },
+              contact: {
+                ContactEnvironment(
+                  messenger: messenger,
+                  db: dbManager.getDB,
+                  mainQueue: mainQueue,
+                  bgQueue: bgQueue,
+                  sendRequest: {
+                    SendRequestEnvironment(
+                      messenger: messenger,
+                      db: dbManager.getDB,
+                      mainQueue: mainQueue,
+                      bgQueue: bgQueue
+                    )
+                  }
+                )
+              }
             )
           }
         )
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8126b792074a9a82050228cc0d0edbe8d26742ce
--- /dev/null
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
@@ -0,0 +1,152 @@
+import AppCore
+import ComposableArchitecture
+import ComposablePresentation
+import Foundation
+import SendRequestFeature
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+
+public struct ContactState: Equatable {
+  public init(
+    id: Data,
+    dbContact: XXModels.Contact? = nil,
+    xxContact: XXClient.Contact? = nil,
+    importUsername: Bool = true,
+    importEmail: Bool = true,
+    importPhone: Bool = true,
+    sendRequest: SendRequestState? = nil
+  ) {
+    self.id = id
+    self.dbContact = dbContact
+    self.xxContact = xxContact
+    self.importUsername = importUsername
+    self.importEmail = importEmail
+    self.importPhone = importPhone
+    self.sendRequest = sendRequest
+  }
+
+  public var id: Data
+  public var dbContact: XXModels.Contact?
+  public var xxContact: XXClient.Contact?
+  @BindableState public var importUsername: Bool
+  @BindableState public var importEmail: Bool
+  @BindableState public var importPhone: Bool
+  public var sendRequest: SendRequestState?
+}
+
+public enum ContactAction: Equatable, BindableAction {
+  case start
+  case dbContactFetched(XXModels.Contact?)
+  case importFactsTapped
+  case sendRequestTapped
+  case sendRequestDismissed
+  case sendRequest(SendRequestAction)
+  case binding(BindingAction<ContactState>)
+}
+
+public struct ContactEnvironment {
+  public init(
+    messenger: Messenger,
+    db: DBManagerGetDB,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>,
+    sendRequest: @escaping () -> SendRequestEnvironment
+  ) {
+    self.messenger = messenger
+    self.db = db
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+    self.sendRequest = sendRequest
+  }
+
+  public var messenger: Messenger
+  public var db: DBManagerGetDB
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
+  public var sendRequest: () -> SendRequestEnvironment
+}
+
+#if DEBUG
+extension ContactEnvironment {
+  public static let unimplemented = ContactEnvironment(
+    messenger: .unimplemented,
+    db: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented,
+    sendRequest: { .unimplemented }
+  )
+}
+#endif
+
+public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironment>
+{ state, action, env in
+  enum DBFetchEffectID {}
+
+  switch action {
+  case .start:
+    return try! env.db().fetchContactsPublisher(.init(id: [state.id]))
+      .assertNoFailure()
+      .map(\.first)
+      .map(ContactAction.dbContactFetched)
+      .subscribe(on: env.bgQueue)
+      .receive(on: env.mainQueue)
+      .eraseToEffect()
+      .cancellable(id: DBFetchEffectID.self, cancelInFlight: true)
+
+  case .dbContactFetched(let contact):
+    state.dbContact = contact
+    return .none
+
+  case .importFactsTapped:
+    guard let xxContact = state.xxContact else { return .none }
+    return .fireAndForget { [state] in
+      var dbContact = state.dbContact ?? XXModels.Contact(id: state.id)
+      dbContact.marshaled = xxContact.data
+      if state.importUsername {
+        dbContact.username = try? xxContact.getFact(.username)?.fact
+      }
+      if state.importEmail {
+        dbContact.email = try? xxContact.getFact(.email)?.fact
+      }
+      if state.importPhone {
+        dbContact.phone = try? xxContact.getFact(.phone)?.fact
+      }
+      _ = try! env.db().saveContact(dbContact)
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .sendRequestTapped:
+    if let xxContact = state.xxContact {
+      state.sendRequest = SendRequestState(contact: xxContact)
+    } else if let marshaled = state.dbContact?.marshaled {
+      state.sendRequest = SendRequestState(contact: .live(marshaled))
+    }
+    return .none
+
+  case .sendRequestDismissed:
+    state.sendRequest = nil
+    return .none
+
+  case .sendRequest(.sendSucceeded):
+    state.sendRequest = nil
+    return .none
+
+  case .sendRequest(_):
+    return .none
+
+  case .binding(_):
+    return .none
+  }
+}
+.binding()
+.presenting(
+  sendRequestReducer,
+  state: .keyPath(\.sendRequest),
+  id: .notNil(),
+  action: /ContactAction.sendRequest,
+  environment: { $0.sendRequest() }
+)
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..48743b07af69b695c6f6ec34159d2b0c75400458
--- /dev/null
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
@@ -0,0 +1,226 @@
+import AppCore
+import ComposableArchitecture
+import ComposablePresentation
+import SendRequestFeature
+import SwiftUI
+import XXClient
+import XXModels
+
+public struct ContactView: View {
+  public init(store: Store<ContactState, ContactAction>) {
+    self.store = store
+  }
+
+  let store: Store<ContactState, ContactAction>
+
+  struct ViewState: Equatable {
+    var dbContact: XXModels.Contact?
+    var xxContactIsSet: Bool
+    var xxContactUsername: String?
+    var xxContactEmail: String?
+    var xxContactPhone: String?
+    var importUsername: Bool
+    var importEmail: Bool
+    var importPhone: Bool
+
+    init(state: ContactState) {
+      dbContact = state.dbContact
+      xxContactIsSet = state.xxContact != nil
+      xxContactUsername = try? state.xxContact?.getFact(.username)?.fact
+      xxContactEmail = try? state.xxContact?.getFact(.email)?.fact
+      xxContactPhone = try? state.xxContact?.getFact(.phone)?.fact
+      importUsername = state.importUsername
+      importEmail = state.importEmail
+      importPhone = state.importPhone
+    }
+  }
+
+  public var body: some View {
+    WithViewStore(store.scope(state: ViewState.init)) { viewStore in
+      Form {
+        if viewStore.xxContactIsSet {
+          Section {
+            Button {
+              viewStore.send(.set(\.$importUsername, !viewStore.importUsername))
+            } label: {
+              HStack {
+                Label(viewStore.xxContactUsername ?? "", systemImage: "person")
+                  .tint(Color.primary)
+                Spacer()
+                Image(systemName: viewStore.importUsername ? "checkmark.circle.fill" : "circle")
+                  .foregroundColor(.accentColor)
+              }
+            }
+
+            Button {
+              viewStore.send(.set(\.$importEmail, !viewStore.importEmail))
+            } label: {
+              HStack {
+                Label(viewStore.xxContactEmail ?? "", systemImage: "envelope")
+                  .tint(Color.primary)
+                Spacer()
+                Image(systemName: viewStore.importEmail ? "checkmark.circle.fill" : "circle")
+                  .foregroundColor(.accentColor)
+              }
+            }
+
+            Button {
+              viewStore.send(.set(\.$importPhone, !viewStore.importPhone))
+            } label: {
+              HStack {
+                Label(viewStore.xxContactPhone ?? "", systemImage: "phone")
+                  .tint(Color.primary)
+                Spacer()
+                Image(systemName: viewStore.importPhone ? "checkmark.circle.fill" : "circle")
+                  .foregroundColor(.accentColor)
+              }
+            }
+
+            Button {
+              viewStore.send(.importFactsTapped)
+            } label: {
+              if viewStore.dbContact == nil {
+                Text("Save contact")
+              } else {
+                Text("Update contact")
+              }
+            }
+          } header: {
+            Text("Facts")
+          }
+        }
+
+        if let dbContact = viewStore.dbContact {
+          Section {
+            Label(dbContact.username ?? "", systemImage: "person")
+            Label(dbContact.email ?? "", systemImage: "envelope")
+            Label(dbContact.phone ?? "", systemImage: "phone")
+          } header: {
+            Text("Contact")
+          }
+
+          Section {
+            switch dbContact.authStatus {
+            case .stranger:
+              HStack {
+                Text("Stranger")
+                Spacer()
+                Image(systemName: "person.fill.questionmark")
+              }
+
+            case .requesting:
+              HStack {
+                Text("Sending auth request")
+                Spacer()
+                ProgressView()
+              }
+
+            case .requested:
+              HStack {
+                Text("Request sent")
+                Spacer()
+                Image(systemName: "paperplane")
+              }
+
+            case .requestFailed:
+              HStack {
+                Text("Sending request failed")
+                Spacer()
+                Image(systemName: "xmark.diamond.fill")
+                  .foregroundColor(.red)
+              }
+
+            case .verificationInProgress:
+              HStack {
+                Text("Verification is progress")
+                Spacer()
+                ProgressView()
+              }
+
+            case .verified:
+              HStack {
+                Text("Verified")
+                Spacer()
+                Image(systemName: "person.fill.checkmark")
+              }
+
+            case .verificationFailed:
+              HStack {
+                Text("Verification failed")
+                Spacer()
+                Image(systemName: "xmark.diamond.fill")
+                  .foregroundColor(.red)
+              }
+
+            case .confirming:
+              HStack {
+                Text("Confirming auth request")
+                Spacer()
+                ProgressView()
+              }
+
+            case .confirmationFailed:
+              HStack {
+                Text("Confirmation failed")
+                Spacer()
+                Image(systemName: "xmark.diamond.fill")
+                  .foregroundColor(.red)
+              }
+
+            case .friend:
+              HStack {
+                Text("Friend")
+                Spacer()
+                Image(systemName: "person.fill.checkmark")
+              }
+
+            case .hidden:
+              HStack {
+                Text("Hidden")
+                Spacer()
+                Image(systemName: "eye.slash")
+              }
+            }
+            Button {
+              viewStore.send(.sendRequestTapped)
+            } label: {
+              HStack {
+                Text("Send request")
+                Spacer()
+                Image(systemName: "chevron.forward")
+              }
+            }
+          } header: {
+            Text("Auth")
+          }
+          .animation(.default, value: viewStore.dbContact?.authStatus)
+        }
+      }
+      .navigationTitle("Contact")
+      .task { viewStore.send(.start) }
+      .background(NavigationLinkWithStore(
+        store.scope(
+          state: \.sendRequest,
+          action: ContactAction.sendRequest
+        ),
+        mapState: replayNonNil(),
+        onDeactivate: { viewStore.send(.sendRequestDismissed) },
+        destination: SendRequestView.init(store:)
+      ))
+    }
+  }
+}
+
+#if DEBUG
+public struct ContactView_Previews: PreviewProvider {
+  public static var previews: some View {
+    ContactView(store: Store(
+      initialState: ContactState(
+        id: "contact-id".data(using: .utf8)!
+      ),
+      reducer: .empty,
+      environment: ()
+    ))
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 51083ea0ccea39c7bf13abdd0ff0566dac623529..712855ba1ba4aecfe467679b4fcc87090de4ee43 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -71,14 +71,14 @@ public enum HomeAction: Equatable {
 public struct HomeEnvironment {
   public init(
     messenger: Messenger,
-    db: DBManagerGetDB,
+    dbManager: DBManager,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
     register: @escaping () -> RegisterEnvironment,
     userSearch: @escaping () -> UserSearchEnvironment
   ) {
     self.messenger = messenger
-    self.db = db
+    self.dbManager = dbManager
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
     self.register = register
@@ -86,7 +86,7 @@ public struct HomeEnvironment {
   }
 
   public var messenger: Messenger
-  public var db: DBManagerGetDB
+  public var dbManager: DBManager
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
   public var register: () -> RegisterEnvironment
@@ -96,7 +96,7 @@ public struct HomeEnvironment {
 extension HomeEnvironment {
   public static let unimplemented = HomeEnvironment(
     messenger: .unimplemented,
-    db: .unimplemented,
+    dbManager: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
     register: { .unimplemented },
@@ -197,13 +197,13 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     return .result {
       do {
         let contactId = try env.messenger.e2e.tryGet().getContact().getId()
-        let contact = try env.db().fetchContacts(.init(id: [contactId])).first
+        let contact = try env.dbManager.getDB().fetchContacts(.init(id: [contactId])).first
         if let username = contact?.username {
           let ud = try env.messenger.ud.tryGet()
           try ud.permanentDeleteAccount(username: Fact(fact: username, type: 0))
         }
         try env.messenger.destroy()
-        try env.db().drop()
+        try env.dbManager.removeDB()
         return .success(.deleteAccount(.success))
       } catch {
         return .success(.deleteAccount(.failure(error as NSError)))
diff --git a/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift b/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
index 8e1f35411fdd0ec6d952f556420f9c72073c1e1f..cb43c43056f6d3e53c2b73b4bdfc10189f052b21 100644
--- a/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
+++ b/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
@@ -2,6 +2,7 @@ import AppCore
 import ComposableArchitecture
 import Foundation
 import XCTestDynamicOverlay
+import XXClient
 import XXMessengerClient
 import XXModels
 
@@ -81,7 +82,8 @@ public let registerReducer = Reducer<RegisterState, RegisterAction, RegisterEnvi
       do {
         let db = try env.db()
         try env.messenger.register(username: username)
-        let contact = env.messenger.e2e()!.getContact()
+        var contact = try env.messenger.e2e.tryGet().getContact()
+        try contact.setFact(.username, username)
         try db.saveContact(Contact(
           id: try contact.getId(),
           marshaled: contact.data,
diff --git a/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f2625b91e6a4042624ad68141515ab34ace7c90c
--- /dev/null
+++ b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift
@@ -0,0 +1,151 @@
+import AppCore
+import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+
+public struct SendRequestState: Equatable {
+  public init(
+    contact: XXClient.Contact,
+    myContact: XXClient.Contact? = nil,
+    sendUsername: Bool = true,
+    sendEmail: Bool = true,
+    sendPhone: Bool = true,
+    isSending: Bool = false,
+    failure: String? = nil
+  ) {
+    self.contact = contact
+    self.myContact = myContact
+    self.sendUsername = sendUsername
+    self.sendEmail = sendEmail
+    self.sendPhone = sendPhone
+    self.isSending = isSending
+    self.failure = failure
+  }
+
+  public var contact: XXClient.Contact
+  public var myContact: XXClient.Contact?
+  @BindableState public var sendUsername: Bool
+  @BindableState public var sendEmail: Bool
+  @BindableState public var sendPhone: Bool
+  public var isSending: Bool
+  public var failure: String?
+}
+
+public enum SendRequestAction: Equatable, BindableAction {
+  case start
+  case sendTapped
+  case sendSucceeded
+  case sendFailed(String)
+  case binding(BindingAction<SendRequestState>)
+  case myContactFetched(XXClient.Contact?)
+}
+
+public struct SendRequestEnvironment {
+  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 SendRequestEnvironment {
+  public static let unimplemented = SendRequestEnvironment(
+    messenger: .unimplemented,
+    db: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
+}
+#endif
+
+public let sendRequestReducer = Reducer<SendRequestState, SendRequestAction, SendRequestEnvironment>
+{ 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 { $0?.marshaled.map { XXClient.Contact.live($0) } }
+      .map(SendRequestAction.myContactFetched)
+      .subscribe(on: env.bgQueue)
+      .receive(on: env.mainQueue)
+      .eraseToEffect()
+      .cancellable(id: DBFetchEffectID.self, cancelInFlight: true)
+
+  case .myContactFetched(let contact):
+    state.myContact = contact
+    return .none
+
+  case .sendTapped:
+    state.isSending = true
+    state.failure = nil
+    return .result { [state] in
+      func updateAuthStatus(_ authStatus: XXModels.Contact.AuthStatus) throws {
+        try env.db().bulkUpdateContacts(
+          .init(id: [try state.contact.getId()]),
+          .init(authStatus: authStatus)
+        )
+      }
+      do {
+        try updateAuthStatus(.requesting)
+        let myFacts = try state.myContact?.getFacts() ?? []
+        var includedFacts: [Fact] = []
+        if state.sendUsername, let fact = myFacts.get(.username) {
+          includedFacts.append(fact)
+        }
+        if state.sendEmail, let fact = myFacts.get(.email) {
+          includedFacts.append(fact)
+        }
+        if state.sendPhone, let fact = myFacts.get(.phone) {
+          includedFacts.append(fact)
+        }
+        _ = try env.messenger.e2e.tryGet().requestAuthenticatedChannel(
+          partner: state.contact,
+          myFacts: includedFacts
+        )
+        try updateAuthStatus(.requested)
+        return .success(.sendSucceeded)
+      } catch {
+        try? updateAuthStatus(.requestFailed)
+        return .success(.sendFailed(error.localizedDescription))
+      }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .sendSucceeded:
+    state.isSending = false
+    state.failure = nil
+    return .none
+
+  case .sendFailed(let failure):
+    state.isSending = false
+    state.failure = failure
+    return .none
+
+  case .binding(_):
+    return .none
+  }
+}
+.binding()
diff --git a/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestView.swift b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..5f1cd7d53e8300fc55ffc907a410fb66c2b92c77
--- /dev/null
+++ b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestView.swift
@@ -0,0 +1,167 @@
+import AppCore
+import ComposableArchitecture
+import SwiftUI
+import XXClient
+
+public struct SendRequestView: View {
+  public init(store: Store<SendRequestState, SendRequestAction>) {
+    self.store = store
+  }
+
+  let store: Store<SendRequestState, SendRequestAction>
+
+  struct ViewState: Equatable {
+    var contactUsername: String?
+    var contactEmail: String?
+    var contactPhone: String?
+    var myUsername: String?
+    var myEmail: String?
+    var myPhone: String?
+    var sendUsername: Bool
+    var sendEmail: Bool
+    var sendPhone: Bool
+    var isSending: Bool
+    var failure: String?
+
+    init(state: SendRequestState) {
+      contactUsername = try? state.contact.getFact(.username)?.fact
+      contactEmail = try? state.contact.getFact(.email)?.fact
+      contactPhone = try? state.contact.getFact(.phone)?.fact
+      myUsername = try? state.myContact?.getFact(.username)?.fact
+      myEmail = try? state.myContact?.getFact(.email)?.fact
+      myPhone = try? state.myContact?.getFact(.phone)?.fact
+      sendUsername = state.sendUsername
+      sendEmail = state.sendEmail
+      sendPhone = state.sendPhone
+      isSending = state.isSending
+      failure = state.failure
+    }
+  }
+
+  public var body: some View {
+    WithViewStore(store.scope(state: ViewState.init)) { viewStore in
+      Form {
+        Section {
+          Button {
+            viewStore.send(.set(\.$sendUsername, !viewStore.sendUsername))
+          } label: {
+            HStack {
+              Label(viewStore.myUsername ?? "", systemImage: "person")
+                .tint(Color.primary)
+              Spacer()
+              Image(systemName: viewStore.sendUsername ? "checkmark.circle.fill" : "circle")
+                .foregroundColor(.accentColor)
+            }
+          }
+          .animation(.default, value: viewStore.sendUsername)
+
+          Button {
+            viewStore.send(.set(\.$sendEmail, !viewStore.sendEmail))
+          } label: {
+            HStack {
+              Label(viewStore.myEmail ?? "", systemImage: "envelope")
+                .tint(Color.primary)
+              Spacer()
+              Image(systemName: viewStore.sendEmail ? "checkmark.circle.fill" : "circle")
+                .foregroundColor(.accentColor)
+            }
+          }
+          .animation(.default, value: viewStore.sendEmail)
+
+          Button {
+            viewStore.send(.set(\.$sendPhone, !viewStore.sendPhone))
+          } label: {
+            HStack {
+              Label(viewStore.myPhone ?? "", systemImage: "phone")
+                .tint(Color.primary)
+              Spacer()
+              Image(systemName: viewStore.sendPhone ? "checkmark.circle.fill" : "circle")
+                .foregroundColor(.accentColor)
+            }
+          }
+          .animation(.default, value: viewStore.sendPhone)
+        } header: {
+          Text("My facts")
+        }
+        .disabled(viewStore.isSending)
+
+        Section {
+          Label(viewStore.contactUsername ?? "", systemImage: "person")
+          Label(viewStore.contactEmail ?? "", systemImage: "envelope")
+          Label(viewStore.contactPhone ?? "", systemImage: "phone")
+        } header: {
+          Text("Contact")
+        }
+
+        Section {
+          Button {
+            viewStore.send(.sendTapped)
+          } label: {
+            HStack {
+              Text("Send request")
+              Spacer()
+              if viewStore.isSending {
+                ProgressView()
+              } else {
+                Image(systemName: "paperplane")
+              }
+            }
+          }
+        }
+        .disabled(viewStore.isSending)
+
+        if let failure = viewStore.failure {
+          Section {
+            Text(failure)
+          } header: {
+            Text("Error")
+          }
+        }
+      }
+      .navigationTitle("Send Request")
+      .task { viewStore.send(.start) }
+    }
+  }
+}
+
+#if DEBUG
+public struct SendRequestView_Previews: PreviewProvider {
+  public static var previews: some View {
+    NavigationView {
+      SendRequestView(store: Store(
+        initialState: SendRequestState(
+          contact: {
+            var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
+            contact.getFactsFromContact.run = { _ in
+              [
+                Fact(fact: "contact-username", type: 0),
+                Fact(fact: "contact-email", type: 1),
+                Fact(fact: "contact-phone", type: 2),
+              ]
+            }
+            return contact
+          }(),
+          myContact: {
+            var contact = XXClient.Contact.unimplemented("my-data".data(using: .utf8)!)
+            contact.getFactsFromContact.run = { _ in
+              [
+                Fact(fact: "my-username", type: 0),
+                Fact(fact: "my-email", type: 1),
+                Fact(fact: "my-phone", type: 2),
+              ]
+            }
+            return contact
+          }(),
+          sendUsername: true,
+          sendEmail: false,
+          sendPhone: true,
+          isSending: false,
+          failure: "Something went wrong"
+        ),
+        reducer: .empty,
+        environment: ()
+      ))
+    }
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift
index 86a13c0ec97966619bea881f5ac0b8c9de962fed..1b10c1b3c416b6cac1ae579da1b71f186f83c583 100644
--- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift
+++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift
@@ -1,4 +1,6 @@
 import ComposableArchitecture
+import ComposablePresentation
+import ContactFeature
 import Foundation
 import XCTestDynamicOverlay
 import XXClient
@@ -11,70 +13,60 @@ public struct UserSearchState: Equatable {
     case phone
   }
 
-  public struct Result: Equatable, Identifiable {
-    public init(
-      id: Data,
-      contact: Contact,
-      username: String? = nil,
-      email: String? = nil,
-      phone: String? = nil
-    ) {
-      self.id = id
-      self.contact = contact
-      self.username = username
-      self.email = email
-      self.phone = phone
-    }
-
-    public var id: Data
-    public var contact: XXClient.Contact
-    public var username: String?
-    public var email: String?
-    public var phone: String?
-  }
-
   public init(
     focusedField: Field? = nil,
     query: MessengerSearchUsers.Query = .init(),
     isSearching: Bool = false,
     failure: String? = nil,
-    results: IdentifiedArrayOf<Result> = []
+    results: IdentifiedArrayOf<UserSearchResultState> = [],
+    contact: ContactState? = nil
   ) {
     self.focusedField = focusedField
     self.query = query
     self.isSearching = isSearching
     self.failure = failure
     self.results = results
+    self.contact = contact
   }
 
   @BindableState public var focusedField: Field?
   @BindableState public var query: MessengerSearchUsers.Query
   public var isSearching: Bool
   public var failure: String?
-  public var results: IdentifiedArrayOf<Result>
+  public var results: IdentifiedArrayOf<UserSearchResultState>
+  public var contact: ContactState?
 }
 
 public enum UserSearchAction: Equatable, BindableAction {
   case searchTapped
   case didFail(String)
   case didSucceed([Contact])
+  case didDismissContact
   case binding(BindingAction<UserSearchState>)
+  case result(id: UserSearchResultState.ID, action: UserSearchResultAction)
+  case contact(ContactAction)
 }
 
 public struct UserSearchEnvironment {
   public init(
     messenger: Messenger,
     mainQueue: AnySchedulerOf<DispatchQueue>,
-    bgQueue: AnySchedulerOf<DispatchQueue>
+    bgQueue: AnySchedulerOf<DispatchQueue>,
+    result: @escaping () -> UserSearchResultEnvironment,
+    contact: @escaping () -> ContactEnvironment
   ) {
     self.messenger = messenger
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
+    self.result = result
+    self.contact = contact
   }
 
   public var messenger: Messenger
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
+  public var result: () -> UserSearchResultEnvironment
+  public var contact: () -> ContactEnvironment
 }
 
 #if DEBUG
@@ -82,7 +74,9 @@ extension UserSearchEnvironment {
   public static let unimplemented = UserSearchEnvironment(
     messenger: .unimplemented,
     mainQueue: .unimplemented,
-    bgQueue: .unimplemented
+    bgQueue: .unimplemented,
+    result: { .unimplemented },
+    contact: { .unimplemented }
   )
 }
 #endif
@@ -111,14 +105,7 @@ public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSe
     state.failure = nil
     state.results = IdentifiedArray(uniqueElements: contacts.compactMap { contact in
       guard let id = try? contact.getId() else { return nil }
-      let facts = (try? contact.getFacts()) ?? []
-      return UserSearchState.Result(
-        id: id,
-        contact: contact,
-        username: facts.first(where: { $0.type == 0 })?.fact,
-        email: facts.first(where: { $0.type == 1 })?.fact,
-        phone: facts.first(where: { $0.type == 2 })?.fact
-      )
+      return UserSearchResultState(id: id, xxContact: contact)
     })
     return .none
 
@@ -128,8 +115,32 @@ public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSe
     state.results = []
     return .none
 
-  case .binding(_):
+  case .didDismissContact:
+    state.contact = nil
+    return .none
+
+  case .result(let id, action: .tapped):
+    state.contact = ContactState(
+      id: id,
+      xxContact: state.results[id: id]?.xxContact
+    )
+    return .none
+
+  case .binding(_), .result(_, _), .contact(_):
     return .none
   }
 }
 .binding()
+.presenting(
+  forEach: userSearchResultReducer,
+  state: \.results,
+  action: /UserSearchAction.result(id:action:),
+  environment: { $0.result() }
+)
+.presenting(
+  contactReducer,
+  state: .keyPath(\.contact),
+  id: .keyPath(\.?.id),
+  action: /UserSearchAction.contact,
+  environment: { $0.contact() }
+)
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift
new file mode 100644
index 0000000000000000000000000000000000000000..839e6886236eec081b64616e64b8ff7c59811fbb
--- /dev/null
+++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift
@@ -0,0 +1,55 @@
+import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct UserSearchResultState: Equatable, Identifiable {
+  public init(
+    id: Data,
+    xxContact: XXClient.Contact,
+    username: String? = nil,
+    email: String? = nil,
+    phone: String? = nil
+  ) {
+    self.id = id
+    self.xxContact = xxContact
+    self.username = username
+    self.email = email
+    self.phone = phone
+  }
+
+  public var id: Data
+  public var xxContact: XXClient.Contact
+  public var username: String?
+  public var email: String?
+  public var phone: String?
+}
+
+public enum UserSearchResultAction: Equatable {
+  case start
+  case tapped
+}
+
+public struct UserSearchResultEnvironment {
+  public init() {}
+}
+
+#if DEBUG
+extension UserSearchResultEnvironment {
+  public static let unimplemented = UserSearchResultEnvironment()
+}
+#endif
+
+public let userSearchResultReducer = Reducer<UserSearchResultState, UserSearchResultAction, UserSearchResultEnvironment>
+{ state, action, env in
+  switch action {
+  case .start:
+    state.username = try? state.xxContact.getFact(.username)?.fact
+    state.email = try? state.xxContact.getFact(.email)?.fact
+    state.phone = try? state.xxContact.getFact(.phone)?.fact
+    return .none
+
+  case .tapped:
+    return .none
+  }
+}
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..fd29a84fb819761505b8b973c757f29a46597490
--- /dev/null
+++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift
@@ -0,0 +1,74 @@
+import ComposableArchitecture
+import SwiftUI
+import XXModels
+
+public struct UserSearchResultView: View {
+  public init(store: Store<UserSearchResultState, UserSearchResultAction>) {
+    self.store = store
+  }
+
+  let store: Store<UserSearchResultState, UserSearchResultAction>
+
+  struct ViewState: Equatable {
+    var username: String?
+    var email: String?
+    var phone: String?
+
+    init(state: UserSearchResultState) {
+      username = state.username
+      email = state.email
+      phone = state.phone
+    }
+
+    var isEmpty: Bool {
+      username == nil && email == nil && phone == nil
+    }
+  }
+
+  public var body: some View {
+    WithViewStore(store.scope(state: ViewState.init)) { viewStore in
+      Section {
+        Button {
+          viewStore.send(.tapped)
+        } label: {
+          HStack {
+            VStack {
+              if viewStore.isEmpty {
+                Image(systemName: "questionmark")
+                  .frame(maxWidth: .infinity)
+              } else {
+                if let username = viewStore.username {
+                  Text(username)
+                }
+                if let email = viewStore.email {
+                  Text(email)
+                }
+                if let phone = viewStore.phone {
+                  Text(phone)
+                }
+              }
+            }
+            Spacer()
+            Image(systemName: "chevron.forward")
+          }
+        }
+      }
+      .task { viewStore.send(.start) }
+    }
+  }
+}
+
+#if DEBUG
+public struct UserSearchResultView_Previews: PreviewProvider {
+  public static var previews: some View {
+    UserSearchResultView(store: Store(
+      initialState: UserSearchResultState(
+        id: "contact-id".data(using: .utf8)!,
+        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
+      reducer: .empty,
+      environment: ()
+    ))
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift
index 266938a29b096d05f695c2f39f55f8c0fb993442..f0416b3a53542ef4195a66ca077617327d62737c 100644
--- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift
+++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift
@@ -1,4 +1,6 @@
 import ComposableArchitecture
+import ComposablePresentation
+import ContactFeature
 import SwiftUI
 import XXMessengerClient
 
@@ -15,14 +17,12 @@ public struct UserSearchView: View {
     var query: MessengerSearchUsers.Query
     var isSearching: Bool
     var failure: String?
-    var results: IdentifiedArrayOf<UserSearchState.Result>
 
     init(state: UserSearchState) {
       focusedField = state.focusedField
       query = state.query
       isSearching = state.isSearching
       failure = state.failure
-      results = state.results
     }
   }
 
@@ -87,27 +87,25 @@ public struct UserSearchView: View {
           }
         }
 
-        ForEach(viewStore.results) { result in
-          Section {
-            if let username = result.username {
-              Text(username)
-            }
-            if let email = result.email {
-              Text(email)
-            }
-            if let phone = result.phone {
-              Text(phone)
-            }
-            if result.username == nil, result.email == nil, result.phone == nil {
-              Image(systemName: "questionmark")
-                .frame(maxWidth: .infinity)
-            }
-          }
-        }
+        ForEachStore(
+          store.scope(
+            state: \.results,
+            action: UserSearchAction.result(id:action:)
+          ),
+          content: UserSearchResultView.init(store:)
+        )
       }
       .onChange(of: viewStore.focusedField) { focusedField = $0 }
       .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) }
       .navigationTitle("User Search")
+      .background(NavigationLinkWithStore(
+        store.scope(
+          state: \.contact,
+          action: UserSearchAction.contact
+        ),
+        onDeactivate: { viewStore.send(.didDismissContact) },
+        destination: ContactView.init(store:)
+      ))
     }
   }
 }
diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..244cc90093c2267fd124a268982c5e65e5c57380
--- /dev/null
+++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
@@ -0,0 +1,166 @@
+import Combine
+import ComposableArchitecture
+import CustomDump
+import SendRequestFeature
+import XCTest
+import XXClient
+import XXModels
+@testable import ContactFeature
+
+final class ContactFeatureTests: XCTestCase {
+  func testStart() {
+    let store = TestStore(
+      initialState: ContactState(
+        id: "contact-id".data(using: .utf8)!
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    var dbDidFetchContacts: [XXModels.Contact.Query] = []
+    let dbContactsPublisher = PassthroughSubject<[XXModels.Contact], Error>()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    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: ["contact-id".data(using: .utf8)!])
+    ])
+
+    let dbContact = XXModels.Contact(id: "contact-id".data(using: .utf8)!)
+    dbContactsPublisher.send([dbContact])
+
+    store.receive(.dbContactFetched(dbContact)) {
+      $0.dbContact = dbContact
+    }
+
+    dbContactsPublisher.send(completion: .finished)
+  }
+
+  func testImportFacts() {
+    let dbContact: XXModels.Contact = .init(
+      id: "contact-id".data(using: .utf8)!
+    )
+
+    var xxContact: XXClient.Contact = .unimplemented("contact-data".data(using: .utf8)!)
+    xxContact.getFactsFromContact.run = { _ in
+      [
+        Fact(fact: "contact-username", type: 0),
+        Fact(fact: "contact-email", type: 1),
+        Fact(fact: "contact-phone", type: 2),
+      ]
+    }
+
+    let store = TestStore(
+      initialState: ContactState(
+        id: "contact-id".data(using: .utf8)!,
+        dbContact: dbContact,
+        xxContact: xxContact
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    var dbDidSaveContact: [XXModels.Contact] = []
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.saveContact.run = { contact in
+        dbDidSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.importFactsTapped)
+
+    var expectedSavedContact = dbContact
+    expectedSavedContact.marshaled = xxContact.data
+    expectedSavedContact.username = "contact-username"
+    expectedSavedContact.email = "contact-email"
+    expectedSavedContact.phone = "contact-phone"
+
+    XCTAssertNoDifference(dbDidSaveContact, [expectedSavedContact])
+  }
+
+  func testSendRequestWithDBContact() {
+    var dbContact = XXModels.Contact(id: "contact-id".data(using: .utf8)!)
+    dbContact.marshaled = "contact-data".data(using: .utf8)!
+
+    let store = TestStore(
+      initialState: ContactState(
+        id: dbContact.id,
+        dbContact: dbContact
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.sendRequestTapped) {
+      $0.sendRequest = SendRequestState(contact: .live(dbContact.marshaled!))
+    }
+  }
+
+  func testSendRequestWithXXContact() {
+    let xxContact = XXClient.Contact.unimplemented("contact-id".data(using: .utf8)!)
+
+    let store = TestStore(
+      initialState: ContactState(
+        id: "contact-id".data(using: .utf8)!,
+        xxContact: xxContact
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.sendRequestTapped) {
+      $0.sendRequest = SendRequestState(contact: xxContact)
+    }
+  }
+
+  func testSendRequestDismissed() {
+    let store = TestStore(
+      initialState: ContactState(
+        id: "contact-id".data(using: .utf8)!,
+        sendRequest: SendRequestState(
+          contact: .unimplemented("contact-id".data(using: .utf8)!)
+        )
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.sendRequestDismissed) {
+      $0.sendRequest = nil
+    }
+  }
+
+  func testSendRequestSucceeded() {
+    let store = TestStore(
+      initialState: ContactState(
+        id: "contact-id".data(using: .utf8)!,
+        sendRequest: SendRequestState(
+          contact: .unimplemented("contact-id".data(using: .utf8)!)
+        )
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.sendRequest(.sendSucceeded)) {
+      $0.sendRequest = nil
+    }
+  }
+}
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index aa309016c08c4a75e62c6ecd5cf47e32191832e1..cbc261cefd8f85153c3316d08bada0337cceea35 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -316,7 +316,7 @@ final class HomeFeatureTests: XCTestCase {
     var dbDidFetchContacts: [XXModels.Contact.Query] = []
     var udDidPermanentDeleteAccount: [Fact] = []
     var messengerDidDestroy = 0
-    var dbDidDrop = 0
+    var didRemoveDB = 0
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
@@ -329,7 +329,7 @@ final class HomeFeatureTests: XCTestCase {
       }
       return e2e
     }
-    store.environment.db.run = {
+    store.environment.dbManager.getDB.run = {
       var db: Database = .failing
       db.fetchContacts.run = { query in
         dbDidFetchContacts.append(query)
@@ -341,11 +341,11 @@ final class HomeFeatureTests: XCTestCase {
           )
         ]
       }
-      db.drop.run = {
-        dbDidDrop += 1
-      }
       return db
     }
+    store.environment.dbManager.removeDB.run = {
+      didRemoveDB += 1
+    }
     store.environment.messenger.ud.get = {
       var ud: UserDiscovery = .unimplemented
       ud.permanentDeleteAccount.run = { usernameFact in
@@ -372,7 +372,7 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(dbDidFetchContacts, [.init(id: ["contact-id".data(using: .utf8)!])])
     XCTAssertNoDifference(udDidPermanentDeleteAccount, [Fact(fact: "MyUsername", type: 0)])
     XCTAssertNoDifference(messengerDidDestroy, 1)
-    XCTAssertNoDifference(dbDidDrop, 1)
+    XCTAssertNoDifference(didRemoveDB, 1)
 
     store.receive(.deleteAccount(.success)) {
       $0.isDeletingAccount = false
diff --git a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
index a2aad5bd7977dbd210a163d8cdac9001de239723..12addba7f1a5901fcf2d9424d44e791db8ab4904 100644
--- a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
@@ -16,6 +16,7 @@ final class RegisterFeatureTests: XCTestCase {
     let now = Date()
     let mainQueue = DispatchQueue.test
     let bgQueue = DispatchQueue.test
+    var didSetFactsOnContact: [[XXClient.Fact]] = []
     var dbDidSaveContact: [XXModels.Contact] = []
     var messengerDidRegisterUsername: [String] = []
 
@@ -30,6 +31,11 @@ final class RegisterFeatureTests: XCTestCase {
       e2e.getContact.run = {
         var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
         contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! }
+        contact.getFactsFromContact.run = { _ in [] }
+        contact.setFactsOnContact.run = { data, facts in
+          didSetFactsOnContact.append(facts)
+          return data
+        }
         return contact
       }
       return e2e
@@ -57,6 +63,7 @@ final class RegisterFeatureTests: XCTestCase {
     bgQueue.advance()
 
     XCTAssertNoDifference(messengerDidRegisterUsername, ["NewUser"])
+    XCTAssertNoDifference(didSetFactsOnContact, [[Fact(fact: "NewUser", type: 0)]])
     XCTAssertNoDifference(dbDidSaveContact, [
       XXModels.Contact(
         id: "contact-id".data(using: .utf8)!,
diff --git a/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1dbe26be1d99196abe4217b23f5daeba46fe15c7
--- /dev/null
+++ b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift
@@ -0,0 +1,185 @@
+import Combine
+import ComposableArchitecture
+import XCTest
+import XXClient
+import XXModels
+@testable import SendRequestFeature
+
+final class SendRequestFeatureTests: XCTestCase {
+  func testStart() {
+    let store = TestStore(
+      initialState: SendRequestState(
+        contact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
+      reducer: sendRequestReducer,
+      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("my-contact-data".data(using: .utf8)!)
+        contact.getIdFromContact.run = { _ in "my-contact-id".data(using: .utf8)! }
+        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: ["my-contact-id".data(using: .utf8)!])])
+
+    dbContactsPublisher.send([])
+
+    store.receive(.myContactFetched(nil))
+
+    var myDbContact = XXModels.Contact(id: "my-contact-id".data(using: .utf8)!)
+    myDbContact.marshaled = "my-contact-data".data(using: .utf8)!
+    dbContactsPublisher.send([myDbContact])
+
+    store.receive(.myContactFetched(.live("my-contact-data".data(using: .utf8)!))) {
+      $0.myContact = .live("my-contact-data".data(using: .utf8)!)
+    }
+
+    dbContactsPublisher.send(completion: .finished)
+  }
+
+  func testSendRequest() {
+    var contact: XXClient.Contact = .unimplemented("contact-data".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! }
+
+    var myContact: XXClient.Contact = .unimplemented("my-contact-data".data(using: .utf8)!)
+    let myFacts = [
+      Fact(fact: "my-username", type: 0),
+      Fact(fact: "my-email", type: 1),
+      Fact(fact: "my-phone", type: 2),
+    ]
+    myContact.getFactsFromContact.run = { _ in myFacts }
+
+    let store = TestStore(
+      initialState: SendRequestState(
+        contact: contact,
+        myContact: myContact
+      ),
+      reducer: sendRequestReducer,
+      environment: .unimplemented
+    )
+
+    struct DidBulkUpdateContacts: Equatable {
+      var query: XXModels.Contact.Query
+      var assignments: XXModels.Contact.Assignments
+    }
+    struct DidRequestAuthChannel: Equatable {
+      var partner: XXClient.Contact
+      var myFacts: [XXClient.Fact]
+    }
+
+    var didBulkUpdateContacts: [DidBulkUpdateContacts] = []
+    var didRequestAuthChannel: [DidRequestAuthChannel] = []
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.bulkUpdateContacts.run = { query, assignments in
+        didBulkUpdateContacts.append(.init(query: query, assignments: assignments))
+        return 0
+      }
+      return db
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.requestAuthenticatedChannel.run = { partner, myFacts in
+        didRequestAuthChannel.append(.init(partner: partner, myFacts: myFacts))
+        return 0
+      }
+      return e2e
+    }
+
+    store.send(.sendTapped) {
+      $0.isSending = true
+    }
+
+    XCTAssertNoDifference(didBulkUpdateContacts, [
+      .init(
+        query: .init(id: ["contact-id".data(using: .utf8)!]),
+        assignments: .init(authStatus: .requesting)
+      ),
+      .init(
+        query: .init(id: ["contact-id".data(using: .utf8)!]),
+        assignments: .init(authStatus: .requested)
+      )
+    ])
+
+    XCTAssertNoDifference(didRequestAuthChannel, [
+      .init(
+        partner: contact,
+        myFacts: myFacts
+      )
+    ])
+
+    store.receive(.sendSucceeded) {
+      $0.isSending = false
+    }
+  }
+
+  func testSendRequestFailure() {
+    var contact: XXClient.Contact = .unimplemented("contact-data".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! }
+
+    var myContact: XXClient.Contact = .unimplemented("my-contact-data".data(using: .utf8)!)
+    let myFacts = [
+      Fact(fact: "my-username", type: 0),
+      Fact(fact: "my-email", type: 1),
+      Fact(fact: "my-phone", type: 2),
+    ]
+    myContact.getFactsFromContact.run = { _ in myFacts }
+
+    let store = TestStore(
+      initialState: SendRequestState(
+        contact: contact,
+        myContact: myContact
+      ),
+      reducer: sendRequestReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let failure = Failure()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.bulkUpdateContacts.run = { _, _ in return 0 }
+      return db
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.requestAuthenticatedChannel.run = { _, _ in throw failure }
+      return e2e
+    }
+
+    store.send(.sendTapped) {
+      $0.isSending = true
+    }
+
+    store.receive(.sendFailed(failure.localizedDescription)) {
+      $0.isSending = false
+      $0.failure = failure.localizedDescription
+    }
+  }
+}
diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
index 8d2f414a0a6c4fa130ea285863ae92ef80f70c21..c457327c86722f020ffe2ffc55c251c3261fcc8a 100644
--- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
@@ -1,4 +1,5 @@
 import ComposableArchitecture
+import ContactFeature
 import XCTest
 import XXClient
 import XXMessengerClient
@@ -19,23 +20,12 @@ final class UserSearchFeatureTests: XCTestCase {
 
     var contact1 = Contact.unimplemented("contact-1".data(using: .utf8)!)
     contact1.getIdFromContact.run = { _ in "contact-1-id".data(using: .utf8)! }
-    contact1.getFactsFromContact.run = { _ in
-      [Fact(fact: "contact-1-username", type: 0),
-       Fact(fact: "contact-1-email", type: 1),
-       Fact(fact: "contact-1-phone", type: 2)]
-    }
     var contact2 = Contact.unimplemented("contact-1".data(using: .utf8)!)
     contact2.getIdFromContact.run = { _ in "contact-2-id".data(using: .utf8)! }
-    contact2.getFactsFromContact.run = { _ in
-      [Fact(fact: "contact-2-username", type: 0),
-       Fact(fact: "contact-2-email", type: 1),
-       Fact(fact: "contact-2-phone", type: 2)]
-    }
     var contact3 = Contact.unimplemented("contact-3".data(using: .utf8)!)
     contact3.getIdFromContact.run = { _ in throw GetIdFromContactError() }
     var contact4 = Contact.unimplemented("contact-4".data(using: .utf8)!)
     contact4.getIdFromContact.run = { _ in "contact-4-id".data(using: .utf8)! }
-    contact4.getFactsFromContact.run = { _ in throw GetFactsFromContactError() }
     let contacts = [contact1, contact2, contact3, contact4]
 
     store.environment.bgQueue = .immediate
@@ -64,27 +54,9 @@ final class UserSearchFeatureTests: XCTestCase {
       $0.isSearching = false
       $0.failure = nil
       $0.results = [
-        .init(
-          id: "contact-1-id".data(using: .utf8)!,
-          contact: contact1,
-          username: "contact-1-username",
-          email: "contact-1-email",
-          phone: "contact-1-phone"
-        ),
-        .init(
-          id: "contact-2-id".data(using: .utf8)!,
-          contact: contact2,
-          username: "contact-2-username",
-          email: "contact-2-email",
-          phone: "contact-2-phone"
-        ),
-        .init(
-          id: "contact-4-id".data(using: .utf8)!,
-          contact: contact4,
-          username: nil,
-          email: nil,
-          phone: nil
-        )
+        .init(id: "contact-1-id".data(using: .utf8)!, xxContact: contact1),
+        .init(id: "contact-2-id".data(using: .utf8)!, xxContact: contact2),
+        .init(id: "contact-4-id".data(using: .utf8)!, xxContact: contact4)
       ]
     }
   }
@@ -116,4 +88,42 @@ final class UserSearchFeatureTests: XCTestCase {
       $0.results = []
     }
   }
+
+  func testResultTapped() {
+    let store = TestStore(
+      initialState: UserSearchState(
+        results: [
+          .init(
+            id: "contact-id".data(using: .utf8)!,
+            xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+          )
+        ]
+      ),
+      reducer: userSearchReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.result(id: "contact-id".data(using: .utf8)!, action: .tapped)) {
+      $0.contact = ContactState(
+        id: "contact-id".data(using: .utf8)!,
+        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+      )
+    }
+  }
+
+  func testDismissingContact() {
+    let store = TestStore(
+      initialState: UserSearchState(
+        contact: ContactState(
+          id: "contact-id".data(using: .utf8)!
+        )
+      ),
+      reducer: userSearchReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.didDismissContact) {
+      $0.contact = nil
+    }
+  }
 }
diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c8f2a99b92b35b645c1d3e94462e6353ec4d33fc
--- /dev/null
+++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift
@@ -0,0 +1,46 @@
+import ComposableArchitecture
+import XCTest
+import XCTestDynamicOverlay
+import XXClient
+@testable import UserSearchFeature
+
+final class UserSearchResultFeatureTests: XCTestCase {
+  func testStart() {
+    var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+    contact.getFactsFromContact.run = { _ in
+      [
+        Fact(fact: "contact-username", type: 0),
+        Fact(fact: "contact-email", type: 1),
+        Fact(fact: "contact-phone", type: 2),
+      ]
+    }
+
+    let store = TestStore(
+      initialState: UserSearchResultState(
+        id: "contact-id".data(using: .utf8)!,
+        xxContact: contact
+      ),
+      reducer: userSearchResultReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.start) {
+      $0.username = "contact-username"
+      $0.email = "contact-email"
+      $0.phone = "contact-phone"
+    }
+  }
+
+  func testTapped() {
+    let store = TestStore(
+      initialState: UserSearchResultState(
+        id: "contact-id".data(using: .utf8)!,
+        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
+      reducer: userSearchResultReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.tapped)
+  }
+}