From ec2230198a1cce4e9bcfb528570c28c9ab66cc2d Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Sat, 27 Aug 2022 04:39:26 -0300
Subject: [PATCH] Continued migration

---
 .../Resources/GoogleService-Info.plist        |  64 +--
 App/client-ios/Resources/Info.plist           |   2 +-
 Package.swift                                 |   1 +
 .../ViewModels/GroupChatViewModel.swift       |  20 +-
 .../ViewModels/SingleChatViewModel.swift      |  25 +-
 .../ChatInputFeature/ChatInputReducer.swift   |   1 +
 .../ViewModel/ChatListViewModel.swift         |  11 +-
 .../ViewModels/ContactViewModel.swift         |  28 +-
 .../ViewModels/ContactListViewModel.swift     |   7 +-
 .../ViewModels/CreateGroupViewModel.swift     |  19 +-
 Sources/LaunchFeature/LaunchController.swift  |   2 +-
 Sources/LaunchFeature/LaunchViewModel.swift   | 512 +++++++++---------
 ...OnboardingEmailConfirmationViewModel.swift |   5 +-
 .../ViewModels/OnboardingEmailViewModel.swift |   5 +-
 ...OnboardingPhoneConfirmationViewModel.swift |   5 +-
 .../ViewModels/OnboardingPhoneViewModel.swift |   8 +-
 .../OnboardingUsernameViewModel.swift         | 274 +---------
 .../ViewModels/ProfileCodeViewModel.swift     |   8 +-
 .../ViewModels/ProfileEmailViewModel.swift    |   8 +-
 .../ViewModels/ProfilePhoneViewModel.swift    |   8 +-
 .../ViewModels/ProfileViewModel.swift         |   7 +-
 .../ViewModels/RequestsFailedViewModel.swift  |  10 +-
 .../RequestsReceivedViewModel.swift           |  28 +-
 .../ViewModels/RequestsSentViewModel.swift    |  10 +-
 .../ViewModels/RestoreViewModel.swift         |   4 +-
 .../ViewModels/ScanDisplayViewModel.swift     |   7 +-
 .../ViewModels/ScanViewModel.swift            |  10 +-
 .../ViewModels/SearchLeftViewModel.swift      |  82 +--
 .../ViewModels/SearchRightViewModel.swift     |  10 +-
 .../xcshareddata/swiftpm/Package.resolved     |  14 +-
 30 files changed, 491 insertions(+), 704 deletions(-)

diff --git a/App/client-ios/Resources/GoogleService-Info.plist b/App/client-ios/Resources/GoogleService-Info.plist
index 03e09469..676030ed 100644
--- a/App/client-ios/Resources/GoogleService-Info.plist
+++ b/App/client-ios/Resources/GoogleService-Info.plist
@@ -1,36 +1,36 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
-<dict>
-	<key>CLIENT_ID</key>
-	<string></string>
-	<key>REVERSED_CLIENT_ID</key>
-	<string></string>
-	<key>ANDROID_CLIENT_ID</key>
-	<string></string>
-	<key>API_KEY</key>
-	<string></string>
-	<key>GCM_SENDER_ID</key>
-	<string></string>
-	<key>PLIST_VERSION</key>
-	<string></string>
-	<key>BUNDLE_ID</key>
-	<string></string>
-	<key>PROJECT_ID</key>
-	<string></string>
-	<key>STORAGE_BUCKET</key>
-	<string></string>
-	<key>IS_ADS_ENABLED</key>
-	<false/>
-	<key>IS_ANALYTICS_ENABLED</key>
-	<false/>
-	<key>IS_APPINVITE_ENABLED</key>
-	<false/>
-	<key>IS_GCM_ENABLED</key>
-	<false/>
-	<key>IS_SIGNIN_ENABLED</key>
-	<false/>
-	<key>GOOGLE_APP_ID</key>
-	<string></string>
-</dict>
+    <dict>
+        <key>CLIENT_ID</key>
+        <string>662236151640-herpu89qikpfs9m4kvbi9bs5fpdji5de.apps.googleusercontent.com</string>
+        <key>REVERSED_CLIENT_ID</key>
+        <string>com.googleusercontent.apps.662236151640-herpu89qikpfs9m4kvbi9bs5fpdji5de</string>
+        <key>ANDROID_CLIENT_ID</key>
+        <string>662236151640-2ughgo2dvc59dm4o39b45lbdungp2mct.apps.googleusercontent.com</string>
+        <key>API_KEY</key>
+        <string>AIzaSyCbI2yQ7pbuVSRvraqanjGcS9CDrjD7lNU</string>
+        <key>GCM_SENDER_ID</key>
+        <string>662236151640</string>
+        <key>PLIST_VERSION</key>
+        <string>1</string>
+        <key>BUNDLE_ID</key>
+        <string>io.xxlabs.messenger</string>
+        <key>PROJECT_ID</key>
+        <string>xx-messenger-6e03e</string>
+        <key>STORAGE_BUCKET</key>
+        <string>xx-messenger-6e03e.appspot.com</string>
+        <key>IS_ADS_ENABLED</key>
+        <false></false>
+        <key>IS_ANALYTICS_ENABLED</key>
+        <false></false>
+        <key>IS_APPINVITE_ENABLED</key>
+        <true></true>
+        <key>IS_GCM_ENABLED</key>
+        <true></true>
+        <key>IS_SIGNIN_ENABLED</key>
+        <true></true>
+        <key>GOOGLE_APP_ID</key>
+        <string>1:662236151640:ios:24badb58ab07515d8cef2d</string>
+    </dict>
 </plist>
diff --git a/App/client-ios/Resources/Info.plist b/App/client-ios/Resources/Info.plist
index d8cb845b..ee4d813d 100644
--- a/App/client-ios/Resources/Info.plist
+++ b/App/client-ios/Resources/Info.plist
@@ -105,6 +105,6 @@
 	<key>UIViewControllerBasedStatusBarAppearance</key>
 	<true/>
 	<key>isReportingOptional</key>
-	<false/>
+	<true/>
 </dict>
 </plist>
diff --git a/Package.swift b/Package.swift
index c4e214a9..dfcf799c 100644
--- a/Package.swift
+++ b/Package.swift
@@ -499,6 +499,7 @@ let package = Package(
                 .target(name: "ReportingFeature"),
                 .target(name: "DependencyInjection"),
                 .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+                .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
                 .product(name: "CombineSchedulers", package: "combine-schedulers"),
                 .product(name: "XXLegacyDatabaseMigrator", package: "client-ios-db"),
             ],
diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
index 4e27cad3..4412378c 100644
--- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
@@ -10,6 +10,7 @@ import ToastFeature
 import DifferenceKit
 import ReportingFeature
 import DependencyInjection
+import XXMessengerClient
 
 import struct XXModels.Message
 import XXClient
@@ -20,25 +21,24 @@ enum GroupChatNavigationRoutes: Equatable {
 }
 
 final class GroupChatViewModel {
-    @Dependency var cMix: CMix
     @Dependency var database: Database
     @Dependency var sendReport: SendReport
     @Dependency var groupManager: GroupChat
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
     @Dependency var reportingStatus: ReportingStatus
     @Dependency var toastController: ToastController
 
     @KeyObject(.username, defaultValue: nil) var username: String?
 
     var myId: Data {
-        try! GetIdFromContact.live(userDiscovery.getContact())
+        try! messenger.ud.get()!.getContact().getId()
     }
 
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
     }
 
