diff --git a/App/client-ios.xcodeproj/project.pbxproj b/App/client-ios.xcodeproj/project.pbxproj
index c5951402ab44c15cbccd0d4017c7b572c1365596..df25d8568ed39b8a2eac0b9e49346bf9065e5e6d 100644
--- a/App/client-ios.xcodeproj/project.pbxproj
+++ b/App/client-ios.xcodeproj/project.pbxproj
@@ -448,7 +448,7 @@
 				CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements";
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 267;
+				CURRENT_PROJECT_VERSION = 283;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
@@ -463,7 +463,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.1.7;
+				MARKETING_VERSION = 1.1.8;
 				OTHER_SWIFT_FLAGS = "$(inherited)";
 				PRODUCT_BUNDLE_IDENTIFIER = xx.messenger.mock;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -487,7 +487,7 @@
 				CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements";
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 267;
+				CURRENT_PROJECT_VERSION = 283;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
@@ -503,7 +503,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.1.7;
+				MARKETING_VERSION = 1.1.8;
 				OTHER_SWIFT_FLAGS = "";
 				PRODUCT_BUNDLE_IDENTIFIER = xx.messenger;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -522,7 +522,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 267;
+				CURRENT_PROJECT_VERSION = 283;
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -536,7 +536,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 1.1.7;
+				MARKETING_VERSION = 1.1.8;
 				PRODUCT_BUNDLE_IDENTIFIER = xx.messenger.mock.notifications;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -553,7 +553,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 267;
+				CURRENT_PROJECT_VERSION = 283;
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -567,7 +567,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 1.1.7;
+				MARKETING_VERSION = 1.1.8;
 				PRODUCT_BUNDLE_IDENTIFIER = xx.messenger.notifications;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
