diff --git a/.gitignore b/.gitignore
index 839d127c8fff949c405a77d770e8f22feb8b2b6b..e051e6d6108fed6f8277dbaea33859447b20c2df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.build
 .DS_Store
 *.xcuserstate
 xcuserdata/
diff --git a/Package.swift b/Package.swift
index 66b1af7d2459202cf5772e2d22e1009df35777d1..617ab061294b661012d646258bf8cde4c3cf4aef 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.3
+// swift-tools-version:5.6
 import PackageDescription
 
 let package = Package(
@@ -14,7 +14,6 @@ let package = Package(
         .library(name: "Shared", targets: ["Shared"]),
         .library(name: "Models", targets: ["Models"]),
         .library(name: "XXLogger", targets: ["XXLogger"]),
-        .library(name: "Database", targets: ["Database"]),
         .library(name: "Defaults", targets: ["Defaults"]),
         .library(name: "Bindings", targets: ["Bindings"]),
         .library(name: "Keychain", targets: ["Keychain"]),
@@ -52,96 +51,24 @@ let package = Package(
         .library(name: "DependencyInjection", targets: ["DependencyInjection"])
     ],
     dependencies: [
-        .package(
-            name: "Quick",
-            url: "https://github.com/Quick/Quick",
-            from: "3.0.0"
-        ),
-        .package(
-            name: "DifferenceKit",
-            url: "https://github.com/ra1028/DifferenceKit",
-            from: "1.2.0"
-        ),
-        .package(
-            name: "Nimble",
-            url: "https://github.com/Quick/Nimble",
-            from: "9.0.0"
-        ),
-        .package(
-            name: "FilesProvider",
-            url: "https://github.com/amosavian/FileProvider.git",
-            from: "0.26.0"
-        ),
-        .package(
-            name: "GRDB",
-            url: "https://github.com/groue/GRDB.swift",
-            from: "5.3.0"
-        ),
-        .package(
-            name: "GoogleSignIn",
-            url: "https://github.com/google/GoogleSignIn-iOS",
-            from: "6.1.0"
-        ),
-        .package(
-            name: "GoogleAPIClientForREST",
-            url: "https://github.com/google/google-api-objectivec-client-for-rest",
-            from: "1.6.0"
-        ),
-        .package(
-            name: "SnapKit",
-            url: "https://github.com/SnapKit/SnapKit",
-            from: "5.0.1"
-        ),
-        .package(
-            name: "Firebase",
-            url: "https://github.com/firebase/firebase-ios-sdk.git",
-            .upToNextMajor(from: "8.10.0")
-        ),
-        .package(
-            name: "SwiftProtobuf",
-            url: "https://github.com/apple/swift-protobuf",
-            from: "1.14.0"
-        ),
-        .package(
-            name: "SwiftyDropbox",
-            url: "https://github.com/dropbox/SwiftyDropbox.git",
-            from: "8.2.1"
-        ),
-        .package(
-            name: "KeychainAccess",
-            url: "https://github.com/kishikawakatsumi/KeychainAccess",
-            from: "4.2.1"
-        ),
-        .package(
-            name: "Retry",
-            url: "https://github.com/icanzilb/Retry.git",
-            from: "0.6.3"
-        ),
-        .package(
-            name: "ChatLayout",
-            url: "https://github.com/ekazaev/ChatLayout",
-            from: "1.1.14"
-        ),
-        .package(
-            name: "SwiftyBeaver",
-            url: "https://github.com/SwiftyBeaver/SwiftyBeaver.git",
-            from: "1.9.5"
-        ),
-        .package(
-            name: "swift-composable-architecture",
-            url: "https://github.com/pointfreeco/swift-composable-architecture.git",
-            .upToNextMajor(from: "0.32.0")
-        ),
-        .package(
-            name: "ScrollViewController",
-            url: "https://github.com/darrarski/ScrollViewController",
-            from: "1.2.0"
-        ),
-        .package(
-            name: "combine-schedulers",
-            url: "https://github.com/pointfreeco/combine-schedulers",
-            from: "0.5.0"
-        )
+        .package(url: "https://github.com/Quick/Quick", from: "3.0.0"),
+        .package(url: "https://github.com/Quick/Nimble", from: "9.0.0"),
+        .package(url: "https://github.com/SnapKit/SnapKit", from: "5.0.1"),
+        .package(url: "https://github.com/icanzilb/Retry.git", from: "0.6.3"),
+        .package(url: "https://github.com/ekazaev/ChatLayout", from: "1.1.14"),
+        .package(url: "https://github.com/ra1028/DifferenceKit", from: "1.2.0"),
+        .package(url: "https://github.com/apple/swift-protobuf", from: "1.14.0"),
+        .package(url: "https://github.com/google/GoogleSignIn-iOS", from: "6.1.0"),
+        .package(url: "https://github.com/dropbox/SwiftyDropbox.git", from: "8.2.1"),
+        .package(url: "https://github.com/amosavian/FileProvider.git", from: "0.26.0"),
+        .package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver.git", from: "1.9.5"),
+        .package(url: "https://github.com/darrarski/ScrollViewController", from: "1.2.0"),
+        .package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.5.0"),
+        .package(url: "https://github.com/kishikawakatsumi/KeychainAccess", from: "4.2.1"),
+        .package(url: "https://github.com/google/google-api-objectivec-client-for-rest", from: "1.6.0"),
+        .package(url: "https://git.xx.network/elixxir/client-ios-db.git", .upToNextMajor(from: "1.0.5")),
+        .package(url: "https://github.com/firebase/firebase-ios-sdk.git", .upToNextMajor(from: "8.10.0")),
+        .package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",.upToNextMajor(from: "0.32.0"))
     ],
     targets: [
         .target(
@@ -197,7 +124,6 @@ let package = Package(
                 name: "PushFeature",
                 dependencies: [
                     "Models",
-                    "Database",
                     "Defaults",
                     "Integration",
                     "DependencyInjection"
@@ -246,7 +172,7 @@ let package = Package(
                     ),
                     .product(
                         name: "SwiftProtobuf",
-                        package: "SwiftProtobuf"
+                        package: "swift-protobuf"
                     )
                 ]
             ),
@@ -277,7 +203,7 @@ let package = Package(
                     "CrashReporting",
                     .product(
                         name: "FirebaseCrashlytics",
-                        package: "Firebase"
+                        package: "firebase-ios-sdk"
                     )
                 ]
             ),
@@ -289,11 +215,11 @@ let package = Package(
                 dependencies: [
                     .product(
                         name: "GoogleSignIn",
-                        package: "GoogleSignIn"
+                        package: "GoogleSignIn-iOS"
                     ),
                     .product(
                         name: "GoogleAPIClientForREST_Drive",
-                        package: "GoogleAPIClientForREST"
+                        package: "google-api-objectivec-client-for-rest"
                     )
                 ],
                 resources: [.process("Resources")]
@@ -306,7 +232,7 @@ let package = Package(
                 dependencies: [
                     .product(
                         name: "FilesProvider",
-                        package: "FilesProvider"
+                        package: "FileProvider"
                     )
                 ]
             ),
@@ -386,40 +312,22 @@ let package = Package(
                 ]
             ),
 
-        // MARK: - Database
-
-            .target(
-                name: "Database",
-                dependencies: [
-                    "Models",
-                    "XXLogger",
-                    .product(
-                        name: "GRDB",
-                        package: "GRDB"
-                    ),
-                    .product(
-                        name: "DifferenceKit",
-                        package: "DifferenceKit"
-                    )
-                ]
-            ),
-
         // MARK: - Shared
 
             .target(
                 name: "Shared",
                 dependencies: [
                     .product(
-                        name: "DifferenceKit",
-                        package: "DifferenceKit"
+                        name: "SnapKit",
+                        package: "SnapKit"
                     ),
                     .product(
                         name: "ChatLayout",
                         package: "ChatLayout"
                     ),
                     .product(
-                        name: "SnapKit",
-                        package: "SnapKit"
+                        name: "DifferenceKit",
+                        package: "DifferenceKit"
                     )
                 ],
                 exclude: ["swiftgen.yml"],
@@ -431,10 +339,10 @@ let package = Package(
             .target(
                 name: "Integration",
                 dependencies: [
-                    "XXLogger",
                     "Shared",
-                    "Database",
                     "Bindings",
+                    "XXLogger",
+                    "Keychain",
                     "ToastFeature",
                     "BackupFeature",
                     "CrashReporting",
@@ -443,6 +351,14 @@ let package = Package(
                     .product(
                         name: "Retry",
                         package: "Retry"
+                    ),
+                    .product(
+                        name: "XXDatabase",
+                        package: "client-ios-db"
+                    ),
+                    .product(
+                        name: "XXLegacyDatabaseMigrator",
+                        package: "client-ios-db"
                     )
                 ],
                 resources: [.process("Resources")]
@@ -500,13 +416,13 @@ let package = Package(
                     "InputField",
                     "ChatFeature",
                     "Presentation",
-                    .product(
-                        name: "ScrollViewController",
-                        package: "ScrollViewController"
-                    ),
                     .product(
                         name: "CombineSchedulers",
                         package: "combine-schedulers"
+                    ),
+                    .product(
+                        name: "ScrollViewController",
+                        package: "ScrollViewController"
                     )
                 ]
             ),
@@ -528,14 +444,14 @@ let package = Package(
                     "DrawerFeature",
                     "ChatInputFeature",
                     "DependencyInjection",
-                    .product(
-                        name: "DifferenceKit",
-                        package: "DifferenceKit"
-                    ),
                     .product(
                         name: "ChatLayout",
                         package: "ChatLayout"
                     ),
+                    .product(
+                        name: "DifferenceKit",
+                        package: "DifferenceKit"
+                    ),
                     .product(
                         name: "ScrollViewController",
                         package: "ScrollViewController"
@@ -612,13 +528,13 @@ let package = Package(
                     "Presentation",
                     "DrawerFeature",
                     "DependencyInjection",
-                    .product(
-                        name: "ScrollViewController",
-                        package: "ScrollViewController"
-                    ),
                     .product(
                         name: "CombineSchedulers",
                         package: "combine-schedulers"
+                    ),
+                    .product(
+                        name: "ScrollViewController",
+                        package: "ScrollViewController"
                     )
                 ]
             ),
@@ -662,13 +578,13 @@ let package = Package(
                     "DrawerFeature",
                     "VersionChecking",
                     "DependencyInjection",
-                    .product(
-                        name: "ScrollViewController",
-                        package: "ScrollViewController"
-                    ),
                     .product(
                         name: "CombineSchedulers",
                         package: "combine-schedulers"
+                    ),
+                    .product(
+                        name: "ScrollViewController",
+                        package: "ScrollViewController"
                     )
                 ]
             ),
@@ -697,9 +613,10 @@ let package = Package(
                     "Models",
                     "InputField",
                     "Presentation",
-                    "GoogleDriveFeature",
                     "iCloudFeature",
+                    "DrawerFeature",
                     "DropboxFeature",
+                    "GoogleDriveFeature",
                     "DependencyInjection"
                 ]
             ),
@@ -760,13 +677,13 @@ let package = Package(
                     "Presentation",
                     "DrawerFeature",
                     "DependencyInjection",
-                    .product(
-                        name: "ScrollViewController",
-                        package: "ScrollViewController"
-                    ),
                     .product(
                         name: "CombineSchedulers",
                         package: "combine-schedulers"
+                    ),
+                    .product(
+                        name: "ScrollViewController",
+                        package: "ScrollViewController"
                     )
                 ]
             ),
diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift
index ecb65d1f54eb3b05cda31850eb98431c6ec2d31f..3675b45050f041af457b16d9bc6105633dabab91 100644
--- a/Sources/App/AppDelegate.swift
+++ b/Sources/App/AppDelegate.swift
@@ -1,6 +1,7 @@
 import UIKit
 import BackgroundTasks
 
+import XXModels
 import Theme
 import XXLogger
 import Defaults
@@ -91,7 +92,10 @@ public class AppDelegate: UIResponder, UIApplicationDelegate {
                 guard UIApplication.shared.backgroundTimeRemaining > 9 else {
                     if !self.forceFailedPendingMessages {
                         self.forceFailedPendingMessages = true
-                        session.forceFailMessages()
+
+                        let query = Message.Query(status: [.sending])
+                        let assignment = Message.Assignments(status: .sendingFailed)
+                        _ = try? session.dbManager.bulkUpdateMessages(query, assignment)
                     }
 
                     return
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index b342330e9012ba1a245d39cbd26f47602c074db4..9102274af7c0c37ce986b8aba6b2607a33cd66ca 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -259,7 +259,7 @@ extension PushRouter {
                     }
                 case .contactChat(id: let id):
                     if let session = try? DependencyInjection.Container.shared.resolve() as SessionType,
-                       let contact = session.getContactWith(userId: id) {
+                       let contact = try? session.dbManager.fetchContacts(.init(id: [id])).first {
                         navigationController.setViewControllers([
                             ChatListController(),
                             SingleChatController(contact)
@@ -267,7 +267,7 @@ extension PushRouter {
                     }
                 case .groupChat(id: let id):
                     if let session = try? DependencyInjection.Container.shared.resolve() as SessionType,
-                       let info = session.getGroupChatInfoWith(groupId: id) {
+                       let info = try? session.dbManager.fetchGroupInfos(.init(groupId: id)).first {
                         navigationController.setViewControllers([
                             ChatListController(),
                             GroupChatController(info)
diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift
index e248c053bc68431171a9817454d68fffb6f6a4a2..b168e70c438b6660f637758270bc28761dfa5c0c 100644
--- a/Sources/ChatFeature/Controllers/GroupChatController.swift
+++ b/Sources/ChatFeature/Controllers/GroupChatController.swift
@@ -3,6 +3,7 @@ import Theme
 import Models
 import Shared
 import Combine
+import XXModels
 import Voxophone
 import ChatLayout
 import DrawerFeature
@@ -32,13 +33,13 @@ public final class GroupChatController: UIViewController {
     private let layoutDelegate = LayoutDelegate()
     private var cancellables = Set<AnyCancellable>()
     private var drawerCancellables = Set<AnyCancellable>()
-    private var sections = [ArraySection<ChatSection, GroupChatItem>]()
+    private var sections = [ArraySection<ChatSection, Message>]()
     private var currentInterfaceActions = SetActor<Set<InterfaceActions>, ReactionTypes>()
 
     public override var canBecomeFirstResponder: Bool { true }
     public override var inputAccessoryView: UIView? { inputComponent }
 
-    public init(_ info: GroupChatInfo) {
+    public init(_ info: GroupInfo) {
         let viewModel = GroupChatViewModel(info)
         self.viewModel = viewModel
         self.members = .init(with: info.members)
@@ -59,7 +60,14 @@ public final class GroupChatController: UIViewController {
 
         super.init(nibName: nil, bundle: nil)
 
-        header.setup(title: info.group.name, memberList: info.members.map { ($0.username, $0.photo) })
+        let memberList = info.members.map {
+            Member(
+                title: ($0.nickname ?? $0.username) ?? "Fetching username...",
+                photo: $0.photo
+            )
+        }
+
+        header.setup(title: info.group.name, memberList: memberList)
     }
 
     public required init?(coder: NSCoder) { nil }
@@ -154,7 +162,9 @@ public final class GroupChatController: UIViewController {
 
         viewModel.replyPublisher
             .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in inputComponent.setupReply(message: $0.text, sender: $0.sender) }
+            .sink { [unowned self] senderTitle, messageText in
+                inputComponent.setupReply(message: messageText, sender: senderTitle)
+            }
             .store(in: &cancellables)
     }
 
@@ -317,9 +327,7 @@ extension GroupChatController: UICollectionViewDataSource {
 
         let item = sections[indexPath.section].elements[indexPath.item]
         let canReply: () -> Bool = {
-            item.status == .sent ||
-            item.status == .received ||
-            item.status == .read
+            (item.status == .sent || item.status == .received) && item.networkId != nil
         }
 
         let performReply: () -> Void = { [weak self] in
@@ -327,21 +335,18 @@ extension GroupChatController: UICollectionViewDataSource {
         }
 
         let name: (Data) -> String = viewModel.getName(from:)
-        let text: (Data) -> String = viewModel.getText(from:)
         let showRound: (String?) -> Void = viewModel.showRoundFrom(_:)
+        let replyContent: (Data) -> (String, String) = viewModel.getReplyContent(for:)
 
         if item.status == .received {
-            if item.payload.reply != nil {
+            if let replyMessageId = item.replyMessageId {
                 let cell: IncomingGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
                 Bubbler.buildReplyGroup(
                     bubble: cell.leftView,
                     with: item,
-                    reply: .init(
-                        text: text(item.payload.reply!.messageId),
-                        sender: name(item.payload.reply!.senderId)
-                    ),
-                    sender: name(item.sender)
+                    reply: replyContent(replyMessageId),
+                    sender: name(item.senderId)
                 )
 
                 cell.canReply = canReply()
@@ -351,25 +356,27 @@ extension GroupChatController: UICollectionViewDataSource {
                 return cell
             } else {
                 let cell: IncomingGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-                Bubbler.buildGroup(bubble: cell.leftView, with: item, with: name(item.sender))
+                Bubbler.buildGroup(
+                    bubble: cell.leftView,
+                    with: item,
+                    with: name(item.senderId)
+                )
+
                 cell.canReply = canReply()
                 cell.performReply = performReply
                 cell.leftView.didTapShowRound = { showRound(item.roundURL) }
 
                 return cell
             }
-        } else if item.status == .failed {
-            if item.payload.reply != nil {
+        } else if item.status == .sendingFailed {
+            if let replyMessageId = item.replyMessageId {
                 let cell: OutgoingFailedGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
                 Bubbler.buildReplyGroup(
                     bubble: cell.rightView,
                     with: item,
-                    reply: .init(
-                        text: text(item.payload.reply!.messageId),
-                        sender: name(item.payload.reply!.senderId)
-                    ),
-                    sender: name(item.sender)
+                    reply: replyContent(replyMessageId),
+                    sender: name(item.senderId)
                 )
 
                 cell.canReply = canReply()
@@ -379,24 +386,26 @@ extension GroupChatController: UICollectionViewDataSource {
             } else {
                 let cell: OutgoingFailedGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-                Bubbler.buildGroup(bubble: cell.rightView, with: item, with: name(item.sender))
+                Bubbler.buildGroup(
+                    bubble: cell.rightView,
+                    with: item,
+                    with: name(item.senderId)
+                )
+
                 cell.canReply = canReply()
                 cell.performReply = performReply
 
                 return cell
             }
         } else {
-            if item.payload.reply != nil {
+            if let replyMessageId = item.replyMessageId {
                 let cell: OutgoingGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
                 Bubbler.buildReplyGroup(
                     bubble: cell.rightView,
                     with: item,
-                    reply: .init(
-                        text: text(item.payload.reply!.messageId),
-                        sender: name(item.payload.reply!.senderId)
-                    ),
-                    sender: name(item.sender)
+                    reply: replyContent(replyMessageId),
+                    sender: name(item.senderId)
                 )
 
                 cell.canReply = canReply()
@@ -407,7 +416,12 @@ extension GroupChatController: UICollectionViewDataSource {
             } else {
                 let cell: OutgoingGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-                Bubbler.buildGroup(bubble: cell.rightView, with: item, with: name(item.sender))
+                Bubbler.buildGroup(
+                    bubble: cell.rightView,
+                    with: item,
+                    with: name(item.senderId)
+                )
+
                 cell.canReply = canReply()
                 cell.performReply = performReply
                 cell.rightView.didTapShowRound = { showRound(item.roundURL) }
@@ -527,7 +541,7 @@ extension GroupChatController: UICollectionViewDelegate {
             let item = self.sections[indexPath.section].elements[indexPath.item]
 
             let copy = UIAction(title: Localized.Chat.BubbleMenu.copy, state: .off) { _ in
-                UIPasteboard.general.string = item.payload.text
+                UIPasteboard.general.string = item.text
             }
 
             let reply = UIAction(title: Localized.Chat.BubbleMenu.reply, state: .off) { [weak self] _ in
@@ -544,7 +558,7 @@ extension GroupChatController: UICollectionViewDelegate {
 
             let menu: UIMenu
 
-            if item.status == .failed {
+            if item.status == .sendingFailed {
                 menu = UIMenu(title: "", children: [copy, retry, delete])
             } else if item.status == .sending {
                 menu = UIMenu(title: "", children: [copy])
diff --git a/Sources/ChatFeature/Controllers/MembersController.swift b/Sources/ChatFeature/Controllers/MembersController.swift
index 488ad42517153bfa8fcb492dad11ced22ebf5a6e..e7bf2c3ece345ace155ed7babbf5c9ec05bf1caf 100644
--- a/Sources/ChatFeature/Controllers/MembersController.swift
+++ b/Sources/ChatFeature/Controllers/MembersController.swift
@@ -1,13 +1,14 @@
 import UIKit
 import Models
 import Shared
+import XXModels
 
 final class MembersController: UIViewController {
     lazy private var stackView = UIStackView()
 
-    private let members: [GroupMember]
+    private let members: [Contact]
 
-    init(with members: [GroupMember]) {
+    init(with members: [Contact]) {
         self.members = members
         super.init(nibName: nil, bundle: nil)
     }
@@ -25,16 +26,17 @@ final class MembersController: UIViewController {
         stackView.distribution = .fillEqually
         view.addSubview(stackView)
 
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(10)
-            make.left.right.equalToSuperview()
-            make.bottom.equalTo(view.safeAreaLayoutGuide)
+        stackView.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(10)
+            $0.left.right.equalToSuperview()
+            $0.bottom.equalTo(view.safeAreaLayoutGuide)
         }
 
-        for member in members {
+        members.forEach {
             let memberView = MemberView()
-            memberView.titleLabel.text = member.username
-            memberView.avatarView.setupProfile(title: member.username, image: member.photo, size: .small)
+            let assignedTitle = ($0.nickname ?? $0.username) ?? "Fetching username..."
+            memberView.titleLabel.text = assignedTitle
+            memberView.avatarView.setupProfile(title: assignedTitle, image: $0.photo, size: .small)
             stackView.addArrangedSubview(memberView)
         }
     }
@@ -56,24 +58,24 @@ private final class MemberView: UIView {
         addSubview(avatarView)
         addSubview(separatorView)
 
-        avatarView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(10)
-            make.width.height.equalTo(30)
-            make.left.equalToSuperview().offset(25)
-            make.centerY.equalToSuperview()
+        avatarView.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(10)
+            $0.width.height.equalTo(30)
+            $0.left.equalToSuperview().offset(25)
+            $0.centerY.equalToSuperview()
         }
 
-        titleLabel.snp.makeConstraints { make in
-            make.centerY.equalTo(avatarView)
-            make.left.equalTo(avatarView.snp.right).offset(14)
-            make.right.lessThanOrEqualToSuperview().offset(-10)
+        titleLabel.snp.makeConstraints {
+            $0.centerY.equalTo(avatarView)
+            $0.left.equalTo(avatarView.snp.right).offset(14)
+            $0.right.lessThanOrEqualToSuperview().offset(-10)
         }
 
-        separatorView.snp.makeConstraints { make in
-            make.height.equalTo(1)
-            make.left.equalToSuperview().offset(25)
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
+        separatorView.snp.makeConstraints {
+            $0.height.equalTo(1)
+            $0.left.equalToSuperview().offset(25)
+            $0.right.equalToSuperview()
+            $0.bottom.equalToSuperview()
         }
     }
 
diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift
index b6fd74a15936d7e89da27d03e555e5e16291af3e..b35d314725ea97149e84edb8b9525e1fc010d4f6 100644
--- a/Sources/ChatFeature/Controllers/SingleChatController.swift
+++ b/Sources/ChatFeature/Controllers/SingleChatController.swift
@@ -1,5 +1,4 @@
 import HUD
-import DrawerFeature
 import UIKit
 import Theme
 import Models
@@ -7,8 +6,10 @@ import Shared
 import Combine
 import XXLogger
 import QuickLook
+import XXModels
 import Voxophone
 import ChatLayout
+import DrawerFeature
 import DifferenceKit
 import ChatInputFeature
 import DependencyInjection
@@ -18,6 +19,10 @@ extension FlexibleSpace: CollectionCellContent {
     func prepareForReuse() {}
 }
 
+extension Message: Differentiable {
+    public var differenceIdentifier: Int64 { id! }
+}
+
 public final class SingleChatController: UIViewController {
     @Dependency private var hud: HUDType
     @Dependency private var logger: XXLogger
@@ -43,7 +48,7 @@ public final class SingleChatController: UIViewController {
     private let layoutDelegate = LayoutDelegate()
     private var cancellables = Set<AnyCancellable>()
     private var drawerCancellables = Set<AnyCancellable>()
-    private var sections = [ArraySection<ChatSection, ChatItem>]()
+    private var sections = [ArraySection<ChatSection, Message>]()
     private var currentInterfaceActions: SetActor<Set<InterfaceActions>, ReactionTypes> = SetActor()
 
     var fileURL: URL?
@@ -153,11 +158,13 @@ public final class SingleChatController: UIViewController {
     }
 
     private func setupNavigationBar(contact: Contact) {
-        screenView.set(name: contact.nickname ?? contact.username)
+        screenView.set(name: contact.nickname ?? contact.username!)
         avatarView.snp.makeConstraints { $0.width.height.equalTo(35) }
-        avatarView.setupProfile(title: contact.nickname ?? contact.username, image: contact.photo, size: .small)
 
-        nameLabel.text = contact.nickname ?? contact.username
+        let title = (contact.nickname ?? contact.username) ?? ""
+        avatarView.setupProfile(title: title, image: contact.photo, size: .small)
+
+        nameLabel.text = title
         nameLabel.textColor = Asset.neutralActive.color
         nameLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
 
@@ -203,7 +210,9 @@ public final class SingleChatController: UIViewController {
 
         viewModel.replyPublisher
             .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in inputComponent.setupReply(message: $0.text, sender: $0.sender) }
+            .sink { [unowned self] senderTitle, messageText in
+                inputComponent.setupReply(message: messageText, sender: senderTitle)
+            }
             .store(in: &cancellables)
 
         viewModel.navigation
@@ -435,8 +444,12 @@ public final class SingleChatController: UIViewController {
 
     private func previewItemAt(_ indexPath: IndexPath) {
         let item = sections[indexPath.section].elements[indexPath.item]
-        guard let attachment = item.payload.attachment, item.status != .receivingAttachment else { return }
-        fileURL = FileManager.url(for: "\(attachment.name).\(attachment._extension.written)")
+        guard let ftid = item.fileTransferId,
+              item.status != .receiving,
+              item.status != .receivingFailed else { return }
+
+        let ft = viewModel.getFileTransferWith(id: ftid)
+        fileURL = FileManager.url(for: "\(ft.name).\(ft.type)")
         coordinator.toPreview(from: self)
     }
 
@@ -482,23 +495,22 @@ extension SingleChatController: UICollectionViewDataSource {
         cellForItemAt indexPath: IndexPath
     ) -> UICollectionViewCell {
 
-        let name: (Data) -> String = viewModel.getName(from:)
-        let text: (Data) -> String = viewModel.getText(from:)
         let showRound: (String?) -> Void = viewModel.showRoundFrom(_:)
         let item = sections[indexPath.section].elements[indexPath.item]
+        let replyContent: (Data) -> (String, String) = viewModel.getReplyContent(for:)
         let performReply: () -> Void = { [weak self] in self?.viewModel.didRequestReply(item) }
 
         let factory = CellFactory.combined(factories: [
-            .incomingImage(),
-            .outgoingImage(),
-            .incomingAudio(voxophone: voxophone),
-            .outgoingAudio(voxophone: voxophone),
+            .incomingImage(transfer: viewModel.getFileTransferWith(id:)),
+            .outgoingImage(transfer: viewModel.getFileTransferWith(id:)),
+            .incomingAudio(voxophone: voxophone, transfer: viewModel.getFileTransferWith(id:)),
+            .outgoingAudio(voxophone: voxophone, transfer: viewModel.getFileTransferWith(id:)),
             .incomingText(performReply: performReply, showRound: showRound),
             .outgoingText(performReply: performReply, showRound: showRound),
             .outgoingFailedText(performReply: performReply),
-            .incomingReply(performReply: performReply, name: name, text: text, showRound: showRound),
-            .outgoingReply(performReply: performReply, name: name, text: text, showRound: showRound),
-            .outgoingFailedReply(performReply: performReply, name: name, text: text)
+            .incomingReply(performReply: performReply, replyContent: replyContent, showRound: showRound),
+            .outgoingReply(performReply: performReply, replyContent: replyContent, showRound: showRound),
+            .outgoingFailedReply(performReply: performReply, replyContent: replyContent)
         ])
 
         return factory(item: item, collectionView: collectionView, indexPath: indexPath)
@@ -561,7 +573,7 @@ extension SingleChatController: UICollectionViewDelegate {
 
         let status = sections[section].elements[item].status
 
-        if status == .received || status == .read || status == .receivingAttachment {
+        if status == .received || status == .receiving {
             var leftView: UIView!
 
             if let cell = cell as? IncomingReplyCell {
diff --git a/Sources/ChatFeature/Coordinator/ChatCoordinator.swift b/Sources/ChatFeature/Coordinator/ChatCoordinator.swift
index 3c04dc1ed0513f95e3fef74248df0fb5edac6185..64a8e46de2eada72bba4b371bed2b1d441a2b846 100644
--- a/Sources/ChatFeature/Coordinator/ChatCoordinator.swift
+++ b/Sources/ChatFeature/Coordinator/ChatCoordinator.swift
@@ -4,6 +4,7 @@ import Shared
 import QuickLook
 import Permissions
 import Presentation
+import XXModels
 
 public protocol ChatCoordinating {
     func toCamera(from: UIViewController)
diff --git a/Sources/ChatFeature/Helpers/BubbleBuilder.swift b/Sources/ChatFeature/Helpers/BubbleBuilder.swift
index 3f9da31df8b01008102bb6bd066a5b27a69ea326..ae33fd01e60bfaf501583779e11eb59575444b4f 100644
--- a/Sources/ChatFeature/Helpers/BubbleBuilder.swift
+++ b/Sources/ChatFeature/Helpers/BubbleBuilder.swift
@@ -1,28 +1,29 @@
 import UIKit
 import Shared
+import XXModels
 
 final class Bubbler {
     static func build(
         audioBubble: AudioMessageView,
-        with item: ChatItem
+        with item: Message
     ) {
         audioBubble.dateLabel.text = item.date.asHoursAndMinutes()
 
         switch item.status {
-        case .received, .read:
+        case .received:
             audioBubble.lockerImageView.removeFromSuperview()
             audioBubble.backgroundColor = Asset.neutralWhite.color
             audioBubble.dateLabel.textColor = Asset.neutralDisabled.color
             audioBubble.progressLabel.textColor = Asset.neutralDisabled.color
-        case .receivingAttachment:
+        case .receiving:
             audioBubble.backgroundColor = Asset.neutralWhite.color
             audioBubble.dateLabel.textColor = Asset.neutralDisabled.color
             audioBubble.progressLabel.textColor = Asset.neutralDisabled.color
-        case .timedOut:
+        case .sendingTimedOut:
             audioBubble.backgroundColor = Asset.accentWarning.color
             audioBubble.dateLabel.textColor = Asset.neutralWhite.color
             audioBubble.progressLabel.textColor = Asset.neutralWhite.color
-        case .failedToSend:
+        case .sendingFailed:
             audioBubble.backgroundColor = Asset.accentDanger.color
             audioBubble.dateLabel.textColor = Asset.neutralWhite.color
             audioBubble.progressLabel.textColor = Asset.neutralWhite.color
@@ -30,36 +31,40 @@ final class Bubbler {
             audioBubble.backgroundColor = Asset.brandBubble.color
             audioBubble.dateLabel.textColor = Asset.neutralWhite.color
             audioBubble.progressLabel.textColor = Asset.neutralWhite.color
-        case .sending, .sendingAttachment:
+        case .sending:
             audioBubble.backgroundColor = Asset.brandBubble.color
             audioBubble.dateLabel.textColor = Asset.neutralWhite.color
             audioBubble.progressLabel.textColor = Asset.neutralWhite.color
+        case .receivingFailed:
+            audioBubble.backgroundColor = Asset.accentWarning.color
+            audioBubble.dateLabel.textColor = Asset.neutralWhite.color
+            audioBubble.progressLabel.textColor = Asset.neutralWhite.color
         }
     }
 
     static func build(
         imageBubble: ImageMessageView,
-        with item: ChatItem
+        with message: Message,
+        with transfer: FileTransfer
     ) {
-        let progress = item.payload.attachment!.progress
-        imageBubble.progressLabel.text = String(format: "%.1f%%", progress * 100)
-        imageBubble.dateLabel.text = item.date.asHoursAndMinutes()
+        imageBubble.progressLabel.text = String(format: "%.1f%%", transfer.progress * 100)
+        imageBubble.dateLabel.text = message.date.asHoursAndMinutes()
 
-        switch item.status {
-        case .received, .read:
+        switch message.status {
+        case .received:
             imageBubble.lockerImageView.removeFromSuperview()
             imageBubble.backgroundColor = Asset.neutralWhite.color
             imageBubble.dateLabel.textColor = Asset.neutralDisabled.color
             imageBubble.progressLabel.textColor = Asset.neutralDisabled.color
-        case .receivingAttachment:
+        case .receiving:
             imageBubble.backgroundColor = Asset.neutralWhite.color
             imageBubble.dateLabel.textColor = Asset.neutralDisabled.color
             imageBubble.progressLabel.textColor = Asset.neutralDisabled.color
-        case .failedToSend:
+        case .sendingFailed:
             imageBubble.backgroundColor = Asset.accentDanger.color
             imageBubble.dateLabel.textColor = Asset.neutralWhite.color
             imageBubble.progressLabel.textColor = Asset.neutralWhite.color
-        case .timedOut:
+        case .sendingTimedOut:
             imageBubble.backgroundColor = Asset.accentWarning.color
             imageBubble.dateLabel.textColor = Asset.neutralWhite.color
             imageBubble.progressLabel.textColor = Asset.neutralWhite.color
@@ -67,37 +72,41 @@ final class Bubbler {
             imageBubble.backgroundColor = Asset.brandBubble.color
             imageBubble.dateLabel.textColor = Asset.neutralWhite.color
             imageBubble.progressLabel.textColor = Asset.neutralWhite.color
-        case .sending, .sendingAttachment:
+        case .sending:
             imageBubble.backgroundColor = Asset.brandBubble.color
             imageBubble.dateLabel.textColor = Asset.neutralWhite.color
             imageBubble.progressLabel.textColor = Asset.neutralWhite.color
+        case .receivingFailed:
+            imageBubble.backgroundColor = Asset.accentWarning.color
+            imageBubble.dateLabel.textColor = Asset.neutralWhite.color
+            imageBubble.progressLabel.textColor = Asset.neutralWhite.color
         }
     }
 
     static func build(
         bubble: StackMessageView,
-        with item: ChatItem
+        with item: Message
     ) {
-        bubble.textView.text = item.payload.text
+        bubble.textView.text = item.text
         bubble.senderLabel.removeFromSuperview()
         bubble.dateLabel.text = item.date.asHoursAndMinutes()
 
         let roundButtonColor: UIColor
 
         switch item.status {
-        case .received, .read, .receivingAttachment:
+        case .received, .receiving:
             bubble.lockerImageView.removeFromSuperview()
             bubble.backgroundColor = Asset.neutralWhite.color
             bubble.textView.textColor = Asset.neutralActive.color
             bubble.dateLabel.textColor = Asset.neutralDisabled.color
             roundButtonColor = Asset.neutralDisabled.color
             bubble.revertBottomStackOrder()
-        case .timedOut:
+        case .sendingTimedOut:
             bubble.backgroundColor = Asset.accentWarning.color
             bubble.textView.textColor = Asset.neutralWhite.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
-        case .failedToSend:
+        case .sendingFailed:
             bubble.backgroundColor = Asset.accentDanger.color
             bubble.textView.textColor = Asset.neutralWhite.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
@@ -107,11 +116,16 @@ final class Bubbler {
             bubble.textView.textColor = Asset.neutralWhite.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
-        case .sending, .sendingAttachment:
+        case .sending:
             bubble.backgroundColor = Asset.brandBubble.color
             bubble.textView.textColor = Asset.neutralWhite.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
+        case .receivingFailed:
+            bubble.backgroundColor = Asset.accentWarning.color
+            bubble.textView.textColor = Asset.neutralWhite.color
+            bubble.dateLabel.textColor = Asset.neutralWhite.color
+            roundButtonColor = Asset.neutralWhite.color
         }
 
         let attrString = NSAttributedString(
@@ -127,37 +141,61 @@ final class Bubbler {
         bubble.roundButton.setAttributedTitle(attrString, for: .normal)
     }
 
-    static func buildGroup(
-        bubble: StackMessageView,
-        with item: GroupChatItem,
-        with senderName: String
+    static func buildReply(
+        bubble: ReplyStackMessageView,
+        with item: Message,
+        reply: (contactTitle: String, messageText: String)
     ) {
-        bubble.textView.text = item.payload.text
         bubble.dateLabel.text = item.date.asHoursAndMinutes()
+        bubble.textView.text = item.text
+
+        bubble.replyView.message.text = reply.messageText
+        bubble.replyView.title.text = reply.contactTitle
 
         let roundButtonColor: UIColor
 
         switch item.status {
-        case .received, .read:
-            bubble.senderLabel.text = senderName
+        case .received, .receiving:
+            bubble.senderLabel.removeFromSuperview()
             bubble.backgroundColor = Asset.neutralWhite.color
             bubble.textView.textColor = Asset.neutralActive.color
             bubble.dateLabel.textColor = Asset.neutralDisabled.color
             roundButtonColor = Asset.neutralDisabled.color
-            bubble.lockerImageView.removeFromSuperview()
+            bubble.replyView.container.backgroundColor = Asset.brandDefault.color
+            bubble.replyView.space.backgroundColor = Asset.brandPrimary.color
             bubble.revertBottomStackOrder()
-        case .failed:
+        case .sendingTimedOut:
+            bubble.senderLabel.removeFromSuperview()
+            bubble.backgroundColor = Asset.accentWarning.color
+            bubble.textView.textColor = Asset.neutralWhite.color
+            bubble.dateLabel.textColor = Asset.neutralWhite.color
+            roundButtonColor = Asset.neutralWhite.color
+            bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
+            bubble.replyView.container.backgroundColor = Asset.brandLight.color
+        case .sendingFailed:
             bubble.senderLabel.removeFromSuperview()
             bubble.backgroundColor = Asset.accentDanger.color
             bubble.textView.textColor = Asset.neutralWhite.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
+            bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
+            bubble.replyView.container.backgroundColor = Asset.brandLight.color
         case .sent, .sending:
             bubble.senderLabel.removeFromSuperview()
+            bubble.textView.textColor = Asset.neutralWhite.color
             bubble.backgroundColor = Asset.brandBubble.color
+            bubble.dateLabel.textColor = Asset.neutralWhite.color
+            roundButtonColor = Asset.neutralWhite.color
+            bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
+            bubble.replyView.container.backgroundColor = Asset.brandLight.color
+        case .receivingFailed:
+            bubble.senderLabel.removeFromSuperview()
             bubble.textView.textColor = Asset.neutralWhite.color
+            bubble.backgroundColor = Asset.accentWarning.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
+            bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
+            bubble.replyView.container.backgroundColor = Asset.brandLight.color
         }
 
         let attrString = NSAttributedString(
@@ -169,53 +207,54 @@ final class Bubbler {
                 .font: Fonts.Mulish.regular.font(size: 12.0) as Any
             ]
         )
-
         bubble.roundButton.setAttributedTitle(attrString, for: .normal)
     }
 
-    static func buildReply(
+    static func buildReplyGroup(
         bubble: ReplyStackMessageView,
-        with item: ChatItem,
-        reply: ReplyModel
+        with item: Message,
+        reply: (contactTitle: String, messageText: String),
+        sender: String
     ) {
         bubble.dateLabel.text = item.date.asHoursAndMinutes()
-        bubble.textView.text = item.payload.text
+        bubble.textView.text = item.text
 
-        bubble.replyView.message.text = reply.text
-        bubble.replyView.title.text = reply.sender
+        bubble.replyView.message.text = reply.messageText
+        bubble.replyView.title.text = reply.contactTitle
 
         let roundButtonColor: UIColor
 
         switch item.status {
-        case .received, .read, .receivingAttachment:
-            bubble.senderLabel.removeFromSuperview()
+        case .received, .receiving:
+            bubble.senderLabel.text = sender
             bubble.backgroundColor = Asset.neutralWhite.color
             bubble.textView.textColor = Asset.neutralActive.color
             bubble.dateLabel.textColor = Asset.neutralDisabled.color
             roundButtonColor = Asset.neutralDisabled.color
             bubble.replyView.container.backgroundColor = Asset.brandDefault.color
             bubble.replyView.space.backgroundColor = Asset.brandPrimary.color
+            bubble.lockerImageView.removeFromSuperview()
             bubble.revertBottomStackOrder()
-        case .timedOut:
+        case .sendingFailed, .sendingTimedOut:
             bubble.senderLabel.removeFromSuperview()
-            bubble.backgroundColor = Asset.accentWarning.color
+            bubble.backgroundColor = Asset.accentDanger.color
             bubble.textView.textColor = Asset.neutralWhite.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
             bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
             bubble.replyView.container.backgroundColor = Asset.brandLight.color
-        case .failedToSend:
+        case .sent, .sending:
             bubble.senderLabel.removeFromSuperview()
-            bubble.backgroundColor = Asset.accentDanger.color
             bubble.textView.textColor = Asset.neutralWhite.color
+            bubble.backgroundColor = Asset.brandBubble.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
             bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
             bubble.replyView.container.backgroundColor = Asset.brandLight.color
-        case .sent, .sending, .sendingAttachment:
+        case .receivingFailed:
             bubble.senderLabel.removeFromSuperview()
             bubble.textView.textColor = Asset.neutralWhite.color
-            bubble.backgroundColor = Asset.brandBubble.color
+            bubble.backgroundColor = Asset.accentWarning.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
             bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
@@ -231,50 +270,47 @@ final class Bubbler {
                 .font: Fonts.Mulish.regular.font(size: 12.0) as Any
             ]
         )
+
         bubble.roundButton.setAttributedTitle(attrString, for: .normal)
     }
 
-    static func buildReplyGroup(
-        bubble: ReplyStackMessageView,
-        with item: GroupChatItem,
-        reply: ReplyModel,
-        sender: String
+    static func buildGroup(
+        bubble: StackMessageView,
+        with item: Message,
+        with senderName: String
     ) {
+        bubble.textView.text = item.text
         bubble.dateLabel.text = item.date.asHoursAndMinutes()
-        bubble.textView.text = item.payload.text
-
-        bubble.replyView.message.text = reply.text
-        bubble.replyView.title.text = reply.sender
 
         let roundButtonColor: UIColor
 
         switch item.status {
-        case .received, .read:
-            bubble.senderLabel.text = sender
+        case .received, .receiving:
+            bubble.senderLabel.text = senderName
             bubble.backgroundColor = Asset.neutralWhite.color
             bubble.textView.textColor = Asset.neutralActive.color
             bubble.dateLabel.textColor = Asset.neutralDisabled.color
             roundButtonColor = Asset.neutralDisabled.color
-            bubble.replyView.container.backgroundColor = Asset.brandDefault.color
-            bubble.replyView.space.backgroundColor = Asset.brandPrimary.color
             bubble.lockerImageView.removeFromSuperview()
             bubble.revertBottomStackOrder()
-        case .failed:
+        case .sendingFailed, .sendingTimedOut:
             bubble.senderLabel.removeFromSuperview()
             bubble.backgroundColor = Asset.accentDanger.color
             bubble.textView.textColor = Asset.neutralWhite.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
-            bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
-            bubble.replyView.container.backgroundColor = Asset.brandLight.color
         case .sent, .sending:
             bubble.senderLabel.removeFromSuperview()
-            bubble.textView.textColor = Asset.neutralWhite.color
             bubble.backgroundColor = Asset.brandBubble.color
+            bubble.textView.textColor = Asset.neutralWhite.color
+            bubble.dateLabel.textColor = Asset.neutralWhite.color
+            roundButtonColor = Asset.neutralWhite.color
+        case .receivingFailed:
+            bubble.senderLabel.removeFromSuperview()
+            bubble.backgroundColor = Asset.accentWarning.color
+            bubble.textView.textColor = Asset.neutralWhite.color
             bubble.dateLabel.textColor = Asset.neutralWhite.color
             roundButtonColor = Asset.neutralWhite.color
-            bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
-            bubble.replyView.container.backgroundColor = Asset.brandLight.color
         }
 
         let attrString = NSAttributedString(
@@ -289,4 +325,6 @@ final class Bubbler {
 
         bubble.roundButton.setAttributedTitle(attrString, for: .normal)
     }
+
+
 }
diff --git a/Sources/ChatFeature/Helpers/CellConfigurator.swift b/Sources/ChatFeature/Helpers/CellConfigurator.swift
index 5f2cb1b6b3348382304af392042bae3184010302..c59050a9221da32149bda8432c69d9271f08a0b7 100644
--- a/Sources/ChatFeature/Helpers/CellConfigurator.swift
+++ b/Sources/ChatFeature/Helpers/CellConfigurator.swift
@@ -1,16 +1,17 @@
 import UIKit
 import Shared
 import Combine
+import XXModels
 import Voxophone
 import AVFoundation
 
 struct CellFactory {
-    var canBuild: (ChatItem) -> Bool
+    var canBuild: (Message) -> Bool
 
-    var build: (ChatItem, UICollectionView, IndexPath) -> UICollectionViewCell
+    var build: (Message, UICollectionView, IndexPath) -> UICollectionViewCell
 
     func callAsFunction(
-        item: ChatItem,
+        item: Message,
         collectionView: UICollectionView,
         indexPath: IndexPath
     ) -> UICollectionViewCell {
@@ -39,33 +40,34 @@ extension CellFactory {
 
 extension CellFactory {
     static func incomingAudio(
-        voxophone: Voxophone
+        voxophone: Voxophone,
+        transfer: @escaping (Data) -> FileTransfer
     ) -> Self {
         .init(
             canBuild: { item in
-                (item.status == .received || item.status == .read || item.status == .receivingAttachment)
-                && item.payload.reply == nil
-                && item.payload.attachment != nil
-                && item.payload.attachment?._extension == .audio
+                guard (item.status == .received || item.status == .receiving),
+                      item.replyMessageId == nil,
+                      item.fileTransferId != nil else { return false }
 
-            }, build: { item, collectionView, indexPath in
-                guard let attachment = item.payload.attachment else { fatalError() }
+                return transfer(item.fileTransferId!).type == "m4a"
 
+            }, build: { item, collectionView, indexPath in
+                let ft = transfer(item.fileTransferId!)
                 let cell: IncomingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-                let url = FileManager.url(for: "\(attachment.name).\(attachment._extension.written)")!
+                let url = FileManager.url(for: ft.name)!
 
                 var model = AudioMessageCellState(
                     date: item.date,
                     audioURL: url,
                     isPlaying: false,
-                    transferProgress: attachment.progress,
+                    transferProgress: ft.progress,
                     isLoudspeaker: false,
                     duration: (try? AVAudioPlayer(contentsOf: url).duration) ?? 0.0,
                     playbackTime: 0.0
                 )
 
                 cell.leftView.setup(with: model)
-                cell.canReply = item.status.canReply
+                cell.canReply = false
                 cell.performReply = {}
 
                 Bubbler.build(audioBubble: cell.leftView, with: item)
@@ -87,13 +89,13 @@ extension CellFactory {
                     }.store(in: &cell.leftView.cancellables)
 
                 cell.leftView.didTapRight = {
-                    guard item.status != .receivingAttachment else { return }
+                    guard item.status != .receiving else { return }
 
                     voxophone.toggleLoudspeaker()
                 }
 
                 cell.leftView.didTapLeft = {
-                    guard item.status != .receivingAttachment else { return }
+                    guard item.status != .receiving else { return }
 
                     if case .playing(url, _, _, _) = voxophone.state {
                         voxophone.reset()
@@ -109,35 +111,38 @@ extension CellFactory {
     }
 
     static func outgoingAudio(
-        voxophone: Voxophone
+        voxophone: Voxophone,
+        transfer: @escaping (Data) -> FileTransfer
     ) -> Self {
         .init(
             canBuild: { item in
-                (item.status == .sent ||
-                 item.status == .failedToSend ||
-                 item.status == .sendingAttachment ||
-                 item.status == .timedOut)
-                && item.payload.reply == nil
-                && item.payload.attachment != nil
-                && item.payload.attachment?._extension == .audio
+                guard (item.status == .sent ||
+                       item.status == .sending ||
+                       item.status == .sendingFailed ||
+                       item.status == .sendingTimedOut)
+                        && item.replyMessageId == nil
+                        && item.fileTransferId != nil else {
+                    return false
+                }
 
-            }, build: { item, collectionView, indexPath in
-                guard let attachment = item.payload.attachment else { fatalError() }
+                return transfer(item.fileTransferId!).type == "m4a"
 
+            }, build: { item, collectionView, indexPath in
+                let ft = transfer(item.fileTransferId!)
                 let cell: OutgoingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-                let url = FileManager.url(for: "\(attachment.name).\(attachment._extension.written)")!
+                let url = FileManager.url(for: ft.name)!
                 var model = AudioMessageCellState(
                     date: item.date,
                     audioURL: url,
                     isPlaying: false,
-                    transferProgress: attachment.progress,
+                    transferProgress: ft.progress,
                     isLoudspeaker: false,
                     duration: (try? AVAudioPlayer(contentsOf: url).duration) ?? 0.0,
                     playbackTime: 0.0
                 )
 
                 cell.rightView.setup(with: model)
-                cell.canReply = item.status.canReply
+                cell.canReply = false
                 cell.performReply = {}
 
                 Bubbler.build(audioBubble: cell.rightView, with: item)
@@ -178,28 +183,31 @@ extension CellFactory {
 }
 
 extension CellFactory {
-    static func outgoingImage() -> Self {
+    static func outgoingImage(
+        transfer:  @escaping (Data) -> FileTransfer
+    ) -> Self {
         .init(
             canBuild: { item in
-                (item.status == .sent ||
-                 item.status == .failedToSend ||
-                 item.status == .sendingAttachment ||
-                 item.status == .timedOut)
-                && item.payload.reply == nil
-                && item.payload.attachment != nil
-                && item.payload.attachment?._extension == .image
+                guard (item.status == .sent ||
+                       item.status == .sending ||
+                       item.status == .sendingFailed ||
+                       item.status == .sendingTimedOut)
+                        && item.replyMessageId == nil
+                        && item.fileTransferId != nil else {
+                    return false
+                }
 
-            }, build: { item, collectionView, indexPath in
-                guard let attachment = item.payload.attachment else { fatalError() }
+                return transfer(item.fileTransferId!).type == "jpeg"
 
+            }, build: { item, collectionView, indexPath in
+                let ft = transfer(item.fileTransferId!)
                 let cell: OutgoingImageCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-                Bubbler.build(imageBubble: cell.rightView, with: item)
-
-                cell.canReply = item.status.canReply
+                Bubbler.build(imageBubble: cell.rightView, with: item, with: transfer(item.fileTransferId!))
+                cell.canReply = false
                 cell.performReply = {}
 
-                if let image = UIImage(data: attachment.data!) {
+                if let image = UIImage(data: ft.data!) {
                     cell.rightView.imageView.image = UIImage(cgImage: image.cgImage!, scale: image.scale, orientation: .up)
                 }
 
@@ -208,23 +216,33 @@ extension CellFactory {
         )
     }
 
-    static func incomingImage() -> Self {
+    static func incomingImage(
+        transfer: @escaping (Data) -> FileTransfer
+    ) -> Self {
         .init(
             canBuild: { item in
-                (item.status == .received || item.status == .read || item.status == .receivingAttachment)
-                && item.payload.reply == nil
-                && item.payload.attachment != nil
-                && item.payload.attachment?._extension == .image
+                guard (item.status == .received || item.status == .receiving)
+                        && item.replyMessageId == nil
+                        && item.fileTransferId != nil else {
+                    return false
+                }
 
-            }, build: { item, collectionView, indexPath in
-                guard let attachment = item.payload.attachment else { fatalError() }
+                return transfer(item.fileTransferId!).type == "jpeg"
 
+            }, build: { item, collectionView, indexPath in
+                let ft = transfer(item.fileTransferId!)
                 let cell: IncomingImageCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-                Bubbler.build(imageBubble: cell.leftView, with: item)
-                cell.canReply = item.status.canReply
+                Bubbler.build(imageBubble: cell.leftView, with: item, with: ft)
+                cell.canReply = false
                 cell.performReply = {}
-                cell.leftView.imageView.image = UIImage(data: attachment.data!)
+
+                if let data = ft.data {
+                    cell.leftView.imageView.image = UIImage(data: data)
+                } else {
+                    cell.leftView.imageView.image = Asset.transferImagePlaceholder.image
+                }
+
                 return cell
             }
         )
@@ -234,15 +252,13 @@ extension CellFactory {
 extension CellFactory {
     static func outgoingReply(
         performReply: @escaping () -> Void,
-        name: @escaping (Data) -> String,
-        text: @escaping (Data) -> String,
+        replyContent: @escaping (Data) -> (String, String),
         showRound: @escaping (String?) -> Void
     ) -> Self {
         .init(
             canBuild: { item in
                 (item.status == .sent || item.status == .sending)
-                && item.payload.reply != nil
-                && item.payload.attachment == nil
+                && item.replyMessageId != nil
 
             }, build: { item, collectionView, indexPath in
                 let cell: OutgoingReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
@@ -250,13 +266,10 @@ extension CellFactory {
                 Bubbler.buildReply(
                     bubble: cell.rightView,
                     with: item,
-                    reply: .init(
-                        text: text(item.payload.reply!.messageId),
-                        sender: name(item.payload.reply!.senderId)
-                    )
+                    reply: replyContent(item.replyMessageId!)
                 )
 
-                cell.canReply = item.status.canReply
+                cell.canReply = item.status == .sent
                 cell.performReply = performReply
                 cell.rightView.didTapShowRound = { showRound(item.roundURL) }
                 return cell
@@ -266,15 +279,13 @@ extension CellFactory {
 
     static func incomingReply(
         performReply: @escaping () -> Void,
-        name: @escaping (Data) -> String,
-        text: @escaping (Data) -> String,
+        replyContent: @escaping (Data) -> (String, String),
         showRound: @escaping (String?) -> Void
     ) -> Self {
         .init(
             canBuild: { item in
-                (item.status == .received || item.status == .read)
-                && item.payload.reply != nil
-                && item.payload.attachment == nil
+                item.status == .received
+                && item.replyMessageId != nil
 
             }, build: { item, collectionView, indexPath in
                 let cell: IncomingReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
@@ -282,12 +293,9 @@ extension CellFactory {
                 Bubbler.buildReply(
                     bubble: cell.leftView,
                     with: item,
-                    reply: .init(
-                        text: text(item.payload.reply!.messageId),
-                        sender: name(item.payload.reply!.senderId)
-                    )
+                    reply: replyContent(item.replyMessageId!)
                 )
-                cell.canReply = item.status.canReply
+                cell.canReply = item.status == .received
                 cell.performReply = performReply
                 cell.leftView.didTapShowRound = { showRound(item.roundURL) }
                 cell.leftView.revertBottomStackOrder()
@@ -298,14 +306,12 @@ extension CellFactory {
 
     static func outgoingFailedReply(
         performReply: @escaping () -> Void,
-        name: @escaping (Data) -> String,
-        text: @escaping (Data) -> String
+        replyContent: @escaping (Data) -> (String, String)
     ) -> Self {
         .init(
             canBuild: { item in
-                (item.status == .failedToSend || item.status == .timedOut)
-                && item.payload.reply != nil
-                && item.payload.attachment == nil
+                (item.status == .sendingFailed || item.status == .sendingTimedOut)
+                && item.replyMessageId != nil
 
             }, build: { item, collectionView, indexPath in
                 let cell: OutgoingFailedReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
@@ -313,13 +319,10 @@ extension CellFactory {
                 Bubbler.buildReply(
                     bubble: cell.rightView,
                     with: item,
-                    reply: .init(
-                        text: text(item.payload.reply!.messageId),
-                        sender: name(item.payload.reply!.senderId)
-                    )
+                    reply: replyContent(item.replyMessageId!)
                 )
 
-                cell.canReply = item.status.canReply
+                cell.canReply = false
                 cell.performReply = performReply
                 return cell
             }
@@ -334,15 +337,14 @@ extension CellFactory {
     ) -> Self {
         .init(
             canBuild: { item in
-                (item.status == .received || item.status == .read)
-                && item.payload.reply == nil
-                && item.payload.attachment == nil
+                item.status == .received
+                && item.replyMessageId == nil
 
             }, build: { item, collectionView, indexPath in
                 let cell: IncomingTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
                 Bubbler.build(bubble: cell.leftView, with: item)
-                cell.canReply = item.status.canReply
+                cell.canReply = item.status == .received
                 cell.performReply = performReply
                 cell.leftView.didTapShowRound = { showRound(item.roundURL) }
                 cell.leftView.revertBottomStackOrder()
@@ -358,14 +360,13 @@ extension CellFactory {
         .init(
             canBuild: { item in
                 (item.status == .sending || item.status == .sent)
-                && item.payload.reply == nil
-                && item.payload.attachment == nil
+                && item.replyMessageId == nil
 
             }, build: { item, collectionView, indexPath in
                 let cell: OutgoingTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
                 Bubbler.build(bubble: cell.rightView, with: item)
-                cell.canReply = item.status.canReply
+                cell.canReply = item.status == .sent
                 cell.performReply = performReply
                 cell.rightView.didTapShowRound = { showRound(item.roundURL) }
 
@@ -377,15 +378,14 @@ extension CellFactory {
     static func outgoingFailedText(performReply: @escaping () -> Void) -> Self {
         .init(
             canBuild: { item in
-                (item.status == .failedToSend || item.status == .timedOut)
-                && item.payload.reply == nil
-                && item.payload.attachment == nil
+                (item.status == .sendingFailed || item.status == .sendingTimedOut)
+                && item.replyMessageId == nil
 
             }, build: { item, collectionView, indexPath in
                 let cell: OutgoingFailedTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
                 Bubbler.build(bubble: cell.rightView, with: item)
-                cell.canReply = item.status.canReply
+                cell.canReply = false
                 cell.performReply = performReply
                 return cell
             }
@@ -416,18 +416,16 @@ struct ActionFactory {
     }
 
     static func build(
-        from item: ChatItem,
+        from item: Message,
         action: Action,
-        closure: @escaping (ChatItem) -> Void
+        closure: @escaping (Message) -> Void
     ) -> UIAction? {
 
-        guard item.payload.attachment == nil else { return nil }
-
         switch action {
         case .reply:
-            guard item.status == .read || item.status == .received || item.status == .sent else { return nil }
+            guard item.status == .received || item.status == .sent else { return nil }
         case .retry:
-            guard item.status == .failedToSend || item.status == .timedOut else { return nil }
+            guard item.status == .sendingFailed || item.status == .sendingTimedOut else { return nil }
         case .delete, .copy:
             break
         }
diff --git a/Sources/ChatFeature/Models/ChatItem.swift b/Sources/ChatFeature/Models/ChatItem.swift
deleted file mode 100644
index 9e68dcf4c13386cd04d390d2530007db6c8cdb5c..0000000000000000000000000000000000000000
--- a/Sources/ChatFeature/Models/ChatItem.swift
+++ /dev/null
@@ -1,43 +0,0 @@
-import Models
-import Foundation
-import DifferenceKit
-
-struct ChatItem: Equatable, Differentiable {
-    let date: Date
-    var uniqueId: Data?
-    let identity: Int64
-    var roundURL: String?
-    let payload: Payload
-    var status: Message.Status
-    var differenceIdentifier: Int64 { identity }
-
-    init(_ message: Message) {
-        self.identity = message.id!
-        self.status = message.status
-        self.payload = message.payload
-        self.roundURL = message.roundURL
-        self.uniqueId = message.uniqueId
-        self.date = Date.fromTimestamp(message.timestamp)
-    }
-}
-
-struct GroupChatItem: Equatable, Differentiable {
-    let date: Date
-    let sender: Data
-    let identity: Int64
-    var uniqueId: Data?
-    var roundURL: String?
-    let payload: Payload
-    var status: GroupMessage.Status
-    var differenceIdentifier: Int64 { identity }
-
-    init(_ groupMessage: GroupMessage) {
-        self.identity = groupMessage.id!
-        self.status = groupMessage.status
-        self.roundURL = groupMessage.roundURL
-        self.sender = groupMessage.sender
-        self.payload = groupMessage.payload
-        self.uniqueId = groupMessage.uniqueId
-        self.date = Date.fromTimestamp(groupMessage.timestamp)
-    }
-}
diff --git a/Sources/ChatFeature/Models/ChatSection.swift b/Sources/ChatFeature/Models/ChatSection.swift
index cb755bf94795c6a69eb9549b5cacbb023aca1e65..a91c016fd166a1f7b2e41286186dd2cae1f2b340 100644
--- a/Sources/ChatFeature/Models/ChatSection.swift
+++ b/Sources/ChatFeature/Models/ChatSection.swift
@@ -2,11 +2,6 @@ import Foundation
 import DifferenceKit
 
 struct ChatSection: Equatable, Differentiable {
-    // MARK: Properties
-
     var date: Date
-
-    // MARK: DifferenceKit
-
     var differenceIdentifier: Date { date }
 }
diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
index ad29c602f94972faaec2aec031838e81b45c2ca6..aa1dbefc8ed34f582d84df609d70b65631c69013 100644
--- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Models
 import Combine
+import XXModels
 import Foundation
 import Integration
 import DifferenceKit
@@ -14,7 +15,7 @@ enum GroupChatNavigationRoutes: Equatable {
 final class GroupChatViewModel {
     @Dependency private var session: SessionType
 
-    var replyPublisher: AnyPublisher<ReplyModel, Never> {
+    var replyPublisher: AnyPublisher<(String, String), Never> {
         replySubject.eraseToAnyPublisher()
     }
 
@@ -22,17 +23,17 @@ final class GroupChatViewModel {
         routesSubject.eraseToAnyPublisher()
     }
 
-    let info: GroupChatInfo
+    let info: GroupInfo
     private var stagedReply: Reply?
     private var cancellables = Set<AnyCancellable>()
-    private let replySubject = PassthroughSubject<ReplyModel, Never>()
+    private let replySubject = PassthroughSubject<(String, String), Never>()
     private let routesSubject = PassthroughSubject<GroupChatNavigationRoutes, Never>()
 
-    var messages: AnyPublisher<[ArraySection<ChatSection, GroupChatItem>], Never> {
-        session.groupMessages(info.group)
-            .map { messages -> [ArraySection<ChatSection, GroupChatItem>] in
-                let domainModels = messages.map { GroupChatItem($0) }
-                let groupedByDate = Dictionary(grouping: domainModels) { domainModel -> Date in
+    var messages: AnyPublisher<[ArraySection<ChatSection, Message>], Never> {
+        session.dbManager.fetchMessagesPublisher(.init(chat: .group(info.group.id)))
+            .assertNoFailure()
+            .map { messages -> [ArraySection<ChatSection, Message>] in
+                let groupedByDate = Dictionary(grouping: messages) { domainModel -> Date in
                     let components = Calendar.current.dateComponents([.day, .month, .year], from: domainModel.date)
                     return Calendar.current.date(from: components)!
                 }
@@ -41,36 +42,38 @@ final class GroupChatViewModel {
                     .map { .init(model: ChatSection(date: $0.key), elements: $0.value) }
                     .sorted(by: { $0.model.date < $1.model.date })
             }
-            .map { sections -> [ArraySection<ChatSection, GroupChatItem>] in
-            var snapshot = [ArraySection<ChatSection, GroupChatItem>]()
+            .map { sections -> [ArraySection<ChatSection, Message>] in
+            var snapshot = [ArraySection<ChatSection, Message>]()
             sections.forEach { snapshot.append(.init(model: $0.model, elements: $0.elements)) }
             return snapshot
         }.eraseToAnyPublisher()
     }
 
-    init(_ info: GroupChatInfo) {
+    init(_ info: GroupInfo) {
         self.info = info
     }
 
     func readAll() {
-        session.readAll(from: info.group)
+        let assignment = Message.Assignments(isUnread: false)
+        let query = Message.Query(chat: .group(info.group.id))
+        _ = try? session.dbManager.bulkUpdateMessages(query, assignment)
     }
 
-    func didRequestDelete(_ items: [GroupChatItem]) {
-        session.delete(groupMessages: items.map { $0.identity })
+    func didRequestDelete(_ messages: [Message]) {
+        _ = try? session.dbManager.deleteMessages(.init(id: Set(messages.map(\.id))))
     }
 
     func send(_ text: String) {
         session.send(.init(
             text: text.trimmingCharacters(in: .whitespacesAndNewlines),
-            reply: stagedReply,
-            attachment: nil
+            reply: stagedReply
         ), toGroup: info.group)
         stagedReply = nil
     }
 
-    func retry(_ model: GroupChatItem) {
-        session.retryGroupMessage(model.identity)
+    func retry(_ message: Message) {
+        guard let id = message.id else { return }
+        session.retryMessage(id)
     }
 
     func showRoundFrom(_ roundURL: String?) {
@@ -85,19 +88,27 @@ final class GroupChatViewModel {
         stagedReply = nil
     }
 
-    func getName(from senderId: Data) -> String {
-        guard let member = info.members.first(where: { $0.userId == senderId }) else { return "You" }
-        return member.username
-    }
+    func getReplyContent(for messageId: Data) -> (String, String) {
+        guard let message = try? session.dbManager.fetchMessages(.init(networkId: messageId)).first else {
+            return ("[DELETED]", "[DELETED]")
+        }
 
-    func getText(from messageId: Data) -> String {
-        session.getTextFromGroupMessage(messageId: messageId) ?? "[DELETED]"
+        return (getName(from: message.senderId), message.text)
     }
 
-    func didRequestReply(_ model: GroupChatItem) {
-        guard let messageId = model.uniqueId else { return }
+    func getName(from senderId: Data) -> String {
+        guard senderId != session.myId else { return "You" }
+
+        guard let contact = try? session.dbManager.fetchContacts(.init(id: [senderId])).first else {
+            return "[DELETED]"
+        }
+
+        return (contact.nickname ?? contact.username) ?? "Fetching username..."
+    }
 
-        stagedReply = Reply(messageId: messageId, senderId: model.sender)
-        replySubject.send(.init(text: model.payload.text, sender: getName(from: model.sender)))
+    func didRequestReply(_ message: Message) {
+        guard let networkId = message.networkId else { return }
+        stagedReply = Reply(messageId: networkId, senderId: message.senderId)
+        replySubject.send(getReplyContent(for: networkId))
     }
 }
diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
index d1be4b6ee2b69fb786f1d371162c4f4d8c420f1c..cd78765910d84d7242ee96ff145c3cd612c2492d 100644
--- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
@@ -4,17 +4,13 @@ import Models
 import Shared
 import Combine
 import XXLogger
+import XXModels
 import Foundation
 import Integration
 import Permissions
 import DifferenceKit
 import DependencyInjection
 
-struct ReplyModel {
-    var text: String
-    var sender: String
-}
-
 enum SingleChatNavigationRoutes: Equatable {
     case none
     case camera
@@ -35,22 +31,22 @@ final class SingleChatViewModel {
     private var stagedReply: Reply?
     private var cancellables = Set<AnyCancellable>()
     private let contactSubject: CurrentValueSubject<Contact, Never>
-    private let replySubject = PassthroughSubject<ReplyModel, Never>()
+    private let replySubject = PassthroughSubject<(String, String), Never>()
     private let navigationRoutes = PassthroughSubject<SingleChatNavigationRoutes, Never>()
-    private let sectionsRelay = CurrentValueSubject<[ArraySection<ChatSection, ChatItem>], Never>([])
+    private let sectionsRelay = CurrentValueSubject<[ArraySection<ChatSection, Message>], Never>([])
 
     var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
     private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
 
     var isOnline: AnyPublisher<Bool, Never> { session.isOnline }
     var contactPublisher: AnyPublisher<Contact, Never> { contactSubject.eraseToAnyPublisher() }
-    var replyPublisher: AnyPublisher<ReplyModel, Never> { replySubject.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() }
 
-    var messages: AnyPublisher<[ArraySection<ChatSection, ChatItem>], Never> {
-        sectionsRelay.map { sections -> [ArraySection<ChatSection, ChatItem>] in
-            var snapshot = [ArraySection<ChatSection, ChatItem>]()
+    var messages: AnyPublisher<[ArraySection<ChatSection, Message>], Never> {
+        sectionsRelay.map { sections -> [ArraySection<ChatSection, Message>] in
+            var snapshot = [ArraySection<ChatSection, Message>]()
             sections.forEach { snapshot.append(.init(model: $0.model, elements: $0.elements)) }
             return snapshot
         }.eraseToAnyPublisher()
@@ -60,7 +56,7 @@ final class SingleChatViewModel {
         if contact.isRecent == true {
             var contact = contact
             contact.isRecent = false
-            session.update(contact)
+            _ = try? session.dbManager.saveContact(contact)
         }
     }
 
@@ -73,17 +69,16 @@ final class SingleChatViewModel {
 
         updateRecentState(contact)
 
-        session.contacts(.withUserId(contact.userId))
+        session.dbManager.fetchContactsPublisher(Contact.Query(id: [contact.id]))
+            .assertNoFailure()
             .compactMap { $0.first }
             .sink { [unowned self] in contactSubject.send($0) }
             .store(in: &cancellables)
 
-        session.singleMessages(contact)
-            .map { $0.sorted(by: { $0.timestamp < $1.timestamp }) }
-            .map { messages in
-
-                let domainModels = messages.map { ChatItem($0) }
-                let groupedByDate = Dictionary(grouping: domainModels) { domainModel -> Date in
+        session.dbManager.fetchMessagesPublisher(.init(chat: .direct(session.myId, contact.id)))
+            .assertNoFailure()
+            .map {
+                let groupedByDate = Dictionary(grouping: $0) { domainModel -> Date in
                     let components = Calendar.current.dateComponents([.day, .month, .year], from: domainModel.date)
                     return Calendar.current.date(from: components)!
                 }
@@ -98,13 +93,16 @@ final class SingleChatViewModel {
 
     // MARK: Public
 
-    func didSendAudio(url: URL) {
-        let name = url.deletingPathExtension().lastPathComponent
-        guard let file = FileManager.retrieve(name: name, type: Attachment.Extension.audio.written) else { return }
+    func getFileTransferWith(id: Data) -> FileTransfer {
+        guard let transfer = try? session.dbManager.fetchFileTransfers(.init(id: [id])).first else {
+            fatalError()
+        }
 
-        let attachment = Attachment(name: name, data: file, _extension: .audio)
-        let payload = Payload(text: "You sent a voice message", reply: nil, attachment: attachment)
-        session.send(payload, toContact: contact)
+        return transfer
+    }
+
+    func didSendAudio(url: URL) {
+        session.sendFile(url: url, to: contact)
     }
 
     func didSend(image: UIImage) {
@@ -122,15 +120,18 @@ final class SingleChatViewModel {
     }
 
     func readAll() {
-        session.readAll(from: contact)
+        let assignment = Message.Assignments(isUnread: false)
+        let query = Message.Query(chat: .direct(session.myId, contact.id))
+        _ = try? session.dbManager.bulkUpdateMessages(query, assignment)
     }
 
     func didRequestDeleteAll() {
-        session.deleteAll(from: contact)
+        _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id)))
     }
 
-    func didRequestRetry(_ model: ChatItem) {
-        session.retryMessage(model.identity)
+    func didRequestRetry(_ message: Message) {
+        guard let id = message.id else { return }
+        session.retryMessage(id)
     }
    
     func didNavigateSomewhere() {
@@ -163,11 +164,11 @@ final class SingleChatViewModel {
         return false
     }
 
-    func didRequestCopy(_ model: ChatItem) {
-        UIPasteboard.general.string = model.payload.text
+    func didRequestCopy(_ model: Message) {
+        UIPasteboard.general.string = model.text
     }
 
-    func didRequestDeleteSingle(_ model: ChatItem) {
+    func didRequestDeleteSingle(_ model: Message) {
         didRequestDelete([model])
     }
 
@@ -177,21 +178,37 @@ final class SingleChatViewModel {
 
     func send(_ string: String) {
         let text = string.trimmingCharacters(in: .whitespacesAndNewlines)
-        let payload = Payload(text: text, reply: stagedReply, attachment: nil)
+        let payload = Payload(text: text, reply: stagedReply)
         session.send(payload, toContact: contact)
         stagedReply = nil
     }
 
-    func didRequestReply(_ model: ChatItem) {
-        guard let messageId = model.uniqueId else { return }
+    func didRequestReply(_ message: Message) {
+        guard let networkId = message.networkId else { return }
 
-        let isIncoming = model.status == .received || model.status == .read
-        stagedReply = Reply(messageId: messageId, senderId: isIncoming ? contact.userId : session.myId)
-        replySubject.send(.init(text: model.payload.text, sender: isIncoming ? contact.nickname ?? contact.username : "You"))
+        let senderTitle: String = {
+            if message.senderId == session.myId {
+                return "You"
+            } else {
+                return (contact.nickname ?? contact.username) ?? "Fetching username..."
+            }
+        }()
+
+        replySubject.send((senderTitle, message.text))
+        stagedReply = Reply(messageId: networkId, senderId: message.senderId)
     }
 
-    func getText(from messageId: Data) -> String {
-        session.getTextFromMessage(messageId: messageId) ?? "[DELETED]"
+    func getReplyContent(for messageId: Data) -> (String, String) {
+        guard let message = try? session.dbManager.fetchMessages(.init(networkId: messageId)).first else {
+            return ("[DELETED]", "[DELETED]")
+        }
+
+        guard let contact = try? session.dbManager.fetchContacts(.init(id: [message.senderId])).first else {
+            fatalError()
+        }
+
+        let contactTitle = (contact.nickname ?? contact.username) ?? "You"
+        return (contactTitle, message.text)
     }
 
     func showRoundFrom(_ roundURL: String?) {
@@ -202,19 +219,15 @@ final class SingleChatViewModel {
         }
     }
 
-    func didRequestDelete(_ items: [ChatItem]) {
-        session.delete(messages: items.map { $0.identity })
-    }
-
-    func itemWith(id: Int64) -> ChatItem? {
-        sectionsRelay.value.flatMap(\.elements).first(where: { $0.identity == id })
+    func didRequestDelete(_ items: [Message]) {
+        _ = try? session.dbManager.deleteMessages(.init(id: Set(items.compactMap(\.id))))
     }
 
-    func getName(from senderId: Data) -> String {
-        senderId == session.myId ? "You" : contact.nickname ?? contact.username
+    func itemWith(id: Int64) -> Message? {
+        sectionsRelay.value.flatMap(\.elements).first(where: { $0.id == id })
     }
 
-    func itemAt(indexPath: IndexPath) -> ChatItem? {
+    func itemAt(indexPath: IndexPath) -> Message? {
         guard sectionsRelay.value.count > indexPath.section else { return nil }
 
         let items = sectionsRelay.value[indexPath.section].elements
diff --git a/Sources/ChatFeature/Views/GroupHeaderView.swift b/Sources/ChatFeature/Views/GroupHeaderView.swift
index d85a49abe97265a8b0ba5bd1dec227b45e97c9ec..b33415707304f070f172764a5ee8a49d89a1a10d 100644
--- a/Sources/ChatFeature/Views/GroupHeaderView.swift
+++ b/Sources/ChatFeature/Views/GroupHeaderView.swift
@@ -1,6 +1,11 @@
 import UIKit
 import Shared
 
+struct Member {
+    let title: String
+    let photo: Data?
+}
+
 final class GroupHeaderView: UIView {
     let titleLabel = UILabel()
     let containerView = UIView()
@@ -39,14 +44,14 @@ final class GroupHeaderView: UIView {
 
     required init?(coder: NSCoder) { nil }
 
-    func setup(title: String, memberList: [(String, Data?)]) {
+    func setup(title: String, memberList: [Member]) {
         titleLabel.text = title
 
-        for member in memberList {
+        memberList.forEach {
             let avatarView = AvatarView()
             avatarView.layer.borderWidth = 3
             avatarView.layer.borderColor = UIColor.white.cgColor
-            avatarView.setupProfile(title: member.0, image: member.1, size: .small)
+            avatarView.setupProfile(title: $0.title, image: $0.photo, size: .small)
             avatarView.snp.makeConstraints { $0.width.height.equalTo(25.0) }
             stackView.addArrangedSubview(avatarView)
         }
diff --git a/Sources/ChatListFeature/Controller/ChatListController.swift b/Sources/ChatListFeature/Controller/ChatListController.swift
index d2d15a400f402ec532ee97a2407ccf88415c1c93..bddccd284ce09ecb2b9e479044aa8fb41eb5e757 100644
--- a/Sources/ChatListFeature/Controller/ChatListController.swift
+++ b/Sources/ChatListFeature/Controller/ChatListController.swift
@@ -3,6 +3,7 @@ import Theme
 import Models
 import Shared
 import Combine
+import XXModels
 import MenuFeature
 import DependencyInjection
 
@@ -115,7 +116,8 @@ public final class ChatListController: UIViewController {
             collectionView: screenView.listContainerView.collectionView
         ) { collectionView, indexPath, contact in
             let cell: ChatListRecentContactCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-            cell.setup(title: contact.nickname ?? contact.username, image: contact.photo)
+            let title = (contact.nickname ?? contact.username) ?? ""
+            cell.setup(title: title, image: contact.photo)
             return cell
         }
 
diff --git a/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift b/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift
index c1abc94ac78f3f3e95f1058a2e8ede9021d11616..46e9c20bed9b21b96d883f0ffc07db81caad0de9 100644
--- a/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift
+++ b/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift
@@ -32,49 +32,41 @@ final class ChatSearchTableController: UITableViewController {
         ) { table, indexPath, item in
             let cell = table.dequeueReusableCell(forIndexPath: indexPath, ofType: ChatListCell.self)
             switch item {
-            case .chat(let subitem):
-                if case .contact(let info) = subitem {
-                    cell.setupContact(
-                        name: info.contact.nickname ?? info.contact.username,
-                        image: info.contact.photo,
-                        date: Date.fromTimestamp(info.lastMessage!.timestamp),
-                        hasUnread: info.lastMessage!.unread,
-                        preview: info.lastMessage!.payload.text
+            case .chat(let info):
+                switch info {
+                case .group(let group):
+                    cell.setupGroup(
+                        name: group.name,
+                        date: group.createdAt,
+                        preview: nil,
+                        unreadCount: 0
                     )
-                }
-
-                if case .group(let info) = subitem {
-                    let date: Date = {
-                        guard let lastMessage = info.lastMessage else {
-                            return info.group.createdAt
-                        }
-
-                        return Date.fromTimestamp(lastMessage.timestamp)
-                    }()
-
-                    let hasUnread: Bool = {
-                        guard let lastMessage = info.lastMessage else {
-                            return false
-                        }
-
-                        return lastMessage.unread
-                    }()
 
+                case .groupChat(let groupChatInfo):
                     cell.setupGroup(
-                        name: info.group.name,
-                        date: date,
-                        preview: info.lastMessage?.payload.text,
-                        hasUnread: hasUnread
+                        name: groupChatInfo.group.name,
+                        date: groupChatInfo.lastMessage.date,
+                        preview: groupChatInfo.lastMessage.text,
+                        unreadCount: groupChatInfo.unreadCount
+                    )
+
+                case .contactChat(let contactChatInfo):
+                    cell.setupContact(
+                        name: (contactChatInfo.contact.nickname ?? contactChatInfo.contact.username) ?? "",
+                        image: contactChatInfo.contact.photo,
+                        date: contactChatInfo.lastMessage.date,
+                        unreadCount: contactChatInfo.unreadCount,
+                        preview: contactChatInfo.lastMessage.text
                     )
                 }
 
             case .connection(let contact):
                 cell.setupContact(
-                    name: contact.nickname ?? contact.username,
+                    name: (contact.nickname ?? contact.username) ?? "",
                     image: contact.photo,
                     date: nil,
-                    hasUnread: false,
-                    preview: contact.username
+                    unreadCount: 0,
+                    preview: contact.username ?? ""
                 )
             }
 
@@ -112,14 +104,23 @@ extension ChatSearchTableController {
     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         if let item = tableDataSource?.itemIdentifier(for: indexPath) {
             switch item {
-            case .chat(let chat):
-                switch chat {
-                case .contact(let info):
-                    guard info.contact.status == .friend else { return }
+            case .chat(let chatInfo):
+                switch chatInfo {
+                case .group(let group):
+                    if let groupInfo = viewModel.groupInfo(from: group) {
+                        coordinator.toGroupChat(with: groupInfo, from: self)
+                    }
+
+                case .groupChat(let info):
+                    if let groupInfo = viewModel.groupInfo(from: info.group) {
+                        coordinator.toGroupChat(with: groupInfo, from: self)
+                    }
+
+                case .contactChat(let info):
+                    guard info.contact.authStatus == .friend else { return }
                     coordinator.toSingleChat(with: info.contact, from: self)
-                case .group(let info):
-                    coordinator.toGroupChat(with: info, from: self)
                 }
+
             case .connection(let contact):
                 coordinator.toContact(contact, from: self)
             }
diff --git a/Sources/ChatListFeature/Controller/ChatListTableController.swift b/Sources/ChatListFeature/Controller/ChatListTableController.swift
index 17b744113f0ecc43eb229f62cd5f4ddd5d8da385..8527195eee326146909a865a6768e553c10a5e3d 100644
--- a/Sources/ChatListFeature/Controller/ChatListTableController.swift
+++ b/Sources/ChatListFeature/Controller/ChatListTableController.swift
@@ -2,14 +2,19 @@ import UIKit
 import Shared
 import Models
 import Combine
+import XXModels
 import DifferenceKit
 import DrawerFeature
 import DependencyInjection
 
+extension ChatInfo: Differentiable {
+    public var differenceIdentifier: ChatInfo.ID { id }
+}
+
 final class ChatListTableController: UITableViewController {
     @Dependency private var coordinator: ChatListCoordinating
 
-    private var rows = [Chat]()
+    private var rows = [ChatInfo]()
     private let viewModel: ChatListViewModel
     private let cellHeight: CGFloat = 83.0
     private var cancellables = Set<AnyCancellable>()
@@ -89,11 +94,19 @@ extension ChatListTableController {
 
     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         switch rows[indexPath.row] {
-        case .contact(let info):
-            guard info.contact.status == .friend else { return }
+        case .group(let group):
+            if let groupInfo = viewModel.groupInfo(from: group) {
+                coordinator.toGroupChat(with: groupInfo, from: self)
+            }
+
+        case .groupChat(let info):
+            if let groupInfo = viewModel.groupInfo(from: info.group) {
+                coordinator.toGroupChat(with: groupInfo, from: self)
+            }
+
+        case .contactChat(let info):
+            guard info.contact.authStatus == .friend else { return }
             coordinator.toSingleChat(with: info.contact, from: self)
-        case .group(let info):
-            coordinator.toGroupChat(with: info, from: self)
         }
     }
 
@@ -104,62 +117,60 @@ extension ChatListTableController {
 
         let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: ChatListCell.self)
 
-        if case .contact(let info) = rows[indexPath.row] {
-            cell.setupContact(
-                name: info.contact.nickname ?? info.contact.username,
-                image: info.contact.photo,
-                date: Date.fromTimestamp(info.lastMessage!.timestamp),
-                hasUnread: info.lastMessage!.unread,
-                preview: info.lastMessage!.payload.text
+        switch rows[indexPath.row] {
+        case .group(let group):
+            cell.setupGroup(
+                name: group.name,
+                date: group.createdAt,
+                preview: nil,
+                unreadCount: 0
             )
-        }
-
-        if case .group(let info) = rows[indexPath.row] {
-            let date: Date = {
-                guard let lastMessage = info.lastMessage else {
-                    return info.group.createdAt
-                }
-
-                return Date.fromTimestamp(lastMessage.timestamp)
-            }()
-
-            let hasUnread: Bool = {
-                guard let lastMessage = info.lastMessage else {
-                    return false
-                }
-
-                return lastMessage.unread
-            }()
 
+        case .groupChat(let info):
             cell.setupGroup(
                 name: info.group.name,
-                date: date,
-                preview: info.lastMessage?.payload.text,
-                hasUnread: hasUnread
+                date: info.lastMessage.date,
+                preview: info.lastMessage.text,
+                unreadCount: info.unreadCount
+            )
+
+        case .contactChat(let info):
+            cell.setupContact(
+                name: (info.contact.nickname ?? info.contact.username) ?? "",
+                image: info.contact.photo,
+                date: info.lastMessage.date,
+                unreadCount: info.unreadCount,
+                preview: info.lastMessage.text
             )
         }
 
         return cell
     }
 
-    private func didRequestDeletionOf(_ item: Chat) {
+    private func didRequestDeletionOf(_ item: ChatInfo) {
         let title: String
         let subtitle: String
         let actionTitle: String
         let actionClosure: () -> Void
 
         switch item {
-        case .group(let info):
+        case .group(let group):
             title = Localized.ChatList.DeleteGroup.title
             subtitle = Localized.ChatList.DeleteGroup.subtitle
             actionTitle = Localized.ChatList.DeleteGroup.action
-            actionClosure = { [weak viewModel] in viewModel?.leave(info.group) }
+            actionClosure = { [weak viewModel] in viewModel?.leave(group) }
 
-        case .contact(let info):
+        case .contactChat(let info):
             title = Localized.ChatList.Delete.title
             subtitle = Localized.ChatList.Delete.subtitle
             actionTitle = Localized.ChatList.Delete.delete
             actionClosure = { [weak viewModel] in viewModel?.clear(info.contact) }
+
+        case .groupChat(let info):
+            title = Localized.ChatList.DeleteGroup.title
+            subtitle = Localized.ChatList.DeleteGroup.subtitle
+            actionTitle = Localized.ChatList.DeleteGroup.action
+            actionClosure = { [weak viewModel] in viewModel?.leave(info.group) }
         }
 
         let actionButton = DrawerCapsuleButton(model: .init(title: actionTitle, style: .red))
diff --git a/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift b/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift
index 42b7dda644aa04aa7f928ad8006d59715d442b4b..bb412859b16fe99591c8b658fc4b8798fe27fb00 100644
--- a/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift
+++ b/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import Models
+import XXModels
 import MenuFeature
 import ChatFeature
 import Presentation
@@ -16,7 +17,7 @@ public protocol ChatListCoordinating {
     func toContact(_: Contact, from: UIViewController)
     func toSingleChat(with: Contact, from: UIViewController)
     func toDrawer(_: UIViewController, from: UIViewController)
-    func toGroupChat(with: GroupChatInfo, from: UIViewController)
+    func toGroupChat(with: GroupInfo, from: UIViewController)
 }
 
 public struct ChatListCoordinator: ChatListCoordinating {
@@ -31,7 +32,7 @@ public struct ChatListCoordinator: ChatListCoordinating {
     var contactsFactory: () -> UIViewController
     var contactFactory: (Contact) -> UIViewController
     var singleChatFactory: (Contact) -> UIViewController
-    var groupChatFactory: (GroupChatInfo) -> UIViewController
+    var groupChatFactory: (GroupInfo) -> UIViewController
     var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController
 
     public init(
@@ -41,7 +42,7 @@ public struct ChatListCoordinator: ChatListCoordinating {
         contactsFactory: @escaping () -> UIViewController,
         contactFactory: @escaping (Contact) -> UIViewController,
         singleChatFactory: @escaping (Contact) -> UIViewController,
-        groupChatFactory: @escaping (GroupChatInfo) -> UIViewController,
+        groupChatFactory: @escaping (GroupInfo) -> UIViewController,
         sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController
     ) {
         self.scanFactory = scanFactory
@@ -81,7 +82,7 @@ public extension ChatListCoordinator {
         pushPresenter.present(screen, from: parent)
     }
 
-    func toGroupChat(with group: GroupChatInfo, from parent: UIViewController) {
+    func toGroupChat(with group: GroupInfo, from parent: UIViewController) {
         let screen = groupChatFactory(group)
         pushPresenter.present(screen, from: parent)
     }
diff --git a/Sources/ChatListFeature/Models/Chat.swift b/Sources/ChatListFeature/Models/Chat.swift
deleted file mode 100644
index 3e159479aeaade0a5b1355d19eccf627f5d5f3b7..0000000000000000000000000000000000000000
--- a/Sources/ChatListFeature/Models/Chat.swift
+++ /dev/null
@@ -1,34 +0,0 @@
-import Models
-import Foundation
-import DifferenceKit
-
-enum Chat: Equatable, Differentiable, Hashable {
-    case group(GroupChatInfo)
-    case contact(SingleChatInfo)
-
-    var differenceIdentifier: Data {
-        switch self {
-        case .contact(let info):
-            return info.contact.userId
-        case .group(let info):
-            return info.group.groupId
-        }
-    }
-
-    var orderingDate: Date {
-        switch self {
-        case .group(let info):
-            if let lastMessage = info.lastMessage {
-                return Date.fromTimestamp(lastMessage.timestamp)
-            } else {
-                return info.group.createdAt
-            }
-        case .contact(let info):
-            guard let lastMessage = info.lastMessage else {
-                fatalError("Should have an E2E chat without a last message")
-            }
-
-            return Date.fromTimestamp(lastMessage.timestamp)
-        }
-    }
-}
diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
index ceb096f383a164fd6ead74e9819089b4e45a5807..98febff09e352ff1e0ff495c032f1e7d1ec1613a 100644
--- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
+++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
@@ -3,6 +3,7 @@ import UIKit
 import Shared
 import Models
 import Combine
+import XXModels
 import Defaults
 import Integration
 import DependencyInjection
@@ -13,7 +14,7 @@ enum SearchSection {
 }
 
 enum SearchItem: Equatable, Hashable {
-    case chat(Chat)
+    case chat(ChatInfo)
     case connection(Contact)
 }
 
@@ -27,7 +28,7 @@ final class ChatListViewModel {
         session.isOnline
     }
 
-    var chatsPublisher: AnyPublisher<[Chat], Never> {
+    var chatsPublisher: AnyPublisher<[ChatInfo], Never> {
         chatsSubject.eraseToAnyPublisher()
     }
 
@@ -36,7 +37,9 @@ final class ChatListViewModel {
     }
 
     var recentsPublisher: AnyPublisher<RecentsSnapshot, Never> {
-        session.contacts(.isRecent).map {
+        session.dbManager.fetchContactsPublisher(.init(isRecent: true))
+            .assertNoFailure()
+            .map {
             let section = SectionId()
             var snapshot = RecentsSnapshot()
             snapshot.appendSections([section])
@@ -47,7 +50,7 @@ final class ChatListViewModel {
 
     var searchPublisher: AnyPublisher<SearchSnapshot, Never> {
         Publishers.CombineLatest3(
-            session.contacts(.all),
+            session.dbManager.fetchContactsPublisher(.init()).assertNoFailure(),
             chatsPublisher,
             searchSubject
                 .removeDuplicates()
@@ -56,23 +59,27 @@ final class ChatListViewModel {
         )
             .map { (contacts, chats, query) in
                 let connectionItems = contacts.filter {
-                    let username = $0.username.lowercased().contains(query.lowercased())
+                    let username = $0.username?.lowercased().contains(query.lowercased()) ?? false
                     let nickname = $0.nickname?.lowercased().contains(query.lowercased()) ?? false
                     return username || nickname
                 }.map(SearchItem.connection)
 
                 let chatItems = chats.filter {
                     switch $0 {
-                    case .contact(let info):
-                        let username = info.contact.username.lowercased().contains(query.lowercased())
-                        let nickname = info.contact.nickname?.lowercased().contains(query.lowercased()) ?? false
-                        let lastMessage = info.lastMessage?.payload.text.lowercased().contains(query.lowercased()) ?? false
-                        return username || nickname || lastMessage
+                    case .group(let group):
+                        return group.name.lowercased().contains(query.lowercased())
 
-                    case .group(let info):
+                    case .groupChat(let info):
                         let name = info.group.name.lowercased().contains(query.lowercased())
-                        let last = info.lastMessage?.payload.text.lowercased().contains(query.lowercased()) ?? false
+                        let last = info.lastMessage.text.lowercased().contains(query.lowercased())
                         return name || last
+
+                    case .contactChat(let info):
+                        let username = info.contact.username?.lowercased().contains(query.lowercased()) ?? false
+                        let nickname = info.contact.nickname?.lowercased().contains(query.lowercased()) ?? false
+                        let lastMessage = info.lastMessage.text.lowercased().contains(query.lowercased())
+                        return username || nickname || lastMessage
+
                     }
                 }.map(SearchItem.chat)
 
@@ -93,9 +100,18 @@ final class ChatListViewModel {
     }
 
     var badgeCountPublisher: AnyPublisher<Int, Never> {
-        Publishers.CombineLatest(
-            session.contacts(.received),
-            session.groups(.pending)
+        let groupQuery = Group.Query(authStatus: [.pending])
+        let contactsQuery = Contact.Query(authStatus: [
+            .verified,
+            .confirming,
+            .confirmationFailed,
+            .verificationFailed,
+            .verificationInProgress
+        ])
+
+        return Publishers.CombineLatest(
+            session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
+            session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure()
         )
         .map { $0.0.count + $0.1.count }
         .eraseToAnyPublisher()
@@ -103,20 +119,27 @@ final class ChatListViewModel {
 
     private var cancellables = Set<AnyCancellable>()
     private let searchSubject = CurrentValueSubject<String, Never>("")
-    private let chatsSubject = CurrentValueSubject<[Chat], Never>([])
+    private let chatsSubject = CurrentValueSubject<[ChatInfo], Never>([])
     private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
 
     init() {
-        Publishers.CombineLatest(
-            session.singleChats(.all),
-            session.groupChats(.accepted)
-        ).map {
-            let groups = $0.1.map(Chat.group)
-            let chats = $0.0.map(Chat.contact)
-            return (chats + groups).sorted { $0.orderingDate > $1.orderingDate }
-        }
-        .sink { [unowned self] in chatsSubject.send($0) }
-        .store(in: &cancellables)
+        session.dbManager.fetchChatInfosPublisher(
+            ChatInfo.Query(
+                contactChatInfoQuery: .init(
+                    userId: session.myId,
+                    authStatus: [.friend]
+                ),
+                groupChatInfoQuery: GroupChatInfo.Query(
+                    authStatus: [.participating]
+                ),
+                groupQuery: Group.Query(
+                    withMessages: false,
+                    authStatus: [.participating]
+                )
+            ))
+            .assertNoFailure()
+            .sink { [unowned self] in chatsSubject.send($0) }
+            .store(in: &cancellables)
     }
 
     func updateSearch(query: String) {
@@ -128,7 +151,7 @@ final class ChatListViewModel {
 
         do {
             try session.leave(group: group)
-            session.deleteAll(from: group)
+            try session.dbManager.deleteMessages(.init(chat: .group(group.id)))
             hudSubject.send(.none)
         } catch {
             hudSubject.send(.error(.init(with: error)))
@@ -136,6 +159,15 @@ final class ChatListViewModel {
     }
 
     func clear(_ contact: Contact) {
-        session.deleteAll(from: contact)
+        _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id)))
+    }
+
+    func groupInfo(from group: Group) -> GroupInfo? {
+        let query = GroupInfo.Query(groupId: group.id)
+        guard let info = try? session.dbManager.fetchGroupInfos(query).first else {
+            return nil
+        }
+
+        return info
     }
 }
diff --git a/Sources/ChatListFeature/Views/ChatListCell.swift b/Sources/ChatListFeature/Views/ChatListCell.swift
index 8eea62d2278b630efeb6afb1d12fad725cd23c18..bb455395abbc36921e364b27a5ca07e573726ea3 100644
--- a/Sources/ChatListFeature/Views/ChatListCell.swift
+++ b/Sources/ChatListFeature/Views/ChatListCell.swift
@@ -4,6 +4,7 @@ import Shared
 final class ChatListCell: UITableViewCell {
     private let titleLabel = UILabel()
     private let unreadView = UIView()
+    private let unreadCountLabel = UILabel()
     private let previewLabel = UILabel()
     private let dateLabel = UILabel()
     private let avatarView = AvatarView()
@@ -31,10 +32,11 @@ final class ChatListCell: UITableViewCell {
         backgroundColor = Asset.neutralWhite.color
         dateLabel.textColor = Asset.neutralWeak.color
         titleLabel.textColor = Asset.neutralActive.color
+        unreadCountLabel.textColor = Asset.neutralWhite.color
 
         dateLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
         titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
-
+        unreadCountLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
 
         timer = Timer.scheduledTimer(withTimeInterval: 59, repeats: true) { [weak self] _ in
             self?.updateTimeAgoLabel()
@@ -47,6 +49,7 @@ final class ChatListCell: UITableViewCell {
         contentView.addSubview(avatarView)
         contentView.addSubview(previewLabel)
         contentView.addSubview(dateLabel)
+        unreadView.addSubview(unreadCountLabel)
 
         avatarView.snp.makeConstraints {
             $0.top.equalToSuperview().offset(14)
@@ -54,6 +57,10 @@ final class ChatListCell: UITableViewCell {
             $0.width.height.equalTo(48)
         }
 
+        unreadCountLabel.snp.makeConstraints {
+            $0.center.equalToSuperview()
+        }
+
         titleLabel.snp.makeConstraints {
             $0.top.equalToSuperview().offset(10)
             $0.left.equalTo(avatarView.snp.right).offset(16)
@@ -85,6 +92,7 @@ final class ChatListCell: UITableViewCell {
         super.prepareForReuse()
         lastDate = nil
         titleLabel.text = nil
+        unreadCountLabel.text = nil
         previewLabel.attributedText = nil
         avatarView.prepareForReuse()
     }
@@ -99,13 +107,14 @@ final class ChatListCell: UITableViewCell {
         name: String,
         image: Data?,
         date: Date?,
-        hasUnread: Bool,
+        unreadCount: Int,
         preview: String
     ) {
         titleLabel.text = name
         setPreview(string: preview)
         avatarView.setupProfile(title: name, image: image, size: .large)
-        unreadView.backgroundColor = hasUnread ? Asset.brandPrimary.color : .clear
+        unreadCountLabel.text = "\(unreadCount)"
+        unreadView.backgroundColor = unreadCount > 0 ? Asset.brandPrimary.color : .clear
 
         if let date = date {
             lastDate = date
@@ -118,13 +127,14 @@ final class ChatListCell: UITableViewCell {
         name: String,
         date: Date,
         preview: String?,
-        hasUnread: Bool
+        unreadCount: Int
     ) {
         lastDate = date
         titleLabel.text = name
         setPreview(string: preview)
         avatarView.setupGroup(size: .large)
-        unreadView.backgroundColor = hasUnread ? Asset.brandPrimary.color : .clear
+        unreadCountLabel.text = "\(unreadCount)"
+        unreadView.backgroundColor = unreadCount > 0 ? Asset.brandPrimary.color : .clear
     }
 
     private func setPreview(string: String?) {
diff --git a/Sources/ContactFeature/Controllers/ContactController.swift b/Sources/ContactFeature/Controllers/ContactController.swift
index f7c33bb8ee34b21cd3582f019cd8ac54b90979b1..1ef8e96ffba5f58ec2ae2ac6e03adc701e1b3ffb 100644
--- a/Sources/ContactFeature/Controllers/ContactController.swift
+++ b/Sources/ContactFeature/Controllers/ContactController.swift
@@ -1,10 +1,11 @@
 import HUD
-import DrawerFeature
 import UIKit
 import Theme
 import Shared
 import Models
 import Combine
+import XXModels
+import DrawerFeature
 import DependencyInjection
 import ScrollViewController
 
@@ -58,7 +59,7 @@ public final class ContactController: UIViewController {
             )
         }
 
-        screenView.set(status: viewModel.contact.status)
+        screenView.set(status: viewModel.contact.authStatus)
     }
 
     private func setupNavigationBar() {
@@ -168,7 +169,7 @@ public final class ContactController: UIViewController {
             .sink { [unowned self] in
                 coordinator.toNickname(
                     from: self,
-                    prefilled: viewModel.contact.nickname ?? viewModel.contact.username,
+                    prefilled: (viewModel.contact.nickname ?? viewModel.contact.username) ?? "",
                     viewModel.didTapRequest(with:)
                 )
             }.store(in: &cancellables)
@@ -180,7 +181,7 @@ public final class ContactController: UIViewController {
             .sink { [unowned self] in
                 coordinator.toNickname(
                     from: self,
-                    prefilled: viewModel.contact.nickname ?? viewModel.contact.username,
+                    prefilled: (viewModel.contact.nickname ?? viewModel.contact.username) ?? "",
                     viewModel.didTapAccept(_:)
                 )
             }.store(in: &cancellables)
@@ -242,7 +243,7 @@ public final class ContactController: UIViewController {
                     .sink { [unowned self] in
                         coordinator.toNickname(
                             from: self,
-                            prefilled: viewModel.contact.nickname ?? viewModel.contact.username,
+                            prefilled: (viewModel.contact.nickname ?? viewModel.contact.username) ?? "",
                             viewModel.didUpdateNickname(_:)
                         )
                     }
diff --git a/Sources/ContactFeature/Coordinator/ContactCoordinator.swift b/Sources/ContactFeature/Coordinator/ContactCoordinator.swift
index 2578bf8cb09a4a58f866dabb4135db243e9c43d4..eea7f1ca94bbcace7a637abc5a1d094d53fff74b 100644
--- a/Sources/ContactFeature/Coordinator/ContactCoordinator.swift
+++ b/Sources/ContactFeature/Coordinator/ContactCoordinator.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Models
 import Shared
+import XXModels
 import ChatFeature
 import Presentation
 
diff --git a/Sources/ContactFeature/ViewModels/ContactViewModel.swift b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
index fd456e3f46d14f1f352a364ab35b1c4295e88635..67005d4b9d01cbc493ead72d8f6b89e42e64535e 100644
--- a/Sources/ContactFeature/ViewModels/ContactViewModel.swift
+++ b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
@@ -2,6 +2,7 @@ import HUD
 import UIKit
 import Models
 import Combine
+import XXModels
 import Integration
 import CombineSchedulers
 import DependencyInjection
@@ -38,8 +39,8 @@ final class ContactViewModel {
         self.contact = contact
 
         do {
-            let email = try session.extract(fact: .email, from: contact.marshaled)
-            let phone = try session.extract(fact: .phone, from: contact.marshaled)
+            let email = try session.extract(fact: .email, from: contact.marshaled!)
+            let phone = try session.extract(fact: .phone, from: contact.marshaled!)
 
             stateRelay.value = .init(
                 title: contact.nickname ?? contact.username,
@@ -57,7 +58,7 @@ final class ContactViewModel {
     func didChoosePhoto(_ photo: UIImage) {
         stateRelay.value.photo = photo
         contact.photo = photo.jpegData(compressionQuality: 0.0)
-        session.update(contact)
+        _ = try? session.dbManager.saveContact(contact)
     }
 
     func didTapDelete() {
@@ -73,18 +74,18 @@ final class ContactViewModel {
     }
 
     func didTapReject() {
-        session.delete(contact, isRequest: true)
+        try? session.deleteContact(contact)
         popRelay.send()
     }
 
     func didTapClear() {
-        session.deleteAll(from: contact)
+        _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id)))
     }
 
     func didUpdateNickname(_ string: String) {
         contact.nickname = string.isEmpty ? nil : string
         stateRelay.value.title = string.isEmpty ? contact.username : string
-        session.update(contact)
+        _ = try? session.dbManager.saveContact(contact)
 
         stateRelay.value.nickname = contact.nickname
     }
diff --git a/Sources/ContactFeature/Views/ContactInProgressView.swift b/Sources/ContactFeature/Views/ContactInProgressView.swift
index b4cbfa038f6abfa9a10a43598566d7dcc65170fc..165600f9deb908c47de78f7e4b71e9213d3d847e 100644
--- a/Sources/ContactFeature/Views/ContactInProgressView.swift
+++ b/Sources/ContactFeature/Views/ContactInProgressView.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import Models
+import XXModels
 
 final class ContactAlmostView: UIView {
     // MARK: UI
@@ -19,7 +20,7 @@ final class ContactAlmostView: UIView {
 
     // MARK: Public
 
-    func set(status: Contact.Status) {
+    func set(status: Contact.AuthStatus) {
         switch status {
         case .requestFailed, .confirmationFailed:
             feedback.set(
diff --git a/Sources/ContactFeature/Views/ContactView.swift b/Sources/ContactFeature/Views/ContactView.swift
index 8111a103c021d7c78bb374e54a10bab2d7cbd83c..e5fb03927caf3de9018a382a004b7c89c8a69e99 100644
--- a/Sources/ContactFeature/Views/ContactView.swift
+++ b/Sources/ContactFeature/Views/ContactView.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import Models
+import XXModels
 
 final class ContactView: UIView {
     let container = UIView()
@@ -38,7 +39,7 @@ final class ContactView: UIView {
 
     required init?(coder: NSCoder) { nil }
 
-    func set(status: Contact.Status) {
+    func set(status: Contact.AuthStatus) {
         let contentView: UIView
 
         switch status {
diff --git a/Sources/ContactListFeature/Controllers/ContactListTableController.swift b/Sources/ContactListFeature/Controllers/ContactListTableController.swift
index 5ae07f2fa985d5a35ec2d296a63096ab4d3f50e9..f940366ae08cfe77f9cd54e5d1008a55b7149bc6 100644
--- a/Sources/ContactListFeature/Controllers/ContactListTableController.swift
+++ b/Sources/ContactListFeature/Controllers/ContactListTableController.swift
@@ -1,7 +1,8 @@
 import UIKit
 import Shared
-import Combine
 import Models
+import Combine
+import XXModels
 
 final class ContactListTableController: UITableViewController {
     private var collation = UILocalizedIndexedCollation.current()
@@ -46,8 +47,9 @@ final class ContactListTableController: UITableViewController {
     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell: SmallAvatarAndTitleCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
         let contact = sections[indexPath.section][indexPath.row]
-        cell.titleLabel.text = contact.nickname ?? contact.username
-        cell.avatarView.setupProfile(title: contact.nickname ?? contact.username, image: contact.photo, size: .medium)
+        let name = (contact.nickname ?? contact.username) ?? "Fetching username..."
+        cell.titleLabel.text = name
+        cell.avatarView.setupProfile(title: name, image: contact.photo, size: .medium)
         return cell
     }
 
diff --git a/Sources/ContactListFeature/Controllers/CreateGroupController.swift b/Sources/ContactListFeature/Controllers/CreateGroupController.swift
index f307813c7f8e3dd959294979f9dfd26183dbccf2..9e9d039dd5e5fe2dd4adfa179fc736ab51fa6c4b 100644
--- a/Sources/ContactListFeature/Controllers/CreateGroupController.swift
+++ b/Sources/ContactListFeature/Controllers/CreateGroupController.swift
@@ -3,6 +3,7 @@ import UIKit
 import Models
 import Shared
 import Combine
+import XXModels
 import DependencyInjection
 
 public final class CreateGroupController: UIViewController {
@@ -73,7 +74,8 @@ public final class CreateGroupController: UIViewController {
         ) { [weak viewModel] collectionView, indexPath, contact in
             let cell: CreateGroupCollectionCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-            cell.setup(title: contact.nickname ?? contact.username, image: contact.photo)
+            let title = (contact.nickname ?? contact.username) ?? ""
+            cell.setup(title: title, image: contact.photo)
             cell.didTapRemove = { viewModel?.didSelect(contact: contact) }
 
             return cell
@@ -83,8 +85,9 @@ public final class CreateGroupController: UIViewController {
             tableView: screenView.tableView
         ) { [weak self] tableView, indexPath, contact in
             let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: SmallAvatarAndTitleCell.self)
-            cell.titleLabel.text = contact.nickname ?? contact.username
-            cell.avatarView.setupProfile(title: contact.nickname ?? contact.username, image: contact.photo, size: .medium)
+            let title = (contact.nickname ?? contact.username) ?? ""
+            cell.titleLabel.text = title
+            cell.avatarView.setupProfile(title: title, image: contact.photo, size: .medium)
 
             if let selectedElements = self?.selectedElements, selectedElements.contains(contact) {
                 tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
diff --git a/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift b/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift
index 6afe76e1aae847c8080d1b5c80795b5884a2c40d..baf38264a85467038888c2b37e618598a7190acd 100644
--- a/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift
+++ b/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import Models
+import XXModels
 import MenuFeature
 import ChatFeature
 import Presentation
@@ -15,7 +16,7 @@ public protocol ContactListCoordinating {
     func toSideMenu(from: UIViewController)
     func toContact(_: Contact, from: UIViewController)
     func toSingleChat(with: Contact, from: UIViewController)
-    func toGroupChat(with: GroupChatInfo, from: UIViewController)
+    func toGroupChat(with: GroupInfo, from: UIViewController)
     func toGroupDrawer(with: Int, from: UIViewController, _: @escaping (String, String?) -> Void)
 }
 
@@ -31,7 +32,7 @@ public struct ContactListCoordinator: ContactListCoordinating {
     var requestsFactory: () -> UIViewController
     var contactFactory: (Contact) -> UIViewController
     var singleChatFactory: (Contact) -> UIViewController
-    var groupChatFactory: (GroupChatInfo) -> UIViewController
+    var groupChatFactory: (GroupInfo) -> UIViewController
     var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController
     var groupDrawerFactory: (Int, @escaping (String, String?) -> Void) -> UIViewController
 
@@ -42,7 +43,7 @@ public struct ContactListCoordinator: ContactListCoordinating {
         requestsFactory: @escaping () -> UIViewController,
         contactFactory: @escaping (Contact) -> UIViewController,
         singleChatFactory: @escaping (Contact) -> UIViewController,
-        groupChatFactory: @escaping (GroupChatInfo) -> UIViewController,
+        groupChatFactory: @escaping (GroupInfo) -> UIViewController,
         sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController,
         groupDrawerFactory: @escaping (Int, @escaping (String, String?) -> Void) -> UIViewController
     ) {
@@ -101,7 +102,7 @@ public extension ContactListCoordinator {
         pushPresenter.present(screen, from: parent)
     }
 
-    func toGroupChat(with info: GroupChatInfo, from parent: UIViewController) {
+    func toGroupChat(with info: GroupInfo, from parent: UIViewController) {
         let screen = groupChatFactory(info)
         pushPresenter.present(screen, from: parent)
     }
diff --git a/Sources/ContactListFeature/Helpers/IndexedListCollator.swift b/Sources/ContactListFeature/Helpers/IndexedListCollator.swift
new file mode 100644
index 0000000000000000000000000000000000000000..249af6fad35653a205a8d485dbab088cc8ad9578
--- /dev/null
+++ b/Sources/ContactListFeature/Helpers/IndexedListCollator.swift
@@ -0,0 +1,53 @@
+import UIKit
+import XXModels
+
+public protocol IndexableItem {
+    var indexedOn: NSString { get }
+}
+
+class IndexedListCollator<Item: IndexableItem> {
+    private final class CollationWrapper: NSObject {
+        let value: Any
+        @objc let indexedOn: NSString
+
+        init(value: Any, indexedOn: NSString) {
+            self.value = value
+            self.indexedOn = indexedOn
+        }
+
+        func unwrappedValue<UnwrappedType>() -> UnwrappedType {
+            return value as! UnwrappedType
+        }
+    }
+
+    public init() {}
+
+    public func sectioned(items: [Item]) -> (sections: [[Item]], collation: UILocalizedIndexedCollation) {
+        let collation = UILocalizedIndexedCollation.current()
+        let selector = #selector(getter: CollationWrapper.indexedOn)
+
+        let wrappedItems = items.map { item in
+            CollationWrapper(value: item, indexedOn: item.indexedOn)
+        }
+
+        let sortedObjects = collation.sortedArray(from: wrappedItems, collationStringSelector: selector) as! [CollationWrapper]
+
+        var sections = collation.sectionIndexTitles.map { _ in [Item]() }
+        sortedObjects.forEach { item in
+            let sectionNumber = collation.section(for: item, collationStringSelector: selector)
+            sections[sectionNumber].append(item.unwrappedValue())
+        }
+
+        return (sections: sections.filter { !$0.isEmpty }, collation: collation)
+    }
+}
+
+extension Contact: IndexableItem {
+    public var indexedOn: NSString {
+        guard let nickname = nickname else {
+            return "\(username!.first!)" as NSString
+        }
+
+        return "\(nickname.first!)" as NSString
+    }
+}
diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
index c1d5d1f83114feaef55a898c9bce85623a20f156..0467446065b81d8f52a0f9517948160e623de1e2 100644
--- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
@@ -1,5 +1,6 @@
 import Models
 import Combine
+import XXModels
 import Integration
 import DependencyInjection
 
@@ -7,27 +8,27 @@ final class ContactListViewModel {
     @Dependency private var session: SessionType
 
     var contacts: AnyPublisher<[Contact], Never> {
-        session.contacts(.friends).eraseToAnyPublisher()
+        session.dbManager.fetchContactsPublisher(.init(authStatus: [.friend]))
+            .assertNoFailure()
+            .map { $0.filter { $0.id != self.session.myId }}
+            .eraseToAnyPublisher()
     }
 
     var requestCount: AnyPublisher<Int, Never> {
-        Publishers.CombineLatest(
-            session.contacts(.received),
-            session.groups(.pending)
-        ).map { (contacts, groups) in
-            let contactRequests = contacts.filter {
-                $0.status == .verified ||
-                $0.status == .confirming ||
-                $0.status == .confirmationFailed ||
-                $0.status == .verificationFailed ||
-                $0.status == .verificationInProgress
-            }
+        let groupQuery = Group.Query(authStatus: [.pending])
+        let contactsQuery = Contact.Query(authStatus: [
+            .verified,
+            .confirming,
+            .confirmationFailed,
+            .verificationFailed,
+            .verificationInProgress
+        ])
 
-            let groupRequests = groups.filter {
-                $0.status == .pending
-            }
-
-            return contactRequests.count + groupRequests.count
-        }.eraseToAnyPublisher()
+        return Publishers.CombineLatest(
+            session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
+            session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure()
+        )
+        .map { $0.0.count + $0.1.count }
+        .eraseToAnyPublisher()
     }
 }
diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
index 403e8361b163faa018d16e2702f349428df38838..67e70645ed6c0d00f02862bdb02f199d3d735d9d 100644
--- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
@@ -1,10 +1,11 @@
 import HUD
-import Combine
+import UIKit
 import Models
+import Combine
+import XXModels
+import Defaults
 import Integration
 import DependencyInjection
-import Defaults
-import UIKit
 
 final class CreateGroupViewModel {
     @KeyObject(.username, defaultValue: "") var username: String
@@ -27,13 +28,13 @@ final class CreateGroupViewModel {
         hudRelay.eraseToAnyPublisher()
     }
 
-    var info: AnyPublisher<GroupChatInfo, Never> {
+    var info: AnyPublisher<GroupInfo, Never> {
         infoRelay.eraseToAnyPublisher()
     }
 
     private var allContacts = [Contact]()
     private var cancellables = Set<AnyCancellable>()
-    private let infoRelay = PassthroughSubject<GroupChatInfo, Never>()
+    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>([])
@@ -41,8 +42,10 @@ final class CreateGroupViewModel {
     // MARK: Lifecycle
 
     init() {
-        session.contacts(.friends)
-            .map { $0.sorted(by: { $0.username < $1.username })}
+        session.dbManager.fetchContactsPublisher(.init(authStatus: [.friend]))
+            .assertNoFailure()
+            .map { $0.filter { $0.id != self.session.myId }}
+            .map { $0.sorted(by: { $0.username! < $1.username! })}
             .sink { [unowned self] in
                 allContacts = $0
                 contactsRelay.send($0)
@@ -65,7 +68,11 @@ final class CreateGroupViewModel {
             return
         }
 
-        contactsRelay.send(allContacts.filter { $0.username.contains(text.lowercased()) })
+        contactsRelay.send(
+            allContacts.filter {
+                ($0.username ?? "").contains(text.lowercased())
+            }
+        )
     }
 
     func create(name: String, welcome: String?, members: [Contact]) {
@@ -77,8 +84,8 @@ final class CreateGroupViewModel {
             self.hudRelay.send(.none)
 
             switch $0 {
-            case .success((let group, let members)):
-                self.infoRelay.send(.init(group: group, members: members))
+            case .success(let info):
+                self.infoRelay.send(info)
             case .failure(let error):
                 self.hudRelay.send(.error(.init(with: error)))
             }
diff --git a/Sources/Database/DB+Contact.swift b/Sources/Database/DB+Contact.swift
deleted file mode 100644
index 4207fea75d83ea0ef6e56ae361cb5a172c8773c7..0000000000000000000000000000000000000000
--- a/Sources/Database/DB+Contact.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-import GRDB
-import Models
-
-extension Contact: Persistable {    
-    public enum Column: String, ColumnExpression {
-        case id
-        case photo
-        case email
-        case phone
-        case userId
-        case status
-        case username
-        case isRecent
-        case nickname
-        case marshaled
-        case createdAt
-    }
-
-    public mutating func didInsert(with rowID: Int64, for column: String?) {
-        id = rowID
-    }
-    
-    public static func query(_ request: Request) -> QueryInterfaceRequest<Contact> {
-        switch request {
-        case .all:
-            return Contact.all()
-        case .isRecent:
-            return Contact
-                .filter(Column.isRecent == true)
-                .order(Column.createdAt.desc)
-        case .verificationInProgress:
-            return Contact.filter(Column.status == Contact.Status.verificationInProgress.rawValue)
-        case .failed:
-            return Contact.filter(
-                Column.status == Contact.Status.requestFailed.rawValue ||
-                Column.status == Contact.Status.confirmationFailed.rawValue
-            )
-        case .requested:
-            return Contact.filter(
-                Column.status == Contact.Status.requested.rawValue ||
-                Column.status == Contact.Status.requesting.rawValue
-            )
-        case .received:
-            return Contact.filter(
-                Column.status == Contact.Status.hidden.rawValue ||
-                Column.status == Contact.Status.verified.rawValue ||
-                Column.status == Contact.Status.verificationFailed.rawValue ||
-                Column.status == Contact.Status.verificationInProgress.rawValue
-            )
-
-        case .friends: return Contact.filter(Column.status == Contact.Status.friend.rawValue)
-        case let .withUserId(data): return Contact.filter(Column.userId == data)
-        case let .withUserIds(ids): return Contact.filter(ids.contains(Contact.Column.userId))
-        case let .withUsername(username): return Contact.filter(Column.username.like("\(username)%"))
-        }
-    }
-}
diff --git a/Sources/Database/DB+FileTransfer.swift b/Sources/Database/DB+FileTransfer.swift
deleted file mode 100644
index 617d8ce34c0b60f3a54c4e9f18e68cfa0ba1dc67..0000000000000000000000000000000000000000
--- a/Sources/Database/DB+FileTransfer.swift
+++ /dev/null
@@ -1,26 +0,0 @@
-import GRDB
-import Models
-
-extension FileTransfer: Persistable {
-    public enum Column: String, ColumnExpression {
-        case id
-        case tid
-        case contact
-        case fileName
-        case fileType
-        case isIncoming
-    }
-
-    public mutating func didInsert(with rowID: Int64, for column: String?) {
-        id = rowID
-    }
-
-    public static func query(_ request: Request) -> QueryInterfaceRequest<FileTransfer> {
-        switch request {
-        case .withTID(let transferId):
-            return FileTransfer.filter(Column.tid == transferId)
-        case .withContactId(let contactId):
-            return FileTransfer.filter(Column.contact == contactId)
-        }
-    }
-}
diff --git a/Sources/Database/DB+Group.swift b/Sources/Database/DB+Group.swift
deleted file mode 100644
index dc78a33efae1c30fd484d7fd2c99e3e996134ec2..0000000000000000000000000000000000000000
--- a/Sources/Database/DB+Group.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-import GRDB
-import Models
-
-extension Group: Persistable {
-    static let members = hasMany(GroupMember.self)
-
-    public enum Column: String, ColumnExpression {
-        case id
-        case name
-        case leader
-        case groupId
-        case status
-        case serialize
-        case createdAt
-        case accepted // Deprecated
-    }
-
-    public mutating func didInsert(with rowID: Int64, for column: String?) {
-        id = rowID
-    }
-
-    public static func query(_ request: Request) -> QueryInterfaceRequest<Group> {
-        switch request {
-        case .withGroupId(let id):
-            return Group.filter(Column.groupId == id)
-        case .accepted:
-            return Group.filter(Column.status == Group.Status.participating.rawValue)
-        case .pending:
-            return Group.filter(
-                Column.status == Group.Status.pending.rawValue ||
-                Column.status == Group.Status.hidden.rawValue
-            )
-        }
-    }
-}
diff --git a/Sources/Database/DB+GroupChatInfo.swift b/Sources/Database/DB+GroupChatInfo.swift
deleted file mode 100644
index 4be4dfad3fccda964b68873b5ddcf3cc4253aeed..0000000000000000000000000000000000000000
--- a/Sources/Database/DB+GroupChatInfo.swift
+++ /dev/null
@@ -1,38 +0,0 @@
-import GRDB
-import Models
-
-extension GroupChatInfo: Requestable  {
-    public static func query(_ request: Request) -> QueryInterfaceRequest<GroupChatInfo> {
-        let lastMessageRequest = GroupMessage
-            .annotated(with: max(GroupMessage.Column.timestamp))
-            .group(GroupMessage.Column.groupId)
-
-        let lastMessageCTE = CommonTableExpression<GroupMessage>(
-            named: "lastMessage",
-            request: lastMessageRequest
-        )
-
-        let lastMessage = Group.association(to: lastMessageCTE) { group, lastMessage in
-            lastMessage[GroupMessage.Column.groupId] == group[Group.Column.groupId]
-        }.order(GroupMessage.Column.timestamp.desc)
-
-        switch request {
-        case .fromGroup(let groupId):
-            return Group
-                .filter(Group.Column.status == Group.Status.participating.rawValue)
-                .filter(Group.Column.groupId == groupId)
-                .with(lastMessageCTE)
-                .including(optional: lastMessage)
-                .including(all: Group.members.forKey("members"))
-                .asRequest(of: Self.self)
-
-        case .accepted:
-            return Group
-                .filter(Group.Column.status == Group.Status.participating.rawValue)
-                .with(lastMessageCTE)
-                .including(optional: lastMessage)
-                .including(all: Group.members.forKey("members"))
-                .asRequest(of: Self.self)
-        }
-    }
-}
diff --git a/Sources/Database/DB+GroupMember.swift b/Sources/Database/DB+GroupMember.swift
deleted file mode 100644
index 2153704e4d14c37b9a4e122ca77299b76bd655a6..0000000000000000000000000000000000000000
--- a/Sources/Database/DB+GroupMember.swift
+++ /dev/null
@@ -1,32 +0,0 @@
-import GRDB
-import Models
-
-extension GroupMember: Persistable {
-    public enum Column: String, ColumnExpression {
-        case id
-        case photo
-        case status
-        case userId
-        case groupId
-        case username
-    }
-
-    public mutating func didInsert(with rowID: Int64, for column: String?) {
-        id = rowID
-    }
-
-    public static func query(_ request: Request) -> QueryInterfaceRequest<GroupMember> {
-        switch request {
-        case .all:
-            return GroupMember.all()
-        case let .withUserId(userId):
-            return GroupMember.filter(Column.userId == userId)
-        case .fromGroup(let groupId):
-            return GroupMember.filter(Column.groupId == groupId)
-        case .strangers:
-            return GroupMember.filter(
-                Column.status == GroupMember.Status.pendingUsername.rawValue
-            )
-        }
-    }
-}
diff --git a/Sources/Database/DB+GroupMessage.swift b/Sources/Database/DB+GroupMessage.swift
deleted file mode 100644
index 7ec6754d6fa0073ba6995b0a44145d1e4a16099e..0000000000000000000000000000000000000000
--- a/Sources/Database/DB+GroupMessage.swift
+++ /dev/null
@@ -1,36 +0,0 @@
-import GRDB
-import Models
-
-extension GroupMessage: Persistable {
-    public enum Column: String, ColumnExpression {
-        case id
-        case sender
-        case status
-        case unread
-        case payload
-        case groupId
-        case uniqueId
-        case roundURL
-        case timestamp
-        case roundId
-    }
-
-    public mutating func didInsert(with rowID: Int64, for column: String?) {
-        id = rowID
-    }
-
-    public static func query(_ request: Request) -> QueryInterfaceRequest<GroupMessage> {
-        switch request {
-        case let .withUniqueId(id):
-            return GroupMessage.filter(Column.uniqueId == id)
-        case let .id(id):
-            return GroupMessage.filter(Column.id == id)
-        case let .fromGroup(id):
-            return GroupMessage.filter(Column.groupId == id).order(Column.timestamp.asc)
-        case let .unreadsFromGroup(id):
-            return GroupMessage.filter(Column.groupId == id).filter(Column.unread == true)
-        case .sending:
-            return GroupMessage.filter(Column.status == GroupMessage.Status.sending.rawValue)
-        }
-    }
-}
diff --git a/Sources/Database/DB+Message.swift b/Sources/Database/DB+Message.swift
deleted file mode 100644
index b5057ea99d870f7861df21f46dac9b47c92d245d..0000000000000000000000000000000000000000
--- a/Sources/Database/DB+Message.swift
+++ /dev/null
@@ -1,46 +0,0 @@
-import GRDB
-import Models
-
-extension Message: Persistable {
-    public enum Column: String, ColumnExpression {
-        case id
-        case report
-        case sender
-        case unread
-        case status
-        case payload
-        case roundURL
-        case receiver
-        case uniqueId
-        case timestamp
-    }
-
-    public mutating func didInsert(with rowID: Int64, for column: String?) {
-        id = rowID
-    }
-
-    public static func query(_ request: Request) -> QueryInterfaceRequest<Message> {
-        switch request {
-        case let .withUniqueId(id):
-            return Message.filter(Column.uniqueId == id)
-        case let .unreadsFromContactId(id):
-            return Message
-                .filter(Column.sender == id || Column.receiver == id)
-                .filter(Column.unread == true)
-
-        case let .latestOnesFromContactIds(ids):
-            return Message
-                .annotated(with: Column.timestamp)
-                .filter(ids.contains(Column.sender) || ids.contains(Column.receiver))
-
-        case let .withId(id):
-            return Message.filter(Column.id == id)
-        case let .withContact(id):
-            return Message.filter(Column.sender == id || Column.receiver == id)
-        case .sending:
-            return Message.filter(Column.status == Message.Status.sending.rawValue)
-        case .sendingAttachment:
-            return Message.filter(Column.status == Message.Status.sendingAttachment.rawValue)
-        }
-    }
-}
diff --git a/Sources/Database/DB+SingleChatInfo.swift b/Sources/Database/DB+SingleChatInfo.swift
deleted file mode 100644
index 16fbc4ff9299a8decc603559c09beebf1aa16f9b..0000000000000000000000000000000000000000
--- a/Sources/Database/DB+SingleChatInfo.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-import GRDB
-import Models
-
-extension SingleChatInfo: Requestable {
-    public static func query(_ request: Request) -> QueryInterfaceRequest<SingleChatInfo> {
-        let lastMessageRequest = Message
-            .annotated(with: max(Message.Column.timestamp))
-            .group(Message.Column.sender || Message.Column.receiver)
-
-        let lastMessageCTE = CommonTableExpression<Message>(
-            named: "lastMessage",
-            request: lastMessageRequest
-        )
-
-        let lastMessage = Contact.association(to: lastMessageCTE) { contact, lastMessage in
-            lastMessage[Message.Column.sender] == contact[Contact.Column.userId] ||
-            lastMessage[Message.Column.receiver] == contact[Contact.Column.userId]
-        }.order(Message.Column.timestamp.desc)
-
-        switch request {
-        case .all:
-            return Contact.with(lastMessageCTE)
-                .including(required: lastMessage)
-                .asRequest(of: Self.self)
-        }
-    }
-}
diff --git a/Sources/Database/DatabaseManager.swift b/Sources/Database/DatabaseManager.swift
deleted file mode 100644
index 86271c2ebfcc146182d867aedf7dd622168807df..0000000000000000000000000000000000000000
--- a/Sources/Database/DatabaseManager.swift
+++ /dev/null
@@ -1,261 +0,0 @@
-import GRDB
-import Models
-import Combine
-import Foundation
-
-public protocol Requestable: FetchableRecord {
-    associatedtype Request
-    static func query(_ request: Request) -> QueryInterfaceRequest<Self>
-}
-
-public protocol Persistable: Requestable & MutablePersistableRecord & Identifiable {
-    var id: Int64? { get }
-}
-
-public protocol DatabaseManager {
-    func drop()
-    func setup() throws
-
-    func updateAll<T>(_ type: T.Type,
-                      _ request: T.Request,
-                      with: [ColumnAssignment]) throws where T: Persistable
-
-    @discardableResult func save<T>(_ model: T) throws -> T where T: Persistable
-    func update<T>(_ model: T) throws where T: Persistable
-    func delete<T>(_ model: T) throws where T: Persistable
-    func fetch<T>(_ request: T.Request) throws -> [T] where T: Requestable
-    func fetch<T>(withId id: Int64) throws -> T? where T: Persistable
-    func publisher<T>(fetch request: T.Request) -> AnyPublisher<[T], Error> where T: Requestable
-    func delete<T>(_ type: T.Type, _ request: T.Request) throws where T: Persistable
-}
-
-public extension DatabaseManager {
-    func publisher<T: Requestable>(
-        fetch type: T.Type,
-        _ request: T.Request
-    ) -> AnyPublisher<[T], Error> {
-        publisher(fetch: request)
-    }
-}
-
-public final class GRDBDatabaseManager {
-    var databaseQueue: DatabaseQueue!
-
-    public init() {}
-}
-
-extension GRDBDatabaseManager: DatabaseManager {
-    public func drop() {
-        try? databaseQueue.write { db in
-            try db.drop(table: Contact.databaseTableName)
-            try db.drop(table: Message.databaseTableName)
-            try db.drop(table: Group.databaseTableName)
-            try db.drop(table: GroupMember.databaseTableName)
-            try db.drop(table: GroupMessage.databaseTableName)
-        }
-    }
-
-    public func updateAll<T>(_ type: T.Type,
-                             _ request: T.Request,
-                             with assignments: [ColumnAssignment]) throws where T : Persistable {
-        _ = try databaseQueue.write {
-            try T.query(request).updateAll($0, assignments)
-        }
-    }
-
-    public func save<T: Persistable>(_ model: T) throws -> T {
-        try databaseQueue.write { db in
-            var model = model
-
-            if model.id == nil {
-                try model.insert(db)
-            } else {
-                try model.update(db)
-            }
-
-            return model
-        }
-    }
-
-    public func update<T>(_ model: T) throws where T: Persistable {
-        try databaseQueue.write { try model.update($0) }
-    }
-
-    public func fetch<T>(withId id: Int64) throws -> T? where T: Persistable {
-        try databaseQueue.read { db in
-            try T.fetchOne(db, key: id)
-        }
-    }
-
-    public func fetch<T>(_ request: T.Request) throws -> [T] where T: Requestable {
-        try databaseQueue.read { db in
-            try T.query(request).fetchAll(db)
-        }
-    }
-
-    public func publisher<T>(fetch request: T.Request) -> AnyPublisher<[T], Error> where T: Requestable {
-        ValueObservation.tracking {
-            try T.query(request).fetchAll($0)
-        }.publisher(in: databaseQueue, scheduling: .immediate)
-        .eraseToAnyPublisher()
-    }
-
-    public func delete<T>(_ model: T) throws where T: Persistable {
-        _ = try databaseQueue.write {
-            try model.delete($0)
-        }
-    }
-
-    public func delete<T>(_ type: T.Type, _ request: T.Request) throws where T: Persistable {
-        _ = try databaseQueue.write {
-            try T.query(request).deleteAll($0)
-        }
-    }
-
-    public func setup() throws {
-        var migrator = DatabaseMigrator()
-
-        let oldPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
-            .appending("/xxmessenger.sqlite")
-
-        let url = FileManager.default
-            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
-            .appendingPathComponent("database")
-            .appendingPathExtension("sqlite")
-
-        if FileManager.default.fileExists(atPath: oldPath) && !FileManager.default.fileExists(atPath: url.path) {
-            do {
-                try FileManager.default.moveItem(atPath: oldPath, toPath: url.path)
-            } catch {
-                fatalError("Couldn't migrate database from old path to new one: \(error.localizedDescription)")
-            }
-        }
-
-        databaseQueue = try DatabaseQueue(path: url.path)
-        try FileManager.default.setAttributes([
-            .protectionKey : FileProtectionType.completeUntilFirstUserAuthentication
-        ], ofItemAtPath: url.path)
-
-        migrator.registerMigration("v1") { db in
-            try db.create(table: Contact.databaseTableName, ifNotExists: true) { table in
-                table.autoIncrementedPrimaryKey(Contact.Column.id.rawValue, onConflict: .replace)
-                table.column(Contact.Column.photo.rawValue, .blob)
-                table.column(Contact.Column.email.rawValue, .text)
-                table.column(Contact.Column.phone.rawValue, .text)
-                table.column(Contact.Column.nickname.rawValue, .text)
-                table.column(Contact.Column.createdAt.rawValue, .datetime)
-                table.column(Contact.Column.userId.rawValue, .blob).unique()
-                table.column(Contact.Column.username.rawValue, .text).notNull()
-                table.column(Contact.Column.status.rawValue, .integer).notNull()
-                table.column(Contact.Column.marshaled.rawValue, .blob).notNull()
-            }
-
-            try db.create(table: Message.databaseTableName, ifNotExists: true) { table in
-                table.autoIncrementedPrimaryKey(Message.Column.id.rawValue, onConflict: .replace)
-                table.column(Message.Column.report.rawValue, .blob)
-                table.column(Message.Column.uniqueId.rawValue, .blob)
-                table.column(Message.Column.sender.rawValue, .blob).notNull()
-                table.column(Message.Column.payload.rawValue, .text).notNull()
-                table.column(Message.Column.receiver.rawValue, .blob).notNull()
-                table.column(Message.Column.roundURL.rawValue, .text)
-                table.column(Message.Column.status.rawValue, .integer).notNull()
-                table.column(Message.Column.unread.rawValue, .boolean).notNull()
-                table.column(Message.Column.timestamp.rawValue, .integer).notNull()
-            }
-
-            try db.create(table: Group.databaseTableName, ifNotExists: true) { table in
-                table.autoIncrementedPrimaryKey(Group.Column.id.rawValue, onConflict: .replace)
-                table.column(Group.Column.groupId.rawValue, .blob).unique()
-                table.column(Group.Column.name.rawValue, .text).notNull()
-                table.column(Group.Column.leader.rawValue, .blob).notNull()
-                table.column(Group.Column.serialize.rawValue, .blob).notNull()
-                table.column(Group.Column.accepted.rawValue, .boolean).notNull()
-            }
-
-            try db.create(table: GroupMember.databaseTableName, ifNotExists: true) { table in
-                table.autoIncrementedPrimaryKey(GroupMember.Column.id.rawValue, onConflict: .replace)
-                table.column(GroupMember.Column.userId.rawValue, .blob).notNull()
-                table.column(GroupMember.Column.username.rawValue, .text).notNull()
-                table.column(GroupMember.Column.photo.rawValue, .blob)
-                table.column(GroupMember.Column.status.rawValue, .integer).notNull()
-                table.column(GroupMember.Column.groupId.rawValue, .blob).notNull()
-                    .references(
-                        Group.databaseTableName,
-                        column: Group.Column.groupId.rawValue,
-                        onDelete: .cascade,
-                        deferred: true
-                    )
-            }
-
-            try db.create(table: GroupMessage.databaseTableName, ifNotExists: true) { table in
-                table.autoIncrementedPrimaryKey(GroupMessage.Column.id.rawValue, onConflict: .replace)
-                table.column(GroupMessage.Column.uniqueId.rawValue, .blob)
-                table.column(GroupMessage.Column.roundId.rawValue, .integer)
-                table.column(GroupMessage.Column.groupId.rawValue, .blob).notNull()
-                table.column(GroupMessage.Column.sender.rawValue, .blob).notNull()
-                table.column(GroupMessage.Column.roundURL.rawValue, .text)
-                table.column(GroupMessage.Column.payload.rawValue, .text).notNull()
-                table.column(GroupMessage.Column.status.rawValue, .integer).notNull()
-                table.column(GroupMessage.Column.unread.rawValue, .boolean).notNull()
-                table.column(GroupMessage.Column.timestamp.rawValue, .integer).notNull()
-            }
-
-            try db.create(table: FileTransfer.databaseTableName, ifNotExists: true) { table in
-                table.autoIncrementedPrimaryKey(FileTransfer.Column.id.rawValue, onConflict: .replace)
-                table.column(FileTransfer.Column.tid.rawValue, .blob).notNull()
-                table.column(FileTransfer.Column.contact.rawValue, .blob).notNull()
-                table.column(FileTransfer.Column.fileName.rawValue, .text).notNull()
-                table.column(FileTransfer.Column.fileType.rawValue, .text).notNull()
-                table.column(FileTransfer.Column.isIncoming.rawValue, .boolean).notNull()
-            }
-        }
-
-        migrator.registerMigration("v1: Updating contact/group requests UI") { db in
-            try db.create(table: "temp_\(Group.databaseTableName)") { table in
-                table.autoIncrementedPrimaryKey(Group.Column.id.rawValue, onConflict: .replace)
-                table.column(Group.Column.groupId.rawValue, .blob).unique()
-                table.column(Group.Column.name.rawValue, .text).notNull()
-                table.column(Group.Column.leader.rawValue, .blob).notNull()
-                table.column(Group.Column.serialize.rawValue, .blob).notNull()
-                table.column(Group.Column.status.rawValue, .integer).notNull()
-                table.column(Group.Column.createdAt.rawValue, .datetime).notNull()
-            }
-
-            let oldRows = try Row.fetchCursor(db, sql: "SELECT * FROM \(Group.databaseTableName)")
-            while let row = try oldRows.next() {
-                let status: Group.Status
-
-                if row["accepted"] == true {
-                    status = .participating
-                } else {
-                    status = .pending
-                }
-
-                try db.execute(
-                    sql: "INSERT INTO temp_\(Group.databaseTableName) (id, groupId, name, leader, serialize, status, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?)",
-                    arguments:
-                        [row["id"],
-                         row["groupId"],
-                         row["name"],
-                         row["leader"],
-                         row["serialize"],
-                         status.rawValue,
-                         Date()
-                        ])
-            }
-
-            try db.drop(table: Group.databaseTableName)
-            try db.rename(table: "temp_\(Group.databaseTableName)", to: Group.databaseTableName)
-        }
-
-        migrator.registerMigration("v2") { db in
-            try db.alter(table: Contact.databaseTableName) { table in
-                table.add(column: Contact.Column.isRecent.rawValue, .boolean)
-            }
-
-            try Contact.updateAll(db, Contact.Column.isRecent.set(to: false))
-        }
-
-        try migrator.migrate(databaseQueue)
-    }
-}
diff --git a/Sources/DrawerFeature/Items/DrawerTable.swift b/Sources/DrawerFeature/Items/DrawerTable.swift
index bf1b8d4432e2220bcdac8372558bfbdccaf91f66..726dae9fe28ccd5720167a7b4ed1944eebf419e5 100644
--- a/Sources/DrawerFeature/Items/DrawerTable.swift
+++ b/Sources/DrawerFeature/Items/DrawerTable.swift
@@ -74,17 +74,20 @@ public final class DrawerTable: DrawerItem {
 }
 
 public struct DrawerTableCellModel: Hashable {
+    let id: Data
     let title: String
     let image: Data?
     let isCreator: Bool
     let isConnection: Bool
 
     public init(
+        id: Data,
         title: String,
         image: Data? = nil,
         isCreator: Bool = false,
         isConnection: Bool = true
     ) {
+        self.id = id
         self.title = title
         self.image = image
         self.isCreator = isCreator
diff --git a/Sources/Integration/Client.swift b/Sources/Integration/Client.swift
index bdd6784d144195da495cfa24db7784fcdd08b999..7d53fb7efaf8ebe3b4f6485bc38dd101ce8960e5 100644
--- a/Sources/Integration/Client.swift
+++ b/Sources/Integration/Client.swift
@@ -2,8 +2,9 @@ import Retry
 import Models
 import Combine
 import Defaults
-import Foundation
 import Bindings
+import XXModels
+import Foundation
 
 public class Client {
     @KeyObject(.inappnotifications, defaultValue: true) var inappnotifications: Bool
@@ -21,10 +22,9 @@ public class Client {
     var messages: AnyPublisher<Message, Never> { messagesSubject.eraseToAnyPublisher() }
     var requests: AnyPublisher<Contact, Never> { requestsSubject.eraseToAnyPublisher() }
     var events: AnyPublisher<BackendEvent, Never> { eventsSubject.eraseToAnyPublisher() }
+    var transfers: AnyPublisher<FileTransfer, Never> { transfersSubject.eraseToAnyPublisher() }
     var requestsSent: AnyPublisher<Contact, Never> { requestsSentSubject.eraseToAnyPublisher() }
     var confirmations: AnyPublisher<Contact, Never> { confirmationsSubject.eraseToAnyPublisher() }
-    var groupMessages: AnyPublisher<GroupMessage, Never> { groupMessagesSubject.eraseToAnyPublisher() }
-    var incomingTransfers: AnyPublisher<FileTransfer, Never> { transfersSubject.eraseToAnyPublisher() }
     var groupRequests: AnyPublisher<(Group, [Data], String?), Never> { groupRequestsSubject.eraseToAnyPublisher() }
 
     private let backupSubject = PassthroughSubject<Data, Never>()
@@ -36,7 +36,6 @@ public class Client {
     private let requestsSentSubject = PassthroughSubject<Contact, Never>()
     private let confirmationsSubject = PassthroughSubject<Contact, Never>()
     private let transfersSubject = PassthroughSubject<FileTransfer, Never>()
-    private let groupMessagesSubject = PassthroughSubject<GroupMessage, Never>()
     private let groupRequestsSubject = PassthroughSubject<(Group, [Data], String?), Never>()
 
     private var isBackupInitialization = false
@@ -110,7 +109,7 @@ public class Client {
 
                 switch $0 {
                 case .success(var contact):
-                    contact.status = .requested
+                    contact.authStatus = .requested
                     self.requestsSentSubject.send(contact)
                     print(">>> Restored \(contact.username). Setting status as requested")
                 case .failure(let error):
@@ -165,8 +164,8 @@ public class Client {
 
         groupManager = try bindings.listenGroupRequests { [weak groupRequestsSubject] request, members, welcome in
             groupRequestsSubject?.send((request, members, welcome))
-        } groupMessages: { [weak groupMessagesSubject] in
-            groupMessagesSubject?.send($0)
+        } groupMessages: { [weak messagesSubject] in
+            messagesSubject?.send($0)
         }
 
         bindings.listenPreImageUpdates()
@@ -188,19 +187,21 @@ public class Client {
             ///
             guard let name = name,
                   let type = type,
-                  let contact = sender,
-                  let _extension = Attachment.Extension.from(type) else {
+                  let contactId = sender else {
                       log(string: "Transfer of \(name ?? "nil").\(type ?? "nil") is being dismissed", type: .error)
                       return
                   }
 
             transfersSubject?.send(
                 FileTransfer(
-                    tid: tid,
-                    contact: contact,
-                    fileName: name,
-                    fileType: _extension.written,
-                    isIncoming: true
+                    id: tid,
+                    contactId: contactId,
+                    name: name,
+                    type: type,
+                    data: nil,
+                    progress: 0.0,
+                    isIncoming: true,
+                    createdAt: Date()
                 )
             )
         }
diff --git a/Sources/Integration/Extensions.swift b/Sources/Integration/Extensions.swift
index 93cf186a5df2963c605b2dc6a93c7ede36425468..f2afdcb99e46f87141081f94e657b7ef5e14721e 100644
--- a/Sources/Integration/Extensions.swift
+++ b/Sources/Integration/Extensions.swift
@@ -1,53 +1,58 @@
 import Models
+import XXModels
 import Bindings
 
 extension Contact {
-    init(with contact: BindingsContact, status: Contact.Status) {
+    init(with contact: BindingsContact, status: Contact.AuthStatus) {
         self.init(
-            photo: nil,
-            userId: contact.getID()!,
-            email: contact.retrieve(fact: .email),
-            phone: contact.retrieve(fact: .phone),
-            status: status,
+            id: contact.getID()!,
             marshaled: try! contact.marshal(),
             username: contact.retrieve(fact: .username) ?? "",
+            email: contact.retrieve(fact: .email),
+            phone: contact.retrieve(fact: .phone),
             nickname: nil,
-            createdAt: Date(),
-            isRecent: false
+            photo: nil,
+            authStatus: status,
+            isRecent: false,
+            createdAt: Date()
         )
     }
 }
 
 extension Message {
-    init(with message: BindingsMessage, meMarshalled: Data) {
+    init(with message: BindingsMessage, myId: Data) {
         guard let payload = try? Payload(with: message.getPayload()!) else { fatalError() }
 
         self.init(
-            sender: message.getSender()!,
-            receiver: meMarshalled,
-            payload: payload,
-            unread: true,
-            timestamp: Int(message.getTimestampNano()),
-            uniqueId: message.getID()!,
+            networkId: message.getID()!,
+            senderId: message.getSender()!,
+            recipientId: myId,
+            groupId: nil,
+            date: Date.fromTimestamp(Int(message.getTimestampNano())),
             status: .received,
-            roundURL: message.getRoundURL()
+            isUnread: true,
+            text: payload.text,
+            replyMessageId: payload.reply?.messageId,
+            roundURL: message.getRoundURL(),
+            fileTransferId: nil
         )
     }
-}
 
-extension GroupMessage {
     init(with message: BindingsGroupMessageReceive) {
         guard let payload = try? Payload(with: message.getPayload()!) else { fatalError() }
 
         self.init(
-            sender: message.getSenderID()!,
+            networkId: message.getMessageID()!,
+            senderId: message.getSenderID()!,
+            recipientId: nil,
             groupId: message.getGroupID()!,
-            payload: payload,
-            unread: true,
-            timestamp: Int(message.getTimestampNano()),
-            uniqueId: message.getMessageID()!,
+            date: Date.fromTimestamp(Int(message.getTimestampNano())),
             status: .received,
-            roundURL: message.getRoundURL()
+            isUnread: true,
+            text: payload.text,
+            replyMessageId: payload.reply?.messageId,
+            roundURL: message.getRoundURL(),
+            fileTransferId: nil
         )
     }
 }
diff --git a/Sources/Integration/Implementations/Bindings.swift b/Sources/Integration/Implementations/Bindings.swift
index ce843f4a7f44392a3705f8c738fd3b2cf99a09a4..6aac71d4e5c02c661c719db62816662286f9c308 100644
--- a/Sources/Integration/Implementations/Bindings.swift
+++ b/Sources/Integration/Implementations/Bindings.swift
@@ -1,8 +1,9 @@
 import Shared
 import Models
 import Bindings
-import DependencyInjection
+import XXModels
 import Foundation
+import DependencyInjection
 
 public let evaluateNotification: NotificationEvaluation = BindingsNotificationsForMe
 
diff --git a/Sources/Integration/Implementations/GroupManager.swift b/Sources/Integration/Implementations/GroupManager.swift
index c51522bea25f9065f485b37ce4a87effc6fcd33d..4b3dc4ef2e03abff18cea433fc30a1f2ed424b33 100644
--- a/Sources/Integration/Implementations/GroupManager.swift
+++ b/Sources/Integration/Implementations/GroupManager.swift
@@ -1,4 +1,5 @@
 import Models
+import XXModels
 import Bindings
 
 extension BindingsGroupChat: GroupManagerInterface {
@@ -67,15 +68,14 @@ extension BindingsGroupChat: GroupManagerInterface {
                         return
                     }
 
-                    completion(.success(
-                        .init(
-                            leader: me,
-                            name: name,
-                            groupId: group.getID()!,
-                            status: .participating,
-                            createdAt: Date(),
-                            serialize: group.serialize()!
-                        )))
+                    completion(.success(.init(
+                        id: group.getID()!,
+                        name: name,
+                        leaderId: me,
+                        createdAt: Date(),
+                        authStatus: .participating,
+                        serialized: group.serialize()!
+                    )))
                     return
                 default:
                     break
diff --git a/Sources/Integration/Implementations/TransferManager.swift b/Sources/Integration/Implementations/TransferManager.swift
index 30fe013e648ee1b51d9da740b716553d1bb3972d..8c38a91fcc20eb289d83d0a62d9efb71592f2554 100644
--- a/Sources/Integration/Implementations/TransferManager.swift
+++ b/Sources/Integration/Implementations/TransferManager.swift
@@ -39,7 +39,7 @@ extension BindingsFileTransfer: TransferManagerInterface {
     }
 
     public func uploadFile(
-        _ file: Attachment,
+        url: URL,
         to recipient: Data,
         _ callback: @escaping (Bool, Int, Int, Int, Error?) -> Void
     ) throws -> Data {
@@ -47,10 +47,12 @@ extension BindingsFileTransfer: TransferManagerInterface {
             callback(completed, sent, arrived, total, error)
         }
 
+        guard let file = try? Data(contentsOf: url) else { fatalError() }
+
         return try send(
-            file.name,
-            fileType: file._extension.written,
-            fileData: file.data!,
+            url.lastPathComponent,
+            fileType: url.pathExtension,
+            fileData: file,
             recipientID: recipient,
             retry: 1,
             preview: nil,
diff --git a/Sources/Integration/Implementations/UserDiscovery.swift b/Sources/Integration/Implementations/UserDiscovery.swift
index b88afa0d8585ef62648eb0af7fe86160fff18b8c..56c9b4de349c6cc807e5a1e7e78a2755ffe7715e 100644
--- a/Sources/Integration/Implementations/UserDiscovery.swift
+++ b/Sources/Integration/Implementations/UserDiscovery.swift
@@ -1,5 +1,6 @@
 import Retry
 import Models
+import XXModels
 import Bindings
 import Foundation
 
diff --git a/Sources/Integration/Interfaces/BindingsInterface.swift b/Sources/Integration/Interfaces/BindingsInterface.swift
index 34e0e4207c6e1f7625cec55ddf44d7946fa18e7a..b2eef8f94ba3283557530f35ca68e83a2b8dba62 100644
--- a/Sources/Integration/Interfaces/BindingsInterface.swift
+++ b/Sources/Integration/Interfaces/BindingsInterface.swift
@@ -1,6 +1,7 @@
 import Models
-import Foundation
 import Combine
+import XXModels
+import Foundation
 
 public enum MessageDeliveryStatus {
     case sent
@@ -157,7 +158,7 @@ public protocol BindingsInterface {
 
     func listenGroupRequests(
         _: @escaping (Group, [Data], String?) -> Void,
-        groupMessages: @escaping (GroupMessage) -> Void
+        groupMessages: @escaping (Message) -> Void
     ) throws -> GroupManagerInterface?
 
     func listenNetworkUpdates(_: @escaping (Bool) -> Void)
diff --git a/Sources/Integration/Interfaces/GroupManagerInterface.swift b/Sources/Integration/Interfaces/GroupManagerInterface.swift
index c1c5523c6fd3f97f2b343d566469d3996b6dc5d6..dcddfa9e39efe1f67d33a172e28c5cf84b595267 100644
--- a/Sources/Integration/Interfaces/GroupManagerInterface.swift
+++ b/Sources/Integration/Interfaces/GroupManagerInterface.swift
@@ -1,4 +1,5 @@
 import Models
+import XXModels
 import Foundation
 
 public protocol GroupManagerInterface {
diff --git a/Sources/Integration/Interfaces/TransferManagerInterface.swift b/Sources/Integration/Interfaces/TransferManagerInterface.swift
index 294dd0ab32ce27d4fed3367002fdce45dadbedd9..b95d1ee34d2cd154192f74b336a44d6deed9dc69 100644
--- a/Sources/Integration/Interfaces/TransferManagerInterface.swift
+++ b/Sources/Integration/Interfaces/TransferManagerInterface.swift
@@ -1,4 +1,3 @@
-import Models
 import Foundation
 
 public protocol TransferManagerInterface {
@@ -20,9 +19,8 @@ public protocol TransferManagerInterface {
         with: Data
     ) throws -> Data
 
-
     func uploadFile(
-        _: Attachment,
+        url: URL,
         to: Data,
         _: @escaping (Bool, Int, Int, Int, Error?) -> Void
     ) throws -> Data
diff --git a/Sources/Integration/Interfaces/UserDiscoveryInterface.swift b/Sources/Integration/Interfaces/UserDiscoveryInterface.swift
index 07398985fd1a25e500bb9015832d18e07226b57b..ded311ecdf19e7b179b2f91d325588f57a33ff2d 100644
--- a/Sources/Integration/Interfaces/UserDiscoveryInterface.swift
+++ b/Sources/Integration/Interfaces/UserDiscoveryInterface.swift
@@ -1,4 +1,5 @@
 import Models
+import XXModels
 import Foundation
 
 public struct LookupResult {
diff --git a/Sources/Integration/Listeners.swift b/Sources/Integration/Listeners.swift
index 23f9f2234250a113862d693254238c8bc8664b74..7ab877603cb879c6d660384a0a596e259a8856e3 100644
--- a/Sources/Integration/Listeners.swift
+++ b/Sources/Integration/Listeners.swift
@@ -1,13 +1,10 @@
 import Models
 import Shared
-import Bindings
-import Foundation
 import os.log
 import Combine
-
-import Combine
-
-import Combine
+import XXModels
+import Bindings
+import Foundation
 
 public extension BindingsClient {
     static func listenLogs() {
@@ -45,7 +42,7 @@ public extension BindingsClient {
 
         let listener = TextListener { bindingsMessage in
             guard let message = bindingsMessage else { return }
-            let domainModel = Message(with: message, meMarshalled: self.meMarshalled)
+            let domainModel = Message(with: message, myId: self.myId)
             callback(domainModel)
         }
 
@@ -111,7 +108,7 @@ public extension BindingsClient {
 
     func listenGroupRequests(
         _ groupRequests: @escaping (Group, [Data], String?) -> Void,
-        groupMessages: @escaping (GroupMessage) -> Void
+        groupMessages: @escaping (Message) -> Void
     ) throws -> GroupManagerInterface? {
         var error: NSError?
 
@@ -136,16 +133,16 @@ public extension BindingsClient {
             }
 
             groupRequests(.init(
-                leader: members.first!,
+                id: id,
                 name: String(data: name, encoding: .utf8)!,
-                groupId: id,
-                status: .pending,
+                leaderId: members.first!,
                 createdAt: Date(),
-                serialize: serialize
+                authStatus: .pending,
+                serialized: serialize
             ), members, welcomeMessage)
         }
 
-        let messageCallback = GroupMessageCallback { groupMessages(GroupMessage(with: $0)) }
+        let messageCallback = GroupMessageCallback { groupMessages(Message(with: $0)) }
         let groupManager = BindingsNewGroupManager(self, requestCallback, messageCallback, &error)
 
         guard let error = error else { return groupManager }
diff --git a/Sources/Integration/Mocks/BindingsMock.swift b/Sources/Integration/Mocks/BindingsMock.swift
index 8eaf8a0688d3efdaab0ba1ccedad20bd4b6c7577..61ccceb42f58ccae7adc40a76c4e13d230f53a47 100644
--- a/Sources/Integration/Mocks/BindingsMock.swift
+++ b/Sources/Integration/Mocks/BindingsMock.swift
@@ -1,5 +1,6 @@
 import Models
 import Combine
+import XXModels
 import Foundation
 
 public final class BindingsMock: BindingsInterface {
@@ -162,7 +163,7 @@ public final class BindingsMock: BindingsInterface {
 
     public func listenGroupRequests(
         _ groupRequests: @escaping (Group, [Data], String?) -> Void,
-        groupMessages: @escaping (GroupMessage) -> Void
+        groupMessages: @escaping (Message) -> Void
     ) throws -> GroupManagerInterface? {
         groupRequestsSubject
             .sink { groupRequests($0, [], nil) }
@@ -187,12 +188,12 @@ public final class BindingsMock: BindingsInterface {
 
 extension Group {
     static let mockGroup = Group(
-        leader: "mockGroupLeader".data(using: .utf8)!,
+        id: "mockGroup".data(using: .utf8)!,
         name: "Bruno's birthday 6/1",
-        groupId: "mockGroup".data(using: .utf8)!,
-        status: .pending,
+        leaderId: "mockGroupLeader".data(using: .utf8)!,
         createdAt: Date.distantPast,
-        serialize: "mockGroup".data(using: .utf8)!
+        authStatus: .pending,
+        serialized: "mockGroup".data(using: .utf8)!
     )
 }
 
@@ -201,17 +202,18 @@ extension Contact {
         var mocks = [Contact]()
 
         for n in 0..<count {
-            mocks.append(.init(
-                photo: nil,
-                userId: "brad\(n)".data(using: .utf8)!,
-                email: "brad\(n)@xx.io",
-                phone: "819820212\(n)5BR",
-                status: .verified,
-                marshaled: "brad\(n)".data(using: .utf8)!,
-                username: "brad\(n)",
-                nickname: nil,
-                createdAt: Date(),
-                isRecent: false
+            mocks.append(
+                .init(
+                    id: "brad\(n)".data(using: .utf8)!,
+                    marshaled: "brad\(n)".data(using: .utf8)!,
+                    username: "brad\(n)",
+                    email: "brad\(n)@xx.io",
+                    phone: "819820212\(n)5BR",
+                    nickname: nil,
+                    photo: nil,
+                    authStatus: .verified,
+                    isRecent: false,
+                    createdAt: Date()
             ))
         }
 
@@ -219,55 +221,55 @@ extension Contact {
     }
 
     static let angelinaRequested = Contact(
-        photo: nil,
-        userId: "angelinajolie".data(using: .utf8)!,
-        email: nil,
-        phone: nil,
-        status: .verificationInProgress,
+        id: "angelinajolie".data(using: .utf8)!,
         marshaled: "angelinajolie".data(using: .utf8)!,
         username: "angelinajolie",
+        email: nil,
+        phone: nil,
         nickname: "Angelica Jolie",
-        createdAt: Date(),
-        isRecent: false
+        photo: nil,
+        authStatus: .verificationInProgress,
+        isRecent: false,
+        createdAt: Date()
     )
 
     static let carlRequested = Contact(
-        photo: nil,
-        userId: "carlsagan".data(using: .utf8)!,
-        email: "carl@jpl.nasa",
-        phone: "81982022244BR",
-        status: .verified,
+        id: "carlsagan".data(using: .utf8)!,
         marshaled: "carlsagan".data(using: .utf8)!,
         username: "carlsagan",
+        email: "carl@jpl.nasa",
+        phone: "81982022244BR",
         nickname: "Carl Sagan",
-        createdAt: Date.distantPast,
-        isRecent: false
+        photo: nil,
+        authStatus: .verified,
+        isRecent: false,
+        createdAt: Date.distantPast
     )
 
     static let elonRequested = Contact(
-        photo: nil,
-        userId: "elonmusk".data(using: .utf8)!,
-        email: "elon@tesla.com",
-        phone: nil,
-        status: .verified,
+        id: "elonmusk".data(using: .utf8)!,
         marshaled: "elonmusk".data(using: .utf8)!,
         username: "elonmusk",
+        email: "elon@tesla.com",
+        phone: nil,
         nickname: "Elon Musk",
-        createdAt: Date.distantPast,
-        isRecent: false
+        photo: nil,
+        authStatus: .verified,
+        isRecent: false,
+        createdAt: Date.distantPast
     )
 
     static let georgeDiscovered = Contact(
-        photo: nil,
-        userId: "georgebenson74".data(using: .utf8)!,
-        email: "george@xx.io",
-        phone: "81987022255BR",
-        status: .stranger,
+        id: "georgebenson74".data(using: .utf8)!,
         marshaled: "georgebenson74".data(using: .utf8)!,
         username: "bruno_muniz74",
+        email: "george@xx.io",
+        phone: "81987022255BR",
         nickname: "Bruno Muniz",
-        createdAt: Date(),
-        isRecent: false
+        photo: nil,
+        authStatus: .stranger,
+        isRecent: false,
+        createdAt: Date()
     )
 }
 
diff --git a/Sources/Integration/Mocks/GroupManagerMock.swift b/Sources/Integration/Mocks/GroupManagerMock.swift
index ecec61c7483c7431a84d4c4e261e7cf0e70789d5..137cfca69c25a188f20c60af867befc3d79a04cf 100644
--- a/Sources/Integration/Mocks/GroupManagerMock.swift
+++ b/Sources/Integration/Mocks/GroupManagerMock.swift
@@ -1,4 +1,5 @@
 import Models
+import XXModels
 import Foundation
 
 final class GroupManagerMock: GroupManagerInterface {
diff --git a/Sources/Integration/Mocks/TransferManagerMock.swift b/Sources/Integration/Mocks/TransferManagerMock.swift
index cd4b812e4cbfeed4f197585b3921e764f665e7c9..9b87da5d214d5cf0dcc5d39cdd88a979ff1a6e76 100644
--- a/Sources/Integration/Mocks/TransferManagerMock.swift
+++ b/Sources/Integration/Mocks/TransferManagerMock.swift
@@ -1,4 +1,3 @@
-import Models
 import Foundation
 
 final class TransferManagerMock: TransferManagerInterface {
@@ -25,7 +24,7 @@ final class TransferManagerMock: TransferManagerInterface {
     }
 
     func uploadFile(
-        _: Attachment,
+        url: URL,
         to: Data,
         _: @escaping (Bool, Int, Int, Int, Error?) -> Void
     ) throws -> Data {
diff --git a/Sources/Integration/Mocks/UserDiscoveryMock.swift b/Sources/Integration/Mocks/UserDiscoveryMock.swift
index 910faf080c1b476af9c63677a25a6211f8f8a448..bb1a8f2440ce0baf65bd8eb5dff4ef305babb842 100644
--- a/Sources/Integration/Mocks/UserDiscoveryMock.swift
+++ b/Sources/Integration/Mocks/UserDiscoveryMock.swift
@@ -1,4 +1,5 @@
 import Models
+import XXModels
 import Foundation
 
 final class UserDiscoveryMock: UserDiscoveryInterface {
@@ -26,17 +27,18 @@ final class UserDiscoveryMock: UserDiscoveryInterface {
         _ completion: @escaping (Result<Contact, Error>) -> Void
     ) {
         DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
-            completion(.success(.init(
-                photo: nil,
-                userId: "mock_username".data(using: .utf8)!,
-                email: nil,
-                phone: nil,
-                status: .stranger,
-                marshaled: "mock_username".data(using: .utf8)!,
-                username: "mock_username",
-                nickname: "mock_nickname",
-                createdAt: Date(),
-                isRecent: false
+            completion(.success(
+                .init(
+                    id: "mock_username".data(using: .utf8)!,
+                    marshaled: "mock_username".data(using: .utf8)!,
+                    username: "mock_username",
+                    email: nil,
+                    phone: nil,
+                    nickname: "mock_nickname",
+                    photo: nil,
+                    authStatus: .stranger,
+                    isRecent: false,
+                    createdAt: Date()
             )))
         }
     }
diff --git a/Sources/Integration/Session/Session+Chat.swift b/Sources/Integration/Session/Session+Chat.swift
index 5bdf71c60b733813df42fadfa5290e011b54e8d0..3e3eb63a7df4c8d2dcc1ab315335f8fb046b6d69 100644
--- a/Sources/Integration/Session/Session+Chat.swift
+++ b/Sources/Integration/Session/Session+Chat.swift
@@ -1,63 +1,32 @@
-import Models
-import Foundation
 import UIKit
+import Models
 import Shared
+import XXModels
+import Foundation
 
 extension Session {
-    public func readAll(from contact: Contact) {
-        do {
-            try dbManager.updateAll(
-                Message.self,
-                Message.Request.unreadsFromContactId(contact.userId),
-                with: [Message.Column.unread.set(to: false)]
-            )
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-        }
-    }
-
-    public func readAll(from group: Group) {
-        do {
-            try dbManager.updateAll(
-                GroupMessage.self,
-                GroupMessage.Request.unreadsFromGroup(group.groupId),
-                with: [GroupMessage.Column.unread.set(to: false)]
-            )
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-        }
-    }
-
-    public func deleteAll(from contact: Contact) {
-        do {
-            try dbManager.delete(Message.self, .withContact(contact.userId))
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-        }
-    }
-
-    public func deleteAll(from group: Group) {
-        do {
-            try dbManager.delete(GroupMessage.self, .fromGroup(group.groupId))
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-        }
-    }
-
     public func send(imageData: Data, to contact: Contact, completion: @escaping (Result<Void, Error>) -> Void) {
-        client.bindings.compress(image: imageData) { [weak self] in
+        client.bindings.compress(image: imageData) { [weak self] result in
             guard let self = self else {
                 completion(.success(()))
                 return
             }
 
-            switch $0 {
-            case .success(let compressed):
-                let name = "image_\(Date.asTimestamp)"
-                try! FileManager.store(data: compressed, name: name, type: Attachment.Extension.image.written)
-                let attachment = Attachment(name: name, data: compressed, _extension: .image)
-                self.send(Payload(text: "You sent an image", reply: nil, attachment: attachment), toContact: contact)
-                completion(.success(()))
+            switch result {
+            case .success(let compressedImage):
+                do {
+                    let url = try FileManager.store(
+                        data: compressedImage,
+                        name: "image_\(Date.asTimestamp)",
+                        type: "jpeg"
+                    )
+
+                    self.sendFile(url: url, to: contact)
+                    completion(.success(()))
+                } catch {
+                    completion(.failure(error))
+                }
+
             case .failure(let error):
                 completion(.failure(error))
                 log(string: "Error when compressing image: \(error.localizedDescription)", type: .error)
@@ -65,59 +34,119 @@ extension Session {
         }
     }
 
-    public func send(_ payload: Payload, toContact contact: Contact) {
-        var message = Message(
-            sender: client.bindings.meMarshalled,
-            receiver: contact.userId,
-            payload: payload,
-            unread: false,
-            timestamp: Date.asTimestamp,
-            uniqueId: nil,
-            status: payload.attachment == nil ? .sending : .sendingAttachment
-        )
+    public func sendFile(url: URL, to contact: Contact) {
+        guard let manager = client.transferManager else { fatalError("A transfer manager was not created") }
 
-        do {
-            message = try dbManager.save(message)
-            send(message: message)
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-        }
-    }
+        DispatchQueue.global().async { [weak self] in
+            guard let self = self else { return }
+
+            var tid: Data?
 
-    public func delete(messages: [Int64]) {
-        messages.forEach {
             do {
-                try dbManager.delete(Message.self, .withId($0))
+                tid = try manager.uploadFile(url: url, to: contact.id) { completed, send, arrived, total, error in
+                    guard let tid = tid else { return }
+
+                    if completed {
+                        self.endTransferWith(tid: tid)
+                    } else {
+                        if error != nil {
+                            self.failTransferWith(tid: tid)
+                        } else {
+                            self.progressTransferWith(tid: tid, arrived: Float(arrived), total: Float(total))
+                        }
+                    }
+                }
+
+                guard let tid = tid else { return }
+
+                let content = url.pathExtension == "m4a" ? "a voice message" : "an image"
+
+                let transfer = FileTransfer(
+                    id: tid,
+                    contactId: contact.id,
+                    name: url.deletingPathExtension().lastPathComponent,
+                    type: url.pathExtension,
+                    data: try? Data(contentsOf: url),
+                    progress: 0.0,
+                    isIncoming: false,
+                    createdAt: Date()
+                )
+
+                _ = try? self.dbManager.saveFileTransfer(transfer)
+
+                let message = Message(
+                    networkId: nil,
+                    senderId: self.client.bindings.myId,
+                    recipientId: contact.id,
+                    groupId: nil,
+                    date: Date(),
+                    status: .sending,
+                    isUnread: false,
+                    text: "You sent \(content)",
+                    replyMessageId: nil,
+                    roundURL: nil,
+                    fileTransferId: tid
+                )
+
+                _ = try? self.dbManager.saveMessage(message)
             } catch {
-                log(string: error.localizedDescription, type: .error)
+                print(error.localizedDescription)
             }
         }
     }
 
-    public func retryMessage(_ id: Int64) {
-        guard var message: Message = try? dbManager.fetch(withId: id) else { return }
-        message.timestamp = Date.asTimestamp
-        message.status = message.payload.attachment == nil ? .sending : .sendingAttachment
+    public func send(_ payload: Payload, toContact contact: Contact) {
+        var message = Message(
+            networkId: nil,
+            senderId: client.bindings.myId,
+            recipientId: contact.id,
+            groupId: nil,
+            date: Date(),
+            status: .sending,
+            isUnread: false,
+            text: payload.text,
+            replyMessageId: payload.reply?.messageId,
+            roundURL: nil,
+            fileTransferId: nil
+        )
 
         do {
-            message = try dbManager.save(message)
+            message = try dbManager.saveMessage(message)
             send(message: message)
         } catch {
             log(string: error.localizedDescription, type: .error)
         }
     }
 
-    private func send(message: Message) {
+    public func retryMessage(_ id: Int64) {
+        if var message = try? dbManager.fetchMessages(.init(id: [id])).first {
+            message.status = .sending
+            message.date = Date()
+
+            if let message = try? dbManager.saveMessage(message) {
+                if let recipientId = message.recipientId {
+                    send(message: message)
+                } else {
+                    send(groupMessage: message)
+                }
+            }
+        }
+    }
+
+    func send(message: Message) {
         var message = message
 
-        if let _ = message.payload.attachment {
-            sendAttachment(message: message)
-            return
+        var reply: Reply?
+        if let replyId = message.replyMessageId,
+           let replyMessage = try? dbManager.fetchMessages(Message.Query(networkId: replyId)).first {
+            reply = Reply(messageId: replyId, senderId: replyMessage.senderId)
         }
 
+        let payloadData = Payload(text: message.text, reply: reply).asData()
+
         DispatchQueue.global().async { [weak self] in
             guard let self = self else { return }
-            switch self.client.bindings.send(message.payload.asData(), to: message.receiver) {
+            switch self.client.bindings.send(payloadData, to: message.recipientId!) {
             case .success(let report):
                 message.roundURL = report.roundURL
 
@@ -126,34 +155,34 @@ extension Session {
                     case .success(let status):
                         switch status {
                         case .failed:
-                            message.status = .failedToSend
+                            message.status = .sendingFailed
                         case .sent:
                             message.status = .sent
                         case .timedout:
-                            message.status = .timedOut
+                            message.status = .sendingTimedOut
                         }
                     case .failure:
-                        message.status = .failedToSend
+                        message.status = .sendingFailed
                     }
 
-                    message.uniqueId = report.uniqueId
-                    message.timestamp = Int(report.timestamp)
+                    message.networkId = report.uniqueId
+                    message.date = Date.fromTimestamp(Int(report.timestamp))
                     DispatchQueue.main.async {
                         do {
-                            _ = try self.dbManager.save(message)
+                            _ = try self.dbManager.saveMessage(message)
                         } catch {
                             log(string: error.localizedDescription, type: .error)
                         }
                     }
                 }
             case .failure(let error):
-                message.status = .failedToSend
+                message.status = .sendingFailed
                 log(string: error.localizedDescription, type: .error)
             }
 
             DispatchQueue.main.async {
                 do {
-                    _ = try self.dbManager.save(message)
+                    _ = try self.dbManager.saveMessage(message)
                 } catch {
                     log(string: error.localizedDescription, type: .error)
                 }
@@ -161,157 +190,77 @@ extension Session {
         }
     }
 
-    private func sendAttachment(message: Message) {
-        guard let manager = client.transferManager else { fatalError("A transfer manager was not created") }
-
-        var message = message
-        let attachment = message.payload.attachment!
-
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                let tid = try manager.uploadFile(attachment, to: message.receiver) { completed, send, arrived, total, error in
-                    if completed {
-                        self.endTransferFrom(message: message)
-                        message.status = .sent
-                        message.payload.attachment?.progress = 1.0
-                        log(string: "FT Up finished", type: .info)
-                    } else {
-                        if let error = error {
-                            log(string: error.localizedDescription, type: .error)
-                            message.status = .failedToSend
-                        } else {
-                            let progress = Float(arrived)/Float(total)
-                            message.payload.attachment?.progress = progress
-                            log(string: "FT Up: \(progress)", type: .crumbs)
-                        }
-                    }
-
-                    do {
-                        _ = try self.dbManager.save(message) // If it fails here, means the chat was cleared.
-                    } catch {
-                        log(string: error.localizedDescription, type: .error)
-                    }
-                }
-
-                let transfer = FileTransfer(
-                    tid: tid,
-                    contact: message.receiver,
-                    fileName: attachment.name,
-                    fileType: attachment._extension.written,
-                    isIncoming: false
-                )
+    private func endTransferWith(tid: Data) {
+        guard let manager = client.transferManager else {
+            fatalError("A transfer manager was not created")
+        }
 
-                message.payload.attachment?.transferId = tid
-                message.status = .sendingAttachment
+        try? manager.endTransferUpload(with: tid)
 
-                do {
-                    _ = try self.dbManager.save(message)
-                    _ = try self.dbManager.save(transfer)
-                } catch {
-                    log(string: error.localizedDescription, type: .error)
-                }
-            } catch {
-                message.status = .failedToSend
-                log(string: error.localizedDescription, type: .error)
+        if var message = try? dbManager.fetchMessages(.init(fileTransferId: tid)).first {
+            message.status = .sent
+            _ = try? dbManager.saveMessage(message)
+        }
 
-                do {
-                    _ = try self.dbManager.save(message)
-                } catch let otherError {
-                    log(string: otherError.localizedDescription, type: .error)
-                }
-            }
+        if var transfer = try? dbManager.fetchFileTransfers(.init(id: [tid])).first {
+            transfer.progress = 1.0
+            _ = try? dbManager.saveFileTransfer(transfer)
         }
     }
 
-    private func endTransferFrom(message: Message) {
-        guard let manager = client.transferManager else { fatalError("A transfer manager was not created") }
-        guard let tid = message.payload.attachment?.transferId else { fatalError("Tried to finish a transfer that had no TID") }
-
-        do {
-            try manager.endTransferUpload(with: tid)
+    private func failTransferWith(tid: Data) {
+        if var message = try? dbManager.fetchMessages(.init(fileTransferId: tid)).first {
+            message.status = .sendingFailed
+            _ = try? dbManager.saveMessage(message)
+        }
+    }
 
-            if let transfer: FileTransfer = try? dbManager.fetch(.withTID(tid)).first {
-                try dbManager.delete(transfer)
-            }
-        } catch {
-            log(string: error.localizedDescription, type: .error)
+    private func progressTransferWith(tid: Data, arrived: Float, total: Float) {
+        if var transfer = try? dbManager.fetchFileTransfers(.init(id: [tid])).first {
+            transfer.progress = arrived/total
+            _ = try? dbManager.saveFileTransfer(transfer)
         }
     }
 
     func handle(incomingTransfer transfer: FileTransfer) {
-        guard let manager = client.transferManager else { fatalError("A transfer manager was not created") }
-
-        let fileExtension: Attachment.Extension = transfer.fileType == "m4a" ? .audio : .image
-        let name = "\(Date.asTimestamp)_\(transfer.fileName)"
-
-        var fakeContent: Data
-
-        if fileExtension == .image {
-            fakeContent = Asset.transferImagePlaceholder.image.jpegData(compressionQuality: 0.1)!
-        } else {
-            fakeContent = FileManager.dummyAudio()
+        guard let manager = client.transferManager else {
+            fatalError("A transfer manager was not created")
         }
 
-        let attachment = Attachment(name: name, data: fakeContent, transferId: transfer.tid, _extension: fileExtension)
+        let content = transfer.type == "m4a" ? "a voice message" : "an image"
 
         var message = Message(
-            sender: transfer.contact,
-            receiver: client.bindings.meMarshalled,
-            payload: .init(text: "Sent you a \(fileExtension.writtenExtended)", reply: nil, attachment: attachment),
-            unread: true,
-            timestamp: Date.asTimestamp,
-            uniqueId: nil,
-            status: .receivingAttachment
+            networkId: nil,
+            senderId: transfer.contactId,
+            recipientId: myId,
+            groupId: nil,
+            date: transfer.createdAt,
+            status: .receiving,
+            isUnread: true,
+            text: "Sent you \(content)",
+            replyMessageId: nil,
+            roundURL: nil,
+            fileTransferId: transfer.id
         )
 
-        do {
-            message = try self.dbManager.save(message)
-            try self.dbManager.save(transfer)
-        } catch {
-            log(string: "Failed to save message/transfer to the database. Will not start listening to transfer... \(error.localizedDescription)", type: .info)
-            return
-        }
-
-        log(string: "FT Down starting", type: .info)
+        message = try! self.dbManager.saveMessage(message)
 
-        try! manager.listenDownloadFromTransfer(with: transfer.tid) { completed, arrived, total, error in
-            if let error = error {
-                fatalError(error.localizedDescription)
-            }
+        try! manager.listenDownloadFromTransfer(with: transfer.id) { completed, arrived, total, error in
+            if let error = error { fatalError(error.localizedDescription) }
 
             if completed {
-                log(string: "FT Down finished", type: .info)
+                guard let rawFile = try? manager.downloadFileFromTransfer(with: transfer.id) else { return }
+                _ = try! FileManager.store(data: rawFile, name: transfer.name, type: transfer.type)
 
-                guard let rawFile = try? manager.downloadFileFromTransfer(with: transfer.tid) else {
-                    log(string: "Received finalized transfer, file was nil. Ignoring...", type: .error)
-                    return
-                }
+                var transfer = transfer
+                transfer.data = rawFile
+                transfer.progress = 1.0
+                _ = try? self.dbManager.saveFileTransfer(transfer)
 
-                try! FileManager.store(data: rawFile, name: name, type: fileExtension.written)
-                var realAttachment = Attachment(name: name, data: rawFile, transferId: transfer.tid, _extension: fileExtension)
-                realAttachment.progress = 1.0
-                message.payload = .init(text: "Sent you a \(transfer.fileType)", reply: nil, attachment: realAttachment)
                 message.status = .received
-
-                if let toDelete: FileTransfer = try? self.dbManager.fetch(.withTID(transfer.tid)).first {
-                    do {
-                        try self.dbManager.delete(toDelete)
-                    } catch {
-                        log(string: error.localizedDescription, type: .error)
-                    }
-                }
+                _ = try? self.dbManager.saveMessage(message)
             } else {
-                let progress = Float(arrived)/Float(total)
-                log(string: "FT Down: \(progress)", type: .crumbs)
-                message.payload.attachment?.progress = progress
-            }
-
-            do {
-                try self.dbManager.save(message) // If it fails here, means the chat was cleared.
-            } catch {
-                log(string: "Failed to update message model from an incoming transfer. Probably chat was cleared: \(error.localizedDescription)", type: .error)
+                self.progressTransferWith(tid: transfer.id, arrived: Float(arrived), total: Float(total))
             }
         }
     }
diff --git a/Sources/Integration/Session/Session+Contacts.swift b/Sources/Integration/Session/Session+Contacts.swift
index 53204708f1fdb09e27d1f48b0bbbde0a76c40a90..fa27fe2ece740c14217d809d8bab6fd48fae670a 100644
--- a/Sources/Integration/Session/Session+Contacts.swift
+++ b/Sources/Integration/Session/Session+Contacts.swift
@@ -1,7 +1,7 @@
 import Retry
 import Models
 import Shared
-import Database
+import XXModels
 import Foundation
 
 extension Session {
@@ -10,13 +10,11 @@ extension Session {
     }
 
     public func verify(contact: Contact) {
-        log(string: "Requested verification of \(contact.username)", type: .crumbs)
-
         var contact = contact
-        contact.status = .verificationInProgress
+        contact.authStatus = .verificationInProgress
 
         do {
-            contact = try dbManager.save(contact)
+            contact = try dbManager.saveContact(contact)
         } catch {
             log(string: "Failed to store contact request upon verification. Returning, request will be abandoned to not crash", type: .error)
         }
@@ -31,16 +29,12 @@ extension Session {
             return
         }
 
-        log(string: "Network is available. Verifying \(contact.username)", type: .crumbs)
-
         let resultClosure: (Result<Contact, Error>) -> Void = { result in
             switch result {
             case .success(let mightBe):
-                guard try! self.client.bindings.verify(marshaled: contact.marshaled, verifiedMarshaled: mightBe.marshaled) else {
-                    log(string: "\(contact.username) is fake. Deleted!", type: .info)
-
+                guard try! self.client.bindings.verify(marshaled: contact.marshaled!, verifiedMarshaled: mightBe.marshaled!) else {
                     do {
-                        try self.dbManager.delete(contact)
+                        try self.dbManager.deleteContact(contact)
                     } catch {
                         log(string: error.localizedDescription, type: .error)
                     }
@@ -48,21 +42,19 @@ extension Session {
                     return
                 }
 
-                contact.status = .verified
-                log(string: "Verified \(contact.username)", type: .info)
+                contact.authStatus = .verified
 
                 do {
-                    try self.dbManager.save(contact)
+                    try self.dbManager.saveContact(contact)
                 } catch {
                     log(string: error.localizedDescription, type: .error)
                 }
 
-            case .failure(let error):
-                log(string: "Verification of \(contact.username) failed: \(error.localizedDescription)", type: .error)
-                contact.status = .verificationFailed
+            case .failure:
+                contact.authStatus = .verificationFailed
 
                 do {
-                    try self.dbManager.save(contact)
+                    try self.dbManager.saveContact(contact)
                 } catch {
                     log(string: error.localizedDescription, type: .error)
                 }
@@ -75,7 +67,7 @@ extension Session {
         let hasPhone = contact.phone != nil
 
         guard hasEmail || hasPhone else {
-            ud.lookup(forUserId: contact.userId, resultClosure)
+            ud.lookup(forUserId: contact.id, resultClosure)
             return
         }
 
@@ -91,10 +83,10 @@ extension Session {
             try ud.search(fact: fact, resultClosure)
         } catch {
             log(string: error.localizedDescription, type: .error)
-            contact.status = .verificationFailed
+            contact.authStatus = .verificationFailed
 
             do {
-                try self.dbManager.save(contact)
+                try self.dbManager.saveContact(contact)
             } catch {
                 log(string: error.localizedDescription, type: .error)
             }
@@ -104,7 +96,7 @@ extension Session {
     public func retryRequest(_ contact: Contact) throws {
         log(string: "Retrying to request a contact", type: .info)
 
-        client.bindings.add(contact.marshaled, from: myQR) { [weak self, contact] in
+        client.bindings.add(contact.marshaled!, from: myQR) { [weak self, contact] in
             var contact = contact
             guard let self = self else { return }
 
@@ -112,13 +104,13 @@ extension Session {
                 switch $0 {
                 case .success:
                     log(string: "Retrying to request a contact -- Success", type: .info)
-                    contact.status = .requested
+                    contact.authStatus = .requested
                 case .failure(let error):
                     log(string: "Retrying to request a contact -- Failed: \(error.localizedDescription)", type: .error)
                     contact.createdAt = Date()
                 }
 
-                _ = try self.dbManager.save(contact)
+                _ = try self.dbManager.saveContact(contact)
             } catch {
                 log(string: error.localizedDescription, type: .error)
             }
@@ -132,23 +124,23 @@ extension Session {
 
         var contactToOperate: Contact!
 
-        if contact.status == .requestFailed || contact.status == .confirmationFailed {
+        if [.requestFailed, .confirmationFailed, .stranger].contains(contact.authStatus) {
             contactToOperate = contact
         } else {
-            guard (try? dbManager.fetch(.withUsername(contact.username)).first as Contact?) == nil else {
+            if let _ = try? dbManager.fetchContacts(.init(id: [contact.id])).first {
                 throw NSError.create("This user has already been requested")
             }
 
-            contactToOperate = try dbManager.save(contact)
+            contactToOperate = try dbManager.saveContact(contact)
         }
 
-        guard contactToOperate.status != .confirmationFailed else {
+        guard contactToOperate.authStatus != .confirmationFailed else {
             contactToOperate.createdAt = Date()
             try confirm(contact)
             return
         }
 
-        contactToOperate.status = .requesting
+        contactToOperate.authStatus = .requesting
 
         let myself = client.bindings.meMarshalled(
             username!,
@@ -156,119 +148,81 @@ extension Session {
             phone: isSharingPhone ? phone : nil
         )
 
-        client.bindings.add(contactToOperate.marshaled, from: myself) { [weak self, contactToOperate] in
+        client.bindings.add(contactToOperate.marshaled!, from: myself) { [weak self, contactToOperate] in
             guard let self = self, var contactToOperate = contactToOperate else { return }
-            let safeName = contactToOperate.nickname ?? contactToOperate.username
-            let title = "\(safeName.prefix(2))...\(safeName.suffix(3))"
 
             do {
                 switch $0 {
                 case .success(let success):
-                    contactToOperate.status = success ? .requested : .requestFailed
-                    contactToOperate = try self.dbManager.save(contactToOperate)
+                    contactToOperate.authStatus = success ? .requested : .requestFailed
+                    contactToOperate = try self.dbManager.saveContact(contactToOperate)
 
-                    log(string: "Successfully added \(title)", type: .info)
-                case .failure(let error):
-                    contactToOperate.status = .requestFailed
+                case .failure:
+                    contactToOperate.authStatus = .requestFailed
                     contactToOperate.createdAt = Date()
-                    contactToOperate = try self.dbManager.save(contactToOperate)
-
-                    log(string: "Failed when adding \(title):\n\(error.localizedDescription)", type: .error)
+                    contactToOperate = try self.dbManager.saveContact(contactToOperate)
 
                     self.toastController.enqueueToast(model: .init(
-                        title: Localized.Requests.Failed.toast(contactToOperate.nickname ?? contact.username),
+                        title: Localized.Requests.Failed.toast(contactToOperate.nickname ?? contact.username!),
                         color: Asset.accentDanger.color,
                         leftImage: Asset.requestFailedToaster.image
                     ))
                 }
             } catch {
-                log(string: "Error adding \(title):\n\(error.localizedDescription)", type: .error)
+                print(error.localizedDescription)
             }
         }
     }
 
     public func confirm(_ contact: Contact) throws {
         var contact = contact
-        contact.status = .confirming
-        contact = try dbManager.save(contact)
-
-        client.bindings.confirm(contact.marshaled) { [weak self] in
-            let safeName = contact.nickname ?? contact.username
-            let title = "\(safeName.prefix(2))...\(safeName.suffix(3))"
+        contact.authStatus = .confirming
+        contact = try dbManager.saveContact(contact)
 
+        client.bindings.confirm(contact.marshaled!) { [weak self] in
             switch $0 {
             case .success(let confirmed):
                 contact.isRecent = true
                 contact.createdAt = Date()
-                contact.status = confirmed ? .friend : .confirmationFailed
-                log(string: "Confirming request from \(title) = \(confirmed)", type: confirmed ? .info : .error)
-            case .failure(let error):
-                contact.status = .confirmationFailed
-                log(string: "Error confirming request from \(title):\n\(error.localizedDescription)", type: .error)
-            }
+                contact.authStatus = confirmed ? .friend : .confirmationFailed
 
-            _ = try? self?.dbManager.save(contact)
-        }
-    }
-
-    public func update(_ contact: Contact) {
-        do {
-            if var stored = try dbManager.fetch(.withUsername(contact.username)).first as Contact? {
-                stored.email = contact.email
-                stored.photo = contact.photo
-                stored.phone = contact.phone
-                stored.nickname = contact.nickname
-                stored.isRecent = contact.isRecent
-                stored.createdAt = contact.createdAt
-                try dbManager.save(stored)
-
-                try dbManager.updateAll(
-                    GroupMember.self,
-                    GroupMember.Request.withUserId(stored.userId),
-                    with: [GroupMember.Column.photo.set(to: stored.photo)]
-                )
+            case .failure:
+                contact.authStatus = .confirmationFailed
             }
-        } catch {
-            log(string: "Error updating a contact: \(error.localizedDescription)", type: .error)
-        }
-    }
-
-    public func delete<T: Persistable>(_ model: T, isRequest: Bool = false) {
-        log(string: "Deleting a model...", type: .info)
 
-        do {
-            try dbManager.delete(model)
-        } catch {
-            log(string: "Error deleting a model: \(error.localizedDescription)", type: .error)
-        }
-    }
-
-    public func find(by username: String) -> Contact? {
-        log(string: "Trying to find contact with username: \(username)", type: .info)
-
-        do {
-            if let contact: Contact = try dbManager.fetch(.withUsername(username)).first {
-                log(string: "Found \(username)!", type: .info)
-                return contact
-            } else {
-                log(string: "No such contact with username: \(username)", type: .info)
-                return nil
-            }
-        } catch {
-            log(string: "Error trying to find a contact: \(error.localizedDescription)", type: .error)
+            _ = try? self?.dbManager.saveContact(contact)
         }
-
-        return nil
     }
 
     public func deleteContact(_ contact: Contact) throws {
-        if let _: FileTransfer = try? dbManager.fetch(.withContactId(contact.userId)).first {
+        if !(try dbManager.fetchFileTransfers(.init(contactId: contact.id))).isEmpty {
             throw NSError.create("There is an ongoing file transfer with this contact as you are receiving or sending a file, please try again later once it’s done")
-        } else {
-            print("No pending transfer with this contact. Free to delete")
         }
 
-        try client.bindings.removeContact(contact.marshaled)
-        try dbManager.delete(contact)
+        try client.bindings.removeContact(contact.marshaled!)
+
+        /// Currently this cascades into deleting
+        /// all messages w/ contact.id == senderId
+        /// But this shouldn't be the always the case
+        /// because if we have a group / this contact
+        /// the messages will be gone as well.
+        ///
+        /// Suggestion: If there's a group where this user belongs to
+        /// we will just cleanup the contact model stored on the db
+        /// leaving only username and id which are the equivalent to
+        /// .stranger contacts.
+        ///
+        //try dbManager.deleteContact(contact)
+
+        _ = try? dbManager.deleteMessages(Message.Query(chat: .direct(myId, contact.id)))
+        var contact = contact
+        contact.email = nil
+        contact.phone = nil
+        contact.photo = nil
+        contact.isRecent = false
+        contact.marshaled = nil
+        contact.authStatus = .stranger
+        contact.nickname = contact.username
+        _ = try? dbManager.saveContact(contact)
     }
 }
diff --git a/Sources/Integration/Session/Session+Group.swift b/Sources/Integration/Session/Session+Group.swift
index 78670c9b0b9083dfa09ee555101a2479a153809f..a3cf68964a307dceecb77893eb5f67fe15367bab 100644
--- a/Sources/Integration/Session/Session+Group.swift
+++ b/Sources/Integration/Session/Session+Group.swift
@@ -1,38 +1,73 @@
 import Models
+import XXModels
 import Foundation
 
-public typealias GroupCompletion = (Result<(Group, [GroupMember]), Error>) -> Void
-
 extension Session {
     public func join(group: Group) throws {
         guard let manager = client.groupManager else { fatalError("A group manager was not created") }
 
-        try manager.join(group.serialize)
+        try manager.join(group.serialized)
         var group = group
-        group.status = .participating
+        group.authStatus = .participating
         scanStrangers {}
-        try dbManager.save(group)
+        try dbManager.saveGroup(group)
     }
 
     public func leave(group: Group) throws {
         guard let manager = client.groupManager else { fatalError("A group manager was not created") }
-        try manager.leave(group.groupId)
-        try dbManager.delete(group)
+        try manager.leave(group.id)
+        try dbManager.deleteGroup(group)
     }
 
-    public func createGroup(name: String, welcome: String?, members: [Contact], _ completion: @escaping GroupCompletion) {
-        guard let manager = client.groupManager else { fatalError("A group manager was not created") }
-
-        let me = client.bindings.meMarshalled
-        let memberIds = members.map { $0.userId }
+    public func createGroup(
+        name: String,
+        welcome: String?,
+        members: [Contact],
+        _ completion: @escaping (Result<GroupInfo, Error>) -> Void
+    ) {
+        guard let manager = client.groupManager else {
+            fatalError("A group manager was not created")
+        }
 
-        manager.create(me: me, name: name, welcome: welcome, with: memberIds) { [weak self] in
+        manager.create(
+            me: myId,
+            name: name,
+            welcome: welcome,
+            with: members.map { $0.id }) { [weak self] result in
             guard let self = self else { return }
 
-            switch $0 {
+            switch result {
             case .success(let group):
-                completion(.success((group, self.processGroupCreation(group, memberIds: memberIds, welcome: welcome))))
-                break
+                try! self.dbManager.saveGroup(group)
+
+                members
+                    .map { GroupMember(groupId: group.id, contactId: $0.id) }
+                    .forEach { try! self.dbManager.saveGroupMember($0) }
+
+                // TODO: Add saveBulkGroupMembers to the database
+
+                if let welcome = welcome {
+                    let message = Message(
+                        networkId: nil,
+                        senderId: self.myId,
+                        recipientId: nil,
+                        groupId: group.id,
+                        date: group.createdAt,
+                        status: .received,
+                        isUnread: false,
+                        text: welcome,
+                        replyMessageId: nil,
+                        roundURL: nil,
+                        fileTransferId: nil
+                    )
+
+                    try! self.dbManager.saveMessage(message)
+                }
+
+                let query = GroupInfo.Query(groupId: group.id)
+                let info = try! self.dbManager.fetchGroupInfos(query).first
+                completion(.success(info!))
+
             case .failure(let error):
                 completion(.failure(error))
             }
@@ -40,118 +75,137 @@ extension Session {
     }
 
     @discardableResult
-    func processGroupCreation(_ group: Group, memberIds: [Data], welcome: String?) -> [GroupMember] {
-        try! dbManager.save(group)
-
-        if let welcome = welcome {
-            try! dbManager.save(GroupMessage(group: group, text: welcome, me: client.bindings.meMarshalled))
+    func processGroupCreation(_ group: Group, memberIds: [Data], welcome: String?) -> GroupInfo {
+        /// Save the group
+        ///
+        _ = try! dbManager.saveGroup(group)
+
+        /// Which of those members are not my friends?
+        ///
+        let friendsParticipating = try! dbManager.fetchContacts(Contact.Query(id: Set(memberIds)))
+
+        /// Save the strangers as contacts
+        ///
+        let friendIds = friendsParticipating.map(\.id)
+        memberIds.forEach {
+            if !friendIds.contains($0) {
+                try! dbManager.saveContact(.init(
+                    id: $0,
+                    marshaled: nil,
+                    username: nil,
+                    email: nil,
+                    phone: nil,
+                    nickname: nil,
+                    photo: nil,
+                    authStatus: .stranger,
+                    isRecent: false,
+                    createdAt: Date()
+                ))
+            }
         }
 
-        var members: [GroupMember] = []
-
-        if let contactsOnGroup: [Contact] = try? dbManager.fetch(.withUserIds(memberIds)) {
-            contactsOnGroup.forEach { members.append(GroupMember(contact: $0, group: group)) }
+        /// Save group members relation
+        ///
+        memberIds.forEach {
+            try! dbManager.saveGroupMember(.init(groupId: group.id, contactId: $0))
         }
 
-        let strangersOnGroup = memberIds
-            .filter { !members.map { $0.userId }.contains($0) }
-            .filter { $0 != client.bindings.myId }
-
-        if !strangersOnGroup.isEmpty {
-            for stranger in strangersOnGroup.enumerated() {
-                members.append(GroupMember(
-                    userId: stranger.element,
-                    groupId: group.groupId,
-                    status: .pendingUsername,
-                    username: "Fetching username...",
-                    photo: nil
-                ))
-            }
+        /// Save the welcome message (if any)
+        ///
+        if let welcome = welcome {
+            _ = try! dbManager.saveMessage(.init(
+                networkId: nil,
+                senderId: group.leaderId,
+                recipientId: nil,
+                groupId: group.id,
+                date: group.createdAt,
+                status: .received,
+                isUnread: true,
+                text: welcome,
+                replyMessageId: nil,
+                roundURL: nil,
+                fileTransferId: nil
+            ))
         }
 
-        members.forEach { try! dbManager.save($0) }
 
-        if group.leader != client.bindings.meMarshalled, inappnotifications {
+        if inappnotifications {
             DeviceFeedback.sound(.contactAdded)
             DeviceFeedback.shake(.notification)
         }
 
         scanStrangers {}
-        return members
+
+        let info = try! dbManager.fetchGroupInfos(.init(groupId: group.id)).first
+        return info!
     }
 }
 
 // MARK: - GroupMessages
 
 extension Session {
-    public func delete(groupMessages: [Int64]) {
-        groupMessages.forEach {
-            do {
-                try dbManager.delete(GroupMessage.self, .id($0))
-            } catch {
-                log(string: error.localizedDescription, type: .error)
-            }
-        }
-    }
-
     public func send(_ payload: Payload, toGroup group: Group) {
-        var groupMessage = GroupMessage(
-            sender: client.bindings.meMarshalled,
-            groupId: group.groupId,
-            payload: payload,
-            unread: false,
-            timestamp: Date.asTimestamp,
-            uniqueId: nil,
-            status: .sending
+        var message = Message(
+            senderId: client.bindings.myId,
+            recipientId: nil,
+            groupId: group.id,
+            date: Date(),
+            status: .sending,
+            isUnread: false,
+            text: payload.text,
+            replyMessageId: payload.reply?.messageId,
+            roundURL: nil,
+            fileTransferId: nil
         )
 
         do {
-            groupMessage = try dbManager.save(groupMessage)
-            send(groupMessage: groupMessage)
+            message = try dbManager.saveMessage(message)
+            send(groupMessage: message)
         } catch {
             log(string: error.localizedDescription, type: .error)
         }
     }
 
-    public func retryGroupMessage(_ id: Int64) {
-        guard var message: GroupMessage = try? dbManager.fetch(withId: id) else { return }
-        message.timestamp = Date.asTimestamp
-        message.status = .sending
-        send(groupMessage: try! dbManager.save(message))
-    }
-
-    private func send(groupMessage: GroupMessage) {
+    func send(groupMessage: Message) {
         guard let manager = client.groupManager else { fatalError("A group manager was not created") }
-        var groupMessage = groupMessage
+        var message = groupMessage
+
+        var reply: Reply?
+        if let replyId = message.replyMessageId,
+           let replyMessage = try? dbManager.fetchMessages(Message.Query(networkId: replyId)).first {
+            reply = Reply(messageId: replyId, senderId: replyMessage.senderId)
+        }
+
+        let payloadData = Payload(text: message.text, reply: reply).asData()
 
         DispatchQueue.global().async { [weak self] in
             guard let self = self else { return }
 
-            switch manager.send(groupMessage.payload.asData(), to: groupMessage.groupId) {
+            switch manager.send(payloadData, to: message.groupId!) {
             case .success((let roundId, let uniqueId, let roundURL)):
-                groupMessage.roundURL = roundURL
+                message.roundURL = roundURL
 
                 self.client.bindings.listenRound(id: Int(roundId)) { result in
                     switch result {
                     case .success(let succeeded):
-                        groupMessage.uniqueId = uniqueId
-                        groupMessage.status = succeeded ? .sent : .failed
+                        message.networkId = uniqueId
+                        message.status = succeeded ? .sent : .sendingFailed
                     case .failure:
-                        groupMessage.status = .failed
+                        message.status = .sendingFailed
                     }
 
                     do {
-                        try self.dbManager.save(groupMessage)
+                        try self.dbManager.saveMessage(message)
                     } catch {
                         log(string: error.localizedDescription, type: .error)
                     }
                 }
             case .failure:
-                groupMessage.status = .failed
+                message.status = .sendingFailed
             }
 
             do {
-                try self.dbManager.save(groupMessage)
+                try self.dbManager.saveMessage(message)
             } catch {
                 log(string: error.localizedDescription, type: .error)
             }
@@ -160,77 +214,30 @@ extension Session {
 
     public func scanStrangers(_ completion: @escaping () -> Void) {
         DispatchQueue.global().async { [weak self] in
-            guard let self = self, let ud = self.client.userDiscovery else { return }
-
-            guard let strangers: [GroupMember] = try? self.dbManager.fetch(.strangers) else {
-                DispatchQueue.main.async {
-                    completion()
-                }
-
-                return
-            }
-
-            let ids = strangers.map { $0.userId }
-
-            var updatedStrangers: [GroupMember] = []
-
-            ud.lookup(idList: ids) {
-                switch $0 {
-                case .success(let contacts):
-                    strangers.forEach { stranger in
-                        if let found = contacts.first(where: { contact in contact.userId == stranger.userId }) {
-                            var updatedStranger = stranger
-                            updatedStranger.status = .usernameSet
-                            updatedStranger.username = found.username
-                            updatedStrangers.append(updatedStranger)
-                        }
+            guard let self = self,
+                  let ud = self.client.userDiscovery,
+                  let strangers = try? self.dbManager.fetchContacts(.init(username: .some(nil))),
+                  !strangers.isEmpty else { return }
+
+            ud.lookup(idList: strangers.map(\.id)) { result in
+                switch result {
+                case .success(let strangersWithUsernames):
+                    let acquaintances = strangers.map { stranger -> Contact in
+                        var exStranger = stranger
+                        exStranger.username = strangersWithUsernames.first(where: { $0.id == stranger.id })?.username
+                        return exStranger
                     }
 
                     DispatchQueue.main.async {
-                        updatedStrangers.forEach {
-                            do {
-                                try self.dbManager.save($0)
-                            } catch {
-                                log(string: error.localizedDescription, type:.error)
-                            }
-                        }
-
-                        log(string: "Scanned unknown group members", type: .info)
-                        completion()
+                        acquaintances.forEach { _ = try? self.dbManager.saveContact($0) }
                     }
+
+                    completion()
                 case .failure(let error):
-                    DispatchQueue.main.async {
-                        log(string: error.localizedDescription, type: .error)
-                        completion()
-                    }
+                    print(error.localizedDescription)
+                    DispatchQueue.main.async { completion() }
                 }
             }
         }
     }
 }
-
-private extension GroupMessage {
-    init(group: Group, text: String, me: Data) {
-        self.init(
-            sender: group.leader,
-            groupId: group.groupId,
-            payload: .init(text: text, reply: nil, attachment: nil),
-            unread: false,
-            timestamp: Date.asTimestamp,
-            uniqueId: nil,
-            status: group.leader == me ? .sent : .received
-        )
-    }
-}
-
-private extension GroupMember {
-    init(contact: Contact, group: Group) {
-        self.init(
-            userId: contact.userId,
-            groupId: group.groupId,
-            status: .usernameSet,
-            username: contact.username,
-            photo: contact.photo
-        )
-    }
-}
diff --git a/Sources/Integration/Session/Session+UD.swift b/Sources/Integration/Session/Session+UD.swift
index 82c1e427b6312ae5ab647e3fb2783388ba4368d6..6dd7472fedb8b3c9fd798134d8ede35a29ce3804 100644
--- a/Sources/Integration/Session/Session+UD.swift
+++ b/Sources/Integration/Session/Session+UD.swift
@@ -1,4 +1,5 @@
 import Models
+import XXModels
 import Foundation
 
 extension Session {
@@ -40,6 +41,19 @@ extension Session {
 
                 switch $0 {
                 case .success(_):
+                    _ = try? self.dbManager.saveContact(.init(
+                        id: self.client.bindings.myId,
+                        marshaled: self.client.bindings.meMarshalled,
+                        username: value,
+                        email: nil,
+                        phone: nil,
+                        nickname: nil,
+                        photo: nil,
+                        authStatus: .friend,
+                        isRecent: false,
+                        createdAt: Date()
+                    ))
+
                     self.username = value
                     completion(.success(nil))
                 case .failure(let error):
diff --git a/Sources/Integration/Session/Session.swift b/Sources/Integration/Session/Session.swift
index 2982b295ff964a8eae68c7fd6d58d4ca37788a7c..8a1c20407a7fa24ede9ea75f9468034210dca89f 100644
--- a/Sources/Integration/Session/Session.swift
+++ b/Sources/Integration/Session/Session.swift
@@ -1,16 +1,17 @@
 import Retry
+import os.log
 import Models
 import Shared
 import Combine
 import Defaults
-import Database
+import XXModels
+import XXDatabase
 import Foundation
+import ToastFeature
 import BackupFeature
 import NetworkMonitor
 import DependencyInjection
-
-import os.log
-import ToastFeature
+import XXLegacyDatabaseMigrator
 
 let logHandler = OSLog(subsystem: "xx.network", category: "Performance debugging")
 
@@ -52,7 +53,7 @@ public final class Session: SessionType {
     @Dependency var networkMonitor: NetworkMonitoring
 
     public let client: Client
-    public let dbManager: DatabaseManager
+    public let dbManager: Database
     private var cancellables = Set<AnyCancellable>()
 
     public var myId: Data { client.bindings.myId }
@@ -76,61 +77,6 @@ public final class Session: SessionType {
         networkMonitor.statusPublisher.map { $0 == .available }.eraseToAnyPublisher()
     }
 
-    public func groups(_ request: Group.Request) -> AnyPublisher<[Group], Never> {
-        self.dbManager
-            .publisher(fetch: Group.self, request)
-            .catch { _ in Just([]) }
-            .eraseToAnyPublisher()
-    }
-
-    public func groupMembers(_ request: GroupMember.Request) -> AnyPublisher<[GroupMember], Never> {
-        self.dbManager
-            .publisher(fetch: GroupMember.self, request)
-            .catch { _ in Just([]) }
-            .eraseToAnyPublisher()
-    }
-
-    lazy public var contacts: (Contact.Request) -> AnyPublisher<[Contact], Never> = {
-        self.dbManager.publisher(fetch: Contact.self, $0).catch { _ in Just([]) }.eraseToAnyPublisher()
-    }
-
-    lazy public var singleMessages: (Contact) -> AnyPublisher<[Message], Never> = {
-        self.dbManager.publisher(fetch: Message.self, .withContact($0.userId)).catch { _ in Just([]) }.eraseToAnyPublisher()
-    }
-
-    lazy public var groupMessages: (Group) -> AnyPublisher<[GroupMessage], Never> = {
-        self.dbManager.publisher(fetch: GroupMessage.self, .fromGroup($0.groupId)).catch { _ in Just([]) }.eraseToAnyPublisher()
-    }
-
-    lazy public var groupChats: (GroupChatInfo.Request) -> AnyPublisher<[GroupChatInfo], Never> = {
-        self.dbManager.publisher(fetch: GroupChatInfo.self, $0).catch { _ in Just([]) }.eraseToAnyPublisher()
-    }
-
-    lazy public var singleChats: (SingleChatInfo.Request) -> AnyPublisher<[SingleChatInfo], Never> = { _ in
-        self.dbManager.publisher(fetch: Contact.self, .friends)
-            .flatMap { [unowned self] contactList -> AnyPublisher<[SingleChatInfo], Error> in
-                let contactIds = contactList.map { $0.userId }
-
-                let messagesPublisher: AnyPublisher<[Message], Error> = dbManager
-                    .publisher(fetch: .latestOnesFromContactIds(contactIds))
-                    .map { $0.sorted(by: { $0.timestamp > $1.timestamp }) }
-                    .eraseToAnyPublisher()
-
-                return messagesPublisher.map { messages -> [SingleChatInfo] in
-                    contactList.map { contact -> SingleChatInfo in
-                        SingleChatInfo(contact: contact, lastMessage: messages.first {
-                            $0.sender == contact.userId || $0.receiver == contact.userId
-                        })
-                    }
-                }
-                .eraseToAnyPublisher()
-            }
-            .catch { _ in Just([]) }
-            .map { $0.filter { $0.lastMessage != nil }}
-            .map { $0.sorted(by: { $0.lastMessage!.timestamp > $1.lastMessage!.timestamp })}
-            .eraseToAnyPublisher()
-    }
-
     public init(
         passphrase: String,
         backupFile: Data,
@@ -143,7 +89,40 @@ public final class Session: SessionType {
         os_signpost(.end, log: logHandler, name: "Decrypting", "Finished newClientFromBackup")
 
         self.client = client
-        dbManager = GRDBDatabaseManager()
+
+        let legacyOldPath = NSSearchPathForDirectoriesInDomains(
+            .documentDirectory, .userDomainMask, true
+        )[0].appending("/xxmessenger.sqlite")
+
+        let legacyPath = FileManager.default
+            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
+            .appendingPathComponent("database")
+            .appendingPathExtension("sqlite").path
+
+        let dbExistsInLegacyOldPath = FileManager.default.fileExists(atPath: legacyOldPath)
+        let dbExistsInLegacyPath = FileManager.default.fileExists(atPath: legacyPath)
+
+        if dbExistsInLegacyOldPath && !dbExistsInLegacyPath {
+            try? FileManager.default.moveItem(atPath: legacyOldPath, toPath: legacyPath)
+        }
+
+        let dbPath = FileManager.default
+            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
+            .appendingPathComponent("xxm_database")
+            .appendingPathExtension("sqlite").path
+
+        dbManager = try Database.onDisk(path: dbPath)
+
+        if dbExistsInLegacyPath {
+            try Migrator.live()(
+                try .init(path: legacyPath),
+                to: dbManager,
+                myContactId: client.bindings.myId,
+                meMarshaled: client.bindings.meMarshalled
+            )
+
+            try FileManager.default.moveItem(atPath: legacyPath, toPath: legacyPath.appending("-backup"))
+        }
 
         let report = try! JSONDecoder().decode(BackupReport.self, from: backupData!)
 
@@ -165,30 +144,60 @@ public final class Session: SessionType {
     public init(ndf: String) throws {
         let network = try! DependencyInjection.Container.shared.resolve() as XXNetworking
         self.client = try network.newClient(ndf: ndf)
-        dbManager = GRDBDatabaseManager()
+
+        let legacyOldPath = NSSearchPathForDirectoriesInDomains(
+            .documentDirectory, .userDomainMask, true
+        )[0].appending("/xxmessenger.sqlite")
+
+        let legacyPath = FileManager.default
+            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
+            .appendingPathComponent("database")
+            .appendingPathExtension("sqlite").path
+
+        let dbExistsInLegacyOldPath = FileManager.default.fileExists(atPath: legacyOldPath)
+        let dbExistsInLegacyPath = FileManager.default.fileExists(atPath: legacyPath)
+
+        if dbExistsInLegacyOldPath && !dbExistsInLegacyPath {
+            try? FileManager.default.moveItem(atPath: legacyOldPath, toPath: legacyPath)
+        }
+
+        let dbPath = FileManager.default
+            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
+            .appendingPathComponent("xxm_database")
+            .appendingPathExtension("sqlite").path
+
+        dbManager = try Database.onDisk(path: dbPath)
+
+        if dbExistsInLegacyPath {
+            try Migrator.live()(
+                try .init(path: legacyPath),
+                to: dbManager,
+                myContactId: client.bindings.myId,
+                meMarshaled: client.bindings.meMarshalled
+            )
+
+            try FileManager.default.moveItem(atPath: legacyPath, toPath: legacyPath.appending("-backup"))
+        }
+
         try continueInitialization()
     }
 
     private func continueInitialization() throws {
-        try dbManager.setup()
-
         setupBindings()
         networkMonitor.start()
 
         networkMonitor.statusPublisher
             .filter { $0 == .available }.first()
-            .sink { [unowned self] _ in client.bindings.replayRequests() }
+            .sink { [unowned self] _ in
+                client.bindings.replayRequests()
+                scanStrangers {}
+            }
             .store(in: &cancellables)
 
         registerUnfinishedTransfers()
 
-        if let pendingVerificationUsers: [Contact] = try? dbManager.fetch(.verificationInProgress) {
-            pendingVerificationUsers.forEach {
-                var contact = $0
-                contact.status = .verificationFailed
-                _ = try? dbManager.save(contact)
-            }
-        }
+        let query = Contact.Query(authStatus: [.verificationInProgress])
+        _ = try? dbManager.bulkUpdateContacts(query, .init(authStatus: .verificationFailed))
     }
 
     public func setDummyTraffic(status: Bool) {
@@ -210,7 +219,7 @@ public final class Session: SessionType {
             guard self.hasRunningTasks == false else { throw NSError.create("") }
         }.finalCatch { _ in fatalError("Couldn't delete account because network is not stopping") }
 
-        dbManager.drop()
+        try! dbManager.drop()
         FileManager.xxCleanup()
 
         email = nil
@@ -230,67 +239,46 @@ public final class Session: SessionType {
         inappnotifications = true
     }
 
-    public func hideRequestOf(group: Group) {
-        var group = group
-        group.status = .hidden
-        _ = try? dbManager.save(group)
-    }
-
-    public func hideRequestOf(contact: Contact) {
-        var contact = contact
-        contact.status = .hidden
-        _ = try? dbManager.save(contact)
-    }
-
-    public func forceFailMessages() {
-        if let pendingE2E: [Message] = try? dbManager.fetch(.sending) {
-            pendingE2E.forEach {
-                var message = $0
-                message.status = .failedToSend
-                _ = try? dbManager.save(message)
+    private func registerUnfinishedTransfers() {
+        guard let unfinishedSendingMessages = try? dbManager.fetchMessages(.init(status: [.sending])),
+              let unfinishedSendingTransfers = try? dbManager.fetchFileTransfers(.init(
+                id: Set(unfinishedSendingMessages
+                    .filter { $0.fileTransferId != nil }
+                    .compactMap(\.fileTransferId))))
+        else { return }
+
+        let pairs = unfinishedSendingMessages.map { message -> (Message, FileTransfer) in
+            let transfer = unfinishedSendingTransfers.first { ft in
+                ft.id == message.fileTransferId
             }
-        }
 
-        if let pendingGroupMessages: [GroupMessage] = try? dbManager.fetch(.sending) {
-            pendingGroupMessages.forEach {
-                var message = $0
-                message.status = .failed
-                _ = try? dbManager.save(message)
-            }
+            return (message, transfer!)
         }
-    }
-
-    private func registerUnfinishedTransfers() {
-        guard let unfinisheds: [Message] = try? dbManager.fetch(.sendingAttachment), !unfinisheds.isEmpty else { return }
 
-        for var message in unfinisheds {
-            guard let tid = message.payload.attachment?.transferId else { return }
+        pairs.forEach { message, transfer in
+            var message = message
+            var transfer = transfer
 
             do {
-                try client.transferManager?.listenUploadFromTransfer(with: tid) { completed, sent, arrived, total, error in
+                try client.transferManager?.listenUploadFromTransfer(with: transfer.id) { completed, sent, arrived, total, error in
                     if completed {
+                        transfer.progress = 1.0
                         message.status = .sent
-                        message.payload.attachment?.progress = 1.0
 
-                        if let transfer: FileTransfer = try? self.dbManager.fetch(.withTID(tid)).first {
-                            try? self.dbManager.delete(transfer)
-                        }
                     } else {
-                        if let error = error {
-                            log(string: error.localizedDescription, type: .error)
-                            message.status = .failedToSend
+                        if error != nil {
+                            message.status = .sendingFailed
                         } else {
-                            let progress = Float(arrived)/Float(total)
-                            message.payload.attachment?.progress = progress
-                            return
+                            transfer.progress = Float(arrived)/Float(total)
                         }
                     }
 
-                    _ = try? self.dbManager.save(message)
+                    _ = try? self.dbManager.saveFileTransfer(transfer)
+                    _ = try? self.dbManager.saveMessage(message)
                 }
             } catch {
-                message.status = .sent
-                _ = try? self.dbManager.save(message)
+                message.status = .sendingFailed
+                _ = try? self.dbManager.saveMessage(message)
             }
         }
     }
@@ -320,19 +308,23 @@ public final class Session: SessionType {
 
     private func setupBindings() {
         client.requests
-            .sink { [unowned self] request in
-                if let _: Contact = try? dbManager.fetch(.withUserId(request.userId)).first { return }
+            .sink { [unowned self] contact in
+                let query = Contact.Query(id: [contact.id])
+
+                if let prexistent = try? dbManager.fetchContacts(query).first {
+                    guard prexistent.authStatus == .stranger else { return }
+                }
 
                 if self.inappnotifications {
                     DeviceFeedback.sound(.contactAdded)
                     DeviceFeedback.shake(.notification)
                 }
 
-                verify(contact: request)
+                verify(contact: contact)
             }.store(in: &cancellables)
 
         client.requestsSent
-            .sink { [unowned self] in _ = try? dbManager.save($0) }
+            .sink { [unowned self] in _ = try? dbManager.saveContact($0) }
             .store(in: &cancellables)
 
         client.backup
@@ -346,8 +338,8 @@ public final class Session: SessionType {
                 /// TODO: Hold a record on the chat that this contact restored.
                 ///
                 var contact = $0
-                contact.status = .friend
-                _ = try? dbManager.save(contact)
+                contact.authStatus = .friend
+                _ = try? dbManager.saveContact(contact)
             }.store(in: &cancellables)
 
         backupService.settingsPublisher
@@ -375,31 +367,25 @@ public final class Session: SessionType {
             .sink { print($0) }
             .store(in: &cancellables)
 
-        client.groupMessages
-            .sink { [unowned self] in _ = try? dbManager.save($0) }
-            .store(in: &cancellables)
-
         client.messages
             .sink { [unowned self] in
-                if var contact: Contact = try? dbManager.fetch(.withUserId($0.sender)).first {
+                if var contact = try? dbManager.fetchContacts(.init(id: [$0.senderId])).first {
                     contact.isRecent = false
-                    _ = try? dbManager.save(contact)
+                    _ = try? dbManager.saveContact(contact)
                 }
 
-                _ = try? dbManager.save($0)
+                _ = try? dbManager.saveMessage($0)
             }.store(in: &cancellables)
 
         client.network
             .sink { [unowned self] in networkMonitor.update($0) }
             .store(in: &cancellables)
 
-        client.incomingTransfers
-            .sink { [unowned self] in handle(incomingTransfer: $0) }
-            .store(in: &cancellables)
-
         client.groupRequests
             .sink { [unowned self] request in
-                if let _: Group = try? dbManager.fetch(.withGroupId(request.0.groupId)).first { return }
+                if let _ = try? dbManager.fetchGroups(.init(id: [request.0.id])).first {
+                    return
+                }
 
                 DispatchQueue.global().async { [weak self] in
                     self?.processGroupCreation(request.0, memberIds: request.1, welcome: request.2)
@@ -408,38 +394,42 @@ public final class Session: SessionType {
 
         client.confirmations
             .sink { [unowned self] in
-                if var contact: Contact = try? dbManager.fetch(.withUserId($0.userId)).first {
-                    contact.status = .friend
+                if var contact = try? dbManager.fetchContacts(.init(id: [$0.id])).first {
+                    contact.authStatus = .friend
                     contact.isRecent = true
                     contact.createdAt = Date()
-                    _ = try? dbManager.save(contact)
+                    _ = try? dbManager.saveContact(contact)
 
                     toastController.enqueueToast(model: .init(
-                        title: contact.nickname ?? contact.username,
+                        title: contact.nickname ?? contact.username!,
                         subtitle: Localized.Requests.Confirmations.toaster,
                         leftImage: Asset.sharedSuccess.image
                     ))
                 }
             }.store(in: &cancellables)
-    }
 
-    public func getTextFromMessage(messageId: Data) -> String? {
-        guard let message: Message = try? dbManager.fetch(.withUniqueId(messageId)).first else { return nil }
-        return message.payload.text
-    }
-
-    public func getTextFromGroupMessage(messageId: Data) -> String? {
-        guard let message: GroupMessage = try? dbManager.fetch(.withUniqueId(messageId)).first else { return nil }
-        return message.payload.text
-    }
-
-    public func getContactWith(userId: Data) -> Contact? {
-        let contact: Contact? = try? dbManager.fetch(.withUserId(userId)).first
-        return contact
-    }
-
-    public func getGroupChatInfoWith(groupId: Data) -> GroupChatInfo? {
-        let info: GroupChatInfo? = try? dbManager.fetch(.fromGroup(groupId)).first
-        return info
+        client.transfers
+            .sink { [unowned self] in
+                _ = try? dbManager.saveFileTransfer($0)
+
+                let content = $0.type == "m4a" ? "a voice message" : "an image"
+
+                let message = Message(
+                    networkId: $0.id,
+                    senderId: $0.contactId,
+                    recipientId: myId,
+                    groupId: nil,
+                    date: $0.createdAt,
+                    status: .receiving,
+                    isUnread: true,
+                    text: "Sent you \(content)",
+                    replyMessageId: nil,
+                    roundURL: nil,
+                    fileTransferId: $0.id
+                )
+
+                _ = try? dbManager.saveMessage(message)
+            }
+            .store(in: &cancellables)
     }
 }
diff --git a/Sources/Integration/Session/SessionType.swift b/Sources/Integration/Session/SessionType.swift
index 121469499c3841d9e1921f14b7f5efdf52a8093e..e99c6f37fb45599aae26836c7a8f248ae20f02f1 100644
--- a/Sources/Integration/Session/SessionType.swift
+++ b/Sources/Integration/Session/SessionType.swift
@@ -1,6 +1,6 @@
 import Models
 import Combine
-import Database
+import XXModels
 import Foundation
 
 public protocol SessionType {
@@ -10,25 +10,12 @@ public protocol SessionType {
     var hasRunningTasks: Bool { get }
     var isOnline: AnyPublisher<Bool, Never> { get }
 
-    var contacts: (Contact.Request) -> AnyPublisher<[Contact], Never> { get }
-    var singleMessages: (Contact) -> AnyPublisher<[Message], Never> { get }
-    var singleChats: (SingleChatInfo.Request) -> AnyPublisher<[SingleChatInfo], Never> { get }
-
-    func groupMembers(_: GroupMember.Request) -> AnyPublisher<[GroupMember], Never>
-
-    func groups(_: Group.Request) -> AnyPublisher<[Group], Never>
-    var groupMessages: (Group) -> AnyPublisher<[GroupMessage], Never> { get }
-    var groupChats: (GroupChatInfo.Request) -> AnyPublisher<[GroupChatInfo], Never> { get }
+    var dbManager: Database { get }
 
     func deleteMyself() throws
     func getId(from: Data) -> Data?
 
-    func forceFailMessages()
-
-    func hideRequestOf(group: Group)
-
-    func hideRequestOf(contact: Contact)
-
+    func sendFile(url: URL, to: Contact)
     func send(imageData: Data, to: Contact, completion: @escaping (Result<Void, Error>) -> Void)
 
     func verify(contact: Contact)
@@ -55,27 +42,13 @@ public protocol SessionType {
 
     // Messages
 
-    func readAll(from: Group)
-    func readAll(from: Contact)
     func retryMessage(_: Int64)
-    func retryGroupMessage(_: Int64)
-    func deleteAll(from: Group)
-    func deleteAll(from: Contact)
-    func delete(messages: [Int64])
-    func delete(groupMessages: [Int64])
     func send(_: Payload, toContact: Contact)
 
-    func getTextFromMessage(messageId: Data) -> String?
-    func getTextFromGroupMessage(messageId: Data) -> String?
-
     // Contacts
 
-    func update(_: Contact)
     func add(_: Contact) throws
     func confirm(_: Contact) throws
-    func find(by: String) -> Contact?
-    func delete<T: Persistable>(_: T, isRequest: Bool)
-
     func deleteContact(_: Contact) throws
 
     func retryRequest(_: Contact) throws
@@ -91,9 +64,6 @@ public protocol SessionType {
         name: String,
         welcome: String?,
         members: [Contact],
-        _ completion: @escaping (Result<(Group, [GroupMember]), Error>) -> Void
+        _ completion: @escaping (Result<GroupInfo, Error>) -> Void
     )
-
-    func getContactWith(userId: Data) -> Contact?
-    func getGroupChatInfoWith(groupId: Data) -> GroupChatInfo?
 }
diff --git a/Sources/LaunchFeature/LaunchCoordinator.swift b/Sources/LaunchFeature/LaunchCoordinator.swift
index 2f446810a29e5639190477705ffd8225bb88eb37..4f5a56291b19634df4c46edc2634acb55ba5812e 100644
--- a/Sources/LaunchFeature/LaunchCoordinator.swift
+++ b/Sources/LaunchFeature/LaunchCoordinator.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Models
+import XXModels
 import Presentation
 
 public protocol LaunchCoordinating {
@@ -7,7 +8,7 @@ public protocol LaunchCoordinating {
     func toRequests(from: UIViewController)
     func toOnboarding(with: String, from: UIViewController)
     func toSingleChat(with: Contact, from: UIViewController)
-    func toGroupChat(with: GroupChatInfo, from: UIViewController)
+    func toGroupChat(with: GroupInfo, from: UIViewController)
 }
 
 public struct LaunchCoordinator: LaunchCoordinating {
@@ -17,14 +18,14 @@ public struct LaunchCoordinator: LaunchCoordinating {
     var chatListFactory: () -> UIViewController
     var onboardingFactory: (String) -> UIViewController
     var singleChatFactory: (Contact) -> UIViewController
-    var groupChatFactory: (GroupChatInfo) -> UIViewController
+    var groupChatFactory: (GroupInfo) -> UIViewController
 
     public init(
         requestsFactory: @escaping () -> UIViewController,
         chatListFactory: @escaping () -> UIViewController,
         onboardingFactory: @escaping (String) -> UIViewController,
         singleChatFactory: @escaping (Contact) -> UIViewController,
-        groupChatFactory: @escaping (GroupChatInfo) -> UIViewController
+        groupChatFactory: @escaping (GroupInfo) -> UIViewController
     ) {
         self.requestsFactory = requestsFactory
         self.chatListFactory = chatListFactory
@@ -56,7 +57,7 @@ public extension LaunchCoordinator {
         replacePresenter.present(chatListScreen, singleChatScreen, from: parent)
     }
 
-    func toGroupChat(with group: GroupChatInfo, from parent: UIViewController) {
+    func toGroupChat(with group: GroupInfo, from parent: UIViewController) {
         let chatListScreen = chatListFactory()
         let groupChatScreen = groupChatFactory(group)
         replacePresenter.present(chatListScreen, groupChatScreen, from: parent)
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index 5e1b78a61a04e567242a2b058f7d5afa872bfb7f..054432b4403e7177bf129c21680a58ac4abb6b12 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -1,8 +1,10 @@
 import HUD
 import Shared
 import Models
+
 import Combine
 import Defaults
+import XXModels
 import Foundation
 import Integration
 import Permissions
@@ -119,16 +121,16 @@ final class LaunchViewModel {
 
     func getContactWith(userId: Data) -> Contact? {
         guard let session = try? DependencyInjection.Container.shared.resolve() as SessionType,
-              let contact = session.getContactWith(userId: userId) else {
+              let contact = try? session.dbManager.fetchContacts(.init(id: [userId])).first else {
             return nil
         }
 
         return contact
     }
 
-    func getGroupInfoWith(groupId: Data) -> GroupChatInfo? {
+    func getGroupInfoWith(groupId: Data) -> GroupInfo? {
         guard let session: SessionType = try? DependencyInjection.Container.shared.resolve(),
-              let info = session.getGroupChatInfoWith(groupId: groupId) else {
+              let info = try? session.dbManager.fetchGroupInfos(.init(groupId: groupId)).first else {
             return nil
         }
 
diff --git a/Sources/MenuFeature/ViewModels/MenuViewModel.swift b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
index 92eb785136c7cc4ba6ff6fb30fc597d93d6a3ac7..f3e4bcbd5260af8c2e62849c7eca05b4098cffe4 100644
--- a/Sources/MenuFeature/ViewModels/MenuViewModel.swift
+++ b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
@@ -1,4 +1,5 @@
 import Combine
+import XXModels
 import Defaults
 import Foundation
 import Integration
@@ -11,24 +12,21 @@ final class MenuViewModel {
     @KeyObject(.username, defaultValue: "") var username: String
 
     var requestCount: AnyPublisher<Int, Never> {
-        Publishers.CombineLatest(
-            session.contacts(.received),
-            session.groups(.pending)
-        ).map { (contacts, groups) in
-            let contactRequests = contacts.filter {
-                $0.status == .verified ||
-                $0.status == .confirming ||
-                $0.status == .confirmationFailed ||
-                $0.status == .verificationFailed ||
-                $0.status == .verificationInProgress
-            }
-
-            let groupRequests = groups.filter {
-                $0.status == .pending
-            }
-
-            return contactRequests.count + groupRequests.count
-        }.eraseToAnyPublisher()
+        let groupQuery = Group.Query(authStatus: [.pending])
+        let contactsQuery = Contact.Query(authStatus: [
+            .verified,
+            .confirming,
+            .confirmationFailed,
+            .verificationFailed,
+            .verificationInProgress
+        ])
+
+        return Publishers.CombineLatest(
+            session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
+            session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure()
+        )
+        .map { $0.0.count + $0.1.count }
+        .eraseToAnyPublisher()
     }
 
     var xxdk: String {
diff --git a/Sources/Models/Attachment.swift b/Sources/Models/Attachment.swift
deleted file mode 100644
index 92eb3c09dcf4a7846bf5e265ec5a5080342a69b1..0000000000000000000000000000000000000000
--- a/Sources/Models/Attachment.swift
+++ /dev/null
@@ -1,49 +0,0 @@
-import Foundation
-
-public struct Attachment: Codable, Equatable, Hashable {
-
-    public enum Extension: Int64, Codable, CaseIterable {
-        case image
-        case audio
-
-        public static func from(_ string: String) -> Extension? {
-            self.allCases.first{ $0.written == string }
-        }
-
-        public var written: String {
-            switch self {
-            case .image:
-                return "jpeg"
-            case .audio:
-                return "m4a"
-            }
-        }
-
-        public var writtenExtended: String {
-            switch self {
-            case .image:
-                return "image"
-            case .audio:
-                return "voice message"
-            }
-        }
-    }
-
-    public let data: Data?
-    public let name: String
-    public var transferId: Data?
-    public let _extension: Extension
-    public var progress: Float = 0.0
-
-    public init(
-        name: String,
-        data: Data? = nil,
-        transferId: Data? = nil,
-        _extension: Extension
-    ) {
-        self.data = data
-        self.name = name
-        self._extension = _extension
-        self.transferId = transferId
-    }
-}
diff --git a/Sources/Models/Contact.swift b/Sources/Models/Contact.swift
deleted file mode 100644
index 1959743eb66d6888db0d7c65e07f5cfd0e11826a..0000000000000000000000000000000000000000
--- a/Sources/Models/Contact.swift
+++ /dev/null
@@ -1,123 +0,0 @@
-import UIKit
-import DifferenceKit
-
-public protocol IndexableItem {
-    var indexedOn: NSString { get }
-}
-
-public class IndexedListCollator<Item: IndexableItem> {
-    private final class CollationWrapper: NSObject {
-        let value: Any
-        @objc let indexedOn: NSString
-
-        init(value: Any, indexedOn: NSString) {
-            self.value = value
-            self.indexedOn = indexedOn
-        }
-
-        func unwrappedValue<UnwrappedType>() -> UnwrappedType {
-            return value as! UnwrappedType
-        }
-    }
-
-    public init() {}
-
-    public func sectioned(items: [Item]) -> (sections: [[Item]], collation: UILocalizedIndexedCollation) {
-        let collation = UILocalizedIndexedCollation.current()
-        let selector = #selector(getter: CollationWrapper.indexedOn)
-
-        let wrappedItems = items.map { item in
-            CollationWrapper(value: item, indexedOn: item.indexedOn)
-        }
-
-        let sortedObjects = collation.sortedArray(from: wrappedItems, collationStringSelector: selector) as! [CollationWrapper]
-
-        var sections = collation.sectionIndexTitles.map { _ in [Item]() }
-        sortedObjects.forEach { item in
-            let sectionNumber = collation.section(for: item, collationStringSelector: selector)
-            sections[sectionNumber].append(item.unwrappedValue())
-        }
-
-        return (sections: sections.filter { !$0.isEmpty }, collation: collation)
-    }
-}
-
-public struct Contact: Codable, Hashable, Equatable {
-    public enum Request {
-        case all
-        case failed
-        case friends
-        case received
-        case requested
-        case isRecent
-        case verificationInProgress
-        case withUserId(Data)
-        case withUserIds([Data])
-        case withUsername(String)
-    }
-
-    public enum Status: Int64, Codable {
-        case friend
-        case stranger
-        case verified
-        case verificationFailed
-        case verificationInProgress
-        case requested
-        case requesting
-        case requestFailed
-        case confirming
-        case confirmationFailed
-        case hidden
-    }
-
-    public var id: Int64?
-    public var photo: Data?
-    public let userId: Data
-    public var email: String?
-    public var phone: String?
-    public var status: Status
-    public var marshaled: Data
-    public var createdAt: Date
-    public let username: String
-    public var nickname: String?
-    public var isRecent: Bool
-
-    public init(
-        photo: Data?,
-        userId: Data,
-        email: String?,
-        phone: String?,
-        status: Status,
-        marshaled: Data,
-        username: String,
-        nickname: String?,
-        createdAt: Date,
-        isRecent: Bool
-    ) {
-        self.email = email
-        self.phone = phone
-        self.photo = photo
-        self.status = status
-        self.userId = userId
-        self.username = username
-        self.nickname = nickname
-        self.marshaled = marshaled
-        self.createdAt = createdAt
-        self.isRecent = isRecent
-    }
-
-    public var differenceIdentifier: Data { userId }
-
-    public static var databaseTableName: String { "contacts" }
-}
-
-extension Contact: Differentiable {}
-extension Contact: IndexableItem {
-    public var indexedOn: NSString {
-        guard let nickname = nickname else {
-            return "\(username.first!)" as NSString
-        }
-
-        return "\(nickname.first!)" as NSString
-    }
-}
diff --git a/Sources/Models/FileTransfer.swift b/Sources/Models/FileTransfer.swift
deleted file mode 100644
index 79fac5d1b0db78480c766a546b695ad426c8ae68..0000000000000000000000000000000000000000
--- a/Sources/Models/FileTransfer.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-import Foundation
-
-public struct FileTransfer {
-    public enum Request {
-        case withTID(Data)
-        case withContactId(Data)
-    }
-
-    public var tid: Data
-    public var id: Int64?
-    public var contact: Data
-    public var fileName: String
-    public var fileType: String
-    public var isIncoming: Bool
-
-    public static var databaseTableName: String { "transfers" }
-
-    public init(
-        id: Int64? = nil,
-        tid: Data,
-        contact: Data,
-        fileName: String,
-        fileType: String,
-        isIncoming: Bool
-    ) {
-        self.id = id
-        self.tid = tid
-        self.contact = contact
-        self.fileName = fileName
-        self.fileType = fileType
-        self.isIncoming = isIncoming
-    }
-}
-
-extension FileTransfer: Codable {}
-extension FileTransfer: Hashable {}
-extension FileTransfer: Equatable {}
diff --git a/Sources/Models/Group.swift b/Sources/Models/Group.swift
deleted file mode 100644
index feda834cb266a933e2470e3d6ab5910b22d4588b..0000000000000000000000000000000000000000
--- a/Sources/Models/Group.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-import Foundation
-import KeychainAccess
-
-public struct Group: Codable, Equatable, Hashable {
-    public enum Status: Int64, Codable {
-        case hidden
-        case pending
-        case deleting
-        case participating
-    }
-
-    public enum Request {
-        case pending
-        case accepted
-        case withGroupId(Data)
-    }
-
-    public var id: Int64?
-    public var name: String
-    public var leader: Data
-    public var groupId: Data
-    public var status: Status
-    public var serialize: Data
-    public var createdAt: Date
-    public static var databaseTableName: String { "groups" }
-
-    public init(
-        leader: Data,
-        name: String,
-        groupId: Data,
-        status: Status,
-        createdAt: Date,
-        serialize: Data
-    ) {
-        self.name = name
-        self.leader = leader
-        self.status = status
-        self.groupId = groupId
-        self.createdAt = createdAt
-        self.serialize = serialize
-    }
-}
diff --git a/Sources/Models/GroupChatInfo.swift b/Sources/Models/GroupChatInfo.swift
deleted file mode 100644
index 9b39ff6dbbd1cbad8202bd8117adfb9fc606b2c3..0000000000000000000000000000000000000000
--- a/Sources/Models/GroupChatInfo.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-import Foundation
-
-public struct GroupChatInfo: Codable, Equatable, Hashable {
-    public enum Request {
-        case accepted
-        case fromGroup(Data)
-    }
-
-    public var group: Group
-    public var members: [GroupMember]
-    public var lastMessage: GroupMessage?
-
-    public init(
-        group: Group,
-        members: [GroupMember],
-        lastMessage: GroupMessage? = nil
-    ) {
-        self.group = group
-        self.members = members
-        self.lastMessage = lastMessage
-    }
-}
diff --git a/Sources/Models/GroupMember.swift b/Sources/Models/GroupMember.swift
deleted file mode 100644
index 25c619f3280d9aa2891ab7b205e6639884783840..0000000000000000000000000000000000000000
--- a/Sources/Models/GroupMember.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-import Foundation
-
-public struct GroupMember {
-    public enum Request {
-        case all
-        case strangers
-        case fromGroup(Data)
-        case withUserId(Data)
-    }
-
-    public enum Status: Int64, Codable {
-        case usernameSet
-        case pendingUsername
-    }
-
-    public var id: Int64?
-    public var userId: Data
-    public var groupId: Data
-    public var status: Status
-    public var username: String
-    public var photo: Data?
-
-    public init(
-        id: Int64? = nil,
-        userId: Data,
-        groupId: Data,
-        status: Status,
-        username: String,
-        photo: Data? = nil
-    ) {
-        self.id = id
-        self.userId = userId
-        self.groupId = groupId
-        self.username = username
-        self.status = status
-        self.photo = photo
-    }
-}
-
-extension GroupMember: Codable {}
-extension GroupMember: Hashable {}
-extension GroupMember: Equatable {}
diff --git a/Sources/Models/GroupMessage.swift b/Sources/Models/GroupMessage.swift
deleted file mode 100644
index ffdb6df473f279bb68bc9e418963a4c5b2f79405..0000000000000000000000000000000000000000
--- a/Sources/Models/GroupMessage.swift
+++ /dev/null
@@ -1,56 +0,0 @@
-import Foundation
-
-public struct GroupMessage: Codable, Equatable, Hashable {
-    public enum Request {
-        case withUniqueId(Data)
-        case id(Int64)
-        case sending
-        case fromGroup(Data)
-        case unreadsFromGroup(Data)
-    }
-
-    public static var databaseTableName: String { "groupMessages" }
-
-    public enum Status: Int64, Codable {
-        case sent
-        case read
-        case failed
-        case sending
-        case received
-    }
-
-    public var id: Int64?
-    public var uniqueId: Data?
-    public var groupId: Data
-    public var sender: Data
-    public var roundId: Int64?
-    public var payload: Payload
-    public var status: Status
-    public var roundURL: String?
-    public var unread: Bool
-    public var timestamp: Int
-
-    public init(
-        id: Int64? = nil,
-        sender: Data,
-        groupId: Data,
-        payload: Payload,
-        unread: Bool,
-        timestamp: Int = 0,
-        uniqueId: Data?,
-        status: Status,
-        roundId: Int64? = nil,
-        roundURL: String? = nil
-    ) {
-        self.id = id
-        self.sender = sender
-        self.groupId = groupId
-        self.payload = payload
-        self.unread = unread
-        self.timestamp = timestamp
-        self.uniqueId = uniqueId
-        self.status = status
-        self.roundId = roundId
-        self.roundURL = roundURL
-    }
-}
diff --git a/Sources/Models/Message.swift b/Sources/Models/Message.swift
deleted file mode 100644
index fec18587bfb4f9b185ceb986af6ca5329a31e451..0000000000000000000000000000000000000000
--- a/Sources/Models/Message.swift
+++ /dev/null
@@ -1,70 +0,0 @@
-import Foundation
-import DifferenceKit
-
-public struct Message: Codable, Equatable, Hashable {
-    public enum Request {
-        case sending
-        case withUniqueId(Data)
-        case withId(Int64)
-        case sendingAttachment
-        case withContact(Data)
-        case unreadsFromContactId(Data)
-        case latestOnesFromContactIds([Data])
-    }
-
-    public enum Status: Int64, Codable {
-        case read
-        case sent
-        case sending
-        case sendingAttachment
-        case receivingAttachment
-        case received
-        case failedToSend
-        case timedOut
-    }
-
-    public var id: Int64?
-    public var unread: Bool
-    public let sender: Data
-    public var roundURL: String?
-    public var report: Data?
-    public var status: Status
-    public let receiver: Data
-    public var timestamp: Int
-    public var uniqueId: Data?
-    public var payload: Payload
-    public static var databaseTableName: String { "messages" }
-
-    public init (
-        sender: Data,
-        receiver: Data,
-        payload: Payload,
-        unread: Bool,
-        timestamp: Int,
-        uniqueId: Data?,
-        status: Status,
-        roundURL: String? = nil
-    ) {
-        self.sender = sender
-        self.unread = unread
-        self.status = status
-        self.payload = payload
-        self.receiver = receiver
-        self.uniqueId = uniqueId
-        self.timestamp = timestamp
-        self.roundURL = roundURL
-    }
-}
-
-public extension Message.Status {
-    var canReply: Bool {
-        switch self {
-        case .sent, .received, .read:
-            return true
-        default:
-            return false
-        }
-    }
-}
-
-extension Message: Differentiable {}
diff --git a/Sources/Models/Payload.swift b/Sources/Models/Payload.swift
index 4fda3b78993e049b6ef7d6986411fac1ba161f34..8e6f519b5ca09531b97961d1616a63477325d002 100644
--- a/Sources/Models/Payload.swift
+++ b/Sources/Models/Payload.swift
@@ -3,12 +3,10 @@ import Foundation
 public struct Payload: Codable, Equatable, Hashable {
     public var text: String
     public var reply: Reply?
-    public var attachment: Attachment?
 
-    public init(text: String, reply: Reply?, attachment: Attachment?) {
+    public init(text: String, reply: Reply?) {
         self.text = text
         self.reply = reply
-        self.attachment = attachment
     }
 
     public init(with marshaled: Data) throws {
@@ -23,7 +21,7 @@ public struct Payload: Codable, Equatable, Hashable {
             )
         }
 
-        self.init(text: proto.text, reply: reply, attachment: nil)
+        self.init(text: proto.text, reply: reply)
     }
 
     public func asData() -> Data {
diff --git a/Sources/Models/SingleChatInfo.swift b/Sources/Models/SingleChatInfo.swift
deleted file mode 100644
index 21c7f7d28bb4fb98b32c4239101cc8308b4be6ae..0000000000000000000000000000000000000000
--- a/Sources/Models/SingleChatInfo.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-import Foundation
-
-public struct SingleChatInfo: Codable, Equatable, Hashable {
-    public enum Request {
-        case all
-    }
-
-    public var contact: Contact
-    public var lastMessage: Message?
-
-    public init(
-        contact: Contact,
-        lastMessage: Message?
-    ) {
-        self.contact = contact
-        self.lastMessage = lastMessage
-    }
-}
diff --git a/Sources/PushFeature/PushHandler.swift b/Sources/PushFeature/PushHandler.swift
index bb0b19cd27060c2bf475ba948acf0333debc388e..f750c575899229ba5f6212261197087755dc8442 100644
--- a/Sources/PushFeature/PushHandler.swift
+++ b/Sources/PushFeature/PushHandler.swift
@@ -1,7 +1,7 @@
 import UIKit
 import Models
 import Defaults
-import Database
+import XXModels
 import Integration
 import DependencyInjection
 
@@ -103,15 +103,19 @@ public final class PushHandler: PushHandling {
             return
         }
 
-        let dbManager = GRDBDatabaseManager()
-        try? dbManager.setup()
+        let dbPath = FileManager.default
+            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
+            .appendingPathComponent("xxm_database")
+            .appendingPathExtension("sqlite").path
 
         let tuples: [(String, Push)] = pushes.compactMap {
-            guard let userId = $0.source, let contact: Contact = try? dbManager.fetch(.withUserId(userId)).first else {
+            guard let userId = $0.source,
+                  let dbManager = try? Database.onDisk(path: dbPath),
+                  let contact = try? dbManager.fetchContacts(.init(id: [userId])).first else {
                 return ($0.type.unknownSenderContent!, $0)
             }
 
-            let name = contact.nickname ?? contact.username
+            let name = (contact.nickname ?? contact.username) ?? ""
             return ($0.type.knownSenderContent(name)!, $0)
         }
 
diff --git a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
index c92d5cb26e82317750b6b039bad65c563c1f7eea..5edb77726fa6d43ba7d4e2a816192fcadd39303b 100644
--- a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
@@ -3,6 +3,7 @@ import UIKit
 import Models
 import Shared
 import Combine
+import XXModels
 import Countries
 import ToastFeature
 import DrawerFeature
@@ -110,10 +111,10 @@ extension RequestsReceivedController: UICollectionViewDelegate {
 
         switch request {
         case .group(let group):
-            guard group.status == .pending || group.status == .hidden else { return }
+            guard group.authStatus == .pending || group.authStatus == .hidden else { return }
             presentGroupRequestDrawer(forGroup: group)
         case .contact(let contact):
-            guard contact.status == .verified || contact.status == .hidden else { return }
+            guard contact.authStatus == .verified || contact.authStatus == .hidden else { return }
             presentSingleRequestDrawer(forContact: contact)
         }
     }
@@ -211,7 +212,7 @@ extension RequestsReceivedController {
 
         let drawerNickname = DrawerText(
             font: Fonts.Mulish.extraBold.font(size: 26.0),
-            text: contact.nickname ?? contact.username,
+            text: (contact.nickname ?? contact.username) ?? "",
             color: Asset.neutralDark.color,
             spacingAfter: 20
         )
@@ -392,7 +393,7 @@ extension RequestsReceivedController {
 
         let drawerUsername = DrawerText(
             font: Fonts.Mulish.extraBold.font(size: 26.0),
-            text: contact.username,
+            text: contact.username ?? "",
             color: Asset.neutralDark.color,
             spacingAfter: 25
         )
@@ -452,7 +453,7 @@ extension RequestsReceivedController {
         items.append(drawerNicknameTitle)
 
         let drawerNicknameInput = DrawerInput(
-            placeholder: contact.username,
+            placeholder: contact.username ?? "",
             validator: .init(
                 wrongIcon: .image(Asset.sharedError.image),
                 correctIcon: .image(Asset.sharedSuccess.image),
diff --git a/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift b/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift
index 4d28c6bf7fc7c8f482a8b69809b679ec9b447816..3c798cfc0243bffbbf54939c37d7ea7eb90c1294 100644
--- a/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift
+++ b/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import Models
+import XXModels
 import MenuFeature
 import Presentation
 import ContactFeature
@@ -11,8 +12,8 @@ public protocol RequestsCoordinating {
     func toSideMenu(from: UIViewController)
     func toContact(_: Contact, from: UIViewController)
     func toSingleChat(with: Contact, from: UIViewController)
+    func toGroupChat(with: GroupInfo, from: UIViewController)
     func toDrawer(_:  UIViewController, from: UIViewController)
-    func toGroupChat(with: GroupChatInfo, from: UIViewController)
     func toDrawerBottom(_:  UIViewController, from: UIViewController)
     func toNickname(from: UIViewController, prefilled: String, _: @escaping StringClosure)
 }
@@ -26,7 +27,7 @@ public struct RequestsCoordinator: RequestsCoordinating {
     var searchFactory: () -> UIViewController
     var contactFactory: (Contact) -> UIViewController
     var singleChatFactory: (Contact) -> UIViewController
-    var groupChatFactory: (GroupChatInfo) -> UIViewController
+    var groupChatFactory: (GroupInfo) -> UIViewController
     var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController
     var nicknameFactory: (String, @escaping StringClosure) -> UIViewController
 
@@ -34,7 +35,7 @@ public struct RequestsCoordinator: RequestsCoordinating {
         searchFactory: @escaping () -> UIViewController,
         contactFactory: @escaping (Contact) -> UIViewController,
         singleChatFactory: @escaping (Contact) -> UIViewController,
-        groupChatFactory: @escaping (GroupChatInfo) -> UIViewController,
+        groupChatFactory: @escaping (GroupInfo) -> UIViewController,
         sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController,
         nicknameFactory: @escaping (String, @escaping StringClosure) -> UIViewController
     ) {
@@ -57,7 +58,7 @@ public extension RequestsCoordinator {
     }
 
     func toGroupChat(
-        with info: GroupChatInfo,
+        with info: GroupInfo,
         from parent: UIViewController
     ) {
         let screen = groupChatFactory(info)
diff --git a/Sources/RequestsFeature/Models/Request.swift b/Sources/RequestsFeature/Models/Request.swift
index 2bf872c5a02815db8be3eb0adf92790328bf0082..595410d43257294a139a0f7b77a717992ed6ffcf 100644
--- a/Sources/RequestsFeature/Models/Request.swift
+++ b/Sources/RequestsFeature/Models/Request.swift
@@ -1,4 +1,5 @@
 import Models
+import XXModels
 import Foundation
 
 enum Section: Int {
@@ -15,16 +16,16 @@ enum Request: Hashable, Equatable {
         case .group:
             return .verified
         case .contact(let contact):
-            return contact.status.toRequestStatus()
+            return contact.authStatus.toRequestStatus()
         }
     }
 
     var id: Data {
         switch self {
         case .group(let group):
-            return group.groupId
+            return group.id
         case .contact(let contact):
-            return contact.userId
+            return contact.id
         }
     }
 }
@@ -40,7 +41,7 @@ enum RequestStatus {
     case failedToRequest
 }
 
-extension Contact.Status {
+extension Contact.AuthStatus {
     func toRequestStatus() -> RequestStatus {
         switch self {
         case .friend, .stranger:
diff --git a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
index f67882bb3fca1237ac3f138b549d262755e7fafe..9214c9ef3f19d337ae66f0f9a6c8368741ff1096 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
@@ -24,7 +24,8 @@ final class RequestsFailedViewModel {
     var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
 
     init() {
-        session.contacts(.failed)
+        session.dbManager.fetchContactsPublisher(.init(authStatus: [.requestFailed]))
+            .assertNoFailure()
             .map { data -> NSDiffableDataSourceSnapshot<Section, Request> in
                 var snapshot = NSDiffableDataSourceSnapshot<Section, Request>()
                 snapshot.appendSections([.appearing])
diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
index 35c2589da673e10cbc6dac35eec8771ead6a309b..7dd9e8c1d07c9b15d9829803be59c55c0955fd12 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
@@ -4,8 +4,9 @@ import Models
 import Shared
 import Combine
 import Defaults
-import DrawerFeature
+import XXModels
 import Integration
+import DrawerFeature
 import CombineSchedulers
 import DependencyInjection
 
@@ -47,51 +48,61 @@ final class RequestsReceivedViewModel {
     private let groupConfirmationSubject = PassthroughSubject<Group, Never>()
     private let contactConfirmationSubject = PassthroughSubject<Contact, Never>()
     private let itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, RequestReceived>, Never>(.init())
-    private var groupChats = [GroupChatInfo]()
 
     var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
 
     init() {
-        Publishers.CombineLatest4(
-            session.groups(.pending),
-            session.contacts(.all),
-            session.groupMembers(.all),
+        let groupsQuery = Group.Query(
+            authStatus: [
+                .hidden,
+                .pending
+            ])
+
+        let contactsQuery = Contact.Query(
+            authStatus: [
+                .friend,
+                .hidden,
+                .verified,
+                .verificationFailed,
+                .verificationInProgress
+            ])
+
+        let groupStream = session.dbManager.fetchGroupsPublisher(groupsQuery).assertNoFailure()
+        let contactsStream = session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure()
+
+        Publishers.CombineLatest3(
+            groupStream,
+            contactsStream,
             updateSubject.eraseToAnyPublisher()
         )
         .subscribe(on: DispatchQueue.main)
         .receive(on: DispatchQueue.global())
         .map { [unowned self] data -> NSDiffableDataSourceSnapshot<Section, RequestReceived> in
-            let contactRequests = data.1.filter {
-                $0.status == .hidden ||
-                $0.status == .verified ||
-                $0.status == .verificationFailed ||
-                $0.status == .verificationInProgress
-            }
-
             var snapshot = NSDiffableDataSourceSnapshot<Section, RequestReceived>()
             snapshot.appendSections([.appearing, .hidden])
 
-            let requests = data.0.map(Request.group) + contactRequests.map(Request.contact)
+            let contactsFilteringFriends = data.1.filter { $0.authStatus != .friend }
+            let requests = data.0.map(Request.group) + contactsFilteringFriends.map(Request.contact)
             let receivedRequests = requests.map { request -> RequestReceived in
                 switch request {
                 case let .group(group):
-                    var leaderTitle = ""
-
-                    if let leader = data.1.first(where: { $0.userId == group.leader }) {
-                        leaderTitle = leader.nickname ?? leader.username
-                    } else if let leader = data.2.first(where: { $0.userId == group.leader }) {
-                        leaderTitle = leader.username
+                    func leaderName() -> String {
+                        if let leader = data.1.first(where: { $0.id == group.leaderId }) {
+                            return (leader.nickname ?? leader.username) ?? "Leader is not a friend"
+                        } else {
+                            return "[Error retrieving leader]"
+                        }
                     }
 
                     return RequestReceived(
                         request: request,
-                        isHidden: group.status == .hidden,
-                        leader: leaderTitle
+                        isHidden: group.authStatus == .hidden,
+                        leader: leaderName()
                     )
                 case let .contact(contact):
                     return RequestReceived(
                         request: request,
-                        isHidden: contact.status == .hidden,
+                        isHidden: contact.authStatus == .hidden,
                         leader: nil
                     )
                 }
@@ -112,10 +123,6 @@ final class RequestsReceivedViewModel {
             receiveCompletion: { _ in },
             receiveValue: { [unowned self] in itemsSubject.send($0) }
         ).store(in: &cancellables)
-
-        session.groupChats(.accepted)
-            .sink { [unowned self] in groupChats = $0 }
-            .store(in: &cancellables)
     }
 
     func didToggleHiddenRequestsSwitcher() {
@@ -136,7 +143,10 @@ final class RequestsReceivedViewModel {
     }
 
     func didRequestHide(group: Group) {
-        session.hideRequestOf(group: group)
+        if var group = try? session.dbManager.fetchGroups(.init(id: [group.id])).first {
+            group.authStatus = .hidden
+            _ = try? session.dbManager.saveGroup(group)
+        }
     }
 
     func didRequestAccept(group: Group) {
@@ -157,52 +167,44 @@ final class RequestsReceivedViewModel {
         _ group: Group,
         _ completion: @escaping (Result<[DrawerTableCellModel], Error>) -> Void
     ) {
-        session.scanStrangers { [weak self] in
-            guard let self = self else { return }
-
-            Publishers.CombineLatest(
-                self.session.contacts(.all),
-                self.session.groupMembers(.fromGroup(group.groupId))
-            )
-            .sink { (allContacts, groupMembers) in
-
-                guard !groupMembers.map(\.status).contains(.pendingUsername) else {
-                    completion(.failure(NSError.create(""))) // Some members are still pending username lookup...
-                    return
-                }
-
-                // Now that all members are set with their usernames lets find our friends:
-                //
-                let contactsAlsoMembers = allContacts.filter { groupMembers.map(\.userId).contains($0.userId) }
-                let membersNonContacts = groupMembers.filter { !contactsAlsoMembers.map(\.userId).contains($0.userId) }
-
-                var models = [DrawerTableCellModel]()
-
-                contactsAlsoMembers.forEach {
-                    models.append(.init(
-                        title: $0.nickname ?? $0.username,
-                        image: $0.photo,
-                        isCreator: $0.userId == group.leader,
-                        isConnection: true
-                    ))
-                }
-
-                membersNonContacts.forEach {
-                    models.append(.init(
-                        title: $0.username,
-                        image: nil,
-                        isCreator: $0.userId == group.leader,
-                        isConnection: false
-                    ))
-                }
-
-                completion(.success(models))
-            }.store(in: &self.cancellables)
+        if let info = try? session.dbManager.fetchGroupInfos(.init(groupId: group.id)).first {
+            session.dbManager.fetchContactsPublisher(.init(id: Set(info.members.map(\.id))))
+                .assertNoFailure()
+                .sink { members in
+                    let withUsername = members
+                        .filter { $0.username != nil }
+                        .map {
+                            DrawerTableCellModel(
+                                id: $0.id,
+                                title: $0.nickname ?? $0.username!,
+                                image: $0.photo,
+                                isCreator: $0.id == group.leaderId,
+                                isConnection: $0.authStatus == .friend
+                            )
+                        }
+
+                    let withoutUsername = members
+                        .filter { $0.username == nil }
+                        .map {
+                            DrawerTableCellModel(
+                                id: $0.id,
+                                title: "Fetching username...",
+                                image: $0.photo,
+                                isCreator: $0.id == group.leaderId,
+                                isConnection: $0.authStatus == .friend
+                            )
+                        }
+
+                    completion(.success(withUsername + withoutUsername))
+                }.store(in: &cancellables)
         }
     }
 
     func didRequestHide(contact: Contact) {
-        session.hideRequestOf(contact: contact)
+        if var contact = try? session.dbManager.fetchContacts(.init(id: [contact.id])).first {
+            contact.authStatus = .hidden
+            _ = try? session.dbManager.saveContact(contact)
+        }
     }
 
     func didRequestAccept(contact: Contact, nickname: String? = nil) {
@@ -222,8 +224,11 @@ final class RequestsReceivedViewModel {
         }
     }
 
-    func groupChatWith(group: Group) -> GroupChatInfo {
-        guard let info = groupChats.first(where: { $0.group.groupId == group.groupId }) else { fatalError() }
+    func groupChatWith(group: Group) -> GroupInfo {
+        guard let info = try? session.dbManager.fetchGroupInfos(.init(groupId: group.id)).first else {
+            fatalError()
+        }
+
         return info
     }
 }
diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
index ebead6be165ce126a66b729321eac1fe8135fbbb..3789428ffa0a9539e35b3344fdd820ee896d03a2 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
@@ -32,7 +32,8 @@ final class RequestsSentViewModel {
     var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
 
     init() {
-        session.contacts(.requested)
+        session.dbManager.fetchContactsPublisher(.init(authStatus: [.requested]))
+            .assertNoFailure()
             .removeDuplicates()
             .map { data -> NSDiffableDataSourceSnapshot<Section, RequestSent> in
                 var snapshot = NSDiffableDataSourceSnapshot<Section, RequestSent>()
diff --git a/Sources/RequestsFeature/Views/RequestCell.swift b/Sources/RequestsFeature/Views/RequestCell.swift
index 5a6bd6db7f9845d54745521bc9f49fd2c8dc753e..e0af9ec6017ef1241ed0831ee0dbad71af7dc3d4 100644
--- a/Sources/RequestsFeature/Views/RequestCell.swift
+++ b/Sources/RequestsFeature/Views/RequestCell.swift
@@ -89,7 +89,7 @@ final class RequestCell: UICollectionViewCell {
         }
 
         setupContact(
-            title: contact.nickname ?? contact.username,
+            title: (contact.nickname ?? contact.username) ?? "",
             photo: contact.photo,
             phone: phone,
             email: contact.email,
@@ -128,7 +128,7 @@ final class RequestCell: UICollectionViewCell {
         }
 
         setupContact(
-            title: contact.nickname ?? contact.username,
+            title: (contact.nickname ?? contact.username) ?? "",
             photo: contact.photo,
             phone: phone,
             email: contact.email,
@@ -165,7 +165,7 @@ final class RequestCell: UICollectionViewCell {
             }
 
             setupContact(
-                title: contact.nickname ?? contact.username,
+                title: (contact.nickname ?? contact.username) ?? "",
                 photo: contact.photo,
                 phone: phone,
                 email: contact.email,
diff --git a/Sources/RestoreFeature/Service/RestoreService.swift b/Sources/RestoreFeature/Service/RestoreService.swift
deleted file mode 100644
index 9bd5b80e169d75d89806e716116b4781a080f691..0000000000000000000000000000000000000000
--- a/Sources/RestoreFeature/Service/RestoreService.swift
+++ /dev/null
@@ -1,38 +0,0 @@
-//import UIKit
-//import Models
-//import Combine
-//
-//import DependencyInjection
-//
-//public struct RestoreService: RestoreServiceType {
-//
-//
-//
-//    @Dependency private var coordinator: RestoreCoordinating
-//
-//    public var inProgress: AnyPublisher<Void, Never> { inProgressSubject.eraseToAnyPublisher() }
-//    public var settings: AnyPublisher<RestoreSettings, Never> { settingsSubject.eraseToAnyPublisher() }
-//
-//    private let inProgressSubject = PassthroughSubject<Void, Never>()
-//    private let settingsSubject = PassthroughSubject<RestoreSettings, Never>()
-//
-//    private var cancellables = Set<AnyCancellable>()
-//
-//    public init() {}
-//
-//    public func authorize(service: CloudService, from controller: UIViewController) {
-//        }
-//    }
-//
-//    public func download(
-//        from settings: RestoreSettings,
-//        progress: @escaping RestoreProgress,
-//        whenFinished: @escaping RestoreDownloadFinished
-//    ) {
-//        drive.downloadBackup(
-//            settings.backup!.id,
-//            progressCallback: progress,
-//            whenFinished
-//        )
-//    }
-//}
diff --git a/Sources/ScanFeature/Coordinator/ScanCoordinator.swift b/Sources/ScanFeature/Coordinator/ScanCoordinator.swift
index 6a36db1801d9874f687a3eef13dabf657125f943..98e605775ccdf54b5ffda4b0a6ad0183a5c15fe1 100644
--- a/Sources/ScanFeature/Coordinator/ScanCoordinator.swift
+++ b/Sources/ScanFeature/Coordinator/ScanCoordinator.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Models
+import XXModels
 import MenuFeature
 import Presentation
 import ContactFeature
diff --git a/Sources/ScanFeature/ViewModels/ScanViewModel.swift b/Sources/ScanFeature/ViewModels/ScanViewModel.swift
index c3f51841e1726407c69f47de05f15570f01c438f..b940226d75390dc33a79118d44ca74ca4cbe3240 100644
--- a/Sources/ScanFeature/ViewModels/ScanViewModel.swift
+++ b/Sources/ScanFeature/ViewModels/ScanViewModel.swift
@@ -1,6 +1,7 @@
 import Shared
 import Models
 import Combine
+import XXModels
 import Foundation
 import Integration
 import CombineSchedulers
@@ -53,10 +54,12 @@ final class ScanViewModel {
                     return
                 }
 
-                if let previouslyAdded = self.session.find(by: usernameAndId.0) {
+
+
+                if let previouslyAdded = try? self.session.dbManager.fetchContacts(.init(id: [usernameAndId.1])).first {
                     var error = ScanError.unknown(Localized.Scan.Error.general)
 
-                    switch previouslyAdded.status {
+                    switch previouslyAdded.authStatus {
                     case .friend:
                         error = .alreadyFriends(usernameAndId.0)
                     case .requested, .verified:
@@ -70,16 +73,16 @@ final class ScanViewModel {
                 }
 
                 let contact = Contact(
-                    photo: nil,
-                    userId: usernameAndId.1,
-                    email: try? self.session.extract(fact: .email, from: data),
-                    phone: try? self.session.extract(fact: .phone, from: data),
-                    status: .stranger,
+                    id: usernameAndId.1,
                     marshaled: data,
                     username: usernameAndId.0,
+                    email: try? self.session.extract(fact: .email, from: data),
+                    phone: try? self.session.extract(fact: .phone, from: data),
                     nickname: nil,
-                    createdAt: Date(),
-                    isRecent: false
+                    photo: nil,
+                    authStatus: .stranger,
+                    isRecent: false,
+                    createdAt: Date()
                 )
 
                 self.succeed(with: contact)
diff --git a/Sources/SearchFeature/Controllers/SearchController.swift b/Sources/SearchFeature/Controllers/SearchController.swift
index e192abf4d0005e266d2e3f609067dd52278cb16f..795c60009cb827d2aacca5d3e27eb89e7114b26b 100644
--- a/Sources/SearchFeature/Controllers/SearchController.swift
+++ b/Sources/SearchFeature/Controllers/SearchController.swift
@@ -2,13 +2,14 @@ import HUD
 import Theme
 import UIKit
 import Shared
-import Combine
-import DependencyInjection
-import ScrollViewController
-import DrawerFeature
 import Models
+import Combine
 import Defaults
+import XXModels
 import Countries
+import DrawerFeature
+import DependencyInjection
+import ScrollViewController
 
 public final class SearchController: UIViewController {
     @KeyObject(.email, defaultValue: nil) var email: String?
@@ -233,7 +234,7 @@ public final class SearchController: UIViewController {
     public func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
         let contact = viewModel.itemsRelay.value[indexPath.row]
 
-        guard contact.status == .stranger else {
+        guard contact.authStatus == .stranger else {
             coordinator.toContact(contact, from: self)
             return
         }
@@ -257,7 +258,7 @@ extension SearchController {
             spacingAfter: 20
         )
 
-        var subtitleFragment = "Share your information with #\(contact.username)"
+        var subtitleFragment = "Share your information with #\(contact.username ?? "")"
 
         if let email = contact.email {
             subtitleFragment.append(contentsOf: " (\(email))#")
@@ -440,7 +441,7 @@ extension SearchController {
         ])
 
         let drawerNicknameInput = DrawerInput(
-            placeholder: contact.username,
+            placeholder: contact.username!,
             validator: .init(
                 wrongIcon: .image(Asset.sharedError.image),
                 correctIcon: .image(Asset.sharedSuccess.image),
@@ -487,7 +488,7 @@ extension SearchController {
                 guard allowsSave else { return }
 
                 drawer.dismiss(animated: true) {
-                    self.viewModel.didSet(nickname: nickname ?? contact.username, for: contact)
+                    self.viewModel.didSet(nickname: nickname ?? contact.username!, for: contact)
                 }
             }
             .store(in: &drawerCancellables)
diff --git a/Sources/SearchFeature/Controllers/SearchTableController.swift b/Sources/SearchFeature/Controllers/SearchTableController.swift
index 625ab603defabb7d9c86b8bead13119198091e2c..a18b5856fe98c454c1a54b3ee4ca3c70d25d49dd 100644
--- a/Sources/SearchFeature/Controllers/SearchTableController.swift
+++ b/Sources/SearchFeature/Controllers/SearchTableController.swift
@@ -1,6 +1,7 @@
 import UIKit
-import Combine
 import Models
+import Combine
+import XXModels
 
 final class SearchTableController: UITableViewController {
     // MARK: Properties
@@ -49,7 +50,7 @@ final class SearchTableController: UITableViewController {
         let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: SearchCell.self)
         cell.title.text = dataSource[indexPath.row].username
         cell.subtitle.text = dataSource[indexPath.row].username
-        cell.avatar.setupProfile(title: dataSource[indexPath.row].username, image: nil, size: .large)
+        cell.avatar.setupProfile(title: dataSource[indexPath.row].username!, image: nil, size: .large)
         return cell
     }
 
diff --git a/Sources/SearchFeature/Coordinator/SearchCoordinator.swift b/Sources/SearchFeature/Coordinator/SearchCoordinator.swift
index c9a831c2c6be45b36e23d7329ad26b282f9b539e..2f66a6228fd96bb156266a80c16a9ac5ba53c5d9 100644
--- a/Sources/SearchFeature/Coordinator/SearchCoordinator.swift
+++ b/Sources/SearchFeature/Coordinator/SearchCoordinator.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Models
+import XXModels
 import Countries
 import Presentation
 import ScrollViewController
diff --git a/Sources/SearchFeature/ViewModels/SearchViewModel.swift b/Sources/SearchFeature/ViewModels/SearchViewModel.swift
index 7702558f24ae08d75c701ef4203a52fe1607574f..7b52fb9762f47cfab9d17f2fd02383560be228bd 100644
--- a/Sources/SearchFeature/ViewModels/SearchViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchViewModel.swift
@@ -3,6 +3,7 @@ import UIKit
 import Models
 import Combine
 import Defaults
+import XXModels
 import Countries
 import Foundation
 import Integration
@@ -167,7 +168,7 @@ final class SearchViewModel {
 
         backgroundScheduler.schedule { [weak self] in
             guard let self = self else { return }
-            self.session.update(contact)
+            _ = try? self.session.dbManager.saveContact(contact)
         }
     }
 
diff --git a/Sources/Shared/Extensions/FileManager.swift b/Sources/Shared/Extensions/FileManager.swift
index a6512bbbc2d19c199d5b58009d280faa6a2fe984..c8639b5f82820335b584fc77d9d4887d07b7cda4 100644
--- a/Sources/Shared/Extensions/FileManager.swift
+++ b/Sources/Shared/Extensions/FileManager.swift
@@ -36,12 +36,13 @@ public extension FileManager {
         root.appendingPathComponent("\(fileName)")
     }
 
-    static func store(data: Data, name: String, type: String) throws {
+    static func store(data: Data, name: String, type: String) throws -> URL {
         guard let url = Self.url(for: "\(name).\(type)") else {
             throw NSError.create("The file path could not be retrieved")
         }
 
         try data.write(to: url)
+        return url
     }
 
     static func delete(name: String, type: String) {
diff --git a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
index d7628f469871f2e1ed53e79524cddf88345b8ac8..eeb145af40bba398a5526019333b5de39606557e 100644
--- a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,349 +1,356 @@
 {
-  "object": {
-    "pins": [
-      {
-        "package": "abseil",
-        "repositoryURL": "https://github.com/firebase/abseil-cpp-SwiftPM.git",
-        "state": {
-          "branch": null,
-          "revision": "fffc3c2729be5747390ad02d5100291a0d9ad26a",
-          "version": "0.20200225.4"
-        }
-      },
-      {
-        "package": "Alamofire",
-        "repositoryURL": "https://github.com/Alamofire/Alamofire.git",
-        "state": {
-          "branch": null,
-          "revision": "f82c23a8a7ef8dc1a49a8bfc6a96883e79121864",
-          "version": "5.5.0"
-        }
-      },
-      {
-        "package": "AppAuth",
-        "repositoryURL": "https://github.com/openid/AppAuth-iOS.git",
-        "state": {
-          "branch": null,
-          "revision": "01131d68346c8ae552961c768d583c715fbe1410",
-          "version": "1.4.0"
-        }
-      },
-      {
-        "package": "BoringSSL-GRPC",
-        "repositoryURL": "https://github.com/firebase/boringssl-SwiftPM.git",
-        "state": {
-          "branch": null,
-          "revision": "734a8247442fde37df4364c21f6a0085b6a36728",
-          "version": "0.7.2"
-        }
-      },
-      {
-        "package": "ChatLayout",
-        "repositoryURL": "https://github.com/ekazaev/ChatLayout",
-        "state": {
-          "branch": null,
-          "revision": "d0edb6f3ae716a26842467c540a6bee909b80360",
-          "version": "1.1.14"
-        }
-      },
-      {
-        "package": "combine-schedulers",
-        "repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
-        "state": {
-          "branch": null,
-          "revision": "4cf088c29a20f52be0f2ca54992b492c54e0076b",
-          "version": "0.5.3"
-        }
-      },
-      {
-        "package": "CwlCatchException",
-        "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git",
-        "state": {
-          "branch": null,
-          "revision": "35f9e770f54ce62dd8526470f14c6e137cef3eea",
-          "version": "2.1.1"
-        }
-      },
-      {
-        "package": "CwlPreconditionTesting",
-        "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git",
-        "state": {
-          "branch": null,
-          "revision": "fb7a26374e8570ff5c68142e5c83406d6abae0d8",
-          "version": "2.0.2"
-        }
-      },
-      {
-        "package": "DifferenceKit",
-        "repositoryURL": "https://github.com/ra1028/DifferenceKit",
-        "state": {
-          "branch": null,
-          "revision": "62745d7780deef4a023a792a1f8f763ec7bf9705",
-          "version": "1.2.0"
-        }
-      },
-      {
-        "package": "FilesProvider",
-        "repositoryURL": "https://github.com/amosavian/FileProvider.git",
-        "state": {
-          "branch": null,
-          "revision": "abf68a62541a4193c8d106367ddb3648e8ab693f",
-          "version": "0.26.0"
-        }
-      },
-      {
-        "package": "Firebase",
-        "repositoryURL": "https://github.com/firebase/firebase-ios-sdk.git",
-        "state": {
-          "branch": null,
-          "revision": "08686f04881483d2bc098b2696e674c0ba135e47",
-          "version": "8.10.0"
-        }
-      },
-      {
-        "package": "GoogleAPIClientForREST",
-        "repositoryURL": "https://github.com/google/google-api-objectivec-client-for-rest",
-        "state": {
-          "branch": null,
-          "revision": "22e0bb02729d60db396e8b90d8189313cd86ba53",
-          "version": "1.6.0"
-        }
-      },
-      {
-        "package": "GoogleAppMeasurement",
-        "repositoryURL": "https://github.com/google/GoogleAppMeasurement.git",
-        "state": {
-          "branch": null,
-          "revision": "9b2f6aca5b4685c45f9f5481f19bee8e7982c538",
-          "version": "8.9.1"
-        }
-      },
-      {
-        "package": "GoogleDataTransport",
-        "repositoryURL": "https://github.com/google/GoogleDataTransport.git",
-        "state": {
-          "branch": null,
-          "revision": "15ccdfd25ac55b9239b82809531ff26605e7556e",
-          "version": "9.1.2"
-        }
-      },
-      {
-        "package": "GoogleSignIn",
-        "repositoryURL": "https://github.com/google/GoogleSignIn-iOS",
-        "state": {
-          "branch": null,
-          "revision": "60ca2bfd218ccb194a746a79b41d9d50eb7e3af0",
-          "version": "6.1.0"
-        }
-      },
-      {
-        "package": "GoogleUtilities",
-        "repositoryURL": "https://github.com/google/GoogleUtilities.git",
-        "state": {
-          "branch": null,
-          "revision": "b3bb0c5551fb3f80ca939829639ab5b093edd14f",
-          "version": "7.7.0"
-        }
-      },
-      {
-        "package": "GRDB",
-        "repositoryURL": "https://github.com/groue/GRDB.swift",
-        "state": {
-          "branch": null,
-          "revision": "32b2923e890df320906e64cbd0faca22a8bfda14",
-          "version": "5.12.0"
-        }
-      },
-      {
-        "package": "gRPC",
-        "repositoryURL": "https://github.com/firebase/grpc-SwiftPM.git",
-        "state": {
-          "branch": null,
-          "revision": "fb405dd2c7901485f7e158b24e3a0a47e4efd8b5",
-          "version": "1.28.4"
-        }
-      },
-      {
-        "package": "GTMSessionFetcher",
-        "repositoryURL": "https://github.com/google/gtm-session-fetcher.git",
-        "state": {
-          "branch": null,
-          "revision": "bc6a19702ac76ac4e488b68148710eb815f9bc56",
-          "version": "1.7.0"
-        }
-      },
-      {
-        "package": "GTMAppAuth",
-        "repositoryURL": "https://github.com/google/GTMAppAuth.git",
-        "state": {
-          "branch": null,
-          "revision": "40f4103fb52109032c05599a0c39ad43edbdf80a",
-          "version": "1.2.2"
-        }
-      },
-      {
-        "package": "KeychainAccess",
-        "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess",
-        "state": {
-          "branch": null,
-          "revision": "84e546727d66f1adc5439debad16270d0fdd04e7",
-          "version": "4.2.2"
-        }
-      },
-      {
-        "package": "leveldb",
-        "repositoryURL": "https://github.com/firebase/leveldb.git",
-        "state": {
-          "branch": null,
-          "revision": "0706abcc6b0bd9cedfbb015ba840e4a780b5159b",
-          "version": "1.22.2"
-        }
-      },
-      {
-        "package": "nanopb",
-        "repositoryURL": "https://github.com/firebase/nanopb.git",
-        "state": {
-          "branch": null,
-          "revision": "7ee9ef9f627d85cbe1b8c4f49a3ed26eed216c77",
-          "version": "2.30908.0"
-        }
-      },
-      {
-        "package": "Nimble",
-        "repositoryURL": "https://github.com/Quick/Nimble",
-        "state": {
-          "branch": null,
-          "revision": "c93f16c25af5770f0d3e6af27c9634640946b068",
-          "version": "9.2.1"
-        }
-      },
-      {
-        "package": "Promises",
-        "repositoryURL": "https://github.com/google/promises.git",
-        "state": {
-          "branch": null,
-          "revision": "611337c330350c9c1823ad6d671e7f936af5ee13",
-          "version": "2.0.0"
-        }
-      },
-      {
-        "package": "Quick",
-        "repositoryURL": "https://github.com/Quick/Quick",
-        "state": {
-          "branch": null,
-          "revision": "8cce6acd38f965f5baa3167b939f86500314022b",
-          "version": "3.1.2"
-        }
-      },
-      {
-        "package": "Retry",
-        "repositoryURL": "https://github.com/icanzilb/Retry.git",
-        "state": {
-          "branch": null,
-          "revision": "3beacada357968bcf9e5a2da520abfb374188afe",
-          "version": "0.6.3"
-        }
-      },
-      {
-        "package": "ScrollViewController",
-        "repositoryURL": "https://github.com/darrarski/ScrollViewController",
-        "state": {
-          "branch": null,
-          "revision": "9a52bb056504bb4766ddb5ac518097dd48736303",
-          "version": "1.2.0"
-        }
-      },
-      {
-        "package": "SnapKit",
-        "repositoryURL": "https://github.com/SnapKit/SnapKit",
-        "state": {
-          "branch": null,
-          "revision": "d458564516e5676af9c70b4f4b2a9178294f1bc6",
-          "version": "5.0.1"
-        }
-      },
-      {
-        "package": "swift-case-paths",
-        "repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
-        "state": {
-          "branch": null,
-          "revision": "241301b67d8551c26d8f09bd2c0e52cc49f18007",
-          "version": "0.8.0"
-        }
-      },
-      {
-        "package": "swift-collections",
-        "repositoryURL": "https://github.com/apple/swift-collections",
-        "state": {
-          "branch": null,
-          "revision": "48254824bb4248676bf7ce56014ff57b142b77eb",
-          "version": "1.0.2"
-        }
-      },
-      {
-        "package": "swift-composable-architecture",
-        "repositoryURL": "https://github.com/pointfreeco/swift-composable-architecture.git",
-        "state": {
-          "branch": null,
-          "revision": "313dd217dcd1d0478118ec5d15225fd473c1564a",
-          "version": "0.32.0"
-        }
-      },
-      {
-        "package": "swift-custom-dump",
-        "repositoryURL": "https://github.com/pointfreeco/swift-custom-dump",
-        "state": {
-          "branch": null,
-          "revision": "51698ece74ecf31959d3fa81733f0a5363ef1b4e",
-          "version": "0.3.0"
-        }
-      },
-      {
-        "package": "swift-identified-collections",
-        "repositoryURL": "https://github.com/pointfreeco/swift-identified-collections",
-        "state": {
-          "branch": null,
-          "revision": "680bf440178a78a627b1c2c64c0855f6523ad5b9",
-          "version": "0.3.2"
-        }
-      },
-      {
-        "package": "SwiftProtobuf",
-        "repositoryURL": "https://github.com/apple/swift-protobuf",
-        "state": {
-          "branch": null,
-          "revision": "7e2c5f3cbbeea68e004915e3a8961e20bd11d824",
-          "version": "1.18.0"
-        }
-      },
-      {
-        "package": "SwiftyBeaver",
-        "repositoryURL": "https://github.com/SwiftyBeaver/SwiftyBeaver.git",
-        "state": {
-          "branch": null,
-          "revision": "2c039501d6eeb4d4cd4aec4a8d884ad28862e044",
-          "version": "1.9.5"
-        }
-      },
-      {
-        "package": "SwiftyDropbox",
-        "repositoryURL": "https://github.com/dropbox/SwiftyDropbox.git",
-        "state": {
-          "branch": null,
-          "revision": "7af87d903be1cf0af0e76e0394d992943055894e",
-          "version": "8.2.1"
-        }
-      },
-      {
-        "package": "xctest-dynamic-overlay",
-        "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
-        "state": {
-          "branch": null,
-          "revision": "50a70a9d3583fe228ce672e8923010c8df2deddd",
-          "version": "0.2.1"
-        }
-      }
-    ]
-  },
-  "version": 1
+  "pins" : [
+    {
+      "identity" : "abseil-cpp-swiftpm",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git",
+      "state" : {
+        "revision" : "fffc3c2729be5747390ad02d5100291a0d9ad26a",
+        "version" : "0.20200225.4"
+      }
+    },
+    {
+      "identity" : "alamofire",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/Alamofire/Alamofire.git",
+      "state" : {
+        "revision" : "f82c23a8a7ef8dc1a49a8bfc6a96883e79121864",
+        "version" : "5.5.0"
+      }
+    },
+    {
+      "identity" : "appauth-ios",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/openid/AppAuth-iOS.git",
+      "state" : {
+        "revision" : "01131d68346c8ae552961c768d583c715fbe1410",
+        "version" : "1.4.0"
+      }
+    },
+    {
+      "identity" : "boringssl-swiftpm",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/firebase/boringssl-SwiftPM.git",
+      "state" : {
+        "revision" : "734a8247442fde37df4364c21f6a0085b6a36728",
+        "version" : "0.7.2"
+      }
+    },
+    {
+      "identity" : "chatlayout",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/ekazaev/ChatLayout",
+      "state" : {
+        "revision" : "d0edb6f3ae716a26842467c540a6bee909b80360",
+        "version" : "1.1.14"
+      }
+    },
+    {
+      "identity" : "client-ios-db",
+      "kind" : "remoteSourceControl",
+      "location" : "https://git.xx.network/elixxir/client-ios-db.git",
+      "state" : {
+        "revision" : "adf3c4b906870ecbd0d1d7208f0666939fd08665",
+        "version" : "1.0.5"
+      }
+    },
+    {
+      "identity" : "combine-schedulers",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/pointfreeco/combine-schedulers",
+      "state" : {
+        "revision" : "4cf088c29a20f52be0f2ca54992b492c54e0076b",
+        "version" : "0.5.3"
+      }
+    },
+    {
+      "identity" : "cwlcatchexception",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/mattgallagher/CwlCatchException.git",
+      "state" : {
+        "revision" : "35f9e770f54ce62dd8526470f14c6e137cef3eea",
+        "version" : "2.1.1"
+      }
+    },
+    {
+      "identity" : "cwlpreconditiontesting",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git",
+      "state" : {
+        "revision" : "fb7a26374e8570ff5c68142e5c83406d6abae0d8",
+        "version" : "2.0.2"
+      }
+    },
+    {
+      "identity" : "differencekit",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/ra1028/DifferenceKit",
+      "state" : {
+        "revision" : "62745d7780deef4a023a792a1f8f763ec7bf9705",
+        "version" : "1.2.0"
+      }
+    },
+    {
+      "identity" : "fileprovider",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/amosavian/FileProvider.git",
+      "state" : {
+        "revision" : "abf68a62541a4193c8d106367ddb3648e8ab693f",
+        "version" : "0.26.0"
+      }
+    },
+    {
+      "identity" : "firebase-ios-sdk",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/firebase/firebase-ios-sdk.git",
+      "state" : {
+        "revision" : "08686f04881483d2bc098b2696e674c0ba135e47",
+        "version" : "8.10.0"
+      }
+    },
+    {
+      "identity" : "google-api-objectivec-client-for-rest",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/google/google-api-objectivec-client-for-rest",
+      "state" : {
+        "revision" : "22e0bb02729d60db396e8b90d8189313cd86ba53",
+        "version" : "1.6.0"
+      }
+    },
+    {
+      "identity" : "googleappmeasurement",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/google/GoogleAppMeasurement.git",
+      "state" : {
+        "revision" : "9b2f6aca5b4685c45f9f5481f19bee8e7982c538",
+        "version" : "8.9.1"
+      }
+    },
+    {
+      "identity" : "googledatatransport",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/google/GoogleDataTransport.git",
+      "state" : {
+        "revision" : "15ccdfd25ac55b9239b82809531ff26605e7556e",
+        "version" : "9.1.2"
+      }
+    },
+    {
+      "identity" : "googlesignin-ios",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/google/GoogleSignIn-iOS",
+      "state" : {
+        "revision" : "60ca2bfd218ccb194a746a79b41d9d50eb7e3af0",
+        "version" : "6.1.0"
+      }
+    },
+    {
+      "identity" : "googleutilities",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/google/GoogleUtilities.git",
+      "state" : {
+        "revision" : "b3bb0c5551fb3f80ca939829639ab5b093edd14f",
+        "version" : "7.7.0"
+      }
+    },
+    {
+      "identity" : "grdb.swift",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/groue/GRDB.swift",
+      "state" : {
+        "revision" : "e02f2c8abacff2799ed14926edcbf6e76fb9f805",
+        "version" : "5.25.0"
+      }
+    },
+    {
+      "identity" : "grpc-swiftpm",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/firebase/grpc-SwiftPM.git",
+      "state" : {
+        "revision" : "fb405dd2c7901485f7e158b24e3a0a47e4efd8b5",
+        "version" : "1.28.4"
+      }
+    },
+    {
+      "identity" : "gtm-session-fetcher",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/google/gtm-session-fetcher.git",
+      "state" : {
+        "revision" : "bc6a19702ac76ac4e488b68148710eb815f9bc56",
+        "version" : "1.7.0"
+      }
+    },
+    {
+      "identity" : "gtmappauth",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/google/GTMAppAuth.git",
+      "state" : {
+        "revision" : "40f4103fb52109032c05599a0c39ad43edbdf80a",
+        "version" : "1.2.2"
+      }
+    },
+    {
+      "identity" : "keychainaccess",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/kishikawakatsumi/KeychainAccess",
+      "state" : {
+        "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
+        "version" : "4.2.2"
+      }
+    },
+    {
+      "identity" : "leveldb",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/firebase/leveldb.git",
+      "state" : {
+        "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b",
+        "version" : "1.22.2"
+      }
+    },
+    {
+      "identity" : "nanopb",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/firebase/nanopb.git",
+      "state" : {
+        "revision" : "7ee9ef9f627d85cbe1b8c4f49a3ed26eed216c77",
+        "version" : "2.30908.0"
+      }
+    },
+    {
+      "identity" : "nimble",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/Quick/Nimble",
+      "state" : {
+        "revision" : "c93f16c25af5770f0d3e6af27c9634640946b068",
+        "version" : "9.2.1"
+      }
+    },
+    {
+      "identity" : "promises",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/google/promises.git",
+      "state" : {
+        "revision" : "611337c330350c9c1823ad6d671e7f936af5ee13",
+        "version" : "2.0.0"
+      }
+    },
+    {
+      "identity" : "quick",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/Quick/Quick",
+      "state" : {
+        "revision" : "8cce6acd38f965f5baa3167b939f86500314022b",
+        "version" : "3.1.2"
+      }
+    },
+    {
+      "identity" : "retry",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/icanzilb/Retry.git",
+      "state" : {
+        "revision" : "3beacada357968bcf9e5a2da520abfb374188afe",
+        "version" : "0.6.3"
+      }
+    },
+    {
+      "identity" : "scrollviewcontroller",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/darrarski/ScrollViewController",
+      "state" : {
+        "revision" : "9a52bb056504bb4766ddb5ac518097dd48736303",
+        "version" : "1.2.0"
+      }
+    },
+    {
+      "identity" : "snapkit",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/SnapKit/SnapKit",
+      "state" : {
+        "revision" : "d458564516e5676af9c70b4f4b2a9178294f1bc6",
+        "version" : "5.0.1"
+      }
+    },
+    {
+      "identity" : "swift-case-paths",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/pointfreeco/swift-case-paths",
+      "state" : {
+        "revision" : "241301b67d8551c26d8f09bd2c0e52cc49f18007",
+        "version" : "0.8.0"
+      }
+    },
+    {
+      "identity" : "swift-collections",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-collections",
+      "state" : {
+        "revision" : "48254824bb4248676bf7ce56014ff57b142b77eb",
+        "version" : "1.0.2"
+      }
+    },
+    {
+      "identity" : "swift-composable-architecture",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/pointfreeco/swift-composable-architecture.git",
+      "state" : {
+        "revision" : "313dd217dcd1d0478118ec5d15225fd473c1564a",
+        "version" : "0.32.0"
+      }
+    },
+    {
+      "identity" : "swift-custom-dump",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/pointfreeco/swift-custom-dump",
+      "state" : {
+        "revision" : "51698ece74ecf31959d3fa81733f0a5363ef1b4e",
+        "version" : "0.3.0"
+      }
+    },
+    {
+      "identity" : "swift-identified-collections",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/pointfreeco/swift-identified-collections",
+      "state" : {
+        "revision" : "680bf440178a78a627b1c2c64c0855f6523ad5b9",
+        "version" : "0.3.2"
+      }
+    },
+    {
+      "identity" : "swift-protobuf",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-protobuf",
+      "state" : {
+        "revision" : "7e2c5f3cbbeea68e004915e3a8961e20bd11d824",
+        "version" : "1.18.0"
+      }
+    },
+    {
+      "identity" : "swiftybeaver",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/SwiftyBeaver/SwiftyBeaver.git",
+      "state" : {
+        "revision" : "2c039501d6eeb4d4cd4aec4a8d884ad28862e044",
+        "version" : "1.9.5"
+      }
+    },
+    {
+      "identity" : "swiftydropbox",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/dropbox/SwiftyDropbox.git",
+      "state" : {
+        "revision" : "7af87d903be1cf0af0e76e0394d992943055894e",
+        "version" : "8.2.1"
+      }
+    },
+    {
+      "identity" : "xctest-dynamic-overlay",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
+      "state" : {
+        "revision" : "50a70a9d3583fe228ce672e8923010c8df2deddd",
+        "version" : "0.2.1"
+      }
+    }
+  ],
+  "version" : 2
 }