From 946ef9d0bae4d0065b971b56d556cc7893cba7da Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Sun, 11 Sep 2022 22:29:59 +0200
Subject: [PATCH 01/12] Add VerifyContactFeature library

---
 .../xcschemes/VerifyContactFeature.xcscheme   | 78 +++++++++++++++++++
 Examples/xx-messenger/Package.swift           | 13 ++++
 .../xcschemes/XXMessenger.xcscheme            | 10 +++
 .../VerifyContactFeature.swift                | 28 +++++++
 .../VerifyContactView.swift                   | 36 +++++++++
 .../VerifyContactFeatureTests.swift           | 15 ++++
 6 files changed, 180 insertions(+)
 create mode 100644 Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme
 create mode 100644 Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
 create mode 100644 Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift
 create mode 100644 Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift

diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme
new file mode 100644
index 00000000..54cf617e
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.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 = "VerifyContactFeature"
+               BuildableName = "VerifyContactFeature"
+               BlueprintName = "VerifyContactFeature"
+               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 = "VerifyContactFeatureTests"
+               BuildableName = "VerifyContactFeatureTests"
+               BlueprintName = "VerifyContactFeatureTests"
+               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 = "VerifyContactFeature"
+            BuildableName = "VerifyContactFeature"
+            BlueprintName = "VerifyContactFeature"
+            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 57081f3e..8c7a1c90 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -27,6 +27,7 @@ let package = Package(
     .library(name: "RestoreFeature", targets: ["RestoreFeature"]),
     .library(name: "SendRequestFeature", targets: ["SendRequestFeature"]),
     .library(name: "UserSearchFeature", targets: ["UserSearchFeature"]),
+    .library(name: "VerifyContactFeature", targets: ["VerifyContactFeature"]),
     .library(name: "WelcomeFeature", targets: ["WelcomeFeature"]),
   ],
   dependencies: [
@@ -223,6 +224,18 @@ let package = Package(
       ],
       swiftSettings: swiftSettings
     ),
+    .target(
+      name: "VerifyContactFeature",
+      dependencies: [
+        .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+      ]
+    ),
+    .testTarget(
+      name: "VerifyContactFeatureTests",
+      dependencies: [
+        .target(name: "VerifyContactFeature"),
+      ]
+    ),
     .target(
       name: "WelcomeFeature",
       dependencies: [
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 9d31f6f7..71af5875 100644
--- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
+++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
@@ -119,6 +119,16 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "VerifyContactFeatureTests"
+               BuildableName = "VerifyContactFeatureTests"
+               BlueprintName = "VerifyContactFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
new file mode 100644
index 00000000..c657dd72
--- /dev/null
+++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
@@ -0,0 +1,28 @@
+import ComposableArchitecture
+import XCTestDynamicOverlay
+
+public struct VerifyContactState: Equatable {
+  public init() {}
+}
+
+public enum VerifyContactAction: Equatable {
+  case start
+}
+
+public struct VerifyContactEnvironment {
+  public init() {}
+}
+
+#if DEBUG
+extension VerifyContactEnvironment {
+  public static let unimplemented = VerifyContactEnvironment()
+}
+#endif
+
+public let verifyContactReducer = Reducer<VerifyContactState, VerifyContactAction, VerifyContactEnvironment>
+{ state, action, env in
+  switch action {
+  case .start:
+    return .none
+  }
+}
diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift
new file mode 100644
index 00000000..ff2d76b2
--- /dev/null
+++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift
@@ -0,0 +1,36 @@
+import ComposableArchitecture
+import SwiftUI
+
+public struct VerifyContactView: View {
+  public init(store: Store<VerifyContactState, VerifyContactAction>) {
+    self.store = store
+  }
+
+  let store: Store<VerifyContactState, VerifyContactAction>
+
+  struct ViewState: Equatable {
+    init(state: VerifyContactState) {}
+  }
+
+  public var body: some View {
+    WithViewStore(store, observe: ViewState.init) { viewStore in
+      Form {
+
+      }
+      .navigationTitle("Verify Contact")
+      .task { viewStore.send(.start) }
+    }
+  }
+}
+
+#if DEBUG
+public struct VerifyContactView_Previews: PreviewProvider {
+  public static var previews: some View {
+    VerifyContactView(store: Store(
+      initialState: VerifyContactState(),
+      reducer: .empty,
+      environment: ()
+    ))
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
new file mode 100644
index 00000000..b0f41108
--- /dev/null
+++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
@@ -0,0 +1,15 @@
+import ComposableArchitecture
+import XCTest
+@testable import VerifyContactFeature
+
+final class VerifyContactFeatureTests: XCTestCase {
+  func testStart() {
+    let store = TestStore(
+      initialState: VerifyContactState(),
+      reducer: verifyContactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.start)
+  }
+}
-- 
GitLab


From 7735cfe7b022c0ad283ed14299c522be29fc5e3c Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Sun, 11 Sep 2022 22:53:20 +0200
Subject: [PATCH 02/12] Present VerifyContact from Contact

---
 Examples/xx-messenger/Package.swift           |  3 ++
 .../AppFeature/AppEnvironment+Live.swift      |  4 ++
 .../ContactFeature/ContactFeature.swift       | 37 +++++++++++++++---
 .../Sources/ContactFeature/ContactView.swift  | 18 +++++++++
 .../VerifyContactFeature.swift                |  9 ++++-
 .../VerifyContactView.swift                   |  4 +-
 .../ContactFeatureTests.swift                 | 39 +++++++++++++++++++
 .../VerifyContactFeatureTests.swift           |  4 +-
 8 files changed, 110 insertions(+), 8 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 8c7a1c90..3951efb5 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -81,6 +81,7 @@ let package = Package(
         .target(name: "RestoreFeature"),
         .target(name: "SendRequestFeature"),
         .target(name: "UserSearchFeature"),
+        .target(name: "VerifyContactFeature"),
         .target(name: "WelcomeFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
@@ -101,6 +102,7 @@ let package = Package(
       dependencies: [
         .target(name: "AppCore"),
         .target(name: "SendRequestFeature"),
+        .target(name: "VerifyContactFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
         .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
@@ -228,6 +230,7 @@ let package = Package(
       name: "VerifyContactFeature",
       dependencies: [
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
       ]
     ),
     .testTarget(
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 5267fcc3..beb07300 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -7,6 +7,7 @@ import RegisterFeature
 import RestoreFeature
 import SendRequestFeature
 import UserSearchFeature
+import VerifyContactFeature
 import WelcomeFeature
 import XXMessengerClient
 import XXModels
@@ -41,6 +42,9 @@ extension AppEnvironment {
           mainQueue: mainQueue,
           bgQueue: bgQueue
         )
+      },
+      verifyContact: {
+        VerifyContactEnvironment()
       }
     )
 
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
index 9f6ef294..9d7a03d8 100644
--- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
@@ -3,6 +3,7 @@ import ComposableArchitecture
 import ComposablePresentation
 import Foundation
 import SendRequestFeature
+import VerifyContactFeature
 import XCTestDynamicOverlay
 import XXClient
 import XXMessengerClient
@@ -16,7 +17,8 @@ public struct ContactState: Equatable {
     importUsername: Bool = true,
     importEmail: Bool = true,
     importPhone: Bool = true,
-    sendRequest: SendRequestState? = nil
+    sendRequest: SendRequestState? = nil,
+    verifyContact: VerifyContactState? = nil
   ) {
     self.id = id
     self.dbContact = dbContact
@@ -25,6 +27,7 @@ public struct ContactState: Equatable {
     self.importEmail = importEmail
     self.importPhone = importPhone
     self.sendRequest = sendRequest
+    self.verifyContact = verifyContact
   }
 
   public var id: Data
@@ -34,6 +37,7 @@ public struct ContactState: Equatable {
   @BindableState public var importEmail: Bool
   @BindableState public var importPhone: Bool
   public var sendRequest: SendRequestState?
+  public var verifyContact: VerifyContactState?
 }
 
 public enum ContactAction: Equatable, BindableAction {
@@ -43,6 +47,9 @@ public enum ContactAction: Equatable, BindableAction {
   case sendRequestTapped
   case sendRequestDismissed
   case sendRequest(SendRequestAction)
+  case verifyContactTapped
+  case verifyContactDismissed
+  case verifyContact(VerifyContactAction)
   case binding(BindingAction<ContactState>)
 }
 
@@ -52,13 +59,15 @@ public struct ContactEnvironment {
     db: DBManagerGetDB,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
-    sendRequest: @escaping () -> SendRequestEnvironment
+    sendRequest: @escaping () -> SendRequestEnvironment,
+    verifyContact: @escaping () -> VerifyContactEnvironment
   ) {
     self.messenger = messenger
     self.db = db
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
     self.sendRequest = sendRequest
+    self.verifyContact = verifyContact
   }
 
   public var messenger: Messenger
@@ -66,6 +75,7 @@ public struct ContactEnvironment {
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
   public var sendRequest: () -> SendRequestEnvironment
+  public var verifyContact: () -> VerifyContactEnvironment
 }
 
 #if DEBUG
@@ -75,7 +85,8 @@ extension ContactEnvironment {
     db: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
-    sendRequest: { .unimplemented }
+    sendRequest: { .unimplemented },
+    verifyContact: { .unimplemented }
   )
 }
 #endif
@@ -135,10 +146,19 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
     state.sendRequest = nil
     return .none
 
-  case .sendRequest(_):
+  case .verifyContactTapped:
+    if let marshaled = state.dbContact?.marshaled {
+      state.verifyContact = VerifyContactState(
+        xxContact: .live(marshaled)
+      )
+    }
+    return .none
+
+  case .verifyContactDismissed:
+    state.verifyContact = nil
     return .none
 
-  case .binding(_):
+  case .binding(_), .sendRequest(_), .verifyContact(_):
     return .none
   }
 }
@@ -150,3 +170,10 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
   action: /ContactAction.sendRequest,
   environment: { $0.sendRequest() }
 )
+.presenting(
+  verifyContactReducer,
+  state: .keyPath(\.verifyContact),
+  id: .notNil(),
+  action: /ContactAction.verifyContact,
+  environment: { $0.verifyContact() }
+)
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
index 1cd8fa2e..86202f15 100644
--- a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
@@ -3,6 +3,7 @@ import ComposableArchitecture
 import ComposablePresentation
 import SendRequestFeature
 import SwiftUI
+import VerifyContactFeature
 import XXClient
 import XXModels
 
@@ -114,6 +115,15 @@ public struct ContactView: View {
                 Image(systemName: "chevron.forward")
               }
             }
+            Button {
+              viewStore.send(.verifyContactTapped)
+            } label: {
+              HStack {
+                Text("Verify contact")
+                Spacer()
+                Image(systemName: "chevron.forward")
+              }
+            }
           } header: {
             Text("Auth")
           }
@@ -131,6 +141,14 @@ public struct ContactView: View {
         onDeactivate: { viewStore.send(.sendRequestDismissed) },
         destination: SendRequestView.init(store:)
       ))
+      .background(NavigationLinkWithStore(
+        store.scope(
+          state: \.verifyContact,
+          action: ContactAction.verifyContact
+        ),
+        onDeactivate: { viewStore.send(.verifyContactDismissed) },
+        destination: VerifyContactView.init(store:)
+      ))
     }
   }
 }
diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
index c657dd72..a64c43c4 100644
--- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
+++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
@@ -1,8 +1,15 @@
 import ComposableArchitecture
 import XCTestDynamicOverlay
+import XXClient
 
 public struct VerifyContactState: Equatable {
-  public init() {}
+  public init(
+    xxContact: XXClient.Contact
+  ) {
+    self.xxContact = xxContact
+  }
+
+  public var xxContact: XXClient.Contact
 }
 
 public enum VerifyContactAction: Equatable {
diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift
index ff2d76b2..fff1ad51 100644
--- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift
+++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift
@@ -27,7 +27,9 @@ public struct VerifyContactView: View {
 public struct VerifyContactView_Previews: PreviewProvider {
   public static var previews: some View {
     VerifyContactView(store: Store(
-      initialState: VerifyContactState(),
+      initialState: VerifyContactState(
+        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
       reducer: .empty,
       environment: ()
     ))
diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
index 247abb49..260ed279 100644
--- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
@@ -2,6 +2,7 @@ import Combine
 import ComposableArchitecture
 import CustomDump
 import SendRequestFeature
+import VerifyContactFeature
 import XCTest
 import XXClient
 import XXModels
@@ -163,4 +164,42 @@ final class ContactFeatureTests: XCTestCase {
       $0.sendRequest = nil
     }
   }
+
+  func testVerifyContactTapped() {
+    let contactData = "contact-data".data(using: .utf8)!
+    let store = TestStore(
+      initialState: ContactState(
+        id: Data(),
+        dbContact: XXModels.Contact(
+          id: Data(),
+          marshaled: contactData
+        )
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.verifyContactTapped) {
+      $0.verifyContact = VerifyContactState(
+        xxContact: .live(contactData)
+      )
+    }
+  }
+
+  func testVerifyContactDismissed() {
+    let store = TestStore(
+      initialState: ContactState(
+        id: "contact-id".data(using: .utf8)!,
+        verifyContact: VerifyContactState(
+          xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+        )
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.verifyContactDismissed) {
+      $0.verifyContact = nil
+    }
+  }
 }
diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
index b0f41108..212b3d0a 100644
--- a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
@@ -5,7 +5,9 @@ import XCTest
 final class VerifyContactFeatureTests: XCTestCase {
   func testStart() {
     let store = TestStore(
-      initialState: VerifyContactState(),
+      initialState: VerifyContactState(
+        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
       reducer: verifyContactReducer,
       environment: .unimplemented
     )
-- 
GitLab


From 0b255adcdde3b4fc145dd7e079ef115c4803dff6 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Sun, 11 Sep 2022 23:36:33 +0200
Subject: [PATCH 03/12] Implement contact verification

---
 Examples/xx-messenger/Package.swift           |  1 +
 .../AppFeature/AppEnvironment+Live.swift      |  6 +-
 .../ContactFeature/ContactFeature.swift       |  2 +-
 .../VerifyContactFeature.swift                | 61 +++++++++++++--
 .../VerifyContactView.swift                   | 68 ++++++++++++++++-
 .../ContactFeatureTests.swift                 |  4 +-
 .../VerifyContactFeatureTests.swift           | 75 ++++++++++++++++++-
 7 files changed, 200 insertions(+), 17 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 3951efb5..3548cd17 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -231,6 +231,7 @@ let package = Package(
       dependencies: [
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
       ]
     ),
     .testTarget(
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index beb07300..ad921825 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -44,7 +44,11 @@ extension AppEnvironment {
         )
       },
       verifyContact: {
-        VerifyContactEnvironment()
+        VerifyContactEnvironment(
+          messenger: messenger,
+          mainQueue: mainQueue,
+          bgQueue: bgQueue
+        )
       }
     )
 
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
index 9d7a03d8..15f32bed 100644
--- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
@@ -149,7 +149,7 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
   case .verifyContactTapped:
     if let marshaled = state.dbContact?.marshaled {
       state.verifyContact = VerifyContactState(
-        xxContact: .live(marshaled)
+        contact: .live(marshaled)
       )
     }
     return .none
diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
index a64c43c4..4a38f3ad 100644
--- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
+++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
@@ -1,35 +1,82 @@
 import ComposableArchitecture
+import Foundation
 import XCTestDynamicOverlay
 import XXClient
+import XXMessengerClient
 
 public struct VerifyContactState: Equatable {
+  public enum Result: Equatable {
+    case success(Bool)
+    case failure(String)
+  }
+
   public init(
-    xxContact: XXClient.Contact
+    contact: Contact,
+    isVerifying: Bool = false,
+    result: Result? = nil
   ) {
-    self.xxContact = xxContact
+    self.contact = contact
+    self.isVerifying = isVerifying
+    self.result = result
   }
 
-  public var xxContact: XXClient.Contact
+  public var contact: Contact
+  public var isVerifying: Bool
+  public var result: Result?
 }
 
 public enum VerifyContactAction: Equatable {
-  case start
+  case verifyTapped
+  case didVerify(VerifyContactState.Result)
 }
 
 public struct VerifyContactEnvironment {
-  public init() {}
+  public init(
+    messenger: Messenger,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>
+  ) {
+    self.messenger = messenger
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+  }
+
+  public var messenger: Messenger
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
 }
 
 #if DEBUG
 extension VerifyContactEnvironment {
-  public static let unimplemented = VerifyContactEnvironment()
+  public static let unimplemented = VerifyContactEnvironment(
+    messenger: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
 }
 #endif
 
 public let verifyContactReducer = Reducer<VerifyContactState, VerifyContactAction, VerifyContactEnvironment>
 { state, action, env in
   switch action {
-  case .start:
+  case .verifyTapped:
+    state.isVerifying = true
+    state.result = nil
+    return Effect.result { [state] in
+      do {
+        let result = try env.messenger.verifyContact(state.contact)
+        return .success(.didVerify(.success(result)))
+      } catch {
+        return .success(.didVerify(.failure(error.localizedDescription)))
+      }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .didVerify(let result):
+    state.isVerifying = false
+    state.result = result
     return .none
   }
 }
diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift
index fff1ad51..32b81ab7 100644
--- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift
+++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift
@@ -9,16 +9,78 @@ public struct VerifyContactView: View {
   let store: Store<VerifyContactState, VerifyContactAction>
 
   struct ViewState: Equatable {
-    init(state: VerifyContactState) {}
+    var username: String?
+    var email: String?
+    var phone: String?
+    var isVerifying: Bool
+    var result: VerifyContactState.Result?
+
+    init(state: VerifyContactState) {
+      username = try? state.contact.getFact(.username)?.value
+      email = try? state.contact.getFact(.email)?.value
+      phone = try? state.contact.getFact(.phone)?.value
+      isVerifying = state.isVerifying
+      result = state.result
+    }
   }
 
   public var body: some View {
     WithViewStore(store, observe: ViewState.init) { viewStore in
       Form {
+        Section {
+          Label(viewStore.username ?? "", systemImage: "person")
+          Label(viewStore.email ?? "", systemImage: "envelope")
+          Label(viewStore.phone ?? "", systemImage: "phone")
+        } header: {
+          Text("Facts")
+        }
+
+        Section {
+          Button {
+            viewStore.send(.verifyTapped)
+          } label: {
+            HStack {
+              Text("Verify")
+              Spacer()
+              if viewStore.isVerifying {
+                ProgressView()
+              } else {
+                Image(systemName: "play")
+              }
+            }
+          }
+          .disabled(viewStore.isVerifying)
+        }
+
+        if let result = viewStore.result {
+          Section {
+            HStack {
+              switch result {
+              case .success(true):
+                Text("Contact verified")
+                Spacer()
+                Image(systemName: "person.fill.checkmark")
+
+              case .success(false):
+                Text("Contact not verified")
+                Spacer()
+                Image(systemName: "person.fill.xmark")
 
+              case .failure(_):
+                Text("Verification failed")
+                Spacer()
+                Image(systemName: "xmark")
+              }
+            }
+            if case .failure(let failure) = result {
+              Text(failure)
+            }
+          } header: {
+            Text("Result")
+          }
+        }
       }
       .navigationTitle("Verify Contact")
-      .task { viewStore.send(.start) }
     }
   }
 }
@@ -28,7 +90,7 @@ public struct VerifyContactView_Previews: PreviewProvider {
   public static var previews: some View {
     VerifyContactView(store: Store(
       initialState: VerifyContactState(
-        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+        contact: .unimplemented("contact-data".data(using: .utf8)!)
       ),
       reducer: .empty,
       environment: ()
diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
index 260ed279..9a24cab9 100644
--- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
@@ -181,7 +181,7 @@ final class ContactFeatureTests: XCTestCase {
 
     store.send(.verifyContactTapped) {
       $0.verifyContact = VerifyContactState(
-        xxContact: .live(contactData)
+        contact: .unimplemented(contactData)
       )
     }
   }
@@ -191,7 +191,7 @@ final class ContactFeatureTests: XCTestCase {
       initialState: ContactState(
         id: "contact-id".data(using: .utf8)!,
         verifyContact: VerifyContactState(
-          xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+          contact: .unimplemented("contact-data".data(using: .utf8)!)
         )
       ),
       reducer: contactReducer,
diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
index 212b3d0a..3721bc46 100644
--- a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
@@ -1,17 +1,86 @@
 import ComposableArchitecture
 import XCTest
+import XXClient
 @testable import VerifyContactFeature
 
 final class VerifyContactFeatureTests: XCTestCase {
-  func testStart() {
+  func testVerify() {
     let store = TestStore(
       initialState: VerifyContactState(
-        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+        contact: .unimplemented("contact-data".data(using: .utf8)!)
       ),
       reducer: verifyContactReducer,
       environment: .unimplemented
     )
 
-    store.send(.start)
+    var didVerifyContact: [Contact] = []
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.verifyContact.run = { contact in
+      didVerifyContact.append(contact)
+      return true
+    }
+
+    store.send(.verifyTapped) {
+      $0.isVerifying = true
+      $0.result = nil
+    }
+
+    store.receive(.didVerify(.success(true))) {
+      $0.isVerifying = false
+      $0.result = .success(true)
+    }
+  }
+
+  func testVerifyNotVerified() {
+    let store = TestStore(
+      initialState: VerifyContactState(
+        contact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
+      reducer: verifyContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.verifyContact.run = { _ in false }
+
+    store.send(.verifyTapped) {
+      $0.isVerifying = true
+      $0.result = nil
+    }
+
+    store.receive(.didVerify(.success(false))) {
+      $0.isVerifying = false
+      $0.result = .success(false)
+    }
+  }
+
+  func testVerifyFailure() {
+    let store = TestStore(
+      initialState: VerifyContactState(
+        contact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
+      reducer: verifyContactReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.verifyContact.run = { _ in throw error }
+
+    store.send(.verifyTapped) {
+      $0.isVerifying = true
+      $0.result = nil
+    }
+
+    store.receive(.didVerify(.failure(error.localizedDescription))) {
+      $0.isVerifying = false
+      $0.result = .failure(error.localizedDescription)
+    }
   }
 }
-- 
GitLab


From 33256ca4d895d4fcc2ff71905e816b717cbf8c92 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 12 Sep 2022 00:07:51 +0200
Subject: [PATCH 04/12] Add CheckContactAuthFeature library

---
 .../CheckContactAuthFeature.xcscheme          |  78 +++++++++++++
 Examples/xx-messenger/Package.swift           |  16 +++
 .../xcschemes/XXMessenger.xcscheme            |  10 ++
 .../CheckContactAuthFeature.swift             |  84 +++++++++++++
 .../CheckContactAuthView.swift                | 101 ++++++++++++++++
 .../CheckContactAuthFeatureTests.swift        | 110 ++++++++++++++++++
 6 files changed, 399 insertions(+)
 create mode 100644 Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme
 create mode 100644 Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift
 create mode 100644 Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift
 create mode 100644 Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift

diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme
new file mode 100644
index 00000000..17099adb
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.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 = "CheckContactAuthFeature"
+               BuildableName = "CheckContactAuthFeature"
+               BlueprintName = "CheckContactAuthFeature"
+               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 = "CheckContactAuthFeatureTests"
+               BuildableName = "CheckContactAuthFeatureTests"
+               BlueprintName = "CheckContactAuthFeatureTests"
+               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 = "CheckContactAuthFeature"
+            BuildableName = "CheckContactAuthFeature"
+            BlueprintName = "CheckContactAuthFeature"
+            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 3548cd17..01878fbd 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -20,6 +20,7 @@ let package = Package(
   products: [
     .library(name: "AppCore", targets: ["AppCore"]),
     .library(name: "AppFeature", targets: ["AppFeature"]),
+    .library(name: "CheckContactAuthFeature", targets: ["CheckContactAuthFeature"]),
     .library(name: "ContactFeature", targets: ["ContactFeature"]),
     .library(name: "ContactsFeature", targets: ["ContactsFeature"]),
     .library(name: "HomeFeature", targets: ["HomeFeature"]),
@@ -97,6 +98,21 @@ let package = Package(
       ],
       swiftSettings: swiftSettings
     ),
+    .target(
+      name: "CheckContactAuthFeature",
+      dependencies: [
+        .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"),
+      ]
+    ),
+    .testTarget(
+      name: "CheckContactAuthFeatureTests",
+      dependencies: [
+        .target(name: "CheckContactAuthFeature"),
+      ]
+    ),
     .target(
       name: "ContactFeature",
       dependencies: [
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 71af5875..85d23241 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 = "CheckContactAuthFeatureTests"
+               BuildableName = "CheckContactAuthFeatureTests"
+               BlueprintName = "CheckContactAuthFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
diff --git a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift
new file mode 100644
index 00000000..119ff27a
--- /dev/null
+++ b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift
@@ -0,0 +1,84 @@
+import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+
+public struct CheckContactAuthState: Equatable {
+  public enum Result: Equatable {
+    case success(Bool)
+    case failure(String)
+  }
+
+  public init(
+    contact: Contact,
+    isChecking: Bool = false,
+    result: Result? = nil
+  ) {
+    self.contact = contact
+    self.isChecking = isChecking
+    self.result = result
+  }
+
+  public var contact: Contact
+  public var isChecking: Bool
+  public var result: Result?
+}
+
+public enum CheckContactAuthAction: Equatable {
+  case checkTapped
+  case didCheck(CheckContactAuthState.Result)
+}
+
+public struct CheckContactAuthEnvironment {
+  public init(
+    messenger: Messenger,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>
+  ) {
+    self.messenger = messenger
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+  }
+
+  public var messenger: Messenger
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
+}
+
+#if DEBUG
+extension CheckContactAuthEnvironment {
+  public static let unimplemented = CheckContactAuthEnvironment(
+    messenger: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
+}
+#endif
+
+public let checkContactAuthReducer = Reducer<CheckContactAuthState, CheckContactAuthAction, CheckContactAuthEnvironment>
+{ state, action, env in
+  switch action {
+  case .checkTapped:
+    state.isChecking = true
+    state.result = nil
+    return Effect.result { [state] in
+      do {
+        let e2e = try env.messenger.e2e.tryGet()
+        let contactId = try state.contact.getId()
+        let result = try e2e.hasAuthenticatedChannel(partnerId: contactId)
+        return .success(.didCheck(.success(result)))
+      } catch {
+        return .success(.didCheck(.failure(error.localizedDescription)))
+      }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .didCheck(let result):
+    state.isChecking = false
+    state.result = result
+    return .none
+  }
+}
diff --git a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift
new file mode 100644
index 00000000..dd2e7894
--- /dev/null
+++ b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift
@@ -0,0 +1,101 @@
+import ComposableArchitecture
+import SwiftUI
+import XXClient
+
+public struct CheckContactAuthView: View {
+  public init(store: Store<CheckContactAuthState, CheckContactAuthAction>) {
+    self.store = store
+  }
+
+  let store: Store<CheckContactAuthState, CheckContactAuthAction>
+
+  struct ViewState: Equatable {
+    var username: String?
+    var email: String?
+    var phone: String?
+    var isChecking: Bool
+    var result: CheckContactAuthState.Result?
+
+    init(state: CheckContactAuthState) {
+      username = try? state.contact.getFact(.username)?.value
+      email = try? state.contact.getFact(.email)?.value
+      phone = try? state.contact.getFact(.phone)?.value
+      isChecking = state.isChecking
+      result = state.result
+    }
+  }
+
+  public var body: some View {
+    WithViewStore(store.scope(state: ViewState.init)) { viewStore in
+      Form {
+        Section {
+          Label(viewStore.username ?? "", systemImage: "person")
+          Label(viewStore.email ?? "", systemImage: "envelope")
+          Label(viewStore.phone ?? "", systemImage: "phone")
+        } header: {
+          Text("Facts")
+        }
+
+        Section {
+          Button {
+            viewStore.send(.checkTapped)
+          } label: {
+            HStack {
+              Text("Check")
+              Spacer()
+              if viewStore.isChecking {
+                ProgressView()
+              } else {
+                Image(systemName: "play")
+              }
+            }
+          }
+          .disabled(viewStore.isChecking)
+        }
+
+        if let result = viewStore.result {
+          Section {
+            HStack {
+              switch result {
+              case .success(true):
+                Text("Authorized")
+                Spacer()
+                Image(systemName: "person.fill.checkmark")
+
+              case .success(false):
+                Text("Not authorized")
+                Spacer()
+                Image(systemName: "person.fill.xmark")
+
+              case .failure(_):
+                Text("Checking status failed")
+                Spacer()
+                Image(systemName: "xmark")
+              }
+            }
+            if case .failure(let failure) = result {
+              Text(failure)
+            }
+          } header: {
+            Text("Result")
+          }
+        }
+      }
+      .navigationTitle("Check connection")
+    }
+  }
+}
+
+#if DEBUG
+public struct CheckContactAuthView_Previews: PreviewProvider {
+  public static var previews: some View {
+    CheckContactAuthView(store: Store(
+      initialState: CheckContactAuthState(
+        contact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
+      reducer: .empty,
+      environment: ()
+    ))
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift
new file mode 100644
index 00000000..1c36e33e
--- /dev/null
+++ b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift
@@ -0,0 +1,110 @@
+import ComposableArchitecture
+import XCTest
+import XXClient
+@testable import CheckContactAuthFeature
+
+final class CheckContactAuthFeatureTests: XCTestCase {
+  func testCheck() {
+    var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+    let contactId = "contact-id".data(using: .utf8)!
+    contact.getIdFromContact.run = { _ in contactId }
+
+    let store = TestStore(
+      initialState: CheckContactAuthState(
+        contact: contact
+      ),
+      reducer: checkContactAuthReducer,
+      environment: .unimplemented
+    )
+
+    var didCheckPartnerId: [Data] = []
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.hasAuthenticatedChannel.run = { partnerId in
+        didCheckPartnerId.append(partnerId)
+        return true
+      }
+      return e2e
+    }
+
+    store.send(.checkTapped) {
+      $0.isChecking = true
+      $0.result = nil
+    }
+
+    store.receive(.didCheck(.success(true))) {
+      $0.isChecking = false
+      $0.result = .success(true)
+    }
+  }
+
+  func testCheckNoConnection() {
+    var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+    let contactId = "contact-id".data(using: .utf8)!
+    contact.getIdFromContact.run = { _ in contactId }
+
+    let store = TestStore(
+      initialState: CheckContactAuthState(
+        contact: contact
+      ),
+      reducer: checkContactAuthReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.hasAuthenticatedChannel.run = { _ in false }
+      return e2e
+    }
+
+    store.send(.checkTapped) {
+      $0.isChecking = true
+      $0.result = nil
+    }
+
+    store.receive(.didCheck(.success(false))) {
+      $0.isChecking = false
+      $0.result = .success(false)
+    }
+  }
+
+  func testCheckFailure() {
+    var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+    let contactId = "contact-id".data(using: .utf8)!
+    contact.getIdFromContact.run = { _ in contactId }
+
+    let store = TestStore(
+      initialState: CheckContactAuthState(
+        contact: contact
+      ),
+      reducer: checkContactAuthReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.hasAuthenticatedChannel.run = { _ in throw error }
+      return e2e
+    }
+
+    store.send(.checkTapped) {
+      $0.isChecking = true
+      $0.result = nil
+    }
+
+    store.receive(.didCheck(.failure(error.localizedDescription))) {
+      $0.isChecking = false
+      $0.result = .failure(error.localizedDescription)
+    }
+  }
+}
-- 
GitLab


From 5152e13d66b4dd4c91b98b657f8e3ff9b634230e Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 12 Sep 2022 00:17:08 +0200
Subject: [PATCH 05/12] Present CheckContactAuth from Contact

---
 Examples/xx-messenger/Package.swift           |  2 +
 .../AppFeature/AppEnvironment+Live.swift      |  8 ++++
 .../ContactFeature/ContactFeature.swift       | 38 ++++++++++++++++--
 .../Sources/ContactFeature/ContactView.swift  | 18 +++++++++
 .../ContactFeatureTests.swift                 | 39 +++++++++++++++++++
 5 files changed, 101 insertions(+), 4 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 01878fbd..c2da9978 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -75,6 +75,7 @@ let package = Package(
       name: "AppFeature",
       dependencies: [
         .target(name: "AppCore"),
+        .target(name: "CheckContactAuthFeature"),
         .target(name: "ContactFeature"),
         .target(name: "ContactsFeature"),
         .target(name: "HomeFeature"),
@@ -117,6 +118,7 @@ let package = Package(
       name: "ContactFeature",
       dependencies: [
         .target(name: "AppCore"),
+        .target(name: "CheckContactAuthFeature"),
         .target(name: "SendRequestFeature"),
         .target(name: "VerifyContactFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index ad921825..ae1da8b9 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -1,4 +1,5 @@
 import AppCore
+import CheckContactAuthFeature
 import ContactFeature
 import ContactsFeature
 import Foundation
@@ -49,6 +50,13 @@ extension AppEnvironment {
           mainQueue: mainQueue,
           bgQueue: bgQueue
         )
+      },
+      checkAuth: {
+        CheckContactAuthEnvironment(
+          messenger: messenger,
+          mainQueue: mainQueue,
+          bgQueue: bgQueue
+        )
       }
     )
 
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
index 15f32bed..8bee1b75 100644
--- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
@@ -1,4 +1,5 @@
 import AppCore
+import CheckContactAuthFeature
 import ComposableArchitecture
 import ComposablePresentation
 import Foundation
@@ -18,7 +19,8 @@ public struct ContactState: Equatable {
     importEmail: Bool = true,
     importPhone: Bool = true,
     sendRequest: SendRequestState? = nil,
-    verifyContact: VerifyContactState? = nil
+    verifyContact: VerifyContactState? = nil,
+    checkAuth: CheckContactAuthState? = nil
   ) {
     self.id = id
     self.dbContact = dbContact
@@ -28,6 +30,7 @@ public struct ContactState: Equatable {
     self.importPhone = importPhone
     self.sendRequest = sendRequest
     self.verifyContact = verifyContact
+    self.checkAuth = checkAuth
   }
 
   public var id: Data
@@ -38,6 +41,7 @@ public struct ContactState: Equatable {
   @BindableState public var importPhone: Bool
   public var sendRequest: SendRequestState?
   public var verifyContact: VerifyContactState?
+  public var checkAuth: CheckContactAuthState?
 }
 
 public enum ContactAction: Equatable, BindableAction {
@@ -50,6 +54,9 @@ public enum ContactAction: Equatable, BindableAction {
   case verifyContactTapped
   case verifyContactDismissed
   case verifyContact(VerifyContactAction)
+  case checkAuthTapped
+  case checkAuthDismissed
+  case checkAuth(CheckContactAuthAction)
   case binding(BindingAction<ContactState>)
 }
 
@@ -60,7 +67,8 @@ public struct ContactEnvironment {
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
     sendRequest: @escaping () -> SendRequestEnvironment,
-    verifyContact: @escaping () -> VerifyContactEnvironment
+    verifyContact: @escaping () -> VerifyContactEnvironment,
+    checkAuth: @escaping () -> CheckContactAuthEnvironment
   ) {
     self.messenger = messenger
     self.db = db
@@ -68,6 +76,7 @@ public struct ContactEnvironment {
     self.bgQueue = bgQueue
     self.sendRequest = sendRequest
     self.verifyContact = verifyContact
+    self.checkAuth = checkAuth
   }
 
   public var messenger: Messenger
@@ -76,6 +85,7 @@ public struct ContactEnvironment {
   public var bgQueue: AnySchedulerOf<DispatchQueue>
   public var sendRequest: () -> SendRequestEnvironment
   public var verifyContact: () -> VerifyContactEnvironment
+  public var checkAuth: () -> CheckContactAuthEnvironment
 }
 
 #if DEBUG
@@ -86,7 +96,8 @@ extension ContactEnvironment {
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
     sendRequest: { .unimplemented },
-    verifyContact: { .unimplemented }
+    verifyContact: { .unimplemented },
+    checkAuth: { .unimplemented }
   )
 }
 #endif
@@ -158,7 +169,19 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
     state.verifyContact = nil
     return .none
 
-  case .binding(_), .sendRequest(_), .verifyContact(_):
+  case .checkAuthTapped:
+    if let marshaled = state.dbContact?.marshaled {
+      state.checkAuth = CheckContactAuthState(
+        contact: .live(marshaled)
+      )
+    }
+    return .none
+
+  case .checkAuthDismissed:
+    state.checkAuth = nil
+    return .none
+
+  case .binding(_), .sendRequest(_), .verifyContact(_), .checkAuth(_):
     return .none
   }
 }
@@ -177,3 +200,10 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
   action: /ContactAction.verifyContact,
   environment: { $0.verifyContact() }
 )
+.presenting(
+  checkContactAuthReducer,
+  state: .keyPath(\.checkAuth),
+  id: .notNil(),
+  action: /ContactAction.checkAuth,
+  environment: { $0.checkAuth() }
+)
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
index 86202f15..07942841 100644
--- a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
@@ -1,4 +1,5 @@
 import AppCore
+import CheckContactAuthFeature
 import ComposableArchitecture
 import ComposablePresentation
 import SendRequestFeature
@@ -124,6 +125,15 @@ public struct ContactView: View {
                 Image(systemName: "chevron.forward")
               }
             }
+            Button {
+              viewStore.send(.checkAuthTapped)
+            } label: {
+              HStack {
+                Text("Check authorization")
+                Spacer()
+                Image(systemName: "chevron.forward")
+              }
+            }
           } header: {
             Text("Auth")
           }
@@ -149,6 +159,14 @@ public struct ContactView: View {
         onDeactivate: { viewStore.send(.verifyContactDismissed) },
         destination: VerifyContactView.init(store:)
       ))
+      .background(NavigationLinkWithStore(
+        store.scope(
+          state: \.checkAuth,
+          action: ContactAction.checkAuth
+        ),
+        onDeactivate: { viewStore.send(.checkAuthDismissed) },
+        destination: CheckContactAuthView.init(store:)
+      ))
     }
   }
 }
diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
index 9a24cab9..250d802c 100644
--- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
@@ -1,3 +1,4 @@
+import CheckContactAuthFeature
 import Combine
 import ComposableArchitecture
 import CustomDump
@@ -202,4 +203,42 @@ final class ContactFeatureTests: XCTestCase {
       $0.verifyContact = nil
     }
   }
+
+  func testCheckAuthTapped() {
+    let contactData = "contact-data".data(using: .utf8)!
+    let store = TestStore(
+      initialState: ContactState(
+        id: Data(),
+        dbContact: XXModels.Contact(
+          id: Data(),
+          marshaled: contactData
+        )
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.checkAuthTapped) {
+      $0.checkAuth = CheckContactAuthState(
+        contact: .unimplemented(contactData)
+      )
+    }
+  }
+
+  func testCheckAuthDismissed() {
+    let store = TestStore(
+      initialState: ContactState(
+        id: "contact-id".data(using: .utf8)!,
+        checkAuth: CheckContactAuthState(
+          contact: .unimplemented("contact-data".data(using: .utf8)!)
+        )
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.checkAuthDismissed) {
+      $0.checkAuth = nil
+    }
+  }
 }
-- 
GitLab


From d7a739086cfbf2dfd260474907f566e854b3ce6a Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 12 Sep 2022 00:34:07 +0200
Subject: [PATCH 06/12] Update db contact after checking auth status

---
 Examples/xx-messenger/Package.swift           |  1 +
 .../AppFeature/AppEnvironment+Live.swift      |  1 +
 .../CheckContactAuthFeature.swift             | 14 +++++-
 .../CheckContactAuthFeatureTests.swift        | 45 +++++++++++++++++--
 4 files changed, 55 insertions(+), 6 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index c2da9978..9c44e441 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -102,6 +102,7 @@ let package = Package(
     .target(
       name: "CheckContactAuthFeature",
       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"),
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index ae1da8b9..fa3774e8 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -54,6 +54,7 @@ extension AppEnvironment {
       checkAuth: {
         CheckContactAuthEnvironment(
           messenger: messenger,
+          db: dbManager.getDB,
           mainQueue: mainQueue,
           bgQueue: bgQueue
         )
diff --git a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift
index 119ff27a..1f768be8 100644
--- a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift
+++ b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift
@@ -1,8 +1,10 @@
+import AppCore
 import ComposableArchitecture
 import Foundation
 import XCTestDynamicOverlay
 import XXClient
 import XXMessengerClient
+import XXModels
 
 public struct CheckContactAuthState: Equatable {
   public enum Result: Equatable {
@@ -11,7 +13,7 @@ public struct CheckContactAuthState: Equatable {
   }
 
   public init(
-    contact: Contact,
+    contact: XXClient.Contact,
     isChecking: Bool = false,
     result: Result? = nil
   ) {
@@ -20,7 +22,7 @@ public struct CheckContactAuthState: Equatable {
     self.result = result
   }
 
-  public var contact: Contact
+  public var contact: XXClient.Contact
   public var isChecking: Bool
   public var result: Result?
 }
@@ -33,15 +35,18 @@ public enum CheckContactAuthAction: Equatable {
 public struct CheckContactAuthEnvironment {
   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>
 }
@@ -50,6 +55,7 @@ public struct CheckContactAuthEnvironment {
 extension CheckContactAuthEnvironment {
   public static let unimplemented = CheckContactAuthEnvironment(
     messenger: .unimplemented,
+    db: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented
   )
@@ -67,6 +73,10 @@ public let checkContactAuthReducer = Reducer<CheckContactAuthState, CheckContact
         let e2e = try env.messenger.e2e.tryGet()
         let contactId = try state.contact.getId()
         let result = try e2e.hasAuthenticatedChannel(partnerId: contactId)
+        try env.db().bulkUpdateContacts.callAsFunction(
+          .init(id: [contactId]),
+          .init(authStatus: result ? .friend : .stranger)
+        )
         return .success(.didCheck(.success(result)))
       } catch {
         return .success(.didCheck(.failure(error.localizedDescription)))
diff --git a/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift
index 1c36e33e..95f5a807 100644
--- a/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift
@@ -1,11 +1,13 @@
 import ComposableArchitecture
+import CustomDump
 import XCTest
 import XXClient
+import XXModels
 @testable import CheckContactAuthFeature
 
 final class CheckContactAuthFeatureTests: XCTestCase {
   func testCheck() {
-    var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+    var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
     let contactId = "contact-id".data(using: .utf8)!
     contact.getIdFromContact.run = { _ in contactId }
 
@@ -18,6 +20,8 @@ final class CheckContactAuthFeatureTests: XCTestCase {
     )
 
     var didCheckPartnerId: [Data] = []
+    var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = []
+    var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = []
 
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
@@ -29,12 +33,25 @@ final class CheckContactAuthFeatureTests: XCTestCase {
       }
       return e2e
     }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.bulkUpdateContacts.run = { query, assignments in
+        didBulkUpdateContactsWithQuery.append(query)
+        didBulkUpdateContactsWithAssignments.append(assignments)
+        return 0
+      }
+      return db
+    }
 
     store.send(.checkTapped) {
       $0.isChecking = true
       $0.result = nil
     }
 
+    XCTAssertNoDifference(didCheckPartnerId, [contactId])
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .friend)])
+
     store.receive(.didCheck(.success(true))) {
       $0.isChecking = false
       $0.result = .success(true)
@@ -42,7 +59,7 @@ final class CheckContactAuthFeatureTests: XCTestCase {
   }
 
   func testCheckNoConnection() {
-    var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+    var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
     let contactId = "contact-id".data(using: .utf8)!
     contact.getIdFromContact.run = { _ in contactId }
 
@@ -54,19 +71,39 @@ final class CheckContactAuthFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
+    var didCheckPartnerId: [Data] = []
+    var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = []
+    var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = []
+
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
     store.environment.messenger.e2e.get = {
       var e2e: E2E = .unimplemented
-      e2e.hasAuthenticatedChannel.run = { _ in false }
+      e2e.hasAuthenticatedChannel.run = { partnerId in
+        didCheckPartnerId.append(partnerId)
+        return false
+      }
       return e2e
     }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.bulkUpdateContacts.run = { query, assignments in
+        didBulkUpdateContactsWithQuery.append(query)
+        didBulkUpdateContactsWithAssignments.append(assignments)
+        return 0
+      }
+      return db
+    }
 
     store.send(.checkTapped) {
       $0.isChecking = true
       $0.result = nil
     }
 
+    XCTAssertNoDifference(didCheckPartnerId, [contactId])
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .stranger)])
+
     store.receive(.didCheck(.success(false))) {
       $0.isChecking = false
       $0.result = .success(false)
@@ -74,7 +111,7 @@ final class CheckContactAuthFeatureTests: XCTestCase {
   }
 
   func testCheckFailure() {
-    var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+    var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
     let contactId = "contact-id".data(using: .utf8)!
     contact.getIdFromContact.run = { _ in contactId }
 
-- 
GitLab


From 71cc4baaf89e8f1070e97da33f14a25b6b506ae9 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 12 Sep 2022 00:42:32 +0200
Subject: [PATCH 07/12] Update db contact after verification

---
 Examples/xx-messenger/Package.swift           |  2 +
 .../AppFeature/AppEnvironment+Live.swift      |  1 +
 .../VerifyContactFeature.swift                | 15 +++++-
 .../VerifyContactFeatureTests.swift           | 53 +++++++++++++++++--
 4 files changed, 65 insertions(+), 6 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 9c44e441..2736c69c 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -248,9 +248,11 @@ let package = Package(
     .target(
       name: "VerifyContactFeature",
       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"),
       ]
     ),
     .testTarget(
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index fa3774e8..906e9992 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -47,6 +47,7 @@ extension AppEnvironment {
       verifyContact: {
         VerifyContactEnvironment(
           messenger: messenger,
+          db: dbManager.getDB,
           mainQueue: mainQueue,
           bgQueue: bgQueue
         )
diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
index 4a38f3ad..8a69a072 100644
--- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
+++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
@@ -1,8 +1,10 @@
+import AppCore
 import ComposableArchitecture
 import Foundation
 import XCTestDynamicOverlay
 import XXClient
 import XXMessengerClient
+import XXModels
 
 public struct VerifyContactState: Equatable {
   public enum Result: Equatable {
@@ -11,7 +13,7 @@ public struct VerifyContactState: Equatable {
   }
 
   public init(
-    contact: Contact,
+    contact: XXClient.Contact,
     isVerifying: Bool = false,
     result: Result? = nil
   ) {
@@ -20,7 +22,7 @@ public struct VerifyContactState: Equatable {
     self.result = result
   }
 
-  public var contact: Contact
+  public var contact: XXClient.Contact
   public var isVerifying: Bool
   public var result: Result?
 }
@@ -33,15 +35,18 @@ public enum VerifyContactAction: Equatable {
 public struct VerifyContactEnvironment {
   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>
 }
@@ -50,6 +55,7 @@ public struct VerifyContactEnvironment {
 extension VerifyContactEnvironment {
   public static let unimplemented = VerifyContactEnvironment(
     messenger: .unimplemented,
+    db: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented
   )
@@ -65,6 +71,11 @@ public let verifyContactReducer = Reducer<VerifyContactState, VerifyContactActio
     return Effect.result { [state] in
       do {
         let result = try env.messenger.verifyContact(state.contact)
+        let contactId = try state.contact.getId()
+        try env.db().bulkUpdateContacts.callAsFunction(
+          .init(id: [contactId]),
+          .init(authStatus: result ? .verified : .verificationFailed)
+        )
         return .success(.didVerify(.success(result)))
       } catch {
         return .success(.didVerify(.failure(error.localizedDescription)))
diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
index 3721bc46..6ca82b1e 100644
--- a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
@@ -1,19 +1,27 @@
 import ComposableArchitecture
+import CustomDump
 import XCTest
 import XXClient
+import XXModels
 @testable import VerifyContactFeature
 
 final class VerifyContactFeatureTests: XCTestCase {
   func testVerify() {
+    var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
+    let contactId = "contact-id".data(using: .utf8)!
+    contact.getIdFromContact.run = { _ in contactId }
+
     let store = TestStore(
       initialState: VerifyContactState(
-        contact: .unimplemented("contact-data".data(using: .utf8)!)
+        contact: contact
       ),
       reducer: verifyContactReducer,
       environment: .unimplemented
     )
 
-    var didVerifyContact: [Contact] = []
+    var didVerifyContact: [XXClient.Contact] = []
+    var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = []
+    var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = []
 
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
@@ -21,12 +29,25 @@ final class VerifyContactFeatureTests: XCTestCase {
       didVerifyContact.append(contact)
       return true
     }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.bulkUpdateContacts.run = { query, assignments in
+        didBulkUpdateContactsWithQuery.append(query)
+        didBulkUpdateContactsWithAssignments.append(assignments)
+        return 0
+      }
+      return db
+    }
 
     store.send(.verifyTapped) {
       $0.isVerifying = true
       $0.result = nil
     }
 
+    XCTAssertNoDifference(didVerifyContact, [contact])
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .verified)])
+
     store.receive(.didVerify(.success(true))) {
       $0.isVerifying = false
       $0.result = .success(true)
@@ -34,23 +55,47 @@ final class VerifyContactFeatureTests: XCTestCase {
   }
 
   func testVerifyNotVerified() {
+    var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
+    let contactId = "contact-id".data(using: .utf8)!
+    contact.getIdFromContact.run = { _ in contactId }
+
     let store = TestStore(
       initialState: VerifyContactState(
-        contact: .unimplemented("contact-data".data(using: .utf8)!)
+        contact: contact
       ),
       reducer: verifyContactReducer,
       environment: .unimplemented
     )
 
+    var didVerifyContact: [XXClient.Contact] = []
+    var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = []
+    var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = []
+
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
-    store.environment.messenger.verifyContact.run = { _ in false }
+    store.environment.messenger.verifyContact.run = { contact in
+      didVerifyContact.append(contact)
+      return false
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.bulkUpdateContacts.run = { query, assignments in
+        didBulkUpdateContactsWithQuery.append(query)
+        didBulkUpdateContactsWithAssignments.append(assignments)
+        return 0
+      }
+      return db
+    }
 
     store.send(.verifyTapped) {
       $0.isVerifying = true
       $0.result = nil
     }
 
+    XCTAssertNoDifference(didVerifyContact, [contact])
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .verificationFailed)])
+
     store.receive(.didVerify(.success(false))) {
       $0.isVerifying = false
       $0.result = .success(false)
-- 
GitLab


From a13e6f066c71d55a1aaa570e3924a548acae6aef Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 12 Sep 2022 00:50:43 +0200
Subject: [PATCH 08/12] Do not verify contact when request received

---
 .../AuthCallbackHandlerRequest.swift          |  15 +-
 .../AppFeature/AppEnvironment+Live.swift      |   6 +-
 .../AuthCallbackHandlerRequestTests.swift     | 132 ++----------------
 3 files changed, 11 insertions(+), 142 deletions(-)

diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
index d8ac3d18..6d1943e9 100644
--- a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
@@ -15,7 +15,6 @@ public struct AuthCallbackHandlerRequest {
 extension AuthCallbackHandlerRequest {
   public static func live(
     db: DBManagerGetDB,
-    messenger: Messenger,
     now: @escaping () -> Date
   ) -> AuthCallbackHandlerRequest {
     AuthCallbackHandlerRequest { xxContact in
@@ -28,21 +27,9 @@ extension AuthCallbackHandlerRequest {
       dbContact.username = try xxContact.getFact(.username)?.value
       dbContact.email = try xxContact.getFact(.email)?.value
       dbContact.phone = try xxContact.getFact(.phone)?.value
-      dbContact.authStatus = .verificationInProgress
+      dbContact.authStatus = .stranger
       dbContact.createdAt = now()
       dbContact = try db().saveContact(dbContact)
-
-      do {
-        try messenger.waitForNetwork()
-        try messenger.waitForNodes()
-        let verified = try messenger.verifyContact(xxContact)
-        dbContact.authStatus = verified ? .verified : .verificationFailed
-        dbContact = try db().saveContact(dbContact)
-      } catch {
-        dbContact.authStatus = .verificationFailed
-        dbContact = try db().saveContact(dbContact)
-        throw error
-      }
     }
   }
 }
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 906e9992..4cbcef24 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -20,11 +20,7 @@ extension AppEnvironment {
     let messenger = Messenger.live(messengerEnv)
     let authHandler = AuthCallbackHandler.live(
       messenger: messenger,
-      handleRequest: .live(
-        db: dbManager.getDB,
-        messenger: messenger,
-        now: Date.init
-      ),
+      handleRequest: .live(db: dbManager.getDB, now: Date.init),
       handleConfirm: .live(db: dbManager.getDB),
       handleReset: .live(db: dbManager.getDB)
     )
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
index 65786884..3a7cb6fb 100644
--- a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
@@ -10,17 +10,8 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
   func testRequestFromNewContact() throws {
     let now = Date()
     var didFetchContacts: [XXModels.Contact.Query] = []
-    var didVerifyContact: [XXClient.Contact] = []
     var didSaveContact: [XXModels.Contact] = []
 
-    var messenger: Messenger = .unimplemented
-    messenger.waitForNetwork.run = { _ in }
-    messenger.waitForNodes.run = { _, _, _, _ in }
-    messenger.verifyContact.run = { contact in
-      didVerifyContact.append(contact)
-      return true
-    }
-
     let request = AuthCallbackHandlerRequest.live(
       db: .init {
         var db: Database = .failing
@@ -34,7 +25,6 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
         }
         return db
       },
-      messenger: messenger,
       now: { now }
     )
     var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
@@ -50,26 +40,15 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
     try request(xxContact)
 
     XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])])
-    XCTAssertNoDifference(didSaveContact, [
-      .init(
-        id: "id".data(using: .utf8)!,
-        marshaled: "contact".data(using: .utf8)!,
-        username: "username",
-        email: "email",
-        phone: "phone",
-        authStatus: .verificationInProgress,
-        createdAt: now
-      ),
-      .init(
-        id: "id".data(using: .utf8)!,
-        marshaled: "contact".data(using: .utf8)!,
-        username: "username",
-        email: "email",
-        phone: "phone",
-        authStatus: .verified,
-        createdAt: now
-      )
-    ])
+    XCTAssertNoDifference(didSaveContact, [.init(
+      id: "id".data(using: .utf8)!,
+      marshaled: "contact".data(using: .utf8)!,
+      username: "username",
+      email: "email",
+      phone: "phone",
+      authStatus: .stranger,
+      createdAt: now
+    )])
   }
 
   func testRequestWhenContactInDatabase() throws {
@@ -79,7 +58,6 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
         db.fetchContacts.run = { _ in [.init(id: "id".data(using: .utf8)!)] }
         return db
       },
-      messenger: .unimplemented,
       now: XCTUnimplemented("now", placeholder: Date())
     )
     var contact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
@@ -87,96 +65,4 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
 
     try request(contact)
   }
-
-  func testRequestFromNewContactVerificationFalse() throws {
-    let now = Date()
-    var didSaveContact: [XXModels.Contact] = []
-
-    var messenger: Messenger = .unimplemented
-    messenger.waitForNetwork.run = { _ in }
-    messenger.waitForNodes.run = { _, _, _, _ in }
-    messenger.verifyContact.run = { _ in false }
-
-    let request = AuthCallbackHandlerRequest.live(
-      db: .init {
-        var db: Database = .failing
-        db.fetchContacts.run = { query in return [] }
-        db.saveContact.run = { contact in
-          didSaveContact.append(contact)
-          return contact
-        }
-        return db
-      },
-      messenger: messenger,
-      now: { now }
-    )
-    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
-    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
-    xxContact.getFactsFromContact.run = { _ in [] }
-
-    try request(xxContact)
-
-    XCTAssertNoDifference(didSaveContact, [
-      .init(
-        id: "id".data(using: .utf8)!,
-        marshaled: "contact".data(using: .utf8)!,
-        authStatus: .verificationInProgress,
-        createdAt: now
-      ),
-      .init(
-        id: "id".data(using: .utf8)!,
-        marshaled: "contact".data(using: .utf8)!,
-        authStatus: .verificationFailed,
-        createdAt: now
-      )
-    ])
-  }
-
-  func testRequestFromNewContactVerificationFailure() throws {
-    struct Failure: Error, Equatable {}
-    let failure = Failure()
-    let now = Date()
-    var didSaveContact: [XXModels.Contact] = []
-
-    var messenger: Messenger = .unimplemented
-    messenger.waitForNetwork.run = { _ in }
-    messenger.waitForNodes.run = { _, _, _, _ in }
-    messenger.verifyContact.run = { _ in throw failure }
-
-    let request = AuthCallbackHandlerRequest.live(
-      db: .init {
-        var db: Database = .failing
-        db.fetchContacts.run = { query in return [] }
-        db.saveContact.run = { contact in
-          didSaveContact.append(contact)
-          return contact
-        }
-        return db
-      },
-      messenger: messenger,
-      now: { now }
-    )
-    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
-    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
-    xxContact.getFactsFromContact.run = { _ in [] }
-
-    XCTAssertThrowsError(try request(xxContact)) { error in
-      XCTAssertNoDifference(error as? Failure, failure)
-    }
-
-    XCTAssertNoDifference(didSaveContact, [
-      .init(
-        id: "id".data(using: .utf8)!,
-        marshaled: "contact".data(using: .utf8)!,
-        authStatus: .verificationInProgress,
-        createdAt: now
-      ),
-      .init(
-        id: "id".data(using: .utf8)!,
-        marshaled: "contact".data(using: .utf8)!,
-        authStatus: .verificationFailed,
-        createdAt: now
-      )
-    ])
-  }
 }
-- 
GitLab


From c36c1d81eadf36934e4cabfbbd6b9174a4f6e93a Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 12 Sep 2022 01:10:28 +0200
Subject: [PATCH 09/12] Add ConfirmRequestFeature library

---
 .../xcschemes/ConfirmRequestFeature.xcscheme  | 78 +++++++++++++++
 Examples/xx-messenger/Package.swift           | 17 ++++
 .../xcschemes/XXMessenger.xcscheme            | 10 ++
 .../ConfirmRequestFeature.swift               | 94 ++++++++++++++++++
 .../ConfirmRequestView.swift                  | 95 +++++++++++++++++++
 .../ConfirmRequestFeatureTests.swift          | 95 +++++++++++++++++++
 6 files changed, 389 insertions(+)
 create mode 100644 Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme
 create mode 100644 Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift
 create mode 100644 Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift
 create mode 100644 Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift

diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme
new file mode 100644
index 00000000..a3026c70
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.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 = "ConfirmRequestFeature"
+               BuildableName = "ConfirmRequestFeature"
+               BlueprintName = "ConfirmRequestFeature"
+               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 = "ConfirmRequestFeatureTests"
+               BuildableName = "ConfirmRequestFeatureTests"
+               BlueprintName = "ConfirmRequestFeatureTests"
+               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 = "ConfirmRequestFeature"
+            BuildableName = "ConfirmRequestFeature"
+            BlueprintName = "ConfirmRequestFeature"
+            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 2736c69c..716b1482 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -21,6 +21,7 @@ let package = Package(
     .library(name: "AppCore", targets: ["AppCore"]),
     .library(name: "AppFeature", targets: ["AppFeature"]),
     .library(name: "CheckContactAuthFeature", targets: ["CheckContactAuthFeature"]),
+    .library(name: "ConfirmRequestFeature", targets: ["ConfirmRequestFeature"]),
     .library(name: "ContactFeature", targets: ["ContactFeature"]),
     .library(name: "ContactsFeature", targets: ["ContactsFeature"]),
     .library(name: "HomeFeature", targets: ["HomeFeature"]),
@@ -115,6 +116,22 @@ let package = Package(
         .target(name: "CheckContactAuthFeature"),
       ]
     ),
+    .target(
+      name: "ConfirmRequestFeature",
+      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"),
+      ]
+    ),
+    .testTarget(
+      name: "ConfirmRequestFeatureTests",
+      dependencies: [
+        .target(name: "ConfirmRequestFeature"),
+      ]
+    ),
     .target(
       name: "ContactFeature",
       dependencies: [
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 85d23241..0e5e54ad 100644
--- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
+++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
@@ -59,6 +59,16 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "ConfirmRequestFeatureTests"
+               BuildableName = "ConfirmRequestFeatureTests"
+               BlueprintName = "ConfirmRequestFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
diff --git a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift
new file mode 100644
index 00000000..b18f9da9
--- /dev/null
+++ b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift
@@ -0,0 +1,94 @@
+import AppCore
+import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+
+public struct ConfirmRequestState: Equatable {
+  public enum Result: Equatable {
+    case success
+    case failure(String)
+  }
+
+  public init(
+    contact: XXClient.Contact,
+    isConfirming: Bool = false,
+    result: Result? = nil
+  ) {
+    self.contact = contact
+    self.isConfirming = isConfirming
+    self.result = result
+  }
+
+  public var contact: XXClient.Contact
+  public var isConfirming: Bool
+  public var result: Result?
+}
+
+public enum ConfirmRequestAction: Equatable {
+  case confirmTapped
+  case didConfirm(ConfirmRequestState.Result)
+}
+
+public struct ConfirmRequestEnvironment {
+  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 ConfirmRequestEnvironment {
+  public static let unimplemented = ConfirmRequestEnvironment(
+    messenger: .unimplemented,
+    db: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
+}
+#endif
+
+public let confirmRequestReducer = Reducer<ConfirmRequestState, ConfirmRequestAction, ConfirmRequestEnvironment>
+{ state, action, env in
+  switch action {
+  case .confirmTapped:
+    state.isConfirming = true
+    state.result = nil
+    return Effect.result { [state] in
+      do {
+        let e2e = try env.messenger.e2e.tryGet()
+        _ = try e2e.confirmReceivedRequest(partner: state.contact)
+        let contactId = try state.contact.getId()
+        try env.db().bulkUpdateContacts.callAsFunction(
+          .init(id: [contactId]),
+          .init(authStatus: .friend)
+        )
+        return .success(.didConfirm(.success))
+      } catch {
+        return .success(.didConfirm(.failure(error.localizedDescription)))
+      }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .didConfirm(let result):
+    state.isConfirming = false
+    state.result = result
+    return .none
+  }
+}
diff --git a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift
new file mode 100644
index 00000000..90ebc70e
--- /dev/null
+++ b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift
@@ -0,0 +1,95 @@
+import ComposableArchitecture
+import SwiftUI
+
+public struct ConfirmRequestView: View {
+  public init(store: Store<ConfirmRequestState, ConfirmRequestAction>) {
+    self.store = store
+  }
+
+  let store: Store<ConfirmRequestState, ConfirmRequestAction>
+
+  struct ViewState: Equatable {
+    var username: String?
+    var email: String?
+    var phone: String?
+    var isConfirming: Bool
+    var result: ConfirmRequestState.Result?
+
+    init(state: ConfirmRequestState) {
+      username = try? state.contact.getFact(.username)?.value
+      email = try? state.contact.getFact(.email)?.value
+      phone = try? state.contact.getFact(.phone)?.value
+      isConfirming = state.isConfirming
+      result = state.result
+    }
+  }
+
+  public var body: some View {
+    WithViewStore(store, observe: ViewState.init) { viewStore in
+      Form {
+        Section {
+          Label(viewStore.username ?? "", systemImage: "person")
+          Label(viewStore.email ?? "", systemImage: "envelope")
+          Label(viewStore.phone ?? "", systemImage: "phone")
+        } header: {
+          Text("Facts")
+        }
+
+        Section {
+          Button {
+            viewStore.send(.confirmTapped)
+          } label: {
+            HStack {
+              Text("Confirm")
+              Spacer()
+              if viewStore.isConfirming {
+                ProgressView()
+              } else {
+                Image(systemName: "checkmark")
+              }
+            }
+          }
+          .disabled(viewStore.isConfirming)
+        }
+
+        if let result = viewStore.result {
+          Section {
+            HStack {
+              switch result {
+              case .success:
+                Text("Request confirmed")
+                Spacer()
+                Image(systemName: "person.fill.checkmark")
+
+              case .failure(_):
+                Text("Confirming request failed")
+                Spacer()
+                Image(systemName: "xmark")
+              }
+            }
+            if case .failure(let failure) = result {
+              Text(failure)
+            }
+          } header: {
+            Text("Result")
+          }
+        }
+      }
+      .navigationTitle("Confirm request")
+    }
+  }
+}
+
+#if DEBUG
+public struct ConfirmRequestView_Previews: PreviewProvider {
+  public static var previews: some View {
+    ConfirmRequestView(store: Store(
+      initialState: ConfirmRequestState(
+        contact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
+      reducer: .empty,
+      environment: ()
+    ))
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift
new file mode 100644
index 00000000..2686d645
--- /dev/null
+++ b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift
@@ -0,0 +1,95 @@
+import ComposableArchitecture
+import CustomDump
+import XCTest
+import XXClient
+import XXModels
+@testable import ConfirmRequestFeature
+
+final class ConfirmRequestFeatureTests: XCTestCase {
+  func testConfirm() {
+    var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
+    let contactId = "contact-id".data(using: .utf8)!
+    contact.getIdFromContact.run = { _ in contactId }
+
+    let store = TestStore(
+      initialState: ConfirmRequestState(
+        contact: contact
+      ),
+      reducer: confirmRequestReducer,
+      environment: .unimplemented
+    )
+
+    var didConfirmRequestFromContact: [XXClient.Contact] = []
+    var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = []
+    var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = []
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.confirmReceivedRequest.run = { contact in
+        didConfirmRequestFromContact.append(contact)
+        return 0
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.bulkUpdateContacts.run = { query, assignments in
+        didBulkUpdateContactsWithQuery.append(query)
+        didBulkUpdateContactsWithAssignments.append(assignments)
+        return 0
+      }
+      return db
+    }
+
+    store.send(.confirmTapped) {
+      $0.isConfirming = true
+      $0.result = nil
+    }
+
+    XCTAssertNoDifference(didConfirmRequestFromContact, [contact])
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .friend)])
+
+    store.receive(.didConfirm(.success)) {
+      $0.isConfirming = false
+      $0.result = .success
+    }
+  }
+
+  func testConfirmFailure() {
+    var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
+    let contactId = "contact-id".data(using: .utf8)!
+    contact.getIdFromContact.run = { _ in contactId }
+
+    let store = TestStore(
+      initialState: ConfirmRequestState(
+        contact: contact
+      ),
+      reducer: confirmRequestReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.confirmReceivedRequest.run = { _ in throw error }
+      return e2e
+    }
+
+    store.send(.confirmTapped) {
+      $0.isConfirming = true
+      $0.result = nil
+    }
+
+    store.receive(.didConfirm(.failure(error.localizedDescription))) {
+      $0.isConfirming = false
+      $0.result = .failure(error.localizedDescription)
+    }
+  }
+}
-- 
GitLab


From 6b5720f017b91d930a074989e97440c85ea83a3a Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 12 Sep 2022 01:20:25 +0200
Subject: [PATCH 10/12] Present ConfirmRequest from Contact

---
 Examples/xx-messenger/Package.swift           |  2 +
 .../AppFeature/AppEnvironment+Live.swift      |  9 +++++
 .../ContactFeature/ContactFeature.swift       | 32 ++++++++++++++-
 .../Sources/ContactFeature/ContactView.swift  | 18 +++++++++
 .../ContactFeatureTests.swift                 | 39 +++++++++++++++++++
 5 files changed, 99 insertions(+), 1 deletion(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 716b1482..5e9a48c3 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -77,6 +77,7 @@ let package = Package(
       dependencies: [
         .target(name: "AppCore"),
         .target(name: "CheckContactAuthFeature"),
+        .target(name: "ConfirmRequestFeature"),
         .target(name: "ContactFeature"),
         .target(name: "ContactsFeature"),
         .target(name: "HomeFeature"),
@@ -137,6 +138,7 @@ let package = Package(
       dependencies: [
         .target(name: "AppCore"),
         .target(name: "CheckContactAuthFeature"),
+        .target(name: "ConfirmRequestFeature"),
         .target(name: "SendRequestFeature"),
         .target(name: "VerifyContactFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 4cbcef24..7adbcae8 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -1,5 +1,6 @@
 import AppCore
 import CheckContactAuthFeature
+import ConfirmRequestFeature
 import ContactFeature
 import ContactsFeature
 import Foundation
@@ -48,6 +49,14 @@ extension AppEnvironment {
           bgQueue: bgQueue
         )
       },
+      confirmRequest: {
+        ConfirmRequestEnvironment(
+          messenger: messenger,
+          db: dbManager.getDB,
+          mainQueue: mainQueue,
+          bgQueue: bgQueue
+        )
+      },
       checkAuth: {
         CheckContactAuthEnvironment(
           messenger: messenger,
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
index 8bee1b75..545cb8fb 100644
--- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
@@ -2,6 +2,7 @@ import AppCore
 import CheckContactAuthFeature
 import ComposableArchitecture
 import ComposablePresentation
+import ConfirmRequestFeature
 import Foundation
 import SendRequestFeature
 import VerifyContactFeature
@@ -20,6 +21,7 @@ public struct ContactState: Equatable {
     importPhone: Bool = true,
     sendRequest: SendRequestState? = nil,
     verifyContact: VerifyContactState? = nil,
+    confirmRequest: ConfirmRequestState? = nil,
     checkAuth: CheckContactAuthState? = nil
   ) {
     self.id = id
@@ -30,6 +32,7 @@ public struct ContactState: Equatable {
     self.importPhone = importPhone
     self.sendRequest = sendRequest
     self.verifyContact = verifyContact
+    self.confirmRequest = confirmRequest
     self.checkAuth = checkAuth
   }
 
@@ -41,6 +44,7 @@ public struct ContactState: Equatable {
   @BindableState public var importPhone: Bool
   public var sendRequest: SendRequestState?
   public var verifyContact: VerifyContactState?
+  public var confirmRequest: ConfirmRequestState?
   public var checkAuth: CheckContactAuthState?
 }
 
@@ -57,6 +61,9 @@ public enum ContactAction: Equatable, BindableAction {
   case checkAuthTapped
   case checkAuthDismissed
   case checkAuth(CheckContactAuthAction)
+  case confirmRequestTapped
+  case confirmRequestDismissed
+  case confirmRequest(ConfirmRequestAction)
   case binding(BindingAction<ContactState>)
 }
 
@@ -68,6 +75,7 @@ public struct ContactEnvironment {
     bgQueue: AnySchedulerOf<DispatchQueue>,
     sendRequest: @escaping () -> SendRequestEnvironment,
     verifyContact: @escaping () -> VerifyContactEnvironment,
+    confirmRequest: @escaping () -> ConfirmRequestEnvironment,
     checkAuth: @escaping () -> CheckContactAuthEnvironment
   ) {
     self.messenger = messenger
@@ -76,6 +84,7 @@ public struct ContactEnvironment {
     self.bgQueue = bgQueue
     self.sendRequest = sendRequest
     self.verifyContact = verifyContact
+    self.confirmRequest = confirmRequest
     self.checkAuth = checkAuth
   }
 
@@ -85,6 +94,7 @@ public struct ContactEnvironment {
   public var bgQueue: AnySchedulerOf<DispatchQueue>
   public var sendRequest: () -> SendRequestEnvironment
   public var verifyContact: () -> VerifyContactEnvironment
+  public var confirmRequest: () -> ConfirmRequestEnvironment
   public var checkAuth: () -> CheckContactAuthEnvironment
 }
 
@@ -97,6 +107,7 @@ extension ContactEnvironment {
     bgQueue: .unimplemented,
     sendRequest: { .unimplemented },
     verifyContact: { .unimplemented },
+    confirmRequest: { .unimplemented },
     checkAuth: { .unimplemented }
   )
 }
@@ -181,7 +192,19 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
     state.checkAuth = nil
     return .none
 
-  case .binding(_), .sendRequest(_), .verifyContact(_), .checkAuth(_):
+  case .confirmRequestTapped:
+    if let marshaled = state.dbContact?.marshaled {
+      state.confirmRequest = ConfirmRequestState(
+        contact: .live(marshaled)
+      )
+    }
+    return .none
+
+  case .confirmRequestDismissed:
+    state.confirmRequest = nil
+    return .none
+
+  case .binding(_), .sendRequest(_), .verifyContact(_), .confirmRequest(_), .checkAuth(_):
     return .none
   }
 }
@@ -200,6 +223,13 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
   action: /ContactAction.verifyContact,
   environment: { $0.verifyContact() }
 )
+.presenting(
+  confirmRequestReducer,
+  state: .keyPath(\.confirmRequest),
+  id: .notNil(),
+  action: /ContactAction.confirmRequest,
+  environment: { $0.confirmRequest() }
+)
 .presenting(
   checkContactAuthReducer,
   state: .keyPath(\.checkAuth),
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
index 07942841..08ae7eb8 100644
--- a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
@@ -2,6 +2,7 @@ import AppCore
 import CheckContactAuthFeature
 import ComposableArchitecture
 import ComposablePresentation
+import ConfirmRequestFeature
 import SendRequestFeature
 import SwiftUI
 import VerifyContactFeature
@@ -125,6 +126,15 @@ public struct ContactView: View {
                 Image(systemName: "chevron.forward")
               }
             }
+            Button {
+              viewStore.send(.confirmRequestTapped)
+            } label: {
+              HStack {
+                Text("Confirm request")
+                Spacer()
+                Image(systemName: "chevron.forward")
+              }
+            }
             Button {
               viewStore.send(.checkAuthTapped)
             } label: {
@@ -159,6 +169,14 @@ public struct ContactView: View {
         onDeactivate: { viewStore.send(.verifyContactDismissed) },
         destination: VerifyContactView.init(store:)
       ))
+      .background(NavigationLinkWithStore(
+        store.scope(
+          state: \.confirmRequest,
+          action: ContactAction.confirmRequest
+        ),
+        onDeactivate: { viewStore.send(.confirmRequestDismissed) },
+        destination: ConfirmRequestView.init(store:)
+      ))
       .background(NavigationLinkWithStore(
         store.scope(
           state: \.checkAuth,
diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
index 250d802c..afc146b1 100644
--- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
@@ -1,6 +1,7 @@
 import CheckContactAuthFeature
 import Combine
 import ComposableArchitecture
+import ConfirmRequestFeature
 import CustomDump
 import SendRequestFeature
 import VerifyContactFeature
@@ -241,4 +242,42 @@ final class ContactFeatureTests: XCTestCase {
       $0.checkAuth = nil
     }
   }
+
+  func testConfirmRequestTapped() {
+    let contactData = "contact-data".data(using: .utf8)!
+    let store = TestStore(
+      initialState: ContactState(
+        id: Data(),
+        dbContact: XXModels.Contact(
+          id: Data(),
+          marshaled: contactData
+        )
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.confirmRequestTapped) {
+      $0.confirmRequest = ConfirmRequestState(
+        contact: .unimplemented(contactData)
+      )
+    }
+  }
+
+  func testConfirmRequestDismissed() {
+    let store = TestStore(
+      initialState: ContactState(
+        id: "contact-id".data(using: .utf8)!,
+        confirmRequest: ConfirmRequestState(
+          contact: .unimplemented("contact-data".data(using: .utf8)!)
+        )
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.confirmRequestDismissed) {
+      $0.confirmRequest = nil
+    }
+  }
 }
-- 
GitLab


From 64847678745b17b433ae399172cb15dd15d0ff14 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 12 Sep 2022 12:58:16 +0200
Subject: [PATCH 11/12] Update auth status when verifying contact

---
 .../VerifyContactFeature.swift                | 14 ++++--
 .../VerifyContactFeatureTests.swift           | 47 +++++++++++++++++--
 2 files changed, 51 insertions(+), 10 deletions(-)

diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
index 8a69a072..1663d155 100644
--- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
+++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift
@@ -69,15 +69,19 @@ public let verifyContactReducer = Reducer<VerifyContactState, VerifyContactActio
     state.isVerifying = true
     state.result = nil
     return Effect.result { [state] in
-      do {
-        let result = try env.messenger.verifyContact(state.contact)
-        let contactId = try state.contact.getId()
+      func updateStatus(_ status: XXModels.Contact.AuthStatus) throws {
         try env.db().bulkUpdateContacts.callAsFunction(
-          .init(id: [contactId]),
-          .init(authStatus: result ? .verified : .verificationFailed)
+          .init(id: [try state.contact.getId()]),
+          .init(authStatus: status)
         )
+      }
+      do {
+        try updateStatus(.verificationInProgress)
+        let result = try env.messenger.verifyContact(state.contact)
+        try updateStatus(result ? .verified : .verificationFailed)
         return .success(.didVerify(.success(result)))
       } catch {
+        try? updateStatus(.verificationFailed)
         return .success(.didVerify(.failure(error.localizedDescription)))
       }
     }
diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
index 6ca82b1e..97f8b428 100644
--- a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
@@ -45,8 +45,14 @@ final class VerifyContactFeatureTests: XCTestCase {
     }
 
     XCTAssertNoDifference(didVerifyContact, [contact])
-    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])])
-    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .verified)])
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [
+      .init(id: [contactId]),
+      .init(id: [contactId]),
+    ])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [
+      .init(authStatus: .verificationInProgress),
+      .init(authStatus: .verified)
+    ])
 
     store.receive(.didVerify(.success(true))) {
       $0.isVerifying = false
@@ -93,8 +99,14 @@ final class VerifyContactFeatureTests: XCTestCase {
     }
 
     XCTAssertNoDifference(didVerifyContact, [contact])
-    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])])
-    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .verificationFailed)])
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [
+      .init(id: [contactId]),
+      .init(id: [contactId]),
+    ])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [
+      .init(authStatus: .verificationInProgress),
+      .init(authStatus: .verificationFailed),
+    ])
 
     store.receive(.didVerify(.success(false))) {
       $0.isVerifying = false
@@ -103,9 +115,13 @@ final class VerifyContactFeatureTests: XCTestCase {
   }
 
   func testVerifyFailure() {
+    var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
+    let contactId = "contact-id".data(using: .utf8)!
+    contact.getIdFromContact.run = { _ in contactId }
+
     let store = TestStore(
       initialState: VerifyContactState(
-        contact: .unimplemented("contact-data".data(using: .utf8)!)
+        contact: contact
       ),
       reducer: verifyContactReducer,
       environment: .unimplemented
@@ -114,15 +130,36 @@ final class VerifyContactFeatureTests: XCTestCase {
     struct Failure: Error {}
     let error = Failure()
 
+    var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = []
+    var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = []
+
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
     store.environment.messenger.verifyContact.run = { _ in throw error }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.bulkUpdateContacts.run = { query, assignments in
+        didBulkUpdateContactsWithQuery.append(query)
+        didBulkUpdateContactsWithAssignments.append(assignments)
+        return 0
+      }
+      return db
+    }
 
     store.send(.verifyTapped) {
       $0.isVerifying = true
       $0.result = nil
     }
 
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [
+      .init(id: [contactId]),
+      .init(id: [contactId]),
+    ])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [
+      .init(authStatus: .verificationInProgress),
+      .init(authStatus: .verificationFailed),
+    ])
+
     store.receive(.didVerify(.failure(error.localizedDescription))) {
       $0.isVerifying = false
       $0.result = .failure(error.localizedDescription)
-- 
GitLab


From 145805a662581c519f93ef27a730e31d5f796fe9 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 12 Sep 2022 13:01:57 +0200
Subject: [PATCH 12/12] Update auth status when confirming request

---
 .../ConfirmRequestFeature.swift               | 14 ++++++---
 .../ConfirmRequestFeatureTests.swift          | 31 +++++++++++++++++--
 2 files changed, 38 insertions(+), 7 deletions(-)

diff --git a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift
index b18f9da9..7cc40da0 100644
--- a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift
+++ b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift
@@ -69,16 +69,20 @@ public let confirmRequestReducer = Reducer<ConfirmRequestState, ConfirmRequestAc
     state.isConfirming = true
     state.result = nil
     return Effect.result { [state] in
+      func updateStatus(_ status: XXModels.Contact.AuthStatus) throws {
+        try env.db().bulkUpdateContacts.callAsFunction(
+          .init(id: [try state.contact.getId()]),
+          .init(authStatus: status)
+        )
+      }
       do {
+        try updateStatus(.confirming)
         let e2e = try env.messenger.e2e.tryGet()
         _ = try e2e.confirmReceivedRequest(partner: state.contact)
-        let contactId = try state.contact.getId()
-        try env.db().bulkUpdateContacts.callAsFunction(
-          .init(id: [contactId]),
-          .init(authStatus: .friend)
-        )
+        try updateStatus(.friend)
         return .success(.didConfirm(.success))
       } catch {
+        try? updateStatus(.confirmationFailed)
         return .success(.didConfirm(.failure(error.localizedDescription)))
       }
     }
diff --git a/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift
index 2686d645..8dea06e1 100644
--- a/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift
@@ -49,8 +49,14 @@ final class ConfirmRequestFeatureTests: XCTestCase {
     }
 
     XCTAssertNoDifference(didConfirmRequestFromContact, [contact])
-    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])])
-    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .friend)])
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [
+      .init(id: [contactId]),
+      .init(id: [contactId]),
+    ])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [
+      .init(authStatus: .confirming),
+      .init(authStatus: .friend),
+    ])
 
     store.receive(.didConfirm(.success)) {
       $0.isConfirming = false
@@ -74,6 +80,9 @@ final class ConfirmRequestFeatureTests: XCTestCase {
     struct Failure: Error {}
     let error = Failure()
 
+    var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = []
+    var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = []
+
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
     store.environment.messenger.e2e.get = {
@@ -81,12 +90,30 @@ final class ConfirmRequestFeatureTests: XCTestCase {
       e2e.confirmReceivedRequest.run = { _ in throw error }
       return e2e
     }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.bulkUpdateContacts.run = { query, assignments in
+        didBulkUpdateContactsWithQuery.append(query)
+        didBulkUpdateContactsWithAssignments.append(assignments)
+        return 0
+      }
+      return db
+    }
 
     store.send(.confirmTapped) {
       $0.isConfirming = true
       $0.result = nil
     }
 
+    XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [
+      .init(id: [contactId]),
+      .init(id: [contactId]),
+    ])
+    XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [
+      .init(authStatus: .confirming),
+      .init(authStatus: .confirmationFailed),
+    ])
+
     store.receive(.didConfirm(.failure(error.localizedDescription))) {
       $0.isConfirming = false
       $0.result = .failure(error.localizedDescription)
-- 
GitLab