diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 10f45cd3ff6b3f9359973783412c08b883c300c3..9f8233b4902e5d7047d7011f3948f29aa559418a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,14 @@
 before_script:
-  - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts
-  - for ip in $(dig @8.8.8.8 git.xx.network +short); do ssh-keyscan git.xx.network,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts
+  - echo $CI_BUILD_REF
+  - echo $CI_PROJECT_DIR
+  - echo $PWD
+  - swift --version
   - xcodebuild -version
+  - eval $(ssh-agent -s)
+  - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
+  - mkdir -p ~/.ssh
+  - chmod 700 ~/.ssh
+  - ssh-keyscan -t rsa $GITLAB_SERVER > ~/.ssh/known_hosts
 
 stages:
   - test
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/elixxir-dapps-sdk-swift-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/elixxir-dapps-sdk-swift-Package.xcscheme
index 7818f7363eeee23304e1aece2235ce6bbaa19fb8..4dad91ad903da2bc783bca223adbadda48954c7d 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/elixxir-dapps-sdk-swift-Package.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/elixxir-dapps-sdk-swift-Package.xcscheme
@@ -36,10 +36,10 @@
          </BuildActionEntry>
          <BuildActionEntry
             buildForTesting = "YES"
-            buildForRunning = "YES"
+            buildForRunning = "NO"
             buildForProfiling = "NO"
             buildForArchiving = "NO"
-            buildForAnalyzing = "YES">
+            buildForAnalyzing = "NO">
             <BuildableReference
                BuildableIdentifier = "primary"
                BlueprintIdentifier = "XXClientTests"
@@ -50,10 +50,10 @@
          </BuildActionEntry>
          <BuildActionEntry
             buildForTesting = "YES"
-            buildForRunning = "YES"
+            buildForRunning = "NO"
             buildForProfiling = "NO"
             buildForArchiving = "NO"
-            buildForAnalyzing = "YES">
+            buildForAnalyzing = "NO">
             <BuildableReference
                BuildableIdentifier = "primary"
                BlueprintIdentifier = "XXMessengerClientTests"
diff --git a/Docs/XXMessengerClient.md b/Docs/XXMessengerClient.md
index 05afa4aaa33cb74d114d0548faf8c157e00678ce..efbb9de768ea89616473b3df51837785ee880dd4 100644
--- a/Docs/XXMessengerClient.md
+++ b/Docs/XXMessengerClient.md
@@ -4,7 +4,7 @@
 
 ## ▶️ Instantiate messenger
 
-Example:
+### Example
 
 ```swift
 // setup environment:
@@ -24,7 +24,7 @@ let messenger: Messenger = .live(environment)
 
 ## 🚀 Start messenger
 
-Example:
+### Example
 
 ```swift
 // allow cancellation of callbacks:
@@ -84,7 +84,7 @@ func start(messenger: Messenger) throws {
 
 ## 🛠 Use client components directly
 
-Example:
+### Example
 
 ```swift
 // get cMix:
@@ -95,4 +95,64 @@ let e2e = messenger.e2e()
 
 // get UserDicovery:
 let ud = messenger.ud()
-```
\ No newline at end of file
+
+// get Backup:
+let backup = messenger.backup()
+```
+
+## 💾 Backup
+
+### Make backup
+
+```swift
+// start receiving backup data before starting or resuming backup:
+let cancellable = messenger.registerBackupCallback(.init { data in
+  // handle backup data, save on disk, upload to cloud, etc.
+})
+
+// check if backup is already running:
+if messenger.isBackupRunning() == false {
+  do {
+    // try to resume previous backup:
+    try messenger.resumeBackup()
+  } catch {
+    // try to start a new backup:
+    let params: BackupParams = ...
+    try messenger.startBackup(
+      password: "backup-passphrase",
+      params: params
+    )
+  }
+}
+
+// update params in the backup:
+let params: BackupParams = ...
+try messenger.backupParams(params)
+
+// stop the backup:
+try messenger.stopBackup()
+
+// optionally stop receiving backup data
+cancellable.cancel()
+```
+
+When starting a new backup you must provide `BackupParams` to prevent creating backups that does not contain it.
+
+The registered backup callback can be reused later when a new backup is started. There is no need to cancel it and register a new callback in such a case.
+
+### Restore from backup
+
+```swift
+let result = try messenger.restoreBackup(
+  backupData: ...,
+  backupPassphrase: "backup-passphrase"
+)
+
+// handle restoration result:
+let restoredUsername = result.restoredParams.username
+let facts = try messenger.ud.tryGet().getFacts()
+let restoredEmail = facts.get(.email)?.value
+let restoredPhone = facts.get(.phone)?.value
+```
+
+If no error was thrown during restoration, the `Messenger` is already loaded, started, connected, and logged in.
\ No newline at end of file
diff --git a/Examples/Package.swift b/Examples/Package.swift
index d0a31fe81dd861a3d33bf4eaeb4f59fb7c9f4947..3cc048d33a65f27d26fa50d1f2779fc77b510dfe 100644
--- a/Examples/Package.swift
+++ b/Examples/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.6
+// swift-tools-version:5.7
 // This file makes Xcode doesn't display this directory inside swift package.
 import PackageDescription
 let package = Package(name: "", products: [], targets: [])
diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/BackupFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/BackupFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..61d3cc5840af204a0b1ac7d0f8fc6ea574d0bce0
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/BackupFeature.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 = "BackupFeature"
+               BuildableName = "BackupFeature"
+               BlueprintName = "BackupFeature"
+               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 = "BackupFeatureTests"
+               BuildableName = "BackupFeatureTests"
+               BlueprintName = "BackupFeatureTests"
+               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 = "BackupFeature"
+            BuildableName = "BackupFeature"
+            BlueprintName = "BackupFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ContactLookupFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ContactLookupFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..e0070c64629dce6e785c4bb8590d0b330d522f5b
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ContactLookupFeature.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 = "ContactLookupFeature"
+               BuildableName = "ContactLookupFeature"
+               BlueprintName = "ContactLookupFeature"
+               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 = "ContactLookupFeatureTests"
+               BuildableName = "ContactLookupFeatureTests"
+               BlueprintName = "ContactLookupFeatureTests"
+               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 = "ContactLookupFeature"
+            BuildableName = "ContactLookupFeature"
+            BlueprintName = "ContactLookupFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..4eb2a43343a9c76a7ff475789759efbed4b3c270
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1400"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "MyContactFeature"
+               BuildableName = "MyContactFeature"
+               BlueprintName = "MyContactFeature"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      codeCoverageEnabled = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "MyContactFeatureTests"
+               BuildableName = "MyContactFeatureTests"
+               BlueprintName = "MyContactFeatureTests"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "MyContactFeature"
+            BuildableName = "MyContactFeature"
+            BlueprintName = "MyContactFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ResetAuthFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ResetAuthFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..dded8f8def9e1bf44393cece148486be88e940d6
--- /dev/null
+++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ResetAuthFeature.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 = "ResetAuthFeature"
+               BuildableName = "ResetAuthFeature"
+               BlueprintName = "ResetAuthFeature"
+               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 = "ResetAuthFeatureTests"
+               BuildableName = "ResetAuthFeatureTests"
+               BlueprintName = "ResetAuthFeatureTests"
+               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 = "ResetAuthFeature"
+            BuildableName = "ResetAuthFeature"
+            BlueprintName = "ResetAuthFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index c5bcba6a09c4d828a7506a531d429f9d9ab8a92d..2b2d10391ee64f2b70f06b607c67437e78f3d55a 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -1,15 +1,10 @@
-// swift-tools-version: 5.6
+// swift-tools-version: 5.7
 import PackageDescription
 
 let swiftSettings: [SwiftSetting] = [
-  .unsafeFlags(
-    [
-      // "-Xfrontend", "-warn-concurrency",
-      // "-Xfrontend", "-debug-time-function-bodies",
-      // "-Xfrontend", "-debug-time-expression-type-checking",
-    ],
-    .when(configuration: .debug)
-  ),
+  //.unsafeFlags(["-Xfrontend", "-warn-concurrency"], .when(configuration: .debug)),
+  //.unsafeFlags(["-Xfrontend", "-debug-time-function-bodies"], .when(configuration: .debug)),
+  //.unsafeFlags(["-Xfrontend", "-debug-time-expression-type-checking"], .when(configuration: .debug)),
 ]
 
 let package = Package(
@@ -20,13 +15,17 @@ let package = Package(
   products: [
     .library(name: "AppCore", targets: ["AppCore"]),
     .library(name: "AppFeature", targets: ["AppFeature"]),
+    .library(name: "BackupFeature", targets: ["BackupFeature"]),
     .library(name: "ChatFeature", targets: ["ChatFeature"]),
     .library(name: "CheckContactAuthFeature", targets: ["CheckContactAuthFeature"]),
     .library(name: "ConfirmRequestFeature", targets: ["ConfirmRequestFeature"]),
     .library(name: "ContactFeature", targets: ["ContactFeature"]),
+    .library(name: "ContactLookupFeature", targets: ["ContactLookupFeature"]),
     .library(name: "ContactsFeature", targets: ["ContactsFeature"]),
     .library(name: "HomeFeature", targets: ["HomeFeature"]),
+    .library(name: "MyContactFeature", targets: ["MyContactFeature"]),
     .library(name: "RegisterFeature", targets: ["RegisterFeature"]),
+    .library(name: "ResetAuthFeature", targets: ["ResetAuthFeature"]),
     .library(name: "RestoreFeature", targets: ["RestoreFeature"]),
     .library(name: "SendRequestFeature", targets: ["SendRequestFeature"]),
     .library(name: "UserSearchFeature", targets: ["UserSearchFeature"]),
@@ -39,11 +38,11 @@ let package = Package(
     ),
     .package(
       url: "https://github.com/pointfreeco/swift-composable-architecture.git",
-      .upToNextMajor(from: "0.40.1")
+      .upToNextMajor(from: "0.40.2")
     ),
     .package(
       url: "https://git.xx.network/elixxir/client-ios-db.git",
-      .upToNextMajor(from: "1.1.0")
+      .upToNextMajor(from: "1.2.0")
     ),
     .package(
       url: "https://github.com/darrarski/swift-composable-presentation.git",
@@ -51,13 +50,26 @@ let package = Package(
     ),
     .package(
       url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
-      .upToNextMajor(from: "0.4.0")
+      .upToNextMajor(from: "0.4.1")
+    ),
+    .package(
+      url: "https://github.com/pointfreeco/swift-custom-dump.git",
+      .upToNextMajor(from: "0.5.2")
+    ),
+    .package(
+      url: "https://github.com/apple/swift-log.git",
+      .upToNextMajor(from: "1.4.4")
+    ),
+    .package(
+      url: "https://github.com/kean/Pulse.git",
+      .upToNextMajor(from: "2.1.2")
     ),
   ],
   targets: [
     .target(
       name: "AppCore",
       dependencies: [
+        .product(name: "Logging", package: "swift-log"),
         .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXDatabase", package: "client-ios-db"),
@@ -69,7 +81,8 @@ let package = Package(
     .testTarget(
       name: "AppCoreTests",
       dependencies: [
-        .target(name: "AppCore")
+        .target(name: "AppCore"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -77,13 +90,17 @@ let package = Package(
       name: "AppFeature",
       dependencies: [
         .target(name: "AppCore"),
+        .target(name: "BackupFeature"),
         .target(name: "ChatFeature"),
         .target(name: "CheckContactAuthFeature"),
         .target(name: "ConfirmRequestFeature"),
         .target(name: "ContactFeature"),
+        .target(name: "ContactLookupFeature"),
         .target(name: "ContactsFeature"),
         .target(name: "HomeFeature"),
+        .target(name: "MyContactFeature"),
         .target(name: "RegisterFeature"),
+        .target(name: "ResetAuthFeature"),
         .target(name: "RestoreFeature"),
         .target(name: "SendRequestFeature"),
         .target(name: "UserSearchFeature"),
@@ -91,6 +108,9 @@ let package = Package(
         .target(name: "WelcomeFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
+        .product(name: "Logging", package: "swift-log"),
+        .product(name: "PulseLogHandler", package: "Pulse"),
+        .product(name: "PulseUI", package: "Pulse"),
         .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXModels", package: "client-ios-db"),
       ],
@@ -100,6 +120,22 @@ let package = Package(
       name: "AppFeatureTests",
       dependencies: [
         .target(name: "AppFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .target(
+      name: "BackupFeature",
+      dependencies: [
+        .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .testTarget(
+      name: "BackupFeatureTests",
+      dependencies: [
+        .target(name: "BackupFeature"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -118,6 +154,7 @@ let package = Package(
       name: "ChatFeatureTests",
       dependencies: [
         .target(name: "ChatFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -135,6 +172,7 @@ let package = Package(
       name: "CheckContactAuthFeatureTests",
       dependencies: [
         .target(name: "CheckContactAuthFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ]
     ),
     .target(
@@ -151,6 +189,7 @@ let package = Package(
       name: "ConfirmRequestFeatureTests",
       dependencies: [
         .target(name: "ConfirmRequestFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ]
     ),
     .target(
@@ -160,6 +199,8 @@ let package = Package(
         .target(name: "ChatFeature"),
         .target(name: "CheckContactAuthFeature"),
         .target(name: "ConfirmRequestFeature"),
+        .target(name: "ContactLookupFeature"),
+        .target(name: "ResetAuthFeature"),
         .target(name: "SendRequestFeature"),
         .target(name: "VerifyContactFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
@@ -173,6 +214,25 @@ let package = Package(
       name: "ContactFeatureTests",
       dependencies: [
         .target(name: "ContactFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .target(
+      name: "ContactLookupFeature",
+      dependencies: [
+        .target(name: "AppCore"),
+        .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXModels", package: "client-ios-db"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .testTarget(
+      name: "ContactLookupFeatureTests",
+      dependencies: [
+        .target(name: "ContactLookupFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -181,6 +241,7 @@ let package = Package(
       dependencies: [
         .target(name: "AppCore"),
         .target(name: "ContactFeature"),
+        .target(name: "MyContactFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
@@ -193,6 +254,7 @@ let package = Package(
       name: "ContactsFeatureTests",
       dependencies: [
         .target(name: "ContactsFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -200,11 +262,13 @@ let package = Package(
       name: "HomeFeature",
       dependencies: [
         .target(name: "AppCore"),
+        .target(name: "BackupFeature"),
         .target(name: "ContactsFeature"),
         .target(name: "RegisterFeature"),
         .target(name: "UserSearchFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
       ],
       swiftSettings: swiftSettings
@@ -213,6 +277,26 @@ let package = Package(
       name: "HomeFeatureTests",
       dependencies: [
         .target(name: "HomeFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .target(
+      name: "MyContactFeature",
+      dependencies: [
+        .target(name: "AppCore"),
+        .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXModels", package: "client-ios-db"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .testTarget(
+      name: "MyContactFeatureTests",
+      dependencies: [
+        .target(name: "MyContactFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -231,13 +315,34 @@ let package = Package(
       name: "RegisterFeatureTests",
       dependencies: [
         .target(name: "RegisterFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .target(
+      name: "ResetAuthFeature",
+      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"),
+      ],
+      swiftSettings: swiftSettings
+    ),
+    .testTarget(
+      name: "ResetAuthFeatureTests",
+      dependencies: [
+        .target(name: "ResetAuthFeature"),
       ],
       swiftSettings: swiftSettings
     ),
     .target(
       name: "RestoreFeature",
       dependencies: [
+        .target(name: "AppCore"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXModels", package: "client-ios-db"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -245,6 +350,7 @@ let package = Package(
       name: "RestoreFeatureTests",
       dependencies: [
         .target(name: "RestoreFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -263,6 +369,7 @@ let package = Package(
       name: "SendRequestFeatureTests",
       dependencies: [
         .target(name: "SendRequestFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -282,6 +389,7 @@ let package = Package(
       name: "UserSearchFeatureTests",
       dependencies: [
         .target(name: "UserSearchFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
@@ -299,11 +407,13 @@ let package = Package(
       name: "VerifyContactFeatureTests",
       dependencies: [
         .target(name: "VerifyContactFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ]
     ),
     .target(
       name: "WelcomeFeature",
       dependencies: [
+        .target(name: "AppCore"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
       ],
@@ -313,6 +423,7 @@ let package = Package(
       name: "WelcomeFeatureTests",
       dependencies: [
         .target(name: "WelcomeFeature"),
+        .product(name: "CustomDump", package: "swift-custom-dump"),
       ],
       swiftSettings: swiftSettings
     ),
diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/project.pbxproj b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/project.pbxproj
index f0505c12949959289b70b0fc9e45c8f1d1f7079c..1d244c566f5a1ce89a6ff2df5a69710f63b5165c 100644
--- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/project.pbxproj
+++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/project.pbxproj
@@ -12,8 +12,9 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
-		31964B8A28A6D37100BBDC17 /* XXMessenger.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XXMessenger.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		31964B8A28A6D37100BBDC17 /* XXME.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XXME.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		31964B9128A6D37200BBDC17 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		31EF69BC28F035DE00BD83FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -39,7 +40,7 @@
 		31964B8B28A6D37100BBDC17 /* Products */ = {
 			isa = PBXGroup;
 			children = (
-				31964B8A28A6D37100BBDC17 /* XXMessenger.app */,
+				31964B8A28A6D37100BBDC17 /* XXME.app */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -48,6 +49,7 @@
 			isa = PBXGroup;
 			children = (
 				31964B9128A6D37200BBDC17 /* Assets.xcassets */,
+				31EF69BC28F035DE00BD83FC /* Info.plist */,
 			);
 			path = XXMessenger;
 			sourceTree = "<group>";
@@ -72,7 +74,7 @@
 				313CFFF928B632E40050B10D /* AppFeature */,
 			);
 			productName = XXMessenger;
-			productReference = 31964B8A28A6D37100BBDC17 /* XXMessenger.app */;
+			productReference = 31964B8A28A6D37100BBDC17 /* XXME.app */;
 			productType = "com.apple.product-type.application";
 		};
 /* End PBXNativeTarget section */
@@ -163,7 +165,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0;
+				CURRENT_PROJECT_VERSION = 2;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
@@ -226,7 +228,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0;
+				CURRENT_PROJECT_VERSION = 2;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -256,9 +258,11 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				DEVELOPMENT_TEAM = "";
+				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_FILE = XXMessenger/Info.plist;
+				INFOPLIST_KEY_LSApplicationCategoryType = "";
 				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
 				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
 				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -270,7 +274,11 @@
 					"@executable_path/Frameworks",
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = xx.network.XXMessengerExample;
-				PRODUCT_NAME = "$(TARGET_NAME)";
+				PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
+				PRODUCT_NAME = XXME;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
 				SWIFT_EMIT_LOC_STRINGS = YES;
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = 1;
@@ -283,9 +291,11 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				DEVELOPMENT_TEAM = "";
+				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_FILE = XXMessenger/Info.plist;
+				INFOPLIST_KEY_LSApplicationCategoryType = "";
 				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
 				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
 				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -297,7 +307,11 @@
 					"@executable_path/Frameworks",
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = xx.network.XXMessengerExample;
-				PRODUCT_NAME = "$(TARGET_NAME)";
+				PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
+				PRODUCT_NAME = XXME;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
 				SWIFT_EMIT_LOC_STRINGS = YES;
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = 1;
diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
index 65400dff681f98714be5c8db74bc82b6558dfe17..aef30c316ef7aa40b1b1c36e37057d4284222bf4 100644
--- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
+++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
@@ -15,7 +15,7 @@
             <BuildableReference
                BuildableIdentifier = "primary"
                BlueprintIdentifier = "31964B8928A6D37100BBDC17"
-               BuildableName = "XXMessenger.app"
+               BuildableName = "XXME.app"
                BlueprintName = "XXMessenger"
                ReferencedContainer = "container:XXMessenger.xcodeproj">
             </BuildableReference>
@@ -49,6 +49,16 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "BackupFeatureTests"
+               BuildableName = "BackupFeatureTests"
+               BlueprintName = "BackupFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
@@ -89,6 +99,16 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "ContactLookupFeatureTests"
+               BuildableName = "ContactLookupFeatureTests"
+               BlueprintName = "ContactLookupFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
@@ -109,6 +129,16 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "MyContactFeatureTests"
+               BuildableName = "MyContactFeatureTests"
+               BlueprintName = "MyContactFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
@@ -119,6 +149,16 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "ResetAuthFeatureTests"
+               BuildableName = "ResetAuthFeatureTests"
+               BlueprintName = "ResetAuthFeatureTests"
+               ReferencedContainer = "container:..">
+            </BuildableReference>
+         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
@@ -186,7 +226,7 @@
          <BuildableReference
             BuildableIdentifier = "primary"
             BlueprintIdentifier = "31964B8928A6D37100BBDC17"
-            BuildableName = "XXMessenger.app"
+            BuildableName = "XXME.app"
             BlueprintName = "XXMessenger"
             ReferencedContainer = "container:XXMessenger.xcodeproj">
          </BuildableReference>
@@ -203,7 +243,7 @@
          <BuildableReference
             BuildableIdentifier = "primary"
             BlueprintIdentifier = "31964B8928A6D37100BBDC17"
-            BuildableName = "XXMessenger.app"
+            BuildableName = "XXME.app"
             BlueprintName = "XXMessenger"
             ReferencedContainer = "container:XXMessenger.xcodeproj">
          </BuildableReference>
diff --git a/Examples/xx-messenger/Project/XXMessenger/Info.plist b/Examples/xx-messenger/Project/XXMessenger/Info.plist
new file mode 100644
index 0000000000000000000000000000000000000000..1114a1ec787c979c3753d26ee5bce26ea246d836
--- /dev/null
+++ b/Examples/xx-messenger/Project/XXMessenger/Info.plist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>ITSAppUsesNonExemptEncryption</key>
+	<false/>
+  <key>NSLocalNetworkUsageDescription</key>
+  <string>Network usage required for debugging purposes </string>
+  <key>NSBonjourServices</key>
+  <array>
+    <string>_pulse._tcp</string>
+  </array>
+</dict>
+</plist>
diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
index 729fe13eff8ee42ed1a12ba2ce03d082e5b2cfa8..a894b5e79200fb13b3986840410492050510e768 100644
--- a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
@@ -18,7 +18,7 @@ extension AuthCallbackHandlerReset {
       guard var dbContact = try db().fetchContacts(.init(id: [id])).first else {
         return
       }
-      dbContact.authStatus = .stranger
+      dbContact.authStatus = .friend
       dbContact = try db().saveContact(dbContact)
     }
   }
diff --git a/Examples/xx-messenger/Sources/AppCore/DBManager/DBManager.swift b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManager.swift
index f591dc1c2d37c3f14cf1a47905ccee2e3379fad1..beec52f86eeb3574b5da410923bc10ee3a9405a9 100644
--- a/Examples/xx-messenger/Sources/AppCore/DBManager/DBManager.swift
+++ b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManager.swift
@@ -1,3 +1,4 @@
+import Foundation
 import XXModels
 
 public struct DBManager {
@@ -8,7 +9,12 @@ public struct DBManager {
 }
 
 extension DBManager {
-  public static func live() -> DBManager {
+  public static func live(
+    url: URL = FileManager.default
+      .urls(for: .applicationSupportDirectory, in: .userDomainMask)
+      .first!
+      .appendingPathComponent("database")
+  ) -> DBManager {
     class Container {
       var db: Database?
     }
@@ -17,9 +23,9 @@ extension DBManager {
 
     return DBManager(
       hasDB: .init { container.db != nil },
-      makeDB: .live(setDB: { container.db = $0 }),
+      makeDB: .live(url: url, setDB: { container.db = $0 }),
       getDB: .live(getDB: { container.db }),
-      removeDB: .live(getDB: { container.db }, unsetDB: { container.db = nil })
+      removeDB: .live(url: url, getDB: { container.db }, unsetDB: { container.db = nil })
     )
   }
 }
diff --git a/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerMakeDB.swift b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerMakeDB.swift
index a3f018b1d617ad402a4c5f8914b07a2c622150ac..d7e5e93db530327715ce72cc7cae602fb1802c8c 100644
--- a/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerMakeDB.swift
+++ b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerMakeDB.swift
@@ -13,18 +13,14 @@ public struct DBManagerMakeDB {
 
 extension DBManagerMakeDB {
   public static func live(
+    url: URL,
     setDB: @escaping (Database) -> Void
   ) -> DBManagerMakeDB {
     DBManagerMakeDB {
-      let dbDirectoryURL = FileManager.default
-        .urls(for: .applicationSupportDirectory, in: .userDomainMask)
-        .first!
-        .appendingPathComponent("database")
-
       try? FileManager.default
-        .createDirectory(at: dbDirectoryURL, withIntermediateDirectories: true)
+        .createDirectory(at: url, withIntermediateDirectories: true)
 
-      let dbFilePath = dbDirectoryURL
+      let dbFilePath = url
         .appendingPathComponent("db")
         .appendingPathExtension("sqlite")
         .path
diff --git a/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerRemoveDB.swift b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerRemoveDB.swift
index 69ab6e020d1ae782b048c82d75fb667eb3d7b985..557a1a53cfaa5902f6a51e2db5189285bc65ec4a 100644
--- a/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerRemoveDB.swift
+++ b/Examples/xx-messenger/Sources/AppCore/DBManager/DBManagerRemoveDB.swift
@@ -13,12 +13,18 @@ public struct DBManagerRemoveDB {
 
 extension DBManagerRemoveDB {
   public static func live(
+    url: URL,
     getDB: @escaping () -> Database?,
     unsetDB: @escaping () -> Void
   ) -> DBManagerRemoveDB {
     DBManagerRemoveDB {
-      try getDB()?.drop()
+      let db = getDB()
       unsetDB()
+      try db?.drop()
+      let fm = FileManager.default
+      if fm.fileExists(atPath: url.path) {
+        try fm.removeItem(atPath: url.path)
+      }
     }
   }
 }
diff --git a/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift b/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift
new file mode 100644
index 0000000000000000000000000000000000000000..128d31124ce1e51ab89c023c3cac258c2d6a9182
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/Logger/Logger.swift
@@ -0,0 +1,43 @@
+import Foundation
+import Logging
+import XCTestDynamicOverlay
+
+public struct Logger {
+  public enum Message: Equatable {
+    case error(NSError)
+  }
+
+  public var run: (Message, String, String, UInt) -> Void
+
+  public func callAsFunction(
+    _ msg: Message,
+    file: String = #file,
+    function: String = #function,
+    line: UInt = #line
+  ) {
+    run(msg, file, function, line)
+  }
+}
+
+extension Logger {
+  public static func live() -> Logger {
+    let logger = Logging.Logger(label: "xx.network.XXMessengerExample")
+    return Logger { msg, file, function, line in
+      switch msg {
+      case .error(let error):
+        logger.error(
+          .init(stringLiteral: error.localizedDescription),
+          file: file,
+          function: function,
+          line: line
+        )
+      }
+    }
+  }
+}
+
+extension Logger {
+  public static let unimplemented = Logger(
+    run: XCTUnimplemented("\(Self.self).error")
+  )
+}
diff --git a/Examples/xx-messenger/Sources/AppCore/SharedUI/AppVersionText.swift b/Examples/xx-messenger/Sources/AppCore/SharedUI/AppVersionText.swift
new file mode 100644
index 0000000000000000000000000000000000000000..47c76873d1cd0253972566c05a525acea32cc509
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/SharedUI/AppVersionText.swift
@@ -0,0 +1,31 @@
+import SwiftUI
+
+public struct AppVersionText: View {
+  public init() {}
+
+  public var body: some View {
+    Text("v\(version) (\(build))")
+  }
+
+  var version: String = Bundle.main.shortVersionString ?? "0.0.0"
+  var build: String = Bundle.main.versionString ?? "0"
+}
+
+private extension Bundle {
+  var shortVersionString: String? {
+    infoDictionary?["CFBundleShortVersionString"] as? String
+  }
+  var versionString: String? {
+    infoDictionary?["CFBundleVersion"] as? String
+  }
+}
+
+#if DEBUG
+struct AppVersionText_Previews: PreviewProvider {
+  static var previews: some View {
+    AppVersionText()
+      .padding()
+      .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Sources/AppCore/SharedUI/Data+hexString.swift b/Examples/xx-messenger/Sources/AppCore/SharedUI/Data+hexString.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e8010b95c6fbeea66049228f1129f929559bdf86
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/SharedUI/Data+hexString.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+extension Data {
+  public func hexString(bytesSeparator: String = " ") -> String {
+    map { String(format: "%02hhx\(bytesSeparator)", $0) }.joined()
+  }
+}
diff --git a/Examples/xx-messenger/Sources/AppCore/SharedUI/ShakeViewModifier.swift b/Examples/xx-messenger/Sources/AppCore/SharedUI/ShakeViewModifier.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d46cdc6d1a61adde2ace4c7fd6aebb3a1cfedd16
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/SharedUI/ShakeViewModifier.swift
@@ -0,0 +1,42 @@
+import SwiftUI
+
+struct ShakeViewModifier: ViewModifier {
+  var action: () -> Void
+
+  func body(content: Content) -> some View {
+    content.onReceive(
+      NotificationCenter.default.publisher(
+        for: UIDevice.deviceDidShakeNotification
+      ),
+      perform: { _ in
+        action()
+      }
+    )
+  }
+}
+
+extension View {
+  public func onShake(perform action: @escaping () -> Void) -> some View {
+    modifier(ShakeViewModifier(action: action))
+  }
+}
+
+extension UIDevice {
+  static let deviceDidShakeNotification = Notification.Name(
+    rawValue: "deviceDidShakeNotification"
+  )
+}
+
+extension UIWindow {
+  open override func motionEnded(
+    _ motion: UIEvent.EventSubtype,
+    with event: UIEvent?
+  ) {
+    super.motionEnded(motion, with: event)
+    guard motion == .motionShake else { return }
+    NotificationCenter.default.post(
+      name: UIDevice.deviceDidShakeNotification,
+      object: nil
+    )
+  }
+}
diff --git a/Examples/xx-messenger/Sources/AppFeature/App.swift b/Examples/xx-messenger/Sources/AppFeature/App.swift
index d6b0b722c4d610d823b5313aae797e3f72dc10a3..7c3f0e82b5b00ee3aa4ff601b50556dbc8a148d2 100644
--- a/Examples/xx-messenger/Sources/AppFeature/App.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/App.swift
@@ -1,8 +1,14 @@
 import ComposableArchitecture
+import Logging
+import PulseLogHandler
 import SwiftUI
 
 @main
 struct App: SwiftUI.App {
+  init() {
+    LoggingSystem.bootstrap(PersistentLogHandler.init)
+  }
+
   var body: some Scene {
     WindowGroup {
       AppView(store: Store(
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 611123e0334c6078250bf0e782153975d6891799..c4ff9b27f027a0eef0d02eaeab6e8e7db519025c 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -1,12 +1,16 @@
 import AppCore
+import BackupFeature
 import ChatFeature
 import CheckContactAuthFeature
 import ConfirmRequestFeature
 import ContactFeature
+import ContactLookupFeature
 import ContactsFeature
 import Foundation
 import HomeFeature
+import MyContactFeature
 import RegisterFeature
+import ResetAuthFeature
 import RestoreFeature
 import SendRequestFeature
 import UserSearchFeature
@@ -26,14 +30,27 @@ extension AppEnvironment {
       handleConfirm: .live(db: dbManager.getDB),
       handleReset: .live(db: dbManager.getDB)
     )
+    let backupStorage = BackupStorage.onDisk()
     let mainQueue = DispatchQueue.main.eraseToAnyScheduler()
     let bgQueue = DispatchQueue.global(qos: .background).eraseToAnyScheduler()
 
+    defer {
+      _ = try! messenger.setLogLevel(.debug)
+      messenger.startLogging()
+    }
+
     let contactEnvironment = ContactEnvironment(
       messenger: messenger,
       db: dbManager.getDB,
       mainQueue: mainQueue,
       bgQueue: bgQueue,
+      lookup: {
+        ContactLookupEnvironment(
+          messenger: messenger,
+          mainQueue: mainQueue,
+          bgQueue: bgQueue
+        )
+      },
       sendRequest: {
         SendRequestEnvironment(
           messenger: messenger,
@@ -66,6 +83,13 @@ extension AppEnvironment {
           bgQueue: bgQueue
         )
       },
+      resetAuth: {
+        ResetAuthEnvironment(
+          messenger: messenger,
+          mainQueue: mainQueue,
+          bgQueue: bgQueue
+        )
+      },
       chat: {
         ChatEnvironment(
           messenger: messenger,
@@ -84,6 +108,13 @@ extension AppEnvironment {
     return AppEnvironment(
       dbManager: dbManager,
       messenger: messenger,
+      authHandler: authHandler,
+      messageListener: .live(
+        messenger: messenger,
+        db: dbManager.getDB
+      ),
+      backupStorage: backupStorage,
+      log: .live(),
       mainQueue: mainQueue,
       bgQueue: bgQueue,
       welcome: {
@@ -94,17 +125,19 @@ extension AppEnvironment {
         )
       },
       restore: {
-        RestoreEnvironment()
+        RestoreEnvironment(
+          messenger: messenger,
+          db: dbManager.getDB,
+          loadData: .live,
+          now: Date.init,
+          mainQueue: mainQueue,
+          bgQueue: bgQueue
+        )
       },
       home: {
         HomeEnvironment(
           messenger: messenger,
           dbManager: dbManager,
-          authHandler: authHandler,
-          messageListener: .live(
-            messenger: messenger,
-            db: dbManager.getDB
-          ),
           mainQueue: mainQueue,
           bgQueue: bgQueue,
           register: {
@@ -122,7 +155,15 @@ extension AppEnvironment {
               db: dbManager.getDB,
               mainQueue: mainQueue,
               bgQueue: bgQueue,
-              contact: { contactEnvironment }
+              contact: { contactEnvironment },
+              myContact: {
+                MyContactEnvironment(
+                  messenger: messenger,
+                  db: dbManager.getDB,
+                  mainQueue: mainQueue,
+                  bgQueue: bgQueue
+                )
+              }
             )
           },
           userSearch: {
@@ -132,6 +173,14 @@ extension AppEnvironment {
               bgQueue: bgQueue,
               contact: { contactEnvironment }
             )
+          },
+          backup: {
+            BackupEnvironment(
+              messenger: messenger,
+              backupStorage: backupStorage,
+              mainQueue: mainQueue,
+              bgQueue: bgQueue
+            )
           }
         )
       }
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
index 43cede697423359094d9b0186350abbc01e79d2e..7796f592bcd5c795c20d4a281d243cd03a7b8df9 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
@@ -6,6 +6,7 @@ import Foundation
 import HomeFeature
 import RestoreFeature
 import WelcomeFeature
+import XXClient
 import XXMessengerClient
 
 struct AppState: Equatable {
@@ -37,6 +38,7 @@ extension AppState.Screen {
 
 enum AppAction: Equatable, BindableAction {
   case start
+  case stop
   case binding(BindingAction<AppState>)
   case welcome(WelcomeAction)
   case restore(RestoreAction)
@@ -46,6 +48,10 @@ enum AppAction: Equatable, BindableAction {
 struct AppEnvironment {
   var dbManager: DBManager
   var messenger: Messenger
+  var authHandler: AuthCallbackHandler
+  var messageListener: MessageListenerHandler
+  var backupStorage: BackupStorage
+  var log: Logger
   var mainQueue: AnySchedulerOf<DispatchQueue>
   var bgQueue: AnySchedulerOf<DispatchQueue>
   var welcome: () -> WelcomeEnvironment
@@ -53,10 +59,15 @@ struct AppEnvironment {
   var home: () -> HomeEnvironment
 }
 
+#if DEBUG
 extension AppEnvironment {
   static let unimplemented = AppEnvironment(
     dbManager: .unimplemented,
     messenger: .unimplemented,
+    authHandler: .unimplemented,
+    messageListener: .unimplemented,
+    backupStorage: .unimplemented,
+    log: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
     welcome: { .unimplemented },
@@ -64,37 +75,57 @@ extension AppEnvironment {
     home: { .unimplemented }
   )
 }
+#endif
 
 let appReducer = Reducer<AppState, AppAction, AppEnvironment>
 { state, action, env in
+  enum EffectId {}
+
   switch action {
   case .start, .welcome(.finished), .restore(.finished), .home(.deleteAccount(.success)):
     state.screen = .loading
-    return .run { subscriber in
+    return Effect.run { subscriber in
+      var cancellables: [XXClient.Cancellable] = []
+
       do {
         if env.dbManager.hasDB() == false {
           try env.dbManager.makeDB()
         }
 
-        if env.messenger.isLoaded() == false {
-          if env.messenger.isCreated() == false {
-            subscriber.send(.set(\.$screen, .welcome(WelcomeState())))
-            subscriber.send(completion: .finished)
-            return AnyCancellable {}
-          }
+        cancellables.append(env.authHandler(onError: { error in
+          env.log(.error(error as NSError))
+        }))
+        cancellables.append(env.messageListener(onError: { error in
+          env.log(.error(error as NSError))
+        }))
+        cancellables.append(env.messenger.registerBackupCallback(.init { data in
+          try? env.backupStorage.store(data)
+        }))
+
+        let isLoaded = env.messenger.isLoaded()
+        let isCreated = env.messenger.isCreated()
+
+        if !isLoaded, !isCreated {
+          subscriber.send(.set(\.$screen, .welcome(WelcomeState())))
+        } else if !isLoaded {
           try env.messenger.load()
+          subscriber.send(.set(\.$screen, .home(HomeState())))
+        } else {
+          subscriber.send(.set(\.$screen, .home(HomeState())))
         }
-
-        subscriber.send(.set(\.$screen, .home(HomeState())))
       } catch {
         subscriber.send(.set(\.$screen, .failure(error.localizedDescription)))
       }
-      subscriber.send(completion: .finished)
-      return AnyCancellable {}
+
+      return AnyCancellable { cancellables.forEach { $0.cancel() } }
     }
     .subscribe(on: env.bgQueue)
     .receive(on: env.mainQueue)
     .eraseToEffect()
+    .cancellable(id: EffectId.self, cancelInFlight: true)
+
+  case .stop:
+    return .cancel(id: EffectId.self)
 
   case .welcome(.restoreTapped):
     state.screen = .restore(RestoreState())
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppView.swift b/Examples/xx-messenger/Sources/AppFeature/AppView.swift
index 64a8411df29fe65ebab6b403ae9f2c88438d140c..57983b1dd0b826321f3dac8c120637736ff64b60 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppView.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppView.swift
@@ -1,11 +1,13 @@
 import ComposableArchitecture
 import HomeFeature
+import PulseUI
 import RestoreFeature
 import SwiftUI
 import WelcomeFeature
 
 struct AppView: View {
   let store: Store<AppState, AppAction>
+  @State var isPresentingPulse = false
 
   enum ViewState: Equatable {
     case loading
@@ -119,6 +121,15 @@ struct AppView: View {
       .animation(.default, value: viewStore.state)
       .task { viewStore.send(.start) }
     }
+    .onShake {
+      isPresentingPulse = true
+    }
+    .fullScreenCover(isPresented: $isPresentingPulse) {
+      PulseUI.MainView(
+        store: .shared,
+        onDismiss: { isPresentingPulse = false }
+      )
+    }
   }
 }
 
diff --git a/Examples/xx-messenger/Sources/BackupFeature/Alerts.swift b/Examples/xx-messenger/Sources/BackupFeature/Alerts.swift
new file mode 100644
index 0000000000000000000000000000000000000000..2bd95f770ff5ce1be9e808284d1d50f3d111af1d
--- /dev/null
+++ b/Examples/xx-messenger/Sources/BackupFeature/Alerts.swift
@@ -0,0 +1,10 @@
+import ComposableArchitecture
+
+extension AlertState where Action == BackupAction {
+  public static func error(_ error: Error) -> AlertState<BackupAction> {
+    AlertState(
+      title: TextState("Error"),
+      message: TextState(error.localizedDescription)
+    )
+  }
+}
diff --git a/Examples/xx-messenger/Sources/BackupFeature/BackupFeature.swift b/Examples/xx-messenger/Sources/BackupFeature/BackupFeature.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8010f17d1d0727c4621841dd124a93dcda0e0b5d
--- /dev/null
+++ b/Examples/xx-messenger/Sources/BackupFeature/BackupFeature.swift
@@ -0,0 +1,228 @@
+import Combine
+import ComposableArchitecture
+import Foundation
+import XXClient
+import XXMessengerClient
+
+public struct BackupState: Equatable {
+  public enum Field: String, Hashable {
+    case passphrase
+  }
+
+  public enum Error: String, Swift.Error, Equatable {
+    case contactUsernameMissing
+  }
+
+  public init(
+    isRunning: Bool = false,
+    isStarting: Bool = false,
+    isResuming: Bool = false,
+    isStopping: Bool = false,
+    backup: BackupStorage.Backup? = nil,
+    alert: AlertState<BackupAction>? = nil,
+    focusedField: Field? = nil,
+    passphrase: String = "",
+    isExporting: Bool = false,
+    exportData: Data? = nil
+  ) {
+    self.isRunning = isRunning
+    self.isStarting = isStarting
+    self.isResuming = isResuming
+    self.isStopping = isStopping
+    self.backup = backup
+    self.alert = alert
+    self.focusedField = focusedField
+    self.passphrase = passphrase
+    self.isExporting = isExporting
+    self.exportData = exportData
+  }
+
+  public var isRunning: Bool
+  public var isStarting: Bool
+  public var isResuming: Bool
+  public var isStopping: Bool
+  public var backup: BackupStorage.Backup?
+  public var alert: AlertState<BackupAction>?
+  @BindableState public var focusedField: Field?
+  @BindableState public var passphrase: String
+  @BindableState public var isExporting: Bool
+  public var exportData: Data?
+}
+
+public enum BackupAction: Equatable, BindableAction {
+  case task
+  case cancelTask
+  case startTapped
+  case resumeTapped
+  case stopTapped
+  case exportTapped
+  case alertDismissed
+  case backupUpdated(BackupStorage.Backup?)
+  case didStart(failure: NSError?)
+  case didResume(failure: NSError?)
+  case didStop(failure: NSError?)
+  case didExport(failure: NSError?)
+  case binding(BindingAction<BackupState>)
+}
+
+public struct BackupEnvironment {
+  public init(
+    messenger: Messenger,
+    backupStorage: BackupStorage,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>
+  ) {
+    self.messenger = messenger
+    self.backupStorage = backupStorage
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+  }
+
+  public var messenger: Messenger
+  public var backupStorage: BackupStorage
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
+}
+
+#if DEBUG
+extension BackupEnvironment {
+  public static let unimplemented = BackupEnvironment(
+    messenger: .unimplemented,
+    backupStorage: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
+}
+#endif
+
+public let backupReducer = Reducer<BackupState, BackupAction, BackupEnvironment>
+{ state, action, env in
+  enum TaskEffectId {}
+
+  switch action {
+  case .task:
+    state.isRunning = env.messenger.isBackupRunning()
+    return Effect.run { subscriber in
+      subscriber.send(.backupUpdated(env.backupStorage.stored()))
+      let cancellable = env.backupStorage.observe { backup in
+        subscriber.send(.backupUpdated(backup))
+      }
+      return AnyCancellable { cancellable.cancel() }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+    .cancellable(id: TaskEffectId.self, cancelInFlight: true)
+
+  case .cancelTask:
+    return .cancel(id: TaskEffectId.self)
+
+  case .startTapped:
+    state.isStarting = true
+    state.focusedField = nil
+    return Effect.run { [state] subscriber in
+      do {
+        let contact = try env.messenger.myContact(includeFacts: .types([.username]))
+        guard let username = try contact.getFact(.username)?.value else {
+          throw BackupState.Error.contactUsernameMissing
+        }
+        try env.messenger.startBackup(
+          password: state.passphrase,
+          params: BackupParams(username: username)
+        )
+        subscriber.send(.didStart(failure: nil))
+      } catch {
+        subscriber.send(.didStart(failure: error as NSError))
+      }
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .resumeTapped:
+    state.isResuming = true
+    return Effect.run { subscriber in
+      do {
+        try env.messenger.resumeBackup()
+        subscriber.send(.didResume(failure: nil))
+      } catch {
+        subscriber.send(.didResume(failure: error as NSError))
+      }
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .stopTapped:
+    state.isStopping = true
+    return Effect.run { subscriber in
+      do {
+        try env.messenger.stopBackup()
+        try env.backupStorage.remove()
+        subscriber.send(.didStop(failure: nil))
+      } catch {
+        subscriber.send(.didStop(failure: error as NSError))
+      }
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .exportTapped:
+    state.isExporting = true
+    state.exportData = state.backup?.data
+    return .none
+
+  case .alertDismissed:
+    state.alert = nil
+    return .none
+
+  case .backupUpdated(let backup):
+    state.backup = backup
+    return .none
+
+  case .didStart(let failure):
+    state.isRunning = env.messenger.isBackupRunning()
+    state.isStarting = false
+    if let failure {
+      state.alert = .error(failure)
+    } else {
+      state.passphrase = ""
+    }
+    return .none
+
+  case .didResume(let failure):
+    state.isRunning = env.messenger.isBackupRunning()
+    state.isResuming = false
+    if let failure {
+      state.alert = .error(failure)
+    }
+    return .none
+
+  case .didStop(let failure):
+    state.isRunning = env.messenger.isBackupRunning()
+    state.isStopping = false
+    if let failure {
+      state.alert = .error(failure)
+    }
+    return .none
+
+  case .didExport(let failure):
+    state.isExporting = false
+    state.exportData = nil
+    if let failure {
+      state.alert = .error(failure)
+    }
+    return .none
+
+  case .binding(_):
+    return .none
+  }
+}
+.binding()
diff --git a/Examples/xx-messenger/Sources/BackupFeature/BackupView.swift b/Examples/xx-messenger/Sources/BackupFeature/BackupView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..89510b2fbf2c993afa1438fd62408dc925d1c30e
--- /dev/null
+++ b/Examples/xx-messenger/Sources/BackupFeature/BackupView.swift
@@ -0,0 +1,250 @@
+import ComposableArchitecture
+import SwiftUI
+import UniformTypeIdentifiers
+
+public struct BackupView: View {
+  public init(store: Store<BackupState, BackupAction>) {
+    self.store = store
+  }
+
+  let store: Store<BackupState, BackupAction>
+  @FocusState var focusedField: BackupState.Field?
+
+  struct ViewState: Equatable {
+    struct Backup: Equatable {
+      var date: Date
+      var size: Int
+    }
+
+    init(state: BackupState) {
+      isRunning = state.isRunning
+      isStarting = state.isStarting
+      isResuming = state.isResuming
+      isStopping = state.isStopping
+      backup = state.backup.map { backup in
+        Backup(date: backup.date, size: backup.data.count)
+      }
+      focusedField = state.focusedField
+      passphrase = state.passphrase
+      isExporting = state.isExporting
+      exportData = state.exportData
+    }
+
+    var isRunning: Bool
+    var isStarting: Bool
+    var isResuming: Bool
+    var isStopping: Bool
+    var isLoading: Bool { isStarting || isResuming || isStopping }
+    var backup: Backup?
+    var focusedField: BackupState.Field?
+    var passphrase: String
+    var isExporting: Bool
+    var exportData: Data?
+  }
+
+  public var body: some View {
+    WithViewStore(store, observe: ViewState.init) { viewStore in
+      Form {
+        Group {
+          if viewStore.isRunning || viewStore.backup != nil {
+            backupSection(viewStore)
+          }
+          if !viewStore.isRunning {
+            newBackupSection(viewStore)
+          }
+        }
+        .disabled(viewStore.isLoading)
+        .alert(
+          store.scope(state: \.alert),
+          dismiss: .alertDismissed
+        )
+      }
+      .navigationTitle("Backup")
+      .task { await viewStore.send(.task).finish() }
+      .onChange(of: viewStore.focusedField) { focusedField = $0 }
+      .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) }
+    }
+  }
+
+  @ViewBuilder func newBackupSection(
+    _ viewStore: ViewStore<ViewState, BackupAction>
+  ) -> some View {
+    Section {
+      SecureField(
+        text: viewStore.binding(
+          get: \.passphrase,
+          send: { .set(\.$passphrase, $0) }
+        ),
+        prompt: Text("Backup passphrase"),
+        label: { Text("Backup passphrase") }
+      )
+      .textContentType(.password)
+      .textInputAutocapitalization(.never)
+      .disableAutocorrection(true)
+      .focused($focusedField, equals: .passphrase)
+
+      Button {
+        viewStore.send(.startTapped)
+      } label: {
+        HStack {
+          Text("Start")
+          Spacer()
+          if viewStore.isStarting {
+            ProgressView()
+          } else {
+            Image(systemName: "play.fill")
+          }
+        }
+      }
+    } header: {
+      Text("New backup")
+    }
+    .disabled(viewStore.isStarting)
+  }
+
+  @ViewBuilder func backupSection(
+    _ viewStore: ViewStore<ViewState, BackupAction>
+  ) -> some View {
+    Section {
+      backupView(viewStore)
+      stopView(viewStore)
+      resumeView(viewStore)
+    } header: {
+      Text("Backup")
+    }
+  }
+
+  @ViewBuilder func backupView(
+    _ viewStore: ViewStore<ViewState, BackupAction>
+  ) -> some View {
+    if let backup = viewStore.backup {
+      HStack {
+        Text("Date")
+        Spacer()
+        Text(backup.date.formatted())
+      }
+      HStack {
+        Text("Size")
+        Spacer()
+        Text(format(bytesCount: backup.size))
+      }
+      Button {
+        viewStore.send(.exportTapped)
+      } label: {
+        HStack {
+          Text("Export")
+          Spacer()
+          if viewStore.isExporting {
+            ProgressView()
+          } else {
+            Image(systemName: "square.and.arrow.up")
+          }
+        }
+      }
+      .disabled(viewStore.isExporting)
+      .fileExporter(
+        isPresented: viewStore.binding(
+          get: \.isExporting,
+          send: { .set(\.$isExporting, $0) }
+        ),
+        document: viewStore.exportData.map(ExportedDocument.init(data:)),
+        contentType: .data,
+        defaultFilename: "backup.xxm",
+        onCompletion: { result in
+          switch result {
+          case .success(_):
+            viewStore.send(.didExport(failure: nil))
+          case .failure(let error):
+            viewStore.send(.didExport(failure: error as NSError?))
+          }
+        }
+      )
+    } else {
+      Text("No backup")
+    }
+  }
+
+  @ViewBuilder func stopView(
+    _ viewStore: ViewStore<ViewState, BackupAction>
+  ) -> some View {
+    if viewStore.isRunning {
+      Button {
+        viewStore.send(.stopTapped)
+      } label: {
+        HStack {
+          Text("Stop")
+          Spacer()
+          if viewStore.isStopping {
+            ProgressView()
+          } else {
+            Image(systemName: "stop.fill")
+          }
+        }
+      }
+    }
+  }
+
+  @ViewBuilder func resumeView(
+    _ viewStore: ViewStore<ViewState, BackupAction>
+  ) -> some View {
+    if !viewStore.isRunning, viewStore.backup != nil {
+      Button {
+        viewStore.send(.resumeTapped)
+      } label: {
+        HStack {
+          Text("Resume")
+          Spacer()
+          if viewStore.isResuming {
+            ProgressView()
+          } else {
+            Image(systemName: "playpause.fill")
+          }
+        }
+      }
+    }
+  }
+
+  func format(bytesCount bytes: Int) -> String {
+    let formatter = ByteCountFormatter()
+    formatter.allowedUnits = [.useMB, .useKB]
+    formatter.countStyle = .binary
+    return formatter.string(fromByteCount: Int64(bytes))
+  }
+}
+
+private struct ExportedDocument: FileDocument {
+  enum Error: Swift.Error {
+    case notAvailable
+  }
+
+  static var readableContentTypes: [UTType] = []
+  static var writableContentTypes: [UTType] = [.data]
+
+  var data: Data
+
+  init(data: Data) {
+    self.data = data
+  }
+
+  init(configuration: ReadConfiguration) throws {
+    throw Error.notAvailable
+  }
+
+  func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
+    FileWrapper(regularFileWithContents: data)
+  }
+}
+
+#if DEBUG
+public struct BackupView_Previews: PreviewProvider {
+  public static var previews: some View {
+    NavigationView {
+      BackupView(store: Store(
+        initialState: BackupState(),
+        reducer: .empty,
+        environment: ()
+      ))
+    }
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
index 993796e4bf4f286a29fdb7319fb39332074c68da..be66fc8dfc8f59f7f2e20e1a1899a23da10106f6 100644
--- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift
@@ -4,7 +4,9 @@ import CheckContactAuthFeature
 import ComposableArchitecture
 import ComposablePresentation
 import ConfirmRequestFeature
+import ContactLookupFeature
 import Foundation
+import ResetAuthFeature
 import SendRequestFeature
 import VerifyContactFeature
 import XCTestDynamicOverlay
@@ -20,10 +22,12 @@ public struct ContactState: Equatable {
     importUsername: Bool = true,
     importEmail: Bool = true,
     importPhone: Bool = true,
+    lookup: ContactLookupState? = nil,
     sendRequest: SendRequestState? = nil,
     verifyContact: VerifyContactState? = nil,
     confirmRequest: ConfirmRequestState? = nil,
     checkAuth: CheckContactAuthState? = nil,
+    resetAuth: ResetAuthState? = nil,
     chat: ChatState? = nil
   ) {
     self.id = id
@@ -32,10 +36,12 @@ public struct ContactState: Equatable {
     self.importUsername = importUsername
     self.importEmail = importEmail
     self.importPhone = importPhone
+    self.lookup = lookup
     self.sendRequest = sendRequest
     self.verifyContact = verifyContact
     self.confirmRequest = confirmRequest
     self.checkAuth = checkAuth
+    self.resetAuth = resetAuth
     self.chat = chat
   }
 
@@ -45,10 +51,12 @@ public struct ContactState: Equatable {
   @BindableState public var importUsername: Bool
   @BindableState public var importEmail: Bool
   @BindableState public var importPhone: Bool
+  public var lookup: ContactLookupState?
   public var sendRequest: SendRequestState?
   public var verifyContact: VerifyContactState?
   public var confirmRequest: ConfirmRequestState?
   public var checkAuth: CheckContactAuthState?
+  public var resetAuth: ResetAuthState?
   public var chat: ChatState?
 }
 
@@ -56,6 +64,9 @@ public enum ContactAction: Equatable, BindableAction {
   case start
   case dbContactFetched(XXModels.Contact?)
   case importFactsTapped
+  case lookupTapped
+  case lookupDismissed
+  case lookup(ContactLookupAction)
   case sendRequestTapped
   case sendRequestDismissed
   case sendRequest(SendRequestAction)
@@ -68,6 +79,9 @@ public enum ContactAction: Equatable, BindableAction {
   case confirmRequestTapped
   case confirmRequestDismissed
   case confirmRequest(ConfirmRequestAction)
+  case resetAuthTapped
+  case resetAuthDismissed
+  case resetAuth(ResetAuthAction)
   case chatTapped
   case chatDismissed
   case chat(ChatAction)
@@ -80,20 +94,24 @@ public struct ContactEnvironment {
     db: DBManagerGetDB,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
+    lookup: @escaping () -> ContactLookupEnvironment,
     sendRequest: @escaping () -> SendRequestEnvironment,
     verifyContact: @escaping () -> VerifyContactEnvironment,
     confirmRequest: @escaping () -> ConfirmRequestEnvironment,
     checkAuth: @escaping () -> CheckContactAuthEnvironment,
+    resetAuth: @escaping () -> ResetAuthEnvironment,
     chat: @escaping () -> ChatEnvironment
   ) {
     self.messenger = messenger
     self.db = db
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
+    self.lookup = lookup
     self.sendRequest = sendRequest
     self.verifyContact = verifyContact
     self.confirmRequest = confirmRequest
     self.checkAuth = checkAuth
+    self.resetAuth = resetAuth
     self.chat = chat
   }
 
@@ -101,10 +119,12 @@ public struct ContactEnvironment {
   public var db: DBManagerGetDB
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
+  public var lookup: () -> ContactLookupEnvironment
   public var sendRequest: () -> SendRequestEnvironment
   public var verifyContact: () -> VerifyContactEnvironment
   public var confirmRequest: () -> ConfirmRequestEnvironment
   public var checkAuth: () -> CheckContactAuthEnvironment
+  public var resetAuth: () -> ResetAuthEnvironment
   public var chat: () -> ChatEnvironment
 }
 
@@ -115,10 +135,12 @@ extension ContactEnvironment {
     db: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
+    lookup: { .unimplemented },
     sendRequest: { .unimplemented },
     verifyContact: { .unimplemented },
     confirmRequest: { .unimplemented },
     checkAuth: { .unimplemented },
+    resetAuth: { .unimplemented },
     chat: { .unimplemented }
   )
 }
@@ -163,6 +185,19 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
     .receive(on: env.mainQueue)
     .eraseToEffect()
 
+  case .lookupTapped:
+    state.lookup = ContactLookupState(id: state.id)
+    return .none
+
+  case .lookupDismissed:
+    state.lookup = nil
+    return .none
+
+  case .lookup(.didLookup(let xxContact)):
+    state.xxContact = xxContact
+    state.lookup = nil
+    return .none
+
   case .sendRequestTapped:
     if let xxContact = state.xxContact {
       state.sendRequest = SendRequestState(contact: xxContact)
@@ -223,11 +258,32 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
     state.chat = nil
     return .none
 
-  case .binding(_), .sendRequest(_), .verifyContact(_), .confirmRequest(_), .checkAuth(_), .chat(_):
+  case .resetAuthTapped:
+    if let marshaled = state.dbContact?.marshaled {
+      state.resetAuth = ResetAuthState(
+        partner: .live(marshaled)
+      )
+    }
+    return .none
+
+  case .resetAuthDismissed:
+    state.resetAuth = nil
+    return .none
+
+  case .binding(_), .lookup(_), .sendRequest(_),
+      .verifyContact(_), .confirmRequest(_),
+      .checkAuth(_), .resetAuth(_), .chat(_):
     return .none
   }
 }
 .binding()
+.presenting(
+  contactLookupReducer,
+  state: .keyPath(\.lookup),
+  id: .notNil(),
+  action: /ContactAction.lookup,
+  environment: { $0.lookup() }
+)
 .presenting(
   sendRequestReducer,
   state: .keyPath(\.sendRequest),
@@ -256,6 +312,13 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm
   action: /ContactAction.checkAuth,
   environment: { $0.checkAuth() }
 )
+.presenting(
+  resetAuthReducer,
+  state: .keyPath(\.resetAuth),
+  id: .notNil(),
+  action: /ContactAction.resetAuth,
+  environment: { $0.resetAuth() }
+)
 .presenting(
   chatReducer,
   state: .keyPath(\.chat),
diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
index d775df01eeed7ee6175b00d231149a351be84882..7da763e03748536aabb0cc99698c2edd0ee0fba4 100644
--- a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
+++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift
@@ -4,6 +4,8 @@ import CheckContactAuthFeature
 import ComposableArchitecture
 import ComposablePresentation
 import ConfirmRequestFeature
+import ContactLookupFeature
+import ResetAuthFeature
 import SendRequestFeature
 import SwiftUI
 import VerifyContactFeature
@@ -26,6 +28,12 @@ public struct ContactView: View {
     var importUsername: Bool
     var importEmail: Bool
     var importPhone: Bool
+    var canLookup: Bool
+    var canSendRequest: Bool
+    var canVerifyContact: Bool
+    var canConfirmRequest: Bool
+    var canCheckAuthorization: Bool
+    var canResetAuthorization: Bool
 
     init(state: ContactState) {
       dbContact = state.dbContact
@@ -36,6 +44,12 @@ public struct ContactView: View {
       importUsername = state.importUsername
       importEmail = state.importEmail
       importPhone = state.importPhone
+      canLookup = state.dbContact?.id != nil
+      canSendRequest = state.xxContact != nil || state.dbContact?.marshaled != nil
+      canVerifyContact = state.dbContact?.marshaled != nil
+      canConfirmRequest = state.dbContact?.marshaled != nil
+      canCheckAuthorization = state.dbContact?.marshaled != nil
+      canResetAuthorization = state.dbContact?.marshaled != nil
     }
   }
 
@@ -100,15 +114,30 @@ public struct ContactView: View {
 
         if let dbContact = viewStore.dbContact {
           Section {
+            Label(dbContact.id.hexString(), systemImage: "number")
+              .font(.footnote.monospaced())
             Label(dbContact.username ?? "", systemImage: "person")
             Label(dbContact.email ?? "", systemImage: "envelope")
             Label(dbContact.phone ?? "", systemImage: "phone")
           } header: {
             Text("Contact")
           }
+          .textSelection(.enabled)
 
           Section {
             ContactAuthStatusView(dbContact.authStatus)
+
+            Button {
+              viewStore.send(.lookupTapped)
+            } label: {
+              HStack {
+                Text("Lookup")
+                Spacer()
+                Image(systemName: "chevron.forward")
+              }
+            }
+            .disabled(!viewStore.canLookup)
+
             Button {
               viewStore.send(.sendRequestTapped)
             } label: {
@@ -118,6 +147,8 @@ public struct ContactView: View {
                 Image(systemName: "chevron.forward")
               }
             }
+            .disabled(!viewStore.canSendRequest)
+
             Button {
               viewStore.send(.verifyContactTapped)
             } label: {
@@ -127,6 +158,8 @@ public struct ContactView: View {
                 Image(systemName: "chevron.forward")
               }
             }
+            .disabled(!viewStore.canVerifyContact)
+
             Button {
               viewStore.send(.confirmRequestTapped)
             } label: {
@@ -136,6 +169,8 @@ public struct ContactView: View {
                 Image(systemName: "chevron.forward")
               }
             }
+            .disabled(!viewStore.canConfirmRequest)
+
             Button {
               viewStore.send(.checkAuthTapped)
             } label: {
@@ -145,6 +180,18 @@ public struct ContactView: View {
                 Image(systemName: "chevron.forward")
               }
             }
+            .disabled(!viewStore.canCheckAuthorization)
+
+            Button {
+              viewStore.send(.resetAuthTapped)
+            } label: {
+              HStack {
+                Text("Reset authorization")
+                Spacer()
+                Image(systemName: "chevron.forward")
+              }
+            }
+            .disabled(!viewStore.canResetAuthorization)
           } header: {
             Text("Auth")
           }
@@ -167,6 +214,15 @@ public struct ContactView: View {
       }
       .navigationTitle("Contact")
       .task { viewStore.send(.start) }
+      .background(NavigationLinkWithStore(
+        store.scope(
+          state: \.lookup,
+          action: ContactAction.lookup
+        ),
+        mapState: replayNonNil(),
+        onDeactivate: { viewStore.send(.lookupDismissed) },
+        destination: ContactLookupView.init(store:)
+      ))
       .background(NavigationLinkWithStore(
         store.scope(
           state: \.sendRequest,
@@ -200,6 +256,14 @@ public struct ContactView: View {
         onDeactivate: { viewStore.send(.checkAuthDismissed) },
         destination: CheckContactAuthView.init(store:)
       ))
+      .background(NavigationLinkWithStore(
+        store.scope(
+          state: \.resetAuth,
+          action: ContactAction.resetAuth
+        ),
+        onDeactivate: { viewStore.send(.resetAuthDismissed) },
+        destination: ResetAuthView.init(store:)
+      ))
       .background(NavigationLinkWithStore(
         store.scope(
           state: \.chat,
diff --git a/Examples/xx-messenger/Sources/ContactLookupFeature/ContactLookupFeature.swift b/Examples/xx-messenger/Sources/ContactLookupFeature/ContactLookupFeature.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0b6f92dd46b351f6d10f78ef4b50961dad3e1fae
--- /dev/null
+++ b/Examples/xx-messenger/Sources/ContactLookupFeature/ContactLookupFeature.swift
@@ -0,0 +1,83 @@
+import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+
+public struct ContactLookupState: Equatable {
+  public init(
+    id: Data,
+    isLookingUp: Bool = false,
+    failure: String? = nil
+  ) {
+    self.id = id
+    self.isLookingUp = isLookingUp
+    self.failure = failure
+  }
+
+  public var id: Data
+  public var isLookingUp: Bool
+  public var failure: String?
+}
+
+public enum ContactLookupAction: Equatable {
+  case lookupTapped
+  case didLookup(XXClient.Contact)
+  case didFail(NSError)
+}
+
+public struct ContactLookupEnvironment {
+  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 ContactLookupEnvironment {
+  public static let unimplemented = ContactLookupEnvironment(
+    messenger: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
+}
+#endif
+
+public let contactLookupReducer = Reducer<ContactLookupState, ContactLookupAction, ContactLookupEnvironment>
+{ state, action, env in
+  switch action {
+  case .lookupTapped:
+    state.isLookingUp = true
+    state.failure = nil
+    return Effect.result { [state] in
+      do {
+        let contact = try env.messenger.lookupContact(id: state.id)
+        return .success(.didLookup(contact))
+      } catch {
+        return .success(.didFail(error as NSError))
+      }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .didLookup(_):
+    state.isLookingUp = false
+    state.failure = nil
+    return .none
+
+  case .didFail(let error):
+    state.isLookingUp = false
+    state.failure = error.localizedDescription
+    return .none
+  }
+}
diff --git a/Examples/xx-messenger/Sources/ContactLookupFeature/ContactLookupView.swift b/Examples/xx-messenger/Sources/ContactLookupFeature/ContactLookupView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6ce83eda6ba5dacfcf15c2fca4e8c202087ef42d
--- /dev/null
+++ b/Examples/xx-messenger/Sources/ContactLookupFeature/ContactLookupView.swift
@@ -0,0 +1,76 @@
+import AppCore
+import ComposableArchitecture
+import SwiftUI
+
+public struct ContactLookupView: View {
+  public init(store: Store<ContactLookupState, ContactLookupAction>) {
+    self.store = store
+  }
+
+  let store: Store<ContactLookupState, ContactLookupAction>
+
+  struct ViewState: Equatable {
+    init(state: ContactLookupState) {
+      id = state.id
+      isLookingUp = state.isLookingUp
+      failure = state.failure
+    }
+
+    var id: Data
+    var isLookingUp: Bool
+    var failure: String?
+  }
+
+  public var body: some View {
+    WithViewStore(store, observe: ViewState.init) { viewStore in
+      Form {
+        Section {
+          Label(viewStore.id.hexString(), systemImage: "number")
+            .font(.footnote.monospaced())
+
+          Button {
+            viewStore.send(.lookupTapped)
+          } label: {
+            HStack {
+              Text("Lookup")
+              Spacer()
+              if viewStore.isLookingUp {
+                ProgressView()
+              } else {
+                Image(systemName: "magnifyingglass")
+              }
+            }
+          }
+          .disabled(viewStore.isLookingUp)
+        } header: {
+          Text("Contact ID")
+        }
+
+        if let failure = viewStore.failure {
+          Section {
+            Text(failure)
+          } header: {
+            Text("Error")
+          }
+        }
+      }
+      .navigationTitle("Lookup")
+    }
+  }
+}
+
+#if DEBUG
+public struct ContactLookupView_Previews: PreviewProvider {
+  public static var previews: some View {
+    NavigationView {
+      ContactLookupView(store: Store(
+        initialState: ContactLookupState(
+          id: "1234".data(using: .utf8)!
+        ),
+        reducer: .empty,
+        environment: ()
+      ))
+    }
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift
index 1ded89de4cbf9dd44014395e3e79bf6ff2fdcc20..680a231ec8cc9e6e13487a849a65941bda7a7428 100644
--- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift
+++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift
@@ -3,6 +3,7 @@ import ComposableArchitecture
 import ComposablePresentation
 import ContactFeature
 import Foundation
+import MyContactFeature
 import XCTestDynamicOverlay
 import XXClient
 import XXMessengerClient
@@ -12,16 +13,19 @@ public struct ContactsState: Equatable {
   public init(
     myId: Data? = nil,
     contacts: IdentifiedArrayOf<XXModels.Contact> = [],
-    contact: ContactState? = nil
+    contact: ContactState? = nil,
+    myContact: MyContactState? = nil
   ) {
     self.myId = myId
     self.contacts = contacts
     self.contact = contact
+    self.myContact = myContact
   }
 
   public var myId: Data?
   public var contacts: IdentifiedArrayOf<XXModels.Contact>
   public var contact: ContactState?
+  public var myContact: MyContactState?
 }
 
 public enum ContactsAction: Equatable {
@@ -30,6 +34,9 @@ public enum ContactsAction: Equatable {
   case contactSelected(XXModels.Contact)
   case contactDismissed
   case contact(ContactAction)
+  case myContactSelected
+  case myContactDismissed
+  case myContact(MyContactAction)
 }
 
 public struct ContactsEnvironment {
@@ -38,13 +45,15 @@ public struct ContactsEnvironment {
     db: DBManagerGetDB,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
-    contact: @escaping () -> ContactEnvironment
+    contact: @escaping () -> ContactEnvironment,
+    myContact: @escaping () -> MyContactEnvironment
   ) {
     self.messenger = messenger
     self.db = db
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
     self.contact = contact
+    self.myContact = myContact
   }
 
   public var messenger: Messenger
@@ -52,6 +61,7 @@ public struct ContactsEnvironment {
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
   public var contact: () -> ContactEnvironment
+  public var myContact: () -> MyContactEnvironment
 }
 
 #if DEBUG
@@ -61,7 +71,8 @@ extension ContactsEnvironment {
     db: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
-    contact: { .unimplemented }
+    contact: { .unimplemented },
+    myContact: { .unimplemented }
   )
 }
 #endif
@@ -96,7 +107,15 @@ public let contactsReducer = Reducer<ContactsState, ContactsAction, ContactsEnvi
     state.contact = nil
     return .none
 
-  case .contact(_):
+  case .myContactSelected:
+    state.myContact = MyContactState()
+    return .none
+
+  case .myContactDismissed:
+    state.myContact = nil
+    return .none
+
+  case .contact(_), .myContact(_):
     return .none
   }
 }
@@ -107,3 +126,10 @@ public let contactsReducer = Reducer<ContactsState, ContactsAction, ContactsEnvi
   action: /ContactsAction.contact,
   environment: { $0.contact() }
 )
+.presenting(
+  myContactReducer,
+  state: .keyPath(\.myContact),
+  id: .notNil(),
+  action: /ContactsAction.myContact,
+  environment: { $0.myContact() }
+)
diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift
index ce811f9d7bab2d22cb530cc6f2725be004a603e4..e09725d92c84feeb7f8cb7da5dbc38cf9d998087 100644
--- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift
+++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift
@@ -2,6 +2,7 @@ import AppCore
 import ComposableArchitecture
 import ComposablePresentation
 import ContactFeature
+import MyContactFeature
 import SwiftUI
 import XXModels
 
@@ -28,13 +29,21 @@ public struct ContactsView: View {
         ForEach(viewStore.contacts) { contact in
           if contact.id == viewStore.myId {
             Section {
-              VStack(alignment: .leading, spacing: 8) {
-                Label(contact.username ?? "", systemImage: "person")
-                Label(contact.email ?? "", systemImage: "envelope")
-                Label(contact.phone ?? "", systemImage: "phone")
+              Button {
+                viewStore.send(.myContactSelected)
+              } label: {
+                HStack {
+                  VStack(alignment: .leading, spacing: 8) {
+                    Label(contact.username ?? "", systemImage: "person")
+                    Label(contact.email ?? "", systemImage: "envelope")
+                    Label(contact.phone ?? "", systemImage: "phone")
+                  }
+                  .font(.callout)
+                  .tint(Color.primary)
+                  Spacer()
+                  Image(systemName: "chevron.forward")
+                }
               }
-              .font(.callout)
-              .tint(Color.primary)
             } header: {
               Text("My contact")
             }
@@ -70,6 +79,14 @@ public struct ContactsView: View {
         onDeactivate: { viewStore.send(.contactDismissed) },
         destination: ContactView.init(store:)
       ))
+      .background(NavigationLinkWithStore(
+        store.scope(
+          state: \.myContact,
+          action: ContactsAction.myContact
+        ),
+        onDeactivate: { viewStore.send(.myContactDismissed) },
+        destination: MyContactView.init(store:)
+      ))
     }
   }
 }
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 894c5aca32ca1170d0acd47ae89a8242f2e73cb8..f2015ebc2434ecaada1ba9abd4d345cea91bd03e 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -1,4 +1,5 @@
 import AppCore
+import BackupFeature
 import Combine
 import ComposableArchitecture
 import ComposablePresentation
@@ -14,30 +15,26 @@ import XXModels
 public struct HomeState: Equatable {
   public init(
     failure: String? = nil,
-    authFailure: String? = nil,
-    messageListenerFailure: String? = nil,
     isNetworkHealthy: Bool? = nil,
     networkNodesReport: NodeRegistrationReport? = nil,
     isDeletingAccount: Bool = false,
     alert: AlertState<HomeAction>? = nil,
     register: RegisterState? = nil,
     contacts: ContactsState? = nil,
-    userSearch: UserSearchState? = nil
+    userSearch: UserSearchState? = nil,
+    backup: BackupState? = nil
   ) {
     self.failure = failure
-    self.authFailure = authFailure
-    self.messageListenerFailure = messageListenerFailure
     self.isNetworkHealthy = isNetworkHealthy
     self.isDeletingAccount = isDeletingAccount
     self.alert = alert
     self.register = register
     self.contacts = contacts
     self.userSearch = userSearch
+    self.backup = backup
   }
 
   public var failure: String?
-  public var authFailure: String?
-  public var messageListenerFailure: String?
   public var isNetworkHealthy: Bool?
   public var networkNodesReport: NodeRegistrationReport?
   public var isDeletingAccount: Bool
@@ -45,6 +42,7 @@ public struct HomeState: Equatable {
   public var register: RegisterState?
   public var contacts: ContactsState?
   public var userSearch: UserSearchState?
+  public var backup: BackupState?
 }
 
 public enum HomeAction: Equatable {
@@ -55,20 +53,6 @@ public enum HomeAction: Equatable {
     case failure(NSError)
   }
 
-  public enum AuthHandler: Equatable {
-    case start
-    case stop
-    case failure(NSError)
-    case failureDismissed
-  }
-
-  public enum MessageListener: Equatable {
-    case start
-    case stop
-    case failure(NSError)
-    case failureDismissed
-  }
-
   public enum NetworkMonitor: Equatable {
     case start
     case stop
@@ -84,8 +68,6 @@ public enum HomeAction: Equatable {
   }
 
   case messenger(Messenger)
-  case authHandler(AuthHandler)
-  case messageListener(MessageListener)
   case networkMonitor(NetworkMonitor)
   case deleteAccount(DeleteAccount)
   case didDismissAlert
@@ -94,71 +76,68 @@ public enum HomeAction: Equatable {
   case didDismissUserSearch
   case contactsButtonTapped
   case didDismissContacts
+  case backupButtonTapped
+  case didDismissBackup
   case register(RegisterAction)
   case contacts(ContactsAction)
   case userSearch(UserSearchAction)
+  case backup(BackupAction)
 }
 
 public struct HomeEnvironment {
   public init(
     messenger: Messenger,
     dbManager: DBManager,
-    authHandler: AuthCallbackHandler,
-    messageListener: MessageListenerHandler,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
     register: @escaping () -> RegisterEnvironment,
     contacts: @escaping () -> ContactsEnvironment,
-    userSearch: @escaping () -> UserSearchEnvironment
+    userSearch: @escaping () -> UserSearchEnvironment,
+    backup: @escaping () -> BackupEnvironment
   ) {
     self.messenger = messenger
     self.dbManager = dbManager
-    self.authHandler = authHandler
-    self.messageListener = messageListener
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
     self.register = register
     self.contacts = contacts
     self.userSearch = userSearch
+    self.backup = backup
   }
 
   public var messenger: Messenger
   public var dbManager: DBManager
-  public var authHandler: AuthCallbackHandler
-  public var messageListener: MessageListenerHandler
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
   public var register: () -> RegisterEnvironment
   public var contacts: () -> ContactsEnvironment
   public var userSearch: () -> UserSearchEnvironment
+  public var backup: () -> BackupEnvironment
 }
 
+#if DEBUG
 extension HomeEnvironment {
   public static let unimplemented = HomeEnvironment(
     messenger: .unimplemented,
     dbManager: .unimplemented,
-    authHandler: .unimplemented,
-    messageListener: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
     register: { .unimplemented },
     contacts: { .unimplemented },
-    userSearch: { .unimplemented }
+    userSearch: { .unimplemented },
+    backup: { .unimplemented }
   )
 }
+#endif
 
 public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 { state, action, env in
   enum NetworkHealthEffectId {}
   enum NetworkNodesEffectId {}
-  enum AuthCallbacksEffectId {}
-  enum MessageListenerEffectId {}
 
   switch action {
   case .messenger(.start):
     return .merge(
-      Effect(value: .authHandler(.start)),
-      Effect(value: .messageListener(.start)),
       Effect(value: .networkMonitor(.stop)),
       Effect.result {
         do {
@@ -166,6 +145,9 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 
           if env.messenger.isConnected() == false {
             try env.messenger.connect()
+          }
+
+          if env.messenger.isListeningForMessages() == false {
             try env.messenger.listenForMessages()
           }
 
@@ -176,6 +158,10 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
             try env.messenger.logIn()
           }
 
+          if !env.messenger.isBackupRunning() {
+            try? env.messenger.resumeBackup()
+          }
+
           return .success(.messenger(.didStartRegistered))
         } catch {
           return .success(.messenger(.failure(error as NSError)))
@@ -197,52 +183,6 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     state.failure = error.localizedDescription
     return .none
 
-  case .authHandler(.start):
-    return Effect.run { subscriber in
-      let cancellable = env.authHandler(onError: { error in
-        subscriber.send(.authHandler(.failure(error as NSError)))
-      })
-      return AnyCancellable { cancellable.cancel() }
-    }
-    .subscribe(on: env.bgQueue)
-    .receive(on: env.mainQueue)
-    .eraseToEffect()
-    .cancellable(id: AuthCallbacksEffectId.self, cancelInFlight: true)
-
-  case .authHandler(.stop):
-    return .cancel(id: AuthCallbacksEffectId.self)
-
-  case .authHandler(.failure(let error)):
-    state.authFailure = error.localizedDescription
-    return .none
-
-  case .authHandler(.failureDismissed):
-    state.authFailure = nil
-    return .none
-
-  case .messageListener(.start):
-    return Effect.run { subscriber in
-      let cancellable = env.messageListener(onError: { error in
-        subscriber.send(.messageListener(.failure(error as NSError)))
-      })
-      return AnyCancellable { cancellable.cancel() }
-    }
-    .subscribe(on: env.bgQueue)
-    .receive(on: env.mainQueue)
-    .eraseToEffect()
-    .cancellable(id: MessageListenerEffectId.self, cancelInFlight: true)
-
-  case .messageListener(.stop):
-    return .cancel(id: MessageListenerEffectId.self)
-
-  case .messageListener(.failure(let error)):
-    state.messageListenerFailure = error.localizedDescription
-    return .none
-
-  case .messageListener(.failureDismissed):
-    state.messageListenerFailure = nil
-    return .none
-
   case .networkMonitor(.start):
     return .merge(
       Effect.run { subscriber in
@@ -344,7 +284,15 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     state.register = nil
     return Effect(value: .messenger(.start))
 
-  case .register(_), .contacts(_), .userSearch(_):
+  case .backupButtonTapped:
+    state.backup = BackupState()
+    return .none
+
+  case .didDismissBackup:
+    state.backup = nil
+    return .none
+
+  case .register(_), .contacts(_), .userSearch(_), .backup(_):
     return .none
   }
 }
@@ -369,3 +317,10 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
   action: /HomeAction.userSearch,
   environment: { $0.userSearch() }
 )
+.presenting(
+  backupReducer,
+  state: .keyPath(\.backup),
+  id: .notNil(),
+  action: /HomeAction.backup,
+  environment: { $0.backup() }
+)
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index 8cd7259b6c57a7054b1bae658df8772b0a7efa3d..8a1775d6a84ffdfc6b5ac1b2b816a8ee686b039c 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -1,3 +1,5 @@
+import AppCore
+import BackupFeature
 import ComposableArchitecture
 import ComposablePresentation
 import ContactsFeature
@@ -15,16 +17,12 @@ public struct HomeView: View {
 
   struct ViewState: Equatable {
     var failure: String?
-    var authFailure: String?
-    var messageListenerFailure: String?
     var isNetworkHealthy: Bool?
     var networkNodesReport: NodeRegistrationReport?
     var isDeletingAccount: Bool
 
     init(state: HomeState) {
       failure = state.failure
-      authFailure = state.authFailure
-      messageListenerFailure = state.messageListenerFailure
       isNetworkHealthy = state.isNetworkHealthy
       isDeletingAccount = state.isDeletingAccount
       networkNodesReport = state.networkNodesReport
@@ -48,32 +46,6 @@ public struct HomeView: View {
             }
           }
 
-          if let authFailure = viewStore.authFailure {
-            Section {
-              Text(authFailure)
-              Button {
-                viewStore.send(.authHandler(.failureDismissed))
-              } label: {
-                Text("Dismiss")
-              }
-            } header: {
-              Text("Auth Error")
-            }
-          }
-
-          if let messageListenerFailure = viewStore.messageListenerFailure {
-            Section {
-              Text(messageListenerFailure)
-              Button {
-                viewStore.send(.messageListener(.failureDismissed))
-              } label: {
-                Text("Dismiss")
-              }
-            } header: {
-              Text("Message Listener Error")
-            }
-          }
-
           Section {
             HStack {
               Text("Health")
@@ -141,6 +113,16 @@ public struct HomeView: View {
           }
 
           Section {
+            Button {
+              viewStore.send(.backupButtonTapped)
+            } label: {
+              HStack {
+                Text("Backup")
+                Spacer()
+                Image(systemName: "chevron.forward")
+              }
+            }
+
             Button(role: .destructive) {
               viewStore.send(.deleteAccount(.buttonTapped))
             } label: {
@@ -156,6 +138,12 @@ public struct HomeView: View {
           } header: {
             Text("Account")
           }
+
+          Section {
+            AppVersionText()
+          } header: {
+            Text("App version")
+          }
         }
         .navigationTitle("Home")
         .alert(
@@ -182,6 +170,16 @@ public struct HomeView: View {
           },
           destination: UserSearchView.init(store:)
         ))
+        .background(NavigationLinkWithStore(
+          store.scope(
+            state: \.backup,
+            action: HomeAction.backup
+          ),
+          onDeactivate: {
+            viewStore.send(.didDismissBackup)
+          },
+          destination: BackupView.init(store:)
+        ))
       }
       .navigationViewStyle(.stack)
       .task { viewStore.send(.messenger(.start)) }
diff --git a/Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift b/Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift
new file mode 100644
index 0000000000000000000000000000000000000000..321139aec18ce7ae51b9be8097878a9133798236
--- /dev/null
+++ b/Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift
@@ -0,0 +1,11 @@
+import ComposableArchitecture
+
+extension AlertState {
+  public static func error(_ message: String) -> AlertState<MyContactAction> {
+    AlertState<MyContactAction>(
+      title: TextState("Error"),
+      message: TextState(message),
+      buttons: []
+    )
+  }
+}
diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift
new file mode 100644
index 0000000000000000000000000000000000000000..434a1aca3c7cdc74b340c54a22558fa67a90a0eb
--- /dev/null
+++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift
@@ -0,0 +1,316 @@
+import AppCore
+import Combine
+import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+
+public struct MyContactState: Equatable {
+  public enum Field: String, Hashable {
+    case email
+    case emailCode
+    case phone
+    case phoneCode
+  }
+
+  public init(
+    contact: XXModels.Contact? = nil,
+    focusedField: Field? = nil,
+    email: String = "",
+    emailConfirmationID: String? = nil,
+    emailConfirmationCode: String = "",
+    isRegisteringEmail: Bool = false,
+    isConfirmingEmail: Bool = false,
+    isUnregisteringEmail: Bool = false,
+    phone: String = "",
+    phoneConfirmationID: String? = nil,
+    phoneConfirmationCode: String = "",
+    isRegisteringPhone: Bool = false,
+    isConfirmingPhone: Bool = false,
+    isUnregisteringPhone: Bool = false,
+    isLoadingFacts: Bool = false,
+    alert: AlertState<MyContactAction>? = nil
+  ) {
+    self.contact = contact
+    self.focusedField = focusedField
+    self.email = email
+    self.emailConfirmationID = emailConfirmationID
+    self.emailConfirmationCode = emailConfirmationCode
+    self.isRegisteringEmail = isRegisteringEmail
+    self.isConfirmingEmail = isConfirmingEmail
+    self.isUnregisteringEmail = isUnregisteringEmail
+    self.phone = phone
+    self.phoneConfirmationID = phoneConfirmationID
+    self.phoneConfirmationCode = phoneConfirmationCode
+    self.isRegisteringPhone = isRegisteringPhone
+    self.isConfirmingPhone = isConfirmingPhone
+    self.isUnregisteringPhone = isUnregisteringPhone
+    self.isLoadingFacts = isLoadingFacts
+    self.alert = alert
+  }
+
+  public var contact: XXModels.Contact?
+  @BindableState public var focusedField: Field?
+  @BindableState public var email: String
+  @BindableState public var emailConfirmationID: String?
+  @BindableState public var emailConfirmationCode: String
+  @BindableState public var isRegisteringEmail: Bool
+  @BindableState public var isConfirmingEmail: Bool
+  @BindableState public var isUnregisteringEmail: Bool
+  @BindableState public var phone: String
+  @BindableState public var phoneConfirmationID: String?
+  @BindableState public var phoneConfirmationCode: String
+  @BindableState public var isRegisteringPhone: Bool
+  @BindableState public var isConfirmingPhone: Bool
+  @BindableState public var isUnregisteringPhone: Bool
+  @BindableState public var isLoadingFacts: Bool
+  public var alert: AlertState<MyContactAction>?
+}
+
+public enum MyContactAction: Equatable, BindableAction {
+  case start
+  case contactFetched(XXModels.Contact?)
+  case registerEmailTapped
+  case confirmEmailTapped
+  case unregisterEmailTapped
+  case registerPhoneTapped
+  case confirmPhoneTapped
+  case unregisterPhoneTapped
+  case loadFactsTapped
+  case didFail(String)
+  case alertDismissed
+  case binding(BindingAction<MyContactState>)
+}
+
+public struct MyContactEnvironment {
+  public init(
+    messenger: Messenger,
+    db: DBManagerGetDB,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>
+  ) {
+    self.messenger = messenger
+    self.db = db
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+  }
+
+  public var messenger: Messenger
+  public var db: DBManagerGetDB
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
+}
+
+#if DEBUG
+extension MyContactEnvironment {
+  public static let unimplemented = MyContactEnvironment(
+    messenger: .unimplemented,
+    db: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
+}
+#endif
+
+public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContactEnvironment>
+{ state, action, env in
+  enum DBFetchEffectID {}
+
+  switch action {
+  case .start:
+    return Effect
+      .catching { try env.messenger.e2e.tryGet().getContact().getId() }
+      .tryMap { try env.db().fetchContactsPublisher(.init(id: [$0])) }
+      .flatMap { $0 }
+      .assertNoFailure()
+      .map(\.first)
+      .map(MyContactAction.contactFetched)
+      .subscribe(on: env.bgQueue)
+      .receive(on: env.mainQueue)
+      .eraseToEffect()
+      .cancellable(id: DBFetchEffectID.self, cancelInFlight: true)
+
+  case .contactFetched(let contact):
+    state.contact = contact
+    return .none
+
+  case .registerEmailTapped:
+    state.focusedField = nil
+    state.isRegisteringEmail = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud = try env.messenger.ud.tryGet()
+        let fact = Fact(type: .email, value: state.email)
+        let confirmationID = try ud.sendRegisterFact(fact)
+        subscriber.send(.set(\.$emailConfirmationID, confirmationID))
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isRegisteringEmail, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .confirmEmailTapped:
+    guard let confirmationID = state.emailConfirmationID else { return .none }
+    state.focusedField = nil
+    state.isConfirmingEmail = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud = try env.messenger.ud.tryGet()
+        try ud.confirmFact(confirmationId: confirmationID, code: state.emailConfirmationCode)
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          dbContact.email = state.email
+          try env.db().saveContact(dbContact)
+        }
+        subscriber.send(.set(\.$email, ""))
+        subscriber.send(.set(\.$emailConfirmationID, nil))
+        subscriber.send(.set(\.$emailConfirmationCode, ""))
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isConfirmingEmail, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .unregisterEmailTapped:
+    guard let email = state.contact?.email else { return .none }
+    state.isUnregisteringEmail = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud: UserDiscovery = try env.messenger.ud.tryGet()
+        let fact = Fact(type: .email, value: email)
+        try ud.removeFact(fact)
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          dbContact.email = nil
+          try env.db().saveContact(dbContact)
+        }
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isUnregisteringEmail, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .registerPhoneTapped:
+    state.focusedField = nil
+    state.isRegisteringPhone = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud = try env.messenger.ud.tryGet()
+        let fact = Fact(type: .phone, value: state.phone)
+        let confirmationID = try ud.sendRegisterFact(fact)
+        subscriber.send(.set(\.$phoneConfirmationID, confirmationID))
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isRegisteringPhone, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .confirmPhoneTapped:
+    guard let confirmationID = state.phoneConfirmationID else { return .none }
+    state.focusedField = nil
+    state.isConfirmingPhone = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud = try env.messenger.ud.tryGet()
+        try ud.confirmFact(confirmationId: confirmationID, code: state.phoneConfirmationCode)
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          dbContact.phone = state.phone
+          try env.db().saveContact(dbContact)
+        }
+        subscriber.send(.set(\.$phone, ""))
+        subscriber.send(.set(\.$phoneConfirmationID, nil))
+        subscriber.send(.set(\.$phoneConfirmationCode, ""))
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isConfirmingPhone, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .unregisterPhoneTapped:
+    guard let phone = state.contact?.phone else { return .none }
+    state.isUnregisteringPhone = true
+    return Effect.run { [state] subscriber in
+      do {
+        let ud: UserDiscovery = try env.messenger.ud.tryGet()
+        let fact = Fact(type: .phone, value: phone)
+        try ud.removeFact(fact)
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          dbContact.phone = nil
+          try env.db().saveContact(dbContact)
+        }
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isUnregisteringPhone, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .loadFactsTapped:
+    state.isLoadingFacts = true
+    return Effect.run { subscriber in
+      do {
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first {
+          let facts = try env.messenger.ud.tryGet().getFacts()
+          dbContact.username = facts.get(.username)?.value
+          dbContact.email = facts.get(.email)?.value
+          dbContact.phone = facts.get(.phone)?.value
+          try env.db().saveContact(dbContact)
+        }
+      } catch {
+        subscriber.send(.didFail(error.localizedDescription))
+      }
+      subscriber.send(.set(\.$isLoadingFacts, false))
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .didFail(let failure):
+    state.alert = .error(failure)
+    return .none
+
+  case .alertDismissed:
+    state.alert = nil
+    return .none
+
+  case .binding(_):
+    return .none
+  }
+}
+.binding()
diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d32a6f68848e89ad7eeb4a9bf5f25b543d379f40
--- /dev/null
+++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift
@@ -0,0 +1,260 @@
+import AppCore
+import ComposableArchitecture
+import SwiftUI
+import XXModels
+
+public struct MyContactView: View {
+  public init(store: Store<MyContactState, MyContactAction>) {
+    self.store = store
+  }
+
+  let store: Store<MyContactState, MyContactAction>
+  @FocusState var focusedField: MyContactState.Field?
+
+  struct ViewState: Equatable {
+    init(state: MyContactState) {
+      contact = state.contact
+      focusedField = state.focusedField
+      email = state.email
+      emailConfirmation = state.emailConfirmationID != nil
+      emailCode = state.emailConfirmationCode
+      isRegisteringEmail = state.isRegisteringEmail
+      isConfirmingEmail = state.isConfirmingEmail
+      isUnregisteringEmail = state.isUnregisteringEmail
+      phone = state.phone
+      phoneConfirmation = state.phoneConfirmationID != nil
+      phoneCode = state.phoneConfirmationCode
+      isRegisteringPhone = state.isRegisteringPhone
+      isConfirmingPhone = state.isConfirmingPhone
+      isUnregisteringPhone = state.isUnregisteringPhone
+      isLoadingFacts = state.isLoadingFacts
+    }
+
+    var contact: XXModels.Contact?
+    var focusedField: MyContactState.Field?
+    var email: String
+    var emailConfirmation: Bool
+    var emailCode: String
+    var isRegisteringEmail: Bool
+    var isConfirmingEmail: Bool
+    var isUnregisteringEmail: Bool
+    var phone: String
+    var phoneConfirmation: Bool
+    var phoneCode: String
+    var isRegisteringPhone: Bool
+    var isConfirmingPhone: Bool
+    var isUnregisteringPhone: Bool
+    var isLoadingFacts: Bool
+  }
+
+  public var body: some View {
+    WithViewStore(store, observe: ViewState.init) { viewStore in
+      Form {
+        Section {
+          Text(viewStore.contact?.id.hexString() ?? "")
+            .font(.footnote.monospaced())
+            .textSelection(.enabled)
+        } header: {
+          Label("ID", systemImage: "number")
+        }
+
+        Section {
+          Text(viewStore.contact?.username ?? "")
+            .textSelection(.enabled)
+        } header: {
+          Label("Username", systemImage: "person")
+        }
+
+        Section {
+          if let contact = viewStore.contact {
+            if let email = contact.email {
+              Text(email)
+                .textSelection(.enabled)
+              Button(role: .destructive) {
+                viewStore.send(.unregisterEmailTapped)
+              } label: {
+                HStack {
+                  Text("Unregister")
+                  Spacer()
+                  if viewStore.isUnregisteringEmail {
+                    ProgressView()
+                  }
+                }
+              }
+              .disabled(viewStore.isUnregisteringEmail)
+            } else {
+              TextField(
+                text: viewStore.binding(
+                  get: \.email,
+                  send: { MyContactAction.set(\.$email, $0) }
+                ),
+                prompt: Text("Enter email"),
+                label: { Text("Email") }
+              )
+              .focused($focusedField, equals: .email)
+              .textInputAutocapitalization(.never)
+              .disableAutocorrection(true)
+              .disabled(viewStore.isRegisteringEmail || viewStore.emailConfirmation)
+              if viewStore.emailConfirmation {
+                TextField(
+                  text: viewStore.binding(
+                    get: \.emailCode,
+                    send: { MyContactAction.set(\.$emailConfirmationCode, $0) }
+                  ),
+                  prompt: Text("Enter confirmation code"),
+                  label: { Text("Confirmation code") }
+                )
+                .focused($focusedField, equals: .emailCode)
+                .textInputAutocapitalization(.never)
+                .disableAutocorrection(true)
+                .disabled(viewStore.isConfirmingEmail)
+                Button {
+                  viewStore.send(.confirmEmailTapped)
+                } label: {
+                  HStack {
+                    Text("Confirm")
+                    Spacer()
+                    if viewStore.isConfirmingEmail {
+                      ProgressView()
+                    }
+                  }
+                }
+                .disabled(viewStore.isConfirmingEmail)
+              } else {
+                Button {
+                  viewStore.send(.registerEmailTapped)
+                } label: {
+                  HStack {
+                    Text("Register")
+                    Spacer()
+                    if viewStore.isRegisteringEmail {
+                      ProgressView()
+                    }
+                  }
+                }
+                .disabled(viewStore.isRegisteringEmail)
+              }
+            }
+          } else {
+            Text("")
+          }
+        } header: {
+          Label("Email", systemImage: "envelope")
+        }
+
+        Section {
+          if let contact = viewStore.contact {
+            if let phone = contact.phone {
+              Text(phone)
+                .textSelection(.enabled)
+              Button(role: .destructive) {
+                viewStore.send(.unregisterPhoneTapped)
+              } label: {
+                HStack {
+                  Text("Unregister")
+                  Spacer()
+                  if viewStore.isUnregisteringPhone {
+                    ProgressView()
+                  }
+                }
+              }
+              .disabled(viewStore.isUnregisteringPhone)
+            } else {
+              TextField(
+                text: viewStore.binding(
+                  get: \.phone,
+                  send: { MyContactAction.set(\.$phone, $0) }
+                ),
+                prompt: Text("Enter phone"),
+                label: { Text("Phone") }
+              )
+              .focused($focusedField, equals: .phone)
+              .textInputAutocapitalization(.never)
+              .disableAutocorrection(true)
+              .disabled(viewStore.isRegisteringPhone || viewStore.phoneConfirmation)
+              if viewStore.phoneConfirmation {
+                TextField(
+                  text: viewStore.binding(
+                    get: \.phoneCode,
+                    send: { MyContactAction.set(\.$phoneConfirmationCode, $0) }
+                  ),
+                  prompt: Text("Enter confirmation code"),
+                  label: { Text("Confirmation code") }
+                )
+                .focused($focusedField, equals: .phoneCode)
+                .textInputAutocapitalization(.never)
+                .disableAutocorrection(true)
+                .disabled(viewStore.isConfirmingPhone)
+                Button {
+                  viewStore.send(.confirmPhoneTapped)
+                } label: {
+                  HStack {
+                    Text("Confirm")
+                    Spacer()
+                    if viewStore.isConfirmingPhone {
+                      ProgressView()
+                    }
+                  }
+                }
+                .disabled(viewStore.isConfirmingPhone)
+              } else {
+                Button {
+                  viewStore.send(.registerPhoneTapped)
+                } label: {
+                  HStack {
+                    Text("Register")
+                    Spacer()
+                    if viewStore.isRegisteringPhone {
+                      ProgressView()
+                    }
+                  }
+                }
+                .disabled(viewStore.isRegisteringPhone)
+              }
+            }
+          } else {
+            Text("")
+          }
+        } header: {
+          Label("Phone", systemImage: "phone")
+        }
+
+        Section {
+          Button {
+            viewStore.send(.loadFactsTapped)
+          } label: {
+            HStack {
+              Text("Reload facts")
+              Spacer()
+              if viewStore.isLoadingFacts {
+                ProgressView()
+              }
+            }
+          }
+          .disabled(viewStore.isLoadingFacts)
+        } header: {
+          Text("Actions")
+        }
+      }
+      .navigationTitle("My Contact")
+      .task { viewStore.send(.start) }
+      .onChange(of: viewStore.focusedField) { focusedField = $0 }
+      .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) }
+      .alert(store.scope(state: \.alert), dismiss: .alertDismissed)
+    }
+  }
+}
+
+#if DEBUG
+public struct MyContactView_Previews: PreviewProvider {
+  public static var previews: some View {
+    NavigationView {
+      MyContactView(store: Store(
+        initialState: MyContactState(),
+        reducer: .empty,
+        environment: ()
+      ))
+    }
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift b/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
index cb43c43056f6d3e53c2b73b4bdfc10189f052b21..f8fdabefea7d859b3153c6716133d3351bff2fdd 100644
--- a/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
+++ b/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
@@ -7,6 +7,10 @@ import XXMessengerClient
 import XXModels
 
 public struct RegisterState: Equatable {
+  public enum Error: Swift.Error, Equatable {
+    case usernameMismatch(registering: String, registered: String?)
+  }
+
   public enum Field: String, Hashable {
     case username
   }
@@ -58,6 +62,7 @@ public struct RegisterEnvironment {
   public var bgQueue: AnySchedulerOf<DispatchQueue>
 }
 
+#if DEBUG
 extension RegisterEnvironment {
   public static let unimplemented = RegisterEnvironment(
     messenger: .unimplemented,
@@ -67,6 +72,7 @@ extension RegisterEnvironment {
     bgQueue: .unimplemented
   )
 }
+#endif
 
 public let registerReducer = Reducer<RegisterState, RegisterAction, RegisterEnvironment>
 { state, action, env in
@@ -82,14 +88,22 @@ public let registerReducer = Reducer<RegisterState, RegisterAction, RegisterEnvi
       do {
         let db = try env.db()
         try env.messenger.register(username: username)
-        var contact = try env.messenger.e2e.tryGet().getContact()
-        try contact.setFact(.username, username)
+        let contact = try env.messenger.myContact()
+        let facts = try contact.getFacts()
         try db.saveContact(Contact(
           id: try contact.getId(),
           marshaled: contact.data,
-          username: username,
+          username: facts.get(.username)?.value,
+          email: facts.get(.email)?.value,
+          phone: facts.get(.phone)?.value,
           createdAt: env.now()
         ))
+        guard facts.get(.username)?.value == username else {
+          throw RegisterState.Error.usernameMismatch(
+            registering: username,
+            registered: facts.get(.username)?.value
+          )
+        }
         fulfill(.success(.finished))
       }
       catch {
@@ -106,6 +120,7 @@ public let registerReducer = Reducer<RegisterState, RegisterAction, RegisterEnvi
     return .none
 
   case .finished:
+    state.isRegistering = false
     return .none
   }
 }
diff --git a/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthFeature.swift b/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthFeature.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d4acb74002902f9b9a82da2981f78cd3312deb9e
--- /dev/null
+++ b/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthFeature.swift
@@ -0,0 +1,90 @@
+import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+
+public struct ResetAuthState: Equatable {
+  public init(
+    partner: Contact,
+    isResetting: Bool = false,
+    failure: String? = nil,
+    didReset: Bool = false
+  ) {
+    self.partner = partner
+    self.isResetting = isResetting
+    self.failure = failure
+    self.didReset = didReset
+  }
+
+  public var partner: Contact
+  public var isResetting: Bool
+  public var failure: String?
+  public var didReset: Bool
+}
+
+public enum ResetAuthAction: Equatable {
+  case resetTapped
+  case didReset
+  case didFail(String)
+}
+
+public struct ResetAuthEnvironment {
+  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 ResetAuthEnvironment {
+  public static let unimplemented = ResetAuthEnvironment(
+    messenger: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
+}
+#endif
+
+public let resetAuthReducer = Reducer<ResetAuthState, ResetAuthAction, ResetAuthEnvironment>
+{ state, action, env in
+  switch action {
+  case .resetTapped:
+    state.isResetting = true
+    state.didReset = false
+    state.failure = nil
+    return Effect.result { [state] in
+      do {
+        let e2e = try env.messenger.e2e.tryGet()
+        _ = try e2e.resetAuthenticatedChannel(partner: state.partner)
+        return .success(.didReset)
+      } catch {
+        return .success(.didFail(error.localizedDescription))
+      }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .didReset:
+    state.isResetting = false
+    state.didReset = true
+    state.failure = nil
+    return .none
+
+  case .didFail(let failure):
+    state.isResetting = false
+    state.didReset = false
+    state.failure = failure
+    return .none
+  }
+}
diff --git a/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthView.swift b/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7b384efb74d841b4edf04a61cf55aa02a7c0d725
--- /dev/null
+++ b/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthView.swift
@@ -0,0 +1,80 @@
+import AppCore
+import ComposableArchitecture
+import SwiftUI
+
+public struct ResetAuthView: View {
+  public init(store: Store<ResetAuthState, ResetAuthAction>) {
+    self.store = store
+  }
+
+  let store: Store<ResetAuthState, ResetAuthAction>
+
+  struct ViewState: Equatable {
+    init(state: ResetAuthState) {
+      contactID = try? state.partner.getId()
+      isResetting = state.isResetting
+      failure = state.failure
+      didReset = state.didReset
+    }
+
+    var contactID: Data?
+    var isResetting: Bool
+    var failure: String?
+    var didReset: Bool
+  }
+
+  public var body: some View {
+    WithViewStore(store, observe: ViewState.init) { viewStore in
+      Form {
+        Section {
+          Text(viewStore.contactID?.hexString() ?? "")
+            .font(.footnote.monospaced())
+            .textSelection(.enabled)
+        } header: {
+          Label("ID", systemImage: "number")
+        }
+
+        Button {
+          viewStore.send(.resetTapped)
+        } label: {
+          HStack {
+            Text("Reset authenticated channel")
+            Spacer()
+            if viewStore.isResetting {
+              ProgressView()
+            } else if viewStore.didReset {
+              Image(systemName: "checkmark")
+                .foregroundColor(.green)
+            }
+          }
+        }
+        .disabled(viewStore.isResetting)
+
+        if let failure = viewStore.failure {
+          Section {
+            Text(failure)
+          } header: {
+            Text("Error")
+          }
+        }
+      }
+      .navigationTitle("Reset auth")
+    }
+  }
+}
+
+#if DEBUG
+public struct ResetAuthView_Previews: PreviewProvider {
+  public static var previews: some View {
+    NavigationView {
+      ResetAuthView(store: Store(
+        initialState: ResetAuthState(
+          partner: .unimplemented(Data())
+        ),
+        reducer: .empty,
+        environment: ()
+      ))
+    }
+  }
+}
+#endif
diff --git a/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift b/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift
index 6ce31e5d67acfde939a1bc9ba03e28226f4e0761..6b3d61d340a7932b6367f71894a0efd707ae81d2 100644
--- a/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift
+++ b/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift
@@ -1,19 +1,181 @@
+import AppCore
+import Combine
 import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXMessengerClient
+import XXModels
 
 public struct RestoreState: Equatable {
-  public init() {}
+  public enum Field: String, Hashable {
+    case passphrase
+  }
+
+  public struct File: Equatable {
+    public init(name: String, data: Data) {
+      self.name = name
+      self.data = data
+    }
+
+    public var name: String
+    public var data: Data
+  }
+
+  public init(
+    file: File? = nil,
+    fileImportFailure: String? = nil,
+    restoreFailures: [String] = [],
+    focusedField: Field? = nil,
+    isImportingFile: Bool = false,
+    passphrase: String = "",
+    isRestoring: Bool = false
+  ) {
+    self.file = file
+    self.fileImportFailure = fileImportFailure
+    self.restoreFailures = restoreFailures
+    self.focusedField = focusedField
+    self.isImportingFile = isImportingFile
+    self.passphrase = passphrase
+    self.isRestoring = isRestoring
+  }
+
+  public var file: File?
+  public var fileImportFailure: String?
+  public var restoreFailures: [String]
+  @BindableState public var focusedField: Field?
+  @BindableState public var isImportingFile: Bool
+  @BindableState public var passphrase: String
+  @BindableState public var isRestoring: Bool
 }
 
-public enum RestoreAction: Equatable {
+public enum RestoreAction: Equatable, BindableAction {
+  case importFileTapped
+  case fileImport(Result<URL, NSError>)
+  case restoreTapped
   case finished
+  case failed([NSError])
+  case binding(BindingAction<RestoreState>)
 }
 
 public struct RestoreEnvironment {
-  public init() {}
+  public init(
+    messenger: Messenger,
+    db: DBManagerGetDB,
+    loadData: URLDataLoader,
+    now: @escaping () -> Date,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>
+  ) {
+    self.messenger = messenger
+    self.db = db
+    self.loadData = loadData
+    self.now = now
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+  }
+
+  public var messenger: Messenger
+  public var db: DBManagerGetDB
+  public var loadData: URLDataLoader
+  public var now: () -> Date
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
 }
 
+#if DEBUG
 extension RestoreEnvironment {
-  public static let unimplemented = RestoreEnvironment()
+  public static let unimplemented = RestoreEnvironment(
+    messenger: .unimplemented,
+    db: .unimplemented,
+    loadData: .unimplemented,
+    now: XCTUnimplemented("\(Self.self).now"),
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
 }
+#endif
+
+public let restoreReducer = Reducer<RestoreState, RestoreAction, RestoreEnvironment>
+{ state, action, env in
+  switch action {
+  case .importFileTapped:
+    state.isImportingFile = true
+    state.fileImportFailure = nil
+    return .none
 
-public let restoreReducer = Reducer<RestoreState, RestoreAction, RestoreEnvironment>.empty
+  case .fileImport(.success(let url)):
+    state.isImportingFile = false
+    do {
+      state.file = .init(
+        name: url.lastPathComponent,
+        data: try env.loadData(url)
+      )
+      state.fileImportFailure = nil
+    } catch {
+      state.file = nil
+      state.fileImportFailure = error.localizedDescription
+    }
+    return .none
+
+  case .fileImport(.failure(let error)):
+    state.isImportingFile = false
+    state.file = nil
+    state.fileImportFailure = error.localizedDescription
+    return .none
+
+  case .restoreTapped:
+    guard let backupData = state.file?.data, backupData.count > 0 else { return .none }
+    let backupPassphrase = state.passphrase
+    state.isRestoring = true
+    state.restoreFailures = []
+    return Effect.result {
+      do {
+        let result = try env.messenger.restoreBackup(
+          backupData: backupData,
+          backupPassphrase: backupPassphrase
+        )
+        let facts = try env.messenger.ud.tryGet().getFacts()
+        try env.db().saveContact(Contact(
+          id: try env.messenger.e2e.tryGet().getContact().getId(),
+          username: facts.get(.username)?.value,
+          email: facts.get(.email)?.value,
+          phone: facts.get(.phone)?.value,
+          createdAt: env.now()
+        ))
+        try result.restoredContacts.forEach { contactId in
+          if try env.db().fetchContacts(.init(id: [contactId])).isEmpty {
+            try env.db().saveContact(Contact(
+              id: contactId,
+              createdAt: env.now()
+            ))
+          }
+        }
+        return .success(.finished)
+      } catch {
+        var errors = [error as NSError]
+        do {
+          try env.messenger.destroy()
+        } catch {
+          errors.append(error as NSError)
+        }
+        return .success(.failed(errors))
+      }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .finished:
+    state.isRestoring = false
+    return .none
+
+  case .failed(let errors):
+    state.isRestoring = false
+    state.restoreFailures = errors.map(\.localizedDescription)
+    return .none
+
+  case .binding(_):
+    return .none
+  }
+}
+.binding()
diff --git a/Examples/xx-messenger/Sources/RestoreFeature/RestoreView.swift b/Examples/xx-messenger/Sources/RestoreFeature/RestoreView.swift
index b2cf3e86cdaa2db59c2ce37eb3c8b33f0da38868..281f3f061e4a3ff5ccc1913bb45e45a03cff6576 100644
--- a/Examples/xx-messenger/Sources/RestoreFeature/RestoreView.swift
+++ b/Examples/xx-messenger/Sources/RestoreFeature/RestoreView.swift
@@ -7,40 +7,164 @@ public struct RestoreView: View {
   }
 
   let store: Store<RestoreState, RestoreAction>
+  @FocusState var focusedField: RestoreState.Field?
 
   struct ViewState: Equatable {
-    init(state: RestoreState) {}
+    struct File: Equatable {
+      var name: String
+      var size: Int
+    }
+
+    var file: File?
+    var isImportingFile: Bool
+    var passphrase: String
+    var isRestoring: Bool
+    var focusedField: RestoreState.Field?
+    var fileImportFailure: String?
+    var restoreFailures: [String]
+
+    init(state: RestoreState) {
+      file = state.file.map { .init(name: $0.name, size: $0.data.count) }
+      isImportingFile = state.isImportingFile
+      passphrase = state.passphrase
+      isRestoring = state.isRestoring
+      focusedField = state.focusedField
+      fileImportFailure = state.fileImportFailure
+      restoreFailures = state.restoreFailures
+    }
   }
 
   public var body: some View {
     WithViewStore(store, observe: ViewState.init) { viewStore in
       NavigationView {
         Form {
-          Section {
-            Text("Not implemented")
+          fileSection(viewStore)
+          if viewStore.file != nil {
+            restoreSection(viewStore)
           }
-
-          Section {
+        }
+        .toolbar {
+          ToolbarItem(placement: .cancellationAction) {
             Button {
               viewStore.send(.finished)
             } label: {
-              Text("OK")
-                .frame(maxWidth: .infinity)
+              Text("Cancel")
             }
+            .disabled(viewStore.isImportingFile || viewStore.isRestoring)
           }
         }
         .navigationTitle("Restore")
+        .onChange(of: viewStore.focusedField) { focusedField = $0 }
+        .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) }
       }
       .navigationViewStyle(.stack)
     }
   }
+
+  @ViewBuilder func fileSection(_ viewStore: ViewStore<ViewState, RestoreAction>) -> some View {
+    Section {
+      if let file = viewStore.file {
+        HStack(alignment: .bottom) {
+          Text(file.name)
+          Spacer()
+          Text(format(byteCount: file.size))
+        }
+      } else {
+        Button {
+          viewStore.send(.importFileTapped)
+        } label: {
+          Text("Import backup file")
+        }
+        .fileImporter(
+          isPresented: viewStore.binding(
+            get: \.isImportingFile,
+            send: { .set(\.$isImportingFile, $0) }
+          ),
+          allowedContentTypes: [.data],
+          onCompletion: { result in
+            viewStore.send(.fileImport(result.mapError { $0 as NSError }))
+          }
+        )
+        .disabled(viewStore.isRestoring)
+      }
+    } header: {
+      Text("File")
+    }
+
+    if let failure = viewStore.fileImportFailure {
+      Section {
+        Text(failure)
+      } header: {
+        Text("Error")
+      }
+    }
+  }
+
+  @ViewBuilder func restoreSection(_ viewStore: ViewStore<ViewState, RestoreAction>) -> some View {
+    Section {
+      SecureField("Passphrase", text: viewStore.binding(
+        get: \.passphrase,
+        send: { .set(\.$passphrase, $0) }
+      ))
+      .textContentType(.password)
+      .textInputAutocapitalization(.never)
+      .disableAutocorrection(true)
+      .focused($focusedField, equals: .passphrase)
+      .disabled(viewStore.isRestoring)
+
+      Button {
+        viewStore.send(.restoreTapped)
+      } label: {
+        HStack {
+          Text("Restore")
+          Spacer()
+          if viewStore.isRestoring {
+            ProgressView()
+          }
+        }
+      }
+    } header: {
+      Text("Restore")
+    }
+    .disabled(viewStore.isRestoring)
+
+    if !viewStore.restoreFailures.isEmpty {
+      Section {
+        ForEach(Array(viewStore.restoreFailures.enumerated()), id: \.offset) { _, failure in
+          Text(failure)
+        }
+        .font(.footnote)
+      } header: {
+        Text("Error")
+      }
+    }
+  }
+
+  func format(byteCount: Int) -> String {
+    let formatter = ByteCountFormatter()
+    formatter.allowedUnits = [.useMB, .useKB, .useBytes]
+    formatter.countStyle = .binary
+    return formatter.string(fromByteCount: Int64(byteCount))
+  }
 }
 
 #if DEBUG
 public struct RestoreView_Previews: PreviewProvider {
   public static var previews: some View {
     RestoreView(store: Store(
-      initialState: RestoreState(),
+      initialState: RestoreState(
+        file: .init(name: "preview", data: Data()),
+        fileImportFailure: nil,
+        restoreFailures: [
+          "Preview failure 1",
+          "Preview failure 2",
+          "Preview failure 3",
+        ],
+        focusedField: nil,
+        isImportingFile: false,
+        passphrase: "",
+        isRestoring: true
+      ),
       reducer: .empty,
       environment: ()
     ))
diff --git a/Examples/xx-messenger/Sources/RestoreFeature/URLDataLoader.swift b/Examples/xx-messenger/Sources/RestoreFeature/URLDataLoader.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bca96e58f59d9880aca1f38d100192fdf9c97f47
--- /dev/null
+++ b/Examples/xx-messenger/Sources/RestoreFeature/URLDataLoader.swift
@@ -0,0 +1,22 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct URLDataLoader {
+  public var load: (URL) throws -> Data
+
+  public func callAsFunction(_ url: URL) throws -> Data {
+    try load(url)
+  }
+}
+
+extension URLDataLoader {
+  public static let live = URLDataLoader { url in
+    try Data(contentsOf: url)
+  }
+}
+
+extension URLDataLoader {
+  public static let unimplemented = URLDataLoader(
+    load: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift
index f2625b91e6a4042624ad68141515ab34ace7c90c..18075179aedc62daff46847052e2bc0071f76b0a 100644
--- a/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift
+++ b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift
@@ -1,4 +1,5 @@
 import AppCore
+import Combine
 import ComposableArchitecture
 import Foundation
 import XCTestDynamicOverlay
@@ -40,7 +41,8 @@ public enum SendRequestAction: Equatable, BindableAction {
   case sendSucceeded
   case sendFailed(String)
   case binding(BindingAction<SendRequestState>)
-  case myContactFetched(XXClient.Contact?)
+  case myContactFetched(XXClient.Contact)
+  case myContactFetchFailed(NSError)
 }
 
 public struct SendRequestEnvironment {
@@ -75,25 +77,30 @@ extension SendRequestEnvironment {
 
 public let sendRequestReducer = Reducer<SendRequestState, SendRequestAction, SendRequestEnvironment>
 { state, action, env in
-  enum DBFetchEffectID {}
-
   switch action {
   case .start:
-    return Effect
-      .catching { try env.messenger.e2e.tryGet().getContact().getId() }
-      .tryMap { try env.db().fetchContactsPublisher(.init(id: [$0])) }
-      .flatMap { $0 }
-      .assertNoFailure()
-      .map(\.first)
-      .map { $0?.marshaled.map { XXClient.Contact.live($0) } }
-      .map(SendRequestAction.myContactFetched)
-      .subscribe(on: env.bgQueue)
-      .receive(on: env.mainQueue)
-      .eraseToEffect()
-      .cancellable(id: DBFetchEffectID.self, cancelInFlight: true)
+    return Effect.run { subscriber in
+      do {
+        let contact = try env.messenger.myContact()
+        subscriber.send(.myContactFetched(contact))
+      } catch {
+        subscriber.send(.myContactFetchFailed(error as NSError))
+      }
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .receive(on: env.mainQueue)
+    .subscribe(on: env.bgQueue)
+    .eraseToEffect()
 
   case .myContactFetched(let contact):
     state.myContact = contact
+    state.failure = nil
+    return .none
+
+  case .myContactFetchFailed(let failure):
+    state.myContact = nil
+    state.failure = failure.localizedDescription
     return .none
 
   case .sendTapped:
diff --git a/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeFeature.swift b/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeFeature.swift
index 6c6f2e22aa36e73ff0fc4acc441eb63b91f56e66..66e9ef1b3492c1d8277fac263a9d76523769030c 100644
--- a/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeFeature.swift
+++ b/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeFeature.swift
@@ -4,12 +4,15 @@ import XXMessengerClient
 
 public struct WelcomeState: Equatable {
   public init(
-    isCreatingCMix: Bool = false
+    isCreatingCMix: Bool = false,
+    failure: String? = nil
   ) {
     self.isCreatingAccount = isCreatingCMix
+    self.failure = failure
   }
 
   public var isCreatingAccount: Bool
+  public var failure: String?
 }
 
 public enum WelcomeAction: Equatable {
@@ -35,6 +38,7 @@ public struct WelcomeEnvironment {
   public var bgQueue: AnySchedulerOf<DispatchQueue>
 }
 
+#if DEBUG
 extension WelcomeEnvironment {
   public static let unimplemented = WelcomeEnvironment(
     messenger: .unimplemented,
@@ -42,12 +46,14 @@ extension WelcomeEnvironment {
     bgQueue: .unimplemented
   )
 }
+#endif
 
 public let welcomeReducer = Reducer<WelcomeState, WelcomeAction, WelcomeEnvironment>
 { state, action, env in
   switch action {
   case .newAccountTapped:
     state.isCreatingAccount = true
+    state.failure = nil
     return .future { fulfill in
       do {
         try env.messenger.create()
@@ -66,10 +72,12 @@ public let welcomeReducer = Reducer<WelcomeState, WelcomeAction, WelcomeEnvironm
 
   case .finished:
     state.isCreatingAccount = false
+    state.failure = nil
     return .none
 
-  case .failed(_):
+  case .failed(let failure):
     state.isCreatingAccount = false
+    state.failure = failure
     return .none
   }
 }
diff --git a/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift b/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift
index 32312386644b0331d069a386e337f17b4003d41c..64396fe1b6392267dc61efae6a8a73997698cd0c 100644
--- a/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift
+++ b/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift
@@ -1,3 +1,4 @@
+import AppCore
 import ComposableArchitecture
 import SwiftUI
 
@@ -11,9 +12,11 @@ public struct WelcomeView: View {
   struct ViewState: Equatable {
     init(_ state: WelcomeState) {
       isCreatingAccount = state.isCreatingAccount
+      failure = state.failure
     }
 
     var isCreatingAccount: Bool
+    var failure: String?
   }
 
   public var body: some View {
@@ -21,7 +24,17 @@ public struct WelcomeView: View {
       NavigationView {
         Form {
           Section {
-            Text("xx messenger")
+            AppVersionText()
+          } header: {
+            Text("App version")
+          }
+
+          if let failure = viewStore.failure {
+            Section {
+              Text(failure)
+            } header: {
+              Text("Error")
+            }
           }
 
           Section {
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift
index 1a1b5f19573669403a069bd4e3cecd2e11347e73..0f5043ab3b50a18c3b9c50d2615c35c7c159f853 100644
--- a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift
@@ -15,7 +15,7 @@ final class AuthCallbackHandlerConfirmTests: XCTestCase {
     )
     let confirm = AuthCallbackHandlerConfirm.live(
       db: .init {
-        var db: Database = .failing
+        var db: Database = .unimplemented
         db.fetchContacts.run = { query in
           didFetchContacts.append(query)
           return [dbContact]
@@ -41,7 +41,7 @@ final class AuthCallbackHandlerConfirmTests: XCTestCase {
   func testConfirmWhenContactNotInDatabase() throws {
     let confirm = AuthCallbackHandlerConfirm.live(
       db: .init {
-        var db: Database = .failing
+        var db: Database = .unimplemented
         db.fetchContacts.run = { _ in [] }
         return db
       }
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
index 3a7cb6fbf2d475eadfdc10aa854938fad247167b..d8e19ce7a8d7441f63f93ec0263468adad1d2443 100644
--- a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
@@ -14,7 +14,7 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
 
     let request = AuthCallbackHandlerRequest.live(
       db: .init {
-        var db: Database = .failing
+        var db: Database = .unimplemented
         db.fetchContacts.run = { query in
           didFetchContacts.append(query)
           return []
@@ -54,7 +54,7 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
   func testRequestWhenContactInDatabase() throws {
     let request = AuthCallbackHandlerRequest.live(
       db: .init {
-        var db: Database = .failing
+        var db: Database = .unimplemented
         db.fetchContacts.run = { _ in [.init(id: "id".data(using: .utf8)!)] }
         return db
       },
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift
index 9a4407bf758a0c0ba922c154d199b758dea701be..a6273fba250c73de2e0cfe6700879fb5a1f15d58 100644
--- a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift
@@ -10,12 +10,11 @@ final class AuthCallbackHandlerResetTests: XCTestCase {
     var didSaveContact: [XXModels.Contact] = []
 
     let dbContact = XXModels.Contact(
-      id: "id".data(using: .utf8)!,
-      authStatus: .friend
+      id: "id".data(using: .utf8)!
     )
     let reset = AuthCallbackHandlerReset.live(
       db: .init {
-        var db: Database = .failing
+        var db: Database = .unimplemented
         db.fetchContacts.run = { query in
           didFetchContacts.append(query)
           return [dbContact]
@@ -34,14 +33,14 @@ final class AuthCallbackHandlerResetTests: XCTestCase {
 
     XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])])
     var expectedSavedContact = dbContact
-    expectedSavedContact.authStatus = .stranger
+    expectedSavedContact.authStatus = .friend
     XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
   }
 
   func testResetWhenContactNotInDatabase() throws {
     let reset = AuthCallbackHandlerReset.live(
       db: .init {
-        var db: Database = .failing
+        var db: Database = .unimplemented
         db.fetchContacts.run = { _ in [] }
         return db
       }
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/MessageListenerHandler/MessageListenerHandlerTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/MessageListenerHandler/MessageListenerHandlerTests.swift
index 4038cb0d36019822347e358c1139809d4877f059..eb1aa60f0f5e9b43601ce2ea5f0c759b865520fb 100644
--- a/Examples/xx-messenger/Tests/AppCoreTests/MessageListenerHandler/MessageListenerHandlerTests.swift
+++ b/Examples/xx-messenger/Tests/AppCoreTests/MessageListenerHandler/MessageListenerHandlerTests.swift
@@ -18,7 +18,7 @@ final class MessageListenerHandlerTests: XCTestCase {
     }
     var db: DBManagerGetDB = .unimplemented
     db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.saveMessage.run = { message in
         didSaveMessage.append(message)
         return message
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendMessageTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendMessageTests.swift
index 0ff8536f453530bba378b8279686d344f8cace74..ae4e6f9e0a55e8819b21c0a326e95b73a6c50056 100644
--- a/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendMessageTests.swift
+++ b/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendMessageTests.swift
@@ -55,7 +55,7 @@ final class SendMessageTests: XCTestCase {
     }
     var db: DBManagerGetDB = .unimplemented
     db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.saveMessage.run = { message in
         dbDidSaveMessage.append(message)
         var message = message
@@ -238,7 +238,7 @@ final class SendMessageTests: XCTestCase {
     }
     var db: DBManagerGetDB = .unimplemented
     db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.saveMessage.run = { $0 }
       db.fetchMessages.run = { _ in [] }
       db.bulkUpdateMessages.run = { _, _ in throw error }
diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
index 5a013b28b16126b422ef3d26b19407d0af2bff5c..098a026c2c665c803b9b1ef00d4c6dbbe9250f0a 100644
--- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
@@ -1,77 +1,113 @@
+import AppCore
 import ComposableArchitecture
+import CustomDump
 import HomeFeature
 import RestoreFeature
 import WelcomeFeature
 import XCTest
+import XXClient
 @testable import AppFeature
 
 final class AppFeatureTests: XCTestCase {
   func testStartWithoutMessengerCreated() {
+    var actions: [Action]!
+
     let store = TestStore(
       initialState: AppState(),
       reducer: appReducer,
       environment: .unimplemented
     )
 
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    var didMakeDB = 0
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
     store.environment.dbManager.hasDB.run = { false }
-    store.environment.dbManager.makeDB.run = { didMakeDB += 1 }
     store.environment.messenger.isLoaded.run = { false }
     store.environment.messenger.isCreated.run = { false }
+    store.environment.dbManager.makeDB.run = {
+      actions.append(.didMakeDB)
+    }
+    store.environment.authHandler.run = { _ in
+      actions.append(.didStartAuthHandler)
+      return Cancellable {}
+    }
+    store.environment.messageListener.run = { _ in
+      actions.append(.didStartMessageListener)
+      return Cancellable {}
+    }
+    store.environment.messenger.registerBackupCallback.run = { _ in
+      actions.append(.didRegisterBackupCallback)
+      return Cancellable {}
+    }
 
+    actions = []
     store.send(.start)
 
-    bgQueue.advance()
-
-    XCTAssertNoDifference(didMakeDB, 1)
-
-    mainQueue.advance()
-
     store.receive(.set(\.$screen, .welcome(WelcomeState()))) {
       $0.screen = .welcome(WelcomeState())
     }
+    XCTAssertNoDifference(actions, [
+      .didMakeDB,
+      .didStartAuthHandler,
+      .didStartMessageListener,
+      .didRegisterBackupCallback,
+    ])
+
+    store.send(.stop)
   }
 
   func testStartWithMessengerCreated() {
+    var actions: [Action]!
+
     let store = TestStore(
       initialState: AppState(),
       reducer: appReducer,
       environment: .unimplemented
     )
 
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    var didMakeDB = 0
-    var messengerDidLoad = 0
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
     store.environment.dbManager.hasDB.run = { false }
-    store.environment.dbManager.makeDB.run = { didMakeDB += 1 }
     store.environment.messenger.isLoaded.run = { false }
     store.environment.messenger.isCreated.run = { true }
-    store.environment.messenger.load.run = { messengerDidLoad += 1 }
+    store.environment.dbManager.makeDB.run = {
+      actions.append(.didMakeDB)
+    }
+    store.environment.messenger.load.run = {
+      actions.append(.didLoadMessenger)
+    }
+    store.environment.authHandler.run = { _ in
+      actions.append(.didStartAuthHandler)
+      return Cancellable {}
+    }
+    store.environment.messageListener.run = { _ in
+      actions.append(.didStartMessageListener)
+      return Cancellable {}
+    }
+    store.environment.messenger.registerBackupCallback.run = { _ in
+      actions.append(.didRegisterBackupCallback)
+      return Cancellable {}
+    }
 
+    actions = []
     store.send(.start)
 
-    bgQueue.advance()
-
-    XCTAssertNoDifference(didMakeDB, 1)
-    XCTAssertNoDifference(messengerDidLoad, 1)
-
-    mainQueue.advance()
-
     store.receive(.set(\.$screen, .home(HomeState()))) {
       $0.screen = .home(HomeState())
     }
+    XCTAssertNoDifference(actions, [
+      .didMakeDB,
+      .didStartAuthHandler,
+      .didStartMessageListener,
+      .didRegisterBackupCallback,
+      .didLoadMessenger,
+    ])
+
+    store.send(.stop)
   }
 
   func testWelcomeFinished() {
+    var actions: [Action]!
+
     let store = TestStore(
       initialState: AppState(
         screen: .welcome(WelcomeState())
@@ -80,33 +116,48 @@ final class AppFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    var messengerDidLoad = 0
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
     store.environment.dbManager.hasDB.run = { true }
     store.environment.messenger.isLoaded.run = { false }
     store.environment.messenger.isCreated.run = { true }
-    store.environment.messenger.load.run = { messengerDidLoad += 1 }
+    store.environment.messenger.load.run = {
+      actions.append(.didLoadMessenger)
+    }
+    store.environment.authHandler.run = { _ in
+      actions.append(.didStartAuthHandler)
+      return Cancellable {}
+    }
+    store.environment.messageListener.run = { _ in
+      actions.append(.didStartMessageListener)
+      return Cancellable {}
+    }
+    store.environment.messenger.registerBackupCallback.run = { _ in
+      actions.append(.didRegisterBackupCallback)
+      return Cancellable {}
+    }
 
+    actions = []
     store.send(.welcome(.finished)) {
       $0.screen = .loading
     }
 
-    bgQueue.advance()
-
-    XCTAssertNoDifference(messengerDidLoad, 1)
-
-    mainQueue.advance()
-
     store.receive(.set(\.$screen, .home(HomeState()))) {
       $0.screen = .home(HomeState())
     }
+    XCTAssertNoDifference(actions, [
+      .didStartAuthHandler,
+      .didStartMessageListener,
+      .didRegisterBackupCallback,
+      .didLoadMessenger,
+    ])
+
+    store.send(.stop)
   }
 
   func testRestoreFinished() {
+    var actions: [Action]!
+
     let store = TestStore(
       initialState: AppState(
         screen: .restore(RestoreState())
@@ -115,33 +166,48 @@ final class AppFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    var messengerDidLoad = 0
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
     store.environment.dbManager.hasDB.run = { true }
     store.environment.messenger.isLoaded.run = { false }
     store.environment.messenger.isCreated.run = { true }
-    store.environment.messenger.load.run = { messengerDidLoad += 1 }
+    store.environment.messenger.load.run = {
+      actions.append(.didLoadMessenger)
+    }
+    store.environment.authHandler.run = { _ in
+      actions.append(.didStartAuthHandler)
+      return Cancellable {}
+    }
+    store.environment.messageListener.run = { _ in
+      actions.append(.didStartMessageListener)
+      return Cancellable {}
+    }
+    store.environment.messenger.registerBackupCallback.run = { _ in
+      actions.append(.didRegisterBackupCallback)
+      return Cancellable {}
+    }
 
+    actions = []
     store.send(.restore(.finished)) {
       $0.screen = .loading
     }
 
-    bgQueue.advance()
-
-    XCTAssertNoDifference(messengerDidLoad, 1)
-
-    mainQueue.advance()
-
     store.receive(.set(\.$screen, .home(HomeState()))) {
       $0.screen = .home(HomeState())
     }
+    XCTAssertNoDifference(actions, [
+      .didStartAuthHandler,
+      .didStartMessageListener,
+      .didRegisterBackupCallback,
+      .didLoadMessenger,
+    ])
+
+    store.send(.stop)
   }
 
   func testHomeDidDeleteAccount() {
+    var actions: [Action]!
+
     let store = TestStore(
       initialState: AppState(
         screen: .home(HomeState())
@@ -150,25 +216,39 @@ final class AppFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
     store.environment.dbManager.hasDB.run = { true }
     store.environment.messenger.isLoaded.run = { false }
     store.environment.messenger.isCreated.run = { false }
+    store.environment.authHandler.run = { _ in
+      actions.append(.didStartAuthHandler)
+      return Cancellable {}
+    }
+    store.environment.messageListener.run = { _ in
+      actions.append(.didStartMessageListener)
+      return Cancellable {}
+    }
+    store.environment.messenger.registerBackupCallback.run = { _ in
+      actions.append(.didRegisterBackupCallback)
+      return Cancellable {}
+    }
 
+    actions = []
     store.send(.home(.deleteAccount(.success))) {
       $0.screen = .loading
     }
 
-    bgQueue.advance()
-    mainQueue.advance()
-
     store.receive(.set(\.$screen, .welcome(WelcomeState()))) {
       $0.screen = .welcome(WelcomeState())
     }
+    XCTAssertNoDifference(actions, [
+      .didStartAuthHandler,
+      .didStartMessageListener,
+      .didRegisterBackupCallback,
+    ])
+
+    store.send(.stop)
   }
 
   func testWelcomeRestoreTapped() {
@@ -186,6 +266,8 @@ final class AppFeatureTests: XCTestCase {
   }
 
   func testWelcomeFailed() {
+    let failure = "Something went wrong"
+
     let store = TestStore(
       initialState: AppState(
         screen: .welcome(WelcomeState())
@@ -194,23 +276,21 @@ final class AppFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let failure = "Something went wrong"
-
     store.send(.welcome(.failed(failure))) {
       $0.screen = .failure(failure)
     }
   }
 
   func testStartDatabaseMakeFailure() {
+    struct Failure: Error {}
+    let error = Failure()
+
     let store = TestStore(
       initialState: AppState(),
       reducer: appReducer,
       environment: .unimplemented
     )
 
-    struct Failure: Error {}
-    let error = Failure()
-
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
     store.environment.dbManager.hasDB.run = { false }
@@ -221,29 +301,177 @@ final class AppFeatureTests: XCTestCase {
     store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
       $0.screen = .failure(error.localizedDescription)
     }
+
+    store.send(.stop)
   }
 
   func testStartMessengerLoadFailure() {
+    struct Failure: Error {}
+    let error = Failure()
+
+    var actions: [Action]!
+
     let store = TestStore(
       initialState: AppState(),
       reducer: appReducer,
       environment: .unimplemented
     )
 
-    struct Failure: Error {}
-    let error = Failure()
-
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
     store.environment.dbManager.hasDB.run = { true }
     store.environment.messenger.isLoaded.run = { false }
     store.environment.messenger.isCreated.run = { true }
     store.environment.messenger.load.run = { throw error }
+    store.environment.authHandler.run = { _ in
+      actions.append(.didStartAuthHandler)
+      return Cancellable {}
+    }
+    store.environment.messageListener.run = { _ in
+      actions.append(.didStartMessageListener)
+      return Cancellable {}
+    }
+    store.environment.messenger.registerBackupCallback.run = { _ in
+      actions.append(.didRegisterBackupCallback)
+      return Cancellable {}
+    }
 
+    actions = []
     store.send(.start)
 
     store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
       $0.screen = .failure(error.localizedDescription)
     }
+
+    XCTAssertNoDifference(actions, [
+      .didStartAuthHandler,
+      .didStartMessageListener,
+      .didRegisterBackupCallback,
+    ])
+
+    store.send(.stop)
+  }
+
+  func testStartHandlersAndListeners() {
+    var actions: [Action]!
+    var authHandlerOnError: [AuthCallbackHandler.OnError] = []
+    var messageListenerOnError: [MessageListenerHandler.OnError] = []
+    var backupCallback: [UpdateBackupFunc] = []
+
+    let store = TestStore(
+      initialState: AppState(),
+      reducer: appReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.dbManager.hasDB.run = { true }
+    store.environment.messenger.isLoaded.run = { true }
+    store.environment.messenger.isCreated.run = { true }
+    store.environment.authHandler.run = { onError in
+      authHandlerOnError.append(onError)
+      actions.append(.didStartAuthHandler)
+      return Cancellable {
+        actions.append(.didCancelAuthHandler)
+      }
+    }
+    store.environment.messageListener.run = { onError in
+      messageListenerOnError.append(onError)
+      actions.append(.didStartMessageListener)
+      return Cancellable {
+        actions.append(.didCancelMessageListener)
+      }
+    }
+    store.environment.messenger.registerBackupCallback.run = { callback in
+      backupCallback.append(callback)
+      actions.append(.didRegisterBackupCallback)
+      return Cancellable {
+        actions.append(.didCancelBackupCallback)
+      }
+    }
+    store.environment.log.run = { msg, _, _, _ in
+      actions.append(.didLog(msg))
+    }
+    store.environment.backupStorage.store = { data in
+      actions.append(.didStoreBackup(data))
+    }
+
+    actions = []
+    store.send(.start)
+
+    store.receive(.set(\.$screen, .home(HomeState()))) {
+      $0.screen = .home(HomeState())
+    }
+    XCTAssertNoDifference(actions, [
+      .didStartAuthHandler,
+      .didStartMessageListener,
+      .didRegisterBackupCallback,
+    ])
+
+    actions = []
+    store.send(.start) {
+      $0.screen = .loading
+    }
+
+    store.receive(.set(\.$screen, .home(HomeState()))) {
+      $0.screen = .home(HomeState())
+    }
+    XCTAssertNoDifference(actions, [
+      .didCancelAuthHandler,
+      .didCancelMessageListener,
+      .didCancelBackupCallback,
+      .didStartAuthHandler,
+      .didStartMessageListener,
+      .didRegisterBackupCallback,
+    ])
+
+    actions = []
+    struct AuthError: Error {}
+    let authError = AuthError()
+    authHandlerOnError.first?(authError)
+
+    XCTAssertNoDifference(actions, [
+      .didLog(.error(authError as NSError))
+    ])
+
+    actions = []
+    struct MessageError: Error {}
+    let messageError = MessageError()
+    messageListenerOnError.first?(messageError)
+
+    XCTAssertNoDifference(actions, [
+      .didLog(.error(messageError as NSError))
+    ])
+
+    actions = []
+    let backupData = "backup".data(using: .utf8)!
+    backupCallback.first?.handle(backupData)
+
+    XCTAssertNoDifference(actions, [
+      .didStoreBackup(backupData),
+    ])
+
+    actions = []
+    store.send(.stop)
+
+    XCTAssertNoDifference(actions, [
+      .didCancelAuthHandler,
+      .didCancelMessageListener,
+      .didCancelBackupCallback,
+    ])
   }
 }
+
+private enum Action: Equatable {
+  case didMakeDB
+  case didStartAuthHandler
+  case didStartMessageListener
+  case didRegisterBackupCallback
+  case didLoadMessenger
+  case didCancelAuthHandler
+  case didCancelMessageListener
+  case didCancelBackupCallback
+  case didLog(Logger.Message)
+  case didStoreBackup(Data)
+}
diff --git a/Examples/xx-messenger/Tests/BackupFeatureTests/BackupFeatureTests.swift b/Examples/xx-messenger/Tests/BackupFeatureTests/BackupFeatureTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d0c69eb87e2efea29f635fce84656b04d2e35cba
--- /dev/null
+++ b/Examples/xx-messenger/Tests/BackupFeatureTests/BackupFeatureTests.swift
@@ -0,0 +1,416 @@
+import ComposableArchitecture
+import XCTest
+import XXClient
+import XXMessengerClient
+@testable import BackupFeature
+
+final class BackupFeatureTests: XCTestCase {
+  func testTask() {
+    var isBackupRunning: [Bool] = [false]
+    var observers: [UUID: BackupStorage.Observer] = [:]
+    let storedBackup = BackupStorage.Backup(
+      date: .init(timeIntervalSince1970: 1),
+      data: "stored".data(using: .utf8)!
+    )
+
+    let store = TestStore(
+      initialState: BackupState(),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.isBackupRunning.run = {
+      isBackupRunning.removeFirst()
+    }
+    store.environment.backupStorage.stored = {
+      storedBackup
+    }
+    store.environment.backupStorage.observe = {
+      let id = UUID()
+      observers[id] = $0
+      return Cancellable { observers[id] = nil }
+    }
+
+    store.send(.task)
+
+    XCTAssertNoDifference(observers.count, 1)
+
+    store.receive(.backupUpdated(storedBackup)) {
+      $0.backup = storedBackup
+    }
+
+    let observedBackup = BackupStorage.Backup(
+      date: .init(timeIntervalSince1970: 2),
+      data: "observed".data(using: .utf8)!
+    )
+    observers.values.forEach { $0(observedBackup) }
+
+    store.receive(.backupUpdated(observedBackup)) {
+      $0.backup = observedBackup
+    }
+
+    observers.values.forEach { $0(nil) }
+
+    store.receive(.backupUpdated(nil)) {
+      $0.backup = nil
+    }
+
+    store.send(.cancelTask)
+
+    XCTAssertNoDifference(observers.count, 0)
+  }
+
+  func testStartBackup() {
+    var actions: [Action]!
+    var isBackupRunning: [Bool] = [true]
+    let username = "test-username"
+    let passphrase = "backup-password"
+
+    let store = TestStore(
+      initialState: BackupState(),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.myContact.run = { includeFacts in
+      actions.append(.didGetMyContact(includingFacts: includeFacts))
+      var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+      contact.getFactsFromContact.run = { _ in [Fact(type: .username, value: username)] }
+      return contact
+    }
+    store.environment.messenger.startBackup.run = { passphrase, params in
+      actions.append(.didStartBackup(passphrase: passphrase, params: params))
+    }
+    store.environment.messenger.isBackupRunning.run = {
+      isBackupRunning.removeFirst()
+    }
+
+    actions = []
+    store.send(.set(\.$focusedField, .passphrase)) {
+      $0.focusedField = .passphrase
+    }
+    store.send(.set(\.$passphrase, passphrase)) {
+      $0.passphrase = passphrase
+    }
+
+    XCTAssertNoDifference(actions, [])
+
+    actions = []
+    store.send(.startTapped) {
+      $0.isStarting = true
+      $0.focusedField = nil
+    }
+
+    XCTAssertNoDifference(actions, [
+      .didGetMyContact(
+        includingFacts: .types([.username])
+      ),
+      .didStartBackup(
+        passphrase: passphrase,
+        params: .init(username: username)
+      )
+    ])
+
+    store.receive(.didStart(failure: nil)) {
+      $0.isRunning = true
+      $0.isStarting = false
+      $0.passphrase = ""
+    }
+  }
+
+  func testStartBackupWithoutContactUsername() {
+    var isBackupRunning: [Bool] = [false]
+
+    let store = TestStore(
+      initialState: BackupState(
+        passphrase: "1234"
+      ),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.myContact.run = { _ in
+      var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+      contact.getFactsFromContact.run = { _ in [] }
+      return contact
+    }
+    store.environment.messenger.isBackupRunning.run = {
+      isBackupRunning.removeFirst()
+    }
+
+    store.send(.startTapped) {
+      $0.isStarting = true
+    }
+
+    let failure = BackupState.Error.contactUsernameMissing
+    store.receive(.didStart(failure: failure as NSError)) {
+      $0.isRunning = false
+      $0.isStarting = false
+      $0.alert = .error(failure)
+    }
+  }
+
+  func testStartBackupMyContactFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+    var isBackupRunning: [Bool] = [false]
+
+    let store = TestStore(
+      initialState: BackupState(
+        passphrase: "1234"
+      ),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.myContact.run = { _ in throw failure }
+    store.environment.messenger.isBackupRunning.run = {
+      isBackupRunning.removeFirst()
+    }
+
+    store.send(.startTapped) {
+      $0.isStarting = true
+    }
+
+    store.receive(.didStart(failure: failure as NSError)) {
+      $0.isRunning = false
+      $0.isStarting = false
+      $0.alert = .error(failure)
+    }
+  }
+
+  func testStartBackupStartFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+    var isBackupRunning: [Bool] = [false]
+
+    let store = TestStore(
+      initialState: BackupState(
+        passphrase: "1234"
+      ),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.myContact.run = { _ in
+      var contact = Contact.unimplemented("data".data(using: .utf8)!)
+      contact.getFactsFromContact.run = { _ in [Fact(type: .username, value: "username")] }
+      return contact
+    }
+    store.environment.messenger.startBackup.run = { _, _ in
+      throw failure
+    }
+    store.environment.messenger.isBackupRunning.run = {
+      isBackupRunning.removeFirst()
+    }
+
+    store.send(.startTapped) {
+      $0.isStarting = true
+    }
+
+    store.receive(.didStart(failure: failure as NSError)) {
+      $0.isRunning = false
+      $0.isStarting = false
+      $0.alert = .error(failure)
+    }
+  }
+
+  func testResumeBackup() {
+    var actions: [Action]!
+    var isBackupRunning: [Bool] = [true]
+
+    let store = TestStore(
+      initialState: BackupState(),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.resumeBackup.run = {
+      actions.append(.didResumeBackup)
+    }
+    store.environment.messenger.isBackupRunning.run = {
+      isBackupRunning.removeFirst()
+    }
+
+    actions = []
+    store.send(.resumeTapped) {
+      $0.isResuming = true
+    }
+
+    XCTAssertNoDifference(actions, [.didResumeBackup])
+
+    actions = []
+    store.receive(.didResume(failure: nil)) {
+      $0.isRunning = true
+      $0.isResuming = false
+    }
+
+    XCTAssertNoDifference(actions, [])
+  }
+
+  func testResumeBackupFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+    var isBackupRunning: [Bool] = [false]
+
+    let store = TestStore(
+      initialState: BackupState(),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.resumeBackup.run = {
+      throw failure
+    }
+    store.environment.messenger.isBackupRunning.run = {
+      isBackupRunning.removeFirst()
+    }
+
+    store.send(.resumeTapped) {
+      $0.isResuming = true
+    }
+
+    store.receive(.didResume(failure: failure as NSError)) {
+      $0.isRunning = false
+      $0.isResuming = false
+      $0.alert = .error(failure)
+    }
+  }
+
+  func testStopBackup() {
+    var actions: [Action]!
+    var isBackupRunning: [Bool] = [false]
+
+    let store = TestStore(
+      initialState: BackupState(),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.stopBackup.run = {
+      actions.append(.didStopBackup)
+    }
+    store.environment.messenger.isBackupRunning.run = {
+      isBackupRunning.removeFirst()
+    }
+    store.environment.backupStorage.remove = {
+      actions.append(.didRemoveBackup)
+    }
+
+    actions = []
+    store.send(.stopTapped) {
+      $0.isStopping = true
+    }
+
+    XCTAssertNoDifference(actions, [
+      .didStopBackup,
+      .didRemoveBackup,
+    ])
+
+    actions = []
+    store.receive(.didStop(failure: nil)) {
+      $0.isRunning = false
+      $0.isStopping = false
+    }
+
+    XCTAssertNoDifference(actions, [])
+  }
+
+  func testStopBackupFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+    var isBackupRunning: [Bool] = [true]
+
+    let store = TestStore(
+      initialState: BackupState(),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.stopBackup.run = {
+      throw failure
+    }
+    store.environment.messenger.isBackupRunning.run = {
+      isBackupRunning.removeFirst()
+    }
+
+    store.send(.stopTapped) {
+      $0.isStopping = true
+    }
+
+    store.receive(.didStop(failure: failure as NSError)) {
+      $0.isRunning = true
+      $0.isStopping = false
+      $0.alert = .error(failure)
+    }
+  }
+
+  func testAlertDismissed() {
+    let store = TestStore(
+      initialState: BackupState(
+        alert: .error(NSError(domain: "test", code: 0))
+      ),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.alertDismissed) {
+      $0.alert = nil
+    }
+  }
+
+  func testExportBackup() {
+    let backupData = "backup-data".data(using: .utf8)!
+
+    let store = TestStore(
+      initialState: BackupState(
+        backup: .init(
+          date: Date(),
+          data: backupData
+        )
+      ),
+      reducer: backupReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.exportTapped) {
+      $0.isExporting = true
+      $0.exportData = backupData
+    }
+
+    store.send(.didExport(failure: nil)) {
+      $0.isExporting = false
+      $0.exportData = nil
+    }
+
+    store.send(.exportTapped) {
+      $0.isExporting = true
+      $0.exportData = backupData
+    }
+
+    let failure = NSError(domain: "test", code: 0)
+    store.send(.didExport(failure: failure)) {
+      $0.isExporting = false
+      $0.exportData = nil
+      $0.alert = .error(failure)
+    }
+  }
+}
+
+private enum Action: Equatable {
+  case didRegisterObserver
+  case didStartBackup(passphrase: String, params: BackupParams)
+  case didResumeBackup
+  case didStopBackup
+  case didRemoveBackup
+  case didGetMyContact(includingFacts: MessengerMyContact.IncludeFacts?)
+}
diff --git a/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift b/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift
index 1a513af07195076dabb987bee120111411a6c633..7f0633021b525d1e26517db1ffc87ed073fae3d6 100644
--- a/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift
@@ -34,7 +34,7 @@ final class ChatFeatureTests: XCTestCase {
       return e2e
     }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.fetchMessagesPublisher.run = { query in
         didFetchMessagesWithQuery.append(query)
         return messagesPublisher.eraseToAnyPublisher()
diff --git a/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift
index 95f5a80773838e9b72d4f8c6e380e0f7ce8799fd..a13645f2f29d6fd2ec3fe87353105cf3adf8df86 100644
--- a/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift
@@ -34,7 +34,7 @@ final class CheckContactAuthFeatureTests: XCTestCase {
       return e2e
     }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.bulkUpdateContacts.run = { query, assignments in
         didBulkUpdateContactsWithQuery.append(query)
         didBulkUpdateContactsWithAssignments.append(assignments)
@@ -86,7 +86,7 @@ final class CheckContactAuthFeatureTests: XCTestCase {
       return e2e
     }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.bulkUpdateContacts.run = { query, assignments in
         didBulkUpdateContactsWithQuery.append(query)
         didBulkUpdateContactsWithAssignments.append(assignments)
diff --git a/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift
index 8dea06e119b266534d4b5c6919023ce69789a762..bc84224a8671219aeb7dbed33f5f8552b3302d59 100644
--- a/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift
@@ -34,7 +34,7 @@ final class ConfirmRequestFeatureTests: XCTestCase {
       return e2e
     }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.bulkUpdateContacts.run = { query, assignments in
         didBulkUpdateContactsWithQuery.append(query)
         didBulkUpdateContactsWithAssignments.append(assignments)
@@ -91,7 +91,7 @@ final class ConfirmRequestFeatureTests: XCTestCase {
       return e2e
     }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.bulkUpdateContacts.run = { query, assignments in
         didBulkUpdateContactsWithQuery.append(query)
         didBulkUpdateContactsWithAssignments.append(assignments)
diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
index 11bfe8a7743ee597706a25f62a0c8f819aea17a8..eba41b7ab57d1dddfc0827747760a9bce224bdab 100644
--- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift
@@ -3,7 +3,9 @@ import CheckContactAuthFeature
 import Combine
 import ComposableArchitecture
 import ConfirmRequestFeature
+import ContactLookupFeature
 import CustomDump
+import ResetAuthFeature
 import SendRequestFeature
 import VerifyContactFeature
 import XCTest
@@ -27,7 +29,7 @@ final class ContactFeatureTests: XCTestCase {
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.fetchContactsPublisher.run = { query in
         dbDidFetchContacts.append(query)
         return dbContactsPublisher.eraseToAnyPublisher()
@@ -80,7 +82,7 @@ final class ContactFeatureTests: XCTestCase {
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.saveContact.run = { contact in
         dbDidSaveContact.append(contact)
         return contact
@@ -99,6 +101,55 @@ final class ContactFeatureTests: XCTestCase {
     XCTAssertNoDifference(dbDidSaveContact, [expectedSavedContact])
   }
 
+  func testLookupTapped() {
+    let contactId = "contact-id".data(using: .utf8)!
+    let store = TestStore(
+      initialState: ContactState(
+        id: contactId
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.lookupTapped) {
+      $0.lookup = ContactLookupState(id: contactId)
+    }
+  }
+
+  func testLookupDismissed() {
+    let contactId = "contact-id".data(using: .utf8)!
+    let store = TestStore(
+      initialState: ContactState(
+        id: contactId,
+        lookup: ContactLookupState(id: contactId)
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.lookupDismissed) {
+      $0.lookup = nil
+    }
+  }
+
+  func testLookupDidLookup() {
+    let contactId = "contact-id".data(using: .utf8)!
+    let contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+    let store = TestStore(
+      initialState: ContactState(
+        id: contactId,
+        lookup: ContactLookupState(id: contactId)
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.lookup(.didLookup(contact))) {
+      $0.xxContact = contact
+      $0.lookup = nil
+    }
+  }
+
   func testSendRequestWithDBContact() {
     var dbContact = XXModels.Contact(id: "contact-id".data(using: .utf8)!)
     dbContact.marshaled = "contact-data".data(using: .utf8)!
@@ -244,6 +295,44 @@ final class ContactFeatureTests: XCTestCase {
     }
   }
 
+  func testResetAuthTapped() {
+    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(.resetAuthTapped) {
+      $0.resetAuth = ResetAuthState(
+        partner: .unimplemented(contactData)
+      )
+    }
+  }
+
+  func testResetAuthDismissed() {
+    let store = TestStore(
+      initialState: ContactState(
+        id: Data(),
+        resetAuth: ResetAuthState(
+          partner: .unimplemented(Data())
+        )
+      ),
+      reducer: contactReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.resetAuthDismissed) {
+      $0.resetAuth = nil
+    }
+  }
+
   func testConfirmRequestTapped() {
     let contactData = "contact-data".data(using: .utf8)!
     let store = TestStore(
diff --git a/Examples/xx-messenger/Tests/ContactLookupFeatureTests/ContactLookupFeatureTests.swift b/Examples/xx-messenger/Tests/ContactLookupFeatureTests/ContactLookupFeatureTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..76dde8d07350e240ccf86acdbef175834851b56f
--- /dev/null
+++ b/Examples/xx-messenger/Tests/ContactLookupFeatureTests/ContactLookupFeatureTests.swift
@@ -0,0 +1,60 @@
+import ComposableArchitecture
+import XCTest
+import XXClient
+@testable import ContactLookupFeature
+
+final class ContactLookupFeatureTests: XCTestCase {
+  func testLookup() {
+    let id: Data = "1234".data(using: .utf8)!
+    var didLookupId: [Data] = []
+    let lookedUpContact = Contact.unimplemented("123data".data(using: .utf8)!)
+
+    let store = TestStore(
+      initialState: ContactLookupState(id: id),
+      reducer: contactLookupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.lookupContact.run = { id in
+      didLookupId.append(id)
+      return lookedUpContact
+    }
+
+    store.send(.lookupTapped) {
+      $0.isLookingUp = true
+      $0.failure = nil
+    }
+
+    XCTAssertEqual(didLookupId, [id])
+
+    store.receive(.didLookup(lookedUpContact)) {
+      $0.isLookingUp = false
+      $0.failure = nil
+    }
+  }
+
+  func testLookupFailure() {
+    let id: Data = "1234".data(using: .utf8)!
+    let failure = NSError(domain: "test", code: 0)
+
+    let store = TestStore(
+      initialState: ContactLookupState(id: id),
+      reducer: contactLookupReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.lookupContact.run = { _ in throw failure }
+
+    store.send(.lookupTapped) {
+      $0.isLookingUp = true
+      $0.failure = nil
+    }
+
+    store.receive(.didFail(failure)) {
+      $0.isLookingUp = false
+      $0.failure = failure.localizedDescription
+    }
+  }
+}
diff --git a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift
index a0c0291ef79865fa6c1f3d1ffeb25235bd520acf..9b3ac0d080ad8e23b8359ab8b3cf073433bed7d8 100644
--- a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift
@@ -2,6 +2,7 @@ import Combine
 import ComposableArchitecture
 import ContactFeature
 import CustomDump
+import MyContactFeature
 import XCTest
 import XXClient
 import XXMessengerClient
@@ -32,7 +33,7 @@ final class ContactsFeatureTests: XCTestCase {
       return e2e
     }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.fetchContactsPublisher.run = { query in
         didFetchContacts.append(query)
         return contactsPublisher.eraseToAnyPublisher()
@@ -94,4 +95,30 @@ final class ContactsFeatureTests: XCTestCase {
       $0.contact = nil
     }
   }
+
+  func testSelectMyContact() {
+    let store = TestStore(
+      initialState: ContactsState(),
+      reducer: contactsReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.myContactSelected) {
+      $0.myContact = MyContactState()
+    }
+  }
+
+  func testDismissMyContact() {
+    let store = TestStore(
+      initialState: ContactsState(
+        myContact: MyContactState()
+      ),
+      reducer: contactsReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.myContactDismissed) {
+      $0.myContact = nil
+    }
+  }
 }
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index fc671b1ec7406823bd903d07307c8146e948ed39..3a57414bb46df0058870adb5078aae2dfc41bf7c 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -1,6 +1,8 @@
 import AppCore
+import BackupFeature
 import ComposableArchitecture
 import ContactsFeature
+import CustomDump
 import RegisterFeature
 import UserSearchFeature
 import XCTest
@@ -23,11 +25,10 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.authHandler.run = { _ in Cancellable {} }
-    store.environment.messageListener.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
+    store.environment.messenger.isListeningForMessages.run = { false }
     store.environment.messenger.listenForMessages.run = { messengerDidListenForMessages += 1 }
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { false }
@@ -38,15 +39,10 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidConnect, 1)
     XCTAssertNoDifference(messengerDidListenForMessages, 1)
 
-    store.receive(.authHandler(.start))
-    store.receive(.messageListener(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartUnregistered)) {
       $0.register = RegisterState()
     }
-
-    store.send(.authHandler(.stop))
-    store.send(.messageListener(.stop))
   }
 
   func testMessengerStartRegistered() {
@@ -60,18 +56,20 @@ final class HomeFeatureTests: XCTestCase {
     var messengerDidConnect = 0
     var messengerDidListenForMessages = 0
     var messengerDidLogIn = 0
+    var messengerDidResumeBackup = 0
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.authHandler.run = { _ in Cancellable {} }
-    store.environment.messageListener.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
+    store.environment.messenger.isListeningForMessages.run = { false }
     store.environment.messenger.listenForMessages.run = { messengerDidListenForMessages += 1 }
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { true }
     store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
+    store.environment.messenger.isBackupRunning.run = { false }
+    store.environment.messenger.resumeBackup.run = { messengerDidResumeBackup += 1 }
     store.environment.messenger.cMix.get = {
       var cMix: CMix = .unimplemented
       cMix.addHealthCallback.run = { _ in Cancellable {} }
@@ -88,16 +86,13 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidConnect, 1)
     XCTAssertNoDifference(messengerDidListenForMessages, 1)
     XCTAssertNoDifference(messengerDidLogIn, 1)
+    XCTAssertNoDifference(messengerDidResumeBackup, 1)
 
-    store.receive(.authHandler(.start))
-    store.receive(.messageListener(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
-    store.send(.authHandler(.stop))
-    store.send(.messageListener(.stop))
   }
 
   func testRegisterFinished() {
@@ -114,13 +109,13 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.authHandler.run = { _ in Cancellable {} }
-    store.environment.messageListener.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { true }
+    store.environment.messenger.isListeningForMessages.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { true }
     store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
+    store.environment.messenger.isBackupRunning.run = { true }
     store.environment.messenger.cMix.get = {
       var cMix: CMix = .unimplemented
       cMix.addHealthCallback.run = { _ in Cancellable {} }
@@ -140,15 +135,11 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
-    store.receive(.authHandler(.start))
-    store.receive(.messageListener(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
-    store.send(.authHandler(.stop))
-    store.send(.messageListener(.stop))
   }
 
   func testMessengerStartFailure() {
@@ -163,21 +154,14 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.authHandler.run = { _ in Cancellable {} }
-    store.environment.messageListener.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in throw error }
 
     store.send(.messenger(.start))
 
-    store.receive(.authHandler(.start))
-    store.receive(.messageListener(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
-
-    store.send(.authHandler(.stop))
-    store.send(.messageListener(.stop))
   }
 
   func testMessengerStartConnectFailure() {
@@ -192,23 +176,16 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.authHandler.run = { _ in Cancellable {} }
-    store.environment.messageListener.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { throw error }
 
     store.send(.messenger(.start))
 
-    store.receive(.authHandler(.start))
-    store.receive(.messageListener(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
-
-    store.send(.authHandler(.stop))
-    store.send(.messageListener(.stop))
   }
 
   func testMessengerStartIsRegisteredFailure() {
@@ -223,24 +200,18 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.authHandler.run = { _ in Cancellable {} }
-    store.environment.messageListener.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
+    store.environment.messenger.isListeningForMessages.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { throw error }
 
     store.send(.messenger(.start))
 
-    store.receive(.authHandler(.start))
-    store.receive(.messageListener(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
-
-    store.send(.authHandler(.stop))
-    store.send(.messageListener(.stop))
   }
 
   func testMessengerStartLogInFailure() {
@@ -255,25 +226,19 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.authHandler.run = { _ in Cancellable {} }
-    store.environment.messageListener.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
+    store.environment.messenger.isListeningForMessages.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { true }
     store.environment.messenger.logIn.run = { throw error }
 
     store.send(.messenger(.start))
 
-    store.receive(.authHandler(.start))
-    store.receive(.messageListener(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
-
-    store.send(.authHandler(.stop))
-    store.send(.messageListener(.stop))
   }
 
   func testNetworkMonitorStart() {
@@ -385,7 +350,7 @@ final class HomeFeatureTests: XCTestCase {
       return e2e
     }
     store.environment.dbManager.getDB.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.fetchContacts.run = { query in
         dbDidFetchContacts.append(query)
         return [
@@ -546,85 +511,29 @@ final class HomeFeatureTests: XCTestCase {
     }
   }
 
-  func testAuthCallbacks() {
+  func testBackupButtonTapped() {
     let store = TestStore(
       initialState: HomeState(),
       reducer: homeReducer,
       environment: .unimplemented
     )
 
-    var didRunAuthHandler = 0
-    var didCancelAuthHandler = 0
-    var authHandlerOnError: [AuthCallbackHandler.OnError] = []
-
-    store.environment.mainQueue = .immediate
-    store.environment.bgQueue = .immediate
-    store.environment.authHandler.run = { onError in
-      didRunAuthHandler += 1
-      authHandlerOnError.append(onError)
-      return Cancellable { didCancelAuthHandler += 1 }
+    store.send(.backupButtonTapped) {
+      $0.backup = BackupState()
     }
-
-    store.send(.authHandler(.start))
-
-    XCTAssertNoDifference(didRunAuthHandler, 1)
-
-    struct AuthHandlerError: Error { var id: Int }
-    authHandlerOnError.first?(AuthHandlerError(id: 1))
-
-    store.receive(.authHandler(.failure(AuthHandlerError(id: 1) as NSError))) {
-      $0.authFailure = AuthHandlerError(id: 1).localizedDescription
-    }
-
-    store.send(.authHandler(.failureDismissed)) {
-      $0.authFailure = nil
-    }
-
-    store.send(.authHandler(.stop))
-
-    XCTAssertNoDifference(didCancelAuthHandler, 1)
-
-    authHandlerOnError.first?(AuthHandlerError(id: 2))
   }
 
-  func testMessageListener() {
+  func testDidDismissBackup() {
     let store = TestStore(
-      initialState: HomeState(),
+      initialState: HomeState(
+        backup: BackupState()
+      ),
       reducer: homeReducer,
       environment: .unimplemented
     )
 
-    var didRunMessageListener = 0
-    var didCancelMessageListener = 0
-    var messageListenerOnError: [MessageListenerHandler.OnError] = []
-
-    store.environment.mainQueue = .immediate
-    store.environment.bgQueue = .immediate
-    store.environment.messageListener.run = { onError in
-      didRunMessageListener += 1
-      messageListenerOnError.append(onError)
-      return Cancellable { didCancelMessageListener += 1 }
+    store.send(.didDismissBackup) {
+      $0.backup = nil
     }
-
-    store.send(.messageListener(.start))
-
-    XCTAssertNoDifference(didRunMessageListener, 1)
-
-    struct MessageListenerError: Error { var id: Int }
-    messageListenerOnError.first?(MessageListenerError(id: 1))
-
-    store.receive(.messageListener(.failure(MessageListenerError(id: 1) as NSError))) {
-      $0.messageListenerFailure = MessageListenerError(id: 1).localizedDescription
-    }
-
-    store.send(.messageListener(.failureDismissed)) {
-      $0.messageListenerFailure = nil
-    }
-
-    store.send(.messageListener(.stop))
-
-    XCTAssertNoDifference(didCancelMessageListener, 1)
-
-    messageListenerOnError.first?(MessageListenerError(id: 2))
   }
 }
diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..830e97156363b151fbff21276169d8dfc2095720
--- /dev/null
+++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift
@@ -0,0 +1,764 @@
+import Combine
+import ComposableArchitecture
+import CustomDump
+import XCTest
+import XXClient
+import XXMessengerClient
+import XXModels
+@testable import MyContactFeature
+
+final class MyContactFeatureTests: XCTestCase {
+  func testStart() {
+    let contactId = "contact-id".data(using: .utf8)!
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    var dbDidFetchContacts: [XXModels.Contact.Query] = []
+    let dbContactsPublisher = PassthroughSubject<[XXModels.Contact], Error>()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactId }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .unimplemented
+      db.fetchContactsPublisher.run = { query in
+        dbDidFetchContacts.append(query)
+        return dbContactsPublisher.eraseToAnyPublisher()
+      }
+      return db
+    }
+
+    store.send(.start)
+
+    XCTAssertNoDifference(dbDidFetchContacts, [.init(id: [contactId])])
+
+    dbContactsPublisher.send([])
+
+    store.receive(.contactFetched(nil))
+
+    let contact = XXModels.Contact(id: contactId)
+    dbContactsPublisher.send([contact])
+
+    store.receive(.contactFetched(contact)) {
+      $0.contact = contact
+    }
+
+    dbContactsPublisher.send(completion: .finished)
+  }
+
+  func testRegisterEmail() {
+    let email = "test@email.com"
+    let confirmationID = "123"
+
+    var didSendRegisterFact: [Fact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.sendRegisterFact.run = { fact in
+        didSendRegisterFact.append(fact)
+        return confirmationID
+      }
+      return ud
+    }
+
+    store.send(.set(\.$focusedField, .email)) {
+      $0.focusedField = .email
+    }
+
+    store.send(.set(\.$email, email)) {
+      $0.email = email
+    }
+
+    store.send(.registerEmailTapped) {
+      $0.focusedField = nil
+      $0.isRegisteringEmail = true
+    }
+
+    XCTAssertNoDifference(didSendRegisterFact, [.init(type: .email, value: email)])
+
+    store.receive(.set(\.$emailConfirmationID, confirmationID)) {
+      $0.emailConfirmationID = confirmationID
+    }
+
+    store.receive(.set(\.$isRegisteringEmail, false)) {
+      $0.isRegisteringEmail = false
+    }
+  }
+
+  func testRegisterEmailFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.sendRegisterFact.run = { _ in throw failure }
+      return ud
+    }
+
+    store.send(.registerEmailTapped) {
+      $0.isRegisteringEmail = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isRegisteringEmail, false)) {
+      $0.isRegisteringEmail = false
+    }
+  }
+
+  func testConfirmEmail() {
+    let contactID = "contact-id".data(using: .utf8)!
+    let email = "test@email.com"
+    let confirmationID = "123"
+    let confirmationCode = "321"
+    let dbContact = XXModels.Contact(id: contactID)
+
+    var didConfirmWithID: [String] = []
+    var didConfirmWithCode: [String] = []
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(
+        email: email,
+        emailConfirmationID: confirmationID
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.confirmFact.run = { id, code in
+        didConfirmWithID.append(id)
+        didConfirmWithCode.append(code)
+      }
+      return ud
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactID }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .unimplemented
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.set(\.$focusedField, .emailCode)) {
+      $0.focusedField = .emailCode
+    }
+
+    store.send(.set(\.$emailConfirmationCode, confirmationCode)) {
+      $0.emailConfirmationCode = confirmationCode
+    }
+
+    store.send(.confirmEmailTapped) {
+      $0.focusedField = nil
+      $0.isConfirmingEmail = true
+    }
+
+    XCTAssertNoDifference(didConfirmWithID, [confirmationID])
+    XCTAssertNoDifference(didConfirmWithCode, [confirmationCode])
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.email = email
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$email, "")) {
+      $0.email = ""
+    }
+    store.receive(.set(\.$emailConfirmationID, nil)) {
+      $0.emailConfirmationID = nil
+    }
+    store.receive(.set(\.$emailConfirmationCode, "")) {
+      $0.emailConfirmationCode = ""
+    }
+    store.receive(.set(\.$isConfirmingEmail, false)) {
+      $0.isConfirmingEmail = false
+    }
+  }
+
+  func testConfirmEmailFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(
+        emailConfirmationID: "123"
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.confirmFact.run = { _, _ in throw failure }
+      return ud
+    }
+
+    store.send(.confirmEmailTapped) {
+      $0.isConfirmingEmail = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isConfirmingEmail, false)) {
+      $0.isConfirmingEmail = false
+    }
+  }
+
+  func testUnregisterEmail() {
+    let contactID = "contact-id".data(using: .utf8)!
+    let email = "test@email.com"
+    let dbContact = XXModels.Contact(id: contactID, email: email)
+
+    var didRemoveFact: [Fact] = []
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(
+        contact: dbContact
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.removeFact.run = { didRemoveFact.append($0) }
+      return ud
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactID }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .unimplemented
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.unregisterEmailTapped) {
+      $0.isUnregisteringEmail = true
+    }
+
+    XCTAssertNoDifference(didRemoveFact, [.init(type: .email, value: email)])
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.email = nil
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$isUnregisteringEmail, false)) {
+      $0.isUnregisteringEmail = false
+    }
+  }
+
+  func testUnregisterEmailFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(
+        contact: .init(id: Data(), email: "test@email.com")
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.removeFact.run = { _ in throw failure }
+      return ud
+    }
+
+    store.send(.unregisterEmailTapped) {
+      $0.isUnregisteringEmail = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isUnregisteringEmail, false)) {
+      $0.isUnregisteringEmail = false
+    }
+  }
+
+  func testRegisterPhone() {
+    let phone = "+123456789"
+    let confirmationID = "123"
+
+    var didSendRegisterFact: [Fact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.sendRegisterFact.run = { fact in
+        didSendRegisterFact.append(fact)
+        return confirmationID
+      }
+      return ud
+    }
+
+    store.send(.set(\.$focusedField, .phone)) {
+      $0.focusedField = .phone
+    }
+
+    store.send(.set(\.$phone, phone)) {
+      $0.phone = phone
+    }
+
+    store.send(.registerPhoneTapped) {
+      $0.focusedField = nil
+      $0.isRegisteringPhone = true
+    }
+
+    XCTAssertNoDifference(didSendRegisterFact, [.init(type: .phone, value: phone)])
+
+    store.receive(.set(\.$phoneConfirmationID, confirmationID)) {
+      $0.phoneConfirmationID = confirmationID
+    }
+
+    store.receive(.set(\.$isRegisteringPhone, false)) {
+      $0.isRegisteringPhone = false
+    }
+  }
+
+  func testRegisterPhoneFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.sendRegisterFact.run = { _ in throw failure }
+      return ud
+    }
+
+    store.send(.registerPhoneTapped) {
+      $0.isRegisteringPhone = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isRegisteringPhone, false)) {
+      $0.isRegisteringPhone = false
+    }
+  }
+
+  func testConfirmPhone() {
+    let contactID = "contact-id".data(using: .utf8)!
+    let phone = "+123456789"
+    let confirmationID = "123"
+    let confirmationCode = "321"
+    let dbContact = XXModels.Contact(id: contactID)
+
+    var didConfirmWithID: [String] = []
+    var didConfirmWithCode: [String] = []
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(
+        phone: phone,
+        phoneConfirmationID: confirmationID
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.confirmFact.run = { id, code in
+        didConfirmWithID.append(id)
+        didConfirmWithCode.append(code)
+      }
+      return ud
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactID }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .unimplemented
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.set(\.$focusedField, .phoneCode)) {
+      $0.focusedField = .phoneCode
+    }
+
+    store.send(.set(\.$phoneConfirmationCode, confirmationCode)) {
+      $0.phoneConfirmationCode = confirmationCode
+    }
+
+    store.send(.confirmPhoneTapped) {
+      $0.focusedField = nil
+      $0.isConfirmingPhone = true
+    }
+
+    XCTAssertNoDifference(didConfirmWithID, [confirmationID])
+    XCTAssertNoDifference(didConfirmWithCode, [confirmationCode])
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.phone = phone
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$phone, "")) {
+      $0.phone = ""
+    }
+    store.receive(.set(\.$phoneConfirmationID, nil)) {
+      $0.phoneConfirmationID = nil
+    }
+    store.receive(.set(\.$phoneConfirmationCode, "")) {
+      $0.phoneConfirmationCode = ""
+    }
+    store.receive(.set(\.$isConfirmingPhone, false)) {
+      $0.isConfirmingPhone = false
+    }
+  }
+
+  func testConfirmPhoneFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(
+        phoneConfirmationID: "123"
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.confirmFact.run = { _, _ in throw failure }
+      return ud
+    }
+
+    store.send(.confirmPhoneTapped) {
+      $0.isConfirmingPhone = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isConfirmingPhone, false)) {
+      $0.isConfirmingPhone = false
+    }
+  }
+  
+  func testUnregisterPhone() {
+    let contactID = "contact-id".data(using: .utf8)!
+    let phone = "+123456789"
+    let dbContact = XXModels.Contact(id: contactID, phone: phone)
+
+    var didRemoveFact: [Fact] = []
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(
+        contact: dbContact
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.removeFact.run = { didRemoveFact.append($0) }
+      return ud
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactID }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .unimplemented
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.unregisterPhoneTapped) {
+      $0.isUnregisteringPhone = true
+    }
+
+    XCTAssertNoDifference(didRemoveFact, [.init(type: .phone, value: phone)])
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.phone = nil
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$isUnregisteringPhone, false)) {
+      $0.isUnregisteringPhone = false
+    }
+  }
+
+  func testUnregisterPhoneFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(
+        contact: .init(id: Data(), phone: "+123456789")
+      ),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.removeFact.run = { _ in throw failure }
+      return ud
+    }
+
+    store.send(.unregisterPhoneTapped) {
+      $0.isUnregisteringPhone = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isUnregisteringPhone, false)) {
+      $0.isUnregisteringPhone = false
+    }
+  }
+
+  func testLoadFactsFromClient() {
+    let contactId = "contact-id".data(using: .utf8)!
+    let dbContact = XXModels.Contact(id: contactId)
+    let username = "user234"
+    let email = "test@email.com"
+    let phone = "123456789"
+
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactId }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getFacts.run = {
+        [
+          Fact(type: .username, value: username),
+          Fact(type: .email, value: email),
+          Fact(type: .phone, value: phone),
+        ]
+      }
+      return ud
+    }
+    store.environment.db.run = {
+      var db: Database = .unimplemented
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return [dbContact]
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.loadFactsTapped) {
+      $0.isLoadingFacts = true
+    }
+
+    XCTAssertNoDifference(didFetchContacts, [.init(id: [contactId])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.username = username
+    expectedSavedContact.email = email
+    expectedSavedContact.phone = phone
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+
+    store.receive(.set(\.$isLoadingFacts, false)) {
+      $0.isLoadingFacts = false
+    }
+  }
+
+  func testLoadFactsFromClientFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in throw failure }
+        return contact
+      }
+      return e2e
+    }
+
+    store.send(.loadFactsTapped) {
+      $0.isLoadingFacts = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.alert = .error(failure.localizedDescription)
+    }
+
+    store.receive(.set(\.$isLoadingFacts, false)) {
+      $0.isLoadingFacts = false
+    }
+  }
+
+  func testErrorAlert() {
+    let store = TestStore(
+      initialState: MyContactState(),
+      reducer: myContactReducer,
+      environment: .unimplemented
+    )
+
+    let failure = "Something went wrong"
+
+    store.send(.didFail(failure)) {
+      $0.alert = .error(failure)
+    }
+
+    store.send(.alertDismissed) {
+      $0.alert = nil
+    }
+  }
+}
diff --git a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
index 6c802874d64bbd0dcf7e0ac5b9fee0de7e136d8f..e0ffc5ef4fe9a3bc5f9d9f91d288025cd1065c85 100644
--- a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
@@ -1,4 +1,5 @@
 import ComposableArchitecture
+import CustomDump
 import XCTest
 import XXClient
 import XXMessengerClient
@@ -7,41 +8,43 @@ import XXModels
 
 final class RegisterFeatureTests: XCTestCase {
   func testRegister() throws {
+    let now = Date()
+    let username = "registering-username"
+    let myContactId = "my-contact-id".data(using: .utf8)!
+    let myContactData = "my-contact-data".data(using: .utf8)!
+    let myContactUsername = username
+    let myContactEmail = "my-contact-email"
+    let myContactPhone = "my-contact-phone"
+    let myContactFacts = [
+      Fact(type: .username, value: myContactUsername),
+      Fact(type: .email, value: myContactEmail),
+      Fact(type: .phone, value: myContactPhone),
+    ]
+
+    var messengerDidRegisterUsername: [String] = []
+    var didGetMyContact: [MessengerMyContact.IncludeFacts?] = []
+    var dbDidSaveContact: [XXModels.Contact] = []
+
     let store = TestStore(
       initialState: RegisterState(),
       reducer: registerReducer,
       environment: .unimplemented
     )
-
-    let now = Date()
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    var didSetFactsOnContact: [[XXClient.Fact]] = []
-    var dbDidSaveContact: [XXModels.Contact] = []
-    var messengerDidRegisterUsername: [String] = []
-
     store.environment.now = { now }
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
     store.environment.messenger.register.run = { username in
       messengerDidRegisterUsername.append(username)
     }
-    store.environment.messenger.e2e.get = {
-      var e2e: E2E = .unimplemented
-      e2e.getContact.run = {
-        var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
-        contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! }
-        contact.getFactsFromContact.run = { _ in [] }
-        contact.setFactsOnContact.run = { data, facts in
-          didSetFactsOnContact.append(facts)
-          return data
-        }
-        return contact
-      }
-      return e2e
+    store.environment.messenger.myContact.run = { includeFacts in
+      didGetMyContact.append(includeFacts)
+      var contact = XXClient.Contact.unimplemented(myContactData)
+      contact.getIdFromContact.run = { _ in myContactId }
+      contact.getFactsFromContact.run = { _ in myContactFacts }
+      return contact
     }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.saveContact.run = { contact in
         dbDidSaveContact.append(contact)
         return contact
@@ -49,33 +52,34 @@ final class RegisterFeatureTests: XCTestCase {
       return db
     }
 
-    store.send(.set(\.$username, "NewUser")) {
-      $0.username = "NewUser"
+    store.send(.set(\.$focusedField, .username)) {
+      $0.focusedField = .username
+    }
+
+    store.send(.set(\.$username, myContactUsername)) {
+      $0.username = myContactUsername
     }
 
     store.send(.registerTapped) {
+      $0.focusedField = nil
       $0.isRegistering = true
     }
 
-    XCTAssertNoDifference(messengerDidRegisterUsername, [])
-    XCTAssertNoDifference(dbDidSaveContact, [])
-
-    bgQueue.advance()
-
-    XCTAssertNoDifference(messengerDidRegisterUsername, ["NewUser"])
-    XCTAssertNoDifference(didSetFactsOnContact, [[Fact(type: .username, value: "NewUser")]])
+    XCTAssertNoDifference(messengerDidRegisterUsername, [username])
     XCTAssertNoDifference(dbDidSaveContact, [
       XXModels.Contact(
-        id: "contact-id".data(using: .utf8)!,
-        marshaled: "contact-data".data(using: .utf8)!,
-        username: "NewUser",
+        id: myContactId,
+        marshaled: myContactData,
+        username: myContactUsername,
+        email: myContactEmail,
+        phone: myContactPhone,
         createdAt: now
       )
     ])
 
-    mainQueue.advance()
-
-    store.receive(.finished)
+    store.receive(.finished) {
+      $0.isRegistering = false
+    }
   }
 
   func testGetDbFailure() throws {
@@ -123,7 +127,7 @@ final class RegisterFeatureTests: XCTestCase {
 
     store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
     store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.db.run = { .failing }
+    store.environment.db.run = { .unimplemented }
     store.environment.messenger.register.run = { _ in throw error }
 
     store.send(.registerTapped) {
@@ -138,4 +142,78 @@ final class RegisterFeatureTests: XCTestCase {
       $0.failure = error.localizedDescription
     }
   }
+
+  func testRegisterUsernameMismatchFailure() throws {
+    let now = Date()
+    let username = "registering-username"
+    let myContactId = "my-contact-id".data(using: .utf8)!
+    let myContactData = "my-contact-data".data(using: .utf8)!
+    let myContactUsername = "my-contact-username"
+    let myContactEmail = "my-contact-email"
+    let myContactPhone = "my-contact-phone"
+    let myContactFacts = [
+      Fact(type: .username, value: myContactUsername),
+      Fact(type: .email, value: myContactEmail),
+      Fact(type: .phone, value: myContactPhone),
+    ]
+
+    var messengerDidRegisterUsername: [String] = []
+    var didGetMyContact: [MessengerMyContact.IncludeFacts?] = []
+    var dbDidSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: RegisterState(
+        username: username
+      ),
+      reducer: registerReducer,
+      environment: .unimplemented
+    )
+    store.environment.now = { now }
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.register.run = { username in
+      messengerDidRegisterUsername.append(username)
+    }
+    store.environment.messenger.myContact.run = { includeFacts in
+      didGetMyContact.append(includeFacts)
+      var contact = XXClient.Contact.unimplemented(myContactData)
+      contact.getIdFromContact.run = { _ in myContactId }
+      contact.getFactsFromContact.run = { _ in myContactFacts }
+      return contact
+    }
+    store.environment.db.run = {
+      var db: Database = .unimplemented
+      db.saveContact.run = { contact in
+        dbDidSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.registerTapped) {
+      $0.focusedField = nil
+      $0.isRegistering = true
+    }
+
+    XCTAssertNoDifference(messengerDidRegisterUsername, [username])
+    XCTAssertNoDifference(dbDidSaveContact, [
+      XXModels.Contact(
+        id: myContactId,
+        marshaled: myContactData,
+        username: myContactUsername,
+        email: myContactEmail,
+        phone: myContactPhone,
+        createdAt: now
+      )
+    ])
+
+    let failure = RegisterState.Error.usernameMismatch(
+      registering: username,
+      registered: myContactUsername
+    )
+    store.receive(.failed(failure.localizedDescription)) {
+      $0.isRegistering = false
+      $0.failure = failure.localizedDescription
+    }
+  }
 }
diff --git a/Examples/xx-messenger/Tests/ResetAuthFeatureTests/ResetAuthFeatureTests.swift b/Examples/xx-messenger/Tests/ResetAuthFeatureTests/ResetAuthFeatureTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..46b5dc9e402d5c2cb71a796790b205732876a658
--- /dev/null
+++ b/Examples/xx-messenger/Tests/ResetAuthFeatureTests/ResetAuthFeatureTests.swift
@@ -0,0 +1,72 @@
+import ComposableArchitecture
+import CustomDump
+import XCTest
+import XXClient
+@testable import ResetAuthFeature
+
+final class ResetAuthFeatureTests: XCTestCase {
+  func testReset() {
+    let partnerData = "contact-data".data(using: .utf8)!
+    let partner = Contact.unimplemented(partnerData)
+
+    var didResetAuthChannel: [Contact] = []
+
+    let store = TestStore(
+      initialState: ResetAuthState(
+        partner: partner
+      ),
+      reducer: resetAuthReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.resetAuthenticatedChannel.run = { contact in
+        didResetAuthChannel.append(contact)
+        return 0
+      }
+      return e2e
+    }
+
+    store.send(.resetTapped) {
+      $0.isResetting = true
+    }
+
+    XCTAssertNoDifference(didResetAuthChannel, [partner])
+
+    store.receive(.didReset) {
+      $0.isResetting = false
+      $0.didReset = true
+    }
+  }
+
+  func testResetFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: ResetAuthState(
+        partner: .unimplemented(Data())
+      ),
+      reducer: resetAuthReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.resetAuthenticatedChannel.run = { _ in throw failure }
+      return e2e
+    }
+
+    store.send(.resetTapped) {
+      $0.isResetting = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.isResetting = false
+      $0.failure = failure.localizedDescription
+    }
+  }
+}
diff --git a/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift b/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift
index c3e77edbf64de2d8df6e3cfb518b610055928397..716d7650255b277cad37fb40ad5a73c4aee5bace 100644
--- a/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift
@@ -1,15 +1,233 @@
 import ComposableArchitecture
+import CustomDump
 import XCTest
+import XXClient
+import XXMessengerClient
+import XXModels
 @testable import RestoreFeature
 
 final class RestoreFeatureTests: XCTestCase {
-  func testFinish() {
+  func testFileImport() {
+    let fileURL = URL(string: "file-url")!
+    var didLoadDataFromURL: [URL] = []
+    let dataFromURL = "data-from-url".data(using: .utf8)!
+
     let store = TestStore(
       initialState: RestoreState(),
       reducer: restoreReducer,
       environment: .unimplemented
     )
 
-    store.send(.finished)
+    store.environment.loadData.load = { url in
+      didLoadDataFromURL.append(url)
+      return dataFromURL
+    }
+
+    store.send(.importFileTapped) {
+      $0.isImportingFile = true
+    }
+
+    store.send(.fileImport(.success(fileURL))) {
+      $0.isImportingFile = false
+      $0.file = .init(name: fileURL.lastPathComponent, data: dataFromURL)
+      $0.fileImportFailure = nil
+    }
+
+    XCTAssertNoDifference(didLoadDataFromURL, [fileURL])
+  }
+
+  func testFileImportFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: RestoreState(
+        isImportingFile: true
+      ),
+      reducer: restoreReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.fileImport(.failure(failure as NSError))) {
+      $0.isImportingFile = false
+      $0.file = nil
+      $0.fileImportFailure = failure.localizedDescription
+    }
+  }
+
+  func testFileImportLoadingFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: RestoreState(
+        isImportingFile: true
+      ),
+      reducer: restoreReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.loadData.load = { _ in throw failure }
+
+    store.send(.fileImport(.success(URL(string: "test")!))) {
+      $0.isImportingFile = false
+      $0.file = nil
+      $0.fileImportFailure = failure.localizedDescription
+    }
+  }
+
+  func testRestore() {
+    let backupData = "backup-data".data(using: .utf8)!
+    let backupPassphrase = "backup-passphrase"
+    let restoredFacts = [
+      Fact(type: .username, value: "restored-fact-username"),
+      Fact(type: .email, value: "restored-fact-email"),
+      Fact(type: .phone, value: "restored-fact-phone"),
+    ]
+    let restoreResult = MessengerRestoreBackup.Result(
+      restoredParams: BackupParams(username: "restored-param-username"),
+      restoredContacts: [
+        "contact-1-id".data(using: .utf8)!,
+        "contact-2-id".data(using: .utf8)!,
+        "contact-3-id".data(using: .utf8)!,
+      ]
+    )
+    let now = Date()
+    let contactId = "contact-id".data(using: .utf8)!
+
+    var udFacts: [Fact] = []
+    var didRestoreWithData: [Data] = []
+    var didRestoreWithPassphrase: [String] = []
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let store = TestStore(
+      initialState: RestoreState(
+        file: .init(name: "file-name", data: backupData)
+      ),
+      reducer: restoreReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.now = { now }
+    store.environment.messenger.restoreBackup.run = { data, passphrase in
+      didRestoreWithData.append(data)
+      didRestoreWithPassphrase.append(passphrase)
+      udFacts = restoredFacts
+      return restoreResult
+    }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: XXClient.Contact = .unimplemented(Data())
+        contact.getIdFromContact.run = { _ in contactId }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getFacts.run = { udFacts }
+      return ud
+    }
+    store.environment.db.run = {
+      var db: Database = .unimplemented
+      db.fetchContacts.run = { query in
+        didFetchContacts.append(query)
+        return []
+      }
+      db.saveContact.run = { contact in
+        didSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.set(\.$passphrase, backupPassphrase)) {
+      $0.passphrase = backupPassphrase
+    }
+
+    store.send(.restoreTapped) {
+      $0.isRestoring = true
+    }
+
+    XCTAssertNoDifference(didRestoreWithData, [backupData])
+    XCTAssertNoDifference(didRestoreWithPassphrase, [backupPassphrase])
+    XCTAssertNoDifference(didFetchContacts, [
+      .init(id: [restoreResult.restoredContacts[0]]),
+      .init(id: [restoreResult.restoredContacts[1]]),
+      .init(id: [restoreResult.restoredContacts[2]]),
+    ])
+    XCTAssertNoDifference(didSaveContact, [
+      Contact(
+        id: contactId,
+        username: restoredFacts.get(.username)?.value,
+        email: restoredFacts.get(.email)?.value,
+        phone: restoredFacts.get(.phone)?.value,
+        createdAt: now
+      ),
+      Contact(
+        id: restoreResult.restoredContacts[0],
+        createdAt: now
+      ),
+      Contact(
+        id: restoreResult.restoredContacts[1],
+        createdAt: now
+      ),
+      Contact(
+        id: restoreResult.restoredContacts[2],
+        createdAt: now
+      ),
+    ])
+
+    store.receive(.finished) {
+      $0.isRestoring = false
+    }
+  }
+
+  func testRestoreWithoutFile() {
+    let store = TestStore(
+      initialState: RestoreState(
+        file: nil
+      ),
+      reducer: restoreReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.restoreTapped)
+  }
+
+  func testRestoreFailure() {
+    enum Failure: Error {
+      case restore
+      case destroy
+    }
+
+    let store = TestStore(
+      initialState: RestoreState(
+        file: .init(name: "name", data: "data".data(using: .utf8)!)
+      ),
+      reducer: restoreReducer,
+      environment: .unimplemented
+    )
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.restoreBackup.run = { _, _ in throw Failure.restore }
+    store.environment.messenger.destroy.run = { throw Failure.destroy }
+
+    store.send(.restoreTapped) {
+      $0.isRestoring = true
+    }
+
+    store.receive(.failed([Failure.restore as NSError, Failure.destroy as NSError])) {
+      $0.isRestoring = false
+      $0.restoreFailures = [
+        Failure.restore.localizedDescription,
+        Failure.destroy.localizedDescription,
+      ]
+    }
   }
 }
diff --git a/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift
index cec3587e845624edc68ef537b00604b8a577c13c..4c68abab06a609346b47b4fe472d5f377417c2f5 100644
--- a/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift
@@ -1,12 +1,18 @@
 import Combine
 import ComposableArchitecture
+import CustomDump
 import XCTest
 import XXClient
+import XXMessengerClient
 import XXModels
 @testable import SendRequestFeature
 
 final class SendRequestFeatureTests: XCTestCase {
   func testStart() {
+    let myContact = XXClient.Contact.unimplemented("my-contact-data".data(using: .utf8)!)
+
+    var didGetMyContact: [MessengerMyContact.IncludeFacts?] = []
+
     let store = TestStore(
       initialState: SendRequestState(
         contact: .unimplemented("contact-data".data(using: .utf8)!)
@@ -14,47 +20,41 @@ final class SendRequestFeatureTests: XCTestCase {
       reducer: sendRequestReducer,
       environment: .unimplemented
     )
-
-    var dbDidFetchContacts: [XXModels.Contact.Query] = []
-    let dbContactsPublisher = PassthroughSubject<[XXModels.Contact], Error>()
-
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
-    store.environment.messenger.e2e.get = {
-      var e2e: E2E = .unimplemented
-      e2e.getContact.run = {
-        var contact: XXClient.Contact = .unimplemented("my-contact-data".data(using: .utf8)!)
-        contact.getIdFromContact.run = { _ in "my-contact-id".data(using: .utf8)! }
-        return contact
-      }
-      return e2e
-    }
-    store.environment.db.run = {
-      var db: Database = .failing
-      db.fetchContactsPublisher.run = { query in
-        dbDidFetchContacts.append(query)
-        return dbContactsPublisher.eraseToAnyPublisher()
-      }
-      return db
+    store.environment.messenger.myContact.run = { includeFacts in
+      didGetMyContact.append(includeFacts)
+      return myContact
     }
 
     store.send(.start)
 
-    XCTAssertNoDifference(dbDidFetchContacts, [.init(id: ["my-contact-id".data(using: .utf8)!])])
+    store.receive(.myContactFetched(myContact)) {
+      $0.myContact = myContact
+    }
+  }
 
-    dbContactsPublisher.send([])
+  func testMyContactFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
 
-    store.receive(.myContactFetched(nil))
+    let store = TestStore(
+      initialState: SendRequestState(
+        contact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
+      reducer: sendRequestReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.myContact.run = { _ in throw failure }
 
-    var myDbContact = XXModels.Contact(id: "my-contact-id".data(using: .utf8)!)
-    myDbContact.marshaled = "my-contact-data".data(using: .utf8)!
-    dbContactsPublisher.send([myDbContact])
+    store.send(.start)
 
-    store.receive(.myContactFetched(.live("my-contact-data".data(using: .utf8)!))) {
-      $0.myContact = .live("my-contact-data".data(using: .utf8)!)
+    store.receive(.myContactFetchFailed(failure as NSError)) {
+      $0.myContact = nil
+      $0.failure = failure.localizedDescription
     }
-
-    dbContactsPublisher.send(completion: .finished)
   }
 
   func testSendRequest() {
@@ -93,7 +93,7 @@ final class SendRequestFeatureTests: XCTestCase {
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.bulkUpdateContacts.run = { query, assignments in
         didBulkUpdateContacts.append(.init(query: query, assignments: assignments))
         return 0
@@ -163,7 +163,7 @@ final class SendRequestFeatureTests: XCTestCase {
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.bulkUpdateContacts.run = { _, _ in return 0 }
       return db
     }
diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
index 44731c1f16d41e0b95ab3ff1675f654d1ab2fcd5..33f1edb9d61d7186503b63f90c1f73f11251185f 100644
--- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
@@ -1,5 +1,6 @@
 import ComposableArchitecture
 import ContactFeature
+import CustomDump
 import XCTest
 import XXClient
 import XXMessengerClient
@@ -64,6 +65,8 @@ final class UserSearchFeatureTests: XCTestCase {
       $0.failure = nil
     }
 
+    XCTAssertNoDifference(didSearchWithQuery, [.init(username: "Username")])
+
     store.receive(.didSucceed(contacts)) {
       $0.isSearching = false
       $0.failure = nil
diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
index 97f8b428e53ffc63ae123bf9f8510594cc0f5daf..ceaa61cc3f215234aea66381b4c7dc17965bb1a4 100644
--- a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift
@@ -30,7 +30,7 @@ final class VerifyContactFeatureTests: XCTestCase {
       return true
     }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.bulkUpdateContacts.run = { query, assignments in
         didBulkUpdateContactsWithQuery.append(query)
         didBulkUpdateContactsWithAssignments.append(assignments)
@@ -84,7 +84,7 @@ final class VerifyContactFeatureTests: XCTestCase {
       return false
     }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.bulkUpdateContacts.run = { query, assignments in
         didBulkUpdateContactsWithQuery.append(query)
         didBulkUpdateContactsWithAssignments.append(assignments)
@@ -137,7 +137,7 @@ final class VerifyContactFeatureTests: XCTestCase {
     store.environment.bgQueue = .immediate
     store.environment.messenger.verifyContact.run = { _ in throw error }
     store.environment.db.run = {
-      var db: Database = .failing
+      var db: Database = .unimplemented
       db.bulkUpdateContacts.run = { query, assignments in
         didBulkUpdateContactsWithQuery.append(query)
         didBulkUpdateContactsWithAssignments.append(assignments)
diff --git a/Examples/xx-messenger/Tests/WelcomeFeatureTests/WelcomeFeatureTests.swift b/Examples/xx-messenger/Tests/WelcomeFeatureTests/WelcomeFeatureTests.swift
index eb6b08566af0f4193c10b5167271bd86b2897330..c6f23b7a6c885a0b8728796f6e3fd41f3a018aa1 100644
--- a/Examples/xx-messenger/Tests/WelcomeFeatureTests/WelcomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/WelcomeFeatureTests/WelcomeFeatureTests.swift
@@ -5,60 +5,65 @@ import XCTest
 @MainActor
 final class WelcomeFeatureTests: XCTestCase {
   func testNewAccountTapped() {
+    let mainQueue = DispatchQueue.test
+    let bgQueue = DispatchQueue.test
+
+    var didCreateMessenger = 0
+
     let store = TestStore(
       initialState: WelcomeState(),
       reducer: welcomeReducer,
       environment: .unimplemented
     )
 
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    var messengerDidCreate = false
-
     store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
     store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.messenger.create.run = { messengerDidCreate = true }
+    store.environment.messenger.create.run = { didCreateMessenger += 1 }
 
     store.send(.newAccountTapped) {
       $0.isCreatingAccount = true
+      $0.failure = nil
     }
 
     bgQueue.advance()
 
-    XCTAssertTrue(messengerDidCreate)
+    XCTAssertNoDifference(didCreateMessenger, 1)
 
     mainQueue.advance()
 
     store.receive(.finished) {
       $0.isCreatingAccount = false
+      $0.failure = nil
     }
   }
 
   func testNewAccountTappedMessengerCreateFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+    let mainQueue = DispatchQueue.test
+    let bgQueue = DispatchQueue.test
+
     let store = TestStore(
       initialState: WelcomeState(),
       reducer: welcomeReducer,
       environment: .unimplemented
     )
 
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    struct Error: Swift.Error, Equatable {}
-    let error = Error()
-
     store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
     store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.messenger.create.run = { throw error }
+    store.environment.messenger.create.run = { throw failure }
 
     store.send(.newAccountTapped) {
       $0.isCreatingAccount = true
+      $0.failure = nil
     }
 
     bgQueue.advance()
     mainQueue.advance()
 
-    store.receive(.failed(error.localizedDescription)) {
+    store.receive(.failed(failure.localizedDescription)) {
       $0.isCreatingAccount = false
+      $0.failure = failure.localizedDescription
     }
   }
 
diff --git a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 45de0ce85881cbcd30f418b3ac71b0983257de07..51664bd603e976bab27b158f1ef45ac937dd18b7 100644
--- a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -5,8 +5,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://git.xx.network/elixxir/client-ios-db.git",
       "state" : {
-        "revision" : "f8e3e0088de8301d6c4816e12f0aca1d6f02a280",
-        "version" : "1.1.0"
+        "revision" : "16480177beaa6bc80a1c6be25812abd9bcb850ea",
+        "version" : "1.2.0"
       }
     },
     {
@@ -23,8 +23,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/groue/GRDB.swift",
       "state" : {
-        "revision" : "dd7e7f39e8e4d7a22d258d9809a882f914690b01",
-        "version" : "5.26.1"
+        "revision" : "0ac435744a4c67c4ec23a4a671c0d53ce1fee7c6",
+        "version" : "6.0.0"
       }
     },
     {
@@ -36,6 +36,15 @@
         "version" : "4.2.2"
       }
     },
+    {
+      "identity" : "pulse",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/kean/Pulse.git",
+      "state" : {
+        "revision" : "6b682c529d98a38e6fdffee2a8bfa40c8de30821",
+        "version" : "2.1.3"
+      }
+    },
     {
       "identity" : "swift-case-paths",
       "kind" : "remoteSourceControl",
@@ -59,8 +68,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/swift-composable-architecture.git",
       "state" : {
-        "revision" : "cbe013b42b3c368957f8f882c960b93845e1589d",
-        "version" : "0.40.1"
+        "revision" : "9ea8c763061287052a68d5e6723fed45e898b7d9",
+        "version" : "0.40.2"
       }
     },
     {
@@ -75,10 +84,10 @@
     {
       "identity" : "swift-custom-dump",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/pointfreeco/swift-custom-dump",
+      "location" : "https://github.com/pointfreeco/swift-custom-dump.git",
       "state" : {
-        "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7",
-        "version" : "0.5.0"
+        "revision" : "819d9d370cd721c9d87671e29d947279292e4541",
+        "version" : "0.6.0"
       }
     },
     {
@@ -90,13 +99,22 @@
         "version" : "0.4.1"
       }
     },
+    {
+      "identity" : "swift-log",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-log.git",
+      "state" : {
+        "revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c",
+        "version" : "1.4.4"
+      }
+    },
     {
       "identity" : "xctest-dynamic-overlay",
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
       "state" : {
-        "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260",
-        "version" : "0.4.0"
+        "revision" : "30314f1ece684dd60679d598a9b89107557b67d9",
+        "version" : "0.4.1"
       }
     }
   ],
diff --git a/Examples/xx-messenger/bump-build-number.sh b/Examples/xx-messenger/bump-build-number.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f95dceaca1c95063eed05e6aa32388216376a0d1
--- /dev/null
+++ b/Examples/xx-messenger/bump-build-number.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+script_dir=$(dirname $(realpath $0))
+project_dir="$script_dir/Project"
+repo_dir="$script_dir/../../"
+
+if [ -n "$(git -C $repo_dir status --porcelain)" ]; then 
+  echo "Repository has uncommitted changes!"
+  exit 1
+fi
+
+cd $project_dir
+xcrun agvtool next-version
+build_number=$(xcrun agvtool what-version -terse)
+
+cd $repo_dir
+git commit -a -m "Bump xx-messenger example app build number to $build_number"
diff --git a/Frameworks/Bindings.txt b/Frameworks/Bindings.txt
index 9fba83df0bda30fec264e321e1a66f76665e3d7c..a5431f62c1ebe21d09d1dc4f609978ce97f7ee3d 100644
--- a/Frameworks/Bindings.txt
+++ b/Frameworks/Bindings.txt
@@ -1,4 +1,4 @@
-https://git.xx.network/elixxir/client/-/commit/7aac8e09168fc8fb33ace2263e8ee576724cf70f
+https://git.xx.network/elixxir/client/-/commit/730d7cd8e9333143cc9fd94de8611861450c9f82
 go version go1.17.13 darwin/arm64
-Xcode 14.0 Build version 14A309
+Xcode 14.0.1 Build version 14A400
 gomobile bind target: ios,iossimulator,macos
diff --git a/Frameworks/Bindings.xcframework/Info.plist b/Frameworks/Bindings.xcframework/Info.plist
index 3d81013ba0ba606da4b42b6f3119f688c6d9b97d..e66824e5243985b4ac053ef62aeaaf571fe03970 100644
--- a/Frameworks/Bindings.xcframework/Info.plist
+++ b/Frameworks/Bindings.xcframework/Info.plist
@@ -21,28 +21,28 @@
 		</dict>
 		<dict>
 			<key>LibraryIdentifier</key>
-			<string>macos-arm64_x86_64</string>
+			<string>ios-arm64</string>
 			<key>LibraryPath</key>
 			<string>Bindings.framework</string>
 			<key>SupportedArchitectures</key>
 			<array>
 				<string>arm64</string>
-				<string>x86_64</string>
 			</array>
 			<key>SupportedPlatform</key>
-			<string>macos</string>
+			<string>ios</string>
 		</dict>
 		<dict>
 			<key>LibraryIdentifier</key>
-			<string>ios-arm64</string>
+			<string>macos-arm64_x86_64</string>
 			<key>LibraryPath</key>
 			<string>Bindings.framework</string>
 			<key>SupportedArchitectures</key>
 			<array>
 				<string>arm64</string>
+				<string>x86_64</string>
 			</array>
 			<key>SupportedPlatform</key>
-			<string>ios</string>
+			<string>macos</string>
 		</dict>
 	</array>
 	<key>CFBundlePackageType</key>
diff --git a/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings b/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings
index fa210455a0a975408dbd63e78456ac6f9a8a4b70..11bc65b9458a47c5168c044c5c2e9aeb5079521a 100644
Binary files a/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings and b/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings differ
diff --git a/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h b/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h
index 96523f8825cf04317f17fab6d169b33c1e82c90c..73defbbf6e76c0043035efc5c5ec165747ff20cc 100644
--- a/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h
+++ b/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h
@@ -132,7 +132,7 @@ Parameters:
 @end
 
 @protocol BindingsGroupChatProcessor <NSObject>
-- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId err:(NSError* _Nullable)err;
+- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId roundUrl:(NSString* _Nullable)roundUrl err:(NSError* _Nullable)err;
 - (NSString* _Nonnull)string;
 @end
 
@@ -2272,29 +2272,24 @@ FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewOrLoadUd(long e2eI
 
 /**
  * NewUdManagerFromBackup builds a new user discover manager from a backup. It
-will construct a manager that is already registered and restore already
-registered facts into store.
+will construct a manager that is already registered. Confirmed facts have
+already been restored via the call NewCmixFromBackup.
 
 Parameters:
  - e2eID - e2e object ID in the tracker
  - follower - network follower func wrapped in UdNetworkStatus
- - username - The username this user registered with initially. This should
-              not be nullable, and be JSON marshalled as retrieved from
-              UserDiscovery.GetFacts().
- - emailFactJson - nullable JSON marshalled email [fact.Fact]
- - phoneFactJson - nullable JSON marshalled phone [fact.Fact]
  - cert - the TLS certificate for the UD server this call will connect with.
    You may use the UD server run by the xx network team by using
-   E2e.GetUdCertFromNdf.
- - contactFile - the data within a marshalled contact.Contact. This
+   [E2e.GetUdCertFromNdf].
+ - contactFile - the data within a marshalled [contact.Contact]. This
    represents the contact file of the server this call will connect with. You
    may use the UD server run by the xx network team by using
-   E2e.GetUdContactFromNdf.
+   [E2e.GetUdContactFromNdf].
  - address - the IP address of the UD server this call will connect with. You
    may use the UD server run by the xx network team by using
-   E2e.GetUdAddressFromNdf.
+   [E2e.GetUdAddressFromNdf].
  */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUdManagerFromBackup(long e2eID, id<BindingsUdNetworkStatus> _Nullable follower, NSData* _Nullable usernameJson, NSData* _Nullable emailFactJson, NSData* _Nullable phoneFactJson, NSData* _Nullable cert, NSData* _Nullable contactFile, NSString* _Nullable address, NSError* _Nullable* _Nullable error);
+FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUdManagerFromBackup(long e2eID, id<BindingsUdNetworkStatus> _Nullable follower, NSData* _Nullable cert, NSData* _Nullable contactFile, NSString* _Nullable address, NSError* _Nullable* _Nullable error);
 
 /**
  * RegisterForNotifications allows a client to register for push notifications.
@@ -2598,7 +2593,7 @@ The decryptedMessage field will be a JSON marshalled GroupChatMessage.
 @property(strong, readonly) _Nonnull id _ref;
 
 - (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId err:(NSError* _Nullable)err;
+- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId roundUrl:(NSString* _Nullable)roundUrl err:(NSError* _Nullable)err;
 - (NSString* _Nonnull)string;
 @end
 
diff --git a/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings b/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings
index 5dd1a70d80a7f20c4ec801fa2c17eee9606724e1..2514d4ed68f5c18e8285457d59f2f1c7c47bd313 100644
Binary files a/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings and b/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings differ
diff --git a/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h b/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h
index 96523f8825cf04317f17fab6d169b33c1e82c90c..73defbbf6e76c0043035efc5c5ec165747ff20cc 100644
--- a/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h
+++ b/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h
@@ -132,7 +132,7 @@ Parameters:
 @end
 
 @protocol BindingsGroupChatProcessor <NSObject>
-- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId err:(NSError* _Nullable)err;
+- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId roundUrl:(NSString* _Nullable)roundUrl err:(NSError* _Nullable)err;
 - (NSString* _Nonnull)string;
 @end
 
@@ -2272,29 +2272,24 @@ FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewOrLoadUd(long e2eI
 
 /**
  * NewUdManagerFromBackup builds a new user discover manager from a backup. It
-will construct a manager that is already registered and restore already
-registered facts into store.
+will construct a manager that is already registered. Confirmed facts have
+already been restored via the call NewCmixFromBackup.
 
 Parameters:
  - e2eID - e2e object ID in the tracker
  - follower - network follower func wrapped in UdNetworkStatus
- - username - The username this user registered with initially. This should
-              not be nullable, and be JSON marshalled as retrieved from
-              UserDiscovery.GetFacts().
- - emailFactJson - nullable JSON marshalled email [fact.Fact]
- - phoneFactJson - nullable JSON marshalled phone [fact.Fact]
  - cert - the TLS certificate for the UD server this call will connect with.
    You may use the UD server run by the xx network team by using
-   E2e.GetUdCertFromNdf.
- - contactFile - the data within a marshalled contact.Contact. This
+   [E2e.GetUdCertFromNdf].
+ - contactFile - the data within a marshalled [contact.Contact]. This
    represents the contact file of the server this call will connect with. You
    may use the UD server run by the xx network team by using
-   E2e.GetUdContactFromNdf.
+   [E2e.GetUdContactFromNdf].
  - address - the IP address of the UD server this call will connect with. You
    may use the UD server run by the xx network team by using
-   E2e.GetUdAddressFromNdf.
+   [E2e.GetUdAddressFromNdf].
  */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUdManagerFromBackup(long e2eID, id<BindingsUdNetworkStatus> _Nullable follower, NSData* _Nullable usernameJson, NSData* _Nullable emailFactJson, NSData* _Nullable phoneFactJson, NSData* _Nullable cert, NSData* _Nullable contactFile, NSString* _Nullable address, NSError* _Nullable* _Nullable error);
+FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUdManagerFromBackup(long e2eID, id<BindingsUdNetworkStatus> _Nullable follower, NSData* _Nullable cert, NSData* _Nullable contactFile, NSString* _Nullable address, NSError* _Nullable* _Nullable error);
 
 /**
  * RegisterForNotifications allows a client to register for push notifications.
@@ -2598,7 +2593,7 @@ The decryptedMessage field will be a JSON marshalled GroupChatMessage.
 @property(strong, readonly) _Nonnull id _ref;
 
 - (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId err:(NSError* _Nullable)err;
+- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId roundUrl:(NSString* _Nullable)roundUrl err:(NSError* _Nullable)err;
 - (NSString* _Nonnull)string;
 @end
 
diff --git a/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Bindings b/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Bindings
index 0f812dc51a542998d225b841fc3e6c7af42710e0..821b8c1854604a15ed47e44709e5cffae8108b75 100644
Binary files a/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Bindings and b/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Bindings differ
diff --git a/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Headers/Bindings.objc.h b/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Headers/Bindings.objc.h
index 96523f8825cf04317f17fab6d169b33c1e82c90c..73defbbf6e76c0043035efc5c5ec165747ff20cc 100644
--- a/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Headers/Bindings.objc.h
+++ b/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Headers/Bindings.objc.h
@@ -132,7 +132,7 @@ Parameters:
 @end
 
 @protocol BindingsGroupChatProcessor <NSObject>
-- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId err:(NSError* _Nullable)err;
+- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId roundUrl:(NSString* _Nullable)roundUrl err:(NSError* _Nullable)err;
 - (NSString* _Nonnull)string;
 @end
 
@@ -2272,29 +2272,24 @@ FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewOrLoadUd(long e2eI
 
 /**
  * NewUdManagerFromBackup builds a new user discover manager from a backup. It
-will construct a manager that is already registered and restore already
-registered facts into store.
+will construct a manager that is already registered. Confirmed facts have
+already been restored via the call NewCmixFromBackup.
 
 Parameters:
  - e2eID - e2e object ID in the tracker
  - follower - network follower func wrapped in UdNetworkStatus
- - username - The username this user registered with initially. This should
-              not be nullable, and be JSON marshalled as retrieved from
-              UserDiscovery.GetFacts().
- - emailFactJson - nullable JSON marshalled email [fact.Fact]
- - phoneFactJson - nullable JSON marshalled phone [fact.Fact]
  - cert - the TLS certificate for the UD server this call will connect with.
    You may use the UD server run by the xx network team by using
-   E2e.GetUdCertFromNdf.
- - contactFile - the data within a marshalled contact.Contact. This
+   [E2e.GetUdCertFromNdf].
+ - contactFile - the data within a marshalled [contact.Contact]. This
    represents the contact file of the server this call will connect with. You
    may use the UD server run by the xx network team by using
-   E2e.GetUdContactFromNdf.
+   [E2e.GetUdContactFromNdf].
  - address - the IP address of the UD server this call will connect with. You
    may use the UD server run by the xx network team by using
-   E2e.GetUdAddressFromNdf.
+   [E2e.GetUdAddressFromNdf].
  */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUdManagerFromBackup(long e2eID, id<BindingsUdNetworkStatus> _Nullable follower, NSData* _Nullable usernameJson, NSData* _Nullable emailFactJson, NSData* _Nullable phoneFactJson, NSData* _Nullable cert, NSData* _Nullable contactFile, NSString* _Nullable address, NSError* _Nullable* _Nullable error);
+FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUdManagerFromBackup(long e2eID, id<BindingsUdNetworkStatus> _Nullable follower, NSData* _Nullable cert, NSData* _Nullable contactFile, NSString* _Nullable address, NSError* _Nullable* _Nullable error);
 
 /**
  * RegisterForNotifications allows a client to register for push notifications.
@@ -2598,7 +2593,7 @@ The decryptedMessage field will be a JSON marshalled GroupChatMessage.
 @property(strong, readonly) _Nonnull id _ref;
 
 - (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId err:(NSError* _Nullable)err;
+- (void)process:(NSData* _Nullable)decryptedMessage msg:(NSData* _Nullable)msg receptionId:(NSData* _Nullable)receptionId ephemeralId:(int64_t)ephemeralId roundId:(int64_t)roundId roundUrl:(NSString* _Nullable)roundUrl err:(NSError* _Nullable)err;
 - (NSString* _Nonnull)string;
 @end
 
diff --git a/Package.resolved b/Package.resolved
index 6741fe9d93c193203c994ec7bd191103eecea29b..eb44c7c2804b6d4b64f6ad27cdde3a8f42b6710b 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -14,8 +14,17 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/swift-custom-dump.git",
       "state" : {
-        "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7",
-        "version" : "0.5.0"
+        "revision" : "819d9d370cd721c9d87671e29d947279292e4541",
+        "version" : "0.6.0"
+      }
+    },
+    {
+      "identity" : "swift-log",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-log.git",
+      "state" : {
+        "revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c",
+        "version" : "1.4.4"
       }
     },
     {
@@ -23,8 +32,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
       "state" : {
-        "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260",
-        "version" : "0.4.0"
+        "revision" : "30314f1ece684dd60679d598a9b89107557b67d9",
+        "version" : "0.4.1"
       }
     }
   ],
diff --git a/Package.swift b/Package.swift
index 30e5c604ce0c444f011056ca371ffb100c704351..cd25ca17a71b46a3f2dfe1d01374d552fb429475 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,15 +1,10 @@
-// swift-tools-version: 5.6
-
+// swift-tools-version: 5.7
 import PackageDescription
 
 let swiftSettings: [SwiftSetting] = [
-  .unsafeFlags(
-    [
-      "-Xfrontend", "-debug-time-function-bodies",
-      "-Xfrontend", "-debug-time-expression-type-checking",
-    ],
-    .when(configuration: .debug)
-  ),
+  //.unsafeFlags(["-Xfrontend", "-warn-concurrency"], .when(configuration: .debug)),
+  //.unsafeFlags(["-Xfrontend", "-debug-time-function-bodies"], .when(configuration: .debug)),
+  //.unsafeFlags(["-Xfrontend", "-debug-time-expression-type-checking"], .when(configuration: .debug)),
 ]
 
 let package = Package(
@@ -26,16 +21,20 @@ let package = Package(
   dependencies: [
     .package(
       url: "https://github.com/pointfreeco/swift-custom-dump.git",
-      .upToNextMajor(from: "0.5.0")
+      .upToNextMajor(from: "0.5.2")
     ),
     .package(
       url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
-      .upToNextMajor(from: "0.4.0")
+      .upToNextMajor(from: "0.4.1")
     ),
     .package(
       url: "https://github.com/kishikawakatsumi/KeychainAccess.git",
       .upToNextMajor(from: "4.2.2")
     ),
+    .package(
+      url: "https://github.com/apple/swift-log.git",
+      .upToNextMajor(from: "1.4.4")
+    ),
   ],
   targets: [
     .target(
@@ -60,6 +59,7 @@ let package = Package(
       dependencies: [
         .target(name: "XXClient"),
         .product(name: "KeychainAccess", package: "KeychainAccess"),
+        .product(name: "Logging", package: "swift-log"),
         .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
       ],
       swiftSettings: swiftSettings
diff --git a/README.md b/README.md
index 83f708d886b695c5fbd58e9f8176699991fa4d97..7b468762ffd317e3c4fe0c12a72e268402932109 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Elixxir dApps Swift SDK
 
-![Swift 5.6](https://img.shields.io/badge/swift-5.6-orange.svg)
+![Swift 5.7](https://img.shields.io/badge/swift-5.7-orange.svg)
 ![platform iOS, macOS](https://img.shields.io/badge/platform-iOS,_macOS-blue.svg)
 
 ## 📖 Documentation 
@@ -14,7 +14,7 @@ Check out included [examples](Examples).
 
 ## 🛠 Development
 
-Open `Package.swift` in Xcode (≥13.4.1).
+Open `Package.swift` in Xcode (≥14).
 
 ### Project structure
 
diff --git a/Sources/XXClient/Callbacks/GroupChatProcessor.swift b/Sources/XXClient/Callbacks/GroupChatProcessor.swift
index e6ab9ee4d8535a1fb8048e6713dc99d4730e512b..f303aae49186ab75df7a796f735e129027367ec6 100644
--- a/Sources/XXClient/Callbacks/GroupChatProcessor.swift
+++ b/Sources/XXClient/Callbacks/GroupChatProcessor.swift
@@ -8,13 +8,15 @@ public struct GroupChatProcessor {
       msg: Data,
       receptionId: Data,
       ephemeralId: Int64,
-      roundId: Int64
+      roundId: Int64,
+      roundUrl: String
     ) {
       self.decryptedMessage = decryptedMessage
       self.msg = msg
       self.receptionId = receptionId
       self.ephemeralId = ephemeralId
       self.roundId = roundId
+      self.roundUrl = roundUrl
     }
 
     public var decryptedMessage: GroupChatMessage
@@ -22,6 +24,7 @@ public struct GroupChatProcessor {
     public var receptionId: Data
     public var ephemeralId: Int64
     public var roundId: Int64
+    public var roundUrl: String
   }
 
   public init(
@@ -58,28 +61,33 @@ extension GroupChatProcessor {
         receptionId: Data?,
         ephemeralId: Int64,
         roundId: Int64,
+        roundUrl: String?,
         err: Error?
       ) {
         if let err = err {
           callback.handle(.failure(err as NSError))
           return
         }
-        guard let decryptedMessage = decryptedMessage else {
+        guard let decryptedMessage else {
           fatalError("BindingsGroupChatProcessor received `nil` decryptedMessage")
         }
-        guard let msg = msg else {
+        guard let msg else {
           fatalError("BindingsGroupChatProcessor received `nil` msg")
         }
-        guard let receptionId = receptionId else {
+        guard let receptionId else {
           fatalError("BindingsGroupChatProcessor received `nil` receptionId")
         }
+        guard let roundUrl else {
+          fatalError("BindingsGroupChatProcessor received `nil` roundUrl")
+        }
         do {
           callback.handle(.success(.init(
             decryptedMessage: try GroupChatMessage.decode(decryptedMessage),
             msg: msg,
             receptionId: receptionId,
             ephemeralId: ephemeralId,
-            roundId: roundId
+            roundId: roundId,
+            roundUrl: roundUrl
           )))
         } catch {
           callback.handle(.failure(error as NSError))
diff --git a/Sources/XXClient/Callbacks/UdMultiLookupCallback.swift b/Sources/XXClient/Callbacks/UdMultiLookupCallback.swift
index 1782c01ef83d2a74feef73f490e422f201c67c9a..cc40b60695912e2b5f76663d214a9592f854f248 100644
--- a/Sources/XXClient/Callbacks/UdMultiLookupCallback.swift
+++ b/Sources/XXClient/Callbacks/UdMultiLookupCallback.swift
@@ -49,21 +49,21 @@ extension UdMultiLookupCallback {
         if let err = err {
           result.errors.append(err as NSError)
         }
-        if let contactListJSON = contactListJSON {
-          do {
-            result.contacts = try JSONDecoder()
-              .decode([Data].self, from: contactListJSON)
-              .map { Contact.live($0) }
-          } catch {
-            result.errors.append(error as NSError)
+        do {
+          if let data = contactListJSON,
+             let contactListJSON = try JSONDecoder().decode([Data]?.self, from: data) {
+            result.contacts = contactListJSON.map { Contact.live($0) }
           }
+        } catch {
+          result.errors.append(error as NSError)
         }
-        if let failedIDs = failedIDs {
-          do {
-            result.failedIds = try JSONDecoder().decode([Data].self, from: failedIDs)
-          } catch {
-            result.errors.append(error as NSError)
+        do {
+          if let data = failedIDs,
+             let failedIDs = try JSONDecoder().decode([Data]?.self, from: data) {
+            result.failedIds = failedIDs
           }
+        } catch {
+            result.errors.append(error as NSError)
         }
         callback.handle(result)
       }
diff --git a/Sources/XXClient/E2E/E2E.swift b/Sources/XXClient/E2E/E2E.swift
index 608ccc6e3703e8818fff89705d5e822fae758916..7dea50476dee3b44b1d3172c243327991b19eff6 100644
--- a/Sources/XXClient/E2E/E2E.swift
+++ b/Sources/XXClient/E2E/E2E.swift
@@ -10,6 +10,7 @@ public struct E2E {
   public var getUdAddressFromNdf: E2EGetUdAddressFromNdf
   public var getUdCertFromNdf: E2EGetUdCertFromNdf
   public var getUdContactFromNdf: E2EGetUdContactFromNdf
+  public var getUdEnvironmentFromNdf: E2EGetUdEnvironmentFromNdf
   public var payloadSize: E2EPayloadSize
   public var partitionSize: E2EPartitionSize
   public var addPartnerCallback: E2EAddPartnerCallback
@@ -40,6 +41,7 @@ extension E2E {
       getUdAddressFromNdf: .live(bindingsE2E),
       getUdCertFromNdf: .live(bindingsE2E),
       getUdContactFromNdf: .live(bindingsE2E),
+      getUdEnvironmentFromNdf: .live(bindingsE2E),
       payloadSize: .live(bindingsE2E),
       partitionSize: .live(bindingsE2E),
       addPartnerCallback: .live(bindingsE2E),
@@ -71,6 +73,7 @@ extension E2E {
     getUdAddressFromNdf: .unimplemented,
     getUdCertFromNdf: .unimplemented,
     getUdContactFromNdf: .unimplemented,
+    getUdEnvironmentFromNdf: .unimplemented,
     payloadSize: .unimplemented,
     partitionSize: .unimplemented,
     addPartnerCallback: .unimplemented,
diff --git a/Sources/XXClient/E2E/Functions/E2EGetUdEnvironmentFromNdf.swift b/Sources/XXClient/E2E/Functions/E2EGetUdEnvironmentFromNdf.swift
new file mode 100644
index 0000000000000000000000000000000000000000..547a1b632187fbb94ddf7ea0196092d6fd8f1c53
--- /dev/null
+++ b/Sources/XXClient/E2E/Functions/E2EGetUdEnvironmentFromNdf.swift
@@ -0,0 +1,28 @@
+import Bindings
+import XCTestDynamicOverlay
+
+public struct E2EGetUdEnvironmentFromNdf {
+  public var run: () throws -> UDEnvironment
+
+  public func callAsFunction() throws -> UDEnvironment {
+    try run()
+  }
+}
+
+extension E2EGetUdEnvironmentFromNdf {
+  public static func live(_ bindingsE2E: BindingsE2e) -> E2EGetUdEnvironmentFromNdf {
+    E2EGetUdEnvironmentFromNdf {
+      UDEnvironment(
+        address: E2EGetUdAddressFromNdf.live(bindingsE2E)(),
+        cert: E2EGetUdCertFromNdf.live(bindingsE2E)(),
+        contact: try E2EGetUdContactFromNdf.live(bindingsE2E)()
+      )
+    }
+  }
+}
+
+extension E2EGetUdEnvironmentFromNdf {
+  public static let unimplemented = E2EGetUdEnvironmentFromNdf(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXClient/Functions/GetFileTransferParams.swift b/Sources/XXClient/Functions/GetFileTransferParams.swift
index 515a3ca17934d316e90c334a211dbb15c68be1f6..f42924d10f0955ad0972b07754db8debc6412051 100644
--- a/Sources/XXClient/Functions/GetFileTransferParams.swift
+++ b/Sources/XXClient/Functions/GetFileTransferParams.swift
@@ -23,4 +23,3 @@ extension GetFileTransferParams {
     run: XCTUnimplemented("\(Self.self)", placeholder: "unimplemented".data(using: .utf8)!)
   )
 }
-
diff --git a/Sources/XXClient/Functions/NewOrLoadUd.swift b/Sources/XXClient/Functions/NewOrLoadUd.swift
index f0a69b0271c1253044db9cddbe8480b35ca6e3dd..35d26b68306b0f1519bc8c57dbab6414b3d5e2f8 100644
--- a/Sources/XXClient/Functions/NewOrLoadUd.swift
+++ b/Sources/XXClient/Functions/NewOrLoadUd.swift
@@ -3,6 +3,20 @@ import XCTestDynamicOverlay
 
 public struct NewOrLoadUd {
   public struct Params: Equatable {
+    public init(
+      e2eId: Int,
+      username: String?,
+      registrationValidationSignature: Data?,
+      environment: UDEnvironment
+    ) {
+      self.e2eId = e2eId
+      self.username = username
+      self.registrationValidationSignature = registrationValidationSignature
+      self.cert = environment.cert
+      self.contact = environment.contact
+      self.address = environment.address
+    }
+
     public init(
       e2eId: Int,
       username: String?,
diff --git a/Sources/XXClient/Functions/NewUdManagerFromBackup.swift b/Sources/XXClient/Functions/NewUdManagerFromBackup.swift
index 14f2b0e494c64ee5a7adaeffe9d3fb1c8d7ee699..3f94037abbb4c143943e76aff57f5e67f1f3c4ce 100644
--- a/Sources/XXClient/Functions/NewUdManagerFromBackup.swift
+++ b/Sources/XXClient/Functions/NewUdManagerFromBackup.swift
@@ -5,26 +5,27 @@ public struct NewUdManagerFromBackup {
   public struct Params: Equatable {
     public init(
       e2eId: Int,
-      username: Fact,
-      email: Fact?,
-      phone: Fact?,
+      environment: UDEnvironment
+    ) {
+      self.e2eId = e2eId
+      self.cert = environment.cert
+      self.contact = environment.contact
+      self.address = environment.address
+    }
+
+    public init(
+      e2eId: Int,
       cert: Data,
       contact: Data,
       address: String
     ) {
       self.e2eId = e2eId
-      self.username = username
-      self.email = email
-      self.phone = phone
       self.cert = cert
       self.contact = contact
       self.address = address
     }
 
     public var e2eId: Int
-    public var username: Fact
-    public var email: Fact?
-    public var phone: Fact?
     public var cert: Data
     public var contact: Data
     public var address: String
@@ -46,9 +47,6 @@ extension NewUdManagerFromBackup {
     let bindingsUD = BindingsNewUdManagerFromBackup(
       params.e2eId,
       follower.makeBindingsUdNetworkStatus(),
-      try params.username.encode(),
-      try params.email?.encode(),
-      try params.phone?.encode(),
       params.cert,
       params.contact,
       params.address,
diff --git a/Sources/XXClient/Models/FactType.swift b/Sources/XXClient/Models/FactType.swift
index cbae1a7ebdeaf510440dfcffb631848f3fbe4353..2d55ff855cf7fd25046d489bf7ac14fb10ff6f8a 100644
--- a/Sources/XXClient/Models/FactType.swift
+++ b/Sources/XXClient/Models/FactType.swift
@@ -1,6 +1,6 @@
 import Foundation
 
-public enum FactType: Equatable {
+public enum FactType: Equatable, Hashable {
   public static let knownTypes: [FactType] = [.username, .email, .phone]
 
   case username
diff --git a/Sources/XXClient/Models/UDEnvironment.swift b/Sources/XXClient/Models/UDEnvironment.swift
new file mode 100644
index 0000000000000000000000000000000000000000..3f8a02d3ea4950caa8724a0f434e365ce61486d5
--- /dev/null
+++ b/Sources/XXClient/Models/UDEnvironment.swift
@@ -0,0 +1,17 @@
+import Foundation
+
+public struct UDEnvironment: Equatable, Codable {
+  public init(
+    address: String,
+    cert: Data,
+    contact: Data
+  ) {
+    self.address = address
+    self.cert = cert
+    self.contact = contact
+  }
+
+  public var address: String
+  public var cert: Data
+  public var contact: Data
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerBackupParams.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerBackupParams.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f5ac71f04d82b27fd3585acfbfe89b4372034ac2
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerBackupParams.swift
@@ -0,0 +1,33 @@
+import Bindings
+import XCTestDynamicOverlay
+
+public struct MessengerBackupParams {
+  public enum Error: Swift.Error, Equatable {
+    case notRunning
+  }
+
+  public var run: (BackupParams) throws -> Void
+
+  public func callAsFunction(_ params: BackupParams) throws {
+    try run(params)
+  }
+}
+
+extension MessengerBackupParams {
+  public static func live(_ env: MessengerEnvironment) -> MessengerBackupParams {
+    MessengerBackupParams { params in
+      guard let backup = env.backup(), backup.isRunning() else {
+        throw Error.notRunning
+      }
+      let paramsData = try params.encode()
+      let paramsString = String(data: paramsData, encoding: .utf8)!
+      backup.addJSON(paramsString)
+    }
+  }
+}
+
+extension MessengerBackupParams {
+  public static let unimplemented = MessengerBackupParams(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerConnect.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerConnect.swift
index efca553a512e4ab3745eb292fd54e59232fe03f4..d4bc0fa90b6e4bfe72e1cd292ca3990875a35d0d 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerConnect.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerConnect.swift
@@ -25,6 +25,7 @@ extension MessengerConnect {
         identity: try cMix.makeReceptionIdentity(legacy: true),
         e2eParamsJSON: env.getE2EParams()
       ))
+      env.isListeningForMessages.set(false)
     }
   }
 }
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerCreate.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerCreate.swift
index ed9d193d3592a10dd19a5a5d68d81576cb422f71..bee7f47245c6ebaabd1f615b63881abd6437fddc 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerCreate.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerCreate.swift
@@ -16,7 +16,7 @@ extension MessengerCreate {
       let password = env.generateSecret()
       try env.passwordStorage.save(password)
       let storageDir = env.storageDir
-      try env.fileManager.removeDirectory(storageDir)
+      try env.fileManager.removeItem(storageDir)
       try env.fileManager.createDirectory(storageDir)
       try env.newCMix(
         ndfJSON: String(data: ndfData, encoding: .utf8)!,
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
index 65a718dffa7c33c246de7a8b371c749433f2898b..89c472ce7c10a142cd49b559ff149af11997d09b 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
@@ -22,7 +22,8 @@ extension MessengerDestroy {
       env.ud.set(nil)
       env.e2e.set(nil)
       env.cMix.set(nil)
-      try env.fileManager.removeDirectory(env.storageDir)
+      env.isListeningForMessages.set(false)
+      try env.fileManager.removeItem(env.storageDir)
       try env.passwordStorage.remove()
     }
   }
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerIsBackupRunning.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsBackupRunning.swift
new file mode 100644
index 0000000000000000000000000000000000000000..4300ec19524ecb06596cdc836fa44f5257460140
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsBackupRunning.swift
@@ -0,0 +1,24 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerIsBackupRunning {
+  public var run: () -> Bool
+
+  public func callAsFunction() -> Bool {
+    run()
+  }
+}
+
+extension MessengerIsBackupRunning {
+  public static func live(_ env: MessengerEnvironment) -> MessengerIsBackupRunning {
+    MessengerIsBackupRunning {
+      env.backup()?.isRunning() == true
+    }
+  }
+}
+
+extension MessengerIsBackupRunning {
+  public static let unimplemented = MessengerIsBackupRunning(
+    run: XCTUnimplemented("\(Self.self)", placeholder: false)
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerIsListeningForMessages.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsListeningForMessages.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a5cbd390456dc8063345655e007daca8bb4693fc
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsListeningForMessages.swift
@@ -0,0 +1,21 @@
+import XCTestDynamicOverlay
+
+public struct MessengerIsListeningForMessages {
+  public var run: () -> Bool
+
+  public func callAsFunction() -> Bool {
+    run()
+  }
+}
+
+extension MessengerIsListeningForMessages {
+  public static func live(_ env: MessengerEnvironment) -> MessengerIsListeningForMessages {
+    MessengerIsListeningForMessages(run: env.isListeningForMessages.get)
+  }
+}
+
+extension MessengerIsListeningForMessages {
+  public static let unimplemented = MessengerIsListeningForMessages(
+    run: XCTUnimplemented("\(Self.self)", placeholder: false)
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift
index 90fa02dab3cf84d85e50e3d696fe0a5a226d4884..62b9a600a58eb7c279a518aff0b23d7110aaaaf5 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerListenForMessages.swift
@@ -16,14 +16,20 @@ public struct MessengerListenForMessages {
 extension MessengerListenForMessages {
   public static func live(_ env: MessengerEnvironment) -> MessengerListenForMessages {
     MessengerListenForMessages {
-      guard let e2e = env.e2e() else {
-        throw Error.notConnected
+      do {
+        guard let e2e = env.e2e() else {
+          throw Error.notConnected
+        }
+        try e2e.registerListener(
+          senderId: nil,
+          messageType: 2,
+          callback: env.messageListeners.registered()
+        )
+        env.isListeningForMessages.set(true)
+      } catch {
+        env.isListeningForMessages.set(false)
+        throw error
       }
-      try e2e.registerListener(
-        senderId: nil,
-        messageType: 2,
-        callback: env.messageListeners.registered()
-      )
     }
   }
 }
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerLogIn.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerLogIn.swift
index b82e808a3fc8e9bb3daf97bd07df9fbc5093efe8..70cfa3ce3483a3879b4a02bd3ee6d88983716fe5 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerLogIn.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerLogIn.swift
@@ -28,9 +28,7 @@ extension MessengerLogIn {
           e2eId: e2e.getId(),
           username: nil,
           registrationValidationSignature: nil,
-          cert: env.udCert ?? e2e.getUdCertFromNdf(),
-          contact: env.udContact ?? (try e2e.getUdContactFromNdf()),
-          address: env.udAddress ?? e2e.getUdAddressFromNdf()
+          environment: env.udEnvironment ?? (try e2e.getUdEnvironmentFromNdf())
         ),
         follower: .init {
           cMix.networkFollowerStatus()
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerMyContact.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerMyContact.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bcedd164fb985724f90f743ce5d417180f39b067
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerMyContact.swift
@@ -0,0 +1,51 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerMyContact {
+  public enum IncludeFacts: Equatable {
+    case all
+    case types(Set<FactType>)
+  }
+
+  public enum Error: Swift.Error {
+    case notConnected
+    case notLoggedIn
+  }
+
+  public var run: (IncludeFacts?) throws -> XXClient.Contact
+
+  public func callAsFunction(includeFacts: IncludeFacts? = .all) throws -> XXClient.Contact {
+    try run(includeFacts)
+  }
+}
+
+extension MessengerMyContact {
+  public static func live(_ env: MessengerEnvironment) -> MessengerMyContact {
+    MessengerMyContact { includeFacts in
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      var contact = e2e.getContact()
+      if let includeFacts {
+        guard let ud = env.ud() else {
+          throw Error.notLoggedIn
+        }
+        let udFacts = try ud.getFacts()
+        switch includeFacts {
+        case .all:
+          try contact.setFacts(udFacts)
+
+        case .types(let types):
+          try contact.setFacts(udFacts.filter { types.contains($0.type) })
+        }
+      }
+      return contact
+    }
+  }
+}
+
+extension MessengerMyContact {
+  public static let unimplemented = MessengerMyContact(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegister.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegister.swift
index 6baad0eb3875b59035e007d92394c9db9e6e589e..4ac83890bdcdef424f1f4292ad6796ff271cce12 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegister.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegister.swift
@@ -30,9 +30,7 @@ extension MessengerRegister {
           e2eId: e2e.getId(),
           username: username,
           registrationValidationSignature: cMix.getReceptionRegistrationValidationSignature(),
-          cert: env.udCert ?? e2e.getUdCertFromNdf(),
-          contact: env.udContact ?? (try e2e.getUdContactFromNdf()),
-          address: env.udAddress ?? e2e.getUdAddressFromNdf()
+          environment: env.udEnvironment ?? (try e2e.getUdEnvironmentFromNdf())
         ),
         follower: .init {
           cMix.networkFollowerStatus()
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterBackupCallback.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterBackupCallback.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f7c9a251072b04f28f7c12f527e42be7174dce15
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterBackupCallback.swift
@@ -0,0 +1,24 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerRegisterBackupCallback {
+  public var run: (UpdateBackupFunc) -> Cancellable
+
+  public func callAsFunction(_ callback: UpdateBackupFunc) -> Cancellable {
+    run(callback)
+  }
+}
+
+extension MessengerRegisterBackupCallback {
+  public static func live(_ env: MessengerEnvironment) -> MessengerRegisterBackupCallback {
+    MessengerRegisterBackupCallback { callback in
+      env.backupCallbacks.register(callback)
+    }
+  }
+}
+
+extension MessengerRegisterBackupCallback {
+  public static let unimplemented = MessengerRegisterBackupCallback(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0e5387a3add343ae79d27aed5eaecf752f9fb6fc
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift
@@ -0,0 +1,82 @@
+import Foundation
+import XXClient
+import XCTestDynamicOverlay
+
+public struct MessengerRestoreBackup {
+  public struct Result: Equatable {
+    public init(
+      restoredParams: BackupParams,
+      restoredContacts: [Data]
+    ) {
+      self.restoredParams = restoredParams
+      self.restoredContacts = restoredContacts
+    }
+
+    public var restoredParams: BackupParams
+    public var restoredContacts: [Data]
+  }
+
+  public var run: (Data, String) throws -> Result
+
+  public func callAsFunction(
+    backupData: Data,
+    backupPassphrase: String
+  ) throws -> Result {
+    try run(backupData, backupPassphrase)
+  }
+}
+
+extension MessengerRestoreBackup {
+  public static func live(_ env: MessengerEnvironment) -> MessengerRestoreBackup {
+    MessengerRestoreBackup { backupData, backupPassphrase in
+      let storageDir = env.storageDir
+      let ndfData = try env.downloadNDF(env.ndfEnvironment)
+      let password = env.generateSecret()
+      try env.passwordStorage.save(password)
+      try env.fileManager.removeItem(storageDir)
+      try env.fileManager.createDirectory(storageDir)
+      let report = try env.newCMixFromBackup(
+        ndfJSON: String(data: ndfData, encoding: .utf8)!,
+        storageDir: storageDir,
+        backupPassphrase: backupPassphrase,
+        sessionPassword: password,
+        backupFileContents: backupData
+      )
+      let paramsData = report.params.data(using: .utf8)!
+      let params = try BackupParams.decode(paramsData)
+      let cMix = try env.loadCMix(
+        storageDir: storageDir,
+        password: password,
+        cMixParamsJSON: env.getCMixParams()
+      )
+      env.cMix.set(cMix)
+      try cMix.startNetworkFollower(timeoutMS: 30_000)
+      let e2e = try env.login(
+        cMixId: cMix.getId(),
+        authCallbacks: env.authCallbacks.registered(),
+        identity: try cMix.makeReceptionIdentity(legacy: true),
+        e2eParamsJSON: env.getE2EParams()
+      )
+      env.e2e.set(e2e)
+      env.isListeningForMessages.set(false)
+      let ud = try env.newUdManagerFromBackup(
+        params: NewUdManagerFromBackup.Params(
+          e2eId: e2e.getId(),
+          environment: env.udEnvironment ?? (try e2e.getUdEnvironmentFromNdf())
+        ),
+        follower: UdNetworkStatus { cMix.networkFollowerStatus() }
+      )
+      env.ud.set(ud)
+      return Result(
+        restoredParams: params,
+        restoredContacts: report.restoredContacts
+      )
+    }
+  }
+}
+
+extension MessengerRestoreBackup {
+  public static let unimplemented = MessengerRestoreBackup(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerResumeBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerResumeBackup.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9c4d62a09fe35d22c4afc7c1d6c7fed50d5b1951
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerResumeBackup.swift
@@ -0,0 +1,44 @@
+import Bindings
+import XCTestDynamicOverlay
+
+public struct MessengerResumeBackup {
+  public enum Error: Swift.Error, Equatable {
+    case isRunning
+    case notConnected
+    case notLoggedIn
+  }
+
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws {
+    try run()
+  }
+}
+
+extension MessengerResumeBackup {
+  public static func live(_ env: MessengerEnvironment) -> MessengerResumeBackup {
+    MessengerResumeBackup {
+      guard env.backup()?.isRunning() != true else {
+        throw Error.isRunning
+      }
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      guard let ud = env.ud() else {
+        throw Error.notLoggedIn
+      }
+      let backup = try env.resumeBackup(
+        e2eId: e2e.getId(),
+        udId: ud.getId(),
+        callback: env.backupCallbacks.registered()
+      )
+      env.backup.set(backup)
+    }
+  }
+}
+
+extension MessengerResumeBackup {
+  public static let unimplemented = MessengerResumeBackup(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerSetLogLevel.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerSetLogLevel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..21e39bd78035438955587c816602956313389fa4
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerSetLogLevel.swift
@@ -0,0 +1,22 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerSetLogLevel {
+  public var run: (LogLevel) throws -> Bool
+
+  public func callAsFunction(_ logLevel: LogLevel) throws -> Bool {
+    try run(logLevel)
+  }
+}
+
+extension MessengerSetLogLevel {
+  public static func live(_ env: MessengerEnvironment) -> MessengerSetLogLevel {
+    MessengerSetLogLevel(run: env.setLogLevel.run)
+  }
+}
+
+extension MessengerSetLogLevel {
+  public static let unimplemented = MessengerSetLogLevel(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStartBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartBackup.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1512d36100b25a17b2b75175520edf94940ef5a2
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartBackup.swift
@@ -0,0 +1,65 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerStartBackup {
+  public enum Error: Swift.Error, Equatable {
+    case isRunning
+    case notConnected
+    case notLoggedIn
+  }
+
+  public var run: (String, BackupParams) throws -> Void
+
+  public func callAsFunction(
+    password: String,
+    params: BackupParams
+  ) throws {
+    try run(password, params)
+  }
+}
+
+extension MessengerStartBackup {
+  public static func live(_ env: MessengerEnvironment) -> MessengerStartBackup {
+    MessengerStartBackup { password, params in
+      guard env.backup()?.isRunning() != true else {
+        throw Error.isRunning
+      }
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      guard let ud = env.ud() else {
+        throw Error.notLoggedIn
+      }
+      let paramsData = try params.encode()
+      let paramsString = String(data: paramsData, encoding: .utf8)!
+      var didAddParams = false
+      var semaphore: DispatchSemaphore? = .init(value: 0)
+      let backup = try env.initializeBackup(
+        e2eId: e2e.getId(),
+        udId: ud.getId(),
+        password: password,
+        callback: .init { data in
+          semaphore?.wait()
+          if !didAddParams {
+            if let backup = env.backup() {
+              backup.addJSON(paramsString)
+              didAddParams = true
+            }
+          } else {
+            env.backupCallbacks.registered().handle(data)
+          }
+        }
+      )
+      env.backup.set(backup)
+      semaphore?.signal()
+      semaphore = nil
+    }
+  }
+}
+
+extension MessengerStartBackup {
+  public static let unimplemented = MessengerStartBackup(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStartLogging.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartLogging.swift
new file mode 100644
index 0000000000000000000000000000000000000000..dd4d12b095f3a1df1ed1fe3b52f10b15e9c1d352
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartLogging.swift
@@ -0,0 +1,28 @@
+import Foundation
+import Logging
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerStartLogging {
+  public var run: () -> Void
+
+  public func callAsFunction() -> Void {
+    run()
+  }
+}
+
+extension MessengerStartLogging {
+  public static func live(_ env: MessengerEnvironment) -> MessengerStartLogging {
+    return MessengerStartLogging {
+      env.registerLogWriter(.init { messageString in
+        env.logger(.parse(messageString))
+      })
+    }
+  }
+}
+
+extension MessengerStartLogging {
+  public static let unimplemented = MessengerStartLogging(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6e49054fbf45c7feaced41449712da617680d9e9
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStop.swift
@@ -0,0 +1,60 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerStop {
+  public struct Wait: Equatable {
+    public init(
+      sleepInterval: TimeInterval = 1,
+      retries: Int = 10
+    ) {
+      self.sleepInterval = sleepInterval
+      self.retries = retries
+    }
+
+    public var sleepInterval: TimeInterval
+    public var retries: Int
+  }
+
+  public enum Error: Swift.Error {
+    case notLoaded
+    case timedOut
+  }
+
+  public var run: (Wait?) throws -> Void
+
+  public func callAsFunction(wait: Wait? = nil) throws -> Void {
+    try run(wait)
+  }
+}
+
+extension MessengerStop {
+  public static func live(_ env: MessengerEnvironment) -> MessengerStop {
+    MessengerStop { wait in
+      guard let cMix = env.cMix() else {
+        throw Error.notLoaded
+      }
+      guard cMix.networkFollowerStatus() == .running else {
+        return
+      }
+      try cMix.stopNetworkFollower()
+      guard let wait else { return }
+      var retries = wait.retries
+      var hasRunningProcesses = cMix.hasRunningProcesses()
+      while retries > 0 && hasRunningProcesses {
+        env.sleep(wait.sleepInterval)
+        hasRunningProcesses = cMix.hasRunningProcesses()
+        retries -= 1
+      }
+      if hasRunningProcesses {
+        throw Error.timedOut
+      }
+    }
+  }
+}
+
+extension MessengerStop {
+  public static let unimplemented = MessengerStop(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStopBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStopBackup.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f5471166652f0d03488f70c74bf3a21aed8df6f8
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStopBackup.swift
@@ -0,0 +1,26 @@
+import Bindings
+import XCTestDynamicOverlay
+
+public struct MessengerStopBackup {
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws {
+    try run()
+  }
+}
+
+extension MessengerStopBackup {
+  public static func live(_ env: MessengerEnvironment) -> MessengerStopBackup {
+    MessengerStopBackup {
+      guard let backup = env.backup() else { return }
+      try backup.stop()
+      env.backup.set(nil)
+    }
+  }
+}
+
+extension MessengerStopBackup {
+  public static let unimplemented = MessengerStopBackup(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift
index 77f7f1e52f85af6d083f3b26916d8432643ab6a4..22f110303674337006b77b2f1de436a09d0392b1 100644
--- a/Sources/XXMessengerClient/Messenger/Messenger.swift
+++ b/Sources/XXMessengerClient/Messenger/Messenger.swift
@@ -4,20 +4,25 @@ public struct Messenger {
   public var cMix: Stored<CMix?>
   public var e2e: Stored<E2E?>
   public var ud: Stored<UserDiscovery?>
+  public var backup: Stored<Backup?>
   public var isCreated: MessengerIsCreated
   public var create: MessengerCreate
+  public var restoreBackup: MessengerRestoreBackup
   public var isLoaded: MessengerIsLoaded
   public var load: MessengerLoad
   public var registerAuthCallbacks: MessengerRegisterAuthCallbacks
   public var registerMessageListener: MessengerRegisterMessageListener
   public var start: MessengerStart
+  public var stop: MessengerStop
   public var isConnected: MessengerIsConnected
   public var connect: MessengerConnect
+  public var isListeningForMessages: MessengerIsListeningForMessages
   public var listenForMessages: MessengerListenForMessages
   public var isRegistered: MessengerIsRegistered
   public var register: MessengerRegister
   public var isLoggedIn: MessengerIsLoggedIn
   public var logIn: MessengerLogIn
+  public var myContact: MessengerMyContact
   public var waitForNetwork: MessengerWaitForNetwork
   public var waitForNodes: MessengerWaitForNodes
   public var destroy: MessengerDestroy
@@ -27,6 +32,14 @@ public struct Messenger {
   public var registerForNotifications: MessengerRegisterForNotifications
   public var verifyContact: MessengerVerifyContact
   public var sendMessage: MessengerSendMessage
+  public var registerBackupCallback: MessengerRegisterBackupCallback
+  public var isBackupRunning: MessengerIsBackupRunning
+  public var startBackup: MessengerStartBackup
+  public var resumeBackup: MessengerResumeBackup
+  public var backupParams: MessengerBackupParams
+  public var stopBackup: MessengerStopBackup
+  public var setLogLevel: MessengerSetLogLevel
+  public var startLogging: MessengerStartLogging
 }
 
 extension Messenger {
@@ -35,20 +48,25 @@ extension Messenger {
       cMix: env.cMix,
       e2e: env.e2e,
       ud: env.ud,
+      backup: env.backup,
       isCreated: .live(env),
       create: .live(env),
+      restoreBackup: .live(env),
       isLoaded: .live(env),
       load: .live(env),
       registerAuthCallbacks: .live(env),
       registerMessageListener: .live(env),
       start: .live(env),
+      stop: .live(env),
       isConnected: .live(env),
       connect: .live(env),
+      isListeningForMessages: .live(env),
       listenForMessages: .live(env),
       isRegistered: .live(env),
       register: .live(env),
       isLoggedIn: .live(env),
       logIn: .live(env),
+      myContact: .live(env),
       waitForNetwork: .live(env),
       waitForNodes: .live(env),
       destroy: .live(env),
@@ -57,7 +75,15 @@ extension Messenger {
       lookupContacts: .live(env),
       registerForNotifications: .live(env),
       verifyContact: .live(env),
-      sendMessage: .live(env)
+      sendMessage: .live(env),
+      registerBackupCallback: .live(env),
+      isBackupRunning: .live(env),
+      startBackup: .live(env),
+      resumeBackup: .live(env),
+      backupParams: .live(env),
+      stopBackup: .live(env),
+      setLogLevel: .live(env),
+      startLogging: .live(env)
     )
   }
 }
@@ -67,20 +93,25 @@ extension Messenger {
     cMix: .unimplemented(),
     e2e: .unimplemented(),
     ud: .unimplemented(),
+    backup: .unimplemented(),
     isCreated: .unimplemented,
     create: .unimplemented,
+    restoreBackup: .unimplemented,
     isLoaded: .unimplemented,
     load: .unimplemented,
     registerAuthCallbacks: .unimplemented,
     registerMessageListener: .unimplemented,
     start: .unimplemented,
+    stop: .unimplemented,
     isConnected: .unimplemented,
     connect: .unimplemented,
+    isListeningForMessages: .unimplemented,
     listenForMessages: .unimplemented,
     isRegistered: .unimplemented,
     register: .unimplemented,
     isLoggedIn: .unimplemented,
     logIn: .unimplemented,
+    myContact: .unimplemented,
     waitForNetwork: .unimplemented,
     waitForNodes: .unimplemented,
     destroy: .unimplemented,
@@ -89,6 +120,14 @@ extension Messenger {
     lookupContacts: .unimplemented,
     registerForNotifications: .unimplemented,
     verifyContact: .unimplemented,
-    sendMessage: .unimplemented
+    sendMessage: .unimplemented,
+    registerBackupCallback: .unimplemented,
+    isBackupRunning: .unimplemented,
+    startBackup: .unimplemented,
+    resumeBackup: .unimplemented,
+    backupParams: .unimplemented,
+    stopBackup: .unimplemented,
+    setLogLevel: .unimplemented,
+    startLogging: .unimplemented
   )
 }
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index 4c1368061cde90c8e9acb5192302dcc18c3d7bd2..0bd1212cebf2bf1d373d1733d2506e1c52b3d098 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -4,6 +4,8 @@ import XCTestDynamicOverlay
 
 public struct MessengerEnvironment {
   public var authCallbacks: AuthCallbacksRegistry
+  public var backup: Stored<Backup?>
+  public var backupCallbacks: BackupCallbacksRegistry
   public var cMix: Stored<CMix?>
   public var downloadNDF: DownloadAndVerifySignedNdf
   public var e2e: Stored<E2E?>
@@ -15,24 +17,30 @@ public struct MessengerEnvironment {
   public var getFileTransferParams: GetFileTransferParams
   public var getSingleUseParams: GetSingleUseParams
   public var initFileTransfer: InitFileTransfer
+  public var initializeBackup: InitializeBackup
+  public var isListeningForMessages: Stored<Bool>
   public var isRegisteredWithUD: IsRegisteredWithUD
   public var loadCMix: LoadCMix
+  public var logger: MessengerLogger
   public var login: Login
   public var lookupUD: LookupUD
   public var messageListeners: ListenersRegistry
   public var multiLookupUD: MultiLookupUD
   public var ndfEnvironment: NDFEnvironment
   public var newCMix: NewCMix
+  public var newCMixFromBackup: NewCMixFromBackup
   public var newOrLoadUd: NewOrLoadUd
+  public var newUdManagerFromBackup: NewUdManagerFromBackup
   public var passwordStorage: PasswordStorage
   public var registerForNotifications: RegisterForNotifications
+  public var registerLogWriter: RegisterLogWriter
+  public var resumeBackup: ResumeBackup
   public var searchUD: SearchUD
+  public var setLogLevel: SetLogLevel
   public var sleep: (TimeInterval) -> Void
   public var storageDir: String
   public var ud: Stored<UserDiscovery?>
-  public var udAddress: String?
-  public var udCert: Data?
-  public var udContact: Data?
+  public var udEnvironment: UDEnvironment?
 }
 
 extension MessengerEnvironment {
@@ -45,6 +53,8 @@ extension MessengerEnvironment {
   public static func live() -> MessengerEnvironment {
     MessengerEnvironment(
       authCallbacks: .live(),
+      backup: .inMemory(),
+      backupCallbacks: .live(),
       cMix: .inMemory(),
       downloadNDF: .live,
       e2e: .inMemory(),
@@ -56,24 +66,30 @@ extension MessengerEnvironment {
       getFileTransferParams: .liveDefault,
       getSingleUseParams: .liveDefault,
       initFileTransfer: .live,
+      initializeBackup: .live,
+      isListeningForMessages: .inMemory(false),
       isRegisteredWithUD: .live,
       loadCMix: .live,
+      logger: .live(),
       login: .live,
       lookupUD: .live,
       messageListeners: .live(),
       multiLookupUD: .live(),
       ndfEnvironment: .mainnet,
       newCMix: .live,
+      newCMixFromBackup: .live,
       newOrLoadUd: .live,
+      newUdManagerFromBackup: .live,
       passwordStorage: .keychain,
       registerForNotifications: .live,
+      registerLogWriter: .live,
+      resumeBackup: .live,
       searchUD: .live,
+      setLogLevel: .live,
       sleep: { Thread.sleep(forTimeInterval: $0) },
       storageDir: MessengerEnvironment.defaultStorageDir,
       ud: .inMemory(),
-      udAddress: nil,
-      udCert: nil,
-      udContact: nil
+      udEnvironment: nil
     )
   }
 }
@@ -81,6 +97,8 @@ extension MessengerEnvironment {
 extension MessengerEnvironment {
   public static let unimplemented = MessengerEnvironment(
     authCallbacks: .unimplemented,
+    backup: .unimplemented(),
+    backupCallbacks: .unimplemented,
     cMix: .unimplemented(),
     downloadNDF: .unimplemented,
     e2e: .unimplemented(),
@@ -92,23 +110,29 @@ extension MessengerEnvironment {
     getFileTransferParams: .unimplemented,
     getSingleUseParams: .unimplemented,
     initFileTransfer: .unimplemented,
+    initializeBackup: .unimplemented,
+    isListeningForMessages: .unimplemented(placeholder: false),
     isRegisteredWithUD: .unimplemented,
     loadCMix: .unimplemented,
+    logger: .unimplemented,
     login: .unimplemented,
     lookupUD: .unimplemented,
     messageListeners: .unimplemented,
     multiLookupUD: .unimplemented,
     ndfEnvironment: .unimplemented,
     newCMix: .unimplemented,
+    newCMixFromBackup: .unimplemented,
     newOrLoadUd: .unimplemented,
+    newUdManagerFromBackup: .unimplemented,
     passwordStorage: .unimplemented,
     registerForNotifications: .unimplemented,
+    registerLogWriter: .unimplemented,
+    resumeBackup: .unimplemented,
     searchUD: .unimplemented,
+    setLogLevel: .unimplemented,
     sleep: XCTUnimplemented("\(Self.self).sleep"),
     storageDir: "unimplemented",
     ud: .unimplemented(),
-    udAddress: nil,
-    udCert: nil,
-    udContact: nil
+    udEnvironment: nil
   )
 }
diff --git a/Sources/XXMessengerClient/Utils/BackupCallbackRegistry.swift b/Sources/XXMessengerClient/Utils/BackupCallbackRegistry.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7c7fc9e8ae08a983f72a4240728610bab152f965
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/BackupCallbackRegistry.swift
@@ -0,0 +1,36 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct BackupCallbacksRegistry {
+  public var register: (UpdateBackupFunc) -> Cancellable
+  public var registered: () -> UpdateBackupFunc
+}
+
+extension BackupCallbacksRegistry {
+  public static func live() -> BackupCallbacksRegistry {
+    class Registry {
+      var callbacks: [UUID: UpdateBackupFunc] = [:]
+    }
+    let registry = Registry()
+    return BackupCallbacksRegistry(
+      register: { callback in
+        let id = UUID()
+        registry.callbacks[id] = callback
+        return Cancellable { registry.callbacks[id] = nil }
+      },
+      registered: {
+        UpdateBackupFunc { data in
+          registry.callbacks.values.forEach { $0.handle(data) }
+        }
+      }
+    )
+  }
+}
+
+extension BackupCallbacksRegistry {
+  public static let unimplemented = BackupCallbacksRegistry(
+    register: XCTUnimplemented("\(Self.self).register", placeholder: Cancellable {}),
+    registered: XCTUnimplemented("\(Self.self).registered", placeholder: UpdateBackupFunc { _ in })
+  )
+}
diff --git a/Sources/XXMessengerClient/Utils/BackupParams.swift b/Sources/XXMessengerClient/Utils/BackupParams.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8bf39a5189049f234ec9ab991c63ecdf59b0f1ce
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/BackupParams.swift
@@ -0,0 +1,21 @@
+import Foundation
+
+public struct BackupParams: Equatable {
+  public init(
+    username: String
+  ) {
+    self.username = username
+  }
+
+  public var username: String
+}
+
+extension BackupParams: Codable {
+  public static func decode(_ data: Data) throws -> Self {
+    try JSONDecoder().decode(Self.self, from: data)
+  }
+
+  public func encode() throws -> Data {
+    try JSONEncoder().encode(self)
+  }
+}
diff --git a/Sources/XXMessengerClient/Utils/BackupStorage.swift b/Sources/XXMessengerClient/Utils/BackupStorage.swift
new file mode 100644
index 0000000000000000000000000000000000000000..2f9112be7718f31fe4528e8a9bbe4c75c5d7db7a
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/BackupStorage.swift
@@ -0,0 +1,82 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct BackupStorage {
+  public struct Backup: Equatable {
+    public init(
+      date: Date,
+      data: Data
+    ) {
+      self.date = date
+      self.data = data
+    }
+
+    public var date: Date
+    public var data: Data
+  }
+
+  public typealias Observer = (Backup?) -> Void
+
+  public var stored: () -> Backup?
+  public var store: (Data) throws -> Void
+  public var observe: (@escaping Observer) -> Cancellable
+  public var remove: () throws -> Void
+}
+
+extension BackupStorage {
+  public static func onDisk(
+    now: @escaping () -> Date = Date.init,
+    fileManager: MessengerFileManager = .live(),
+    path: String = FileManager.default
+      .urls(for: .applicationSupportDirectory, in: .userDomainMask)
+      .first!
+      .appendingPathComponent("backup.xxm")
+      .path
+  ) -> BackupStorage {
+    var observers: [UUID: Observer] = [:]
+    var backup: Backup?
+    func notifyObservers() {
+      observers.values.forEach { $0(backup) }
+    }
+    if let fileData = try? fileManager.loadFile(path),
+       let fileDate = try? fileManager.modifiedTime(path) {
+      backup = Backup(date: fileDate, data: fileData)
+    }
+    return BackupStorage(
+      stored: {
+        backup
+      },
+      store: { data in
+        let newBackup = Backup(
+          date: now(),
+          data: data
+        )
+        backup = newBackup
+        notifyObservers()
+        try fileManager.saveFile(path, newBackup.data)
+      },
+      observe: { observer in
+        let id = UUID()
+        observers[id] = observer
+        return Cancellable {
+          observers[id] = nil
+        }
+      },
+      remove: {
+        backup = nil
+        notifyObservers()
+        try fileManager.removeItem(path)
+      }
+    )
+  }
+}
+
+extension BackupStorage {
+  public static let unimplemented = BackupStorage(
+    stored: XCTUnimplemented("\(Self.self).stored"),
+    store: XCTUnimplemented("\(Self.self).store"),
+    observe: XCTUnimplemented("\(Self.self).observe", placeholder: Cancellable {}),
+    remove: XCTUnimplemented("\(Self.self).remove")
+  )
+}
diff --git a/Sources/XXMessengerClient/Utils/LogMessage.swift b/Sources/XXMessengerClient/Utils/LogMessage.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7169d953a60109e654c6d7063cbbe3b1857394cd
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/LogMessage.swift
@@ -0,0 +1,56 @@
+import Foundation
+import Logging
+
+public struct LogMessage: Equatable {
+  public init(level: Logger.Level, text: String) {
+    self.level = level
+    self.text = text
+  }
+
+  public var level: Logger.Level
+  public var text: String
+}
+
+extension LogMessage {
+  public static func parse(_ string: String) -> LogMessage {
+    let level: Logger.Level
+    let text: String
+    let pattern = #"^([A-Z]+)( \d{4}/\d{2}/\d{2})?( \d{1,2}:\d{2}:\d{2}\.\d+)? (.*)"#
+    let regex = try! NSRegularExpression(
+      pattern: pattern,
+      options: .dotMatchesLineSeparators
+    )
+    let stringRange = NSRange(location: 0, length: string.utf16.count)
+    if let match = regex.firstMatch(in: string, range: stringRange) {
+      var groups: [Int: String] = [:]
+      for rangeIndex in 1..<match.numberOfRanges {
+        let nsRange = match.range(at: rangeIndex)
+        if !NSEqualRanges(nsRange, NSMakeRange(NSNotFound, 0)) {
+          let group = (string as NSString).substring(with: nsRange)
+          groups[rangeIndex] = group
+        }
+      }
+      level = .fromString(groups[1])
+      text = groups[4] ?? string
+    } else {
+      level = .notice
+      text = string
+    }
+    return LogMessage(level: level, text: text)
+  }
+}
+
+private extension Logger.Level {
+  static func fromString(_ string: String?) -> Logger.Level {
+    switch string {
+    case "TRACE": return .trace
+    case "DEBUG": return .debug
+    case "INFO": return .info
+    case "WARN": return .warning
+    case "ERROR": return .error
+    case "CRITICAL": return .critical
+    case "FATAL": return .critical
+    default: return .notice
+    }
+  }
+}
diff --git a/Sources/XXMessengerClient/Utils/MessengerFileManager.swift b/Sources/XXMessengerClient/Utils/MessengerFileManager.swift
index 700990ea64b97c92175995ded858de7fa7486381..ad116d82a6f4a15df0021aa5f8b6ebdd845b7298 100644
--- a/Sources/XXMessengerClient/Utils/MessengerFileManager.swift
+++ b/Sources/XXMessengerClient/Utils/MessengerFileManager.swift
@@ -3,8 +3,11 @@ import XCTestDynamicOverlay
 
 public struct MessengerFileManager {
   public var isDirectoryEmpty: (String) -> Bool
-  public var removeDirectory: (String) throws -> Void
+  public var removeItem: (String) throws -> Void
   public var createDirectory: (String) throws -> Void
+  public var saveFile: (String, Data) throws -> Void
+  public var loadFile: (String) throws -> Data?
+  public var modifiedTime: (String) throws -> Date?
 }
 
 extension MessengerFileManager {
@@ -16,7 +19,7 @@ extension MessengerFileManager {
         let contents = try? fileManager.contentsOfDirectory(atPath: path)
         return contents?.isEmpty ?? true
       },
-      removeDirectory: { path in
+      removeItem: { path in
         if fileManager.fileExists(atPath: path) {
           try fileManager.removeItem(atPath: path)
         }
@@ -26,6 +29,16 @@ extension MessengerFileManager {
           atPath: path,
           withIntermediateDirectories: true
         )
+      },
+      saveFile: { path, data in
+        try data.write(to: URL(fileURLWithPath: path))
+      },
+      loadFile: { path in
+        try Data(contentsOf: URL(fileURLWithPath: path))
+      },
+      modifiedTime: { path in
+        let attributes = try fileManager.attributesOfItem(atPath: path)
+        return attributes[.modificationDate] as? Date
       }
     )
   }
@@ -34,7 +47,10 @@ extension MessengerFileManager {
 extension MessengerFileManager {
   public static let unimplemented = MessengerFileManager(
     isDirectoryEmpty: XCTUnimplemented("\(Self.self).isDirectoryEmpty", placeholder: false),
-    removeDirectory: XCTUnimplemented("\(Self.self).removeDirectory"),
-    createDirectory: XCTUnimplemented("\(Self.self).createDirectory")
+    removeItem: XCTUnimplemented("\(Self.self).removeItem"),
+    createDirectory: XCTUnimplemented("\(Self.self).createDirectory"),
+    saveFile: XCTUnimplemented("\(Self.self).saveFile"),
+    loadFile: XCTUnimplemented("\(Self.self).loadFile"),
+    modifiedTime: XCTUnimplemented("\(Self.self).modifiedTime")
   )
 }
diff --git a/Sources/XXMessengerClient/Utils/MessengerLogger.swift b/Sources/XXMessengerClient/Utils/MessengerLogger.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0dcf0bc52ca3bdb1c20e935caeefcef9a81b8d8b
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/MessengerLogger.swift
@@ -0,0 +1,90 @@
+import Foundation
+import Logging
+import XCTestDynamicOverlay
+
+public struct MessengerLogger {
+  public struct Log: Equatable {
+    public init(level: Logger.Level, message: String) {
+      self.level = level
+      self.message = message
+    }
+
+    public var level: Logger.Level
+    public var message: String
+  }
+
+  public var run: (Log, String, String, UInt) -> Void
+
+  public func callAsFunction(
+    _ item: Log,
+    file: String = #fileID,
+    function: String = #function,
+    line: UInt = #line
+  ) {
+    run(item, file, function, line)
+  }
+}
+
+extension MessengerLogger {
+  public static func live(
+    logger: Logger = Logger(label: "xx.network.MessengerClient")
+  ) -> MessengerLogger {
+    MessengerLogger { item, file, function, line in
+      logger.log(
+        level: item.level,
+        .init(stringLiteral: item.message),
+        file: file,
+        function: function,
+        line: line
+      )
+    }
+  }
+}
+
+extension MessengerLogger {
+  public static let unimplemented = MessengerLogger(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
+
+extension MessengerLogger.Log {
+  static func parse(_ string: String) -> MessengerLogger.Log {
+    let level: Logger.Level
+    let message: String
+    let pattern = #"^([A-Z]+)( \d{4}/\d{2}/\d{2})?( \d{1,2}:\d{2}:\d{2}\.\d+)? (.*)"#
+    let regex = try! NSRegularExpression(
+      pattern: pattern,
+      options: .dotMatchesLineSeparators
+    )
+    let stringRange = NSRange(location: 0, length: string.utf16.count)
+    if let match = regex.firstMatch(in: string, range: stringRange) {
+      var groups: [Int: String] = [:]
+      for rangeIndex in 1..<match.numberOfRanges {
+        let nsRange = match.range(at: rangeIndex)
+        if !NSEqualRanges(nsRange, NSMakeRange(NSNotFound, 0)) {
+          let group = (string as NSString).substring(with: nsRange)
+          groups[rangeIndex] = group
+        }
+      }
+      level = MessengerLogger.Log.level(form: groups[1])
+      message = groups[4] ?? string
+    } else {
+      level = .notice
+      message = string
+    }
+    return MessengerLogger.Log(level: level, message: message)
+  }
+
+  static func level(form string: String?) -> Logger.Level {
+    switch string {
+    case "TRACE": return .trace
+    case "DEBUG": return .debug
+    case "INFO": return .info
+    case "WARN": return .warning
+    case "ERROR": return .error
+    case "CRITICAL": return .critical
+    case "FATAL": return .critical
+    default: return .notice
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerBackupParamsTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerBackupParamsTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..b65a34b9fbac97ab2a3fc629a7bce09552ae39b1
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerBackupParamsTests.swift
@@ -0,0 +1,55 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerBackupParamsTests: XCTestCase {
+  func testBackupParams() throws {
+    var didAddJSON: [String] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { true }
+      backup.addJSON.run = { didAddJSON.append($0) }
+      return backup
+    }
+    let backup: MessengerBackupParams = .live(env)
+
+    try backup(.stub)
+
+    XCTAssertNoDifference(didAddJSON, [
+      String(data: try BackupParams.stub.encode(), encoding: .utf8)!
+    ])
+  }
+
+  func testBackupParamsWhenNoBackup() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    let backup: MessengerBackupParams = .live(env)
+
+    XCTAssertThrowsError(try backup(.stub)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerBackupParams.Error.notRunning as NSError
+      )
+    }
+  }
+
+  func testBackupParamsWhenBackupNotRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { false }
+      return backup
+    }
+    let backup: MessengerBackupParams = .live(env)
+
+    XCTAssertThrowsError(try backup(.stub)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerBackupParams.Error.notRunning as NSError
+      )
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerConnectTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerConnectTests.swift
index 1f69797f10465a6c4d4ddd027d368cdc7dc69c7a..e17d6028ed3fd2eb59577f2b94df31338a60b1af 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerConnectTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerConnectTests.swift
@@ -17,12 +17,14 @@ final class MessengerConnectTests: XCTestCase {
     var didLogInWithAuthCallbacks: [AuthCallbacks?] = []
     var didSetE2E: [E2E?] = []
     var didHandleAuthCallbacks: [AuthCallbacks.Callback] = []
+    var didSetIsListeningForMessages: [Bool] = []
 
     let cMixId = 1234
     let receptionId = ReceptionIdentity.stub
     let e2eParams = "e2e-params".data(using: .utf8)!
 
     var env: MessengerEnvironment = .unimplemented
+    env.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
     env.cMix.get = {
       var cMix: CMix = .unimplemented
       cMix.getId.run = { cMixId }
@@ -62,6 +64,7 @@ final class MessengerConnectTests: XCTestCase {
         e2eParamsJSON: e2eParams
       )
     ])
+    XCTAssertNoDifference(didSetIsListeningForMessages, [false])
     XCTAssertEqual(didLogInWithAuthCallbacks.compactMap { $0 }.count, 1)
     XCTAssertEqual(didSetE2E.compactMap { $0 }.count, 1)
 
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerCreateTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerCreateTests.swift
index 2180dda3db578b74a892b2166e66a9beb20357dd..0c8ce3bab33f2923976b113098da9c124c9cc2bf 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerCreateTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerCreateTests.swift
@@ -15,7 +15,7 @@ final class MessengerCreateTests: XCTestCase {
     var didDownloadNDF: [NDFEnvironment] = []
     var didGenerateSecret: [Int] = []
     var didSavePassword: [Data] = []
-    var didRemoveDirectory: [String] = []
+    var didRemoveItem: [String] = []
     var didCreateDirectory: [String] = []
     var didNewCMix: [DidNewCMix] = []
 
@@ -37,8 +37,8 @@ final class MessengerCreateTests: XCTestCase {
       didSavePassword.append(password)
     }
     env.storageDir = storageDir
-    env.fileManager.removeDirectory = { path in
-      didRemoveDirectory.append(path)
+    env.fileManager.removeItem = { path in
+      didRemoveItem.append(path)
     }
     env.fileManager.createDirectory = { path in
       didCreateDirectory.append(path)
@@ -58,7 +58,7 @@ final class MessengerCreateTests: XCTestCase {
     XCTAssertNoDifference(didDownloadNDF, [.unimplemented])
     XCTAssertNoDifference(didGenerateSecret, [32])
     XCTAssertNoDifference(didSavePassword, [password])
-    XCTAssertNoDifference(didRemoveDirectory, [storageDir])
+    XCTAssertNoDifference(didRemoveItem, [storageDir])
     XCTAssertNoDifference(didCreateDirectory, [storageDir])
     XCTAssertNoDifference(didNewCMix, [.init(
       ndfJSON: String(data: ndf, encoding: .utf8)!,
@@ -108,7 +108,7 @@ final class MessengerCreateTests: XCTestCase {
     env.generateSecret.run = { _ in "password".data(using: .utf8)! }
     env.passwordStorage.save = { _ in }
     env.storageDir = "storage-dir"
-    env.fileManager.removeDirectory = { _ in throw error }
+    env.fileManager.removeItem = { _ in throw error }
     let create: MessengerCreate = .live(env)
 
     XCTAssertThrowsError(try create()) { err in
@@ -126,7 +126,7 @@ final class MessengerCreateTests: XCTestCase {
     env.generateSecret.run = { _ in "password".data(using: .utf8)! }
     env.passwordStorage.save = { _ in }
     env.storageDir = "storage-dir"
-    env.fileManager.removeDirectory = { _ in }
+    env.fileManager.removeItem = { _ in }
     env.fileManager.createDirectory = { _ in throw error }
     let create: MessengerCreate = .live(env)
 
@@ -145,7 +145,7 @@ final class MessengerCreateTests: XCTestCase {
     env.generateSecret.run = { _ in "password".data(using: .utf8)! }
     env.passwordStorage.save = { _ in }
     env.storageDir = "storage-dir"
-    env.fileManager.removeDirectory = { _ in }
+    env.fileManager.removeItem = { _ in }
     env.fileManager.createDirectory = { _ in }
     env.newCMix.run = { _, _, _, _ in throw error }
     let create: MessengerCreate = .live(env)
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
index 36745d028934142b59912d341649e1519b435e1e..99a52e8402ac9c93e71988adaf99610f21b1cb95 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
@@ -9,11 +9,12 @@ final class MessengerDestroyTests: XCTestCase {
     var hasRunningProcesses: [Bool] = [true, true, false]
     var didStopNetworkFollower = 0
     var didSleep: [TimeInterval] = []
-    var didRemoveDirectory: [String] = []
+    var didRemoveItem: [String] = []
     var didSetUD: [UserDiscovery?] = []
     var didSetE2E: [E2E?] = []
     var didSetCMix: [CMix?] = []
     var didRemovePassword = 0
+    var didSetIsListeningForMessages: [Bool] = []
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = {
@@ -28,7 +29,8 @@ final class MessengerDestroyTests: XCTestCase {
     env.ud.set = { didSetUD.append($0) }
     env.e2e.set = { didSetE2E.append($0) }
     env.cMix.set = { didSetCMix.append($0) }
-    env.fileManager.removeDirectory = { didRemoveDirectory.append($0) }
+    env.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
+    env.fileManager.removeItem = { didRemoveItem.append($0) }
     env.passwordStorage.remove = { didRemovePassword += 1 }
     let destroy: MessengerDestroy = .live(env)
 
@@ -39,7 +41,8 @@ final class MessengerDestroyTests: XCTestCase {
     XCTAssertNoDifference(didSetUD.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetE2E.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetCMix.map { $0 == nil }, [true])
-    XCTAssertNoDifference(didRemoveDirectory, [storageDir])
+    XCTAssertNoDifference(didSetIsListeningForMessages, [false])
+    XCTAssertNoDifference(didRemoveItem, [storageDir])
     XCTAssertNoDifference(didRemovePassword, 1)
   }
 
@@ -67,13 +70,15 @@ final class MessengerDestroyTests: XCTestCase {
     var didSetUD: [UserDiscovery?] = []
     var didSetE2E: [E2E?] = []
     var didSetCMix: [CMix?] = []
+    var didSetIsListeningForMessages: [Bool] = []
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = { nil }
     env.ud.set = { didSetUD.append($0) }
     env.e2e.set = { didSetE2E.append($0) }
     env.cMix.set = { didSetCMix.append($0) }
-    env.fileManager.removeDirectory = { _ in throw error }
+    env.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
+    env.fileManager.removeItem = { _ in throw error }
     let destroy: MessengerDestroy = .live(env)
 
     XCTAssertThrowsError(try destroy()) { err in
@@ -82,24 +87,27 @@ final class MessengerDestroyTests: XCTestCase {
     XCTAssertNoDifference(didSetUD.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetE2E.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetCMix.map { $0 == nil }, [true])
+    XCTAssertNoDifference(didSetIsListeningForMessages, [false])
   }
 
   func testRemovePasswordFailure() {
     struct Error: Swift.Error, Equatable {}
     let error = Error()
     let storageDir = "test-storage-dir"
-    var didRemoveDirectory: [String] = []
+    var didRemoveItem: [String] = []
     var didSetUD: [UserDiscovery?] = []
     var didSetE2E: [E2E?] = []
     var didSetCMix: [CMix?] = []
+    var didSetIsListeningForMessages: [Bool] = []
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = { nil }
     env.ud.set = { didSetUD.append($0) }
     env.e2e.set = { didSetE2E.append($0) }
     env.cMix.set = { didSetCMix.append($0) }
+    env.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
     env.storageDir = storageDir
-    env.fileManager.removeDirectory = { didRemoveDirectory.append($0) }
+    env.fileManager.removeItem = { didRemoveItem.append($0) }
     env.passwordStorage.remove = { throw error }
     let destroy: MessengerDestroy = .live(env)
 
@@ -109,6 +117,7 @@ final class MessengerDestroyTests: XCTestCase {
     XCTAssertNoDifference(didSetUD.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetE2E.map { $0 == nil }, [true])
     XCTAssertNoDifference(didSetCMix.map { $0 == nil }, [true])
-    XCTAssertNoDifference(didRemoveDirectory, [storageDir])
+    XCTAssertNoDifference(didSetIsListeningForMessages, [false])
+    XCTAssertNoDifference(didRemoveItem, [storageDir])
   }
 }
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsBackupRunningTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsBackupRunningTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7076aa648b4d287ece157cf56ae868000fdfe6ed
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsBackupRunningTests.swift
@@ -0,0 +1,37 @@
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerIsBackupRunningTests: XCTestCase {
+  func testWithoutBackup() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    let isRunning: MessengerIsBackupRunning = .live(env)
+
+    XCTAssertFalse(isRunning())
+  }
+
+  func testWithBackupRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { true }
+      return backup
+    }
+    let isRunning: MessengerIsBackupRunning = .live(env)
+
+    XCTAssertTrue(isRunning())
+  }
+
+  func testWithBackupNotRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { false }
+      return backup
+    }
+    let isRunning: MessengerIsBackupRunning = .live(env)
+
+    XCTAssertFalse(isRunning())
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsListeningForMessagesTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsListeningForMessagesTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bda5200bd315d498f9af8085aeb0571bfedfb290
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsListeningForMessagesTests.swift
@@ -0,0 +1,20 @@
+import XCTest
+@testable import XXMessengerClient
+
+final class MessengerIsListeningForMessagesTests: XCTestCase {
+  func testListening() {
+    var env: MessengerEnvironment = .unimplemented
+    env.isListeningForMessages.get = { true }
+    let isListening: MessengerIsListeningForMessages = .live(env)
+
+    XCTAssertTrue(isListening())
+  }
+
+  func testNotListening() {
+    var env: MessengerEnvironment = .unimplemented
+    env.isListeningForMessages.get = { false }
+    let isListening: MessengerIsListeningForMessages = .live(env)
+
+    XCTAssertFalse(isListening())
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerListenForMessagesTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerListenForMessagesTests.swift
index 948ee33e3d668fdb0eff4a4cfad72f82b908c395..1f78a60a959ffb64d1bfbff41e59cb624ab36eb3 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerListenForMessagesTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerListenForMessagesTests.swift
@@ -12,6 +12,7 @@ final class MessengerListenForMessagesTests: XCTestCase {
     var didRegisterListenerWithParams: [RegisterListenerParams] = []
     var didRegisterListenerWithCallback: [Listener] = []
     var didHandleMessage: [Message] = []
+    var didSetIsListeningForMessages: [Bool] = []
 
     var env: MessengerEnvironment = .unimplemented
     env.e2e.get = {
@@ -25,6 +26,9 @@ final class MessengerListenForMessagesTests: XCTestCase {
     env.messageListeners.registered = {
       Listener { message in didHandleMessage.append(message) }
     }
+    env.isListeningForMessages.set = {
+      didSetIsListeningForMessages.append($0)
+    }
     let listen: MessengerListenForMessages = .live(env)
 
     try listen()
@@ -32,6 +36,7 @@ final class MessengerListenForMessagesTests: XCTestCase {
     XCTAssertNoDifference(didRegisterListenerWithParams, [
       .init(senderId: nil, messageType: 2)
     ])
+    XCTAssertNoDifference(didSetIsListeningForMessages, [true])
 
     let message = Message.stub(123)
     didRegisterListenerWithCallback.first?.handle(message)
@@ -40,19 +45,26 @@ final class MessengerListenForMessagesTests: XCTestCase {
   }
 
   func testListenWhenNotLoggedIn() {
+    var didSetIsListeningForMessages: [Bool] = []
+
     var env: MessengerEnvironment = .unimplemented
     env.e2e.get = { nil }
+    env.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
     let listen: MessengerListenForMessages = .live(env)
 
     XCTAssertThrowsError(try listen()) { error in
       XCTAssertNoDifference(error as? MessengerListenForMessages.Error, .notConnected)
     }
+
+    XCTAssertNoDifference(didSetIsListeningForMessages, [false])
   }
 
   func testListenFailure() {
     struct Failure: Error, Equatable {}
     let error = Failure()
 
+    var didSetIsListeningForMessages: [Bool] = []
+
     var env: MessengerEnvironment = .unimplemented
     env.e2e.get = {
       var e2e: E2E = .unimplemented
@@ -60,10 +72,13 @@ final class MessengerListenForMessagesTests: XCTestCase {
       return e2e
     }
     env.messageListeners.registered = { Listener.unimplemented }
+    env.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
     let listen: MessengerListenForMessages = .live(env)
 
     XCTAssertThrowsError(try listen()) { err in
       XCTAssertNoDifference(err as? Failure, error)
     }
+
+    XCTAssertNoDifference(didSetIsListeningForMessages, [false])
   }
 }
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerLogInTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerLogInTests.swift
index db4e9742a0c6d5160c144243443d68c2babce8bc..26dd2da85798c9e0a50ecac9e05665a7c55ba780 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerLogInTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerLogInTests.swift
@@ -11,10 +11,12 @@ final class MessengerLogInTests: XCTestCase {
 
     let e2eId = 1234
     let networkFollowerStatus: NetworkFollowerStatus = .stopped
-    let udCertFromNDF = "ndf-ud-cert".data(using: .utf8)!
-    let udContactFromNDF = "ndf-ud-contact".data(using: .utf8)!
-    let udAddressFromNDF = "ndf-ud-address"
-
+    let udEnvironmentFromNDF = UDEnvironment(
+      address: "ndf-ud-address",
+      cert: "ndf-ud-cert".data(using: .utf8)!,
+      contact: "ndf-ud-contact".data(using: .utf8)!
+    )
+    
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = {
       var cMix: CMix = .unimplemented
@@ -24,15 +26,11 @@ final class MessengerLogInTests: XCTestCase {
     env.e2e.get = {
       var e2e: E2E = .unimplemented
       e2e.getId.run = { e2eId }
-      e2e.getUdCertFromNdf.run = { udCertFromNDF }
-      e2e.getUdContactFromNdf.run = { udContactFromNDF }
-      e2e.getUdAddressFromNdf.run = { udAddressFromNDF }
+      e2e.getUdEnvironmentFromNdf.run = { udEnvironmentFromNDF }
       return e2e
     }
     env.ud.set = { didSetUD.append($0) }
-    env.udCert = nil
-    env.udContact = nil
-    env.udAddress = nil
+    env.udEnvironment = nil
     env.newOrLoadUd.run = { params, follower in
       didNewOrLoadUDWithParams.append(params)
       didNewOrLoadUDWithFollower.append(follower)
@@ -45,9 +43,7 @@ final class MessengerLogInTests: XCTestCase {
       e2eId: e2eId,
       username: nil,
       registrationValidationSignature: nil,
-      cert: udCertFromNDF,
-      contact: udContactFromNDF,
-      address: udAddressFromNDF
+      environment: udEnvironmentFromNDF
     )])
     XCTAssertEqual(didNewOrLoadUDWithFollower.count, 1)
     XCTAssertEqual(
@@ -62,9 +58,11 @@ final class MessengerLogInTests: XCTestCase {
     var didSetUD: [UserDiscovery?] = []
 
     let e2eId = 1234
-    let altUdCert = "alt-ud-cert".data(using: .utf8)!
-    let altUdContact = "alt-ud-contact".data(using: .utf8)!
-    let altUdAddress = "alt-ud-address"
+    let udEnvironment = UDEnvironment(
+      address: "alt-ud-address",
+      cert: "alt-ud-cert".data(using: .utf8)!,
+      contact: "alt-ud-contact".data(using: .utf8)!
+    )
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = {
@@ -78,9 +76,7 @@ final class MessengerLogInTests: XCTestCase {
       return e2e
     }
     env.ud.set = { didSetUD.append($0) }
-    env.udCert = altUdCert
-    env.udContact = altUdContact
-    env.udAddress = altUdAddress
+    env.udEnvironment = udEnvironment
     env.newOrLoadUd.run = { params, _ in
       didNewOrLoadUDWithParams.append(params)
       return .unimplemented
@@ -92,9 +88,7 @@ final class MessengerLogInTests: XCTestCase {
       e2eId: e2eId,
       username: nil,
       registrationValidationSignature: nil,
-      cert: altUdCert,
-      contact: altUdContact,
-      address: altUdAddress
+      environment: udEnvironment
     )])
     XCTAssertEqual(didSetUD.compactMap { $0 }.count, 1)
   }
@@ -139,12 +133,10 @@ final class MessengerLogInTests: XCTestCase {
     env.e2e.get = {
       var e2e: E2E = .unimplemented
       e2e.getId.run = { 1234 }
-      e2e.getUdCertFromNdf.run = { "ndf-ud-cert".data(using: .utf8)! }
-      e2e.getUdContactFromNdf.run = { throw error }
+      e2e.getUdEnvironmentFromNdf.run = { throw error }
       return e2e
     }
-    env.udCert = nil
-    env.udContact = nil
+    env.udEnvironment = nil
     let logIn: MessengerLogIn = .live(env)
 
     XCTAssertThrowsError(try logIn()) { err in
@@ -167,9 +159,11 @@ final class MessengerLogInTests: XCTestCase {
       e2e.getId.run = { 1234 }
       return e2e
     }
-    env.udCert = "ud-cert".data(using: .utf8)!
-    env.udContact = "ud-contact".data(using: .utf8)!
-    env.udAddress = "ud-address"
+    env.udEnvironment = UDEnvironment(
+      address: "ud-address",
+      cert: "ud-cert".data(using: .utf8)!,
+      contact: "ud-contact".data(using: .utf8)!
+    )
     env.newOrLoadUd.run = { _, _ in throw error }
     let logIn: MessengerLogIn = .live(env)
 
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerMyContactTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerMyContactTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..113677612ceb36dd8d08e8c35965c890c2586d9c
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerMyContactTests.swift
@@ -0,0 +1,121 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerMyContactTests: XCTestCase {
+  func testMyContactWithAllFacts() throws {
+    let e2eContactData = "e2e-contact-data".data(using: .utf8)!
+    var e2eContactSetFacts: [[Fact]] = []
+    let e2eContactWithFactsData = "e2e-contact-with-facts-data".data(using: .utf8)!
+    let udFacts = [
+      Fact(type: .username, value: "ud-fact-username"),
+      Fact(type: .email, value: "ud-fact-email"),
+      Fact(type: .phone, value: "ud-fact-phone"),
+    ]
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: Contact = .unimplemented(e2eContactData)
+        contact.setFactsOnContact.run = { _, facts in
+          e2eContactSetFacts.append(facts)
+          return e2eContactWithFactsData
+        }
+        return contact
+      }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getFacts.run = { udFacts }
+      return ud
+    }
+    let myContact: MessengerMyContact = .live(env)
+
+    let contact = try myContact()
+
+    XCTAssertNoDifference(e2eContactSetFacts, [udFacts])
+    XCTAssertNoDifference(contact, .unimplemented(e2eContactWithFactsData))
+  }
+
+  func testMyContactWithoutFacts() throws {
+    let e2eContactData = "e2e-contact-data".data(using: .utf8)!
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = { .unimplemented(e2eContactData) }
+      return e2e
+    }
+    let myContact: MessengerMyContact = .live(env)
+
+    let contact = try myContact(includeFacts: .none)
+
+    XCTAssertNoDifference(contact, .unimplemented(e2eContactData))
+  }
+
+  func testMyContactWithFactTypes() throws {
+    let e2eContactData = "e2e-contact-data".data(using: .utf8)!
+    var e2eContactSetFacts: [[Fact]] = []
+    let e2eContactWithFactsData = "e2e-contact-with-facts-data".data(using: .utf8)!
+    let udFactUsername = Fact(type: .username, value: "ud-fact-username")
+    let udFactEmail = Fact(type: .email, value: "ud-fact-email")
+    let udFactPhone = Fact(type: .phone, value: "ud-fact-phone")
+    let udFacts = [udFactUsername, udFactEmail, udFactPhone]
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact: Contact = .unimplemented(e2eContactData)
+        contact.setFactsOnContact.run = { _, facts in
+          e2eContactSetFacts.append(facts)
+          return e2eContactWithFactsData
+        }
+        return contact
+      }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getFacts.run = { udFacts }
+      return ud
+    }
+    let myContact: MessengerMyContact = .live(env)
+
+    let contact = try myContact(includeFacts: .types([.username, .phone]))
+
+    XCTAssertNoDifference(e2eContactSetFacts, [[udFactUsername, udFactPhone]])
+    XCTAssertNoDifference(contact, .unimplemented(e2eContactWithFactsData))
+  }
+
+  func testMyContactWhenNotConnected() {
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = { nil }
+    let myContact: MessengerMyContact = .live(env)
+
+    XCTAssertThrowsError(try myContact()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerMyContact.Error.notConnected as NSError
+      )
+    }
+  }
+
+  func testMyContactWithFactsWhenNotLoggedIn() {
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = { .unimplemented(Data()) }
+      return e2e
+    }
+    env.ud.get = { nil }
+    let myContact: MessengerMyContact = .live(env)
+
+    XCTAssertThrowsError(try myContact()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerMyContact.Error.notLoggedIn as NSError
+      )
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterBackupCallbackTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterBackupCallbackTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..44e7e21c2bbac9badafd94370a595fea209da2f9
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterBackupCallbackTests.swift
@@ -0,0 +1,34 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerRegisterBackupCallbackTests: XCTestCase {
+  func testRegisterBackupCallback() {
+    var registeredCallbacks: [UpdateBackupFunc] = []
+    var didHandleData: [Data] = []
+    var didCancelRegisteredCallback = 0
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backupCallbacks.register = { callback in
+      registeredCallbacks.append(callback)
+      return Cancellable { didCancelRegisteredCallback += 1 }
+    }
+    let registerBackupCallback: MessengerRegisterBackupCallback = .live(env)
+    let cancellable = registerBackupCallback(UpdateBackupFunc { data in
+      didHandleData.append(data)
+    })
+
+    XCTAssertEqual(registeredCallbacks.count, 1)
+
+    registeredCallbacks.forEach { callback in
+      callback.handle("test".data(using: .utf8)!)
+    }
+
+    XCTAssertNoDifference(didHandleData, ["test".data(using: .utf8)!])
+
+    cancellable.cancel()
+
+    XCTAssertEqual(didCancelRegisteredCallback, 1)
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterTests.swift
index 35d078c2a38314245dbd1ede4517d82022ef1507..2ec30071accc8ef475a3e89134221c76e26bc4da 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterTests.swift
@@ -12,9 +12,11 @@ final class MessengerRegisterTests: XCTestCase {
     let e2eId = 1234
     let networkFollowerStatus: NetworkFollowerStatus = .stopped
     let registrationSignature = "registration-signature".data(using: .utf8)!
-    let udCertFromNDF = "ndf-ud-cert".data(using: .utf8)!
-    let udContactFromNDF = "ndf-ud-contact".data(using: .utf8)!
-    let udAddressFromNDF = "ndf-ud-address"
+    let udEnvironmentFromNDF = UDEnvironment(
+      address: "ndf-ud-address",
+      cert: "ndf-ud-cert".data(using: .utf8)!,
+      contact: "ndf-ud-contact".data(using: .utf8)!
+    )
     let username = "new-user-name"
 
     var env: MessengerEnvironment = .unimplemented
@@ -29,15 +31,11 @@ final class MessengerRegisterTests: XCTestCase {
     env.e2e.get = {
       var e2e: E2E = .unimplemented
       e2e.getId.run = { e2eId }
-      e2e.getUdCertFromNdf.run = { udCertFromNDF }
-      e2e.getUdContactFromNdf.run = { udContactFromNDF }
-      e2e.getUdAddressFromNdf.run = { udAddressFromNDF }
+      e2e.getUdEnvironmentFromNdf.run = { udEnvironmentFromNDF }
       return e2e
     }
     env.ud.set = { didSetUD.append($0) }
-    env.udCert = nil
-    env.udContact = nil
-    env.udAddress = nil
+    env.udEnvironment = nil
     env.newOrLoadUd.run = { params, follower in
       didNewOrLoadUDWithParams.append(params)
       didNewOrLoadUDWithFollower.append(follower)
@@ -50,9 +48,7 @@ final class MessengerRegisterTests: XCTestCase {
       e2eId: e2eId,
       username: username,
       registrationValidationSignature: registrationSignature,
-      cert: udCertFromNDF,
-      contact: udContactFromNDF,
-      address: udAddressFromNDF
+      environment: udEnvironmentFromNDF
     )])
     XCTAssertEqual(didNewOrLoadUDWithFollower.count, 1)
     XCTAssertEqual(
@@ -68,9 +64,11 @@ final class MessengerRegisterTests: XCTestCase {
 
     let e2eId = 1234
     let registrationSignature = "registration-signature".data(using: .utf8)!
-    let altUdCert = "alt-ud-cert".data(using: .utf8)!
-    let altUdContact = "alt-ud-contact".data(using: .utf8)!
-    let altUdAddress = "alt-ud-address"
+    let udEnvironment = UDEnvironment(
+      address: "alt-ud-address",
+      cert: "alt-ud-cert".data(using: .utf8)!,
+      contact: "alt-ud-contact".data(using: .utf8)!
+    )
     let username = "new-user-name"
 
     var env: MessengerEnvironment = .unimplemented
@@ -88,9 +86,7 @@ final class MessengerRegisterTests: XCTestCase {
       return e2e
     }
     env.ud.set = { didSetUD.append($0) }
-    env.udCert = altUdCert
-    env.udContact = altUdContact
-    env.udAddress = altUdAddress
+    env.udEnvironment = udEnvironment
     env.newOrLoadUd.run = { params, _ in
       didNewOrLoadUDWithParams.append(params)
       return .unimplemented
@@ -102,9 +98,7 @@ final class MessengerRegisterTests: XCTestCase {
       e2eId: e2eId,
       username: username,
       registrationValidationSignature: registrationSignature,
-      cert: altUdCert,
-      contact: altUdContact,
-      address: altUdAddress
+      environment: udEnvironment
     )])
     XCTAssertEqual(didSetUD.compactMap { $0 }.count, 1)
   }
@@ -152,12 +146,10 @@ final class MessengerRegisterTests: XCTestCase {
     env.e2e.get = {
       var e2e: E2E = .unimplemented
       e2e.getId.run = { 1234 }
-      e2e.getUdCertFromNdf.run = { "ndf-ud-cert".data(using: .utf8)! }
-      e2e.getUdContactFromNdf.run = { throw error }
+      e2e.getUdEnvironmentFromNdf.run = { throw error }
       return e2e
     }
-    env.udCert = nil
-    env.udContact = nil
+    env.udEnvironment = nil
     let register: MessengerRegister = .live(env)
 
     XCTAssertThrowsError(try register(username: "new-user-name")) { err in
@@ -183,9 +175,11 @@ final class MessengerRegisterTests: XCTestCase {
       e2e.getId.run = { 1234 }
       return e2e
     }
-    env.udCert = "ud-cert".data(using: .utf8)!
-    env.udContact = "ud-contact".data(using: .utf8)!
-    env.udAddress = "ud-address"
+    env.udEnvironment = UDEnvironment(
+      address: "ud-address",
+      cert: "ud-cert".data(using: .utf8)!,
+      contact: "ud-contact".data(using: .utf8)!
+    )
     env.newOrLoadUd.run = { _, _ in throw error }
     let register: MessengerRegister = .live(env)
 
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRestoreBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRestoreBackupTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..4f819b47393545d46caffb71097dc92ff2f08337
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRestoreBackupTests.swift
@@ -0,0 +1,225 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerRestoreBackupTests: XCTestCase {
+  func testRestore() throws {
+    let backupData = "backup-data".data(using: .utf8)!
+    let backupPassphrase = "backup-passphrase"
+    let ndfData = "ndf-data".data(using: .utf8)!
+    let password = "password".data(using: .utf8)!
+    let backupContacts: [Data] = (1...3).map { "contact-\($0)" }.map { $0.data(using: .utf8)! }
+    let backupParams = BackupParams.stub
+    let backupReport = BackupReport(
+      restoredContacts: backupContacts,
+      params: String(data: try! JSONEncoder().encode(backupParams), encoding: .utf8)!
+    )
+    let cMixParams = "cmix-params".data(using: .utf8)!
+    let e2eParams = "e2e-params".data(using: .utf8)!
+    let cMixId = 123
+    let e2eId = 456
+    let receptionIdentity = ReceptionIdentity(
+      id: "reception-id".data(using: .utf8)!,
+      rsaPrivatePem: "reception-rsaPrivatePem".data(using: .utf8)!,
+      salt: "reception-salt".data(using: .utf8)!,
+      dhKeyPrivate: "reception-dhKeyPrivate".data(using: .utf8)!,
+      e2eGrp: "reception-e2eGrp".data(using: .utf8)!
+    )
+    let udEnvironmentFromNDF = UDEnvironment(
+      address: "ud-address",
+      cert: "ud-cert".data(using: .utf8)!,
+      contact: "ud-contact".data(using: .utf8)!
+    )
+
+    var caughtActions: [CaughtAction] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.downloadNDF.run = { ndfEnvironment in
+      caughtActions.append(.didDownloadNDF(environment: ndfEnvironment))
+      return ndfData
+    }
+    env.generateSecret.run = { _ in password }
+    env.passwordStorage.save = { caughtActions.append(.didSavePassword(password: $0)) }
+    env.passwordStorage.load = { password }
+    env.fileManager.removeItem = { caughtActions.append(.didRemoveItem(path: $0)) }
+    env.fileManager.createDirectory = { caughtActions.append(.didCreateDirectory(path: $0)) }
+    env.newCMixFromBackup.run = {
+      ndfJSON, storageDir, backupPassphrase, sessionPassword, backupFileContents in
+      caughtActions.append(.didNewCMixFromBackup(
+        ndfJSON: ndfJSON,
+        storageDir: storageDir,
+        backupPassphrase: backupPassphrase,
+        sessionPassword: sessionPassword,
+        backupFileContents: backupFileContents
+      ))
+      return backupReport
+    }
+    env.getCMixParams.run = { cMixParams }
+    env.getE2EParams.run = { e2eParams }
+    env.loadCMix.run = { storageDir, password, cMixParams in
+      caughtActions.append(.didLoadCMix(
+        storageDir: storageDir,
+        password: password,
+        cMixParams: cMixParams
+      ))
+      var cMix: CMix = .unimplemented
+      cMix.getId.run = { cMixId }
+      cMix.makeReceptionIdentity.run = { legacy in
+        caughtActions.append(.cMixDidMakeReceptionIdentity(legacy: legacy))
+        return receptionIdentity
+      }
+      cMix.startNetworkFollower.run = { timeoutMS in
+        caughtActions.append(.cMixDidStartNetworkFollower(timeoutMS: timeoutMS))
+      }
+      return cMix
+    }
+    env.login.run = { ephemeral, cMixId, _, identity, e2eParams in
+      caughtActions.append(.didLogin(
+        ephemeral: ephemeral,
+        cMixId: cMixId,
+        identity: identity,
+        e2eParamsJSON: e2eParams
+      ))
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { e2eId }
+      e2e.getUdEnvironmentFromNdf.run = { udEnvironmentFromNDF }
+      return e2e
+    }
+    env.newUdManagerFromBackup.run = { params, _ in
+      caughtActions.append(.didNewUdManagerFromBackup(params: params))
+      return .unimplemented
+    }
+    env.authCallbacks.registered = {
+      AuthCallbacks { _ in }
+    }
+    env.cMix.set = { _ in caughtActions.append(.didSetCMix) }
+    env.e2e.set = { _ in caughtActions.append(.didSetE2E) }
+    env.ud.set = { _ in caughtActions.append(.didSetUD) }
+    env.isListeningForMessages.set = {
+      caughtActions.append(.didSetIsListeningForMessages(isListening: $0))
+    }
+
+    let restore: MessengerRestoreBackup = .live(env)
+
+    let result = try restore(
+      backupData: backupData,
+      backupPassphrase: backupPassphrase
+    )
+
+    XCTAssertNoDifference(caughtActions, [
+      .didDownloadNDF(
+        environment: env.ndfEnvironment
+      ),
+      .didSavePassword(
+        password: password
+      ),
+      .didRemoveItem(
+        path: env.storageDir
+      ),
+      .didCreateDirectory(
+        path: env.storageDir
+      ),
+      .didNewCMixFromBackup(
+        ndfJSON: String(data: ndfData, encoding: .utf8)!,
+        storageDir: env.storageDir,
+        backupPassphrase: backupPassphrase,
+        sessionPassword: password,
+        backupFileContents: backupData
+      ),
+      .didLoadCMix(
+        storageDir: env.storageDir,
+        password: password,
+        cMixParams: cMixParams
+      ),
+      .didSetCMix,
+      .cMixDidStartNetworkFollower(
+        timeoutMS: 30_000
+      ),
+      .cMixDidMakeReceptionIdentity(
+        legacy: true
+      ),
+      .didLogin(
+        ephemeral: false,
+        cMixId: cMixId,
+        identity: receptionIdentity,
+        e2eParamsJSON: e2eParams
+      ),
+      .didSetE2E,
+      .didSetIsListeningForMessages(
+        isListening: false
+      ),
+      .didNewUdManagerFromBackup(params: .init(
+        e2eId: e2eId,
+        environment: udEnvironmentFromNDF
+      )),
+      .didSetUD,
+    ])
+
+    XCTAssertNoDifference(result, MessengerRestoreBackup.Result(
+      restoredParams: backupParams,
+      restoredContacts: backupContacts
+    ))
+  }
+
+  func testDownloadNdfFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.downloadNDF.run = { _ in throw failure }
+    let restore: MessengerRestoreBackup = .live(env)
+
+    XCTAssertThrowsError(try restore(backupData: Data(), backupPassphrase: "")) { error in
+      XCTAssertNoDifference(error as? Failure, failure)
+    }
+  }
+}
+
+private enum CaughtAction: Equatable {
+  case didDownloadNDF(
+    environment: NDFEnvironment
+  )
+  case didSavePassword(
+    password: Data
+  )
+  case didRemoveItem(
+    path: String
+  )
+  case didCreateDirectory(
+    path: String
+  )
+  case didNewCMixFromBackup(
+    ndfJSON: String,
+    storageDir: String,
+    backupPassphrase: String,
+    sessionPassword: Data,
+    backupFileContents: Data
+  )
+  case didLoadCMix(
+    storageDir: String,
+    password: Data,
+    cMixParams: Data
+  )
+  case didLogin(
+    ephemeral: Bool,
+    cMixId: Int,
+    identity: ReceptionIdentity,
+    e2eParamsJSON: Data
+  )
+  case cMixDidMakeReceptionIdentity(
+    legacy: Bool
+  )
+  case cMixDidStartNetworkFollower(
+    timeoutMS: Int
+  )
+  case didNewUdManagerFromBackup(
+    params: NewUdManagerFromBackup.Params
+  )
+  case didSetCMix
+  case didSetE2E
+  case didSetUD
+  case didSetIsListeningForMessages(
+    isListening: Bool
+  )
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerResumeBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerResumeBackupTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7979ffbd7da561d8b3405e701d2bb84718e39ea1
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerResumeBackupTests.swift
@@ -0,0 +1,126 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerResumeBackupTests: XCTestCase {
+  func testResume() throws {
+    struct ResumeBackupParams: Equatable {
+      var e2eId: Int
+      var udId: Int
+    }
+    var didResumeBackup: [ResumeBackupParams] = []
+    var backupCallbacks: [UpdateBackupFunc] = []
+    var didHandleCallback: [Data] = []
+    var didSetBackup: [Backup?] = []
+
+    let e2eId = 123
+    let udId = 321
+    let data = "test-data".data(using: .utf8)!
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.backup.set = { didSetBackup.append($0) }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { e2eId }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getId.run = { udId }
+      return ud
+    }
+    env.backupCallbacks.registered = {
+      UpdateBackupFunc { didHandleCallback.append($0) }
+    }
+    env.resumeBackup.run = { e2eId, udId, callback in
+      didResumeBackup.append(.init(e2eId: e2eId, udId: udId))
+      backupCallbacks.append(callback)
+      return .unimplemented
+    }
+    let resume: MessengerResumeBackup = .live(env)
+
+    try resume()
+
+    XCTAssertNoDifference(didResumeBackup, [
+      .init(e2eId: e2eId, udId: udId)
+    ])
+    XCTAssertNoDifference(didSetBackup.map { $0 != nil }, [true])
+
+    backupCallbacks.forEach { $0.handle(data) }
+
+    XCTAssertNoDifference(didHandleCallback, [data])
+  }
+
+  func testResumeWhenRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { true }
+      return backup
+    }
+    let resume: MessengerResumeBackup = .live(env)
+
+    XCTAssertThrowsError(try resume()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerResumeBackup.Error.isRunning as NSError
+      )
+    }
+  }
+
+  func testResumeWhenNotConnected() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = { nil }
+    let resume: MessengerResumeBackup = .live(env)
+
+    XCTAssertThrowsError(try resume()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerResumeBackup.Error.notConnected as NSError
+      )
+    }
+  }
+
+  func testResumeWhenNotLoggedIn() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = { .unimplemented }
+    env.ud.get = { nil }
+    let resume: MessengerResumeBackup = .live(env)
+
+    XCTAssertThrowsError(try resume()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerResumeBackup.Error.notLoggedIn as NSError
+      )
+    }
+  }
+
+  func testResumeFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { 123 }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getId.run = { 321 }
+      return ud
+    }
+    env.backupCallbacks.registered = { UpdateBackupFunc { _ in  } }
+    env.resumeBackup.run = { _, _ , _ in throw failure }
+    let resume: MessengerResumeBackup = .live(env)
+
+    XCTAssertThrowsError(try resume()) { error in
+      XCTAssertNoDifference(error as NSError, failure as NSError)
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSetLogLevelTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSetLogLevelTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d6d44d05465f52cc6c9cb7b0a949397d22560b42
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerSetLogLevelTests.swift
@@ -0,0 +1,44 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerSetLogLevelTests: XCTestCase {
+  func testSetLogLevel() throws {
+    var didSetLogLevel: [LogLevel] = []
+    var env: MessengerEnvironment = .unimplemented
+    env.setLogLevel.run = { level in
+      didSetLogLevel.append(level)
+      return true
+    }
+    let setLogLevel: MessengerSetLogLevel = .live(env)
+
+    let result = try setLogLevel(.debug)
+
+    XCTAssertNoDifference(didSetLogLevel, [.debug])
+    XCTAssertNoDifference(result, true)
+  }
+
+  func testSetLogLevelReturnsFalse() throws {
+    var env: MessengerEnvironment = .unimplemented
+    env.setLogLevel.run = { _ in return false }
+    let setLogLevel: MessengerSetLogLevel = .live(env)
+
+    let result = try setLogLevel(.debug)
+
+    XCTAssertNoDifference(result, false)
+  }
+
+  func testSetLogLevelFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.setLogLevel.run = { _ in throw failure }
+    let setLogLevel: MessengerSetLogLevel = .live(env)
+
+    XCTAssertThrowsError(try setLogLevel(.debug)) { error in
+      XCTAssertNoDifference(error as NSError, failure as NSError)
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartBackupTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..b92bd1ee38948737453e973a8e4b4cd874797b84
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartBackupTests.swift
@@ -0,0 +1,147 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerStartBackupTests: XCTestCase {
+  func testStart() throws {
+    struct InitBackupParams: Equatable {
+      var e2eId: Int
+      var udId: Int
+      var password: String
+    }
+    var didInitializeBackup: [InitBackupParams] = []
+    var backupCallbacks: [UpdateBackupFunc] = []
+    var didHandleCallback: [Data] = []
+    var didSetBackup: [Backup?] = []
+    var didAddJSON: [String] = []
+
+    let password = "test-password"
+    let e2eId = 123
+    let udId = 321
+    let dataWithoutParams = "backup-without-params".data(using: .utf8)!
+    let dataWithParams = "backup-with-params".data(using: .utf8)!
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { didSetBackup.last as? Backup }
+    env.backup.set = { didSetBackup.append($0) }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { e2eId }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getId.run = { udId }
+      return ud
+    }
+    env.backupCallbacks.registered = {
+      UpdateBackupFunc { didHandleCallback.append($0) }
+    }
+    env.initializeBackup.run = { e2eId, udId, password, callback in
+      didInitializeBackup.append(.init(e2eId: e2eId, udId: udId, password: password))
+      backupCallbacks.append(callback)
+      var backup: Backup = .unimplemented
+      backup.addJSON.run = { string in
+        didAddJSON.append(string)
+      }
+      return backup
+    }
+    let start: MessengerStartBackup = .live(env)
+
+    try start(password: password, params: .stub)
+
+    XCTAssertNoDifference(didInitializeBackup, [
+      .init(e2eId: e2eId, udId: udId, password: password)
+    ])
+    XCTAssertNoDifference(didSetBackup.map { $0 != nil }, [true])
+
+    backupCallbacks.forEach { $0.handle(dataWithoutParams) }
+
+    XCTAssertNoDifference(
+      didHandleCallback.map(StringData.init),
+      [].map(StringData.init)
+    )
+    XCTAssertNoDifference(didAddJSON, [
+      String(data: try BackupParams.stub.encode(), encoding: .utf8)!
+    ])
+
+    backupCallbacks.forEach { $0.handle(dataWithParams) }
+
+    XCTAssertNoDifference(
+      didHandleCallback.map(StringData.init),
+      [dataWithParams].map(StringData.init)
+    )
+  }
+
+  func testStartWhenRunning() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.isRunning.run = { true }
+      return backup
+    }
+    let start: MessengerStartBackup = .live(env)
+
+    XCTAssertThrowsError(try start(password: "", params: .stub)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStartBackup.Error.isRunning as NSError
+      )
+    }
+  }
+
+  func testStartWhenNotConnected() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = { nil }
+    let start: MessengerStartBackup = .live(env)
+
+    XCTAssertThrowsError(try start(password: "", params: .stub)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStartBackup.Error.notConnected as NSError
+      )
+    }
+  }
+
+  func testStartWhenNotLoggedIn() {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = { .unimplemented }
+    env.ud.get = { nil }
+    let start: MessengerStartBackup = .live(env)
+
+    XCTAssertThrowsError(try start(password: "", params: .stub)) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStartBackup.Error.notLoggedIn as NSError
+      )
+    }
+  }
+
+  func testStartFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { 123 }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getId.run = { 321 }
+      return ud
+    }
+    env.backupCallbacks.registered = { UpdateBackupFunc { _ in  } }
+    env.initializeBackup.run = { _, _, _, _ in throw failure }
+    let start: MessengerStartBackup = .live(env)
+
+    XCTAssertThrowsError(try start(password: "", params: .stub)) { error in
+      XCTAssertNoDifference(error as NSError, failure as NSError)
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartLoggingTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartLoggingTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d3b4fb38c9e21fea85a58e49c9e6e93f80e5a33f
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartLoggingTests.swift
@@ -0,0 +1,30 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerStartLoggingTests: XCTestCase {
+  func testStartLogging() {
+    var registeredLogWriters: [LogWriter] = []
+    var logs: [MessengerLogger.Log] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.registerLogWriter.run = { writer in
+      registeredLogWriters.append(writer)
+    }
+    env.logger.run = { log, _, _, _ in
+      logs.append(log)
+    }
+    let start: MessengerStartLogging = .live(env)
+
+    start()
+
+    XCTAssertNoDifference(registeredLogWriters.count, 1)
+
+    registeredLogWriters.first?.handle("DEBUG Hello, World!")
+
+    XCTAssertNoDifference(logs, [
+      .init(level: .debug, message: "Hello, World!"),
+    ])
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopBackupTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..306b6c2d1d6b6507cd0bd83071d54ad99fd2e8f7
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopBackupTests.swift
@@ -0,0 +1,53 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerStopBackupTests: XCTestCase {
+  func testStop() throws {
+    var didStopBackup = 0
+    var didSetBackup: [Backup?] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.stop.run = { didStopBackup += 1 }
+      return backup
+    }
+    env.backup.set = { backup in
+      didSetBackup.append(backup)
+    }
+    let stop: MessengerStopBackup = .live(env)
+
+    try stop()
+
+    XCTAssertEqual(didStopBackup, 1)
+    XCTAssertEqual(didSetBackup.count, 1)
+    XCTAssertNil(didSetBackup.first as? Backup)
+  }
+
+  func testStopFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = {
+      var backup: Backup = .unimplemented
+      backup.stop.run = { throw failure }
+      return backup
+    }
+    let stop: MessengerStopBackup = .live(env)
+
+    XCTAssertThrowsError(try stop()) { error in
+      XCTAssertNoDifference(error as NSError, failure as NSError)
+    }
+  }
+
+  func testStopWithoutBackup() throws {
+    var env: MessengerEnvironment = .unimplemented
+    env.backup.get = { nil }
+    let stop: MessengerStopBackup = .live(env)
+
+    try stop()
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e9cfd928ef2fb878121fb074fdb7dc389e2313f8
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopTests.swift
@@ -0,0 +1,123 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerStopTests: XCTestCase {
+  func testStop() throws {
+    var didStopNetworkFollower = 0
+
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .running }
+      cMix.stopNetworkFollower.run = { didStopNetworkFollower += 1 }
+      return cMix
+    }
+    let stop: MessengerStop = .live(env)
+
+    try stop()
+
+    XCTAssertNoDifference(didStopNetworkFollower, 1)
+  }
+
+  func testStopWhenNotLoaded() {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = { nil }
+    let stop: MessengerStop = .live(env)
+
+    XCTAssertThrowsError(try stop()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStop.Error.notLoaded as NSError
+      )
+    }
+  }
+
+  func testStopWhenNotRunning() throws {
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .stopped }
+      return cMix
+    }
+    let stop: MessengerStop = .live(env)
+
+    try stop()
+  }
+
+  func testStopFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .running }
+      cMix.stopNetworkFollower.run = { throw failure }
+      return cMix
+    }
+    let stop: MessengerStop = .live(env)
+
+    XCTAssertThrowsError(try stop()) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        failure as NSError
+      )
+    }
+  }
+
+  func testStopAndWait() throws {
+    var hasRunningProcesses: [Bool] = [true, true, false]
+    var didStopNetworkFollower = 0
+    var didSleep: [TimeInterval] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .running }
+      cMix.stopNetworkFollower.run = { didStopNetworkFollower += 1 }
+      cMix.hasRunningProcesses.run = { hasRunningProcesses.removeFirst() }
+      return cMix
+    }
+    env.sleep = { didSleep.append($0) }
+    let stop: MessengerStop = .live(env)
+
+    try stop(wait: .init(sleepInterval: 123, retries: 3))
+
+    XCTAssertNoDifference(didStopNetworkFollower, 1)
+    XCTAssertNoDifference(didSleep, [123, 123])
+  }
+
+  func testStopAndWaitTimeout() {
+    var hasRunningProcesses: [Bool] = [true, true, true, true]
+    var didStopNetworkFollower = 0
+    var didSleep: [TimeInterval] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.cMix.get = {
+      var cMix: CMix = .unimplemented
+      cMix.networkFollowerStatus.run = { .running }
+      cMix.stopNetworkFollower.run = { didStopNetworkFollower += 1 }
+      cMix.hasRunningProcesses.run = { hasRunningProcesses.removeFirst() }
+      return cMix
+    }
+    env.sleep = { didSleep.append($0) }
+    let stop: MessengerStop = .live(env)
+
+    XCTAssertThrowsError(
+      try stop(wait: .init(
+        sleepInterval: 123,
+        retries: 3
+      ))
+    ) { error in
+      XCTAssertNoDifference(
+        error as NSError,
+        MessengerStop.Error.timedOut as NSError
+      )
+    }
+
+    XCTAssertNoDifference(didStopNetworkFollower, 1)
+    XCTAssertNoDifference(didSleep, [123, 123, 123])
+  }
+}
diff --git a/Tests/XXMessengerClientTests/TestHelpers/StringData.swift b/Tests/XXMessengerClientTests/TestHelpers/StringData.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6987a4ea385989f6823e701b0e00002b036f0f00
--- /dev/null
+++ b/Tests/XXMessengerClientTests/TestHelpers/StringData.swift
@@ -0,0 +1,14 @@
+import CustomDump
+import Foundation
+
+struct StringData: Equatable, CustomDumpStringConvertible {
+  var data: Data
+
+  var customDumpDescription: String {
+    if let string = String(data: data, encoding: .utf8) {
+      return #"Data(string: "\#(string)", encoding: .utf8)"#
+    } else {
+      return data.customDumpDescription
+    }
+  }
+}
diff --git a/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
similarity index 80%
rename from Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift
rename to Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
index e10bea4a6298544c2e4c4d01ba267a00d2dcc167..c9a90bde352a9da4ac0c359c26fed8e1a89d9f8f 100644
--- a/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift
+++ b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift
@@ -1,4 +1,5 @@
 import XXClient
+import XXMessengerClient
 
 extension Message {
   static func stub(_ stubId: Int) -> Message {
@@ -16,3 +17,9 @@ extension Message {
     )
   }
 }
+
+extension BackupParams {
+  static let stub = BackupParams(
+    username: "stub-username"
+  )
+}
diff --git a/Tests/XXMessengerClientTests/Utils/BackupCallbackRegistryTests.swift b/Tests/XXMessengerClientTests/Utils/BackupCallbackRegistryTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d3cba50cb81fe1efb2188985fed7ef9a8470290d
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/BackupCallbackRegistryTests.swift
@@ -0,0 +1,43 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class BackupCallbackRegistryTests: XCTestCase {
+  func testRegistry() {
+    var firstCallbackDidHandle: [Data] = []
+    var secondCallbackDidHandle: [Data] = []
+
+    let firstCallback = UpdateBackupFunc { data in
+      firstCallbackDidHandle.append(data)
+    }
+    let secondCallback = UpdateBackupFunc { data in
+      secondCallbackDidHandle.append(data)
+    }
+    let callbackRegistry: BackupCallbacksRegistry = .live()
+    let registeredCallbacks = callbackRegistry.registered()
+    let firstCallbackCancellable = callbackRegistry.register(firstCallback)
+    let secondCallbackCancellable = callbackRegistry.register(secondCallback)
+
+    let firstData = "1".data(using: .utf8)!
+    registeredCallbacks.handle(firstData)
+
+    XCTAssertNoDifference(firstCallbackDidHandle, [firstData])
+    XCTAssertNoDifference(secondCallbackDidHandle, [firstData])
+
+    firstCallbackCancellable.cancel()
+    let secondData = "2".data(using: .utf8)!
+    registeredCallbacks.handle(secondData)
+
+    XCTAssertNoDifference(firstCallbackDidHandle, [firstData])
+    XCTAssertNoDifference(secondCallbackDidHandle, [firstData, secondData])
+
+    secondCallbackCancellable.cancel()
+
+    let thirdData = "3".data(using: .utf8)!
+    registeredCallbacks.handle(thirdData)
+
+    XCTAssertNoDifference(firstCallbackDidHandle, [firstData])
+    XCTAssertNoDifference(secondCallbackDidHandle, [firstData, secondData])
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Utils/BackupStorageTests.swift b/Tests/XXMessengerClientTests/Utils/BackupStorageTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..10da4a7c0eb62ae7647b3a52b506f6fcdf8fc57c
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/BackupStorageTests.swift
@@ -0,0 +1,120 @@
+import CustomDump
+import XCTest
+@testable import XXMessengerClient
+
+final class BackupStorageTests: XCTestCase {
+  func testStorage() throws {
+    var actions: [Action]!
+
+    var now: Date = .init(0)
+    let path = "backup-path"
+    let fileData = "file-data".data(using: .utf8)!
+    let fileDate = Date(123)
+    var fileManager = MessengerFileManager.unimplemented
+    fileManager.loadFile = { path in
+      actions.append(.didLoadFile(path))
+      return fileData
+    }
+    fileManager.modifiedTime = { path in
+      actions.append(.didGetModifiedTime(path))
+      return fileDate
+    }
+    fileManager.saveFile = { path, data in
+      actions.append(.didSaveFile(path, data))
+    }
+    fileManager.removeItem = { path in
+      actions.append(.didRemoveItem(path))
+    }
+    actions = []
+    let storage: BackupStorage = .onDisk(
+      now: { now },
+      fileManager: fileManager,
+      path: path
+    )
+
+    XCTAssertNoDifference(
+      storage.stored(),
+      BackupStorage.Backup(date: fileDate, data: fileData)
+    )
+    XCTAssertNoDifference(actions, [
+      .didLoadFile(path),
+      .didGetModifiedTime(path),
+    ])
+
+    actions = []
+    let observerA = storage.observe { backup in
+      actions.append(.didObserve("A", backup))
+    }
+
+    XCTAssertNoDifference(actions, [])
+    XCTAssertNoDifference(
+      storage.stored(),
+      BackupStorage.Backup(date: fileDate, data: fileData)
+    )
+
+    actions = []
+    now = .init(1)
+    let data1 = "data-1".data(using: .utf8)!
+    try storage.store(data1)
+
+    XCTAssertNoDifference(
+      storage.stored(),
+      BackupStorage.Backup(date: .init(1), data: data1)
+    )
+    XCTAssertNoDifference(actions, [
+      .didObserve("A", .init(date: .init(1), data: data1)),
+      .didSaveFile(path, data1),
+    ])
+
+    actions = []
+    let observerB = storage.observe { backup in
+      actions.append(.didObserve("B", backup))
+    }
+
+    XCTAssertNoDifference(actions, [])
+
+    actions = []
+    now = .init(2)
+    observerA.cancel()
+    let data2 = "data-2".data(using: .utf8)!
+    try storage.store(data2)
+
+    XCTAssertNoDifference(actions, [
+      .didObserve("B", .init(date: .init(2), data: data2)),
+      .didSaveFile(path, data2),
+    ])
+
+    actions = []
+    now = .init(3)
+    try storage.remove()
+
+    XCTAssertNoDifference(actions, [
+      .didObserve("B", nil),
+      .didRemoveItem(path),
+    ])
+
+    actions = []
+    now = .init(4)
+    observerB.cancel()
+    let data3 = "data-3".data(using: .utf8)!
+    try storage.store(data3)
+
+    XCTAssertNoDifference(actions, [
+      .didSaveFile(path, data3),
+    ])
+  }
+}
+
+private extension Date {
+  init(_ timeIntervalSince1970: TimeInterval) {
+    self.init(timeIntervalSince1970: timeIntervalSince1970)
+  }
+}
+
+private enum Action: Equatable {
+  case didLoadFile(String)
+  case didGetModifiedTime(String)
+  case didObserve(String, BackupStorage.Backup?)
+  case didSaveFile(String, Data)
+  case didRemoveItem(String)
+}
diff --git a/Tests/XXMessengerClientTests/Utils/LogMessageTests.swift b/Tests/XXMessengerClientTests/Utils/LogMessageTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bbea8995439aec672f0ebddf778484cf7889f0ee
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/LogMessageTests.swift
@@ -0,0 +1,71 @@
+import CustomDump
+import XCTest
+@testable import XXMessengerClient
+
+final class LogMessageTests: XCTestCase {
+  func testParsing() {
+    XCTAssertNoDifference(
+      LogMessage.parse("TRACE Tracing..."),
+      LogMessage(level: .trace, text: "Tracing...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("DEBUG Debugging..."),
+      LogMessage(level: .debug, text: "Debugging...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("INFO Informing..."),
+      LogMessage(level: .info, text: "Informing...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("WARN Warning!"),
+      LogMessage(level: .warning, text: "Warning!")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("ERROR Failure!"),
+      LogMessage(level: .error, text: "Failure!")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("CRITICAL Critical failure!"),
+      LogMessage(level: .critical, text: "Critical failure!")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("FATAL Fatal failure!"),
+      LogMessage(level: .critical, text: "Fatal failure!")
+    )
+  }
+
+  func testParsingFallbacks() {
+    XCTAssertNoDifference(
+      LogMessage.parse("1234 Wrongly formatted"),
+      LogMessage(level: .notice, text: "1234 Wrongly formatted")
+    )
+  }
+
+  func testParsingStripsDateTime() {
+    XCTAssertNoDifference(
+      LogMessage.parse("INFO 2022/10/04 Informing..."),
+      LogMessage(level: .info, text: "Informing...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("INFO 23:36:55.755390 Informing..."),
+      LogMessage(level: .info, text: "Informing...")
+    )
+    XCTAssertNoDifference(
+      LogMessage.parse("INFO 2022/10/04 23:36:55.755390 Informing..."),
+      LogMessage(level: .info, text: "Informing...")
+    )
+  }
+
+  func testParsingMultilineMessage() {
+    XCTAssertNoDifference(
+      LogMessage.parse("""
+      ERROR 2022/10/04 23:51:15.021658 First line
+      Second line
+      """),
+      LogMessage(level: .error, text: """
+      First line
+      Second line
+      """)
+    )
+  }
+}
diff --git a/Tests/XXMessengerClientTests/Utils/MessengerLoggerTests.swift b/Tests/XXMessengerClientTests/Utils/MessengerLoggerTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..755863d99a7d26a35496256bb9be11b776952add
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Utils/MessengerLoggerTests.swift
@@ -0,0 +1,71 @@
+import CustomDump
+import XCTest
+@testable import XXMessengerClient
+
+final class MessengerLoggerTests: XCTestCase {
+  func testParsingLog() {
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("TRACE Tracing..."),
+      MessengerLogger.Log(level: .trace, message: "Tracing...")
+    )
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("DEBUG Debugging..."),
+      MessengerLogger.Log(level: .debug, message: "Debugging...")
+    )
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("INFO Informing..."),
+      MessengerLogger.Log(level: .info, message: "Informing...")
+    )
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("WARN Warning!"),
+      MessengerLogger.Log(level: .warning, message: "Warning!")
+    )
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("ERROR Failure!"),
+      MessengerLogger.Log(level: .error, message: "Failure!")
+    )
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("CRITICAL Critical failure!"),
+      MessengerLogger.Log(level: .critical, message: "Critical failure!")
+    )
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("FATAL Fatal failure!"),
+      MessengerLogger.Log(level: .critical, message: "Fatal failure!")
+    )
+  }
+
+  func testParsingFallbacks() {
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("1234 Wrongly formatted"),
+      MessengerLogger.Log(level: .notice, message: "1234 Wrongly formatted")
+    )
+  }
+
+  func testParsingStripsDateTime() {
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("INFO 2022/10/04 Informing..."),
+      MessengerLogger.Log(level: .info, message: "Informing...")
+    )
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("INFO 23:36:55.755390 Informing..."),
+      MessengerLogger.Log(level: .info, message: "Informing...")
+    )
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("INFO 2022/10/04 23:36:55.755390 Informing..."),
+      MessengerLogger.Log(level: .info, message: "Informing...")
+    )
+  }
+
+  func testParsingMultilineMessage() {
+    XCTAssertNoDifference(
+      MessengerLogger.Log.parse("""
+      ERROR 2022/10/04 23:51:15.021658 First line
+      Second line
+      """),
+      MessengerLogger.Log(level: .error, message: """
+      First line
+      Second line
+      """)
+    )
+  }
+}
diff --git a/run-tests.sh b/run-tests.sh
index 1a87f661803af51200586db33dac341407cc7e78..d69dcac5dec6225c758182325019f63e42fe538d 100755
--- a/run-tests.sh
+++ b/run-tests.sh
@@ -4,17 +4,17 @@ set -e
 if [ "$1" = "macos" ]; then
 
   echo "\n\033[1;32mâ–¶ Running package tests on macOS...\033[0m"
-  set -o pipefail && swift test 2>&1 | ./xcbeautify
+  set -o pipefail && swift test | ./xcbeautify
 
 elif [ "$1" = "ios" ]; then
 
   echo "\n\033[1;32mâ–¶ Running package tests on iOS Simulator...\033[0m"
-  set -o pipefail && xcodebuild -scheme 'elixxir-dapps-sdk-swift-Package' -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=15.5,name=iPhone 13' test | ./xcbeautify
+  set -o pipefail && xcodebuild -scheme 'elixxir-dapps-sdk-swift-Package' -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=16.0,name=iPhone 14' test | ./xcbeautify
 
 elif [ "$1" = "examples-ios" ]; then
 
   echo "\n\033[1;32mâ–¶ Running XXMessenger example tests on iOS Simulator...\033[0m"
-  set -o pipefail && xcodebuild -workspace 'Examples/xx-messenger/XXMessenger.xcworkspace' -scheme 'XXMessenger' -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=15.5,name=iPhone 13' test | ./xcbeautify
+  set -o pipefail && xcodebuild -workspace 'Examples/xx-messenger/XXMessenger.xcworkspace' -scheme 'XXMessenger' -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=16.0,name=iPhone 14' test | ./xcbeautify
 
 else
 
diff --git a/xcbeautify b/xcbeautify
index c68c40e98dc4aff708435c6c159fa713ac74a58b..76b5df583a5d8f479e2088d4927ad90288c9d4cd 100755
Binary files a/xcbeautify and b/xcbeautify differ
diff --git a/xcode-remove-caches.sh b/xcode-remove-caches.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4d24eab47daf55841f3152f75e371388188612af
--- /dev/null
+++ b/xcode-remove-caches.sh
@@ -0,0 +1,6 @@
+pkill -int com.apple.CoreSimulator.CoreSimulatorService
+killall Xcode
+rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang/ModuleCache"
+rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang.$(whoami)/ModuleCache"
+rm -rf ~/Library/Developer/Xcode/DerivedData/*
+rm -rf ~/Library/Caches/com.apple.dt.Xcode/*