-    var reportPopupPublisher: AnyPublisher<Contact, Never> {
+    var reportPopupPublisher: AnyPublisher<XXModels.Contact, Never> {
         reportPopupSubject.eraseToAnyPublisher()
     }
 
@@ -54,7 +54,7 @@ final class GroupChatViewModel {
     private var stagedReply: Reply?
     private var cancellables = Set<AnyCancellable>()
     private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let reportPopupSubject = PassthroughSubject<Contact, Never>()
+    private let reportPopupSubject = PassthroughSubject<XXModels.Contact, Never>()
     private let replySubject = PassthroughSubject<(String, String), Never>()
     private let routesSubject = PassthroughSubject<GroupChatNavigationRoutes, Never>()
 
@@ -121,7 +121,7 @@ final class GroupChatViewModel {
                 ).asData()
             )
 
-            try cMix.waitForRoundResult(
+            try messenger.cMix.get()!.waitForRoundResult(
                 roundList: try report.encode(),
                 timeoutMS: 5_000,
                 callback: .init(handle: {
@@ -174,7 +174,7 @@ final class GroupChatViewModel {
                 ).asData()
             )
 
-            try cMix.waitForRoundResult(
+            try messenger.cMix.get()!.waitForRoundResult(
                 roundList: try report.encode(),
                 timeoutMS: 5_000,
                 callback: .init(handle: {
@@ -246,7 +246,7 @@ final class GroupChatViewModel {
         replySubject.send(getReplyContent(for: networkId))
     }
 
-    func report(contact: Contact, screenshot: UIImage, completion: @escaping () -> Void) {
+    func report(contact: XXModels.Contact, screenshot: UIImage, completion: @escaping () -> Void) {
         let report = Report(
             sender: .init(
                 userId: contact.id.base64EncodedString(),
@@ -285,13 +285,13 @@ final class GroupChatViewModel {
         }
     }
 
-    private func blockContact(_ contact: Contact) {
+    private func blockContact(_ contact: XXModels.Contact) {
         var contact = contact
         contact.isBlocked = true
         _ = try? database.saveContact(contact)
     }
 
-    private func presentReportConfirmation(contact: Contact) {
+    private func presentReportConfirmation(contact: XXModels.Contact) {
         let name = (contact.nickname ?? contact.username) ?? "the contact"
         toastController.enqueueToast(model: .init(
             title: "Your report has been sent and \(name) is now blocked.",
diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
index 5014a493..9b01d4d4 100644
--- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
@@ -13,6 +13,7 @@ import ToastFeature
 import DifferenceKit
 import ReportingFeature
 import DependencyInjection
+import XXMessengerClient
 
 import struct XXModels.Message
 import struct XXModels.FileTransfer
@@ -29,22 +30,20 @@ enum SingleChatNavigationRoutes: Equatable {
 }
 
 final class SingleChatViewModel: NSObject {
-    @Dependency var e2e: E2E
-    @Dependency var cMix: CMix
     @Dependency var logger: XXLogger
     @Dependency var database: Database
     @Dependency var sendReport: SendReport
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
     @Dependency var permissions: PermissionHandling
     @Dependency var toastController: ToastController
     @Dependency var transferManager: XXClient.FileTransfer
 
     @KeyObject(.username, defaultValue: nil) var username: String?
 
-    var contact: Contact { contactSubject.value }
+    var contact: XXModels.Contact { contactSubject.value }
     private var stagedReply: Reply?
     private var cancellables = Set<AnyCancellable>()
-    private let contactSubject: CurrentValueSubject<Contact, Never>
+    private let contactSubject: CurrentValueSubject<XXModels.Contact, Never>
     private let replySubject = PassthroughSubject<(String, String), Never>()
     private let navigationRoutes = PassthroughSubject<SingleChatNavigationRoutes, Never>()
     private let sectionsRelay = CurrentValueSubject<[ArraySection<ChatSection, Message>], Never>([])
@@ -59,10 +58,10 @@ final class SingleChatViewModel: NSObject {
     }
 
     var myId: Data {
-        try! GetIdFromContact.live(userDiscovery.getContact())
+        try! messenger.ud.get()!.getContact().getId()
     }
 
-    var contactPublisher: AnyPublisher<Contact, Never> { contactSubject.eraseToAnyPublisher() }
+    var contactPublisher: AnyPublisher<XXModels.Contact, Never> { contactSubject.eraseToAnyPublisher() }
     var replyPublisher: AnyPublisher<(String, String), Never> { replySubject.eraseToAnyPublisher() }
     var navigation: AnyPublisher<SingleChatNavigationRoutes, Never> { navigationRoutes.eraseToAnyPublisher() }
     var shouldDisplayEmptyView: AnyPublisher<Bool, Never> { sectionsRelay.map { $0.isEmpty }.eraseToAnyPublisher() }
@@ -79,7 +78,7 @@ final class SingleChatViewModel: NSObject {
         }.eraseToAnyPublisher()
     }
 
-    private func updateRecentState(_ contact: Contact) {
+    private func updateRecentState(_ contact: XXModels.Contact) {
         if contact.isRecent == true {
             var contact = contact
             contact.isRecent = false
@@ -91,7 +90,7 @@ final class SingleChatViewModel: NSObject {
         updateRecentState(contact)
     }
 
-    init(_ contact: Contact) {
+    init(_ contact: XXModels.Contact) {
         self.contactSubject = .init(contact)
         super.init()
 
@@ -215,7 +214,7 @@ final class SingleChatViewModel: NSObject {
                 reply = Reply(messageId: replyId, senderId: myId)
             }
 
-            let report = try e2e.send(
+            let report = try messenger.e2e.get()!.send(
                 messageType: 2,
                 recipientId: contact.id,
                 payload: Payload(
@@ -225,7 +224,7 @@ final class SingleChatViewModel: NSObject {
                 e2eParams: GetE2EParams.liveDefault()
             )
 
-            try cMix.waitForRoundResult(
+            try messenger.cMix.get()!.waitForRoundResult(
                 roundList: try report.encode(),
                 timeoutMS: 5_000,
                 callback: .init(handle: {
@@ -320,14 +319,14 @@ final class SingleChatViewModel: NSObject {
         do {
             message = try database.saveMessage(message)
 
-            let report = try e2e.send(
+            let report = try messenger.e2e.get()!.send(
                 messageType: 2,
                 recipientId: contact.id,
                 payload: Payload(text: message.text, reply: stagedReply).asData(),
                 e2eParams: GetE2EParams.liveDefault()
             )
 
-            try cMix.waitForRoundResult(
+            try messenger.cMix.get()!.waitForRoundResult(
                 roundList: try report.encode(),
                 timeoutMS: 5_000,
                 callback: .init(handle: {
diff --git a/Sources/ChatInputFeature/ChatInputReducer.swift b/Sources/ChatInputFeature/ChatInputReducer.swift
index 23bb35ba..170647ee 100644
--- a/Sources/ChatInputFeature/ChatInputReducer.swift
+++ b/Sources/ChatInputFeature/ChatInputReducer.swift
@@ -1,3 +1,4 @@
+import Foundation
 import ComposableArchitecture
 
 public let chatInputReducer = Reducer<ChatInputState, ChatInputAction, ChatInputEnvironment> { state, action, env in
diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
index e578d5f7..557223c0 100644
--- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
+++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
@@ -5,6 +5,7 @@ import Models
 import Combine
 import XXModels
 import Defaults
+import XXMessengerClient
 import ReportingFeature
 import DependencyInjection
 
@@ -18,16 +19,16 @@ enum SearchSection {
 
 enum SearchItem: Equatable, Hashable {
     case chat(ChatInfo)
-    case connection(Contact)
+    case connection(XXModels.Contact)
 }
 
-typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<SectionId, Contact>
+typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<SectionId, XXModels.Contact>
 typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
 
 final class ChatListViewModel {
     @Dependency var database: Database
+    @Dependency var messenger: Messenger
     @Dependency var groupManager: GroupChat
-    @Dependency var userDiscovery: UserDiscovery
     @Dependency var reportingStatus: ReportingStatus
 
     // TO REFACTOR:
@@ -36,7 +37,7 @@ final class ChatListViewModel {
     }
 
     var myId: Data {
-        try! GetIdFromContact.live(userDiscovery.getContact())
+        try! messenger.ud.get()!.getContact().getId()
     }
 
     var chatsPublisher: AnyPublisher<[ChatInfo], Never> {
@@ -189,7 +190,7 @@ final class ChatListViewModel {
         }
     }
 
-    func clear(_ contact: Contact) {
+    func clear(_ contact: XXModels.Contact) {
         _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id)))
     }
 
diff --git a/Sources/ContactFeature/ViewModels/ContactViewModel.swift b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
index c5c5d085..7f915791 100644
--- a/Sources/ContactFeature/ViewModels/ContactViewModel.swift
+++ b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
@@ -6,6 +6,7 @@ import XXModels
 import Defaults
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 import XXClient
 
@@ -19,14 +20,13 @@ struct ContactViewState: Equatable {
 }
 
 final class ContactViewModel {
-    @Dependency var e2e: E2E
     @Dependency var database: Database
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
     @Dependency var getFactsFromContact: GetFactsFromContact
 
     @KeyObject(.username, defaultValue: nil) var username: String?
 
-    var contact: Contact
+    var contact: XXModels.Contact
 
     var popToRootPublisher: AnyPublisher<Void, Never> { popToRootRelay.eraseToAnyPublisher() }
     var popPublisher: AnyPublisher<Void, Never> { popRelay.eraseToAnyPublisher() }
@@ -41,15 +41,15 @@ final class ContactViewModel {
     private let stateRelay = CurrentValueSubject<ContactViewState, Never>(.init())
 
     var myId: Data {
-        try! GetIdFromContact.live(userDiscovery.getContact())
+        try! messenger.ud.get()!.getContact().getId()
     }
 
     var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
 
-    init(_ contact: Contact) {
+    init(_ contact: XXModels.Contact) {
         self.contact = contact
 
-        let facts = try? getFactsFromContact(contact: contact.marshaled!)
+        let facts = try? getFactsFromContact(contact.marshaled!)
         let email = facts?.first(where: { $0.type == FactType.email.rawValue })?.fact
         let phone = facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact
 
@@ -73,7 +73,7 @@ final class ContactViewModel {
         hudRelay.send(.on)
 
         do {
-            try e2e.deleteRequest.partner(contact.id)
+            try messenger.e2e.get()!.deleteRequest.partnerId(contact.id)
             try database.deleteContact(contact)
 
             hudRelay.send(.none)
@@ -111,11 +111,11 @@ final class ContactViewModel {
             do {
                 try self.database.saveContact(self.contact)
 
-                var myFacts = try self.userDiscovery.getFacts()
+                var myFacts = try self.messenger.ud.get()!.getFacts()
                 myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue))
 
-                let _ = try self.e2e.requestAuthenticatedChannel(
-                    partnerContact: self.contact.id,
+                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+                    partner: XXClient.Contact.live(self.contact.marshaled!),
                     myFacts: myFacts
                 )
 
@@ -143,11 +143,11 @@ final class ContactViewModel {
             do {
                 try self.database.saveContact(self.contact)
 
-                var myFacts = try self.userDiscovery.getFacts()
+                var myFacts = try self.messenger.ud.get()!.getFacts()
                 myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue))
 
-                let _ = try self.e2e.requestAuthenticatedChannel(
-                    partnerContact: self.contact.marshaled!,
+                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+                    partner: XXClient.Contact.live(self.contact.marshaled!),
                     myFacts: myFacts
                 )
 
@@ -175,7 +175,7 @@ final class ContactViewModel {
             do {
                 try self.database.saveContact(self.contact)
 
-                let _ = try self.e2e.confirmReceivedRequest(partnerContact: self.contact.marshaled!)
+                let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: XXClient.Contact.live(self.contact.marshaled!))
 
                 self.contact.authStatus = .friend
                 try self.database.saveContact(self.contact)
diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
index f37561ac..ad365d34 100644
--- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
@@ -4,20 +4,21 @@ import XXModels
 import Defaults
 import ReportingFeature
 import DependencyInjection
+import XXMessengerClient
 
 import Foundation
 import XXClient
 
 final class ContactListViewModel {
     @Dependency var database: Database
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
     @Dependency private var reportingStatus: ReportingStatus
 
     var myId: Data {
-        try! GetIdFromContact.live(userDiscovery.getContact())
+        try! messenger.ud.get()!.getContact().getId()
     }
 
-    var contacts: AnyPublisher<[Contact], Never> {
+    var contacts: AnyPublisher<[XXModels.Contact], Never> {
         let query = Contact.Query(
             authStatus: [.friend],
             isBlocked: reportingStatus.isEnabled() ? false : nil,
diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
index 06dabcb0..019e1333 100644
--- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
@@ -8,6 +8,7 @@ import XXClient
 import ReportingFeature
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 final class CreateGroupViewModel {
     @KeyObject(.username, defaultValue: "") var username: String
@@ -16,20 +17,20 @@ final class CreateGroupViewModel {
 
     @Dependency var database: Database
     @Dependency var groupManager: GroupChat
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
     @Dependency var reportingStatus: ReportingStatus
 
     // MARK: Properties
 
     var myId: Data {
-        try! GetIdFromContact.live(userDiscovery.getContact())
+        try! messenger.ud.get()!.getContact().getId()
     }
 
-    var selected: AnyPublisher<[Contact], Never> {
+    var selected: AnyPublisher<[XXModels.Contact], Never> {
         selectedContactsRelay.eraseToAnyPublisher()
     }
 
-    var contacts: AnyPublisher<[Contact], Never> {
+    var contacts: AnyPublisher<[XXModels.Contact], Never> {
         contactsRelay.eraseToAnyPublisher()
     }
 
@@ -44,12 +45,12 @@ final class CreateGroupViewModel {
     var backgroundScheduler: AnySchedulerOf<DispatchQueue>
     = DispatchQueue.global().eraseToAnyScheduler()
 
-    private var allContacts = [Contact]()
+    private var allContacts = [XXModels.Contact]()
     private var cancellables = Set<AnyCancellable>()
     private let infoRelay = PassthroughSubject<GroupInfo, Never>()
     private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let contactsRelay = CurrentValueSubject<[Contact], Never>([])
-    private let selectedContactsRelay = CurrentValueSubject<[Contact], Never>([])
+    private let contactsRelay = CurrentValueSubject<[XXModels.Contact], Never>([])
+    private let selectedContactsRelay = CurrentValueSubject<[XXModels.Contact], Never>([])
 
     // MARK: Lifecycle
 
@@ -72,7 +73,7 @@ final class CreateGroupViewModel {
 
     // MARK: Public
 
-    func didSelect(contact: Contact) {
+    func didSelect(contact: XXModels.Contact) {
         if selectedContactsRelay.value.contains(contact) {
             selectedContactsRelay.value.removeAll { $0.username == contact.username }
         } else {
@@ -93,7 +94,7 @@ final class CreateGroupViewModel {
         )
     }
 
-    func create(name: String, welcome: String?, members: [Contact]) {
+    func create(name: String, welcome: String?, members: [XXModels.Contact]) {
         hudRelay.send(.on)
 
         backgroundScheduler.schedule { [weak self] in
diff --git a/Sources/LaunchFeature/LaunchController.swift b/Sources/LaunchFeature/LaunchController.swift
index a17c9fc3..651da26c 100644
--- a/Sources/LaunchFeature/LaunchController.swift
+++ b/Sources/LaunchFeature/LaunchController.swift
@@ -144,7 +144,7 @@ public final class LaunchController: UIViewController {
             negativeButton.publisher(for: .touchUpInside)
                 .sink { [unowned self] in
                     blocker.hideWindow()
-                    viewModel.versionApproved()
+                    viewModel.continueWithInitialization()
                 }.store(in: &cancellables)
 
             vStack.addArrangedSubview(negativeButton)
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index a520214e..5178c2e2 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -16,9 +16,11 @@ import DependencyInjection
 
 import XXClient
 import struct XXClient.FileTransfer
+import class XXClient.Cancellable
 
 import XXDatabase
 import XXLegacyDatabaseMigrator
+import XXMessengerClient
 
 struct Update {
     let content: String
@@ -36,17 +38,14 @@ enum LaunchRoute {
 
 final class LaunchViewModel {
     @Dependency var database: Database
-    @Dependency var cMixManager: CMixManager
     @Dependency var versionChecker: VersionChecker
     @Dependency var dropboxService: DropboxInterface
     @Dependency var fetchBannedList: FetchBannedList
     @Dependency var reportingStatus: ReportingStatus
     @Dependency var toastController: ToastController
     @Dependency var keychainHandler: KeychainHandling
-    @Dependency var getIdFromContact: GetIdFromContact
     @Dependency var processBannedList: ProcessBannedList
     @Dependency var permissionHandler: PermissionHandling
-    @Dependency var getFactsFromContact: GetFactsFromContact
 
     @KeyObject(.username, defaultValue: nil) var username: String?
     @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool
@@ -55,6 +54,8 @@ final class LaunchViewModel {
         hudSubject.eraseToAnyPublisher()
     }
 
+    var authCallbacksCancellable: Cancellable?
+
     var routePublisher: AnyPublisher<LaunchRoute, Never> {
         routeSubject.eraseToAnyPublisher()
     }
@@ -80,7 +81,7 @@ final class LaunchViewModel {
             self.versionChecker().sink { [unowned self] in
                 switch $0 {
                 case .upToDate:
-                    self.versionApproved()
+                    self.updateBannedList { self.continueWithInitialization() }
                 case .failure(let error):
                     self.versionFailed(error: error)
                 case .updateRequired(let info):
@@ -92,58 +93,110 @@ final class LaunchViewModel {
         }
     }
 
-    func versionApproved() {
-        updateBannedList { [weak self] in
-            guard let self = self else { return }
+    func continueWithInitialization() {
+        do {
+            try self.setupDatabase()
 
-            _ = try? SetLogLevel.live(.trace)
+            try SetLogLevel.live(.trace)
 
-            try! self.setupDatabase()
+            guard let certPath = Bundle.module.path(forResource: "cmix.rip", ofType: "crt"),
+                  let contactFilePath = Bundle.module.path(forResource: "udContact", ofType: "bin") else {
+                fatalError("Couldn't retrieve alternative UD credentials")
+            }
 
-            if self.cMixManager.hasStorage(), self.username != nil {
-                self.checkBiometrics { [weak self] in
-                    guard let self = self else { return }
+            let address = "46.101.98.49:18001"
+            let cert = try Data(contentsOf: URL(fileURLWithPath: certPath))
+            let contactFile = try Data(contentsOf: URL(fileURLWithPath: contactFilePath))
+
+            var environment: MessengerEnvironment = .live()
+            environment.udCert = cert
+            environment.udAddress = address
+            environment.udContact = contactFile
+            environment.ndfEnvironment = .mainnet
+
+            let messenger = Messenger.live(environment)
+
+            DependencyInjection.Container.shared.register(messenger)
+
+            if messenger.isLoaded() == false {
+                if messenger.isCreated() == false {
+                    try messenger.create()
+                }
+
+                try messenger.load()
+            }
+
+            try messenger.start()
 
+            authCallbacksCancellable = messenger.registerAuthCallbacks(
+                AuthCallbacks(handle: {
                     switch $0 {
-                    case .success(false):
-                        break
-                    case .success(true):
-                        do {
-                            //UpdateCommonErrors.live(jsonFile: ) DOWNLOAD THE JSON FROM THE REPO
-
-                            let cMix = try self.initCMix()
-                            try cMix.startNetworkFollower(timeoutMS: 10_000)
-                            guard cMix.waitForNetwork(timeoutMS: 30_000) else {
-                                fatalError("^^^ cMix.waitForNetwork returned FALSE")
-                            }
+                    case .confirm(contact: let contact, receptionId: _, ephemeralId: _, roundId: _):
+                        self.handleConfirm(from: contact)
+                    case .request(contact: let contact, receptionId: _, ephemeralId: _, roundId: _):
+                        self.handleDirectRequest(from: contact)
+                    case .reset(contact: let contact, receptionId: _, ephemeralId: _, roundId: _):
+                        self.handleReset(from: contact)
+                    }
+                })
+            )
+
+            if messenger.isConnected() == false {
+                try messenger.connect()
+            }
+
+            try generateGroupManager(messenger: messenger)
 
-                            let e2e = try self.initE2E(cMix)
-                            _ = try self.initUD(alternative: true, e2e: e2e, cMix: cMix)
-                            _ = try self.initGroupManager(e2e)
-                            _ = try self.initTransferManager(e2e)
-                            _ = try self.initDummyTrafficManager(e2e)
+            try generateTrafficManager(messenger: messenger)
 
-                            self.hudSubject.send(.none)
-                            self.routeSubject.send(.chats)
-                        } catch {
-                            self.hudSubject.send(.error(.init(with: error)))
+            try generateTransferManager(messenger: messenger)
+
+            if messenger.isLoggedIn() == false {
+                if try messenger.isRegistered() == false {
+                    hudSubject.send(.none)
+                    routeSubject.send(.onboarding)
+                } else {
+                    hudSubject.send(.none)
+                    checkBiometrics { [weak self] bioResult in
+
+                        switch bioResult {
+                        case .success(let granted):
+                            if granted {
+                                try! messenger.logIn()
+                                self?.routeSubject.send(.chats)
+                            } else {
+                                // WHAT SHOULD HAPPEN HERE?
+                            }
+                        case .failure(let error):
+                            print(">>> Bio auth failed: \(error.localizedDescription)")
                         }
-                    case .failure(let error):
-                        self.hudSubject.send(.error(.init(with: error)))
                     }
                 }
             } else {
-                self.cleanUp()
-                self.presentOnboardingFlow()
+                hudSubject.send(.none)
+                checkBiometrics { [weak self] bioResult in
+                    switch bioResult {
+                    case .success(let granted):
+                        if granted {
+                            self?.routeSubject.send(.chats)
+                        } else {
+                            // WHAT SHOULD HAPPEN HERE?
+                        }
+                    case .failure(let error):
+                        print(">>> Bio auth failed: \(error.localizedDescription)")
+                    }
+                }
             }
+        } catch {
+            print(">>> Initialization couldn't be completed: \(error.localizedDescription)")
         }
     }
 
     private func cleanUp() {
-        try? cMixManager.remove()
-        try? keychainHandler.clear()
-
-        dropboxService.unlink()
+//        try? cMixManager.remove()
+//        try? keychainHandler.clear()
+//
+//        dropboxService.unlink()
     }
 
     private func presentOnboardingFlow() {
@@ -189,7 +242,7 @@ final class LaunchViewModel {
         DependencyInjection.Container.shared.register(database)
     }
 
-    func getContactWith(userId: Data) -> Contact? {
+    func getContactWith(userId: Data) -> XXModels.Contact? {
         let query = Contact.Query(
             id: [userId],
             isBlocked: reportingStatus.isEnabled() ? false : nil,
@@ -267,102 +320,80 @@ final class LaunchViewModel {
         }
     }
 
-    private func initCMix() throws -> CMix {
-        if let cMix = try? DependencyInjection.Container.shared.resolve() as CMix {
-            return cMix
+    private func updateBannedList(completion: @escaping () -> Void) {
+        fetchBannedList { result in
+            switch result {
+            case .failure(_):
+                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+                    self.updateBannedList(completion: completion)
+                }
+            case .success(let data):
+                self.processBannedList(data, completion: completion)
+            }
         }
-
-        let cMix = try cMixManager.load()
-        DependencyInjection.Container.shared.register(cMix)
-        return cMix
     }
 
-    private func initE2E(_ cMix: CMix) throws -> E2E {
-        if let e2e = try? DependencyInjection.Container.shared.resolve() as E2E {
-            return e2e
-        }
-
-        let e2e = try Login.live(
-            cMixId: cMix.getId(),
-            authCallbacks: .init(
-                handle: {
-                    switch $0 {
-                    case .reset(contact: let contact, receptionId: _, ephemeralId: _, roundId: _):
-                        self.handleReset(from: contact)
-                    case .confirm(contact: let contact, receptionId: _, ephemeralId: _, roundId: _):
-                        self.handleConfirm(from: contact)
-                    case .request(contact: let contact, receptionId: _, ephemeralId: _, roundId: _):
-                        self.handleRequest(from: contact)
+    private func processBannedList(_ data: Data, completion: @escaping () -> Void) {
+        processBannedList(
+            data: data,
+            forEach: { result in
+                switch result {
+                case .success(let userId):
+                    let query = Contact.Query(id: [userId])
+                    if var contact = try! database.fetchContacts(query).first {
+                        if contact.isBanned == false {
+                            contact.isBanned = true
+                            try! database.saveContact(contact)
+                            self.enqueueBanWarning(contact: contact)
+                        }
+                    } else {
+                        try! database.saveContact(.init(id: userId, isBanned: true))
                     }
+
+                case .failure(_):
+                    break
                 }
-            ),
-            identity: cMix.makeLegacyReceptionIdentity()
-        )
+            },
+            completion: { result in
+                switch result {
+                case .failure(_):
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+                        self.updateBannedList(completion: completion)
+                    }
 
-        try e2e.registerListener(
-            senderId: nil,
-            messageType: 2,
-            callback: .init(handle: { message in
-                print(message.timestamp)
-            })
+                case .success(_):
+                    completion()
+                }
+            }
         )
-
-        DependencyInjection.Container.shared.register(e2e)
-        return e2e
     }
 
-    private func initUD(alternative: Bool, e2e: E2E, cMix: CMix) throws -> UserDiscovery {
-        if let userDiscovery = try? DependencyInjection.Container.shared.resolve() as UserDiscovery {
-            return userDiscovery
-        }
-
-        guard let certPath = Bundle.module.path(forResource: "cmix.rip", ofType: "crt"),
-              let contactFilePath = Bundle.module.path(forResource: "udContact", ofType: "bin") else {
-            fatalError("Couldn't retrieve alternative UD credentials")
-        }
-
-        let address = alternative ? "46.101.98.49:18001" : e2e.getUdAddressFromNdf()
-        let cert = alternative ? try Data(contentsOf: URL(fileURLWithPath: certPath)) : e2e.getUdCertFromNdf()
-        let contactFile = alternative ? try Data(contentsOf: URL(fileURLWithPath: contactFilePath)) : try e2e.getUdContactFromNdf()
-
-        let userDiscovery = try NewOrLoadUd.live(
-            params: .init(
-                e2eId: e2e.getId(),
-                username: username!,
-                registrationValidationSignature: cMix.getReceptionRegistrationValidationSignature(),
-                cert: cert,
-                contactFile: contactFile,
-                address: address
-            ),
-            follower: .init(handle: { cMix.networkFollowerStatus() })
-        )
-
-        DependencyInjection.Container.shared.register(userDiscovery)
-        return userDiscovery
+    private func enqueueBanWarning(contact: XXModels.Contact) {
+        let name = (contact.nickname ?? contact.username) ?? "One of your contacts"
+        toastController.enqueueToast(model: .init(
+            title: "\(name) has been banned for offensive content.",
+            leftImage: Asset.requestSentToaster.image
+        ))
     }
+}
 
-    private func initGroupManager(_ e2e: E2E) throws -> GroupChat {
-        if let groupManager = try? DependencyInjection.Container.shared.resolve() as GroupChat {
-            return groupManager
-        }
-
-        let groupManager = try NewGroupChat.live(
-            e2eId: e2e.getId(),
-            groupRequest: .init(handle: { print($0) }),
-            groupChatProcessor: .init(handle: { print($0) })
+extension LaunchViewModel {
+    private func generateGroupManager(messenger: Messenger) throws {
+        let manager = try NewGroupChat.live(
+            e2eId: messenger.e2e()!.getId(),
+            groupRequest: .init(handle: { [weak self] group in
+                guard let self = self else { return }
+                self.handleGroupRequest(from: group)
+            }),
+            groupChatProcessor: .init(handle: { print($0) }) // What is this?
         )
 
-        DependencyInjection.Container.shared.register(groupManager)
-        return groupManager
+        DependencyInjection.Container.shared.register(manager)
     }
 
-    private func initTransferManager(_ e2e: E2E) throws -> XXClient.FileTransfer {
-        if let transferManager = try? DependencyInjection.Container.shared.resolve() as FileTransfer {
-            return transferManager
-        }
-
-        let transferManager = try InitFileTransfer.live(
-            e2eId: e2e.getId(),
+    private func generateTransferManager(messenger: Messenger) throws {
+        let manager = try InitFileTransfer.live(
+            e2eId: messenger.e2e()!.getId(),
             callback: .init(handle: {
                 switch $0 {
                 case .success(let receivedFile):
@@ -373,186 +404,141 @@ final class LaunchViewModel {
             })
         )
 
-        DependencyInjection.Container.shared.register(transferManager)
-        return transferManager
+        DependencyInjection.Container.shared.register(manager)
     }
 
-    private func initDummyTrafficManager(_ e2e: E2E) throws -> DummyTraffic {
-        if let dummyTrafficManager = try? DependencyInjection.Container.shared.resolve() as DummyTraffic {
-            return dummyTrafficManager
-        }
-
-        let dummyTrafficManager = try NewDummyTrafficManager.live(
-            cMixId: e2e.getId(),
+    private func generateTrafficManager(messenger: Messenger) throws {
+        let manager = try NewDummyTrafficManager.live(
+            cMixId: messenger.e2e()!.getId(),
             maxNumMessages: 1,
             avgSendDeltaMS: 1,
             randomRangeMS: 1
         )
 
-        DependencyInjection.Container.shared.register(dummyTrafficManager)
-        return dummyTrafficManager
-    }
-
-    private func handleRequest(from contact: Data) {
-        guard isRepeatedRequest(from: contact) == false else { return }
-
-        do {
-            let facts = try? getFactsFromContact(contact: contact)
-
-            let model = try self.database.saveContact(.init(
-                id: try getIdFromContact(contact),
-                marshaled: contact,
-                username: facts?.first(where: { $0.type == FactType.username.rawValue })?.fact,
-                email: facts?.first(where: { $0.type == FactType.email.rawValue })?.fact,
-                phone: facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact,
-                nickname: nil,
-                photo: nil,
-                authStatus: .verificationInProgress,
-                isRecent: true,
-                createdAt: Date()
-            ))
-
-            if model.email == nil, model.phone == nil {
-                performLookup(on: model)
-            } else {
-                //performSearch()
-            }
-        } catch {
-            print("^^^ Request processing failed: \(error.localizedDescription)")
-        }
+        DependencyInjection.Container.shared.register(manager)
     }
+}
 
-    private func isRepeatedRequest(from contact: Data) -> Bool {
-        if let id = try? getIdFromContact(contact),
-           let _ = try? self.database.fetchContacts(Contact.Query(id: [id])).first {
-            return true
+extension LaunchViewModel {
+    private func handleDirectRequest(from contact: XXClient.Contact) {
+        guard let id = try? contact.getId() else {
+            fatalError("Couldn't extract ID from contact request arrived.")
         }
 
-        return false
-    }
-
-    private func performLookup(on contact: Contact) {
-        guard let e2e = try? DependencyInjection.Container.shared.resolve() as E2E,
-              let userDiscovery = try? DependencyInjection.Container.shared.resolve() as UserDiscovery else {
-            print("^^^ couldn't resolve UD/E2E to process lookup")
+        if let _ = try? database.fetchContacts(.init(id: [id])).first {
+            print(">>> Tried to handle request from pre-existing contact.")
             return
         }
 
-        do {
-            let _ = try LookupUD.live(
-                e2eId: e2e.getId(),
-                udContact: try userDiscovery.getContact(),
-                lookupId: contact.id,
-                callback: .init(handle: { [weak self] in
+        let facts = try? contact.getFacts()
+        let email = facts?.first(where: { $0.type == FactType.email.rawValue })?.fact
+        let phone = facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact
+        let username = facts?.first(where: { $0.type == FactType.username.rawValue })?.fact
+
+        var model = try! database.saveContact(.init(
+            id: id,
+            marshaled: contact.data,
+            username: username,
+            email: email,
+            phone: phone,
+            nickname: nil,
+            photo: nil,
+            authStatus: .verificationInProgress,
+            isRecent: false,
+            createdAt: Date()
+        ))
+
+        if email == nil, phone == nil {
+            do {
+                try performLookup(on: contact) { [weak self] in
                     guard let self = self else { return }
 
                     switch $0 {
-                    case .success(let id):
-                        self.performOwnershipVerification(contact: contact, idLookedUp: id)
+                    case .success(let lookedUpContact):
+                        if try! self.verifyOwnership(contact, lookedUpContact) { // How could this ever throw?
+                            model.authStatus = .verified
+                            try! self.database.saveContact(model)
+                        } else {
+                            try! self.database.deleteContact(model)
+                        }
                     case .failure(let error):
-                        print("^^^ Lookup failed: \(error.localizedDescription)")
+                        model.authStatus = .verificationFailed
+                        print(">>> Error \(#file):\(#line): \(error.localizedDescription)")
+                        try! self.database.saveContact(model)
                     }
-                })
-            )
-        } catch {
-            print("^^^ Error when trying to run lookup: \(error.localizedDescription)")
+                }
+            } catch {
+                print(">>> Error \(#file):\(#line): \(error.localizedDescription)")
+            }
+        } else {
+            performSearch(on: contact)
         }
     }
 
-    private func performOwnershipVerification(contact: Contact, idLookedUp: Data) {
-        guard let e2e = try? DependencyInjection.Container.shared.resolve() as E2E else {
-            print("^^^ couldn't resolve E2E to process verification")
-            return
-        }
-
-        do {
-            let result = try e2e.verifyOwnership(
-                receivedContact: contact.marshaled!,
-                verifiedContact: idLookedUp,
-                e2eId: e2e.getId()
-            )
-
-            if result == true {
-                var contact = contact
-                contact.authStatus = .verified
-                try database.saveContact(contact)
-            } else {
-                try database.deleteContact(contact)
-            }
-        } catch {
-            print("^^^ Exception thrown at verify ownership")
+    private func handleConfirm(from contact: XXClient.Contact) {
+        guard let id = try? contact.getId() else {
+            fatalError("Couldn't extract ID from contact confirmation arrived.")
         }
-    }
 
-    private func handleConfirm(from contact: Data) {
-        guard let id = try? getIdFromContact(contact) else {
-            print("^^^ Couldn't get id from contact. Confirmation failed")
+        guard var existentContact = try? database.fetchContacts(.init(id: [id])).first else {
+            print(">>> Tried to handle a confirmation from someone that is not a contact yet")
             return
         }
 
-        if var model = try? database.fetchContacts(.init(id: [id])).first {
-            model.authStatus = .friend
-            _ = try? database.saveContact(model)
-        }
+        existentContact.authStatus = .friend
+        try! database.saveContact(existentContact)
     }
 
-    private func handleReset(from contact: Data) {
+    private func handleReset(from contact: XXClient.Contact) {
         // TODO
     }
 
-    private func updateBannedList(completion: @escaping () -> Void) {
-        fetchBannedList { result in
-            switch result {
-            case .failure(_):
-                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
-                    self.updateBannedList(completion: completion)
-                }
-            case .success(let data):
-                self.processBannedList(data, completion: completion)
-            }
-        }
+    private func handleGroupRequest(from group: XXClient.Group) {
+        // TODO
     }
 
-    private func processBannedList(_ data: Data, completion: @escaping () -> Void) {
-        processBannedList(
-            data: data,
-            forEach: { result in
-                switch result {
-                case .success(let userId):
-                    let query = Contact.Query(id: [userId])
-                    if var contact = try! database.fetchContacts(query).first {
-                        if contact.isBanned == false {
-                            contact.isBanned = true
-                            try! database.saveContact(contact)
-                            self.enqueueBanWarning(contact: contact)
-                        }
-                    } else {
-                        try! database.saveContact(.init(id: userId, isBanned: true))
-                    }
+    private func performLookup(
+        on contact: XXClient.Contact,
+        completion: @escaping (Result<XXClient.Contact, Error>) -> Void
+    ) throws {
+        guard let messenger = try? DependencyInjection.Container.shared.resolve() as Messenger else {
+            fatalError(">>> Tried to lookup, but there's no messenger instance on DI container")
+        }
 
-                case .failure(_):
-                    break
-                }
-            },
-            completion: { result in
-                switch result {
-                case .failure(_):
-                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
-                        self.updateBannedList(completion: completion)
-                    }
+        print(">>> Performing Lookup")
 
-                case .success(_):
-                    completion()
+        let _ = try LookupUD.live(
+            e2eId: messenger.e2e.get()!.getId(),
+            udContact: try messenger.ud.get()!.getContact(),
+            lookupId: contact.getId(),
+            callback: .init(handle: {
+                switch $0 {
+                case .success(let otherContact):
+                    print(">>> Lookup succeeded")
+
+                    completion(.success(otherContact))
+                case .failure(let error):
+                    print(">>> Lookup failed: \(error.localizedDescription)")
+
+                    completion(.failure(error))
                 }
-            }
+            })
         )
     }
 
-    private func enqueueBanWarning(contact: Contact) {
-        let name = (contact.nickname ?? contact.username) ?? "One of your contacts"
-        toastController.enqueueToast(model: .init(
-            title: "\(name) has been banned for offensive content.",
-            leftImage: Asset.requestSentToaster.image
-        ))
+    private func performSearch(on contact: XXClient.Contact) {
+        fatalError(">>> UD Search is not implemented yet")
+    }
+
+    private func verifyOwnership(
+        _ lhs: XXClient.Contact,
+        _ rhs: XXClient.Contact
+    ) throws -> Bool {
+        guard let messenger = try? DependencyInjection.Container.shared.resolve() as Messenger else {
+            fatalError(">>> Tried to verify ownership, but there's no messenger instance on DI container")
+        }
+
+        let e2e = messenger.e2e.get()!
+        return try e2e.verifyOwnership(received: lhs, verified: rhs, e2eId: e2e.getId())
     }
 }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
index b22252cb..d379b9cc 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
@@ -8,6 +8,7 @@ import InputField
 import XXClient
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 struct OnboardingEmailConfirmationViewState: Equatable {
     var input: String = ""
@@ -16,7 +17,7 @@ struct OnboardingEmailConfirmationViewState: Equatable {
 }
 
 final class OnboardingEmailConfirmationViewModel {
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
 
     @KeyObject(.email, defaultValue: nil) var email: String?
 
@@ -66,7 +67,7 @@ final class OnboardingEmailConfirmationViewModel {
             guard let self = self else { return }
 
             do {
-                try self.userDiscovery.confirmFact(
+                try self.messenger.ud.get()!.confirmFact(
                     confirmationId: self.confirmation.confirmationId!,
                     code: self.stateRelay.value.input
                 )
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
index 85776bd9..98262328 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
@@ -8,6 +8,7 @@ import InputField
 import XXClient
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 struct OnboardingEmailViewState: Equatable {
     var input: String = ""
@@ -16,7 +17,7 @@ struct OnboardingEmailViewState: Equatable {
 }
 
 final class OnboardingEmailViewModel {
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
 
     @KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications
 
@@ -44,7 +45,7 @@ final class OnboardingEmailViewModel {
             guard let self = self else { return }
 
             do {
-                let confirmationId = try self.userDiscovery.sendRegisterFact(
+                let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
                     .init(fact: self.stateRelay.value.input, type: FactType.email.rawValue)
                 )
 
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
index b05a6b21..49e15708 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
@@ -8,6 +8,7 @@ import InputField
 import XXClient
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 struct OnboardingPhoneConfirmationViewState: Equatable {
     var input: String = ""
@@ -16,7 +17,7 @@ struct OnboardingPhoneConfirmationViewState: Equatable {
 }
 
 final class OnboardingPhoneConfirmationViewModel {
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
 
     @KeyObject(.phone, defaultValue: nil) var phone: String?
 
@@ -66,7 +67,7 @@ final class OnboardingPhoneConfirmationViewModel {
             guard let self = self else { return }
 
             do {
-                try self.userDiscovery.confirmFact(
+                try self.messenger.ud.get()!.confirmFact(
                     confirmationId: self.confirmation.confirmationId!,
                     code: self.stateRelay.value.input
                 )
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
index 3469c45c..a3141d2c 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
@@ -2,11 +2,13 @@ import HUD
 import Shared
 import Models
 import Combine
+import XXClient
 import Countries
 import InputField
-import XXClient
+import Foundation
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 struct OnboardingPhoneViewState: Equatable {
     var input: String = ""
@@ -16,7 +18,7 @@ struct OnboardingPhoneViewState: Equatable {
 }
 
 final class OnboardingPhoneViewModel {
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
 
     var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
     private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
@@ -55,7 +57,7 @@ final class OnboardingPhoneViewModel {
             let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)"
 
             do {
-                let confirmationId = try self.userDiscovery.sendRegisterFact(
+                let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
                     .init(fact: content, type: FactType.phone.rawValue)
                 )
 
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
index ff426498..d6d9b690 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
@@ -5,12 +5,11 @@ import Combine
 import Defaults
 import XXModels
 import InputField
-import XXClient
+import Foundation
+import XXMessengerClient
 import CombineSchedulers
 import DependencyInjection
 
-import struct XXClient.FileTransfer
-
 struct OnboardingUsernameViewState: Equatable {
     var input: String = ""
     var status: InputField.ValidationStatus = .unknown(nil)
@@ -18,9 +17,7 @@ struct OnboardingUsernameViewState: Equatable {
 
 final class OnboardingUsernameViewModel {
     @Dependency var database: Database
-    @Dependency var cMixManager: CMixManager
-    @Dependency var getIdFromContact: GetIdFromContact
-    @Dependency var getFactsFromContact: GetFactsFromContact
+    @Dependency var messenger: Messenger
 
     @KeyObject(.username, defaultValue: "") var username: String
 
@@ -54,17 +51,26 @@ final class OnboardingUsernameViewModel {
             guard let self = self else { return }
 
             do {
-                let cMix = try self.initCMix()
-                try cMix.startNetworkFollower(timeoutMS: 10_000)
-                guard cMix.waitForNetwork(timeoutMS: 10_000) else {
-                    fatalError("^^^ cMix.waitForNetwork returned FALSE")
-                }
-                let e2e = try self.initE2E(cMix)
-                let ud = try self.initUD(alternative: true, e2e: e2e, cMix: cMix)
-                _ = try self.initGroupManager(e2e)
-                _ = try self.initTransferManager(e2e)
-                _ = try self.initDummyTrafficManager(e2e)
-
+                try self.messenger.register(
+                    username: self.stateRelay.value.input
+                )
+
+                try self.database.saveContact(.init(
+                    id: self.messenger.ud.get()!.getContact().getId(),
+                    marshaled: self.messenger.ud.get()!.getContact().data,
+                    username: self.stateRelay.value.input,
+                    email: nil,
+                    phone: nil,
+                    nickname: nil,
+                    photo: nil,
+                    authStatus: .friend,
+                    isRecent: false,
+                    isBlocked: false,
+                    isBanned: false,
+                    createdAt: Date()
+                ))
+
+                self.username = self.stateRelay.value.input
                 self.hudRelay.send(.none)
                 self.greenRelay.send()
             } catch {
@@ -73,238 +79,4 @@ final class OnboardingUsernameViewModel {
             }
         }
     }
-
-    private func initCMix() throws -> CMix {
-        if let cMix = try? DependencyInjection.Container.shared.resolve() as CMix {
-            return cMix
-        }
-
-        let cMix = try cMixManager.create()
-        DependencyInjection.Container.shared.register(cMix)
-        return cMix
-    }
-
-    private func initE2E(_ cMix: CMix) throws -> E2E {
-        if let e2e = try? DependencyInjection.Container.shared.resolve() as E2E {
-            return e2e
-        }
-
-        let e2e = try Login.live(
-            cMixId: cMix.getId(),
-            authCallbacks: .init(
-                handle: {
-                    switch $0 {
-                    case .reset(contact: let contact, receptionId: _, ephemeralId: _, roundId: _):
-                        self.handleReset(from: contact)
-                    case .confirm(contact: let contact, receptionId: _, ephemeralId: _, roundId: _):
-                        self.handleConfirm(from: contact)
-                    case .request(contact: let contact, receptionId: _, ephemeralId: _, roundId: _):
-                        self.handleRequest(from: contact)
-                    }
-                }
-            ),
-            identity: cMix.makeLegacyReceptionIdentity()
-        )
-
-        try e2e.registerListener(
-            senderId: nil,
-            messageType: 2,
-            callback: .init(handle: { message in
-                print(message.timestamp)
-            })
-        )
-
-        DependencyInjection.Container.shared.register(e2e)
-        return e2e
-    }
-
-    private func initUD(alternative: Bool, e2e: E2E, cMix: CMix) throws -> UserDiscovery {
-        if let userDiscovery = try? DependencyInjection.Container.shared.resolve() as UserDiscovery {
-            return userDiscovery
-        }
-
-        guard let certPath = Bundle.module.path(forResource: "cmix.rip", ofType: "crt"),
-              let contactFilePath = Bundle.module.path(forResource: "udContact", ofType: "bin") else {
-            fatalError("Couldn't retrieve alternative UD credentials")
-        }
-
-        let address = alternative ? "46.101.98.49:18001" : e2e.getUdAddressFromNdf()
-        let cert = alternative ? try Data(contentsOf: URL(fileURLWithPath: certPath)) : e2e.getUdCertFromNdf()
-        let contactFile = alternative ? try Data(contentsOf: URL(fileURLWithPath: contactFilePath)) : try e2e.getUdContactFromNdf()
-
-        let userDiscovery = try NewOrLoadUd.live(
-            params: .init(
-                e2eId: e2e.getId(),
-                username: self.stateRelay.value.input,
-                registrationValidationSignature: cMix.getReceptionRegistrationValidationSignature(),
-                cert: cert,
-                contactFile: contactFile,
-                address: address
-            ),
-            follower: .init(handle: { cMix.networkFollowerStatus() })
-        )
-
-        username = self.stateRelay.value.input
-        DependencyInjection.Container.shared.register(userDiscovery)
-        return userDiscovery
-    }
-
-    private func initGroupManager(_ e2e: E2E) throws -> GroupChat {
-        if let groupManager = try? DependencyInjection.Container.shared.resolve() as GroupChat {
-            return groupManager
-        }
-
-        let groupManager = try NewGroupChat.live(
-            e2eId: e2e.getId(),
-            groupRequest: .init(handle: { print($0) }),
-            groupChatProcessor: .init(handle: { print($0) })
-        )
-
-        DependencyInjection.Container.shared.register(groupManager)
-        return groupManager
-    }
-
-    private func initTransferManager(_ e2e: E2E) throws -> XXClient.FileTransfer {
-        if let transferManager = try? DependencyInjection.Container.shared.resolve() as FileTransfer {
-            return transferManager
-        }
-
-        let transferManager = try InitFileTransfer.live(
-            e2eId: e2e.getId(),
-            callback: .init(handle: {
-                switch $0 {
-                case .success(let receivedFile):
-                    print(receivedFile.name)
-                case .failure(let error):
-                    print(error.localizedDescription)
-                }
-            })
-        )
-
-        DependencyInjection.Container.shared.register(transferManager)
-        return transferManager
-    }
-
-    private func initDummyTrafficManager(_ e2e: E2E) throws -> DummyTraffic {
-        if let dummyTrafficManager = try? DependencyInjection.Container.shared.resolve() as DummyTraffic {
-            return dummyTrafficManager
-        }
-
-        let dummyTrafficManager = try NewDummyTrafficManager.live(
-            cMixId: e2e.getId(),
-            maxNumMessages: 1,
-            avgSendDeltaMS: 1,
-            randomRangeMS: 1
-        )
-
-        DependencyInjection.Container.shared.register(dummyTrafficManager)
-        return dummyTrafficManager
-    }
-
-    private func handleRequest(from contact: Data) {
-        guard isRepeatedRequest(from: contact) == false else { return }
-
-        do {
-            let facts = try? getFactsFromContact(contact: contact)
-
-            let model = try self.database.saveContact(.init(
-                id: try getIdFromContact(contact),
-                marshaled: contact,
-                username: facts?.first(where: { $0.type == FactType.username.rawValue })?.fact,
-                email: facts?.first(where: { $0.type == FactType.email.rawValue })?.fact,
-                phone: facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact,
-                nickname: nil,
-                photo: nil,
-                authStatus: .verificationInProgress,
-                isRecent: true,
-                createdAt: Date()
-            ))
-
-            if model.email == nil, model.phone == nil {
-                performLookup(on: model)
-            } else {
-                //performSearch()
-            }
-        } catch {
-            print("^^^ Request processing failed: \(error.localizedDescription)")
-        }
-    }
-
-    private func isRepeatedRequest(from contact: Data) -> Bool {
-        if let id = try? getIdFromContact(contact),
-           let _ = try? self.database.fetchContacts(Contact.Query(id: [id])).first {
-            return true
-        }
-
-        return false
-    }
-
-    private func performLookup(on contact: Contact) {
-        guard let e2e = try? DependencyInjection.Container.shared.resolve() as E2E,
-              let userDiscovery = try? DependencyInjection.Container.shared.resolve() as UserDiscovery else {
-            print("^^^ couldn't resolve UD/E2E to process lookup")
-            return
-        }
-
-        do {
-            let _ = try LookupUD.live(
-                e2eId: e2e.getId(),
-                udContact: try userDiscovery.getContact(),
-                lookupId: contact.id,
-                callback: .init(handle: { [weak self] in
-                    guard let self = self else { return }
-
-                    switch $0 {
-                    case .success(let id):
-                        self.performOwnershipVerification(contact: contact, idLookedUp: id)
-                    case .failure(let error):
-                        print("^^^ Lookup failed: \(error.localizedDescription)")
-                    }
-                })
-            )
-        } catch {
-            print("^^^ Error when trying to run lookup: \(error.localizedDescription)")
-        }
-    }
-
-    private func performOwnershipVerification(contact: Contact, idLookedUp: Data) {
-        guard let e2e = try? DependencyInjection.Container.shared.resolve() as E2E else {
-            print("^^^ couldn't resolve E2E to process verification")
-            return
-        }
-
-        do {
-            let result = try e2e.verifyOwnership(
-                receivedContact: contact.marshaled!,
-                verifiedContact: idLookedUp,
-                e2eId: e2e.getId()
-            )
-
-            if result == true {
-                var contact = contact
-                contact.authStatus = .verified
-                try database.saveContact(contact)
-            } else {
-                try database.deleteContact(contact)
-            }
-        } catch {
-            print("^^^ Exception thrown at verify ownership")
-        }
-    }
-
-    private func handleConfirm(from contact: Data) {
-        guard let id = try? getIdFromContact(contact) else {
-            print("^^^ Couldn't get id from contact. Confirmation failed")
-            return
-        }
-
-        if var model = try? database.fetchContacts(.init(id: [id])).first {
-            model.authStatus = .friend
-            _ = try? database.saveContact(model)
-        }
-    }
-
-    private func handleReset(from contact: Data) {
-        // TODO
-    }
 }
diff --git a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
index 49be0077..f2c0d8a5 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
@@ -3,10 +3,12 @@ import Shared
 import Models
 import Combine
 import Defaults
-import InputField
 import XXClient
+import Foundation
+import InputField
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 struct ProfileCodeViewState: Equatable {
     var input: String = ""
@@ -15,7 +17,7 @@ struct ProfileCodeViewState: Equatable {
 }
 
 final class ProfileCodeViewModel {
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
 
     @KeyObject(.email, defaultValue: nil) var email: String?
     @KeyObject(.phone, defaultValue: nil) var phone: String?
@@ -67,7 +69,7 @@ final class ProfileCodeViewModel {
             guard let self = self else { return }
 
             do {
-                try self.userDiscovery.confirmFact(
+                try self.messenger.ud.get()!.confirmFact(
                     confirmationId: self.confirmation.confirmationId!,
                     code: self.stateRelay.value.input
                 )
diff --git a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
index 21ec9fa8..8edf40aa 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
@@ -2,10 +2,12 @@ import HUD
 import Models
 import Shared
 import Combine
-import InputField
 import XXClient
+import Foundation
+import InputField
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 struct ProfileEmailViewState: Equatable {
     var input: String = ""
@@ -16,7 +18,7 @@ struct ProfileEmailViewState: Equatable {
 final class ProfileEmailViewModel {
     // MARK: Injected
 
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
 
     // MARK: Properties
 
@@ -46,7 +48,7 @@ final class ProfileEmailViewModel {
             guard let self = self else { return }
 
             do {
-                let confirmationId = try self.userDiscovery.sendRegisterFact(
+                let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
                     .init(fact: self.stateRelay.value.input, type: FactType.email.rawValue)
                 )
 
diff --git a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
index 37db504e..e5504140 100644
--- a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
@@ -2,11 +2,13 @@ import HUD
 import Shared
 import Models
 import Combine
+import XXClient
 import Countries
 import InputField
-import XXClient
+import Foundation
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 struct ProfilePhoneViewState: Equatable {
     var input: String = ""
@@ -18,7 +20,7 @@ struct ProfilePhoneViewState: Equatable {
 final class ProfilePhoneViewModel {
     // MARK: Injected
 
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
 
     // MARK: Properties
 
@@ -55,7 +57,7 @@ final class ProfilePhoneViewModel {
             let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)"
 
             do {
-                let confirmationId = try self.userDiscovery.sendRegisterFact(
+                let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
                     .init(fact: content, type: FactType.phone.rawValue)
                 )
 
diff --git a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
index d6bc8bb3..57334dcf 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
@@ -10,6 +10,7 @@ import Permissions
 import XXClient
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 enum ProfileNavigationRoutes {
     case none
@@ -31,7 +32,7 @@ final class ProfileViewModel {
     @KeyObject(.sharingEmail, defaultValue: false) var isEmailSharing: Bool
     @KeyObject(.sharingPhone, defaultValue: false) var isPhoneSharing: Bool
 
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
     @Dependency private var permissions: PermissionHandling
 
     var name: String { username! }
@@ -90,9 +91,9 @@ final class ProfileViewModel {
             guard let self = self else { return }
 
             do {
-                try self.userDiscovery.removeFact(
+                try self.messenger.ud.get()!.removeFact(
                     .init(
-                        fact: isEmail ? "E\(self.emailStored!)" : "P\(self.phoneStored!)",
+                        fact: isEmail ? self.emailStored! : self.phoneStored!,
                         type: isEmail ? FactType.email.rawValue : FactType.phone.rawValue
                     )
                 )
diff --git a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
index 6b3cf746..29636883 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
@@ -7,11 +7,11 @@ import Defaults
 import XXClient
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 final class RequestsFailedViewModel {
-    @Dependency var e2e: E2E
     @Dependency var database: Database
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
 
     @KeyObject(.username, defaultValue: nil) var username: String?
 
@@ -49,11 +49,11 @@ final class RequestsFailedViewModel {
             guard let self = self else { return }
 
             do {
-                var myFacts = try self.userDiscovery.getFacts()
+                var myFacts = try self.messenger.ud.get()!.getFacts()
                 myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue))
 
-                let _ = try self.e2e.requestAuthenticatedChannel(
-                    partnerContact: contact.id,
+                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+                    partner: XXClient.Contact.live(contact.marshaled!),
                     myFacts: myFacts
                 )
 
diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
index 9ae5fc8e..326b72a9 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
@@ -10,6 +10,7 @@ import DrawerFeature
 import ReportingFeature
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 import struct XXModels.Group
 
@@ -20,10 +21,9 @@ struct RequestReceived: Hashable, Equatable {
 }
 
 final class RequestsReceivedViewModel {
-    @Dependency var e2e: E2E
     @Dependency var database: Database
     @Dependency var groupManager: GroupChat
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
     @Dependency var reportingStatus: ReportingStatus
 
     @KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool
@@ -44,7 +44,7 @@ final class RequestsReceivedViewModel {
         groupConfirmationSubject.eraseToAnyPublisher()
     }
 
-    var contactConfirmationPublisher: AnyPublisher<Contact, Never> {
+    var contactConfirmationPublisher: AnyPublisher<XXModels.Contact, Never> {
         contactConfirmationSubject.eraseToAnyPublisher()
     }
 
@@ -53,7 +53,7 @@ final class RequestsReceivedViewModel {
     private let verifyingSubject = PassthroughSubject<Void, Never>()
     private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
     private let groupConfirmationSubject = PassthroughSubject<Group, Never>()
-    private let contactConfirmationSubject = PassthroughSubject<Contact, Never>()
+    private let contactConfirmationSubject = PassthroughSubject<XXModels.Contact, Never>()
     private let itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, RequestReceived>, Never>(.init())
 
     var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
@@ -156,16 +156,16 @@ final class RequestsReceivedViewModel {
 
                     if contact.email == nil && contact.phone == nil {
                         let _ = try LookupUD.live(
-                            e2eId: self.e2e.getId(),
-                            udContact: self.userDiscovery.getContact(),
+                            e2eId: self.messenger.e2e.get()!.getId(),
+                            udContact: self.messenger.ud.get()!.getContact(),
                             lookupId: contact.id,
                             callback: .init(handle: {
                                 switch $0 {
-                                case .success(let data):
-                                    let ownershipResult = try! self.e2e.verifyOwnership(
-                                        receivedContact: contact.marshaled!,
-                                        verifiedContact: data,
-                                        e2eId: self.e2e.getId()
+                                case .success(let secondContact):
+                                    let ownershipResult = try! self.messenger.e2e.get()!.verifyOwnership(
+                                        received: XXClient.Contact.live(contact.marshaled!),
+                                        verified: secondContact,
+                                        e2eId: self.messenger.e2e.get()!.getId()
                                     )
 
                                     if ownershipResult == true {
@@ -262,14 +262,14 @@ final class RequestsReceivedViewModel {
         }
     }
 
-    func didRequestHide(contact: Contact) {
+    func didRequestHide(contact: XXModels.Contact) {
         if var contact = try? database.fetchContacts(.init(id: [contact.id])).first {
             contact.authStatus = .hidden
             _ = try? database.saveContact(contact)
         }
     }
 
-    func didRequestAccept(contact: Contact, nickname: String? = nil) {
+    func didRequestAccept(contact: XXModels.Contact, nickname: String? = nil) {
         hudSubject.send(.on)
 
         var contact = contact
@@ -282,7 +282,7 @@ final class RequestsReceivedViewModel {
             do {
                 try self.database.saveContact(contact)
 
-                let _ = try self.e2e.confirmReceivedRequest(partnerContact: contact.id)
+                let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: XXClient.Contact.live(contact.marshaled!))
                 contact.authStatus = .friend
                 try self.database.saveContact(contact)
 
diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
index 2463f011..1522b9ec 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
@@ -11,6 +11,7 @@ import ToastFeature
 import ReportingFeature
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 struct RequestSent: Hashable, Equatable {
     var request: Request
@@ -18,9 +19,8 @@ struct RequestSent: Hashable, Equatable {
 }
 
 final class RequestsSentViewModel {
-    @Dependency var e2e: E2E
     @Dependency var database: Database
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
     @Dependency var reportingStatus: ReportingStatus
     @Dependency var toastController: ToastController
 
@@ -70,11 +70,11 @@ final class RequestsSentViewModel {
             guard let self = self else { return }
 
             do {
-                var myFacts = try self.userDiscovery.getFacts()
+                var myFacts = try self.messenger.ud.get()!.getFacts()
                 myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue))
 
-                let _ = try self.e2e.requestAuthenticatedChannel(
-                    partnerContact: contact.id,
+                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+                    partner: XXClient.Contact.live(contact.marshaled!),
                     myFacts: myFacts
                 )
 
diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
index 4560f3cd..002ba409 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
@@ -225,7 +225,7 @@ final class RestoreViewModel {
                             }
                         }
                     ),
-                    identity: try cMix.makeLegacyReceptionIdentity()
+                    identity: try cMix.makeReceptionIdentity()
                 )
 
                 guard let certPath = Bundle.module.path(forResource: "cmix.rip", ofType: "crt"),
@@ -240,7 +240,7 @@ final class RestoreViewModel {
                         email: emailFact,
                         phone: phoneFact,
                         cert: Data(contentsOf: URL(fileURLWithPath: certPath)),
-                        contactFile: Data(contentsOf: URL(fileURLWithPath: contactFilePath)),
+                        contact: Data(contentsOf: URL(fileURLWithPath: contactFilePath)),
                         address: "46.101.98.49:18001"
                     ),
                     follower: .init(handle: { cMix.networkFollowerStatus() })
diff --git a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift
index 1b7ed9b2..d18e7ab8 100644
--- a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift
+++ b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift
@@ -5,6 +5,7 @@ import Defaults
 import Countries
 import XXClient
 import DependencyInjection
+import XXMessengerClient
 
 struct ScanDisplayViewState: Equatable {
     var image: CIImage?
@@ -15,7 +16,7 @@ struct ScanDisplayViewState: Equatable {
 }
 
 final class ScanDisplayViewModel {
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
 
     @KeyObject(.email, defaultValue: nil) private var email: String?
     @KeyObject(.phone, defaultValue: nil) private var phone: String?
@@ -60,7 +61,7 @@ final class ScanDisplayViewModel {
         guard let filter = CIFilter(name: "CIQRCodeGenerator") else { return }
 
         var facts: [Fact] = []
-        let myContact = try! userDiscovery.getContact()
+        let myContact = try! messenger.ud.get()!.getContact()
 
         if sharingPhone {
             facts.append(Fact(fact: phone!, type: FactType.phone.rawValue))
@@ -71,7 +72,7 @@ final class ScanDisplayViewModel {
         }
 
         let myContactWithFacts = try! SetFactsOnContact.live(
-            contact: myContact,
+            contactData: myContact.data,
             facts: facts
         )
 
diff --git a/Sources/ScanFeature/ViewModels/ScanViewModel.swift b/Sources/ScanFeature/ViewModels/ScanViewModel.swift
index c7c9b11a..365533cd 100644
--- a/Sources/ScanFeature/ViewModels/ScanViewModel.swift
+++ b/Sources/ScanFeature/ViewModels/ScanViewModel.swift
@@ -32,8 +32,8 @@ final class ScanViewModel {
     var backgroundScheduler: AnySchedulerOf<DispatchQueue>
         = DispatchQueue.global().eraseToAnyScheduler()
 
-    var contactPublisher: AnyPublisher<Contact, Never> { contactRelay.eraseToAnyPublisher() }
-    private let contactRelay = PassthroughSubject<Contact, Never>()
+    var contactPublisher: AnyPublisher<XXModels.Contact, Never> { contactRelay.eraseToAnyPublisher() }
+    private let contactRelay = PassthroughSubject<XXModels.Contact, Never>()
 
     var state: AnyPublisher<ScanViewState, Never> { stateRelay.eraseToAnyPublisher() }
     private let stateRelay = CurrentValueSubject<ScanViewState, Never>(.init())
@@ -73,7 +73,7 @@ final class ScanViewModel {
                     return
                 }
 
-                let facts = try? self.getFactsFromContact(contact: data)
+                let facts = try? self.getFactsFromContact(data)
                 let contactEmail = facts?.first(where: { $0.type == FactType.email.rawValue })?.fact
                 let contactPhone = facts?.first(where: { $0.type == FactType.phone.rawValue })?.fact
 
@@ -99,14 +99,14 @@ final class ScanViewModel {
 
     private func verifyScanned(_ data: Data) throws -> (String, Data)? {
         let id = try? GetIdFromContact.live(data)
-        let facts = try? getFactsFromContact(contact: data)
+        let facts = try? getFactsFromContact(data)
         let username = facts?.first(where: { $0.type == FactType.username.rawValue })?.fact
 
         guard let id = id, let username = username else { return nil }
         return (username, id)
     }
 
-    private func succeed(with contact: Contact) {
+    private func succeed(with contact: XXModels.Contact) {
         stateRelay.value.status = .success
         contactRelay.send(contact)
     }
diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
index f24715e9..c6158e73 100644
--- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
@@ -13,6 +13,7 @@ import NetworkMonitor
 import ReportingFeature
 import CombineSchedulers
 import DependencyInjection
+import XXMessengerClient
 
 typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
 
@@ -24,24 +25,22 @@ struct SearchLeftViewState {
 }
 
 final class SearchLeftViewModel {
-    @Dependency var e2e: E2E
-    @Dependency var cMix: CMix
     @Dependency var database: Database
-    @Dependency var userDiscovery: UserDiscovery
+    @Dependency var messenger: Messenger
     @Dependency var reportingStatus: ReportingStatus
     @Dependency var networkMonitor: NetworkMonitoring
 
     @KeyObject(.username, defaultValue: nil) var username: String?
 
     var myId: Data {
-        try! GetIdFromContact.live(userDiscovery.getContact())
+        try! messenger.ud.get()!.getContact().getId()
     }
 
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
     }
 
-    var successPublisher: AnyPublisher<Contact, Never> {
+    var successPublisher: AnyPublisher<XXModels.Contact, Never> {
         successSubject.eraseToAnyPublisher()
     }
 
@@ -53,7 +52,7 @@ final class SearchLeftViewModel {
 
     private var invitation: String?
     private var searchCancellables = Set<AnyCancellable>()
-    private let successSubject = PassthroughSubject<Contact, Never>()
+    private let successSubject = PassthroughSubject<XXModels.Contact, Never>()
     private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
     private let stateSubject = CurrentValueSubject<SearchLeftViewState, Never>(.init())
     private var networkCancellable = Set<AnyCancellable>()
@@ -70,17 +69,17 @@ final class SearchLeftViewModel {
 
             networkCancellable.removeAll()
 
-            networkMonitor.statusPublisher
-                .first { $0 == .available }
-                .eraseToAnyPublisher()
-                .flatMap { _ in self.session.waitForNodes(timeout: 5) }
-                .sink {
-                    if case .failure(let error) = $0 {
-                        self.hudSubject.send(.error(.init(with: error)))
-                    }
-                } receiveValue: {
-                    self.didStartSearching()
-                }.store(in: &networkCancellable)
+//            networkMonitor.statusPublisher
+//                .first { $0 == .available }
+//                .eraseToAnyPublisher()
+//                .flatMap { _ in self.session.waitForNodes(timeout: 5) }
+//                .sink {
+//                    if case .failure(let error) = $0 {
+//                        self.hudSubject.send(.error(.init(with: error)))
+//                    }
+//                } receiveValue: {
+//                    self.didStartSearching()
+//                }.store(in: &networkCancellable)
         }
     }
 
@@ -113,7 +112,7 @@ final class SearchLeftViewModel {
             content += stateSubject.value.country.code
         }
 
-        let nrr = try! cMix.getNodeRegistrationStatus()
+        let nrr = try! messenger.cMix.get()!.getNodeRegistrationStatus()
         print("^^^ NRR: \(nrr.ratio)")
 
         backgroundScheduler.schedule { [weak self] in
@@ -121,16 +120,29 @@ final class SearchLeftViewModel {
 
             do {
                 let report = try SearchUD.live(
-                    e2eId: self.e2e.getId(),
-                    udContact: self.userDiscovery.getContact(),
+                    e2eId: self.messenger.e2e.get()!.getId(),
+                    udContact: self.messenger.ud.get()!.getContact(),
                     facts: [Fact(fact: content, type: self.stateSubject.value.item.rawValue)],
                     callback: .init(handle: {
                         switch $0 {
-                        case .success(let dataArray):
-                            print("^^^ searchUD success: \(dataArray.map { $0.base64EncodedString() })")
-
+                        case .success(let results):
                              self.hudSubject.send(.none)
-//                             self.appendToLocalSearch(contact)
+                             self.appendToLocalSearch(
+                                XXModels.Contact(
+                                    id: try! results.first!.getId(),
+                                    marshaled: results.first!.data,
+                                    username: try! results.first?.getFacts().first(where: { $0.type == FactType.username.rawValue })?.fact,
+                                    email: try? results.first?.getFacts().first(where: { $0.type == FactType.email.rawValue })?.fact,
+                                    phone: try? results.first?.getFacts().first(where: { $0.type == FactType.phone.rawValue })?.fact,
+                                    nickname: nil,
+                                    photo: nil,
+                                    authStatus: .stranger,
+                                    isRecent: false,
+                                    isBlocked: false,
+                                    isBanned: false,
+                                    createdAt: Date()
+                                )
+                             )
 
                         case .failure(let error):
                             print("^^^ searchUD error: \(error.localizedDescription)")
@@ -147,7 +159,7 @@ final class SearchLeftViewModel {
         }
     }
 
-    func didTapResend(contact: Contact) {
+    func didTapResend(contact: XXModels.Contact) {
         hudSubject.send(.on)
 
         var contact = contact
@@ -159,11 +171,11 @@ final class SearchLeftViewModel {
             do {
                 try self.database.saveContact(contact)
 
-                var myFacts = try self.userDiscovery.getFacts()
+                var myFacts = try self.messenger.ud.get()!.getFacts()
                 myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue))
 
-                let _ = try self.e2e.requestAuthenticatedChannel(
-                    partnerContact: contact.id,
+                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+                    partner: XXClient.Contact.live(contact.marshaled!),
                     myFacts: myFacts
                 )
 
@@ -179,7 +191,7 @@ final class SearchLeftViewModel {
         }
     }
 
-    func didTapRequest(contact: Contact) {
+    func didTapRequest(contact: XXModels.Contact) {
         hudSubject.send(.on)
 
         var contact = contact
@@ -192,11 +204,11 @@ final class SearchLeftViewModel {
             do {
                 try self.database.saveContact(contact)
 
-                var myFacts = try self.userDiscovery.getFacts()
+                var myFacts = try self.messenger.ud.get()!.getFacts()
                 myFacts.append(Fact(fact: self.username!, type: FactType.username.rawValue))
 
-                let _ = try self.e2e.requestAuthenticatedChannel(
-                    partnerContact: contact.marshaled!,
+                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+                    partner: XXClient.Contact.live(contact.marshaled!),
                     myFacts: myFacts
                 )
 
@@ -213,14 +225,14 @@ final class SearchLeftViewModel {
         }
     }
 
-    func didSet(nickname: String, for contact: Contact) {
+    func didSet(nickname: String, for contact: XXModels.Contact) {
         if var contact = try? database.fetchContacts(.init(id: [contact.id])).first {
             contact.nickname = nickname
             _ = try? database.saveContact(contact)
         }
     }
 
-    private func appendToLocalSearch(_ user: Contact?) {
+    private func appendToLocalSearch(_ user: XXModels.Contact?) {
         var snapshot = SearchSnapshot()
 
         if var user = user {
@@ -259,7 +271,7 @@ final class SearchLeftViewModel {
         stateSubject.value.snapshot = snapshot
     }
 
-    private func removeMyself(from collection: [Contact]) -> [Contact]? {
+    private func removeMyself(from collection: [XXModels.Contact]) -> [XXModels.Contact]? {
         collection.filter { $0.id != myId }
     }
 }
diff --git a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
index 671e340d..8d41282f 100644
--- a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
@@ -29,7 +29,7 @@ final class SearchRightViewModel {
     @Dependency var reportingStatus: ReportingStatus
     @Dependency var getFactsFromContact: GetFactsFromContact
 
-    var foundPublisher: AnyPublisher<Contact, Never> {
+    var foundPublisher: AnyPublisher<XXModels.Contact, Never> {
         foundSubject.eraseToAnyPublisher()
     }
 
@@ -41,7 +41,7 @@ final class SearchRightViewModel {
         statusSubject.eraseToAnyPublisher()
     }
 
-    private let foundSubject = PassthroughSubject<Contact, Never>()
+    private let foundSubject = PassthroughSubject<XXModels.Contact, Never>()
     private let cameraSemaphoreSubject = PassthroughSubject<Bool, Never>()
     private(set) var statusSubject = CurrentValueSubject<ScanningStatus, Never>(.reading)
 
@@ -73,7 +73,7 @@ final class SearchRightViewModel {
         /// otherwise is just noise or an unknown qr code
         ///
         let userId = try? GetIdFromContact.live(data)
-        let facts = try? getFactsFromContact(contact: data)
+        let facts = try? getFactsFromContact(data)
         let username = facts?.first(where: { $0.type == FactType.username.rawValue })?.fact
 
         guard let userId = userId, let username = username else {
@@ -113,11 +113,11 @@ final class SearchRightViewModel {
         statusSubject.send(.success)
         cameraSemaphoreSubject.send(false)
 
-        let email = try? GetFactsFromContact.live(contact: data)
+        let email = try? GetFactsFromContact.live(data)
             .first(where: { $0.type == FactType.email.rawValue })
             .map(\.fact)
 
-        let phone = try? GetFactsFromContact.live(contact: data)
+        let phone = try? GetFactsFromContact.live(data)
             .first(where: { $0.type == FactType.phone.rawValue })
             .map(\.fact)
 
diff --git a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 00c840d9..4fd5a955 100644
--- a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -68,8 +68,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/combine-schedulers",
       "state" : {
-        "revision" : "8fee20f993e64bbbf22bc3e3f444758ac2d05692",
-        "version" : "0.7.2"
+        "revision" : "9e42b4b0453da417a44daa17174103e7d1c5be07",
+        "version" : "0.7.3"
       }
     },
     {
@@ -105,7 +105,7 @@
       "location" : "https://git.xx.network/elixxir/elixxir-dapps-sdk-swift",
       "state" : {
         "branch" : "development",
-        "revision" : "e5088ee5e3a3aeb6d6887d2402df829acdb4f8c2"
+        "revision" : "32ac1c46d6015069efa20dfe772f20adee6e3ecb"
       }
     },
     {
@@ -328,8 +328,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/swift-composable-architecture.git",
       "state" : {
-        "revision" : "108e3a536fcebb16c4f247ef92c2d7326baf9fe3",
-        "version" : "0.39.0"
+        "revision" : "a518935116b2bada7234f47073159b433d432af1",
+        "version" : "0.39.1"
       }
     },
     {
@@ -355,8 +355,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/apple/swift-protobuf",
       "state" : {
-        "revision" : "fa0fcd43f272a260e7f734f23e6dc55e16fcae0a",
-        "version" : "1.19.1"
+        "revision" : "b8230909dedc640294d7324d37f4c91ad3dcf177",
+        "version" : "1.20.1"
       }
     },
     {
-- 
GitLab