diff --git a/Package.swift b/Package.swift
index b3f848aaf253b239def7a62d44a4703b3fea0bcf..14e5880804888513d778f51ee860c843bd3e3060 100644
--- a/Package.swift
+++ b/Package.swift
@@ -345,10 +345,13 @@ let package = Package(
         .target(name: "HUD"),
         .target(name: "Shared"),
         .target(name: "Presentation"),
-        .target(name: "BackupFeature"),
         .target(name: "DependencyInjection"),
         .product(name: "XXDatabase", package: "client-ios-db"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "CloudFilesDrive", package: "xxm-cloud-providers"),
+        .product(name: "CloudFilesDropbox", package: "xxm-cloud-providers"),
+        .product(name: "CloudFilesSFTP", package: "xxm-cloud-providers"),
+        .product(name: "CloudFilesICloud", package: "xxm-cloud-providers"),
       ]
     ),
     .target(
@@ -441,9 +444,11 @@ let package = Package(
         .target(name: "ReportingFeature"),
         .target(name: "DependencyInjection"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
-        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "CloudFilesSFTP", package: "xxm-cloud-providers"),
         .product(name: "CombineSchedulers", package: "combine-schedulers"),
+        .product(name: "CloudFilesDropbox", package: "xxm-cloud-providers"),
         .product(name: "XXLegacyDatabaseMigrator", package: "client-ios-db"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
       ]
     ),
     .target(
@@ -580,7 +585,14 @@ let package = Package(
         .target(name: "InputField"),
         .target(name: "Presentation"),
         .target(name: "DrawerFeature"),
+        .target(name: "NetworkMonitor"),
         .target(name: "DependencyInjection"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "CloudFilesSFTP", package: "xxm-cloud-providers"),
+        .product(name: "CloudFilesDrive", package: "xxm-cloud-providers"),
+        .product(name: "CloudFilesICloud", package: "xxm-cloud-providers"),
+        .product(name: "CloudFilesDropbox", package: "xxm-cloud-providers"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
       ]
     ),
     .target(
diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift
index b85cc758f100ce2c383b144772cd9f0c18921633..365a58740c054b5b86d90819a943fd1e9c6e67d0 100644
--- a/Sources/App/AppDelegate.swift
+++ b/Sources/App/AppDelegate.swift
@@ -14,11 +14,14 @@ import DependencyInjection
 import XXClient
 import XXMessengerClient
 
+import CloudFiles
+import CloudFilesDrive
+import CloudFilesDropbox
+
 public class AppDelegate: UIResponder, UIApplicationDelegate {
   @Dependency private var pushRouter: PushRouter
   @Dependency private var pushHandler: PushHandling
   @Dependency private var crashReporter: CrashReporter
-  @Dependency private var dropboxService: DropboxInterface
 
   @KeyObject(.hideAppList, defaultValue: false) var hideAppList: Bool
   @KeyObject(.recordingLogs, defaultValue: true) var recordingLogs: Bool
@@ -61,6 +64,7 @@ public class AppDelegate: UIResponder, UIApplicationDelegate {
       PushRouter.live(navigationController: navController)
     )
 
+    restoreIfPossible()
     return true
   }
 
@@ -148,7 +152,7 @@ public class AppDelegate: UIResponder, UIApplicationDelegate {
     open url: URL,
     options: [UIApplication.OpenURLOptionsKey : Any] = [:]
   ) -> Bool {
-    dropboxService.handleOpenUrl(url)
+    handleRedirectURL(url)
   }
 
   public func application(
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index bbc8ef75a756e0ce2c10c5a41d142fd8a5c60993..b15fe648a8c9d98c6faf73de46d1b5025d68cb0c 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -63,13 +63,6 @@ struct DependencyRegistrator {
         container.register(MockKeychainHandler() as KeychainHandling)
         container.register(MockPermissionHandler() as PermissionHandling)
 
-        /// Restore / Backup
-
-        container.register(SFTPService.mock)
-        container.register(iCloudServiceMock() as iCloudInterface)
-        container.register(DropboxServiceMock() as DropboxInterface)
-        container.register(GoogleDriveServiceMock() as GoogleDriveInterface)
-
         registerCommonDependencies()
     }
 
@@ -94,13 +87,6 @@ struct DependencyRegistrator {
         container.register(KeychainHandler() as KeychainHandling)
         container.register(PermissionHandler() as PermissionHandling)
 
-        /// Restore / Backup
-
-        container.register(SFTPService.live)
-        container.register(iCloudService() as iCloudInterface)
-        container.register(DropboxService() as DropboxInterface)
-        container.register(GoogleDriveService() as GoogleDriveInterface)
-
         registerCommonDependencies()
     }
 
@@ -143,6 +129,7 @@ struct DependencyRegistrator {
 
         container.register(
             BackupCoordinator(
+                sftpFactory: BackupSFTPController.init(_:),
                 passphraseFactory: BackupPassphraseController.init(_:_:)
             ) as BackupCoordinating)
 
@@ -188,6 +175,7 @@ struct DependencyRegistrator {
                 successFactory: RestoreSuccessController.init,
                 chatListFactory: ChatListController.init,
                 restoreFactory: RestoreController.init(_:),
+                sftpFactory: RestoreSFTPController.init(_:),
                 passphraseFactory: RestorePassphraseController.init(_:_:)
             ) as RestoreCoordinating)
 
diff --git a/Sources/BackupFeature/Controllers/BackupConfigController.swift b/Sources/BackupFeature/Controllers/BackupConfigController.swift
index a901e6b9668fc89df2cd1ae1184ca655fa8bdb1b..722e5530adcbe0bf9e4e8be9d0f9518f3893fd02 100644
--- a/Sources/BackupFeature/Controllers/BackupConfigController.swift
+++ b/Sources/BackupFeature/Controllers/BackupConfigController.swift
@@ -72,7 +72,7 @@ final class BackupConfigController: UIViewController {
                     return
                 }
 
-                screenView.latestBackupDetailView.subtitleLabel.text = backup.date.backupStyle()
+                screenView.latestBackupDetailView.subtitleLabel.text = backup.lastModified.backupStyle()
             }.store(in: &cancellables)
 
         screenView.actionView.backupNowButton
@@ -131,7 +131,7 @@ final class BackupConfigController: UIViewController {
             .store(in: &cancellables)
     }
 
-    private func decorate(enabledService: CloudService?) {
+    private func decorate(enabledService: BackupProvider?) {
         var button: BackupSwitcherButton?
 
         switch enabledService {
@@ -188,7 +188,7 @@ final class BackupConfigController: UIViewController {
         }
     }
 
-    private func decorate(connectedServices: Set<CloudService>) {
+    private func decorate(connectedServices: Set<BackupProvider>) {
         if connectedServices.contains(.icloud) {
             screenView.iCloudButton.showSwitcher(enabled: false)
         } else {
diff --git a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
index f16acd561f4ca41c9f2abe0db1101a58df4cfcad..9ef0fdfb6087c96941953ef25b5bafdf686b1a80 100644
--- a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
+++ b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
@@ -3,67 +3,89 @@ import Shared
 import Presentation
 import ScrollViewController
 
+public typealias SFTPDetailsClosure = (String, String, String) -> Void
+
 public protocol BackupCoordinating {
-    func toDrawer(
-        _: UIViewController,
-        from: UIViewController
-    )
+  func toDrawer(
+    _: UIViewController,
+    from: UIViewController
+  )
+
+  func toSFTP(
+    from: UIViewController,
+    detailsClosure: @escaping SFTPDetailsClosure
+  )
 
-    func toPassphrase(
-        from: UIViewController,
-        cancelClosure: @escaping EmptyClosure,
-        passphraseClosure: @escaping StringClosure
-    )
+  func toPassphrase(
+    from: UIViewController,
+    cancelClosure: @escaping EmptyClosure,
+    passphraseClosure: @escaping StringClosure
+  )
 }
 
 public struct BackupCoordinator: BackupCoordinating {
-    var fullscreenPresenter: Presenting = FullscreenPresenter()
+  var pushPresenter: Presenting = PushPresenter()
+  var fullscreenPresenter: Presenting = FullscreenPresenter()
 
-    var passphraseFactory: (
-        @escaping EmptyClosure,
-        @escaping StringClosure
-    ) -> UIViewController
+  var sftpFactory: (@escaping SFTPDetailsClosure) -> UIViewController
 
-    public init(
-        passphraseFactory: @escaping (
-            @escaping EmptyClosure,
-            @escaping StringClosure
-        ) -> UIViewController
-    ) {
-        self.passphraseFactory = passphraseFactory
-    }
+  var passphraseFactory: (
+    @escaping EmptyClosure,
+    @escaping StringClosure
+  ) -> UIViewController
+
+  public init(
+    sftpFactory: @escaping (
+      @escaping SFTPDetailsClosure
+    ) -> UIViewController,
+    passphraseFactory: @escaping (
+      @escaping EmptyClosure,
+      @escaping StringClosure
+    ) -> UIViewController
+  ) {
+    self.sftpFactory = sftpFactory
+    self.passphraseFactory = passphraseFactory
+  }
 }
 
 public extension BackupCoordinator {
-    func toDrawer(
-        _ screen: UIViewController,
-        from parent: UIViewController
-    ) {
-        let target = ScrollViewController.embedding(screen)
-        fullscreenPresenter.present(target, from: parent)
-    }
+  func toSFTP(
+    from parent: UIViewController,
+    detailsClosure: @escaping SFTPDetailsClosure
+  ) {
+    let screen = sftpFactory(detailsClosure)
+    pushPresenter.present(screen, from: parent)
+  }
+
+  func toDrawer(
+    _ screen: UIViewController,
+    from parent: UIViewController
+  ) {
+    let target = ScrollViewController.embedding(screen)
+    fullscreenPresenter.present(target, from: parent)
+  }
 
-    func toPassphrase(
-        from parent: UIViewController,
-        cancelClosure: @escaping EmptyClosure,
-        passphraseClosure: @escaping StringClosure
-    ) {
-        let screen = passphraseFactory(cancelClosure, passphraseClosure)
-        let target = ScrollViewController.embedding(screen)
-        fullscreenPresenter.present(target, from: parent)
-    }
+  func toPassphrase(
+    from parent: UIViewController,
+    cancelClosure: @escaping EmptyClosure,
+    passphraseClosure: @escaping StringClosure
+  ) {
+    let screen = passphraseFactory(cancelClosure, passphraseClosure)
+    let target = ScrollViewController.embedding(screen)
+    fullscreenPresenter.present(target, from: parent)
+  }
 }
 
 extension ScrollViewController {
-    static func embedding(_ viewController: UIViewController) -> ScrollViewController {
-        let scrollViewController = ScrollViewController()
-        scrollViewController.addChild(viewController)
-        scrollViewController.contentView = viewController.view
-        scrollViewController.wrapperView.handlesTouchesOutsideContent = false
-        scrollViewController.wrapperView.alignContentToBottom = true
-        scrollViewController.scrollView.bounces = false
+  static func embedding(_ viewController: UIViewController) -> ScrollViewController {
+    let scrollViewController = ScrollViewController()
+    scrollViewController.addChild(viewController)
+    scrollViewController.contentView = viewController.view
+    scrollViewController.wrapperView.handlesTouchesOutsideContent = false
+    scrollViewController.wrapperView.alignContentToBottom = true
+    scrollViewController.scrollView.bounces = false
 
-        viewController.didMove(toParent: scrollViewController)
-        return scrollViewController
-    }
+    viewController.didMove(toParent: scrollViewController)
+    return scrollViewController
+  }
 }
diff --git a/Sources/BackupFeature/Models/BackupSettings.swift b/Sources/BackupFeature/Models/BackupSettings.swift
new file mode 100644
index 0000000000000000000000000000000000000000..5442f3015665a829f20287318a35ae47221a27f9
--- /dev/null
+++ b/Sources/BackupFeature/Models/BackupSettings.swift
@@ -0,0 +1,48 @@
+import Foundation
+import CloudFiles
+
+public struct BackupSettings: Equatable, Codable {
+  public var wifiOnlyBackup: Bool
+  public var automaticBackups: Bool
+  public var enabledService: BackupProvider?
+  public var connectedServices: Set<BackupProvider>
+  public var backups: [BackupProvider: Fetch.Metadata]
+
+  public init(
+    wifiOnlyBackup: Bool = false,
+    automaticBackups: Bool = false,
+    enabledService: BackupProvider? = nil,
+    connectedServices: Set<BackupProvider> = [],
+    backups: [BackupProvider: Fetch.Metadata] = [:]
+  ) {
+    self.wifiOnlyBackup = wifiOnlyBackup
+    self.automaticBackups = automaticBackups
+    self.enabledService = enabledService
+    self.connectedServices = connectedServices
+    self.backups = backups
+  }
+
+  public func toData() -> Data {
+    (try? PropertyListEncoder().encode(self)) ?? Data()
+  }
+
+  public init(fromData data: Data?) {
+    if let data = data, let settings = try? PropertyListDecoder().decode(BackupSettings.self, from: data) {
+      self.init(
+        wifiOnlyBackup: settings.wifiOnlyBackup,
+        automaticBackups: settings.automaticBackups,
+        enabledService: settings.enabledService,
+        connectedServices: settings.connectedServices,
+        backups: settings.backups
+      )
+    } else {
+      self.init(
+        wifiOnlyBackup: false,
+        automaticBackups: true,
+        enabledService: nil,
+        connectedServices: [],
+        backups: [:]
+      )
+    }
+  }
+}
diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift
index 1ffc64356fa3307d5312ea76c1294f3f7d18848f..72cbe7fee7b3fb7cc1c6381f62720e11c394770f 100644
--- a/Sources/BackupFeature/Service/BackupService.swift
+++ b/Sources/BackupFeature/Service/BackupService.swift
@@ -1,21 +1,30 @@
 import UIKit
 import Models
 import Combine
+import XXClient
 import Defaults
-import Keychain
 import NetworkMonitor
-import DependencyInjection
-import XXClient
 import XXMessengerClient
+import DependencyInjection
+
+import CloudFiles
+import CloudFilesSFTP
+import CloudFilesDrive
+import CloudFilesICloud
+import CloudFilesDropbox
+
+import KeychainAccess
+
+public enum BackupProvider: Equatable, Codable {
+  case sftp
+  case drive
+  case icloud
+  case dropbox
+}
 
 public final class BackupService {
   @Dependency var messenger: Messenger
-  @Dependency var sftpService: SFTPService
-  @Dependency var icloudService: iCloudInterface
-  @Dependency var dropboxService: DropboxInterface
   @Dependency var networkManager: NetworkMonitoring
-  @Dependency var keychainHandler: KeychainHandling
-  @Dependency var driveService: GoogleDriveInterface
 
   @KeyObject(.username, defaultValue: nil) var username: String?
   @KeyObject(.backupSettings, defaultValue: nil) var storedSettings: Data?
@@ -44,17 +53,23 @@ public final class BackupService {
       .sink { [unowned self] in connType = $0 }
       .store(in: &cancellables)
   }
+
+  public func setupSFTP(host: String, username: String, password: String) {
+    managers[.sftp] = .sftp(
+      host: host,
+      username: username,
+      password: password,
+      fileName: "backup.xxm"
+    )
+    refreshBackups()
+    refreshConnections()
+  }
 }
 
 extension BackupService {
   public func stopBackups() {
-    print(">>> [AccountBackup] Requested to stop backup mechanism")
-
     if messenger.isBackupRunning() == true {
-      print(">>> [AccountBackup] messenger.isBackupRunning() == true")
       try! messenger.stopBackup()
-
-      print(">>> [AccountBackup] Stopped backup mechanism")
     }
   }
 
@@ -63,20 +78,14 @@ extension BackupService {
       password: passphrase,
       params: .init(username: username!)
     )
-
-    print(">>> [AccountBackup] Initialized backup mechanism")
   }
 
   public func performBackupIfAutomaticIsEnabled() {
-    print(">>> [AccountBackup] Requested backup if automatic is enabled")
-
     guard settings.value.automaticBackups == true else { return }
     performBackup()
   }
 
   public func performBackup() {
-    print(">>> [AccountBackup] Requested backup without explicitly passing data")
-
     guard let directoryUrl = try? FileManager.default.url(
       for: .applicationSupportDirectory,
       in: .userDomainMask,
@@ -88,17 +97,11 @@ extension BackupService {
       .appendingPathComponent("backup")
       .appendingPathExtension("xxm")
 
-    guard let data = try? Data(contentsOf: fileUrl) else {
-      print(">>> [AccountBackup] Tried to backup arbitrarily but there was nothing to be backed up. Aborting...")
-      return
-    }
-
+    guard let data = try? Data(contentsOf: fileUrl) else { return }
     performBackup(data: data)
   }
 
   public func updateBackup(data: Data) {
-    print(">>> [AccountBackup] Requested to update backup passing data")
-
     guard let directoryUrl = try? FileManager.default.url(
       for: .applicationSupportDirectory,
       in: .userDomainMask,
@@ -144,161 +147,64 @@ extension BackupService {
     performBackup()
   }
 
-  public func toggle(service: CloudService, enabling: Bool) {
+  public func toggle(service: BackupProvider, enabling: Bool) {
     settings.value.enabledService = enabling ? service : nil
   }
 
-  public func authorize(service: CloudService, presenting screen: UIViewController) {
-    switch service {
-    case .drive:
-      driveService.authorize(presenting: screen) { [weak self] _ in
-        guard let self = self else { return }
-        self.refreshConnections()
-        self.refreshBackups()
-      }
-    case .icloud:
-      if !icloudService.isAuthorized() {
-        icloudService.openSettings()
-      } else {
-        refreshConnections()
-        refreshBackups()
-      }
-    case .dropbox:
-      if !dropboxService.isAuthorized() {
-        dropboxService.authorize(presenting: screen)
-          .sink { [weak self] _ in
-            guard let self = self else { return }
-            self.refreshConnections()
-            self.refreshBackups()
-          }.store(in: &cancellables)
-      }
-    case .sftp:
-      if !sftpService.isAuthorized() {
-        sftpService.authorizeFlow((screen, { [weak self] in
-          guard let self = self else { return }
-          screen.navigationController?.popViewController(animated: true)
+  public func authorize(
+    service: BackupProvider,
+    presenting screen: UIViewController
+  ) {
+    do {
+      try managers[service]?.link(screen) { [weak self] in
+        guard let self else { return }
+        switch $0 {
+        case .success:
           self.refreshConnections()
           self.refreshBackups()
-        }))
+        case .failure(let error):
+          print(error.localizedDescription)
+        }
       }
+    } catch {
+      print(error.localizedDescription)
     }
   }
 }
 
 extension BackupService {
   private func refreshConnections() {
-    if icloudService.isAuthorized() && !settings.value.connectedServices.contains(.icloud) {
-      settings.value.connectedServices.insert(.icloud)
-    } else if !icloudService.isAuthorized() && settings.value.connectedServices.contains(.icloud) {
-      settings.value.connectedServices.remove(.icloud)
-    }
-
-    if dropboxService.isAuthorized() && !settings.value.connectedServices.contains(.dropbox) {
-      settings.value.connectedServices.insert(.dropbox)
-    } else if !dropboxService.isAuthorized() && settings.value.connectedServices.contains(.dropbox) {
-      settings.value.connectedServices.remove(.dropbox)
-    }
-
-    if sftpService.isAuthorized() && !settings.value.connectedServices.contains(.sftp) {
-      settings.value.connectedServices.insert(.sftp)
-    } else if !sftpService.isAuthorized() && settings.value.connectedServices.contains(.sftp) {
-      settings.value.connectedServices.remove(.sftp)
-    }
-
-    driveService.isAuthorized { [weak settings] isAuthorized in
-      guard let settings = settings else { return }
-
-      if isAuthorized && !settings.value.connectedServices.contains(.drive) {
-        settings.value.connectedServices.insert(.drive)
-      } else if !isAuthorized && settings.value.connectedServices.contains(.drive) {
-        settings.value.connectedServices.remove(.drive)
+    managers.forEach { provider, manager in
+      if manager.isLinked() && !settings.value.connectedServices.contains(provider) {
+        settings.value.connectedServices.insert(provider)
+      } else if !manager.isLinked() && settings.value.connectedServices.contains(provider) {
+        settings.value.connectedServices.remove(provider)
       }
     }
   }
 
   private func refreshBackups() {
-    print(">>> Refreshing backups...")
-
-    if icloudService.isAuthorized() {
-      print(">>> Refreshing icloud backup...")
-
-      icloudService.downloadMetadata { [weak settings] in
-        guard let settings = settings else { return }
-
-        guard let metadata = try? $0.get() else {
-          settings.value.backups[.icloud] = nil
-          return
-        }
-
-        settings.value.backups[.icloud] = BackupModel(
-          id: metadata.path,
-          date: metadata.modifiedDate,
-          size: metadata.size
-        )
-      }
-    }
-
-    if sftpService.isAuthorized() {
-      print(">>> Refreshing sftp backup...")
-
-      sftpService.fetchMetadata { [weak settings] in
-        guard let settings = settings else { return }
-
-        guard let metadata = try? $0.get()?.backup else {
-          settings.value.backups[.sftp] = nil
-          return
-        }
-
-        settings.value.backups[.sftp] = BackupModel(
-          id: metadata.id,
-          date: metadata.date,
-          size: metadata.size
-        )
-      }
-    }
-
-    if dropboxService.isAuthorized() {
-      print(">>> Refreshing dropbox backup...")
-
-      dropboxService.downloadMetadata { [weak settings] in
-        guard let settings = settings else { return }
-
-        guard let metadata = try? $0.get() else {
-          settings.value.backups[.dropbox] = nil
-          return
-        }
-
-        settings.value.backups[.dropbox] = BackupModel(
-          id: metadata.path,
-          date: metadata.modifiedDate,
-          size: metadata.size
-        )
-      }
-    }
-
-    driveService.isAuthorized { [weak settings] isAuthorized  in
-      print(">>> Refreshing drive backup...")
-      guard let settings = settings else { return }
-
-      if isAuthorized {
-        self.driveService.downloadMetadata {
-          guard let metadata = try? $0.get() else { return }
-
-          settings.value.backups[.drive] = BackupModel(
-            id: metadata.identifier,
-            date: metadata.modifiedDate,
-            size: metadata.size
-          )
+    managers.forEach { provider, manager in
+      if manager.isLinked() {
+        do {
+          try manager.fetch { [weak self] in
+            guard let self else { return }
+
+            switch $0 {
+            case .success(let metadata):
+              self.settings.value.backups[provider] = metadata
+            case .failure(let error):
+              print(error.localizedDescription)
+            }
+          }
+        } catch {
+          print(error.localizedDescription)
         }
-      } else {
-        settings.value.backups[.drive] = nil
       }
     }
   }
 
   private func performBackup(data: Data) {
-    print(">>> Did call performBackup(data)")
-
     guard let enabledService = settings.value.enabledService else {
       fatalError("Trying to backup but nothing is enabled")
     }
@@ -313,62 +219,39 @@ extension BackupService {
       return
     }
 
-    switch enabledService {
-    case .drive:
-      print(">>> Performing upload on drive")
-      driveService.uploadBackup(url) {
-        switch $0 {
-        case .success(let metadata):
-          self.settings.value.backups[.drive] = .init(
-            id: metadata.identifier,
-            date: metadata.modifiedDate,
-            size: metadata.size
-          )
-        case .failure(let error):
-          print(error.localizedDescription)
-        }
-      }
-    case .icloud:
-      print(">>> Performing upload on iCloud")
-      icloudService.uploadBackup(url) {
-        switch $0 {
-        case .success(let metadata):
-          self.settings.value.backups[.icloud] = .init(
-            id: metadata.path,
-            date: metadata.modifiedDate,
-            size: metadata.size
-          )
-        case .failure(let error):
-          print(error.localizedDescription)
-        }
+    if enabledService == .sftp {
+      let keychain = Keychain(service: "SFTP-XXM")
+      guard let host = try? keychain.get("host"),
+            let password = try? keychain.get("pwd"),
+            let username = try? keychain.get("username") else {
+        fatalError("Tried to perform an sftp backup but its not configured")
       }
-    case .dropbox:
-      print(">>> Performing upload on dropbox")
-      dropboxService.uploadBackup(url) {
-        switch $0 {
-        case .success(let metadata):
-          print(">>> Performed upload on dropbox: \(metadata)")
 
-          self.settings.value.backups[.dropbox] = .init(
-            id: metadata.path,
-            date: metadata.modifiedDate,
-            size: metadata.size
-          )
+      managers[.sftp] = .sftp(
+        host: host,
+        username: username,
+        password: password,
+        fileName: "backup.xxm"
+      )
+    }
 
-          self.refreshBackups()
-        case .failure(let error):
-          print(error.localizedDescription)
-        }
-      }
-    case .sftp:
-      print(">>> Performing upload on sftp")
-      sftpService.uploadBackup(url: url) {
-        switch $0 {
-        case .success(let backup):
-          self.settings.value.backups[.sftp] = backup
-        case .failure(let error):
-          print(error.localizedDescription)
+    if let manager = managers[enabledService] {
+      do {
+        try manager.upload(data) { [weak self] in
+          guard let self else { return }
+
+          switch $0 {
+          case .success(let metadata):
+            self.settings.value.backups[enabledService] = .init(
+              size: metadata.size,
+              lastModified: metadata.lastModified
+            )
+          case .failure(let error):
+            print(error.localizedDescription)
+          }
         }
+      } catch {
+        print(error.localizedDescription)
       }
     }
   }
diff --git a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
index 0b205733e2f44e8554a8c3df9add0d8382d72af7..ba2ce35ef322919fa4584106eca3fdc848ff89e4 100644
--- a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
@@ -9,6 +9,8 @@ import Foundation
 
 import DependencyInjection
 
+import CloudFiles
+
 enum BackupActionState {
   case backupFinished
   case backupAllowed(Bool)
@@ -19,15 +21,15 @@ struct BackupConfigViewModel {
   var didTapBackupNow: () -> Void
   var didChooseWifiOnly: (Bool) -> Void
   var didChooseAutomatic: (Bool) -> Void
-  var didToggleService: (UIViewController, CloudService, Bool) -> Void
-  var didTapService: (CloudService, UIViewController) -> Void
+  var didToggleService: (UIViewController, BackupProvider, Bool) -> Void
+  var didTapService: (BackupProvider, UIViewController) -> Void
 
   var wifiOnly: () -> AnyPublisher<Bool, Never>
   var automatic: () -> AnyPublisher<Bool, Never>
-  var lastBackup: () -> AnyPublisher<BackupModel?, Never>
+  var lastBackup: () -> AnyPublisher<Fetch.Metadata?, Never>
   var actionState: () -> AnyPublisher<BackupActionState, Never>
-  var enabledService: () -> AnyPublisher<CloudService?, Never>
-  var connectedServices: () -> AnyPublisher<Set<CloudService>, Never>
+  var enabledService: () -> AnyPublisher<BackupProvider?, Never>
+  var connectedServices: () -> AnyPublisher<Set<BackupProvider>, Never>
 }
 
 extension BackupConfigViewModel {
@@ -56,7 +58,6 @@ extension BackupConfigViewModel {
           context.service.stopBackups()
           return
         }
-
         context.coordinator.toPassphrase(from: controller, cancelClosure: {
           context.service.toggle(service: service, enabling: false)
         }, passphraseClosure: { passphrase in
@@ -66,7 +67,16 @@ extension BackupConfigViewModel {
           context.hud.update(with: .none)
         })
       },
-      didTapService: context.service.authorize,
+      didTapService: { service, controller in
+        if service == .sftp {
+          context.coordinator.toSFTP(from: controller) { host, username, password in
+            context.service.setupSFTP(host: host, username: username, password: password)
+          }
+          return
+        }
+
+        context.service.authorize(service: service, presenting: controller)
+      },
       wifiOnly: {
         context.service.settingsPublisher
           .map(\.wifiOnlyBackup)
@@ -79,7 +89,6 @@ extension BackupConfigViewModel {
       },
       lastBackup: {
         context.service.settingsPublisher
-          .print(">>> lastBackup updated!")
           .map {
             guard let enabledService = $0.enabledService else { return nil }
             return $0.backups[enabledService]
diff --git a/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift b/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift
index fe94e5b00d0090c8bd39d443dd771ad3468fccfc..c504cd0dec0903c6b21add727adedddc83d6b16b 100644
--- a/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift
@@ -5,7 +5,7 @@ import Combine
 import DependencyInjection
 
 struct BackupSetupViewModel {
-    var didTapService: (CloudService, UIViewController) -> Void
+    var didTapService: (BackupProvider, UIViewController) -> Void
 }
 
 extension BackupSetupViewModel {
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index 4ac8d1bfa793b4b4fa9854de283f62dec0770ccc..a207de77bfd825167e5f71efeac4600512daa929 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -24,6 +24,10 @@ import XXLegacyDatabaseMigrator
 import XXMessengerClient
 import NetworkMonitor
 
+import CloudFiles
+import CloudFilesSFTP
+import CloudFilesDropbox
+
 struct Update {
   let content: String
   let urlString: String
@@ -42,7 +46,6 @@ final class LaunchViewModel {
   @Dependency var database: Database
   @Dependency var backupService: BackupService
   @Dependency var versionChecker: VersionChecker
-  @Dependency var dropboxService: DropboxInterface
   @Dependency var fetchBannedList: FetchBannedList
   @Dependency var reportingStatus: ReportingStatus
   @Dependency var toastController: ToastController
@@ -72,6 +75,18 @@ final class LaunchViewModel {
     DispatchQueue.global().eraseToAnyScheduler()
   }()
 
+  private let dropboxManager = CloudFilesManager.dropbox(
+    appKey: "ppx0de5f16p9aq2",
+    path: "/backup/backup.xxm"
+  )
+
+  private let sftpManager = CloudFilesManager.sftp(
+    host: "",
+    username: "",
+    password: "",
+    fileName: ""
+  )
+
   private var cancellables = Set<AnyCancellable>()
   private let routeSubject = PassthroughSubject<LaunchRoute, Never>()
   private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
@@ -139,7 +154,8 @@ final class LaunchViewModel {
           hudSubject.send(.none)
           routeSubject.send(.chats)
         } else {
-          dropboxService.unlink()
+          try? sftpManager.unlink()
+          try? dropboxManager.unlink()
           hudSubject.send(.none)
           routeSubject.send(.onboarding)
         }
@@ -161,8 +177,8 @@ final class LaunchViewModel {
   }
 
   private func cleanUp() {
-    //        try? cMixManager.remove()
-    //        try? keychainHandler.clear()
+    // try? cMixManager.remove()
+    // try? keychainHandler.clear()
   }
 
   private func presentOnboardingFlow() {
@@ -699,13 +715,13 @@ extension LaunchViewModel {
           })
         )
       } else {
-        //                print(DependencyInjection.Container.shared.dependencies)
+        //print(DependencyInjection.Container.shared.dependencies)
       }
     }
   }
 
   private func setupLogWriter() {
-    _ = try! SetLogLevel.live(.debug)
+    _ = try! SetLogLevel.live(.fatal)
     RegisterLogWriter.live(.init(handle: { XXLogger.live().debug($0) }))
   }
 
diff --git a/Sources/Models/Backup.swift b/Sources/Models/Backup.swift
deleted file mode 100644
index 104462f05c616bc5556d12b1185237e416ea24ee..0000000000000000000000000000000000000000
--- a/Sources/Models/Backup.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-import Foundation
-
-public struct BackupModel: Equatable, Codable {
-    public var id: String
-    public var date: Date
-    public var size: Float
-
-    public init(
-        id: String,
-        date: Date,
-        size: Float
-    ) {
-        self.id = id
-        self.date = date
-        self.size = size
-    }
-}
diff --git a/Sources/Models/BackupSettings.swift b/Sources/Models/BackupSettings.swift
deleted file mode 100644
index 41b5d13d19dccc4aa84fea4a980d6efe0ee26eb7..0000000000000000000000000000000000000000
--- a/Sources/Models/BackupSettings.swift
+++ /dev/null
@@ -1,60 +0,0 @@
-import Foundation
-
-public struct BackupSettings: Equatable, Codable {
-    public var wifiOnlyBackup: Bool
-    public var automaticBackups: Bool
-    public var enabledService: CloudService?
-    public var connectedServices: Set<CloudService>
-    public var backups: [CloudService: BackupModel]
-
-    public init(
-        wifiOnlyBackup: Bool = false,
-        automaticBackups: Bool = false,
-        enabledService: CloudService? = nil,
-        connectedServices: Set<CloudService> = [],
-        backups: [CloudService: BackupModel] = [:]
-    ) {
-        self.wifiOnlyBackup = wifiOnlyBackup
-        self.automaticBackups = automaticBackups
-        self.enabledService = enabledService
-        self.connectedServices = connectedServices
-        self.backups = backups
-    }
-
-    public func toData() -> Data {
-        (try? PropertyListEncoder().encode(self)) ?? Data()
-    }
-
-    public init(fromData data: Data?) {
-      if let data = data, let settings = try? PropertyListDecoder().decode(BackupSettings.self, from: data) {
-        self.init(
-          wifiOnlyBackup: settings.wifiOnlyBackup,
-          automaticBackups: settings.automaticBackups,
-          enabledService: settings.enabledService,
-          connectedServices: settings.connectedServices,
-          backups: settings.backups
-        )
-      } else {
-        self.init(
-          wifiOnlyBackup: false,
-          automaticBackups: true,
-          enabledService: nil,
-          connectedServices: [],
-          backups: [:]
-        )
-      }
-    }
-}
-
-public struct RestoreSettings {
-    public var backup: BackupModel?
-    public var cloudService: CloudService
-
-    public init(
-        backup: BackupModel? = nil,
-        cloudService: CloudService
-    ) {
-        self.backup = backup
-        self.cloudService = cloudService
-    }
-}
diff --git a/Sources/Models/CloudService.swift b/Sources/Models/CloudService.swift
deleted file mode 100644
index d217dca853e6ecf22f119520d9805567f7ead3a5..0000000000000000000000000000000000000000
--- a/Sources/Models/CloudService.swift
+++ /dev/null
@@ -1,6 +0,0 @@
-public enum CloudService: Equatable, Codable {
-    case drive
-    case icloud
-    case dropbox
-    case sftp
-}
diff --git a/Sources/RestoreFeature/Controllers/RestoreController.swift b/Sources/RestoreFeature/Controllers/RestoreController.swift
index 83af547638a6b8ec1808ec234f76adb801fd44ba..27031b7556a1aa0c7550358bc704b0fa181e257c 100644
--- a/Sources/RestoreFeature/Controllers/RestoreController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreController.swift
@@ -1,5 +1,4 @@
 import UIKit
-import Models
 import Shared
 import Combine
 import DrawerFeature
@@ -14,8 +13,8 @@ public final class RestoreController: UIViewController {
     private var cancellables = Set<AnyCancellable>()
     private var drawerCancellables = Set<AnyCancellable>()
 
-    public init(_ settings: RestoreSettings) {
-        viewModel = .init(settings: settings)
+    public init(_ details: RestorationDetails) {
+        viewModel = .init(details: details)
         super.init(nibName: nil, bundle: nil)
     }
 
@@ -29,7 +28,7 @@ public final class RestoreController: UIViewController {
     public override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar.customize()
+        navigationController?.navigationBar.customize(translucent: true)
     }
 
     public override func viewDidLoad() {
@@ -43,56 +42,55 @@ public final class RestoreController: UIViewController {
         title.text = Localized.AccountRestore.header
         title.textColor = Asset.neutralActive.color
         title.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
         navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
         navigationItem.leftItemsSupplementBackButton = true
     }
 
     private func setupBindings() {
-        viewModel.step
-            .receive(on: DispatchQueue.main)
-            .removeDuplicates()
-            .sink { [unowned self] in
-                screenView.updateFor(step: $0)
-
-                if $0 == .wrongPass {
-                  coordinator.toPassphrase(
-                    from: self,
-                    cancelClosure: { self.dismiss(animated: true) },
-                    passphraseClosure: { pwd in
-                      self.viewModel.retryWith(passphrase: pwd)
-                    }
-                  )
-
-                    return
-                }
-
-                if $0 == .done {
-                    coordinator.toSuccess(from: self)
-                }
-            }.store(in: &cancellables)
-
-        screenView.backButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in didTapBack() }
-            .store(in: &cancellables)
-
-        screenView.cancelButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in didTapBack() }
-            .store(in: &cancellables)
-
-        screenView.restoreButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-              coordinator.toPassphrase(
-                from: self,
-                cancelClosure: { self.dismiss(animated: true) },
-                passphraseClosure: { pwd in
-                  self.viewModel.didTapRestore(passphrase: pwd)
-                }
-              )
-            }.store(in: &cancellables)
+      viewModel.stepPublisher
+        .receive(on: DispatchQueue.main)
+        .removeDuplicates()
+        .sink { [unowned self] in
+          screenView.updateFor(step: $0)
+
+          if $0 == .wrongPass {
+            coordinator.toPassphrase(
+              from: self,
+              cancelClosure: { self.dismiss(animated: true) },
+              passphraseClosure: { pwd in
+                self.viewModel.retryWith(passphrase: pwd)
+              }
+            )
+
+            return
+          }
+
+          if $0 == .done {
+            coordinator.toSuccess(from: self)
+          }
+        }.store(in: &cancellables)
+
+      screenView.backButton
+        .publisher(for: .touchUpInside)
+        .sink { [unowned self] in didTapBack() }
+        .store(in: &cancellables)
+
+      screenView.cancelButton
+        .publisher(for: .touchUpInside)
+        .sink { [unowned self] in didTapBack() }
+        .store(in: &cancellables)
+
+      screenView.restoreButton
+        .publisher(for: .touchUpInside)
+        .sink { [unowned self] in
+          coordinator.toPassphrase(
+            from: self,
+            cancelClosure: { self.dismiss(animated: true) },
+            passphraseClosure: { pwd in
+              self.viewModel.didTapRestore(passphrase: pwd)
+            }
+          )
+        }.store(in: &cancellables)
     }
 
     @objc private func didTapBack() {
diff --git a/Sources/RestoreFeature/Controllers/RestoreListController.swift b/Sources/RestoreFeature/Controllers/RestoreListController.swift
index a10fdfcf8101fc59cd63b8ff7a8807aa56aea77f..15acce20539862d69c36ab73b483a97fa356dc2f 100644
--- a/Sources/RestoreFeature/Controllers/RestoreListController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreListController.swift
@@ -6,104 +6,126 @@ import DrawerFeature
 import DependencyInjection
 
 public final class RestoreListController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: RestoreCoordinating
-
-    lazy private var screenView = RestoreListView()
-
-    private let viewModel = RestoreListViewModel()
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public override func loadView() {
-        view = screenView
-        presentWarning()
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar.customize(translucent: true)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        viewModel.hudPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
-
-        viewModel.backupPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                coordinator.toRestore(with: $0, from: self)
-            }.store(in: &cancellables)
-
-        screenView.cancelButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in didTapBack() }
-            .store(in: &cancellables)
-
-        screenView.driveButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                viewModel.didTapCloud(.drive, from: self)
-            }.store(in: &cancellables)
-
-        screenView.icloudButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                viewModel.didTapCloud(.icloud, from: self)
-            }.store(in: &cancellables)
-
-        screenView.dropboxButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                viewModel.didTapCloud(.dropbox, from: self)
-            }.store(in: &cancellables)
-
-        screenView.sftpButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                viewModel.didTapCloud(.sftp, from: self)
-            }.store(in: &cancellables)
-    }
-
-    @objc private func didTapBack() {
-        navigationController?.popViewController(animated: true)
-    }
+  @Dependency var hud: HUD
+  @Dependency var coordinator: RestoreCoordinating
+
+  lazy private var screenView = RestoreListView()
+
+  private let viewModel = RestoreListViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public override func loadView() {
+    view = screenView
+    presentWarning()
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    viewModel.sftpPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] _ in
+        coordinator.toSFTP(from: self) { [weak self] host, username, password in
+          guard let self else { return }
+          self.viewModel.setupSFTP(
+            host: host,
+            username: username,
+            password: password
+          )
+        }
+      }.store(in: &cancellables)
+
+    viewModel.hudPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [hud] in hud.update(with: $0) }
+      .store(in: &cancellables)
+
+    viewModel.detailsPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        coordinator.toRestore(with: $0, from: self)
+      }.store(in: &cancellables)
+
+    screenView.cancelButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in didTapBack() }
+      .store(in: &cancellables)
+
+    screenView.driveButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.link(provider: .drive, from: self) { [weak self] in
+          guard let self else { return }
+          self.viewModel.fetch(provider: .drive)
+        }
+      }.store(in: &cancellables)
+
+    screenView.icloudButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.link(provider: .icloud, from: self) { [weak self] in
+          guard let self else { return }
+          self.viewModel.fetch(provider: .icloud)
+        }
+      }.store(in: &cancellables)
+
+    screenView.dropboxButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.link(provider: .dropbox, from: self) { [weak self] in
+          guard let self else { return }
+          self.viewModel.fetch(provider: .dropbox)
+        }
+      }.store(in: &cancellables)
+
+    screenView.sftpButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.link(provider: .sftp, from: self) {}
+      }.store(in: &cancellables)
+  }
+
+  @objc private func didTapBack() {
+    navigationController?.popViewController(animated: true)
+  }
 }
 
 extension RestoreListController {
-    private func presentWarning() {
-        let actionButton = DrawerCapsuleButton(model: .init(
-            title: Localized.AccountRestore.Warning.action,
-            style: .brandColored
-        ))
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: Localized.AccountRestore.Warning.title,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                text: Localized.AccountRestore.Warning.subtitle,
-                spacingAfter: 37
-            ),
-            actionButton
-        ])
-
-        actionButton.action
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+  private func presentWarning() {
+    let actionButton = DrawerCapsuleButton(model: .init(
+      title: Localized.AccountRestore.Warning.action,
+      style: .brandColored
+    ))
+
+    let drawer = DrawerController(with: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: Localized.AccountRestore.Warning.title,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        text: Localized.AccountRestore.Warning.subtitle,
+        spacingAfter: 37
+      ),
+      actionButton
+    ])
+
+    actionButton.action
+      .receive(on: DispatchQueue.main)
+      .sink {
+        drawer.dismiss(animated: true) { [weak self] in
+          guard let self = self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    coordinator.toDrawer(drawer, from: self)
+  }
 }
diff --git a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift b/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c60b41d9e19d3540dd3090abdd9ea550a21b3906
--- /dev/null
+++ b/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift
@@ -0,0 +1,89 @@
+import HUD
+import UIKit
+import Combine
+import DependencyInjection
+import ScrollViewController
+
+public final class RestoreSFTPController: UIViewController {
+  @Dependency private var hud: HUD
+
+  lazy private var screenView = RestoreSFTPView()
+  lazy private var scrollViewController = ScrollViewController()
+
+  private let completion: (String, String, String) -> Void
+  private let viewModel = RestoreSFTPViewModel()
+  private var cancellables = Set<AnyCancellable>()
+
+  public init(_ completion: @escaping (String, String, String) -> Void) {
+    self.completion = completion
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+  }
+
+  private func setupScrollView() {
+    scrollViewController.scrollView.backgroundColor = .white
+
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+  }
+
+  private func setupBindings() {
+    viewModel.hudPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [hud] in hud.update(with: $0) }
+      .store(in: &cancellables)
+
+    viewModel.authPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] params in
+        dismiss(animated: true) {
+          self.completion(params.0, params.1, params.2)
+        }
+      }.store(in: &cancellables)
+
+    screenView.hostField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterHost($0) }
+      .store(in: &cancellables)
+
+    screenView.usernameField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterUsername($0) }
+      .store(in: &cancellables)
+
+    screenView.passwordField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterPassword($0) }
+      .store(in: &cancellables)
+
+    viewModel.statePublisher
+      .receive(on: DispatchQueue.main)
+      .map(\.isButtonEnabled)
+      .sink { [unowned self] in screenView.loginButton.isEnabled = $0 }
+      .store(in: &cancellables)
+
+    screenView.loginButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewModel.didTapLogin() }
+      .store(in: &cancellables)
+  }
+}
diff --git a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift b/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift
index 83c698fb30595098474fcf5e324f3723eee4cf59..ee3e429c60263a60bbd3df20fb7fbdc91c8e7f1c 100644
--- a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift
+++ b/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift
@@ -1,14 +1,20 @@
 import UIKit
-import Models
 import Shared
 import Presentation
 import ScrollViewController
 
+public typealias SFTPDetailsClosure = (String, String, String) -> Void
+
 public protocol RestoreCoordinating {
   func toChats(from: UIViewController)
   func toSuccess(from: UIViewController)
   func toDrawer(_: UIViewController, from: UIViewController)
-  func toRestore(with: RestoreSettings, from: UIViewController)
+  func toRestore(with: RestorationDetails, from: UIViewController)
+
+  func toSFTP(
+    from: UIViewController,
+    detailsClosure: @escaping SFTPDetailsClosure
+  )
 
   func toPassphrase(
     from: UIViewController,
@@ -25,7 +31,8 @@ public struct RestoreCoordinator: RestoreCoordinating {
 
   var successFactory: () -> UIViewController
   var chatListFactory: () -> UIViewController
-  var restoreFactory: (RestoreSettings) -> UIViewController
+  var restoreFactory: (RestorationDetails) -> UIViewController
+  var sftpFactory: (@escaping SFTPDetailsClosure) -> UIViewController
 
   var passphraseFactory: (
     @escaping EmptyClosure,
@@ -35,12 +42,16 @@ public struct RestoreCoordinator: RestoreCoordinating {
   public init(
     successFactory: @escaping () -> UIViewController,
     chatListFactory: @escaping () -> UIViewController,
-    restoreFactory: @escaping (RestoreSettings) -> UIViewController,
+    restoreFactory: @escaping (RestorationDetails) -> UIViewController,
+    sftpFactory: @escaping (
+      @escaping SFTPDetailsClosure
+    ) -> UIViewController,
     passphraseFactory: @escaping (
       @escaping EmptyClosure,
       @escaping StringClosure
     ) -> UIViewController
   ) {
+    self.sftpFactory = sftpFactory
     self.successFactory = successFactory
     self.restoreFactory = restoreFactory
     self.chatListFactory = chatListFactory
@@ -49,11 +60,19 @@ public struct RestoreCoordinator: RestoreCoordinating {
 }
 
 public extension RestoreCoordinator {
+  func toSFTP(
+    from parent: UIViewController,
+    detailsClosure: @escaping SFTPDetailsClosure
+  ) {
+    let screen = sftpFactory(detailsClosure)
+    pushPresenter.present(screen, from: parent)
+  }
+
   func toRestore(
-    with settings: RestoreSettings,
+    with details: RestorationDetails,
     from parent: UIViewController
   ) {
-    let screen = restoreFactory(settings)
+    let screen = restoreFactory(details)
     pushPresenter.present(screen, from: parent)
   }
 
diff --git a/Sources/RestoreFeature/Utils/PlistSecrets.swift b/Sources/RestoreFeature/Utils/PlistSecrets.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a96d25f9e7a272043daa2ade32e9dea5626bfb08
--- /dev/null
+++ b/Sources/RestoreFeature/Utils/PlistSecrets.swift
@@ -0,0 +1,48 @@
+import Foundation
+
+struct PlistSecrets {
+  struct GooglePlist: Decodable {
+    let apiKey: String
+    let clientId: String
+
+    enum CodingKeys: String, CodingKey {
+      case apiKey = "API_KEY"
+      case clientId = "CLIENT_ID"
+    }
+  }
+
+  struct InfoPlist: Decodable {
+    let dropboxAppKey: String
+
+    enum CodingKeys: String, CodingKey {
+      case dropboxAppKey = "DROPBOX_APP_KEY"
+    }
+  }
+
+  static var googleAPIKey: String {
+    guard let url = Bundle.main.url(forResource: "GoogleService-Info", withExtension: "plist"),
+          let data = try? Data(contentsOf: url),
+          let plist = try? PropertyListDecoder().decode(GooglePlist.self, from: data) else {
+      fatalError("Can't decode GoogleService-Info.plist")
+    }
+    return plist.apiKey
+  }
+
+  static var googleClientId: String {
+    guard let url = Bundle.main.url(forResource: "GoogleService-Info", withExtension: "plist"),
+          let data = try? Data(contentsOf: url),
+          let plist = try? PropertyListDecoder().decode(GooglePlist.self, from: data) else {
+      fatalError("Can't decode GoogleService-Info.plist")
+    }
+    return plist.clientId
+  }
+
+  static var dropboxAppKey: String {
+    guard let url = Bundle.main.url(forResource: "Info", withExtension: "plist"),
+          let data = try? Data(contentsOf: url),
+          let plist = try? PropertyListDecoder().decode(InfoPlist.self, from: data) else {
+      fatalError("Can't decode info.plist")
+    }
+    return plist.dropboxAppKey
+  }
+}
diff --git a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
index a8bc39c3a3f3fa106d9500044b8091813e1baeff..f9b252fa459fb1f38ff8b7e5c3167588442f0dfe 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
@@ -1,149 +1,119 @@
 import HUD
 import UIKit
-import Models
-import Shared
 import Combine
-import BackupFeature
-import DependencyInjection
 
-final class RestoreListViewModel {
-    @Dependency private var sftpService: SFTPService
-    @Dependency private var icloudService: iCloudInterface
-    @Dependency private var dropboxService: DropboxInterface
-    @Dependency private var googleDriveService: GoogleDriveInterface
+import CloudFiles
+import CloudFilesSFTP
+import CloudFilesDrive
+import CloudFilesICloud
+import CloudFilesDropbox
 
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
+import DependencyInjection
 
-    var backupPublisher: AnyPublisher<RestoreSettings, Never> {
-        backupSubject.eraseToAnyPublisher()
-    }
+enum RestorationProvider: String, Equatable, Hashable {
+  case sftp
+  case drive
+  case icloud
+  case dropbox
+}
 
-    private var dropboxAuthCancellable: AnyCancellable?
-    private let hudSubject = PassthroughSubject<HUDStatus, Never>()
-    private let backupSubject = PassthroughSubject<RestoreSettings, Never>()
+public struct RestorationDetails {
+  var provider: RestorationProvider
+  var metadata: Fetch.Metadata?
+}
 
-    func didTapCloud(_ cloudService: CloudService, from parent: UIViewController) {
-        switch cloudService {
-        case .drive:
-            didRequestDriveAuthorization(from: parent)
-        case .icloud:
-            didRequestICloudAuthorization()
-        case .dropbox:
-            didRequestDropboxAuthorization(from: parent)
-        case .sftp:
-            didRequestSFTPAuthorization(from: parent)
-        }
+final class RestoreListViewModel {
+  var sftpPublisher: AnyPublisher<Void, Never> {
+    sftpSubject.eraseToAnyPublisher()
+  }
+
+  var hudPublisher: AnyPublisher<HUDStatus, Never> {
+    hudSubject.eraseToAnyPublisher()
+  }
+
+  var detailsPublisher: AnyPublisher<RestorationDetails, Never> {
+    detailsSubject.eraseToAnyPublisher()
+  }
+
+  private var managers: [RestorationProvider: CloudFilesManager] = [
+    .icloud: .iCloud(
+      fileName: "backup.xxm"
+    ),
+    .dropbox: .dropbox(
+      appKey: PlistSecrets.dropboxAppKey,
+      path: "/backup/backup.xxm"
+    ),
+    .drive: .drive(
+      apiKey: PlistSecrets.googleAPIKey,
+      clientId: PlistSecrets.googleClientId,
+      fileName: "backup.xxm"
+    )
+  ]
+
+  private let sftpSubject = PassthroughSubject<Void, Never>()
+  private let hudSubject = PassthroughSubject<HUDStatus, Never>()
+  private let detailsSubject = PassthroughSubject<RestorationDetails, Never>()
+
+  func setupSFTP(host: String, username: String, password: String) {
+    managers[.sftp] = .sftp(
+      host: host,
+      username: username,
+      password: password,
+      fileName: "backup.xxm"
+    )
+    fetch(provider: .sftp)
+  }
+
+  func link(
+    provider: RestorationProvider,
+    from controller: UIViewController,
+    onSuccess: @escaping () -> Void
+  ) {
+    if provider == .sftp {
+      sftpSubject.send(())
+      return
     }
-
-    private func didRequestSFTPAuthorization(from controller: UIViewController) {
-        let params = SFTPAuthorizationParams(controller, { [weak self] in
-            guard let self = self else { return }
-            controller.navigationController?.popViewController(animated: true)
-
-            self.hudSubject.send(.on)
-
-            self.sftpService.fetchMetadata{ result in
-                switch result {
-                case .success(let settings):
-                    self.hudSubject.send(.none)
-
-                    if let settings = settings {
-                        self.backupSubject.send(settings)
-                    } else {
-                        self.backupSubject.send(.init(cloudService: .sftp))
-                    }
-                case .failure(let error):
-                    self.hudSubject.send(.error(.init(with: error)))
-                }
-            }
-        })
-
-        sftpService.authorizeFlow(params)
+    guard let manager = managers[provider] else {
+      return
     }
-
-    private func didRequestDriveAuthorization(from controller: UIViewController) {
-        googleDriveService.authorize(presenting: controller) { authResult in
-            switch authResult {
-            case .success:
-                self.hudSubject.send(.on)
-                self.googleDriveService.downloadMetadata { downloadResult in
-                    switch downloadResult {
-                    case .success(let metadata):
-                        var backup: BackupModel?
-
-                        if let metadata = metadata {
-                            backup = .init(id: metadata.identifier, date: metadata.modifiedDate, size: metadata.size)
-                        }
-
-                        self.hudSubject.send(.none)
-                        self.backupSubject.send(RestoreSettings(backup: backup, cloudService: .drive))
-
-                    case .failure(let error):
-                        self.hudSubject.send(.error(.init(with: error)))
-                    }
-                }
-            case .failure(let error):
-                self.hudSubject.send(.error(.init(with: error)))
-            }
+    do {
+      try manager.link(controller) { [weak self] in
+        guard let self else {return }
+
+        switch $0 {
+        case .success:
+          onSuccess()
+        case .failure(let error):
+          self.hudSubject.send(.error(.init(with: error)))
         }
+      }
+    } catch {
+      hudSubject.send(.error(.init(with: error)))
     }
+  }
 
-    private func didRequestICloudAuthorization() {
-        if icloudService.isAuthorized() {
-            self.hudSubject.send(.on)
-
-            icloudService.downloadMetadata { result in
-                switch result {
-                case .success(let metadata):
-                    var backup: BackupModel?
-
-                    if let metadata = metadata {
-                        backup = .init(id: metadata.path, date: metadata.modifiedDate, size: metadata.size)
-                    }
-
-                    self.hudSubject.send(.none)
-                    self.backupSubject.send(RestoreSettings(backup: backup, cloudService: .icloud))
-                case .failure(let error):
-                    self.hudSubject.send(.error(.init(with: error)))
-                }
-            }
-        } else {
-            /// This could be an alert controller asking if user wants to enable/deeplink
-            ///
-            icloudService.openSettings()
-        }
+  func fetch(provider: RestorationProvider) {
+    guard let manager = managers[provider] else {
+      return
     }
-
-    private func didRequestDropboxAuthorization(from controller: UIViewController) {
-        dropboxAuthCancellable = dropboxService.authorize(presenting: controller)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] authResult in
-                switch authResult {
-                case .success(let bool):
-                    guard bool == true else { return }
-
-                    self.hudSubject.send(.on)
-                    dropboxService.downloadMetadata { metadataResult in
-                        switch metadataResult {
-                        case .success(let metadata):
-                            var backup: BackupModel?
-
-                            if let metadata = metadata {
-                                backup = .init(id: metadata.path, date: metadata.modifiedDate, size: metadata.size)
-                            }
-
-                            self.hudSubject.send(.none)
-                            self.backupSubject.send(RestoreSettings(backup: backup, cloudService: .dropbox))
-
-                        case .failure(let error):
-                            self.hudSubject.send(.error(.init(with: error)))
-                        }
-                    }
-                case .failure(let error):
-                    self.hudSubject.send(.error(.init(with: error)))
-                }
-            }
+    do {
+      try manager.fetch { [weak self] in
+        guard let self else { return }
+
+        switch $0 {
+        case .success(let metadata):
+          DependencyInjection.Container.shared.register(manager)
+
+          self.detailsSubject.send(.init(
+            provider: provider,
+            metadata: metadata
+          ))
+        case .failure(let error):
+          self.hudSubject.send(.error(.init(with: error)))
+        }
+      }
+    } catch {
+      hudSubject.send(.error(.init(with: error)))
     }
+  }
 }
diff --git a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a24baf97da13697938f17a59a26aa4dbce2f1a99
--- /dev/null
+++ b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
@@ -0,0 +1,86 @@
+import UIKit
+
+import HUD
+import Combine
+import Foundation
+import CloudFiles
+import CloudFilesSFTP
+
+struct SFTPViewState {
+  var host: String = ""
+  var username: String = ""
+  var password: String = ""
+  var isButtonEnabled: Bool = false
+}
+
+final class RestoreSFTPViewModel {
+  var hudPublisher: AnyPublisher<HUDStatus, Never> {
+    hudSubject.eraseToAnyPublisher()
+  }
+
+  var statePublisher: AnyPublisher<SFTPViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  var authPublisher: AnyPublisher<(String, String, String), Never> {
+    authSubject.eraseToAnyPublisher()
+  }
+
+  private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
+  private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init())
+  private let authSubject = PassthroughSubject<(String, String, String), Never>()
+
+  func didEnterHost(_ string: String) {
+    stateSubject.value.host = string
+    validate()
+  }
+
+  func didEnterUsername(_ string: String) {
+    stateSubject.value.username = string
+    validate()
+  }
+
+  func didEnterPassword(_ string: String) {
+    stateSubject.value.password = string
+    validate()
+  }
+
+  func didTapLogin() {
+    hudSubject.send(.on)
+
+    let host = stateSubject.value.host
+    let username = stateSubject.value.username
+    let password = stateSubject.value.password
+
+    let anyController = UIViewController()
+
+    DispatchQueue.global().async { [weak self] in
+      guard let self = self else { return }
+      do {
+        try CloudFilesManager.sftp(
+          host: host,
+          username: username,
+          password: password,
+          fileName: "backup.xxm"
+        ).link(anyController) {
+          switch $0 {
+          case .success:
+            self.hudSubject.send(.none)
+            self.authSubject.send((host, username, password))
+          case .failure(let error):
+            self.hudSubject.send(.error(.init(with: error)))
+          }
+        }
+      } catch {
+        self.hudSubject.send(.error(.init(with: error)))
+      }
+    }
+  }
+
+  private func validate() {
+    stateSubject.value.isButtonEnabled =
+    !stateSubject.value.host.isEmpty &&
+    !stateSubject.value.username.isEmpty &&
+    !stateSubject.value.password.isEmpty
+  }
+}
diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
index d28bb3e6574f0fda353fe24fccc23cf3d1e65d91..bda1797a4f2d800de73290ce4238bb53d675da31 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
@@ -3,27 +3,25 @@ import Models
 import Shared
 import Combine
 import Defaults
-import Foundation
-import BackupFeature
+import CloudFiles
 import DependencyInjection
 
+import XXClient
 import XXModels
 import XXDatabase
-
-import XXClient
 import XXMessengerClient
 
-enum RestorationStep {
-  case idle(CloudService, BackupModel?)
-  case downloading(Float, Float)
-  case failDownload(Error)
+enum Step {
+  case done
   case wrongPass
   case parsingData
-  case done
+  case failDownload(Error)
+  case downloading(Float, Float)
+  case idle(RestorationProvider, CloudFiles.Fetch.Metadata?)
 }
 
-extension RestorationStep: Equatable {
-  static func ==(lhs: RestorationStep, rhs: RestorationStep) -> Bool {
+extension Step: Equatable {
+  static func ==(lhs: Step, rhs: Step) -> Bool {
     switch (lhs, rhs) {
     case (.done, .done), (.wrongPass, .wrongPass):
       return true
@@ -41,30 +39,27 @@ extension RestorationStep: Equatable {
 final class RestoreViewModel {
   @Dependency var database: Database
   @Dependency var messenger: Messenger
-  @Dependency var sftpService: SFTPService
-  @Dependency var iCloudService: iCloudInterface
-  @Dependency var dropboxService: DropboxInterface
-  @Dependency var googleService: GoogleDriveInterface
+  @Dependency var manager: CloudFilesManager
 
-  @KeyObject(.username, defaultValue: nil) var username: String?
   @KeyObject(.phone, defaultValue: nil) var phone: String?
   @KeyObject(.email, defaultValue: nil) var email: String?
+  @KeyObject(.username, defaultValue: nil) var username: String?
 
-  var step: AnyPublisher<RestorationStep, Never> {
-    stepRelay.eraseToAnyPublisher()
+  var stepPublisher: AnyPublisher<Step, Never> {
+    stepSubject.eraseToAnyPublisher()
   }
 
-  // TO REFACTOR:
-  //
   private var pendingData: Data?
-
   private var passphrase: String!
-  private let settings: RestoreSettings
-  private let stepRelay: CurrentValueSubject<RestorationStep, Never>
-
-  init(settings: RestoreSettings) {
-    self.settings = settings
-    self.stepRelay = .init(.idle(settings.cloudService, settings.backup))
+  private let details: RestorationDetails
+  private let stepSubject: CurrentValueSubject<Step, Never>
+
+  init(details: RestorationDetails) {
+    self.details = details
+    self.stepSubject = .init(.idle(
+      details.provider,
+      details.metadata
+    ))
   }
 
   func retryWith(passphrase: String) {
@@ -75,83 +70,33 @@ final class RestoreViewModel {
   func didTapRestore(passphrase: String) {
     self.passphrase = passphrase
 
-    guard let backup = settings.backup else { fatalError() }
-
-    stepRelay.send(.downloading(0.0, backup.size))
-
-    switch settings.cloudService {
-    case .drive:
-      downloadBackupForDrive(backup)
-    case .dropbox:
-      downloadBackupForDropbox(backup)
-    case .icloud:
-      downloadBackupForiCloud(backup)
-    case .sftp:
-      downloadBackupForSFTP(backup)
-    }
-  }
-
-  private func downloadBackupForSFTP(_ backup: BackupModel) {
-    sftpService.downloadBackup(path: backup.id) { [weak self] in
-      guard let self = self else { return }
-      self.stepRelay.send(.downloading(backup.size, backup.size))
-
-      switch $0 {
-      case .success(let data):
-        self.continueRestoring(data: data)
-      case .failure(let error):
-        self.stepRelay.send(.failDownload(error))
-      }
-    }
-  }
-
-  private func downloadBackupForDropbox(_ backup: BackupModel) {
-    dropboxService.downloadBackup(backup.id) { [weak self] in
-      guard let self = self else { return }
-      self.stepRelay.send(.downloading(backup.size, backup.size))
-
-      switch $0 {
-      case .success(let data):
-        self.continueRestoring(data: data)
-      case .failure(let error):
-        self.stepRelay.send(.failDownload(error))
-      }
+    guard let metadata = details.metadata else {
+      fatalError()
     }
-  }
 
-  private func downloadBackupForiCloud(_ backup: BackupModel) {
-    iCloudService.downloadBackup(backup.id) { [weak self] in
-      guard let self = self else { return }
-      self.stepRelay.send(.downloading(backup.size, backup.size))
+    stepSubject.send(.downloading(0.0, metadata.size))
 
-      switch $0 {
-      case .success(let data):
-        self.continueRestoring(data: data)
-      case .failure(let error):
-        self.stepRelay.send(.failDownload(error))
-      }
-    }
-  }
+    do {
+      try manager.download { [weak self] in
+        guard let self else { return }
 
-  private func downloadBackupForDrive(_ backup: BackupModel) {
-    googleService.downloadBackup(backup.id) { [weak self] in
-      if let stepRelay = self?.stepRelay {
-        stepRelay.send(.downloading($0, backup.size))
-      }
-    } _: { [weak self] in
-      guard let self = self else { return }
-
-      switch $0 {
-      case .success(let data):
-        self.continueRestoring(data: data)
-      case .failure(let error):
-        self.stepRelay.send(.failDownload(error))
+        switch $0 {
+        case .success(let data):
+          guard let data else {
+            fatalError("There was metadata, but not data.")
+          }
+          self.continueRestoring(data: data)
+        case .failure(let error):
+          self.stepSubject.send(.failDownload(error))
+        }
       }
+    } catch {
+      stepSubject.send(.failDownload(error))
     }
   }
 
   private func continueRestoring(data: Data) {
-    stepRelay.send(.parsingData)
+    stepSubject.send(.parsingData)
 
     DispatchQueue.global().async { [weak self] in
       guard let self = self else { return }
@@ -208,11 +153,11 @@ final class RestoreViewModel {
           print(">>> Error: \($0.localizedDescription)")
         }
 
-        self.stepRelay.send(.done)
+        self.stepSubject.send(.done)
       } catch {
         print(">>> Error on restoration: \(error.localizedDescription)")
         self.pendingData = data
-        self.stepRelay.send(.wrongPass)
+        self.stepSubject.send(.wrongPass)
       }
     }
   }
diff --git a/Sources/RestoreFeature/Views/RestoreSFTPView.swift b/Sources/RestoreFeature/Views/RestoreSFTPView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..5c12026bf8eda8865a226df8886c11a05108740a
--- /dev/null
+++ b/Sources/RestoreFeature/Views/RestoreSFTPView.swift
@@ -0,0 +1,83 @@
+import UIKit
+import Shared
+import InputField
+
+final class RestoreSFTPView: UIView {
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let hostField = OutlinedInputField()
+  let usernameField = OutlinedInputField()
+  let passwordField = OutlinedInputField()
+  let loginButton = CapsuleButton()
+  let stackView = UIStackView()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    titleLabel.textColor = Asset.neutralDark.color
+    titleLabel.text = Localized.AccountRestore.Sftp.title
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+
+    let attString = NSMutableAttributedString(
+      string: Localized.AccountRestore.Sftp.subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ])
+
+    attString.setAttributes(
+      attributes: [
+        .foregroundColor: Asset.neutralDark.color,
+        .font: Fonts.Mulish.bold.font(size: 12.0) as Any,
+        .paragraphStyle: paragraph
+      ], betweenCharacters: "*")
+
+    subtitleLabel.numberOfLines = 0
+    subtitleLabel.attributedText = attString
+
+    hostField.setup(title: Localized.AccountRestore.Sftp.host)
+    usernameField.setup(title: Localized.AccountRestore.Sftp.username)
+    passwordField.setup(title: Localized.AccountRestore.Sftp.password, sensitive: true)
+
+    loginButton.set(style: .brandColored, title: Localized.AccountRestore.Sftp.login)
+
+    stackView.spacing = 30
+    stackView.axis = .vertical
+    stackView.distribution = .fillEqually
+    stackView.addArrangedSubview(hostField)
+    stackView.addArrangedSubview(usernameField)
+    stackView.addArrangedSubview(passwordField)
+    stackView.addArrangedSubview(loginButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(15)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    subtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/RestoreFeature/Views/RestoreView.swift b/Sources/RestoreFeature/Views/RestoreView.swift
index de4499113b1b6550e823551f1456c953f1a5c794..5f1648ff53f89bec8ad233d748b932a27511a5e9 100644
--- a/Sources/RestoreFeature/Views/RestoreView.swift
+++ b/Sources/RestoreFeature/Views/RestoreView.swift
@@ -1,171 +1,173 @@
 import UIKit
 import Shared
-import Models
 
 final class RestoreView: UIView {
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let detailsView = RestoreDetailsView()
-    let progressView = RestoreProgressView()
-
-    let bottomStackView = UIStackView()
-    let backButton = CapsuleButton()
-    let cancelButton = CapsuleButton()
-    let restoreButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        subtitleLabel.numberOfLines = 0
-        titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
-        subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
-        titleLabel.textColor = Asset.neutralDark.color
-        subtitleLabel.textColor = Asset.neutralDark.color
-
-        restoreButton.set(style: .brandColored, title: Localized.AccountRestore.Found.restore)
-        cancelButton.set(style: .simplestColoredBrand, title: Localized.AccountRestore.Found.cancel)
-        backButton.set(style: .seeThrough, title: Localized.AccountRestore.NotFound.back)
-
-        bottomStackView.axis = .vertical
-
-        addSubview(titleLabel)
-        addSubview(subtitleLabel)
-        addSubview(detailsView)
-        addSubview(progressView)
-        addSubview(bottomStackView)
-
-        bottomStackView.addArrangedSubview(restoreButton)
-        bottomStackView.addArrangedSubview(cancelButton)
-        bottomStackView.addArrangedSubview(backButton)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(20)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-38)
-        }
-
-        subtitleLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(20)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-38)
-        }
-
-        detailsView.snp.makeConstraints {
-            $0.top.equalTo(subtitleLabel.snp.bottom).offset(40)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-        }
-
-        progressView.snp.makeConstraints {
-            $0.top.greaterThanOrEqualTo(detailsView.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.lessThanOrEqualTo(bottomStackView.snp.top)
-        }
-
-        bottomStackView.snp.makeConstraints {
-            $0.top.greaterThanOrEqualTo(detailsView.snp.bottom).offset(10)
-            $0.left.equalToSuperview().offset(40)
-            $0.right.equalToSuperview().offset(-40)
-            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20)
-        }
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let detailsView = RestoreDetailsView()
+  let progressView = RestoreProgressView()
+
+  let bottomStackView = UIStackView()
+  let backButton = CapsuleButton()
+  let cancelButton = CapsuleButton()
+  let restoreButton = CapsuleButton()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    subtitleLabel.numberOfLines = 0
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+    subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    titleLabel.textColor = Asset.neutralDark.color
+    subtitleLabel.textColor = Asset.neutralDark.color
+
+    restoreButton.set(style: .brandColored, title: Localized.AccountRestore.Found.restore)
+    cancelButton.set(style: .simplestColoredBrand, title: Localized.AccountRestore.Found.cancel)
+    backButton.set(style: .seeThrough, title: Localized.AccountRestore.NotFound.back)
+
+    bottomStackView.axis = .vertical
+
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(detailsView)
+    addSubview(progressView)
+    addSubview(bottomStackView)
+
+    bottomStackView.addArrangedSubview(restoreButton)
+    bottomStackView.addArrangedSubview(cancelButton)
+    bottomStackView.addArrangedSubview(backButton)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(20)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
     }
 
-    required init?(coder: NSCoder) { nil }
+    subtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+    }
+
+    detailsView.snp.makeConstraints {
+      $0.top.equalTo(subtitleLabel.snp.bottom).offset(40)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+    }
 
-    func updateFor(step: RestorationStep) {
-        switch step {
-        case .idle(let cloudService, let backup):
-            guard let backup = backup else {
-                showNoBackupForCloud(named: cloudService.name())
-                return
-            }
+    progressView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(detailsView.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.lessThanOrEqualTo(bottomStackView.snp.top)
+    }
 
-            showBackup(backup, fromCloud: cloudService)
+    bottomStackView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(detailsView.snp.bottom).offset(10)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20)
+    }
+  }
 
-        case .downloading(let downloaded, let total):
-            restoreButton.isHidden = true
-            cancelButton.isHidden = true
-            progressView.isHidden = false
+  required init?(coder: NSCoder) { nil }
 
-            progressView.update(downloaded: downloaded, total: total)
-        case .wrongPass:
-            progressView.descriptiveProgressLabel.text = "Incorrect password"
+  func updateFor(step: Step) {
+    switch step {
+    case .idle(let provider, let metadata):
+      guard let metadata = metadata else {
+        missingMetadataFor(provider)
+        return
+      }
 
-        case .failDownload(let error):
-            progressView.descriptiveProgressLabel.text = error.localizedDescription
+      displayDetailsFrom(provider, size: metadata.size, lastDate: metadata.lastModified)
 
-        case .parsingData:
-            progressView.descriptiveProgressLabel.text = "Parsing backup data"
+    case .downloading(let downloaded, let total):
+      restoreButton.isHidden = true
+      cancelButton.isHidden = true
+      progressView.isHidden = false
 
-        case .done:
-            progressView.descriptiveProgressLabel.text = "Done"
-        }
-    }
+      progressView.update(downloaded: downloaded, total: total)
+    case .wrongPass:
+      progressView.descriptiveProgressLabel.text = "Incorrect password"
 
-    private func showBackup(_ backup: BackupModel, fromCloud cloud: CloudService) {
-        titleLabel.text = Localized.AccountRestore.Found.title
-        subtitleLabel.text = Localized.AccountRestore.Found.subtitle
-
-        detailsView.titleLabel.text = cloud.name()
-        detailsView.imageView.image = cloud.asset()
-
-        detailsView.dateView.setup(
-            title: Localized.AccountRestore.Found.date,
-            value: backup.date.backupStyle(),
-            hasArrow: false
-        )
-
-        detailsView.sizeView.setup(
-            title: Localized.AccountRestore.Found.size,
-            value: String(format: "%.1f kb", backup.size/1000),
-            hasArrow: false
-        )
-
-        detailsView.isHidden = false
-        backButton.isHidden = true
-        restoreButton.isHidden = false
-        cancelButton.isHidden = false
-        progressView.isHidden = true
-    }
+    case .failDownload(let error):
+      progressView.descriptiveProgressLabel.text = error.localizedDescription
 
-    private func showNoBackupForCloud(named cloud: String) {
-        titleLabel.text = Localized.AccountRestore.NotFound.title
-        subtitleLabel.text = Localized.AccountRestore.NotFound.subtitle(cloud)
+    case .parsingData:
+      progressView.descriptiveProgressLabel.text = "Parsing backup data"
 
-        restoreButton.isHidden = true
-        cancelButton.isHidden = true
-        detailsView.isHidden = true
-        backButton.isHidden = false
-        progressView.isHidden = true
+    case .done:
+      progressView.descriptiveProgressLabel.text = "Done"
     }
+  }
+
+  private func displayDetailsFrom(
+    _ provider: RestorationProvider,
+    size: Float,
+    lastDate: Date
+  ) {
+    titleLabel.text = Localized.AccountRestore.Found.title
+    subtitleLabel.text = Localized.AccountRestore.Found.subtitle
+    detailsView.titleLabel.text = provider.name()
+    detailsView.imageView.image = provider.asset()
+
+    detailsView.dateView.setup(
+      title: Localized.AccountRestore.Found.date,
+      value: lastDate.backupStyle(),
+      hasArrow: false
+    )
+
+    detailsView.sizeView.setup(
+      title: Localized.AccountRestore.Found.size,
+      value: String(format: "%.1f kb", size/1000),
+      hasArrow: false
+    )
+
+    detailsView.isHidden = false
+    backButton.isHidden = true
+    restoreButton.isHidden = false
+    cancelButton.isHidden = false
+    progressView.isHidden = true
+  }
+
+  private func missingMetadataFor(_ provider: RestorationProvider) {
+    titleLabel.text = Localized.AccountRestore.NotFound.title
+    subtitleLabel.text = Localized.AccountRestore.NotFound.subtitle(provider.name())
+
+    restoreButton.isHidden = true
+    cancelButton.isHidden = true
+    detailsView.isHidden = true
+    backButton.isHidden = false
+    progressView.isHidden = true
+  }
 }
 
-private extension CloudService {
-    func name() -> String {
-        switch self {
-        case .drive:
-            return Localized.Backup.googleDrive
-        case .icloud:
-            return Localized.Backup.iCloud
-        case .dropbox:
-            return Localized.Backup.dropbox
-        case .sftp:
-            return Localized.Backup.sftp
-        }
+private extension RestorationProvider {
+  func name() -> String {
+    switch self {
+    case .drive:
+      return Localized.Backup.googleDrive
+    case .icloud:
+      return Localized.Backup.iCloud
+    case .dropbox:
+      return Localized.Backup.dropbox
+    case .sftp:
+      return Localized.Backup.sftp
     }
-
-    func asset() -> UIImage {
-        switch self {
-        case .drive:
-            return Asset.restoreDrive.image
-        case .icloud:
-            return Asset.restoreIcloud.image
-        case .dropbox:
-            return Asset.restoreDropbox.image
-        case .sftp:
-            return Asset.restoreSFTP.image
-        }
+  }
+
+  func asset() -> UIImage {
+    switch self {
+    case .drive:
+      return Asset.restoreDrive.image
+    case .icloud:
+      return Asset.restoreIcloud.image
+    case .dropbox:
+      return Asset.restoreDropbox.image
+    case .sftp:
+      return Asset.restoreSFTP.image
     }
+  }
 }