diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index c0a4f9054a911c82a801544c2ff17e4629c0ecfb..34d2f4657375e1a4baf78a7811a96a64d2490571 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -219,6 +219,7 @@ let package = Package(
         .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
@@ -273,6 +274,7 @@ let package = Package(
       name: "RestoreFeature",
       dependencies: [
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
       ],
       swiftSettings: swiftSettings
     ),
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/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index d824d6d400b402270d341bb43fbd5f363c84a879..3c75f40afb41be86846967f43d2b8fb0f8462b8c 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -85,6 +85,11 @@ extension AppEnvironment {
     return AppEnvironment(
       dbManager: dbManager,
       messenger: messenger,
+      authHandler: authHandler,
+      messageListener: .live(
+        messenger: messenger,
+        db: dbManager.getDB
+      ),
       mainQueue: mainQueue,
       bgQueue: bgQueue,
       welcome: {
@@ -95,17 +100,17 @@ extension AppEnvironment {
         )
       },
       restore: {
-        RestoreEnvironment()
+        RestoreEnvironment(
+          messenger: messenger,
+          loadData: .live,
+          mainQueue: mainQueue,
+          bgQueue: bgQueue
+        )
       },
       home: {
         HomeEnvironment(
           messenger: messenger,
           dbManager: dbManager,
-          authHandler: authHandler,
-          messageListener: .live(
-            messenger: messenger,
-            db: dbManager.getDB
-          ),
           mainQueue: mainQueue,
           bgQueue: bgQueue,
           register: {
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
index 43cede697423359094d9b0186350abbc01e79d2e..9424cbf2991248fa6369fb44a2a23982ff6fe8d8 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,8 @@ enum AppAction: Equatable, BindableAction {
 struct AppEnvironment {
   var dbManager: DBManager
   var messenger: Messenger
+  var authHandler: AuthCallbackHandler
+  var messageListener: MessageListenerHandler
   var mainQueue: AnySchedulerOf<DispatchQueue>
   var bgQueue: AnySchedulerOf<DispatchQueue>
   var welcome: () -> WelcomeEnvironment
@@ -57,6 +61,8 @@ extension AppEnvironment {
   static let unimplemented = AppEnvironment(
     dbManager: .unimplemented,
     messenger: .unimplemented,
+    authHandler: .unimplemented,
+    messageListener: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
     welcome: { .unimplemented },
@@ -67,34 +73,50 @@ extension AppEnvironment {
 
 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
+          // TODO: handle error
+        }))
+        cancellables.append(env.messageListener(onError: { error in
+          // TODO: handle error
+        }))
+
+        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/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 894c5aca32ca1170d0acd47ae89a8242f2e73cb8..068cdb4dd77598fede025166b1a5e7505f1a7ba7 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -14,8 +14,6 @@ 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,
@@ -25,8 +23,6 @@ public struct HomeState: Equatable {
     userSearch: UserSearchState? = nil
   ) {
     self.failure = failure
-    self.authFailure = authFailure
-    self.messageListenerFailure = messageListenerFailure
     self.isNetworkHealthy = isNetworkHealthy
     self.isDeletingAccount = isDeletingAccount
     self.alert = alert
@@ -36,8 +32,6 @@ public struct HomeState: Equatable {
   }
 
   public var failure: String?
-  public var authFailure: String?
-  public var messageListenerFailure: String?
   public var isNetworkHealthy: Bool?
   public var networkNodesReport: NodeRegistrationReport?
   public var isDeletingAccount: Bool
@@ -55,20 +49,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 +64,6 @@ public enum HomeAction: Equatable {
   }
 
   case messenger(Messenger)
-  case authHandler(AuthHandler)
-  case messageListener(MessageListener)
   case networkMonitor(NetworkMonitor)
   case deleteAccount(DeleteAccount)
   case didDismissAlert
@@ -103,8 +81,6 @@ public struct HomeEnvironment {
   public init(
     messenger: Messenger,
     dbManager: DBManager,
-    authHandler: AuthCallbackHandler,
-    messageListener: MessageListenerHandler,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
     register: @escaping () -> RegisterEnvironment,
@@ -113,8 +89,6 @@ public struct HomeEnvironment {
   ) {
     self.messenger = messenger
     self.dbManager = dbManager
-    self.authHandler = authHandler
-    self.messageListener = messageListener
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
     self.register = register
@@ -124,8 +98,6 @@ public struct HomeEnvironment {
 
   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
@@ -137,8 +109,6 @@ extension HomeEnvironment {
   public static let unimplemented = HomeEnvironment(
     messenger: .unimplemented,
     dbManager: .unimplemented,
-    authHandler: .unimplemented,
-    messageListener: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
     register: { .unimplemented },
@@ -151,14 +121,10 @@ 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 +132,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()
           }
 
@@ -197,52 +166,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
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index 8cd7259b6c57a7054b1bae658df8772b0a7efa3d..03907b18d08fa6a5e39502060ec3f63b44773404 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -15,16 +15,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 +44,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")
diff --git a/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift b/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift
index 6ce31e5d67acfde939a1bc9ba03e28226f4e0761..5339c89bbd3dba39edaf2cc8f0181e673b396f4b 100644
--- a/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift
+++ b/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift
@@ -1,19 +1,147 @@
+import Combine
 import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXMessengerClient
 
 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,
+    restoreFailure: String? = nil,
+    focusedField: Field? = nil,
+    isImportingFile: Bool = false,
+    passphrase: String = "",
+    isRestoring: Bool = false
+  ) {
+    self.file = file
+    self.fileImportFailure = fileImportFailure
+    self.restoreFailure = restoreFailure
+    self.focusedField = focusedField
+    self.isImportingFile = isImportingFile
+    self.passphrase = passphrase
+    self.isRestoring = isRestoring
+  }
+
+  public var file: File?
+  public var fileImportFailure: String?
+  public var restoreFailure: 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,
+    loadData: URLDataLoader,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>
+  ) {
+    self.messenger = messenger
+    self.loadData = loadData
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+  }
+
+  public var messenger: Messenger
+  public var loadData: URLDataLoader
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
 }
 
 extension RestoreEnvironment {
-  public static let unimplemented = RestoreEnvironment()
+  public static let unimplemented = RestoreEnvironment(
+    messenger: .unimplemented,
+    loadData: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
 }
 
-public let restoreReducer = Reducer<RestoreState, RestoreAction, RestoreEnvironment>.empty
+public let restoreReducer = Reducer<RestoreState, RestoreAction, RestoreEnvironment>
+{ state, action, env in
+  switch action {
+  case .importFileTapped:
+    state.isImportingFile = true
+    state.fileImportFailure = nil
+    return .none
+
+  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.restoreFailure = nil
+    return Effect.result {
+      do {
+        _ = try env.messenger.restoreBackup(
+          backupData: backupData,
+          backupPassphrase: backupPassphrase
+        )
+        return .success(.finished)
+      } catch {
+        return .success(.failed(error as NSError))
+      }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .finished:
+    state.isRestoring = false
+    return .none
+
+  case .failed(let error):
+    state.isRestoring = false
+    state.restoreFailure = error.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..06f7b91515725dcd81157dd876b3bcba2783a8e1 100644
--- a/Examples/xx-messenger/Sources/RestoreFeature/RestoreView.swift
+++ b/Examples/xx-messenger/Sources/RestoreFeature/RestoreView.swift
@@ -7,9 +7,31 @@ 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 restoreFailure: 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
+      restoreFailure = state.restoreFailure
+    }
   }
 
   public var body: some View {
@@ -17,23 +39,94 @@ public struct RestoreView: View {
       NavigationView {
         Form {
           Section {
-            Text("Not implemented")
+            if let file = viewStore.file {
+              HStack(alignment: .bottom) {
+                Text(file.name)
+                Spacer()
+                Text(format(byteCount: file.size))
+              }
+            }
+
+            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 }))
+              }
+            )
+
+            if let failure = viewStore.fileImportFailure {
+              Text("Error: \(failure)")
+            }
+          } header: {
+            Text("File")
           }
+          .disabled(viewStore.isRestoring)
 
-          Section {
+          if viewStore.file != nil {
+            Section {
+              SecureField("Passphrase", text: viewStore.binding(
+                get: \.passphrase,
+                send: { .set(\.$passphrase, $0) }
+              ))
+              .textContentType(.password)
+              .textInputAutocapitalization(.never)
+              .disableAutocorrection(true)
+              .focused($focusedField, equals: .passphrase)
+
+              Button {
+                viewStore.send(.restoreTapped)
+              } label: {
+                HStack {
+                  Text("Restore")
+                  Spacer()
+                  if viewStore.isRestoring {
+                    ProgressView()
+                  }
+                }
+              }
+
+              if let failure = viewStore.restoreFailure {
+                Text("Error: \(failure)")
+              }
+            } header: {
+              Text("Backup")
+            }
+            .disabled(viewStore.isRestoring)
+          }
+        }
+        .toolbar {
+          ToolbarItem(placement: .cancellationAction) {
             Button {
               viewStore.send(.finished)
             } label: {
-              Text("OK")
-                .frame(maxWidth: .infinity)
+              Text("Cancel")
             }
+            .disabled(viewStore.isRestoring)
           }
         }
         .navigationTitle("Restore")
+        .onChange(of: viewStore.focusedField) { focusedField = $0 }
+        .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) }
       }
       .navigationViewStyle(.stack)
     }
   }
+
+  func format(byteCount: Int) -> String {
+    let formatter = ByteCountFormatter()
+    formatter.allowedUnits = [.useMB, .useKB, .useBytes]
+    formatter.countStyle = .binary
+    return formatter.string(fromByteCount: Int64(byteCount))
+  }
 }
 
 #if DEBUG
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/WelcomeFeature/WelcomeFeature.swift b/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeFeature.swift
index 6c6f2e22aa36e73ff0fc4acc441eb63b91f56e66..5bfe814829ab4e85ff12ed43ec4b01fa91d9dc45 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 {
@@ -48,6 +51,7 @@ public let welcomeReducer = Reducer<WelcomeState, WelcomeAction, WelcomeEnvironm
   switch action {
   case .newAccountTapped:
     state.isCreatingAccount = true
+    state.failure = nil
     return .future { fulfill in
       do {
         try env.messenger.create()
@@ -66,10 +70,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..0050a572f08f565a88ef3581ddc464503151c260 100644
--- a/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift
+++ b/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift
@@ -11,9 +11,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 {
@@ -24,6 +26,14 @@ public struct WelcomeView: View {
             Text("xx messenger")
           }
 
+          if let failure = viewStore.failure {
+            Section {
+              Text(failure)
+            } header: {
+              Text("Error")
+            }
+          }
+
           Section {
             Button {
               viewStore.send(.newAccountTapped)
diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
index fc58fe4eaf2ec4fa8be79df08205638c640d656b..93d719030e6bd5554465078f957f1e0412c3da76 100644
--- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
@@ -1,78 +1,103 @@
+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.send(.start)
 
-    bgQueue.advance()
-
-    XCTAssertNoDifference(didMakeDB, 1)
-
-    mainQueue.advance()
-
     store.receive(.set(\.$screen, .welcome(WelcomeState()))) {
       $0.screen = .welcome(WelcomeState())
     }
+
+    XCTAssertNoDifference(actions, [
+      .didMakeDB,
+      .didStartAuthHandler,
+      .didStartMessageListener,
+    ])
+
+    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.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,
+      .didLoadMessenger,
+    ])
+
+    store.send(.stop)
   }
 
   func testWelcomeFinished() {
+    var actions: [Action] = []
+
     let store = TestStore(
       initialState: AppState(
         screen: .welcome(WelcomeState())
@@ -81,33 +106,43 @@ 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.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,
+      .didLoadMessenger,
+    ])
+
+    store.send(.stop)
   }
 
   func testRestoreFinished() {
+    var actions: [Action] = []
+
     let store = TestStore(
       initialState: AppState(
         screen: .restore(RestoreState())
@@ -116,33 +151,43 @@ 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.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,
+      .didLoadMessenger,
+    ])
+
+    store.send(.stop)
   }
 
   func testHomeDidDeleteAccount() {
+    var actions: [Action] = []
+
     let store = TestStore(
       initialState: AppState(
         screen: .home(HomeState())
@@ -151,25 +196,34 @@ 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.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,
+    ])
+
+    store.send(.stop)
   }
 
   func testWelcomeRestoreTapped() {
@@ -187,6 +241,8 @@ final class AppFeatureTests: XCTestCase {
   }
 
   func testWelcomeFailed() {
+    let failure = "Something went wrong"
+
     let store = TestStore(
       initialState: AppState(
         screen: .welcome(WelcomeState())
@@ -195,23 +251,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 }
@@ -222,29 +276,136 @@ 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.send(.start)
 
     store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
       $0.screen = .failure(error.localizedDescription)
     }
+
+    XCTAssertNoDifference(actions, [
+      .didStartAuthHandler,
+      .didStartMessageListener,
+    ])
+
+    store.send(.stop)
   }
+
+  func testStartHandlersAndListeners() {
+    var actions: [Action] = []
+    var authHandlerOnError: [AuthCallbackHandler.OnError] = []
+    var messageListenerOnError: [MessageListenerHandler.OnError] = []
+
+    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.send(.start)
+
+    store.receive(.set(\.$screen, .home(HomeState()))) {
+      $0.screen = .home(HomeState())
+    }
+
+    XCTAssertNoDifference(actions, [
+      .didStartAuthHandler,
+      .didStartMessageListener,
+    ])
+    actions = []
+
+    store.send(.start) {
+      $0.screen = .loading
+    }
+
+    store.receive(.set(\.$screen, .home(HomeState()))) {
+      $0.screen = .home(HomeState())
+    }
+
+    XCTAssertNoDifference(actions, [
+      .didCancelAuthHandler,
+      .didCancelMessageListener,
+      .didStartAuthHandler,
+      .didStartMessageListener,
+    ])
+    actions = []
+
+    struct AuthError: Error {}
+    authHandlerOnError.first?(AuthError())
+
+    XCTAssertNoDifference(actions, [])
+    actions = []
+
+    struct MessageError: Error {}
+    messageListenerOnError.first?(MessageError())
+
+    XCTAssertNoDifference(actions, [])
+    actions = []
+
+    store.send(.stop)
+
+    XCTAssertNoDifference(actions, [
+      .didCancelAuthHandler,
+      .didCancelMessageListener,
+    ])
+  }
+}
+
+private enum Action: Equatable {
+  case didMakeDB
+  case didStartAuthHandler
+  case didStartMessageListener
+  case didLoadMessenger
+  case didCancelAuthHandler
+  case didCancelMessageListener
 }
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index d922d4f9f9e141ddaef8b577cdaccf6a0e24e922..a40f432cf7f02e207010f1a161baefb4082e73da 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -24,11 +24,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 }
@@ -39,15 +38,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() {
@@ -64,11 +58,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 = { true }
@@ -90,15 +83,11 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidListenForMessages, 1)
     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 testRegisterFinished() {
@@ -115,10 +104,9 @@ 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 }
@@ -141,15 +129,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() {
@@ -164,21 +148,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() {
@@ -193,23 +170,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() {
@@ -224,24 +194,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() {
@@ -256,25 +220,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() {
@@ -546,86 +504,4 @@ final class HomeFeatureTests: XCTestCase {
       $0.contacts = nil
     }
   }
-
-  func testAuthCallbacks() {
-    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(.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() {
-    let store = TestStore(
-      initialState: HomeState(),
-      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(.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/RestoreFeatureTests/RestoreFeatureTests.swift b/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift
index c3e77edbf64de2d8df6e3cfb518b610055928397..9a3cde3227357eb11d878c8c8f7fd7cc76f00fb5 100644
--- a/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift
@@ -1,15 +1,161 @@
+import CustomDump
 import ComposableArchitecture
 import XCTest
 @testable import RestoreFeature
+import XXMessengerClient
 
 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 restoreResult = MessengerRestoreBackup.Result(
+      restoredParams: BackupParams.init(
+        username: "",
+        email: nil,
+        phone: nil
+      ),
+      restoredContacts: []
+    )
+
+    var didRestoreWithData: [Data] = []
+    var didRestoreWithPassphrase: [String] = []
+
+    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.messenger.restoreBackup.run = { data, passphrase in
+      didRestoreWithData.append(data)
+      didRestoreWithPassphrase.append(passphrase)
+      return restoreResult
+    }
+
+    store.send(.set(\.$passphrase, backupPassphrase)) {
+      $0.passphrase = backupPassphrase
+    }
+
+    store.send(.restoreTapped) {
+      $0.isRestoring = true
+    }
+
+    XCTAssertNoDifference(didRestoreWithData, [backupData])
+    XCTAssertNoDifference(didRestoreWithPassphrase, [backupPassphrase])
+
+    store.receive(.finished) {
+      $0.isRestoring = false
+    }
+  }
+
+  func testRestoreWithoutFile() {
+    let store = TestStore(
+      initialState: RestoreState(
+        file: nil
+      ),
+      reducer: restoreReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.restoreTapped)
+  }
+
+  func testRestoreFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    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 }
+
+    store.send(.restoreTapped) {
+      $0.isRestoring = true
+    }
+
+    store.receive(.failed(failure as NSError)) {
+      $0.isRestoring = false
+      $0.restoreFailure = failure.localizedDescription
+    }
   }
 }
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/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/MessengerDestroy.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
index 65a718dffa7c33c246de7a8b371c749433f2898b..7d6932badafbae5128694b414df70a32c405fd02 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerDestroy.swift
@@ -22,6 +22,7 @@ extension MessengerDestroy {
       env.ud.set(nil)
       env.e2e.set(nil)
       env.cMix.set(nil)
+      env.isListeningForMessages.set(false)
       try env.fileManager.removeDirectory(env.storageDir)
       try env.passwordStorage.remove()
     }
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/MessengerRestoreBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9f0d591b44e0820c6f2f6af6611055c72f74be06
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift
@@ -0,0 +1,93 @@
+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
+      do {
+        let ndfData = try env.downloadNDF(env.ndfEnvironment)
+        let password = env.generateSecret()
+        try env.passwordStorage.save(password)
+        try env.fileManager.removeDirectory(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 cMix = try env.loadCMix(
+          storageDir: storageDir,
+          password: password,
+          cMixParamsJSON: env.getCMixParams()
+        )
+        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()
+        )
+        let decoder = JSONDecoder()
+        let paramsData = report.params.data(using: .utf8)!
+        let params = try decoder.decode(BackupParams.self, from: paramsData)
+        let ud = try env.newUdManagerFromBackup(
+          params: NewUdManagerFromBackup.Params(
+            e2eId: e2e.getId(),
+            username: Fact(type: .username, value: params.username),
+            email: params.email.map { Fact(type: .email, value: $0) },
+            phone: params.phone.map { Fact(type: .phone, value: $0) },
+            cert: env.udCert ?? e2e.getUdCertFromNdf(),
+            contact: env.udContact ?? (try e2e.getUdContactFromNdf()),
+            address: env.udAddress ?? e2e.getUdAddressFromNdf()
+          ),
+          follower: UdNetworkStatus { cMix.networkFollowerStatus() }
+        )
+        env.cMix.set(cMix)
+        env.e2e.set(e2e)
+        env.ud.set(ud)
+        env.isListeningForMessages.set(false)
+        return Result(
+          restoredParams: params,
+          restoredContacts: report.restoredContacts
+        )
+      } catch {
+        try? env.fileManager.removeDirectory(storageDir)
+        throw error
+      }
+    }
+  }
+}
+
+extension MessengerRestoreBackup {
+  public static let unimplemented = MessengerRestoreBackup(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift
index 77f7f1e52f85af6d083f3b26916d8432643ab6a4..ea85852c6752e30af60987264851d57af910e811 100644
--- a/Sources/XXMessengerClient/Messenger/Messenger.swift
+++ b/Sources/XXMessengerClient/Messenger/Messenger.swift
@@ -6,6 +6,7 @@ public struct Messenger {
   public var ud: Stored<UserDiscovery?>
   public var isCreated: MessengerIsCreated
   public var create: MessengerCreate
+  public var restoreBackup: MessengerRestoreBackup
   public var isLoaded: MessengerIsLoaded
   public var load: MessengerLoad
   public var registerAuthCallbacks: MessengerRegisterAuthCallbacks
@@ -13,6 +14,7 @@ public struct Messenger {
   public var start: MessengerStart
   public var isConnected: MessengerIsConnected
   public var connect: MessengerConnect
+  public var isListeningForMessages: MessengerIsListeningForMessages
   public var listenForMessages: MessengerListenForMessages
   public var isRegistered: MessengerIsRegistered
   public var register: MessengerRegister
@@ -37,6 +39,7 @@ extension Messenger {
       ud: env.ud,
       isCreated: .live(env),
       create: .live(env),
+      restoreBackup: .live(env),
       isLoaded: .live(env),
       load: .live(env),
       registerAuthCallbacks: .live(env),
@@ -44,6 +47,7 @@ extension Messenger {
       start: .live(env),
       isConnected: .live(env),
       connect: .live(env),
+      isListeningForMessages: .live(env),
       listenForMessages: .live(env),
       isRegistered: .live(env),
       register: .live(env),
@@ -69,6 +73,7 @@ extension Messenger {
     ud: .unimplemented(),
     isCreated: .unimplemented,
     create: .unimplemented,
+    restoreBackup: .unimplemented,
     isLoaded: .unimplemented,
     load: .unimplemented,
     registerAuthCallbacks: .unimplemented,
@@ -76,6 +81,7 @@ extension Messenger {
     start: .unimplemented,
     isConnected: .unimplemented,
     connect: .unimplemented,
+    isListeningForMessages: .unimplemented,
     listenForMessages: .unimplemented,
     isRegistered: .unimplemented,
     register: .unimplemented,
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index f125c1ef3ae705a1dde5d8e9ff1dc4467e25044c..641a8062d3f9ae136192af38b2814fe14cafccb9 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -12,6 +12,7 @@ public struct MessengerEnvironment {
   public var getCMixParams: GetCMixParams
   public var getE2EParams: GetE2EParams
   public var getSingleUseParams: GetSingleUseParams
+  public var isListeningForMessages: Stored<Bool>
   public var isRegisteredWithUD: IsRegisteredWithUD
   public var loadCMix: LoadCMix
   public var login: Login
@@ -20,7 +21,9 @@ public struct MessengerEnvironment {
   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 searchUD: SearchUD
@@ -50,6 +53,7 @@ extension MessengerEnvironment {
       getCMixParams: .liveDefault,
       getE2EParams: .liveDefault,
       getSingleUseParams: .liveDefault,
+      isListeningForMessages: .inMemory(false),
       isRegisteredWithUD: .live,
       loadCMix: .live,
       login: .live,
@@ -58,7 +62,9 @@ extension MessengerEnvironment {
       multiLookupUD: .live(),
       ndfEnvironment: .mainnet,
       newCMix: .live,
+      newCMixFromBackup: .live,
       newOrLoadUd: .live,
+      newUdManagerFromBackup: .live,
       passwordStorage: .keychain,
       registerForNotifications: .live,
       searchUD: .live,
@@ -83,6 +89,7 @@ extension MessengerEnvironment {
     getCMixParams: .unimplemented,
     getE2EParams: .unimplemented,
     getSingleUseParams: .unimplemented,
+    isListeningForMessages: .unimplemented(placeholder: false),
     isRegisteredWithUD: .unimplemented,
     loadCMix: .unimplemented,
     login: .unimplemented,
@@ -91,7 +98,9 @@ extension MessengerEnvironment {
     multiLookupUD: .unimplemented,
     ndfEnvironment: .unimplemented,
     newCMix: .unimplemented,
+    newCMixFromBackup: .unimplemented,
     newOrLoadUd: .unimplemented,
+    newUdManagerFromBackup: .unimplemented,
     passwordStorage: .unimplemented,
     registerForNotifications: .unimplemented,
     searchUD: .unimplemented,
diff --git a/Sources/XXMessengerClient/Utils/BackupParams.swift b/Sources/XXMessengerClient/Utils/BackupParams.swift
new file mode 100644
index 0000000000000000000000000000000000000000..02fdc595461cbcc14e617560b6f7b91b6b398fc2
--- /dev/null
+++ b/Sources/XXMessengerClient/Utils/BackupParams.swift
@@ -0,0 +1,17 @@
+import Foundation
+
+public struct BackupParams: Equatable, Codable {
+  public init(
+    username: String,
+    email: String?,
+    phone: String?
+  ) {
+    self.username = username
+    self.email = email
+    self.phone = phone
+  }
+
+  public var username: String
+  public var email: String?
+  public var phone: String?
+}
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/MessengerDestroyTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
index 36745d028934142b59912d341649e1519b435e1e..ccf999e77e6771724d17e27b6b43ce6a811fb8ae 100644
--- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerDestroyTests.swift
@@ -14,6 +14,7 @@ final class MessengerDestroyTests: XCTestCase {
     var didSetE2E: [E2E?] = []
     var didSetCMix: [CMix?] = []
     var didRemovePassword = 0
+    var didSetIsListeningForMessages: [Bool] = []
 
     var env: MessengerEnvironment = .unimplemented
     env.cMix.get = {
@@ -28,6 +29,7 @@ final class MessengerDestroyTests: XCTestCase {
     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.fileManager.removeDirectory = { didRemoveDirectory.append($0) }
     env.passwordStorage.remove = { didRemovePassword += 1 }
     let destroy: MessengerDestroy = .live(env)
@@ -39,6 +41,7 @@ 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])
     XCTAssertNoDifference(didRemoveDirectory, [storageDir])
     XCTAssertNoDifference(didRemovePassword, 1)
   }
@@ -67,12 +70,14 @@ 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.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
     env.fileManager.removeDirectory = { _ in throw error }
     let destroy: MessengerDestroy = .live(env)
 
@@ -82,6 +87,7 @@ 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() {
@@ -92,12 +98,14 @@ 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.isListeningForMessages.set = { didSetIsListeningForMessages.append($0) }
     env.storageDir = storageDir
     env.fileManager.removeDirectory = { didRemoveDirectory.append($0) }
     env.passwordStorage.remove = { throw error }
@@ -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(didSetIsListeningForMessages, [false])
     XCTAssertNoDifference(didRemoveDirectory, [storageDir])
   }
 }
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/MessengerRestoreBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRestoreBackupTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..fb8fe7da315df608763b572a273ec67ef2c8366c
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRestoreBackupTests.swift
@@ -0,0 +1,240 @@
+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(
+      username: "backup-username",
+      email: "backup-email",
+      phone: "backup-phone"
+    )
+    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 udContactFromNdf = "ud-contact".data(using: .utf8)!
+    let udCertFromNdf = "ud-cert".data(using: .utf8)!
+    let udAddressFromNdf = "ud-address"
+
+    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.removeDirectory = { caughtActions.append(.didRemoveDirectory(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.getUdCertFromNdf.run = { udCertFromNdf }
+      e2e.getUdContactFromNdf.run = { udContactFromNdf }
+      e2e.getUdAddressFromNdf.run = { udAddressFromNdf }
+      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
+      ),
+      .didRemoveDirectory(
+        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
+      ),
+      .cMixDidStartNetworkFollower(
+        timeoutMS: 30_000
+      ),
+      .cMixDidMakeReceptionIdentity(
+        legacy: true
+      ),
+      .didLogin(
+        ephemeral: false,
+        cMixId: cMixId,
+        identity: receptionIdentity,
+        e2eParamsJSON: e2eParams
+      ),
+      .didNewUdManagerFromBackup(params: .init(
+        e2eId: e2eId,
+        username: Fact(type: .username, value: backupParams.username),
+        email: Fact(type: .email, value: backupParams.email!),
+        phone: Fact(type: .phone, value: backupParams.phone!),
+        cert: udCertFromNdf,
+        contact: udContactFromNdf,
+        address: udAddressFromNdf
+      )),
+      .didSetCMix,
+      .didSetE2E,
+      .didSetUD,
+      .didSetIsListeningForMessages(
+        isListening: false
+      ),
+    ])
+
+    XCTAssertNoDifference(result, MessengerRestoreBackup.Result(
+      restoredParams: backupParams,
+      restoredContacts: backupContacts
+    ))
+  }
+
+  func testDownloadNdfFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var actions: [CaughtAction] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.downloadNDF.run = { _ in throw failure }
+    env.fileManager.removeDirectory = { actions.append(.didRemoveDirectory(path: $0)) }
+    let restore: MessengerRestoreBackup = .live(env)
+
+    XCTAssertThrowsError(try restore(backupData: Data(), backupPassphrase: "")) { error in
+      XCTAssertNoDifference(error as? Failure, failure)
+    }
+    XCTAssertNoDifference(actions, [
+      .didRemoveDirectory(path: env.storageDir)
+    ])
+  }
+}
+
+private enum CaughtAction: Equatable {
+  case didDownloadNDF(
+    environment: NDFEnvironment
+  )
+  case didSavePassword(
+    password: Data
+  )
+  case didRemoveDirectory(
+    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
+  )
+}