diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index c0a4f9054a911c82a801544c2ff17e4629c0ecfb..f552abe40f22920d1358f3487de7207633d272af 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -273,6 +273,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/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index d824d6d400b402270d341bb43fbd5f363c84a879..e00d9d8231fd35509eabc64d79c22f2de715646b 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -95,7 +95,12 @@ extension AppEnvironment {
         )
       },
       restore: {
-        RestoreEnvironment()
+        RestoreEnvironment(
+          messenger: messenger,
+          loadData: .live,
+          mainQueue: mainQueue,
+          bgQueue: bgQueue
+        )
       },
       home: {
         HomeEnvironment(
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/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
+    }
   }
 }