diff --git a/Package.swift b/Package.swift
index 87d07c65d6a04fa2c353e2e1c914a0bc1db1453c..49a7cd827aee04a45a5e85ba0fe355a52bb1532b 100644
--- a/Package.swift
+++ b/Package.swift
@@ -466,6 +466,9 @@ let package = Package(
                 .product(name: "ChatLayout", package: "ChatLayout"),
                 .product(name: "DifferenceKit", package: "DifferenceKit"),
                 .product(name: "ScrollViewController", package: "ScrollViewController"),
+            ],
+            resources: [
+                .process("Resources"),
             ]
         ),
         .testTarget(
diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift
index 3d2a35d19c39a1b06a703a794e089e6adf196769..d2f7f95a8f78c2c7cee0264f9bf43fe7b802bd66 100644
--- a/Sources/ChatFeature/Controllers/SingleChatController.swift
+++ b/Sources/ChatFeature/Controllers/SingleChatController.swift
@@ -430,8 +430,9 @@ public final class SingleChatController: UIViewController {
                 drawer.dismiss(animated: true) { [weak self] in
                     guard let self = self else { return }
                     self.drawerCancellables.removeAll()
-                    self.viewModel.proceeedWithReport(screenshot: self.takeAppScreenshot())
-                    self.navigationController?.popViewController(animated: true)
+                    self.viewModel.proceeedWithReport(screenshot: self.takeAppScreenshot()) {
+                        self.navigationController?.popViewController(animated: true)
+                    }
                 }
             }.store(in: &drawerCancellables)
 
@@ -447,18 +448,11 @@ public final class SingleChatController: UIViewController {
     }
 
     func takeAppScreenshot() -> UIImage {
-        let foregroundWindowScene: UIWindowScene? = UIApplication.shared.connectedScenes
-            .filter { $0.activationState == .foregroundActive }
-            .compactMap { $0 as? UIWindowScene }
-            .first
-
         guard let foregroundWindowScene = foregroundWindowScene else {
             fatalError("[takeAppScreenshot]: Unable to get foreground window scene")
         }
 
-        guard let keyWindow = foregroundWindowScene.windows.first(where: \.isKeyWindow) else {
-            fatalError("[takeAppScreenshot]: Unable to get key window")
-        }
+        let keyWindow = getKeyWindow(foregroundWindowScene)
 
         let rendererFormat = UIGraphicsImageRendererFormat()
         rendererFormat.scale = foregroundWindowScene.screen.scale
@@ -602,11 +596,15 @@ extension SingleChatController: KeyboardListenerDelegate {
     }
 
     func keyboardWillChangeFrame(info: KeyboardInfo) {
-        let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
+        guard let scene = foregroundWindowScene else {
+            fatalError("[keyboardWillChangeFrame]: Couldn't get foregroundWindowScene")
+        }
+
+        let keyWindow = getKeyWindow(scene)
+        let keyboardFrame = keyWindow.convert(info.frameEnd, to: view)
 
         guard !currentInterfaceActions.options.contains(.changingFrameSize),
               collectionView.contentInsetAdjustmentBehavior != .never,
-              let keyboardFrame = keyWindow?.convert(info.frameEnd, to: view),
               collectionView.convert(collectionView.bounds, to: keyWindow).maxY > info.frameEnd.minY else { return }
 
         currentInterfaceActions.options.insert(.changingKeyboardFrame)
@@ -766,3 +764,16 @@ extension SingleChatController: QLPreviewControllerDelegate {
         fileURL = nil
     }
 }
+
+let foregroundWindowScene: UIWindowScene? = UIApplication.shared.connectedScenes
+    .filter { $0.activationState == .foregroundActive }
+    .compactMap { $0 as? UIWindowScene }
+    .first
+
+func getKeyWindow(_ scene: UIWindowScene) -> UIWindow {
+    guard let keyWindow = scene.windows.first(where: \.isKeyWindow) else {
+        fatalError("Unable to get key window")
+    }
+
+    return keyWindow
+}
diff --git a/Sources/ChatFeature/Resources/report_cert.crt b/Sources/ChatFeature/Resources/report_cert.crt
new file mode 100644
index 0000000000000000000000000000000000000000..be1d50ad2e61be90d6725bb98292ac46b25b440b
--- /dev/null
+++ b/Sources/ChatFeature/Resources/report_cert.crt
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+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
+WuHZNZ2W6k8sb2qp0vBbj6TXfhs=
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
index d4aa517ba54e33073824556891e46c8c7766f606..2a1e34930b4bcd9fc4a577346da743e37a1577b6 100644
--- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
@@ -7,6 +7,7 @@ import XXLogger
 import XXModels
 import Foundation
 import Integration
+import Defaults
 import Permissions
 import ToastFeature
 import DifferenceKit
@@ -23,12 +24,14 @@ enum SingleChatNavigationRoutes: Equatable {
     case webview(String)
 }
 
-final class SingleChatViewModel {
+final class SingleChatViewModel: NSObject {
     @Dependency private var logger: XXLogger
     @Dependency private var session: SessionType
     @Dependency private var permissions: PermissionHandling
     @Dependency private var toastController: ToastController
 
+    @KeyObject(.username, defaultValue: nil) var username: String?
+
     var contact: Contact { contactSubject.value }
     private var stagedReply: Reply?
     private var cancellables = Set<AnyCancellable>()
@@ -73,6 +76,7 @@ final class SingleChatViewModel {
 
     init(_ contact: Contact) {
         self.contactSubject = .init(contact)
+        super.init()
 
         updateRecentState(contact)
 
@@ -140,11 +144,11 @@ final class SingleChatViewModel {
         guard let id = message.id else { return }
         session.retryMessage(id)
     }
-   
+
     func didNavigateSomewhere() {
         navigationRoutes.send(.none)
     }
-    
+
     @discardableResult
     func didTest(permission: PermissionType) -> Bool {
         switch permission {
@@ -222,18 +226,6 @@ final class SingleChatViewModel {
         return (contactTitle, message.text)
     }
 
-    func proceeedWithReport(screenshot: UIImage) {
-        var contact = contact
-        contact.isBlocked = true
-        _ = try? session.dbManager.saveContact(contact)
-
-        let name = (contact.nickname ?? contact.username) ?? ""
-        toastController.enqueueToast(model: .init(
-            title: "Your report has been sent and \(name) is now blocked.",
-            leftImage: Asset.requestSentToaster.image
-        ))
-    }
-
     func showRoundFrom(_ roundURL: String?) {
         if let urlString = roundURL, !urlString.isEmpty {
             navigationRoutes.send(.webview(urlString))
@@ -261,3 +253,117 @@ final class SingleChatViewModel {
         sectionsRelay.value.count > 0 ? sectionsRelay.value[index].model : nil
     }
 }
+
+extension SingleChatViewModel {
+    struct Report: Encodable {
+        struct ReportUser: Encodable {
+            var userId: String
+            var username: String
+        }
+
+        var sender: ReportUser
+        var recipient: ReportUser
+        var type: String
+        var screenshot: String
+    }
+
+    private func blockContact() {
+        var contact = contact
+        contact.isBlocked = true
+        _ = try? session.dbManager.saveContact(contact)
+    }
+
+    private func makeReportRequest(with screenshot: UIImage) -> URLRequest {
+        let url = URL(string: "https://3.74.237.181:11420/report")
+
+        let report = Report(
+            sender: .init(
+                userId: contact.id.base64EncodedString(),
+                username: contact.username!
+            ),
+            recipient: .init(
+                userId: session.myId.base64EncodedString(),
+                username: username!
+            ), type: "dm",
+            screenshot: screenshot.jpegData(compressionQuality: 0.1)!.base64EncodedString())
+
+        var request = try! URLRequest(url: url!, method: .post)
+        request.httpBody = try! JSONEncoder().encode(report)
+        return request
+    }
+
+    private func enqueueBlockedToast() {
+        let name = (contact.nickname ?? contact.username) ?? ""
+        toastController.enqueueToast(model: .init(
+            title: "Your report has been sent and \(name) is now blocked.",
+            leftImage: Asset.requestSentToaster.image
+        ))
+    }
+
+    private func uploadReport(
+        _ request: URLRequest,
+        completion: @escaping (Result<Void, Error>) -> Void
+    ) {
+        URLSession(configuration: .default, delegate: self, delegateQueue: nil)
+            .dataTask(with: request) { data, response, error in
+            if let error = error as? NSError {
+                completion(.failure(error))
+                return
+            }
+
+            if let data = data {
+                completion(.success(()))
+            }
+        }.resume()
+    }
+
+    func proceeedWithReport(screenshot: UIImage, completion: @escaping () -> Void) {
+        hudRelay.send(.on)
+
+        uploadReport(makeReportRequest(with: screenshot)) { [weak self] in
+            guard let self = self else { return }
+
+            switch $0 {
+            case .success:
+                DispatchQueue.main.async {
+                    self.blockContact()
+                    self.enqueueBlockedToast()
+                    self.hudRelay.send(.none)
+                    completion()
+                }
+
+            case .failure(let error):
+                DispatchQueue.main.async {
+                    self.hudRelay.send(.error(.init(with: error)))
+                    completion()
+                }
+            }
+        }
+    }
+}
+
+extension SingleChatViewModel: URLSessionDelegate {
+    func urlSession(
+        _ session: URLSession,
+        didReceive challenge: URLAuthenticationChallenge,
+        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
+    ) {
+        let serverTrust = challenge.protectionSpace.serverTrust
+        let certificate = SecTrustGetCertificateAtIndex(serverTrust!, 0)
+
+        let policies = NSMutableArray()
+        policies.add(SecPolicyCreateSSL(true, challenge.protectionSpace.host as CFString))
+        SecTrustSetPolicies(serverTrust!, policies)
+
+        let remoteCertificateData: NSData = SecCertificateCopyData(certificate!)
+        let pathToCert = Bundle.module.path(forResource: "report_cert", ofType: "crt")
+        let localCertificate: NSData = NSData(contentsOfFile: pathToCert!)!
+
+        if (remoteCertificateData.isEqual(to: localCertificate as Data)) {
+            let credential: URLCredential = URLCredential(trust: serverTrust!)
+            completionHandler(.useCredential, credential)
+        } else {
+            completionHandler(.cancelAuthenticationChallenge, nil)
+        }
+    }
+}