diff --git a/Package.swift b/Package.swift
index 9317c5bef0efb8db66aabe1cb1485b61fd2426f6..bcec957c2be3321cb1b9f1bcc1dd176814764b54 100644
--- a/Package.swift
+++ b/Package.swift
@@ -68,7 +68,7 @@ let package = Package(
         .package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.5.0"),
         .package(url: "https://github.com/kishikawakatsumi/KeychainAccess", from: "4.2.1"),
         .package(url: "https://github.com/google/google-api-objectivec-client-for-rest", from: "1.6.0"),
-        .package(url: "https://git.xx.network/elixxir/client-ios-db.git", .upToNextMajor(from: "1.0.5")),
+        .package(url: "https://git.xx.network/elixxir/client-ios-db.git", .upToNextMajor(from: "1.0.8")),
         .package(url: "https://github.com/firebase/firebase-ios-sdk.git", .upToNextMajor(from: "8.10.0")),
         .package(url: "https://github.com/darrarski/Shout.git", revision: "df5a662293f0ac15eeb4f2fd3ffd0c07b73d0de0"),
         .package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",.upToNextMajor(from: "0.32.0")),
@@ -220,9 +220,11 @@ let package = Package(
                 name: "SFTPFeature",
                 dependencies: [
                     "HUD",
+                    "Models",
                     "Shared",
                     "Keychain",
                     "InputField",
+                    "Presentation",
                     "DependencyInjection",
                     .product(
                         name: "Shout",
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index 87b8e7dd9d903fbf11f686ac46ecb730fac4eef7..14a7b7bdb482974cff2b5b2be573d3f5fb1a33fc 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -104,7 +104,7 @@ struct DependencyRegistrator {
 
         // MARK: Isolated
 
-        container.register(HUD() as HUDType)
+        container.register(HUD())
         container.register(ThemeController() as ThemeControlling)
         container.register(ToastController())
         container.register(StatusBarController() as StatusBarStyleControlling)
@@ -137,6 +137,8 @@ struct DependencyRegistrator {
 
         container.register(
             SearchCoordinator(
+                contactsFactory: ContactListController.init,
+                requestsFactory: RequestsContainerController.init,
                 contactFactory: ContactController.init(_:),
                 countriesFactory: CountryListController.init(_:)
             ) as SearchCoordinating)
diff --git a/Sources/BackupFeature/Controllers/BackupController.swift b/Sources/BackupFeature/Controllers/BackupController.swift
index a01ff0caa91be5ffdfa55e6fe9fe8ea7ebab41bf..a9942d8c03466fe8b620d156c9eb1cc02f2ef70a 100644
--- a/Sources/BackupFeature/Controllers/BackupController.swift
+++ b/Sources/BackupFeature/Controllers/BackupController.swift
@@ -6,7 +6,7 @@ import Combine
 import DependencyInjection
 
 public final class BackupController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency var hud: HUD
 
     private let viewModel = BackupViewModel.live()
     private var cancellables = Set<AnyCancellable>()
@@ -14,7 +14,7 @@ public final class BackupController: UIViewController {
     public override func viewDidLoad() {
         super.viewDidLoad()
         view.backgroundColor = Asset.neutralWhite.color
-        hud.update(with: .on(nil))
+        hud.update(with: .on)
 
         setupNavigationBar()
         setupBindings()
diff --git a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
index f136ca8c15c83578636ed3ef00643817d73b8de9..0731bc4a94f5be17d6e224d5f67573ea68ea608f 100644
--- a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
@@ -30,7 +30,7 @@ struct BackupConfigViewModel {
 extension BackupConfigViewModel {
     static func live() -> Self {
         class Context {
-            @Dependency var hud: HUDType
+            @Dependency var hud: HUD
             @Dependency var service: BackupService
             @Dependency var coordinator: BackupCoordinating
         }
@@ -40,7 +40,7 @@ extension BackupConfigViewModel {
         return .init(
             didTapBackupNow: {
                 context.service.performBackup()
-                context.hud.update(with: .on(nil))
+                context.hud.update(with: .on)
                 DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                     context.hud.update(with: .none)
                 }
@@ -57,7 +57,7 @@ extension BackupConfigViewModel {
                     context.service.toggle(service: service, enabling: false)
                 }, passphraseClosure: { passphrase in
                     context.service.passphrase = passphrase
-                    context.hud.update(with: .on("Initializing and securing your backup file will take few seconds, please keep the app open."))
+                    context.hud.update(with: .onTitle("Initializing and securing your backup file will take few seconds, please keep the app open."))
                     DispatchQueue.global().async {
                         context.service.toggle(service: service, enabling: enabling)
 
diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift
index b35d314725ea97149e84edb8b9525e1fc010d4f6..f34be438f7df3afd84d5ba7c6db04612bc891cc6 100644
--- a/Sources/ChatFeature/Controllers/SingleChatController.swift
+++ b/Sources/ChatFeature/Controllers/SingleChatController.swift
@@ -24,7 +24,7 @@ extension Message: Differentiable {
 }
 
 public final class SingleChatController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var logger: XXLogger
     @Dependency private var voxophone: Voxophone
     @Dependency private var coordinator: ChatCoordinating
diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
index cd78765910d84d7242ee96ff145c3cd612c2492d..f51a893610402c317f938f0b29445317de9bb6d4 100644
--- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
@@ -107,7 +107,7 @@ final class SingleChatViewModel {
 
     func didSend(image: UIImage) {
         guard let imageData = image.orientedUp().jpegData(compressionQuality: 1.0) else { return }
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         session.send(imageData: imageData, to: contact) { [weak self] in
             switch $0 {
diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
index 98febff09e352ff1e0ff495c032f1e7d1ec1613a..f481b96fb7e3e14b74096919090bf1df8c96c840 100644
--- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
+++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
@@ -147,7 +147,7 @@ final class ChatListViewModel {
     }
 
     func leave(_ group: Group) {
-        hudSubject.send(.on(nil))
+        hudSubject.send(.on)
 
         do {
             try session.leave(group: group)
diff --git a/Sources/ChatListFeature/Views/ChatListView.swift b/Sources/ChatListFeature/Views/ChatListView.swift
index c7303c48d04db46983e2985d40745ce236d72c43..03a407980700123d9843d9239e4822b9247dac63 100644
--- a/Sources/ChatListFeature/Views/ChatListView.swift
+++ b/Sources/ChatListFeature/Views/ChatListView.swift
@@ -14,7 +14,7 @@ final class ChatListView: UIView {
         backgroundColor = Asset.neutralWhite.color
         listContainerView.backgroundColor = Asset.neutralWhite.color
         searchListContainerView.backgroundColor = Asset.neutralWhite.color
-        searchView.update(placeholder: "Search chats")
+        searchView.update(placeholder: Localized.ChatList.Search.title)
 
         addSubview(snackBar)
         addSubview(searchView)
@@ -22,6 +22,34 @@ final class ChatListView: UIView {
         containerView.addSubview(searchListContainerView)
         containerView.addSubview(listContainerView)
 
+        setupConstraints()
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    func showConnectingBanner(_ show: Bool) {
+        if show == true {
+            snackBar.alpha = 0.0
+            snackBar.snp.updateConstraints {
+                $0.bottom
+                    .equalTo(snp.top)
+                    .offset(snackBar.bounds.height)
+            }
+        } else {
+            snackBar.alpha = 1.0
+            snackBar.snp.updateConstraints {
+                $0.bottom.equalTo(snp.top)
+            }
+        }
+
+        UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) {
+            self.setNeedsLayout()
+            self.layoutIfNeeded()
+            self.snackBar.alpha = show ? 1.0 : 0.0
+        }
+    }
+
+    private func setupConstraints() {
         snackBar.snp.makeConstraints {
             $0.left.equalToSuperview()
             $0.right.equalToSuperview()
@@ -49,28 +77,4 @@ final class ChatListView: UIView {
             $0.edges.equalToSuperview()
         }
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func showConnectingBanner(_ show: Bool) {
-        if show == true {
-            snackBar.alpha = 0.0
-            snackBar.snp.updateConstraints {
-                $0.bottom
-                    .equalTo(snp.top)
-                    .offset(snackBar.bounds.height)
-            }
-        } else {
-            snackBar.alpha = 1.0
-            snackBar.snp.updateConstraints {
-                $0.bottom.equalTo(snp.top)
-            }
-        }
-
-        UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) {
-            self.setNeedsLayout()
-            self.layoutIfNeeded()
-            self.snackBar.alpha = show ? 1.0 : 0.0
-        }
-    }
 }
diff --git a/Sources/ContactFeature/Controllers/ContactController.swift b/Sources/ContactFeature/Controllers/ContactController.swift
index cf64268edbdd1cad2986105c38d72a74b2273499..02859bab60bf861a5615795a016352a4f4509900 100644
--- a/Sources/ContactFeature/Controllers/ContactController.swift
+++ b/Sources/ContactFeature/Controllers/ContactController.swift
@@ -10,7 +10,7 @@ import DependencyInjection
 import ScrollViewController
 
 public final class ContactController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: ContactCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/ContactFeature/ViewModels/ContactViewModel.swift b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
index 67005d4b9d01cbc493ead72d8f6b89e42e64535e..5e6e06698cbfeb359a79ce231dde08f37c03ee6a 100644
--- a/Sources/ContactFeature/ViewModels/ContactViewModel.swift
+++ b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
@@ -62,7 +62,7 @@ final class ContactViewModel {
     }
 
     func didTapDelete() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         do {
             try session.deleteContact(contact)
@@ -91,7 +91,7 @@ final class ContactViewModel {
     }
 
     func didTapResend() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
@@ -107,7 +107,7 @@ final class ContactViewModel {
     }
 
     func didTapRequest(with nickname: String) {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
         contact.nickname = nickname
 
         backgroundScheduler.schedule { [weak self] in
@@ -124,7 +124,7 @@ final class ContactViewModel {
     }
 
     func didTapAccept(_ nickname: String) {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
         contact.nickname = nickname
 
         backgroundScheduler.schedule { [weak self] in
diff --git a/Sources/ContactListFeature/Controllers/ContactListTableController.swift b/Sources/ContactListFeature/Controllers/ContactListTableController.swift
index f940366ae08cfe77f9cd54e5d1008a55b7149bc6..ac7a06628045f199393f1839e2a6f700a7cb194e 100644
--- a/Sources/ContactListFeature/Controllers/ContactListTableController.swift
+++ b/Sources/ContactListFeature/Controllers/ContactListTableController.swift
@@ -30,7 +30,7 @@ final class ContactListTableController: UITableViewController {
 
     private func setupTableView() {
         tableView.separatorStyle = .none
-        tableView.register(SmallAvatarAndTitleCell.self)
+        tableView.register(AvatarCell.self)
         tableView.backgroundColor = Asset.neutralWhite.color
         tableView.sectionIndexColor = Asset.neutralDark.color
         tableView.contentInset = UIEdgeInsets(top: -20, left: 0, bottom: 0, right: 0)
@@ -45,11 +45,11 @@ final class ContactListTableController: UITableViewController {
     }
 
     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let cell: SmallAvatarAndTitleCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
+        let cell: AvatarCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
         let contact = sections[indexPath.section][indexPath.row]
         let name = (contact.nickname ?? contact.username) ?? "Fetching username..."
-        cell.titleLabel.text = name
-        cell.avatarView.setupProfile(title: name, image: contact.photo, size: .medium)
+
+        cell.setup(title: name, image: contact.photo)
         return cell
     }
 
diff --git a/Sources/ContactListFeature/Controllers/CreateGroupController.swift b/Sources/ContactListFeature/Controllers/CreateGroupController.swift
index 9e9d039dd5e5fe2dd4adfa179fc736ab51fa6c4b..a9559f5c3c38ccdf7a90543a9963748155e6fa6b 100644
--- a/Sources/ContactListFeature/Controllers/CreateGroupController.swift
+++ b/Sources/ContactListFeature/Controllers/CreateGroupController.swift
@@ -7,7 +7,7 @@ import XXModels
 import DependencyInjection
 
 public final class CreateGroupController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: ContactListCoordinating
 
     lazy private var titleLabel = UILabel()
@@ -66,7 +66,7 @@ public final class CreateGroupController: UIViewController {
 
     private func setupTableAndCollection() {
         screenView.tableView.rowHeight = 64.0
-        screenView.tableView.register(SmallAvatarAndTitleCell.self)
+        screenView.tableView.register(AvatarCell.self)
         screenView.collectionView.register(CreateGroupCollectionCell.self)
 
         collectionDataSource = UICollectionViewDiffableDataSource<SectionId, Contact>(
@@ -84,10 +84,10 @@ public final class CreateGroupController: UIViewController {
         tableDataSource = DiffEditableDataSource<SectionId, Contact>(
             tableView: screenView.tableView
         ) { [weak self] tableView, indexPath, contact in
-            let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: SmallAvatarAndTitleCell.self)
+            let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: AvatarCell.self)
             let title = (contact.nickname ?? contact.username) ?? ""
-            cell.titleLabel.text = title
-            cell.avatarView.setupProfile(title: title, image: contact.photo, size: .medium)
+
+            cell.setup(title: title, image: contact.photo)
 
             if let selectedElements = self?.selectedElements, selectedElements.contains(contact) {
                 tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
index 67e70645ed6c0d00f02862bdb02f199d3d735d9d..a8f94de8042f3cd7f7d0da14a9de703f217f573b 100644
--- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
@@ -76,7 +76,7 @@ final class CreateGroupViewModel {
     }
 
     func create(name: String, welcome: String?, members: [Contact]) {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         session.createGroup(name: name, welcome: welcome, members: members) { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/HUD/HUD.swift b/Sources/HUD/HUD.swift
index 9f45b18078d75a2d11654f99a9bd2d1b2746f4ee..207f3473c9173a3a64c8636a8373d734d207807b 100644
--- a/Sources/HUD/HUD.swift
+++ b/Sources/HUD/HUD.swift
@@ -10,15 +10,17 @@ private enum Constants {
 }
 
 public enum HUDStatus: Equatable {
-    case on(String?)
     case none
+    case on
+    case onTitle(String)
+    case onAction(String)
     case error(HUDError)
 
     var isPresented: Bool {
         switch self {
         case .none:
             return false
-        case .on, .error:
+        case .on, .error, .onTitle, .onAction:
             return true
         }
     }
@@ -50,15 +52,12 @@ public struct HUDError: Equatable {
     }
 }
 
-public protocol HUDType {
-    func update(with status: HUDStatus)
-}
-
-public final class HUD: HUDType {
+public final class HUD {
     private(set) var window: UIWindow?
     private(set) var errorView: ErrorView?
     private(set) var titleLabel: UILabel?
     private(set) var animation: DotAnimation?
+    public var actionButton: CapsuleButton?
     private var cancellables = Set<AnyCancellable>()
 
     private var status: HUDStatus = .none {
@@ -67,16 +66,23 @@ public final class HUD: HUDType {
                 self.errorView = nil
                 self.animation = nil
                 self.window = nil
+                self.actionButton = nil
                 self.titleLabel = nil
 
                 switch status {
-                case .on(let text):
+                case .on:
+                    animation = DotAnimation()
+
+                case .onTitle(let text):
                     animation = DotAnimation()
+                    titleLabel = UILabel()
+                    titleLabel!.text = text
+
+                case .onAction(let title):
+                    animation = DotAnimation()
+                    actionButton = CapsuleButton()
+                    actionButton!.set(style: .seeThroughWhite, title: title)
 
-                    if let text = text {
-                        titleLabel = UILabel()
-                        titleLabel!.text = text
-                    }
                 case .error(let error):
                     errorView = ErrorView(with: error)
                 case .none:
@@ -88,13 +94,19 @@ public final class HUD: HUDType {
 
             if oldValue.isPresented == false && status.isPresented == true {
                 switch status {
-                case .on(let text):
+                case .on:
                     animation = DotAnimation()
 
-                    if let text = text {
-                        titleLabel = UILabel()
-                        titleLabel!.text = text
-                    }
+                case .onTitle(let text):
+                    animation = DotAnimation()
+                    titleLabel = UILabel()
+                    titleLabel!.text = text
+
+                case .onAction(let title):
+                    animation = DotAnimation()
+                    actionButton = CapsuleButton()
+                    actionButton!.set(style: .seeThroughWhite, title: title)
+
                 case .error(let error):
                     errorView = ErrorView(with: error)
                 case .none:
@@ -118,7 +130,7 @@ public final class HUD: HUDType {
 
     private func showWindow() {
         window = Window()
-        window?.backgroundColor = UIColor.black.withAlphaComponent(0.5)
+        window?.backgroundColor = UIColor.black.withAlphaComponent(0.8)
         window?.rootViewController = StatusBarViewController(nil)
 
         if let animation = animation {
@@ -138,6 +150,15 @@ public final class HUD: HUDType {
             }
         }
 
+        if let actionButton = actionButton {
+            window?.addSubview(actionButton)
+            actionButton.snp.makeConstraints {
+                $0.left.equalToSuperview().offset(18)
+                $0.right.equalToSuperview().offset(-18)
+                $0.bottom.equalToSuperview().offset(-50)
+            }
+        }
+
         if let errorView = errorView {
             window?.addSubview(errorView)
             errorView.snp.makeConstraints { make in
@@ -166,6 +187,7 @@ public final class HUD: HUDType {
             self.cancellables.removeAll()
             self.errorView = nil
             self.animation = nil
+            self.actionButton = nil
             self.titleLabel = nil
             self.window = nil
         }
diff --git a/Sources/Integration/Client.swift b/Sources/Integration/Client.swift
index 2b488ef925bd1d5367b94585dc4959bf14b66ea6..450c674ebc5647268114eb3b251e6b56c12efca4 100644
--- a/Sources/Integration/Client.swift
+++ b/Sources/Integration/Client.swift
@@ -90,7 +90,7 @@ public class Client {
 
     public func addJson(_ string: String) {
         guard let backupManager = backupManager else {
-            fatalError()
+            fatalError("Trying to add json parameters to backup but no backup manager created yet")
         }
 
         print("^^^ Set params: \(string) to backup")
diff --git a/Sources/Integration/Implementations/Bindings.swift b/Sources/Integration/Implementations/Bindings.swift
index 7c6feb9f10688bc0d34726aec2479c8174438484..a05387dda2653644a26a3bc28b40e011c6eb5e03 100644
--- a/Sources/Integration/Implementations/Bindings.swift
+++ b/Sources/Integration/Implementations/Bindings.swift
@@ -154,17 +154,15 @@ extension BindingsClient: BindingsInterface {
         for env: NetworkEnvironment,
         _ completion: @escaping (Result<Data?, Error>) -> Void
     ) {
-        log(type: .crumbs)
-
         var error: NSError?
         let ndf = BindingsDownloadAndVerifySignedNdfWithUrl(env.url, env.cert, &error)
 
-        if let error = error {
-            log(string: error.localizedDescription, type: .error)
-            completion(.failure(error))
-        } else {
-            completion(.success(ndf))
+        guard error == nil else {
+            Self.updateNDF(for: env, completion)
+            return
         }
+
+        completion(.success(ndf))
     }
 
     /// Fetches a JSON with up-to-date error descriptions
diff --git a/Sources/Integration/Session/Session+Contacts.swift b/Sources/Integration/Session/Session+Contacts.swift
index 3f6a27711512043b3677b0c54fcc68265a72192e..7bbe5254ab00b17b8053323586347ef819f10ff1 100644
--- a/Sources/Integration/Session/Session+Contacts.swift
+++ b/Sources/Integration/Session/Session+Contacts.swift
@@ -94,7 +94,7 @@ extension Session {
     }
 
     public func retryRequest(_ contact: Contact) throws {
-        log(string: "Retrying to request a contact", type: .info)
+        let name = (contact.nickname ?? contact.username) ?? ""
 
         client.bindings.add(contact.marshaled!, from: myQR) { [weak self, contact] in
             var contact = contact
@@ -103,11 +103,21 @@ extension Session {
             do {
                 switch $0 {
                 case .success:
-                    log(string: "Retrying to request a contact -- Success", type: .info)
                     contact.authStatus = .requested
-                case .failure(let error):
-                    log(string: "Retrying to request a contact -- Failed: \(error.localizedDescription)", type: .error)
+
+                    self.toastController.enqueueToast(model: .init(
+                        title: Localized.Requests.Sent.Toast.resent(name),
+                        leftImage: Asset.sharedSuccess.image
+                    ))
+
+                case .failure:
                     contact.createdAt = Date()
+
+                    self.toastController.enqueueToast(model: .init(
+                        title: Localized.Requests.Sent.Toast.resentFailed(name),
+                        color: Asset.accentDanger.color,
+                        leftImage: Asset.requestFailedToaster.image
+                    ))
                 }
 
                 _ = try self.dbManager.saveContact(contact)
@@ -170,6 +180,13 @@ extension Session {
                     contact.authStatus = success ? .requested : .requestFailed
                     contact = try self.dbManager.saveContact(contact)
 
+                    let name = contact.nickname ?? contact.username
+
+                    self.toastController.enqueueToast(model: .init(
+                        title: Localized.Requests.Sent.Toast.sent(name ?? ""),
+                        leftImage: Asset.sharedSuccess.image
+                    ))
+
                 case .failure:
                     contact.createdAt = Date()
                     contact.authStatus = .requestFailed
diff --git a/Sources/Integration/Session/Session+UD.swift b/Sources/Integration/Session/Session+UD.swift
index 630d822af475c32bb21fb09bb4d9c8bc80a5a54f..1fe3e2e09b2f7f0ae327b039602320bbc884eb2b 100644
--- a/Sources/Integration/Session/Session+UD.swift
+++ b/Sources/Integration/Session/Session+UD.swift
@@ -1,8 +1,35 @@
 import Models
 import XXModels
 import Foundation
+import Combine
 
 extension Session {
+    public func search(fact: String) -> AnyPublisher<Contact, Error> {
+        Deferred {
+            Future { promise in
+                guard let ud = self.client.userDiscovery else {
+                    let error = NSError(domain: "", code: 0)
+                    promise(.failure(error))
+                    return
+                }
+
+                do {
+                    try self.client.bindings.nodeRegistrationStatus()
+                    try ud.search(fact: fact) {
+                        switch $0 {
+                        case .success(let contact):
+                            promise(.success(contact))
+                        case .failure(let error):
+                            promise(.failure(error))
+                        }
+                    }
+                } catch {
+                    promise(.failure(error))
+                }
+            }
+        }.eraseToAnyPublisher()
+    }
+
     public func search(fact: String, _ completion: @escaping (Result<Contact, Error>) -> Void) throws {
         guard let ud = client.userDiscovery else { return }
         try client.bindings.nodeRegistrationStatus()
@@ -69,6 +96,8 @@ extension Session {
             phone = confirmation.content
         }
 
-        updateFactsOnBackup()
+        if let _ = client.backupManager {
+            updateFactsOnBackup()
+        }
     }
 }
diff --git a/Sources/Integration/Session/Session.swift b/Sources/Integration/Session/Session.swift
index e14c4901e1a22fe97869768b154ec25dd70eff93..f85a6c6f0e3350e20c2bb234e6a92935e589b01f 100644
--- a/Sources/Integration/Session/Session.swift
+++ b/Sources/Integration/Session/Session.swift
@@ -130,8 +130,20 @@ public final class Session: SessionType {
             let params = try! JSONDecoder().decode(BackupParameters.self, from: Data(report.parameters.utf8))
 
             username = params.username
-            phone = params.phone
-            email = params.email
+
+            if let paramsPhone = params.phone, !paramsPhone.isEmpty {
+                phone = paramsPhone
+            }
+
+            if let paramsEmail = params.email, !paramsEmail.isEmpty {
+                email = paramsEmail
+            }
+        }
+
+        print("^^^ \(report.parameters)")
+
+        guard username!.isEmpty == false else {
+            fatalError("Trying to restore an account that has no username")
         }
 
         try continueInitialization()
diff --git a/Sources/Integration/Session/SessionType.swift b/Sources/Integration/Session/SessionType.swift
index e99c6f37fb45599aae26836c7a8f248ae20f02f1..b871332b216e5c7dbdc0adfebc4bac3f306913b7 100644
--- a/Sources/Integration/Session/SessionType.swift
+++ b/Sources/Integration/Session/SessionType.swift
@@ -66,4 +66,6 @@ public protocol SessionType {
         members: [Contact],
         _ completion: @escaping (Result<GroupInfo, Error>) -> Void
     )
+
+    func search(fact: String) -> AnyPublisher<Contact, Error>
 }
diff --git a/Sources/LaunchFeature/LaunchController.swift b/Sources/LaunchFeature/LaunchController.swift
index 33c2f8dad55d29d02a7e74b3d70437fbaea4aa6f..2eb373f55784bed3e9a11293acc7cca9aee2fc08 100644
--- a/Sources/LaunchFeature/LaunchController.swift
+++ b/Sources/LaunchFeature/LaunchController.swift
@@ -7,7 +7,7 @@ import DependencyInjection
 
 
 public final class LaunchController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: LaunchCoordinating
 
     lazy private var screenView = LaunchView()
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index 4e1b25ea61eb4475c9849f1873482b8a6eec3b25..fefbe7cf6035b19d804febd7e544e22166d11bf8 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -58,7 +58,7 @@ final class LaunchViewModel {
 
     func viewDidAppear() {
         DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
-            self?.hudSubject.send(.on(nil))
+            self?.hudSubject.send(.on)
             self?.checkVersion()
         }
     }
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift b/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift
index 75b7d0ffb081bec57a2c8cf06d0fa6915fd07092..90c210569b58b904473f5c2e2020740e2945a09d 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift
@@ -9,7 +9,7 @@ import ScrollViewController
 import Models
 
 public final class OnboardingEmailConfirmationController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: OnboardingCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift b/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift
index 68b10159a32ab4e5167d10ba98d993f453fdf24d..54fb8cf4efcf4cd29489729598ed22144b918f65 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift
@@ -8,7 +8,7 @@ import DependencyInjection
 import ScrollViewController
 
 public final class OnboardingEmailController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: OnboardingCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift b/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift
index dbef5be43b8314ffcc71a2481bb4bcc0d67a5e6d..0017798bc5fc2a4703a56cf6b4fc2a45fbcffa55 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift
@@ -9,7 +9,7 @@ import ScrollViewController
 import Models
 
 public final class OnboardingPhoneConfirmationController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: OnboardingCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift
index b935ae07434e90d94818a2ea400202924323ea32..8793508fe6ec8d94e3ee00ff5f3052031c70ee53 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift
@@ -8,7 +8,7 @@ import DependencyInjection
 import ScrollViewController
 
 public final class OnboardingPhoneController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: OnboardingCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift
index 2ed639b116b86ab36b2cdf8924b63626bd653d0c..d95169bbd9c5ce078687a77c96768d91e9d2336d 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift
@@ -6,7 +6,7 @@ import Combine
 import DependencyInjection
 
 public final class OnboardingStartController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: OnboardingCoordinating
 
     lazy private var screenView = OnboardingStartView()
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
index d25db4581f5c5dbd3083515b1828433494631e45..3bc4493ebd99899a4b9ea646964816ff0140009e 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
@@ -8,7 +8,7 @@ import DependencyInjection
 import ScrollViewController
 
 public final class OnboardingUsernameController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: OnboardingCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
index 1263fc26efb0f01d50f00a25e8b4b27e3b821b81..b3871d6234517f1d493ab61da1466bc754446fd7 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
@@ -58,7 +58,7 @@ final class OnboardingEmailConfirmationViewModel {
     }
 
     func didTapNext() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
index 618a903459267835ea47210d17ab0e5f763ac68e..c3cbbb897840964e15f908b9fa83d5d6537f7b23 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
@@ -38,7 +38,7 @@ final class OnboardingEmailViewModel {
     }
 
     func didTapNext() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
index 0af84d605d2c56a938e474d27fd79ad324943ee3..2bd5a7ae35fbca7bf871479f998abeef9e4ce625 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
@@ -58,7 +58,7 @@ final class OnboardingPhoneConfirmationViewModel {
     }
 
     func didTapNext() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
index 4698673d45cbe3b05f73fb3b1943f598f1c9655e..0aff02f402420b959d03f166ba4eb456d1098bea 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
@@ -47,7 +47,7 @@ final class OnboardingPhoneViewModel {
     }
 
     func didTapNext() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
index 8766aac8e7629e4bdb9079cd8a3774aa2d1989a2..ecb64035e73e259bf147ce4ee7f427723d40b3f3 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
@@ -42,7 +42,7 @@ final class OnboardingUsernameViewModel {
     }
 
     func didTapRegister() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/ProfileFeature/Controllers/ProfileCodeController.swift b/Sources/ProfileFeature/Controllers/ProfileCodeController.swift
index d909662aa961fda594217d71d4dd940bd0cf80af..c005b0bf9c10daa6db1f8521799da86bbfad26a4 100644
--- a/Sources/ProfileFeature/Controllers/ProfileCodeController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfileCodeController.swift
@@ -10,7 +10,7 @@ import ScrollViewController
 public typealias ControllerClosure = (UIViewController, AttributeConfirmation) -> Void
 
 public final class ProfileCodeController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
 
     lazy private var screenView = ProfileCodeView()
     lazy private var scrollViewController = ScrollViewController()
diff --git a/Sources/ProfileFeature/Controllers/ProfileController.swift b/Sources/ProfileFeature/Controllers/ProfileController.swift
index 62a2c90ee9bfb56674f8601bb1007d62c5f3323d..e3138f275dd967d73d58548649e4156409178e57 100644
--- a/Sources/ProfileFeature/Controllers/ProfileController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfileController.swift
@@ -7,7 +7,7 @@ import Combine
 import DependencyInjection
 
 public final class ProfileController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: ProfileCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/ProfileFeature/Controllers/ProfileEmailController.swift b/Sources/ProfileFeature/Controllers/ProfileEmailController.swift
index fb85221449bea9ecd89b1a9507db4182b7365622..97ced65c65a10b7d4ac8033a5b2c0d80cf959997 100644
--- a/Sources/ProfileFeature/Controllers/ProfileEmailController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfileEmailController.swift
@@ -7,7 +7,7 @@ import DependencyInjection
 import ScrollViewController
 
 public final class ProfileEmailController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: ProfileCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift b/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift
index b6ddaffb5574bdb44f70994fe2ce2c7b0e98b3c0..eb77fd14d36bce3960f30a73aa4ac7c117e453e7 100644
--- a/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift
@@ -9,7 +9,7 @@ import ScrollViewController
 #warning("TODO: Merge ProfilePhoneController/ProfileEmailController")
 
 public final class ProfilePhoneController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: ProfileCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
index 0cc8f60cfaea6921d99a90ccbba74a0cdfc322c2..d9763e989e23e37c9736582a41fcf1b63aef03b2 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
@@ -57,7 +57,7 @@ final class ProfileCodeViewModel {
     }
 
     func didTapNext() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
index df2bb28cdaa889462d1db1eb506cb14129567312..6b57bc81fe4040faaccaeefff057a275a90deaac 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
@@ -40,7 +40,7 @@ final class ProfileEmailViewModel {
     }
 
     func didTapNext() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
index 9bf5da6e11788124e2bba6b04ba05f2f64b7f033..1013725b419a118c4e437d3e8959c50748447d72 100644
--- a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
@@ -47,7 +47,7 @@ final class ProfilePhoneViewModel {
     }
 
     func didTapNext() {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
index 8f03379f02adde54606a70e4bd7c76f3e1fea87d..a7066eccfaada6e74960beb7a5b3b8b7e72548cb 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
@@ -83,7 +83,7 @@ final class ProfileViewModel {
     }
 
     func didTapDelete(isEmail: Bool) {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
diff --git a/Sources/RequestsFeature/Controllers/RequestsFailedController.swift b/Sources/RequestsFeature/Controllers/RequestsFailedController.swift
index c166da6b2c97b01f38e273b2a1a91ccbed475055..c0af65d5f83a4bc1528065eef1551e597a52f6c9 100644
--- a/Sources/RequestsFeature/Controllers/RequestsFailedController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsFailedController.swift
@@ -5,7 +5,7 @@ import Combine
 import DependencyInjection
 
 final class RequestsFailedController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
 
     lazy private var screenView = RequestsFailedView()
     private var cancellables = Set<AnyCancellable>()
diff --git a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
index 5edb77726fa6d43ba7d4e2a816192fcadd39303b..0a30d05fb32b5ec4d3eb54b1fdeb9ced674a17ac 100644
--- a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
@@ -10,7 +10,7 @@ import DrawerFeature
 import DependencyInjection
 
 final class RequestsReceivedController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var toaster: ToastController
     @Dependency private var coordinator: RequestsCoordinating
 
diff --git a/Sources/RequestsFeature/Controllers/RequestsSentController.swift b/Sources/RequestsFeature/Controllers/RequestsSentController.swift
index 36c245f4476fdd8795a6c0c38612f86c1ec6953e..ceed2cc28989b51d72d0191e63e81ade2f930bdd 100644
--- a/Sources/RequestsFeature/Controllers/RequestsSentController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsSentController.swift
@@ -5,7 +5,7 @@ import Combine
 import DependencyInjection
 
 final class RequestsSentController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
 
     var connectionsPublisher: AnyPublisher<Void, Never> {
         connectionSubject.eraseToAnyPublisher()
diff --git a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
index 9214c9ef3f19d337ae66f0f9a6c8368741ff1096..b7d81db3a65b791e001a897506418295b56f8b3a 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
@@ -38,7 +38,7 @@ final class RequestsFailedViewModel {
     func didTapStateButtonFor(request: Request) {
         guard case let .contact(contact) = request, request.status == .failedToRequest else { return }
 
-        hudSubject.send(.on(nil))
+        hudSubject.send(.on)
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
 
diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
index 7dd9e8c1d07c9b15d9829803be59c55c0955fd12..ded18f77a4da0d956d2bdb0b1779495c818d41f5 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
@@ -150,7 +150,7 @@ final class RequestsReceivedViewModel {
     }
 
     func didRequestAccept(group: Group) {
-        hudSubject.send(.on(nil))
+        hudSubject.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
             do {
@@ -208,7 +208,7 @@ final class RequestsReceivedViewModel {
     }
 
     func didRequestAccept(contact: Contact, nickname: String? = nil) {
-        hudSubject.send(.on(nil))
+        hudSubject.send(.on)
 
         var contact = contact
         contact.nickname = nickname ?? contact.username
diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
index 313a3694ac8498710b4847c501f41a7889c6535a..f94ed8f5164de5c5a419a9b9271499ae7daa4952 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
@@ -53,7 +53,7 @@ final class RequestsSentViewModel {
     func didTapStateButtonFor(request item: RequestSent) {
         guard case let .contact(contact) = item.request, item.request.status == .requested else { return }
 
-        hudSubject.send(.on(nil))
+        hudSubject.send(.on)
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
 
@@ -71,8 +71,10 @@ final class RequestsSentViewModel {
                 item.isResent = true
                 allRequests.append(item)
 
+                let name = (contact.nickname ?? contact.username) ?? ""
+
                 self.toastController.enqueueToast(model: .init(
-                    title: Localized.Requests.Sent.Toast.resent(contact.nickname ?? contact.username),
+                    title: Localized.Requests.Sent.Toast.resent(name),
                     leftImage: Asset.requestSentToaster.image
                 ))
 
diff --git a/Sources/RestoreFeature/Controllers/RestoreListController.swift b/Sources/RestoreFeature/Controllers/RestoreListController.swift
index c73e94edec314484dc06307f84e9e528f66bcc2a..b396d1069df7008772290e5c9e2f275adb5ccf4b 100644
--- a/Sources/RestoreFeature/Controllers/RestoreListController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreListController.swift
@@ -6,7 +6,7 @@ import DrawerFeature
 import DependencyInjection
 
 public final class RestoreListController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: RestoreCoordinating
 
     lazy private var screenView = RestoreListView()
diff --git a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
index a4c2402e819c0576afdacc501c4550cd3656269f..438207bb1cc5df10104d3be19029ef01cc7d3c19 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
@@ -47,7 +47,7 @@ final class RestoreListViewModel {
             guard let self = self else { return }
             controller.navigationController?.popViewController(animated: true)
 
-            self.hudSubject.send(.on(nil))
+            self.hudSubject.send(.on)
 
             self.sftpService.fetchMetadata{ result in
                 switch result {
@@ -72,7 +72,7 @@ final class RestoreListViewModel {
         googleDriveService.authorize(presenting: controller) { authResult in
             switch authResult {
             case .success:
-                self.hudSubject.send(.on(nil))
+                self.hudSubject.send(.on)
                 self.googleDriveService.downloadMetadata { downloadResult in
                     switch downloadResult {
                     case .success(let metadata):
@@ -97,7 +97,7 @@ final class RestoreListViewModel {
 
     private func didRequestICloudAuthorization() {
         if icloudService.isAuthorized() {
-            self.hudSubject.send(.on(nil))
+            self.hudSubject.send(.on)
 
             icloudService.downloadMetadata { result in
                 switch result {
@@ -129,7 +129,7 @@ final class RestoreListViewModel {
                 case .success(let bool):
                     guard bool == true else { return }
 
-                    self.hudSubject.send(.on(nil))
+                    self.hudSubject.send(.on)
                     dropboxService.downloadMetadata { metadataResult in
                         switch metadataResult {
                         case .success(let metadata):
diff --git a/Sources/SFTPFeature/SFTPController.swift b/Sources/SFTPFeature/SFTPController.swift
index 0a8c8d38ed8dbd9d802d58962b468b11f4c57431..f80908b9cdf6106bacef3c2fa9f6c4eac46e8145 100644
--- a/Sources/SFTPFeature/SFTPController.swift
+++ b/Sources/SFTPFeature/SFTPController.swift
@@ -5,7 +5,7 @@ import DependencyInjection
 import ScrollViewController
 
 public final class SFTPController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
 
     lazy private var screenView = SFTPView()
     lazy private var scrollViewController = ScrollViewController()
diff --git a/Sources/SFTPFeature/SFTPViewModel.swift b/Sources/SFTPFeature/SFTPViewModel.swift
index dcf397a1da1c474681001b87df0d148ae26b2706..e64536bfd96277642d25ddb13f5c81570836a22b 100644
--- a/Sources/SFTPFeature/SFTPViewModel.swift
+++ b/Sources/SFTPFeature/SFTPViewModel.swift
@@ -45,7 +45,7 @@ final class SFTPViewModel {
     }
 
     func didTapLogin() {
-        hudSubject.send(.on(nil))
+        hudSubject.send(.on)
 
         let host = stateSubject.value.host
         let username = stateSubject.value.username
diff --git a/Sources/SearchFeature/Controllers/CameraController.swift b/Sources/SearchFeature/Controllers/CameraController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7473920844e51bf6b77634818ee56282cb00b5d2
--- /dev/null
+++ b/Sources/SearchFeature/Controllers/CameraController.swift
@@ -0,0 +1,61 @@
+import Combine
+import AVFoundation
+
+final class CameraController: NSObject {
+    var dataPublisher: AnyPublisher<Data, Never> {
+        dataSubject
+            .receive(on: DispatchQueue.main)
+            .eraseToAnyPublisher()
+    }
+
+    lazy var previewLayer: CALayer = {
+        let layer = AVCaptureVideoPreviewLayer(session: session)
+        layer.videoGravity = .resizeAspectFill
+        return layer
+    }()
+
+    private let session = AVCaptureSession()
+    private let metadataOutput = AVCaptureMetadataOutput()
+    private let dataSubject = PassthroughSubject<Data, Never>()
+
+    override init() {
+        super.init()
+        setupCameraDevice()
+    }
+
+    func start() {
+        guard session.isRunning == false else { return }
+        session.startRunning()
+    }
+
+    func stop() {
+        guard session.isRunning == true else { return }
+        session.stopRunning()
+    }
+
+    private func setupCameraDevice() {
+        if let captureDevice = AVCaptureDevice.default(for: .video),
+           let input = try? AVCaptureDeviceInput(device: captureDevice) {
+
+            if session.canAddInput(input) && session.canAddOutput(metadataOutput) {
+                session.addInput(input)
+                session.addOutput(metadataOutput)
+            }
+
+            metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
+            metadataOutput.metadataObjectTypes = [.qr]
+        }
+    }
+
+    func metadataOutput(
+        _ output: AVCaptureMetadataOutput,
+        didOutput metadataObjects: [AVMetadataObject],
+        from connection: AVCaptureConnection
+    ) {
+        guard let object = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
+              let data = object.stringValue?.data(using: .nonLossyASCII), object.type == .qr else { return }
+        dataSubject.send(data)
+    }
+}
+
+extension CameraController: AVCaptureMetadataOutputObjectsDelegate {}
diff --git a/Sources/SearchFeature/Controllers/SearchContainerController.swift b/Sources/SearchFeature/Controllers/SearchContainerController.swift
index 1baedc82b9ca8d74ddb2f6a122b4a3d9b1262351..ad85b25640a1f1a5e418bf9974b2a0656021d202 100644
--- a/Sources/SearchFeature/Controllers/SearchContainerController.swift
+++ b/Sources/SearchFeature/Controllers/SearchContainerController.swift
@@ -2,32 +2,49 @@ import UIKit
 import Theme
 import Shared
 import Combine
+import XXModels
+import DrawerFeature
 import DependencyInjection
 
 public final class SearchContainerController: UIViewController {
-    @Dependency private var statusBarController: StatusBarStyleControlling
+    @Dependency var coordinator: SearchCoordinating
+    @Dependency var statusBarController: StatusBarStyleControlling
 
     lazy private var screenView = SearchContainerView()
 
+    private var contentOffset: CGPoint?
     private var cancellables = Set<AnyCancellable>()
-    private let qrController = SearchQRController()
-    private let emailController = SearchEmailController()
-    private let phoneController = SearchPhoneController()
-    private let usernameController = SearchUsernameController()
+    private let viewModel = SearchContainerViewModel()
+    private let leftController = SearchLeftController()
+    private let rightController = SearchRightController()
+    private var drawerCancellables = Set<AnyCancellable>()
 
     public override func loadView() {
         view = screenView
-        screenView.scrollView.delegate = self
         embedControllers()
     }
 
     public override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         statusBarController.style.send(.darkContent)
-
         navigationController?.navigationBar.customize(
             backgroundColor: Asset.neutralWhite.color
         )
+
+        if let contentOffset = self.contentOffset {
+            screenView.scrollView.setContentOffset(contentOffset, animated: true)
+        }
+    }
+
+    public override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        contentOffset = screenView.scrollView.contentOffset
+    }
+
+    public override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        viewModel.didAppear()
+        rightController.viewModel.viewWillAppear()
     }
 
     public override func viewDidLoad() {
@@ -55,12 +72,23 @@ public final class SearchContainerController: UIViewController {
     private func setupBindings() {
         screenView.segmentedControl
             .actionPublisher
+            .removeDuplicates()
             .receive(on: DispatchQueue.main)
             .sink { [unowned self] in
-                let page = CGFloat($0.rawValue)
-                let point: CGPoint = CGPoint(x: screenView.frame.width * page, y: 0.0)
-                screenView.scrollView.setContentOffset(point, animated: true)
+                if $0 == .qr {
+                    let point = CGPoint(x: screenView.frame.width, y: 0.0)
+                    screenView.scrollView.setContentOffset(point, animated: true)
+                    leftController.endEditing()
+                } else {
+                    screenView.scrollView.setContentOffset(.zero, animated: true)
+                    leftController.viewModel.didSelectItem($0)
+                }
             }.store(in: &cancellables)
+
+        viewModel.coverTrafficPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in presentCoverTrafficDrawer() }
+            .store(in: &cancellables)
     }
 
     @objc private func didTapBack() {
@@ -68,91 +96,90 @@ public final class SearchContainerController: UIViewController {
     }
 
     private func embedControllers() {
-        addChild(qrController)
-        addChild(emailController)
-        addChild(phoneController)
-        addChild(usernameController)
+        addChild(leftController)
+        addChild(rightController)
 
-        screenView.scrollView.addSubview(qrController.view)
-        screenView.scrollView.addSubview(emailController.view)
-        screenView.scrollView.addSubview(phoneController.view)
-        screenView.scrollView.addSubview(usernameController.view)
+        screenView.scrollView.addSubview(leftController.view)
+        screenView.scrollView.addSubview(rightController.view)
 
-        usernameController.view.snp.makeConstraints {
+        leftController.view.snp.makeConstraints {
             $0.top.equalTo(screenView.segmentedControl.snp.bottom)
             $0.width.equalTo(screenView)
             $0.bottom.equalTo(screenView)
             $0.left.equalToSuperview()
-            $0.right.equalTo(emailController.view.snp.left)
-        }
-
-        emailController.view.snp.makeConstraints {
-            $0.top.equalTo(screenView.segmentedControl.snp.bottom)
-            $0.width.equalTo(screenView)
-            $0.bottom.equalTo(screenView)
-            $0.right.equalTo(phoneController.view.snp.left)
+            $0.right.equalTo(rightController.view.snp.left)
         }
 
-        phoneController.view.snp.makeConstraints {
+        rightController.view.snp.makeConstraints {
             $0.top.equalTo(screenView.segmentedControl.snp.bottom)
             $0.width.equalTo(screenView)
             $0.bottom.equalTo(screenView)
-            $0.right.equalTo(qrController.view.snp.left)
         }
 
-        qrController.view.snp.makeConstraints {
-            $0.top.equalTo(screenView.segmentedControl.snp.bottom)
-            $0.width.equalTo(screenView)
-            $0.bottom.equalTo(screenView)
-        }
-
-        qrController.didMove(toParent: self)
-        emailController.didMove(toParent: self)
-        phoneController.didMove(toParent: self)
-        usernameController.didMove(toParent: self)
+        leftController.didMove(toParent: self)
+        rightController.didMove(toParent: self)
     }
 }
 
-extension SearchContainerController: UIScrollViewDelegate {
-    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
-        let pageOffset = scrollView.contentOffset.x / view.frame.width
-        scrollSegmentedControlTrack(using: pageOffset)
-        updateSegmentedControlButtonsColor(using: pageOffset)
-    }
-
-    private func scrollSegmentedControlTrack(using pageOffset: CGFloat) {
-        let amountOfTabs = 4.0
-        let tabWidth = screenView.bounds.width / amountOfTabs
-
-        if let leftConstraint = screenView.segmentedControl.leftConstraint {
-            leftConstraint.update(offset: pageOffset * tabWidth)
-        }
-    }
-
-    private func updateSegmentedControlButtonsColor(using pageOffset: CGFloat) {
-        let qrRate = highlightRateFor(page: 3, offset: pageOffset)
-        let emailRate = highlightRateFor(page: 1, offset: pageOffset)
-        let phoneRate = highlightRateFor(page: 2, offset: pageOffset)
-        let usernameRate = highlightRateFor(page: 0, offset: pageOffset)
+extension SearchContainerController {
+    private func presentCoverTrafficDrawer() {
+        let enableButton = CapsuleButton()
+        enableButton.set(
+            style: .brandColored,
+            title: Localized.ChatList.Traffic.positive
+        )
 
-        screenView.segmentedControl.qrCodeButton.updateHighlighting(rate: qrRate)
-        screenView.segmentedControl.emailButton.updateHighlighting(rate: emailRate)
-        screenView.segmentedControl.phoneButton.updateHighlighting(rate: phoneRate)
-        screenView.segmentedControl.usernameButton.updateHighlighting(rate: usernameRate)
-    }
+        let dismissButton = CapsuleButton()
+        dismissButton.set(
+            style: .seeThrough,
+            title: Localized.ChatList.Traffic.negative
+        )
 
-    private func highlightRateFor(page: CGFloat, offset: CGFloat) -> CGFloat {
-        let lowerBound = page - 1
-        let upperBound = page + 1
-
-        if offset > lowerBound && offset < upperBound {
-            if (offset - lowerBound) > 1 {
-                return 1 - (offset - page)
-            } else {
-                return offset - lowerBound
-            }
-        } else {
-            return 0
-        }
+        let drawer = DrawerController(with: [
+            DrawerText(
+                font: Fonts.Mulish.bold.font(size: 26.0),
+                text: Localized.ChatList.Traffic.title,
+                color: Asset.neutralActive.color,
+                alignment: .left,
+                spacingAfter: 19
+            ),
+            DrawerText(
+                font: Fonts.Mulish.regular.font(size: 16.0),
+                text: Localized.ChatList.Traffic.subtitle,
+                color: Asset.neutralBody.color,
+                alignment: .left,
+                lineHeightMultiple: 1.1,
+                spacingAfter: 39
+            ),
+            DrawerStack(
+                axis: .horizontal,
+                spacing: 20,
+                distribution: .fillEqually,
+                views: [enableButton, dismissButton]
+            )
+        ])
+
+        enableButton
+            .publisher(for: .touchUpInside)
+            .receive(on: DispatchQueue.main)
+            .sink {
+                drawer.dismiss(animated: true) { [weak self] in
+                    guard let self = self else { return }
+                    self.drawerCancellables.removeAll()
+                    self.viewModel.didEnableCoverTraffic()
+                }
+            }.store(in: &drawerCancellables)
+
+        dismissButton
+            .publisher(for: .touchUpInside)
+            .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/SearchFeature/Controllers/SearchEmailController.swift b/Sources/SearchFeature/Controllers/SearchEmailController.swift
deleted file mode 100644
index 1380e7f190f76d68068dd253f0a2bd5ac35359f3..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Controllers/SearchEmailController.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-import UIKit
-
-final class SearchEmailController: UIViewController {
-    lazy private var screenView = SearchEmailView()
-
-    override func loadView() {
-        view = screenView
-    }
-}
diff --git a/Sources/SearchFeature/Controllers/SearchLeftController.swift b/Sources/SearchFeature/Controllers/SearchLeftController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bbfabd2836990bb00c86e01a09527bdbac8b1145
--- /dev/null
+++ b/Sources/SearchFeature/Controllers/SearchLeftController.swift
@@ -0,0 +1,433 @@
+import HUD
+import UIKit
+import Shared
+import Combine
+import XXModels
+import Defaults
+import Countries
+import DrawerFeature
+import DependencyInjection
+
+final class SearchLeftController: UIViewController {
+    @Dependency private var hud: HUD
+    @Dependency private var coordinator: SearchCoordinating
+
+    @KeyObject(.email, defaultValue: nil) var email: String?
+    @KeyObject(.phone, defaultValue: nil) var phone: String?
+    @KeyObject(.sharingEmail, defaultValue: false) var isSharingEmail: Bool
+    @KeyObject(.sharingPhone, defaultValue: false) var isSharingPhone: Bool
+
+    lazy private var screenView = SearchLeftView()
+
+    private var dataSource: SearchDiffableDataSource!
+    private(set) var viewModel = SearchLeftViewModel()
+    private var drawerCancellables = Set<AnyCancellable>()
+    private let adrpURLString = "https://links.xx.network/adrp"
+
+    private var cancellables = Set<AnyCancellable>()
+    private var hudCancellables = Set<AnyCancellable>()
+
+    override func loadView() {
+        view = screenView
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        setupTableView()
+        setupBindings()
+    }
+
+    func endEditing() {
+        screenView.inputField.endEditing(true)
+    }
+
+    private func setupTableView() {
+        screenView.tableView.separatorStyle = .none
+        screenView.tableView.tableFooterView = UIView()
+        screenView.tableView.register(AvatarCell.self)
+        screenView.tableView.dataSource = dataSource
+        screenView.tableView.delegate = self
+
+        dataSource = SearchDiffableDataSource(
+            tableView: screenView.tableView
+        ) { tableView, indexPath, item in
+            let contact: Contact
+            let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: AvatarCell.self)
+
+            let h1Text: String
+            var h2Text: String?
+
+            switch item {
+            case .stranger(let stranger):
+                contact = stranger
+                h1Text = stranger.username ?? ""
+
+                if stranger.authStatus == .requested {
+                    h2Text = "Request pending"
+                } else if stranger.authStatus == .requestFailed {
+                    h2Text = "Request failed"
+                }
+
+            case .connection(let connection):
+                contact = connection
+                h1Text = (connection.nickname ?? contact.username) ?? ""
+
+                if connection.nickname != nil {
+                    h2Text = contact.username ?? ""
+                }
+            }
+
+            cell.setup(
+                title: h1Text,
+                image: contact.photo,
+                firstSubtitle: h2Text,
+                secondSubtitle: contact.email,
+                thirdSubtitle: contact.phone,
+                showSeparator: false,
+                sent: contact.authStatus == .requested
+            )
+
+            cell.didTapStateButton = { [weak self] in
+                guard let self = self else { return }
+                self.viewModel.didTapResend(contact: contact)
+                cell.updateToResent()
+            }
+
+            return cell
+        }
+    }
+
+    private func setupBindings() {
+        viewModel.hudPublisher
+            .removeDuplicates()
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in
+                hud.update(with: $0)
+
+                if case .onAction = $0, let hudBtn = hud.actionButton {
+                    hudBtn.publisher(for: .touchUpInside)
+                        .receive(on: DispatchQueue.main)
+                        .sink { [unowned self] in viewModel.didTapCancelSearch() }
+                        .store(in: &self.hudCancellables)
+                } else {
+                    hudCancellables.forEach { $0.cancel() }
+                    hudCancellables.removeAll()
+                }
+            }
+            .store(in: &cancellables)
+
+
+        viewModel.statePublisher
+            .map(\.item)
+            .removeDuplicates()
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in screenView.updateUIForItem(item: $0) }
+            .store(in: &cancellables)
+
+        viewModel.statePublisher
+            .map(\.country)
+            .removeDuplicates()
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in screenView.countryButton.setFlag($0.flag, prefix: $0.prefix) }
+            .store(in: &cancellables)
+
+        viewModel.statePublisher
+            .compactMap(\.snapshot)
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in
+                screenView.placeholderView.isHidden = true
+                screenView.emptyView.isHidden = $0.numberOfItems != 0
+
+                dataSource.apply($0, animatingDifferences: false)
+            }.store(in: &cancellables)
+
+        screenView.placeholderView
+            .infoPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in presentSearchDisclaimer() }
+            .store(in: &cancellables)
+
+        screenView.countryButton
+            .publisher(for: .touchUpInside)
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in
+                coordinator.toCountries(from: self) { [weak self] country in
+                    guard let self = self else { return }
+                    self.viewModel.didPick(country: country)
+                }
+            }.store(in: &cancellables)
+
+        screenView.inputField
+            .textPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in viewModel.didEnterInput($0) }
+            .store(in: &cancellables)
+
+        screenView.inputField
+            .returnPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] _ in viewModel.didStartSearching() }
+            .store(in: &cancellables)
+
+        screenView.inputField
+            .isEditingPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] isEditing in
+                UIView.animate(withDuration: 0.25) {
+                    self.screenView.placeholderView.titleLabel.alpha = isEditing ? 0.1 : 1.0
+                    self.screenView.placeholderView.subtitleWithInfo.alpha = isEditing ? 0.1 : 1.0
+                }
+            }.store(in: &cancellables)
+
+        viewModel.successPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in presentSucessDrawerFor(contact: $0) }
+            .store(in: &cancellables)
+    }
+
+    private func presentSearchDisclaimer() {
+        let actionButton = CapsuleButton()
+        actionButton.set(
+            style: .seeThrough,
+            title: Localized.Ud.Placeholder.Drawer.action
+        )
+
+        let drawer = DrawerController(with: [
+            DrawerText(
+                font: Fonts.Mulish.bold.font(size: 26.0),
+                text: Localized.Ud.Placeholder.Drawer.title,
+                color: Asset.neutralActive.color,
+                alignment: .left,
+                spacingAfter: 19
+            ),
+            DrawerLinkText(
+                text: Localized.Ud.Placeholder.Drawer.subtitle,
+                urlString: adrpURLString,
+                spacingAfter: 37
+            ),
+            DrawerStack(views: [
+                actionButton,
+                FlexibleSpace()
+            ])
+        ])
+
+        actionButton.publisher(for: .touchUpInside)
+            .receive(on: DispatchQueue.main)
+            .sink {
+                drawer.dismiss(animated: true) { [weak self] in
+                    guard let self = self else { return }
+                    self.drawerCancellables.removeAll()
+                }
+            }.store(in: &self.drawerCancellables)
+
+        coordinator.toDrawer(drawer, from: self)
+    }
+
+    private func presentSucessDrawerFor(contact: Contact) {
+        var items: [DrawerItem] = []
+
+        let drawerTitle = DrawerText(
+            font: Fonts.Mulish.extraBold.font(size: 26.0),
+            text: Localized.Ud.NicknameDrawer.title,
+            color: Asset.neutralDark.color,
+            spacingAfter: 20
+        )
+
+        let drawerSubtitle = DrawerText(
+            font: Fonts.Mulish.regular.font(size: 16.0),
+            text: Localized.Ud.NicknameDrawer.subtitle,
+            color: Asset.neutralDark.color,
+            spacingAfter: 20
+        )
+
+        items.append(contentsOf: [
+            drawerTitle,
+            drawerSubtitle
+        ])
+
+        let drawerNicknameInput = DrawerInput(
+            placeholder: contact.username!,
+            validator: .init(
+                wrongIcon: .image(Asset.sharedError.image),
+                correctIcon: .image(Asset.sharedSuccess.image),
+                shouldAcceptPlaceholder: true
+            ),
+            spacingAfter: 29
+        )
+
+        items.append(drawerNicknameInput)
+
+        let drawerSaveButton = DrawerCapsuleButton(
+            model: .init(
+                title: Localized.Ud.NicknameDrawer.save,
+                style: .brandColored
+            ), spacingAfter: 5
+        )
+
+        items.append(drawerSaveButton)
+
+        let drawer = DrawerController(with: items)
+        var nickname: String?
+        var allowsSave = true
+
+        drawerNicknameInput.validationPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { allowsSave = $0 }
+            .store(in: &drawerCancellables)
+
+        drawerNicknameInput.inputPublisher
+            .receive(on: DispatchQueue.main)
+            .sink {
+                guard !$0.isEmpty else {
+                    nickname = contact.username
+                    return
+                }
+
+                nickname = $0
+            }
+            .store(in: &drawerCancellables)
+
+        drawerSaveButton.action
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in
+                guard allowsSave else { return }
+
+                drawer.dismiss(animated: true) {
+                    self.viewModel.didSet(nickname: nickname ?? contact.username!, for: contact)
+                }
+            }
+            .store(in: &drawerCancellables)
+
+        coordinator.toNicknameDrawer(drawer, from: self)
+    }
+
+    private func presentRequestDrawer(forContact contact: Contact) {
+        var items: [DrawerItem] = []
+
+        let drawerTitle = DrawerText(
+            font: Fonts.Mulish.extraBold.font(size: 26.0),
+            text: Localized.Ud.RequestDrawer.title,
+            color: Asset.neutralDark.color,
+            spacingAfter: 20
+        )
+
+        var subtitleFragment = "Share your information with #\(contact.username ?? "")"
+
+        if let email = contact.email {
+            subtitleFragment.append(contentsOf: " (\(email))#")
+        } else if let phone = contact.phone {
+            subtitleFragment.append(contentsOf: " (\(Country.findFrom(phone).prefix) \(phone.dropLast(2)))#")
+        } else {
+            subtitleFragment.append(contentsOf: "#")
+        }
+
+        subtitleFragment.append(contentsOf: " so they know its you.")
+
+        let drawerSubtitle = DrawerText(
+            font: Fonts.Mulish.regular.font(size: 16.0),
+            text: subtitleFragment,
+            color: Asset.neutralDark.color,
+            spacingAfter: 31.5,
+            customAttributes: [
+                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+                .foregroundColor: Asset.brandPrimary.color
+            ]
+        )
+
+        items.append(contentsOf: [
+            drawerTitle,
+            drawerSubtitle
+        ])
+
+        if let email = email {
+            let drawerEmail = DrawerSwitch(
+                title: Localized.Ud.RequestDrawer.email,
+                content: email,
+                spacingAfter: phone != nil ? 23 : 31,
+                isInitiallyOn: isSharingEmail
+            )
+
+            items.append(drawerEmail)
+
+            drawerEmail.isOnPublisher
+                .receive(on: DispatchQueue.main)
+                .sink { [weak self] in self?.isSharingEmail = $0 }
+                .store(in: &drawerCancellables)
+        }
+
+        if let phone = phone {
+            let drawerPhone = DrawerSwitch(
+                title: Localized.Ud.RequestDrawer.phone,
+                content: "\(Country.findFrom(phone).prefix) \(phone.dropLast(2))",
+                spacingAfter: 31,
+                isInitiallyOn: isSharingPhone
+            )
+
+            items.append(drawerPhone)
+
+            drawerPhone.isOnPublisher
+                .receive(on: DispatchQueue.main)
+                .sink { [weak self] in self?.isSharingPhone = $0 }
+                .store(in: &drawerCancellables)
+        }
+
+        let drawerSendButton = DrawerCapsuleButton(
+            model: .init(
+                title: Localized.Ud.RequestDrawer.send,
+                style: .brandColored
+            ), spacingAfter: 5
+        )
+
+        let drawerCancelButton = DrawerCapsuleButton(
+            model: .init(
+                title: Localized.Ud.RequestDrawer.cancel,
+                style: .simplestColoredBrand
+            ), spacingAfter: 5
+        )
+
+        items.append(contentsOf: [drawerSendButton, drawerCancelButton])
+        let drawer = DrawerController(with: items)
+
+        drawerSendButton.action
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in
+                drawer.dismiss(animated: true) {
+                    self.viewModel.didTapRequest(contact: contact)
+                }
+            }.store(in: &drawerCancellables)
+
+        drawerCancelButton.action
+            .receive(on: DispatchQueue.main)
+            .sink { drawer.dismiss(animated: true) }
+            .store(in: &drawerCancellables)
+
+        coordinator.toDrawer(drawer, from: self)
+    }
+
+}
+
+extension SearchLeftController: UITableViewDelegate {
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if let item = dataSource.itemIdentifier(for: indexPath) {
+            switch item {
+            case .stranger(let contact):
+                didTap(contact: contact)
+            case .connection(let contact):
+                didTap(contact: contact)
+            }
+        }
+    }
+
+    func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
+        (view as! UITableViewHeaderFooterView).textLabel?.textColor = Asset.neutralWeak.color
+    }
+
+    private func didTap(contact: Contact) {
+        guard contact.authStatus == .stranger else {
+            coordinator.toContact(contact, from: self)
+            return
+        }
+
+        presentRequestDrawer(forContact: contact)
+    }
+}
diff --git a/Sources/SearchFeature/Controllers/SearchPhoneController.swift b/Sources/SearchFeature/Controllers/SearchPhoneController.swift
deleted file mode 100644
index 63d6f56897223f6291aa360974d8c1cf3ac98856..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Controllers/SearchPhoneController.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-import UIKit
-
-final class SearchPhoneController: UIViewController {
-    lazy private var screenView = SearchPhoneView()
-
-    override func loadView() {
-        view = screenView
-    }
-}
diff --git a/Sources/SearchFeature/Controllers/SearchQRController.swift b/Sources/SearchFeature/Controllers/SearchQRController.swift
deleted file mode 100644
index 04fe440f0ab5c3b3753f0758c2e58aa7edaa1432..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Controllers/SearchQRController.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-import UIKit
-
-final class SearchQRController: UIViewController {
-    lazy private var screenView = SearchQRView()
-
-    override func loadView() {
-        view = screenView
-    }
-}
diff --git a/Sources/SearchFeature/Controllers/SearchRightController.swift b/Sources/SearchFeature/Controllers/SearchRightController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..35240054497992ff2b5a277618321dceffeb652a
--- /dev/null
+++ b/Sources/SearchFeature/Controllers/SearchRightController.swift
@@ -0,0 +1,81 @@
+import UIKit
+import Combine
+import DependencyInjection
+
+final class SearchRightController: UIViewController {
+    @Dependency var coordinator: SearchCoordinating
+
+    lazy private var screenView = SearchRightView()
+
+    private var cancellables = Set<AnyCancellable>()
+    private let cameraController = CameraController()
+    private(set) var viewModel = SearchRightViewModel()
+
+    override func loadView() {
+        view = screenView
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        screenView.layer.insertSublayer(cameraController.previewLayer, at: 0)
+        setupBindings()
+    }
+
+    override func viewDidLayoutSubviews() {
+        super.viewDidLayoutSubviews()
+        cameraController.previewLayer.frame = screenView.bounds
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        viewModel.viewWillDisappear()
+    }
+
+    private func setupBindings() {
+        cameraController
+            .dataPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in viewModel.didScan(data: $0) }
+            .store(in: &cancellables)
+
+        viewModel.cameraSemaphorePublisher
+            .removeDuplicates()
+            .receive(on: DispatchQueue.global())
+            .sink { [unowned self] setOn in
+                if setOn {
+                    cameraController.start()
+                } else {
+                    cameraController.stop()
+                }
+            }.store(in: &cancellables)
+
+        viewModel.foundPublisher
+            .receive(on: DispatchQueue.main)
+            .delay(for: 1, scheduler: DispatchQueue.main)
+            .sink { [unowned self] in coordinator.toContact($0, from: self) }
+            .store(in: &cancellables)
+
+        viewModel.statusPublisher
+            .receive(on: DispatchQueue.main)
+            .removeDuplicates()
+            .sink { [unowned self] in screenView.update(status: $0) }
+            .store(in: &cancellables)
+
+        screenView.actionButton
+            .publisher(for: .touchUpInside)
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in
+                switch viewModel.statusSubject.value {
+                case .failed(.cameraPermission):
+                    guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
+                    UIApplication.shared.open(url, options: [:])
+                case .failed(.requestOpened):
+                    coordinator.toRequests(from: self)
+                case .failed(.alreadyFriends):
+                    coordinator.toContacts(from: self)
+                default:
+                    break
+                }
+            }.store(in: &cancellables)
+    }
+}
diff --git a/Sources/SearchFeature/Controllers/SearchTableController.swift b/Sources/SearchFeature/Controllers/SearchTableController.swift
deleted file mode 100644
index 67459cffbf9a7e1f9cbd05ceab5fb788f4af9f3f..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Controllers/SearchTableController.swift
+++ /dev/null
@@ -1,55 +0,0 @@
-import UIKit
-import Models
-import Combine
-import XXModels
-
-final class SearchTableController: UITableViewController {
-    private let viewModel: SearchViewModel
-    private var cancellables = Set<AnyCancellable>()
-    private(set) var dataSource = [Contact]()
-
-    init(_ viewModel: SearchViewModel) {
-        self.viewModel = viewModel
-        super.init(style: .grouped)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        tableView.backgroundColor = .clear
-        tableView.separatorStyle = .none
-        tableView.register(SearchCell.self)
-
-        viewModel.itemsRelay
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dataSource = $0
-                tableView.reloadData()
-            }.store(in: &cancellables)
-    }
-
-    override func tableView(
-        _ tableView: UITableView,
-        cellForRowAt indexPath: IndexPath
-    ) -> UITableViewCell {
-        let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: SearchCell.self)
-        let username = dataSource[indexPath.row].username!
-
-        cell.setup(
-            title: username,
-            subtitle: username,
-            avatarTitle: username,
-            avatarImage: nil,
-            avatarSize: .large
-        )
-
-        return cell
-    }
-
-    override func tableView(
-        _: UITableView,
-        numberOfRowsInSection: Int
-    ) -> Int { dataSource.count }
-}
-
diff --git a/Sources/SearchFeature/Controllers/SearchUsernameController.swift b/Sources/SearchFeature/Controllers/SearchUsernameController.swift
deleted file mode 100644
index 136951c882abe313099b5ced226a0d16fa497893..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Controllers/SearchUsernameController.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-import UIKit
-
-final class SearchUsernameController: UIViewController {
-    lazy private var screenView = SearchUsernameView()
-
-    override func loadView() {
-        view = screenView
-    }
-}
diff --git a/Sources/SearchFeature/Coordinator/SearchCoordinator.swift b/Sources/SearchFeature/Coordinator/SearchCoordinator.swift
index 2f66a6228fd96bb156266a80c16a9ac5ba53c5d9..21a9d7b3d5eb9b1cef283a410135157fb7571ef1 100644
--- a/Sources/SearchFeature/Coordinator/SearchCoordinator.swift
+++ b/Sources/SearchFeature/Coordinator/SearchCoordinator.swift
@@ -6,6 +6,8 @@ import Presentation
 import ScrollViewController
 
 public protocol SearchCoordinating {
+    func toRequests(from: UIViewController)
+    func toContacts(from: UIViewController)
     func toContact(_: Contact, from: UIViewController)
     func toDrawer(_: UIViewController, from: UIViewController)
     func toNicknameDrawer(_: UIViewController, from: UIViewController)
@@ -15,21 +17,38 @@ public protocol SearchCoordinating {
 public struct SearchCoordinator {
     var pushPresenter: Presenting = PushPresenter()
     var bottomPresenter: Presenting = BottomPresenter()
+    var replacePresenter: Presenting = ReplacePresenter()
     var fullscreenPresenter: Presenting = FullscreenPresenter()
 
+    var contactsFactory: () -> UIViewController
+    var requestsFactory: () -> UIViewController
     var contactFactory: (Contact) -> UIViewController
     var countriesFactory: (@escaping (Country) -> Void) -> UIViewController
 
     public init(
+        contactsFactory: @escaping () -> UIViewController,
+        requestsFactory: @escaping () -> UIViewController,
         contactFactory: @escaping (Contact) -> UIViewController,
         countriesFactory: @escaping (@escaping (Country) -> Void) -> UIViewController
     ) {
         self.contactFactory = contactFactory
+        self.contactsFactory = contactsFactory
+        self.requestsFactory = requestsFactory
         self.countriesFactory = countriesFactory
     }
 }
 
 extension SearchCoordinator: SearchCoordinating {
+    public func toRequests(from parent: UIViewController) {
+        let screen = requestsFactory()
+        replacePresenter.present(screen, from: parent)
+    }
+
+    public func toContacts(from parent: UIViewController) {
+        let screen = contactsFactory()
+        replacePresenter.present(screen, from: parent)
+    }
+
     public func toContact(_ contact: Contact, from parent: UIViewController) {
         let screen = contactFactory(contact)
         pushPresenter.present(screen, from: parent)
diff --git a/Sources/SearchFeature/Utils/SearchDiffableDataSource.swift b/Sources/SearchFeature/Utils/SearchDiffableDataSource.swift
new file mode 100644
index 0000000000000000000000000000000000000000..10d7bccc8c0f100b32cd34bf7a91312796ae9f15
--- /dev/null
+++ b/Sources/SearchFeature/Utils/SearchDiffableDataSource.swift
@@ -0,0 +1,23 @@
+import UIKit
+import XXModels
+
+enum SearchSection {
+    case stranger
+    case connections
+}
+
+enum SearchItem: Equatable, Hashable {
+    case stranger(Contact)
+    case connection(Contact)
+}
+
+class SearchDiffableDataSource: UITableViewDiffableDataSource<SearchSection, SearchItem> {
+    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+        switch snapshot().sectionIdentifiers[section] {
+        case .stranger:
+            return ""
+        case .connections:
+            return "LOCAL RESULTS"
+        }
+    }
+}
diff --git a/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift b/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e308f6a5b21134224ec17155b9585a9718cf1204
--- /dev/null
+++ b/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift
@@ -0,0 +1,58 @@
+import UIKit
+import Combine
+import Defaults
+import Integration
+import PushFeature
+import DependencyInjection
+
+final class SearchContainerViewModel {
+    @Dependency var session: SessionType
+    @Dependency var pushHandler: PushHandling
+
+    @KeyObject(.dummyTrafficOn, defaultValue: false) var isCoverTrafficEnabled
+    @KeyObject(.pushNotifications, defaultValue: false) var pushNotifications
+    @KeyObject(.askedDummyTrafficOnce, defaultValue: false) var offeredCoverTraffic
+
+    var coverTrafficPublisher: AnyPublisher<Void, Never> {
+        coverTrafficSubject.eraseToAnyPublisher()
+    }
+
+    private let coverTrafficSubject = PassthroughSubject<Void, Never>()
+
+    func didAppear() {
+        verifyCoverTraffic()
+        verifyNotifications()
+    }
+
+    func didEnableCoverTraffic() {
+        isCoverTrafficEnabled = true
+        session.setDummyTraffic(status: true)
+    }
+
+    private func verifyCoverTraffic() {
+        guard offeredCoverTraffic == false else { return }
+        offeredCoverTraffic = true
+        coverTrafficSubject.send()
+    }
+
+    private func verifyNotifications() {
+        guard pushNotifications == false else { return }
+
+        pushHandler.requestAuthorization { [weak self] result in
+            guard let self = self else { return }
+
+            switch result {
+            case .success(let granted):
+                if granted {
+                    DispatchQueue.main.async {
+                        UIApplication.shared.registerForRemoteNotifications()
+                    }
+                }
+
+                self.pushNotifications = granted
+            case .failure:
+                self.pushNotifications = false
+            }
+        }
+    }
+}
diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..30f77b191cbd9115f856d6133a2df3b2fa01fb0b
--- /dev/null
+++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
@@ -0,0 +1,137 @@
+import HUD
+import UIKit
+import Shared
+import Combine
+import XXModels
+import Countries
+import Integration
+import DependencyInjection
+
+typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
+
+struct SearchLeftViewState {
+    var input = ""
+    var snapshot: SearchSnapshot?
+    var country: Country = .fromMyPhone()
+    var item: SearchSegmentedControl.Item = .username
+}
+
+final class SearchLeftViewModel {
+    @Dependency var session: SessionType
+
+    var hudPublisher: AnyPublisher<HUDStatus, Never> {
+        hudSubject.eraseToAnyPublisher()
+    }
+
+    var successPublisher: AnyPublisher<Contact, Never> {
+        successSubject.eraseToAnyPublisher()
+    }
+
+    var statePublisher: AnyPublisher<SearchLeftViewState, Never> {
+        stateSubject.eraseToAnyPublisher()
+    }
+
+    private var searchCancellables = Set<AnyCancellable>()
+    private let successSubject = PassthroughSubject<Contact, Never>()
+    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
+    private let stateSubject = CurrentValueSubject<SearchLeftViewState, Never>(.init())
+
+    func didEnterInput(_ string: String) {
+        stateSubject.value.input = string
+    }
+
+    func didPick(country: Country) {
+        stateSubject.value.country = country
+    }
+
+    func didSelectItem(_ item: SearchSegmentedControl.Item) {
+        stateSubject.value.item = item
+    }
+
+    func didTapCancelSearch() {
+        searchCancellables.forEach { $0.cancel() }
+        searchCancellables.removeAll()
+        hudSubject.send(.none)
+    }
+
+    func didStartSearching() {
+        guard stateSubject.value.input.isEmpty == false else { return }
+
+        hudSubject.send(.onAction(Localized.Ud.Search.cancel))
+
+        var content = stateSubject.value.input
+        let prefix = stateSubject.value.item.written.first!.uppercased()
+
+        if stateSubject.value.item == .phone {
+            content += stateSubject.value.country.code
+        }
+
+        session.search(fact: "\(prefix)\(content)")
+            .sink { [unowned self] in
+                if case .failure(let error) = $0 {
+                    self.appendToLocalSearch(nil)
+                    self.hudSubject.send(.error(.init(with: error)))
+                }
+            } receiveValue: { contact in
+                self.hudSubject.send(.none)
+                self.appendToLocalSearch(contact)
+            }.store(in: &searchCancellables)
+    }
+
+    func didTapResend(contact: Contact) {
+        hudSubject.send(.on)
+
+        do {
+            try self.session.retryRequest(contact)
+            hudSubject.send(.none)
+        } catch {
+            hudSubject.send(.error(.init(with: error)))
+        }
+    }
+
+    func didTapRequest(contact: Contact) {
+        hudSubject.send(.on)
+        var contact = contact
+        contact.nickname = contact.username
+
+        do {
+            try self.session.add(contact)
+            hudSubject.send(.none)
+            successSubject.send(contact)
+        } catch {
+            hudSubject.send(.error(.init(with: error)))
+        }
+    }
+
+    func didSet(nickname: String, for contact: Contact) {
+        if var contact = try? session.dbManager.fetchContacts(.init(id: [contact.id])).first {
+            contact.nickname = nickname
+            _ = try? session.dbManager.saveContact(contact)
+        }
+    }
+
+    private func appendToLocalSearch(_ user: Contact?) {
+        var snapshot = SearchSnapshot()
+
+        if var user = user {
+            if let contact = try? session.dbManager.fetchContacts(.init(id: [user.id])).first {
+                user.authStatus = contact.authStatus
+            }
+
+            if user.authStatus != .friend {
+                snapshot.appendSections([.stranger])
+                snapshot.appendItems([.stranger(user)], toSection: .stranger)
+            }
+        }
+
+        let localsQuery = Contact.Query(text: stateSubject.value.input, authStatus: [.friend])
+
+        if let locals = try? session.dbManager.fetchContacts(localsQuery), locals.count > 0 {
+            let localsWithoutMe = locals.filter { $0.id != session.myId }
+            snapshot.appendSections([.connections])
+            snapshot.appendItems(localsWithoutMe.map(SearchItem.connection), toSection: .connections)
+        }
+
+        stateSubject.value.snapshot = snapshot
+    }
+}
diff --git a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..5cfe38db68c498a15522f745174e41415281ba17
--- /dev/null
+++ b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
@@ -0,0 +1,111 @@
+import Shared
+import Combine
+import XXModels
+import Foundation
+import Permissions
+import Integration
+import DependencyInjection
+
+enum ScanningStatus: Equatable {
+    case reading
+    case processing
+    case success
+    case failed(ScanningError)
+}
+
+enum ScanningError: Equatable {
+    case requestOpened
+    case unknown(String)
+    case cameraPermission
+    case alreadyFriends(String)
+}
+
+final class SearchRightViewModel {
+    @Dependency var session: SessionType
+    @Dependency var permissions: PermissionHandling
+
+    var foundPublisher: AnyPublisher<Contact, Never> {
+        foundSubject.eraseToAnyPublisher()
+    }
+
+    var cameraSemaphorePublisher: AnyPublisher<Bool, Never> {
+        cameraSemaphoreSubject.eraseToAnyPublisher()
+    }
+
+    var statusPublisher: AnyPublisher<ScanningStatus, Never> {
+        statusSubject.eraseToAnyPublisher()
+    }
+
+    private let foundSubject = PassthroughSubject<Contact, Never>()
+    private let cameraSemaphoreSubject = PassthroughSubject<Bool, Never>()
+    private(set) var statusSubject = CurrentValueSubject<ScanningStatus, Never>(.reading)
+
+    func viewWillAppear() {
+        permissions.requestCamera { [weak self] granted in
+            guard let self = self else { return }
+
+            if granted {
+                self.statusSubject.value = .reading
+                self.cameraSemaphoreSubject.send(true)
+            } else {
+                self.statusSubject.send(.failed(.cameraPermission))
+            }
+        }
+    }
+
+    func viewWillDisappear() {
+        cameraSemaphoreSubject.send(false)
+    }
+
+    func didScan(data: Data) {
+        /// We need to be accepting new readings in order
+        /// to process what just got scanned.
+        ///
+        guard statusSubject.value == .reading else { return }
+        statusSubject.send(.processing)
+
+        /// Whatever got scanned, needs to have id and username
+        /// otherwise is just noise or an unknown qr code
+        ///
+        guard let userId = session.getId(from: data),
+              let username = try? session.extract(fact: .username, from: data) else {
+            let errorTitle = Localized.Scan.Error.invalid
+            statusSubject.send(.failed(.unknown(errorTitle)))
+            return
+        }
+
+        /// Make sure we are not processing a contact
+        /// that we already have
+        ///
+        if let alreadyContact = try? session.dbManager.fetchContacts(.init(id: [userId])).first {
+            /// Show error accordingly to the auth status
+            ///
+            if alreadyContact.authStatus == .friend {
+                statusSubject.send(.failed(.alreadyFriends(username)))
+            } else if [.requested, .verified].contains(alreadyContact.authStatus) {
+                statusSubject.send(.failed(.requestOpened))
+            } else {
+                let generalErrorTitle = Localized.Scan.Error.general
+                statusSubject.send(.failed(.unknown(generalErrorTitle)))
+            }
+
+            return
+        }
+
+        statusSubject.send(.success)
+        cameraSemaphoreSubject.send(false)
+
+        foundSubject.send(.init(
+            id: userId,
+            marshaled: data,
+            username: username,
+            email: try? session.extract(fact: .email, from: data),
+            phone: try? session.extract(fact: .phone, from: data),
+            nickname: nil,
+            photo: nil,
+            authStatus: .stranger,
+            isRecent: false,
+            createdAt: Date()
+        ))
+    }
+}
diff --git a/Sources/SearchFeature/ViewModels/SearchViewModel.swift b/Sources/SearchFeature/ViewModels/SearchViewModel.swift
deleted file mode 100644
index 5dae23c8883eeccafb3a66ac2c3dd339d45d1094..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/ViewModels/SearchViewModel.swift
+++ /dev/null
@@ -1,190 +0,0 @@
-import HUD
-import UIKit
-import Models
-import Combine
-import Defaults
-import XXModels
-import Countries
-import Foundation
-import Integration
-import PushFeature
-import CombineSchedulers
-import DependencyInjection
-
-enum SelectedFilter {
-    case username
-    case email
-    case phone
-
-    var prefix: String {
-        switch self {
-        case .username:
-            return "U"
-        case .phone:
-            return "P"
-        case .email:
-            return "E"
-        }
-    }
-}
-
-struct SearchViewState: Equatable {
-    var input: String = ""
-    var phoneInput: String = ""
-    var selectedFilter: SelectedFilter = .username
-    var country: Country = .fromMyPhone()
-}
-
-final class SearchViewModel {
-    @KeyObject(.dummyTrafficOn, defaultValue: false) var isCoverTrafficEnabled: Bool
-    @KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications
-    @KeyObject(.askedDummyTrafficOnce, defaultValue: false) var offeredCoverTraffic: Bool
-
-    @Dependency private var session: SessionType
-    @Dependency private var pushHandler: PushHandling
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var placeholderPublisher: AnyPublisher<Bool, Never> {
-        placeholderSubject.eraseToAnyPublisher()
-    }
-
-    var coverTrafficPublisher: AnyPublisher<Void, Never> {
-        coverTrafficSubject.eraseToAnyPublisher()
-    }
-
-    var statePublisher: AnyPublisher<SearchViewState, Never> {
-        stateSubject.eraseToAnyPublisher()
-    }
-
-    var successPublisher: AnyPublisher<Contact, Never> {
-        successSubject.eraseToAnyPublisher()
-    }
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue>
-    = DispatchQueue.global().eraseToAnyScheduler()
-
-    let itemsRelay = CurrentValueSubject<[Contact], Never>([])
-    private let successSubject = PassthroughSubject<Contact, Never>()
-    private let coverTrafficSubject = PassthroughSubject<Void, Never>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let placeholderSubject = CurrentValueSubject<Bool, Never>(true)
-    private let stateSubject = CurrentValueSubject<SearchViewState, Never>(.init())
-
-    func didAppear() {
-        verifyCoverTraffic()
-        verifyNotifications()
-    }
-
-    func didSelect(filter: SelectedFilter) {
-        stateSubject.value.selectedFilter = filter
-    }
-
-    func didInput(_ string: String) {
-        stateSubject.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines)
-    }
-
-    func didInputPhone(_ string: String) {
-        stateSubject.value.phoneInput = string.trimmingCharacters(in: .whitespacesAndNewlines)
-    }
-
-    func didChooseCountry(_ country: Country) {
-        stateSubject.value.country = country
-    }
-
-    func didEnableCoverTraffic() {
-        isCoverTrafficEnabled = true
-        session.setDummyTraffic(status: true)
-    }
-
-    func didTapSearch() {
-        hudSubject.send(.on(nil))
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                var content = self.stateSubject.value.selectedFilter.prefix
-
-                if self.stateSubject.value.selectedFilter == .phone {
-                    content += self.stateSubject.value.phoneInput + self.stateSubject.value.country.code
-                } else {
-                    content += self.stateSubject.value.input
-                }
-
-                try self.session.search(fact: content) { result in
-                    self.placeholderSubject.send(false)
-
-                    switch result {
-                    case .success(let searched):
-                        self.hudSubject.send(.none)
-                        self.itemsRelay.send([searched])
-                    case .failure(let error):
-                        self.hudSubject.send(.error(.init(with: error)))
-                        self.itemsRelay.send([])
-                    }
-                }
-            } catch {
-                self.hudSubject.send(.error(.init(with: error)))
-            }
-        }
-    }
-
-    private func verifyCoverTraffic() {
-        guard offeredCoverTraffic == false else {
-            return
-        }
-
-        offeredCoverTraffic = true
-        coverTrafficSubject.send()
-    }
-
-    private func verifyNotifications() {
-        guard pushNotifications == false else { return }
-
-        pushHandler.requestAuthorization { [weak self] result in
-            guard let self = self else { return }
-
-            switch result {
-            case .success(let granted):
-                if granted {
-                    DispatchQueue.main.async {
-                        UIApplication.shared.registerForRemoteNotifications()
-                    }
-                }
-
-                self.pushNotifications = granted
-            case .failure:
-                self.pushNotifications = false
-            }
-        }
-    }
-
-    func didSet(nickname: String, for contact: Contact) {
-        if var contact = try? session.dbManager.fetchContacts(.init(id: [contact.id])).first {
-            contact.nickname = nickname
-            _ = try? session.dbManager.saveContact(contact)
-        }
-    }
-
-    func didTapRequest(contact: Contact) {
-        hudSubject.send(.on(nil))
-        var contact = contact
-        contact.nickname = contact.username
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.session.add(contact)
-                self.hudSubject.send(.none)
-                self.successSubject.send(contact)
-            } catch {
-                self.hudSubject.send(.error(.init(with: error)))
-            }
-        }
-
-    }
-}
diff --git a/Sources/SearchFeature/Views/OverlayView.swift b/Sources/SearchFeature/Views/OverlayView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8242857716936fe49cf21a59bad280195c726165
--- /dev/null
+++ b/Sources/SearchFeature/Views/OverlayView.swift
@@ -0,0 +1,181 @@
+import UIKit
+import Shared
+
+final class OverlayView: UIView {
+    private let cropView = UIView()
+    private let scanViewLength = 266.0
+    private let maskLayer = CAShapeLayer()
+    private let topLeftLayer = CAShapeLayer()
+    private let topRightLayer = CAShapeLayer()
+    private let bottomLeftLayer = CAShapeLayer()
+    private let bottomRightLayer = CAShapeLayer()
+
+    init() {
+        super.init(frame: .zero)
+        backgroundColor = Asset.neutralDark.color.withAlphaComponent(0.5)
+
+        addSubview(cropView)
+
+        cropView.snp.makeConstraints {
+            $0.width.equalTo(scanViewLength)
+            $0.centerY.equalToSuperview().offset(-50)
+            $0.centerX.equalToSuperview()
+            $0.height.equalTo(scanViewLength)
+        }
+
+        maskLayer.fillRule = .evenOdd
+        layer.mask = maskLayer
+        layer.masksToBounds = true
+
+        [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
+            $0.strokeColor = Asset.brandPrimary.color.cgColor
+            $0.fillColor = UIColor.clear.cgColor
+            $0.lineWidth = 3.0
+            $0.lineCap = .round
+            layer.addSublayer($0)
+        }
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    override func layoutSubviews() {
+        super.layoutSubviews()
+
+        maskLayer.frame = bounds
+        let path = UIBezierPath(rect: bounds)
+        path.append(UIBezierPath(roundedRect: cropView.frame, cornerRadius: 30.0))
+        maskLayer.path = path.cgPath
+
+        topLeftLayer.frame = bounds
+        topRightLayer.frame = bounds
+        bottomRightLayer.frame = bounds
+        bottomLeftLayer.frame = bounds
+
+        topLeftLayer.path = topLeftPath()
+        topRightLayer.path = topRightPath()
+        bottomRightLayer.path = bottomRightPath()
+        bottomLeftLayer.path = bottomLeftPath()
+    }
+
+    func updateCornerColor(_ color: UIColor) {
+        [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
+            $0.strokeColor = color.cgColor
+        }
+    }
+
+    func topLeftPath() -> CGPath {
+        let path = UIBezierPath()
+
+        let vert0X = cropView.frame.minX - 15
+        let vert0Y = cropView.frame.minY + 45
+        let vert0 = CGPoint(x: vert0X, y: vert0Y)
+        path.move(to: vert0)
+
+        let vertNX = cropView.frame.minX - 15
+        let vertNY = cropView.frame.minY + 15
+        let vertN = CGPoint(x: vertNX, y: vertNY)
+        path.addLine(to: vertN)
+
+        let arcCenterX = cropView.frame.minX + 15
+        let arcCenterY = cropView.frame.minY + 15
+        let arcCenter = CGPoint(x: arcCenterX , y: arcCenterY)
+        path.addArc(center: arcCenter, startAngle: .pi)
+
+        let horizX = cropView.frame.minX + 45
+        let horizY = cropView.frame.minY - 15
+        let horiz = CGPoint(x: horizX, y: horizY)
+        path.addLine(to: horiz)
+
+        return path.cgPath
+    }
+
+    func topRightPath() -> CGPath {
+        let path = UIBezierPath()
+
+        let horiz0X = cropView.frame.maxX - 45
+        let horiz0Y = cropView.frame.minY - 15
+        let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
+        path.move(to: horiz0)
+
+        let horizNX = cropView.frame.maxX - 15
+        let horizNY = cropView.frame.minY - 15
+        let horizN = CGPoint(x: horizNX, y: horizNY)
+        path.addLine(to: horizN)
+
+        let arcCenterX = cropView.frame.maxX - 15
+        let arcCenterY = cropView.frame.minY + 15
+        let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
+        path.addArc(center: arcCenter, startAngle: 3 * .pi/2)
+
+        let vertX = cropView.frame.maxX + 15
+        let vertY = cropView.frame.minY + 45
+        let vert = CGPoint(x: vertX, y: vertY)
+        path.addLine(to: vert)
+
+        return path.cgPath
+    }
+
+    func bottomRightPath() -> CGPath {
+        let path = UIBezierPath()
+
+        let vert0X = cropView.frame.maxX + 15
+        let vert0Y = cropView.frame.maxY - 45
+        let vert0 = CGPoint(x: vert0X, y: vert0Y)
+        path.move(to: vert0)
+
+        let vertNX = cropView.frame.maxX + 15
+        let vertNY = cropView.frame.maxY - 15
+        let vertN = CGPoint(x: vertNX, y: vertNY)
+        path.addLine(to: vertN)
+
+        let arcCenterX = cropView.frame.maxX - 15
+        let arcCenterY = cropView.frame.maxY - 15
+        let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
+        path.addArc(center: arcCenter, startAngle: 0)
+
+        let horizX = cropView.frame.maxX - 45
+        let horizY = cropView.frame.maxY + 15
+        let horiz = CGPoint(x: horizX, y: horizY)
+        path.addLine(to: horiz)
+
+        return path.cgPath
+    }
+
+    func bottomLeftPath() -> CGPath {
+        let path = UIBezierPath()
+
+        let horiz0X = cropView.frame.minX + 45
+        let horiz0Y = cropView.frame.maxY + 15
+        let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
+        path.move(to: horiz0)
+
+        let horizNX = cropView.frame.minX + 15
+        let horizNY = cropView.frame.maxY + 15
+        let horizN = CGPoint(x: horizNX, y: horizNY)
+        path.addLine(to: horizN)
+
+        let arcCenterX = cropView.frame.minX + 15
+        let arcCenterY = cropView.frame.maxY - 15
+        let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
+        path.addArc(center: arcCenter, startAngle: .pi/2)
+
+        let vertX = cropView.frame.minX - 15
+        let vertY = cropView.frame.maxY - 45
+        let vert = CGPoint(x: vertX, y: vertY)
+        path.addLine(to: vert)
+
+        return path.cgPath
+    }
+}
+
+private extension UIBezierPath {
+    func addArc(center: CGPoint, startAngle: CGFloat) {
+        addArc(
+            withCenter: center,
+            radius: 30,
+            startAngle: startAngle,
+            endAngle: startAngle + .pi/2,
+            clockwise: true
+        )
+    }
+}
diff --git a/Sources/SearchFeature/Views/SearchCell.swift b/Sources/SearchFeature/Views/SearchCell.swift
deleted file mode 100644
index 08efd502958c8373949e09f83cc01101150a1ddd..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Views/SearchCell.swift
+++ /dev/null
@@ -1,82 +0,0 @@
-import UIKit
-import Shared
-
-final class SearchCell: UITableViewCell {
-    private let titleLabel = UILabel()
-    private let subtitleLabel = UILabel()
-    private let separatorView = UIView()
-    private let avatarView = AvatarView()
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-
-        selectionStyle = .none
-        backgroundColor = Asset.neutralWhite.color
-
-        titleLabel.textColor = Asset.neutralActive.color
-        subtitleLabel.textColor = Asset.neutralDisabled.color
-        separatorView.backgroundColor = Asset.neutralLine.color
-
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        subtitleLabel.font = Fonts.Mulish.regular.font(size: 12.0)
-
-        contentView.addSubview(titleLabel)
-        contentView.addSubview(avatarView)
-        contentView.addSubview(subtitleLabel)
-        contentView.addSubview(separatorView)
-
-        setupConstraints()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        titleLabel.text = nil
-        subtitleLabel.text = nil
-        avatarView.prepareForReuse()
-    }
-
-    func setup(
-        title: String,
-        subtitle: String,
-        avatarTitle: String,
-        avatarImage: Data?,
-        avatarSize: AvatarView.Size
-    ) {
-        titleLabel.text = title
-        subtitleLabel.text = subtitle
-        avatarView.setupProfile(
-            title: avatarTitle,
-            image: avatarImage,
-            size: avatarSize
-        )
-    }
-
-    private func setupConstraints() {
-        titleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(10)
-            $0.left.equalTo(avatarView.snp.right).offset(16)
-            $0.right.lessThanOrEqualToSuperview().offset(-20)
-        }
-
-        subtitleLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(3)
-            $0.left.equalTo(titleLabel)
-            $0.bottom.equalToSuperview().offset(-22)
-        }
-
-        avatarView.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(28)
-            $0.width.height.equalTo(48)
-            $0.bottom.equalToSuperview().offset(-16)
-        }
-
-        separatorView.snp.makeConstraints {
-            $0.height.equalTo(1)
-            $0.left.equalToSuperview().offset(24)
-            $0.right.equalToSuperview().offset(-24)
-            $0.bottom.equalToSuperview()
-        }
-    }
-}
diff --git a/Sources/SearchFeature/Views/SearchEmailView.swift b/Sources/SearchFeature/Views/SearchEmailView.swift
deleted file mode 100644
index 053c62592714b4822b94e063d3747d1a693b141f..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Views/SearchEmailView.swift
+++ /dev/null
@@ -1,28 +0,0 @@
-import UIKit
-import Shared
-import InputField
-
-final class SearchEmailView: UIView {
-    let inputField = InputField()
-
-    init() {
-        super.init(frame: .zero)
-
-        inputField.setup(
-            style: .regular,
-            title: "Email",
-            placeholder: "Email"
-        )
-
-        addSubview(inputField)
-
-        inputField.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.left.equalToSuperview().offset(15)
-            $0.right.equalToSuperview().offset(-15)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/SearchFeature/Views/SearchLeftEmptyView.swift b/Sources/SearchFeature/Views/SearchLeftEmptyView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..84c64c87a6096a16b2bbf793a9eadc1c95495324
--- /dev/null
+++ b/Sources/SearchFeature/Views/SearchLeftEmptyView.swift
@@ -0,0 +1,26 @@
+import UIKit
+import Shared
+
+final class SearchLeftEmptyView: UIView {
+    let titleLabel = UILabel()
+
+    init() {
+        super.init(frame: .zero)
+        backgroundColor = Asset.neutralWhite.color
+
+        titleLabel.numberOfLines = 0
+        titleLabel.textAlignment = .center
+        titleLabel.font = Fonts.Mulish.regular.font(size: 15.0)
+        titleLabel.textColor = Asset.neutralSecondaryAlternative.color
+
+        addSubview(titleLabel)
+
+        titleLabel.snp.makeConstraints {
+            $0.center.equalToSuperview()
+            $0.left.equalToSuperview().offset(20)
+            $0.right.equalToSuperview().offset(-20)
+        }
+    }
+
+    required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/SearchFeature/Views/SearchLeftPlaceholderView.swift b/Sources/SearchFeature/Views/SearchLeftPlaceholderView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7742ff1d64c56151a88ae9b5ae47e82ebb59dc1b
--- /dev/null
+++ b/Sources/SearchFeature/Views/SearchLeftPlaceholderView.swift
@@ -0,0 +1,74 @@
+import UIKit
+import Shared
+import Combine
+
+final class SearchLeftPlaceholderView: UIView {
+    let titleLabel = UILabel()
+    let subtitleWithInfo = TextWithInfoView()
+
+    var infoPublisher: AnyPublisher<Void, Never> {
+        infoSubject.eraseToAnyPublisher()
+    }
+
+    private let infoSubject = PassthroughSubject<Void, Never>()
+
+    init() {
+        super.init(frame: .zero)
+        backgroundColor = Asset.neutralWhite.color
+
+        let attrString = NSMutableAttributedString(
+            string: Localized.Ud.Search.Placeholder.title,
+            attributes: [
+                .foregroundColor: Asset.neutralDark.color,
+                .font: Fonts.Mulish.bold.font(size: 32.0)
+            ]
+        )
+
+        attrString.addAttribute(
+            name: .foregroundColor,
+            value: Asset.brandPrimary.color,
+            betweenCharacters: "#"
+        )
+
+        titleLabel.numberOfLines = 0
+        titleLabel.attributedText = attrString
+
+        let paragraph = NSMutableParagraphStyle()
+        paragraph.lineHeightMultiple = 1.3
+
+        subtitleWithInfo.setup(
+            text: Localized.Ud.Search.Placeholder.subtitle,
+            attributes: [
+                .paragraphStyle: paragraph,
+                .foregroundColor: Asset.neutralBody.color,
+                .font: Fonts.Mulish.regular.font(size: 16.0)
+            ],
+            didTapInfo: { [weak self] in
+                guard let self = self else { return }
+                self.infoSubject.send(())
+            }
+        )
+
+        addSubview(titleLabel)
+        addSubview(subtitleWithInfo)
+
+        setupConstraints()
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    private func setupConstraints() {
+        titleLabel.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(50)
+            $0.left.equalToSuperview().offset(32.5)
+            $0.right.equalToSuperview().offset(-32.5)
+        }
+
+        subtitleWithInfo.snp.makeConstraints {
+            $0.top.equalTo(titleLabel.snp.bottom).offset(30)
+            $0.left.equalToSuperview().offset(32.5)
+            $0.right.equalToSuperview().offset(-32.5)
+            $0.bottom.equalToSuperview()
+        }
+    }
+}
diff --git a/Sources/SearchFeature/Views/SearchLeftView.swift b/Sources/SearchFeature/Views/SearchLeftView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bbdda3f926818439e4c28d5beb020239a623048a
--- /dev/null
+++ b/Sources/SearchFeature/Views/SearchLeftView.swift
@@ -0,0 +1,71 @@
+import UIKit
+import Shared
+
+final class SearchLeftView: UIView {
+    let tableView = UITableView()
+    let inputStackView = UIStackView()
+    let inputField = SearchComponent()
+    let emptyView = SearchLeftEmptyView()
+    let countryButton = SearchCountryComponent()
+    let placeholderView = SearchLeftPlaceholderView()
+
+    init() {
+        super.init(frame: .zero)
+
+        emptyView.isHidden = true
+        backgroundColor = Asset.neutralWhite.color
+        tableView.backgroundColor = Asset.neutralWhite.color
+
+        inputStackView.spacing = 5
+        inputStackView.addArrangedSubview(countryButton)
+        inputStackView.addArrangedSubview(inputField)
+
+        addSubview(inputStackView)
+        addSubview(tableView)
+        addSubview(emptyView)
+        addSubview(placeholderView)
+
+        setupConstraints()
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    func updateUIForItem(item: SearchSegmentedControl.Item) {
+        countryButton.isHidden = item != .phone
+
+        let emptyTitle = Localized.Ud.Search.empty(item.written)
+        emptyView.titleLabel.text = emptyTitle
+
+        let inputFieldTitle = Localized.Ud.Search.input(item.written)
+        inputField.set(placeholder: inputFieldTitle, imageAtRight: nil)
+    }
+
+    private func setupConstraints() {
+        inputStackView.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(20)
+            $0.left.equalToSuperview().offset(20)
+            $0.right.equalToSuperview().offset(-20)
+        }
+
+        tableView.snp.makeConstraints {
+            $0.top.equalTo(inputField.snp.bottom).offset(20)
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.bottom.equalToSuperview()
+        }
+
+        emptyView.snp.makeConstraints {
+            $0.top.equalTo(inputField.snp.bottom).offset(20)
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.bottom.equalToSuperview()
+        }
+
+        placeholderView.snp.makeConstraints {
+            $0.top.equalTo(inputField.snp.bottom)
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.bottom.equalToSuperview()
+        }
+    }
+}
diff --git a/Sources/SearchFeature/Views/SearchPhoneView.swift b/Sources/SearchFeature/Views/SearchPhoneView.swift
deleted file mode 100644
index 1868cec6960091bcf08f9294740d8121b1a41bca..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Views/SearchPhoneView.swift
+++ /dev/null
@@ -1,28 +0,0 @@
-import UIKit
-import Shared
-import InputField
-
-final class SearchPhoneView: UIView {
-    let inputField = InputField()
-
-    init() {
-        super.init(frame: .zero)
-
-        inputField.setup(
-            style: .regular,
-            title: "Phone",
-            placeholder: "Phone"
-        )
-
-        addSubview(inputField)
-
-        inputField.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.left.equalToSuperview().offset(15)
-            $0.right.equalToSuperview().offset(-15)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/SearchFeature/Views/SearchPlaceholderView.swift b/Sources/SearchFeature/Views/SearchPlaceholderView.swift
deleted file mode 100644
index 84ae459286df757555853e8fd4eb7369c323d476..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Views/SearchPlaceholderView.swift
+++ /dev/null
@@ -1,65 +0,0 @@
-import UIKit
-import Shared
-
-final class SearchPlaceholderView: UIView {
-    let titleView = TextWithInfoView()
-    let didTapInfo: () -> Void
-
-    init(didTapInfo: @escaping () -> Void) {
-        self.didTapInfo = didTapInfo
-
-        super.init(frame: .zero)
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.lineSpacing = 5
-        paragraph.lineHeightMultiple = 1.0
-        paragraph.alignment = .center
-
-        titleView.setup(
-            text: "Your searches are anonymous.\nSearch information is never linked to your account or personally identifiable.",
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ],
-            didTapInfo: { didTapInfo() }
-        )
-
-        addSubview(titleView)
-
-        titleView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(60)
-            make.left.equalToSuperview().offset(60)
-            make.right.equalToSuperview().offset(-60)
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
-
-final class SearchEmptyView: UIView {
-    private let title = UILabel()
-
-    init() {
-        super.init(frame: .zero)
-
-        backgroundColor = Asset.neutralWhite.color
-
-        title.textColor = Asset.neutralBody.color
-        title.font = Fonts.Mulish.regular.font(size: 12.0)
-
-        addSubview(title)
-
-        title.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(20)
-            make.left.equalToSuperview().offset(30)
-            make.right.equalToSuperview().offset(-30)
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func set(filter: String) {
-        title.text = Localized.Ud.noneFound(filter)
-    }
-}
diff --git a/Sources/SearchFeature/Views/SearchQRView.swift b/Sources/SearchFeature/Views/SearchQRView.swift
deleted file mode 100644
index 4adae04506dd7f525df2563018aeedb3dba37b10..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Views/SearchQRView.swift
+++ /dev/null
@@ -1,28 +0,0 @@
-import UIKit
-import Shared
-import InputField
-
-final class SearchQRView: UIView {
-    let inputField = InputField()
-
-    init() {
-        super.init(frame: .zero)
-
-        inputField.setup(
-            style: .regular,
-            title: "QR",
-            placeholder: "QR"
-        )
-
-        addSubview(inputField)
-
-        inputField.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.left.equalToSuperview().offset(15)
-            $0.right.equalToSuperview().offset(-15)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/SearchFeature/Views/SearchRightView.swift b/Sources/SearchFeature/Views/SearchRightView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..363808754852969a75c3c811836faa9b40455034
--- /dev/null
+++ b/Sources/SearchFeature/Views/SearchRightView.swift
@@ -0,0 +1,116 @@
+import UIKit
+import Shared
+
+final class SearchRightView: UIView {
+    let statusLabel = UILabel()
+    let imageView = UIImageView()
+    let stackView = UIStackView()
+    let overlayView = OverlayView()
+    let animationView = DotAnimation()
+    let actionButton = CapsuleButton()
+
+    init() {
+        super.init(frame: .zero)
+        imageView.contentMode = .center
+        actionButton.setStyle(.brandColored)
+
+        statusLabel.numberOfLines = 0
+        statusLabel.textAlignment = .center
+        statusLabel.textColor = Asset.neutralWhite.color
+        statusLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+
+        stackView.spacing = 15
+        stackView.axis = .vertical
+        stackView.addArrangedSubview(animationView)
+        stackView.addArrangedSubview(imageView)
+        stackView.addArrangedSubview(statusLabel)
+        stackView.addArrangedSubview(actionButton)
+
+        imageView.isHidden = true
+        actionButton.isHidden = true
+        animationView.isHidden = false
+
+        addSubview(overlayView)
+        addSubview(stackView)
+
+        setupConstraints()
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    func update(status: ScanningStatus) {
+        var text: String
+
+        switch status {
+        case .reading, .processing:
+            imageView.isHidden = true
+            actionButton.isHidden = true
+            text = Localized.Scan.Status.reading
+            overlayView.updateCornerColor(Asset.brandPrimary.color)
+
+        case .success:
+            animationView.isHidden = true
+            actionButton.isHidden = true
+            imageView.isHidden = false
+            imageView.image = Asset.sharedSuccess.image
+            text = Localized.Scan.Status.success
+            overlayView.updateCornerColor(Asset.accentSuccess.color)
+
+        case .failed(let error):
+            animationView.isHidden = true
+            imageView.image = Asset.scanError.image
+            imageView.isHidden = false
+            overlayView.updateCornerColor(Asset.accentDanger.color)
+
+            switch error {
+            case .requestOpened:
+                text = Localized.Scan.Error.requested
+                actionButton.setTitle(Localized.Scan.requests, for: .normal)
+                actionButton.isHidden = false
+
+            case .alreadyFriends(let name):
+                text = Localized.Scan.Error.friends(name)
+                actionButton.setTitle(Localized.Scan.contact, for: .normal)
+                actionButton.isHidden = false
+
+            case .cameraPermission:
+                text = Localized.Scan.Error.denied
+                actionButton.setTitle(Localized.Scan.settings, for: .normal)
+                actionButton.isHidden = false
+
+            case .unknown(let content):
+                text = content
+            }
+        }
+
+        let attString = NSMutableAttributedString(string: text)
+        let paragraph = NSMutableParagraphStyle()
+        paragraph.alignment = .center
+        paragraph.lineHeightMultiple = 1.35
+
+        attString.addAttribute(.paragraphStyle, value: paragraph)
+        attString.addAttribute(.foregroundColor, value: Asset.neutralWhite.color)
+        attString.addAttribute(.font, value: Fonts.Mulish.regular.font(size: 14.0) as Any)
+
+        if text.contains("#") {
+            attString.addAttribute(name: .foregroundColor, value: Asset.brandPrimary.color, betweenCharacters: "#")
+        }
+
+        statusLabel.attributedText = attString
+    }
+
+    private func setupConstraints() {
+        overlayView.snp.makeConstraints {
+            $0.top.equalToSuperview()
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.bottom.equalToSuperview()
+        }
+
+        stackView.snp.makeConstraints {
+            $0.left.equalToSuperview().offset(57)
+            $0.right.equalToSuperview().offset(-57)
+            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-100)
+        }
+    }
+}
diff --git a/Sources/SearchFeature/Views/SearchSegmentedButton.swift b/Sources/SearchFeature/Views/SearchSegmentedButton.swift
index bc4c0f283059e19f3941731dea73f0f907c2dfcb..3b8e65fb1b778703a748626155d6f2b0b18ec94b 100644
--- a/Sources/SearchFeature/Views/SearchSegmentedButton.swift
+++ b/Sources/SearchFeature/Views/SearchSegmentedButton.swift
@@ -12,7 +12,6 @@ final class SearchSegmentedButton: UIControl {
 
         imageView.contentMode = .center
         titleLabel.textAlignment = .center
-        titleLabel.textColor = Asset.neutralWhite.color
         titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
 
         addSubview(titleLabel)
@@ -23,27 +22,16 @@ final class SearchSegmentedButton: UIControl {
 
     required init?(coder: NSCoder) { nil }
 
-    func setup(
-        title: String,
-        icon: UIImage,
-        iconColor: UIColor = Asset.neutralDisabled.color,
-        titleColor: UIColor = Asset.neutralDisabled.color
-    ) {
-        self.imageView.image = icon
-        self.titleLabel.text = title
-        self.imageView.tintColor = iconColor
-        self.titleLabel.textColor = titleColor
+    func setup(title: String, icon: UIImage) {
+        imageView.image = icon
+        titleLabel.text = title
+        imageView.tintColor = discreteColor
+        titleLabel.textColor = discreteColor
     }
 
-    func updateHighlighting(rate: CGFloat) {
-        let color = UIColor.fade(
-            from: discreteColor,
-            to: highlightColor,
-            pcent: rate
-        )
-
-        imageView.tintColor = color
-        titleLabel.textColor = color
+    func setSelected(_ bool: Bool) {
+        imageView.tintColor = bool ? highlightColor : discreteColor
+        titleLabel.textColor = bool ? highlightColor : discreteColor
     }
 
     private func setupConstraints() {
diff --git a/Sources/SearchFeature/Views/SearchSegmentedControl.swift b/Sources/SearchFeature/Views/SearchSegmentedControl.swift
index 212c78e6900ab3a4735d86e124b2970f905ffc79..6141360378cd659e89dbdf67c4180f4a0e9b4ce6 100644
--- a/Sources/SearchFeature/Views/SearchSegmentedControl.swift
+++ b/Sources/SearchFeature/Views/SearchSegmentedControl.swift
@@ -9,39 +9,42 @@ final class SearchSegmentedControl: UIView {
         case email
         case phone
         case qr
+
+        var written: String {
+            switch self {
+            case .qr: return "qr"
+            case .email: return "email"
+            case .phone: return "phone number"
+            case .username: return "username"
+            }
+        }
     }
 
     private let trackView = UIView()
     private let stackView = UIStackView()
+    private var leftConstraint: Constraint?
     private let trackIndicatorView = UIView()
-    private(set) var leftConstraint: Constraint?
-    private(set) var usernameButton = SearchSegmentedButton()
-    private(set) var emailButton = SearchSegmentedButton()
-    private(set) var phoneButton = SearchSegmentedButton()
-    private(set) var qrCodeButton = SearchSegmentedButton()
+    private let emailButton = SearchSegmentedButton()
+    private let phoneButton = SearchSegmentedButton()
+    private let qrCodeButton = SearchSegmentedButton()
+    private let usernameButton = SearchSegmentedButton()
 
     var actionPublisher: AnyPublisher<Item, Never> {
         actionSubject.eraseToAnyPublisher()
     }
 
     private var cancellables = Set<AnyCancellable>()
-    private let actionSubject = PassthroughSubject<Item, Never>()
+    private let actionSubject = CurrentValueSubject<Item, Never>(.username)
 
     init() {
         super.init(frame: .zero)
         trackView.backgroundColor = Asset.neutralLine.color
         trackIndicatorView.backgroundColor = Asset.brandPrimary.color
 
-        usernameButton.setup(
-            title: Localized.Ud.Tab.username,
-            icon: Asset.searchTabUsername.image,
-            iconColor: Asset.brandPrimary.color,
-            titleColor: Asset.brandPrimary.color
-        )
-
         qrCodeButton.setup(title: Localized.Ud.Tab.qr, icon: Asset.searchTabQr.image)
         emailButton.setup(title: Localized.Ud.Tab.email, icon: Asset.searchTabEmail.image)
         phoneButton.setup(title: Localized.Ud.Tab.phone, icon: Asset.searchTabPhone.image)
+        usernameButton.setup(title: Localized.Ud.Tab.username, icon: Asset.searchTabUsername.image)
 
         stackView.distribution = .fillEqually
         stackView.addArrangedSubview(usernameButton)
@@ -61,25 +64,38 @@ final class SearchSegmentedControl: UIView {
     required init?(coder: NSCoder) { nil }
 
     private func setupBindings() {
-        usernameButton
-            .publisher(for: .touchUpInside)
+        usernameButton.publisher(for: .touchUpInside)
             .sink { [unowned self] in actionSubject.send(.username) }
             .store(in: &cancellables)
 
-        emailButton
-            .publisher(for: .touchUpInside)
+        emailButton.publisher(for: .touchUpInside)
             .sink { [unowned self] in actionSubject.send(.email) }
             .store(in: &cancellables)
 
-        phoneButton
-            .publisher(for: .touchUpInside)
+        phoneButton.publisher(for: .touchUpInside)
             .sink { [unowned self] in actionSubject.send(.phone) }
             .store(in: &cancellables)
 
-        qrCodeButton
-            .publisher(for: .touchUpInside)
+        qrCodeButton.publisher(for: .touchUpInside)
             .sink { [unowned self] in actionSubject.send(.qr) }
             .store(in: &cancellables)
+
+        actionSubject
+            .removeDuplicates()
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in
+                let tabWidth = bounds.width / 4
+                if let leftConstraint = leftConstraint {
+                    leftConstraint.update(offset: tabWidth * CGFloat($0.rawValue))
+                    setNeedsLayout()
+                    UIView.animate(withDuration: 0.25) { self.layoutIfNeeded() }
+                }
+
+                qrCodeButton.setSelected($0 == .qr)
+                emailButton.setSelected($0 == .email)
+                phoneButton.setSelected($0 == .phone)
+                usernameButton.setSelected($0 == .username)
+            }.store(in: &cancellables)
     }
 
     private func setupConstraints() {
diff --git a/Sources/SearchFeature/Views/SearchUsernamePlaceholderView.swift b/Sources/SearchFeature/Views/SearchUsernamePlaceholderView.swift
deleted file mode 100644
index effcf60783cbbf8dfa1a296b7171d522ad82a52a..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Views/SearchUsernamePlaceholderView.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-import UIKit
-import Shared
-
-final class SearchUsernamePlaceholderView: UIView {
-    let titleLabel = UILabel()
-
-    init() {
-        super.init(frame: .zero)
-
-        titleLabel.text = "[SearchUsernamePlaceholderView]"
-
-        addSubview(titleLabel)
-
-        titleLabel.snp.makeConstraints {
-            $0.center.equalToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/SearchFeature/Views/SearchUsernameView.swift b/Sources/SearchFeature/Views/SearchUsernameView.swift
deleted file mode 100644
index 2dd66fcda8c4a775a72f5ba9dc5986c441d765fd..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Views/SearchUsernameView.swift
+++ /dev/null
@@ -1,36 +0,0 @@
-import UIKit
-import Shared
-import InputField
-
-final class SearchUsernameView: UIView {
-    let inputField = InputField()
-    let placeholderView = SearchUsernamePlaceholderView()
-
-    init() {
-        super.init(frame: .zero)
-
-        inputField.setup(
-            style: .regular,
-            title: "Username",
-            placeholder: "Username"
-        )
-
-        addSubview(inputField)
-        addSubview(placeholderView)
-
-        inputField.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.left.equalToSuperview().offset(15)
-            $0.right.equalToSuperview().offset(-15)
-        }
-
-        placeholderView.snp.makeConstraints {
-            $0.top.equalTo(inputField.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/SettingsFeature/Controllers/AccountDeleteController.swift b/Sources/SettingsFeature/Controllers/AccountDeleteController.swift
index 7eae36405a3bf673064d10fd743b648d99d0fc28..1dc36e2ef958cc0c7fba6177e621915d96cfc4cc 100644
--- a/Sources/SettingsFeature/Controllers/AccountDeleteController.swift
+++ b/Sources/SettingsFeature/Controllers/AccountDeleteController.swift
@@ -10,7 +10,7 @@ import DependencyInjection
 public final class AccountDeleteController: UIViewController {
     @KeyObject(.username, defaultValue: "") var username: String
 
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: SettingsCoordinating
 
     lazy private var screenView = AccountDeleteView()
diff --git a/Sources/SettingsFeature/Controllers/SettingsController.swift b/Sources/SettingsFeature/Controllers/SettingsController.swift
index e12674906cde540f35bbff5741f3ec5e816b4507..52e78e17a42c3933f671917aca89b186362f349a 100644
--- a/Sources/SettingsFeature/Controllers/SettingsController.swift
+++ b/Sources/SettingsFeature/Controllers/SettingsController.swift
@@ -8,7 +8,7 @@ import DependencyInjection
 import ScrollViewController
 
 public final class SettingsController: UIViewController {
-    @Dependency private var hud: HUDType
+    @Dependency private var hud: HUD
     @Dependency private var coordinator: SettingsCoordinating
     @Dependency private var statusBarController: StatusBarStyleControlling
 
diff --git a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift b/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift
index 0547bbbce7fd175563e525276af29f27c2a2df6c..db7e64016a317b8fe74fa496102437c771f500db 100644
--- a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift
+++ b/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift
@@ -17,7 +17,7 @@ final class AccountDeleteViewModel {
         deleting = true
 
         DispatchQueue.main.async { [weak self] in
-            self?.hudRelay.send(.on(nil))
+            self?.hudRelay.send(.on)
         }
 
         do {
diff --git a/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift b/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift
index 97c146a6aa7645ef12298e8bcc2dae2edf2072e2..9b985922329599ac17010bdd2d16c43dec81a454 100644
--- a/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift
+++ b/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift
@@ -110,7 +110,7 @@ final class SettingsViewModel {
     }
 
     private func pushNotifications(enable: Bool) {
-        hudRelay.send(.on(nil))
+        hudRelay.send(.on)
 
         if enable == true {
             pushHandler.requestAuthorization { [weak self] result in
diff --git a/Sources/Shared/AutoGenerated/Strings.swift b/Sources/Shared/AutoGenerated/Strings.swift
index 9f9fbfb27622937b8f189ffb9327c60ea9a681d0..ed861800297b7c53e92a0e83f19aa322de123ec4 100644
--- a/Sources/Shared/AutoGenerated/Strings.swift
+++ b/Sources/Shared/AutoGenerated/Strings.swift
@@ -218,7 +218,7 @@ public enum Localized {
       public static let login = Localized.tr("Localizable", "accountRestore.sftp.login")
       /// Password
       public static let password = Localized.tr("Localizable", "accountRestore.sftp.password")
-      /// Login to your server. Your credentials will be automatically and securley saved locally on your device.
+      /// Login to your server. Your credentials will be automatically and securely saved locally on your device.
       public static let subtitle = Localized.tr("Localizable", "accountRestore.sftp.subtitle")
       /// Login to your SFTP
       public static let title = Localized.tr("Localizable", "accountRestore.sftp.title")
@@ -430,6 +430,10 @@ public enum Localized {
       /// Cancel
       public static let cancel = Localized.tr("Localizable", "chatList.navigationBar.cancel")
     }
+    public enum Search {
+      /// Search chats
+      public static let title = Localized.tr("Localizable", "chatList.search.title")
+    }
     public enum Traffic {
       /// Not now
       public static let negative = Localized.tr("Localizable", "chatList.traffic.negative")
@@ -961,6 +965,14 @@ public enum Localized {
         public static func resent(_ p1: Any) -> String {
           return Localized.tr("Localizable", "requests.sent.toast.resent", String(describing: p1))
         }
+        /// Request couldn't be resent to %@
+        public static func resentFailed(_ p1: Any) -> String {
+          return Localized.tr("Localizable", "requests.sent.toast.resentFailed", String(describing: p1))
+        }
+        /// Request successfully sent to %@
+        public static func sent(_ p1: Any) -> String {
+          return Localized.tr("Localizable", "requests.sent.toast.sent", String(describing: p1))
+        }
       }
     }
   }
@@ -1201,8 +1213,6 @@ public enum Localized {
     public static func noneFound(_ p1: Any) -> String {
       return Localized.tr("Localizable", "ud.noneFound", String(describing: p1))
     }
-    /// User
-    public static let sectionTitle = Localized.tr("Localizable", "ud.sectionTitle")
     /// Search
     public static let title = Localized.tr("Localizable", "ud.title")
     public enum NicknameDrawer {
@@ -1214,8 +1224,6 @@ public enum Localized {
       public static let title = Localized.tr("Localizable", "ud.nicknameDrawer.title")
     }
     public enum Placeholder {
-      /// Searching is private by nature. The network cannot identify who a search request came from.
-      public static let title = Localized.tr("Localizable", "ud.placeholder.title")
       public enum Drawer {
         /// Got it
         public static let action = Localized.tr("Localizable", "ud.placeholder.drawer.action")
@@ -1237,6 +1245,24 @@ public enum Localized {
       /// Request Contact
       public static let title = Localized.tr("Localizable", "ud.requestDrawer.title")
     }
+    public enum Search {
+      /// Cancel search
+      public static let cancel = Localized.tr("Localizable", "ud.search.cancel")
+      /// There are no users with that %@.
+      public static func empty(_ p1: Any) -> String {
+        return Localized.tr("Localizable", "ud.search.empty", String(describing: p1))
+      }
+      /// Search by %@
+      public static func input(_ p1: Any) -> String {
+        return Localized.tr("Localizable", "ud.search.input", String(describing: p1))
+      }
+      public enum Placeholder {
+        /// Your searches are anonymous. Search information is never linked to your account or personally identifiable.
+        public static let subtitle = Localized.tr("Localizable", "ud.search.placeholder.subtitle")
+        /// Search for #friends# anonymously, add them to your #connections# to start a completely private messaging channel.
+        public static let title = Localized.tr("Localizable", "ud.search.placeholder.title")
+      }
+    }
     public enum Tab {
       /// Email
       public static let email = Localized.tr("Localizable", "ud.tab.email")
diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings
index 3aa07cedf9576e85634d4d61ec701efcee5229c5..dd95d4002c125c9caa98007bf7074c172bf5aaf8 100644
--- a/Sources/Shared/Resources/en.lproj/Localizable.strings
+++ b/Sources/Shared/Resources/en.lproj/Localizable.strings
@@ -25,6 +25,8 @@
 
 // ChatListFeature
 
+"chatList.search.title"
+= "Search chats";
 "chatList.navigationBar.cancel"
 = "Cancel";
 "chatList.title"
@@ -351,8 +353,12 @@
 = "Search for connections";
 "requests.sent.empty"
 = "You haven't sent any requests";
+"requests.sent.toast.sent"
+= "Request successfully sent to %@";
 "requests.sent.toast.resent"
 = "Request successfully resent to %@";
+"requests.sent.toast.resentFailed"
+= "Request couldn't be resent to %@";
 
 // RequestsFeature - Failed
 
@@ -948,24 +954,20 @@
 
 "ud.title"
 = "Search";
-"ud.placeholder.title"
-= "Searching is private by nature. The network cannot identify who a search request came from.";
+"ud.tab.username"
+= "Username";
+"ud.tab.email"
+= "Email";
+"ud.tab.phone"
+= "Phone";
+"ud.tab.qr"
+= "QR Code";
 "ud.placeholder.drawer.title"
 = "Search";
 "ud.placeholder.drawer.subtitle"
 = "You can search for users by their username, email, or phone number using the xx network’s #Anonymous Data Retrieval protocol# which keeps a user’s identity anonymous while requesting data. All sent requests contain salted hashes of what you are searching for. Raw data on emails, usernames, and phone numbers do not leave your phone.";
 "ud.placeholder.drawer.action"
 = "Got it";
-"ud.tab.phone"
-= "Phone";
-"ud.tab.email"
-= "Email";
-"ud.tab.username"
-= "Username";
-"ud.tab.qr"
-= "QR Code";
-"ud.sectionTitle"
-= "User";
 "ud.noneFound"
 = "There are no users with that %@.";
 "ud.requestDrawer.title"
@@ -985,6 +987,18 @@
 "ud.nicknameDrawer.save"
 = "Save";
 
+"ud.search.input"
+= "Search by %@";
+"ud.search.empty"
+= "There are no users with that %@.";
+"ud.search.cancel"
+= "Cancel search";
+
+"ud.search.placeholder.title"
+= "Search for #friends# anonymously, add them to your #connections# to start a completely private messaging channel.";
+"ud.search.placeholder.subtitle"
+= "Your searches are anonymous. Search information is never linked to your account or personally identifiable.";
+
 // LaunchFeature
 
 "launch.version.failed"
diff --git a/Sources/Shared/Views/AvatarCell.swift b/Sources/Shared/Views/AvatarCell.swift
new file mode 100644
index 0000000000000000000000000000000000000000..43f0e3fd6686464b6d66c31770627134e9c075c0
--- /dev/null
+++ b/Sources/Shared/Views/AvatarCell.swift
@@ -0,0 +1,187 @@
+import UIKit
+import Combine
+
+final class AvatarCellButton: UIControl {
+    let titleLabel = UILabel()
+    let imageView = UIImageView()
+
+    init() {
+        super.init(frame: .zero)
+        titleLabel.numberOfLines = 0
+        titleLabel.textAlignment = .right
+        titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
+
+        addSubview(imageView)
+        addSubview(titleLabel)
+
+        imageView.snp.makeConstraints {
+            $0.top.greaterThanOrEqualToSuperview()
+            $0.left.equalToSuperview()
+            $0.centerY.equalToSuperview()
+            $0.bottom.lessThanOrEqualToSuperview()
+        }
+
+        titleLabel.snp.makeConstraints {
+            $0.top.greaterThanOrEqualToSuperview()
+            $0.left.equalTo(imageView.snp.right).offset(5)
+            $0.centerY.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.width.equalTo(60)
+            $0.bottom.lessThanOrEqualToSuperview()
+        }
+    }
+
+    required init?(coder: NSCoder) { nil }
+}
+
+public final class AvatarCell: UITableViewCell {
+    let h1Label = UILabel()
+    let h2Label = UILabel()
+    let h3Label = UILabel()
+    let h4Label = UILabel()
+    let separatorView = UIView()
+    let avatarView = AvatarView()
+    let stackView = UIStackView()
+    let stateButton = AvatarCellButton()
+
+    var cancellables = Set<AnyCancellable>()
+    public var didTapStateButton: (() -> Void)!
+
+    public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+        selectedBackgroundView = UIView()
+        multipleSelectionBackgroundView = UIView()
+        backgroundColor = Asset.neutralWhite.color
+
+        h1Label.textColor = Asset.neutralActive.color
+        h2Label.textColor = Asset.neutralSecondaryAlternative.color
+        h3Label.textColor = Asset.neutralSecondaryAlternative.color
+        h4Label.textColor = Asset.neutralSecondaryAlternative.color
+
+        h1Label.font = Fonts.Mulish.semiBold.font(size: 14.0)
+        h2Label.font = Fonts.Mulish.regular.font(size: 14.0)
+        h3Label.font = Fonts.Mulish.regular.font(size: 14.0)
+        h4Label.font = Fonts.Mulish.regular.font(size: 14.0)
+
+        stackView.spacing = 4
+        stackView.axis = .vertical
+        
+        stackView.addArrangedSubview(h1Label)
+        stackView.addArrangedSubview(h2Label)
+        stackView.addArrangedSubview(h3Label)
+        stackView.addArrangedSubview(h4Label)
+
+        separatorView.backgroundColor = Asset.neutralLine.color
+
+        contentView.addSubview(stackView)
+        contentView.addSubview(avatarView)
+        contentView.addSubview(stateButton)
+        contentView.addSubview(separatorView)
+
+        setupConstraints()
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    public override func prepareForReuse() {
+        super.prepareForReuse()
+        h1Label.text = nil
+        h2Label.text = nil
+        h3Label.text = nil
+        h4Label.text = nil
+
+        stateButton.imageView.image = nil
+        stateButton.titleLabel.text = nil
+
+        avatarView.prepareForReuse()
+        cancellables.removeAll()
+    }
+
+    public func setup(
+        title: String,
+        image: Data?,
+        firstSubtitle: String? = nil,
+        secondSubtitle: String? = nil,
+        thirdSubtitle: String? = nil,
+        showSeparator: Bool = true,
+        sent: Bool = false
+    ) {
+        h1Label.text = title
+
+        if let firstSubtitle = firstSubtitle {
+            h2Label.isHidden = false
+            h2Label.text = firstSubtitle
+        } else {
+            h2Label.isHidden = true
+        }
+
+        if let secondSubtitle = secondSubtitle {
+            h3Label.isHidden = false
+            h3Label.text = secondSubtitle
+        } else {
+            h3Label.isHidden = true
+        }
+
+        if let thirdSubtitle = thirdSubtitle {
+            h4Label.isHidden = false
+            h4Label.text = thirdSubtitle
+        } else {
+            h4Label.isHidden = true
+        }
+
+        avatarView.setupProfile(title: title, image: image, size: .medium)
+        separatorView.alpha = showSeparator ? 1.0 : 0.0
+
+        cancellables.removeAll()
+
+        if sent {
+            stateButton.imageView.image = Asset.requestsResend.image
+            stateButton.titleLabel.text = Localized.Requests.Cell.requested
+            stateButton.titleLabel.textColor = Asset.brandPrimary.color
+
+            stateButton
+                .publisher(for: .touchUpInside)
+                .sink { [unowned self] in didTapStateButton() }
+                .store(in: &cancellables)
+        }
+    }
+
+    public func updateToResent() {
+        stateButton.imageView.image = Asset.requestsResent.image
+        stateButton.titleLabel.text = Localized.Requests.Cell.resent
+        stateButton.titleLabel.textColor = Asset.neutralWeak.color
+
+        cancellables.forEach { $0.cancel() }
+        cancellables.removeAll()
+    }
+
+    private func setupConstraints() {
+        avatarView.snp.makeConstraints {
+            $0.width.height.equalTo(36)
+            $0.left.equalToSuperview().offset(27)
+            $0.centerY.equalToSuperview()
+        }
+
+        stackView.snp.makeConstraints {
+            $0.top.equalTo(avatarView)
+            $0.left.equalTo(avatarView.snp.right).offset(14)
+            $0.right.lessThanOrEqualToSuperview().offset(-10)
+            $0.bottom.greaterThanOrEqualTo(avatarView)
+            $0.bottom.lessThanOrEqualToSuperview()
+        }
+
+        separatorView.snp.makeConstraints {
+            $0.height.equalTo(1)
+            $0.top.greaterThanOrEqualTo(stackView.snp.bottom).offset(10)
+            $0.left.equalToSuperview().offset(25)
+            $0.right.equalToSuperview()
+            $0.bottom.equalToSuperview()
+        }
+
+        stateButton.snp.makeConstraints {
+            $0.centerY.equalTo(stackView)
+            $0.right.equalToSuperview().offset(-24)
+        }
+    }
+}
diff --git a/Sources/Shared/Views/SearchComponent.swift b/Sources/Shared/Views/SearchComponent.swift
index 2e87cfbf6dceddcb50009a78b41fd2c0aa598ea5..9608aad71aaa56a5da852516405d069837a703c5 100644
--- a/Sources/Shared/Views/SearchComponent.swift
+++ b/Sources/Shared/Views/SearchComponent.swift
@@ -15,6 +15,10 @@ public final class SearchComponent: UIView {
         textSubject.eraseToAnyPublisher()
     }
 
+    public var returnPublisher: AnyPublisher<Void, Never> {
+        returnSubject.eraseToAnyPublisher()
+    }
+
     private var rightImage = Asset.sharedScan.image {
         didSet {
             rightButton.setImage(rightImage, for: .normal)
@@ -26,13 +30,59 @@ public final class SearchComponent: UIView {
     }
 
     private var cancellables = Set<AnyCancellable>()
-    private var rightSubject = PassthroughSubject<Void, Never>()
-    private var textSubject = PassthroughSubject<String, Never>()
-    private var isEditingSubject = CurrentValueSubject<Bool, Never>(false)
+    private let rightSubject = PassthroughSubject<Void, Never>()
+    private let textSubject = PassthroughSubject<String, Never>()
+    private let returnSubject = PassthroughSubject<Void, Never>()
+    private let isEditingSubject = CurrentValueSubject<Bool, Never>(false)
 
     public init() {
         super.init(frame: .zero)
-        setup()
+
+        containerView.layer.cornerRadius = 25
+        containerView.backgroundColor = Asset.neutralSecondary.color
+
+        leftImageView.image = Asset.lens.image
+        leftImageView.contentMode = .center
+        leftImageView.tintColor = Asset.neutralDisabled.color
+
+        rightButton.tintColor = Asset.neutralBody.color
+        rightButton.setImage(rightImage, for: .normal)
+        rightButton.setContentHuggingPriority(.required, for: .horizontal)
+        rightButton.setContentCompressionResistancePriority(.required, for: .horizontal)
+
+        inputField.delegate = self
+        inputField.textColor = Asset.neutralActive.color
+        inputField.font = Fonts.Mulish.regular.font(size: 16.0)
+
+        let attrPlaceholder
+        = NSAttributedString(
+            string: Localized.Shared.Search.placeholder,
+            attributes: [
+                .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
+                .foregroundColor: Asset.neutralWeak.color
+            ])
+
+        inputField.attributedPlaceholder = attrPlaceholder
+
+        inputField.textPublisher
+            .sink { [weak textSubject] in textSubject?.send($0) }
+            .store(in: &cancellables)
+
+        rightButton.publisher(for: .touchUpInside)
+            .sink { [weak rightSubject, self] in
+                if isEditingSubject.value == true {
+                    abortEditing()
+                } else {
+                    rightSubject?.send()
+                }
+            }.store(in: &cancellables)
+
+        addSubview(containerView)
+        containerView.addSubview(inputField)
+        containerView.addSubview(leftImageView)
+        containerView.addSubview(rightButton)
+
+        setupConstraints()
     }
 
     required init?(coder: NSCoder) { nil }
@@ -81,55 +131,6 @@ public final class SearchComponent: UIView {
         isEditingSubject.send(false)
     }
 
-    private func setup() {
-        containerView.layer.cornerRadius = 25
-        containerView.backgroundColor = Asset.neutralSecondary.color
-
-        leftImageView.image = Asset.lens.image
-        leftImageView.contentMode = .center
-        leftImageView.tintColor = Asset.neutralDisabled.color
-
-        rightButton.tintColor = Asset.neutralBody.color
-        rightButton.setImage(rightImage, for: .normal)
-        rightButton.setContentHuggingPriority(.required, for: .horizontal)
-        rightButton.setContentCompressionResistancePriority(.required, for: .horizontal)
-
-        inputField.delegate = self
-        inputField.textColor = Asset.neutralActive.color
-        inputField.font = Fonts.Mulish.regular.font(size: 16.0)
-
-        let attrPlaceholder
-            = NSAttributedString(
-                string: Localized.Shared.Search.placeholder,
-                attributes: [
-                    .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
-                    .foregroundColor: Asset.neutralWeak.color
-                ])
-
-        inputField.attributedPlaceholder = attrPlaceholder
-
-        inputField.textPublisher
-            .sink { [weak textSubject] in textSubject?.send($0) }
-            .store(in: &cancellables)
-
-        rightButton.publisher(for: .touchUpInside)
-            .sink { [weak rightSubject, self] in
-                if isEditingSubject.value == true {
-                    abortEditing()
-                } else {
-                    rightSubject?.send()
-                }
-            }.store(in: &cancellables)
-
-        addSubview(containerView)
-        containerView.addSubview(inputField)
-        containerView.addSubview(leftImageView)
-        containerView.addSubview(rightButton)
-
-        setupConstraints()
-        setupAccessibility()
-    }
-
     private func setupConstraints() {
         containerView.snp.makeConstraints {
             $0.top.equalToSuperview()
@@ -159,16 +160,17 @@ public final class SearchComponent: UIView {
         }
     }
 
-    private func setupAccessibility() {
-        inputField.accessibilityIdentifier = Localized.Accessibility.Shared.Search.textField
-        rightButton.accessibilityIdentifier = Localized.Accessibility.Shared.Search.rightButton
-    }
-
     public func textFieldDidBeginEditing(_ textField: UITextField) {
         rightButton.setImage(Asset.sharedCross.image, for: .normal)
         isEditingSubject.send(true)
     }
 
+    public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+        inputField.resignFirstResponder()
+        returnSubject.send(())
+        return true
+    }
+
     public func textFieldDidEndEditing(_ textField: UITextField) {
         rightButton.setImage(rightImage, for: .normal)
         isEditingSubject.send(false)
diff --git a/Sources/Shared/Views/SearchCountryComponent.swift b/Sources/Shared/Views/SearchCountryComponent.swift
new file mode 100644
index 0000000000000000000000000000000000000000..186c58b95981ce45a799c93e27c8a53530b16cf4
--- /dev/null
+++ b/Sources/Shared/Views/SearchCountryComponent.swift
@@ -0,0 +1,57 @@
+import UIKit
+
+public final class SearchCountryComponent: UIControl {
+    let flagLabel = UILabel()
+    let prefixLabel = UILabel()
+    let containerView = UIView()
+
+    public init() {
+        super.init(frame: .zero)
+
+        containerView.layer.cornerRadius = 25
+        containerView.backgroundColor = Asset.neutralSecondary.color
+
+        flagLabel.text = "🇺🇸"
+        prefixLabel.text = "+1"
+        prefixLabel.textColor = Asset.neutralDisabled.color
+        prefixLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+
+        addSubview(containerView)
+        containerView.addSubview(flagLabel)
+        containerView.addSubview(prefixLabel)
+
+        containerView.isUserInteractionEnabled = false
+
+        setupConstraints()
+        flagLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
+        prefixLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    public func setFlag(_ flag: String, prefix: String) {
+        flagLabel.text = flag
+        prefixLabel.text = prefix
+    }
+
+    private func setupConstraints() {
+        containerView.snp.makeConstraints {
+            $0.top.equalToSuperview()
+            $0.left.equalToSuperview()
+            $0.right.equalToSuperview()
+            $0.bottom.equalToSuperview()
+            $0.height.equalTo(50)
+        }
+
+        flagLabel.snp.makeConstraints {
+            $0.left.equalToSuperview().offset(13)
+            $0.centerY.equalToSuperview()
+        }
+
+        prefixLabel.snp.makeConstraints {
+            $0.left.equalTo(flagLabel.snp.right).offset(10)
+            $0.right.equalToSuperview().offset(-13)
+            $0.centerY.equalToSuperview()
+        }
+    }
+}
diff --git a/Sources/Shared/Views/SmallAvatarAndTitleCell.swift b/Sources/Shared/Views/SmallAvatarAndTitleCell.swift
deleted file mode 100644
index 4c37a96467729da2f4234ad646287a6127455f63..0000000000000000000000000000000000000000
--- a/Sources/Shared/Views/SmallAvatarAndTitleCell.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-import UIKit
-
-public final class SmallAvatarAndTitleCell: UITableViewCell {
-    let separatorView = UIView()
-    public let titleLabel = UILabel()
-    public let avatarView = AvatarView()
-
-    public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-
-        selectedBackgroundView = UIView()
-        multipleSelectionBackgroundView = UIView()
-        backgroundColor = Asset.neutralWhite.color
-
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        separatorView.backgroundColor = Asset.neutralLine.color
-
-        contentView.addSubview(titleLabel)
-        contentView.addSubview(avatarView)
-        contentView.addSubview(separatorView)
-
-        avatarView.snp.makeConstraints {
-            $0.width.height.equalTo(36)
-            $0.left.equalToSuperview().offset(27)
-            $0.centerY.equalToSuperview()
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.centerY.equalTo(avatarView)
-            $0.left.equalTo(avatarView.snp.right).offset(14)
-            $0.right.lessThanOrEqualToSuperview().offset(-10)
-        }
-
-        separatorView.snp.makeConstraints {
-            $0.height.equalTo(1)
-            $0.left.equalToSuperview().offset(25)
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func prepareForReuse() {
-        super.prepareForReuse()
-        titleLabel.text = nil
-        avatarView.prepareForReuse()
-    }
-}
diff --git a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
index f9de6930fb00ffc8a1befdc355e207f45741c671..0f086b282d6502bb27ba0b20989c093d0be98ab2 100644
--- a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -59,8 +59,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://git.xx.network/elixxir/client-ios-db.git",
       "state" : {
-        "revision" : "adf3c4b906870ecbd0d1d7208f0666939fd08665",
-        "version" : "1.0.5"
+        "revision" : "785e1f653ee5eaaaf58a82c8abbcda2174fbc27a",
+        "version" : "1.0.8"
       }
     },
     {