diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift
index 3d3d40e30c3f9dfc5a347fd759b8b61013480657..a7eb985d0bd3826d2920cf0cf1bdfdbe6a9c78a2 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift
@@ -1,7 +1,13 @@
+import Foundation
 import XCTestDynamicOverlay
 import XXClient
 
 public struct MessengerVerifyContact {
+  public enum Error: Swift.Error, Equatable {
+    case notConnected
+    case notLoggedIn
+  }
+
   public var run: (Contact) throws -> Bool
 
   public func callAsFunction(_ contact: Contact) throws -> Bool {
@@ -12,8 +18,54 @@ public struct MessengerVerifyContact {
 extension MessengerVerifyContact {
   public static func live(_ env: MessengerEnvironment) -> MessengerVerifyContact {
     MessengerVerifyContact { contact in
-      // TODO:
-      return false
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      guard let ud = env.ud() else {
+        throw Error.notLoggedIn
+      }
+      let facts = try contact.getFacts()
+      let verifiedContact: Contact?
+      if facts.isEmpty {
+        var lookupResult: Result<Contact, NSError>!
+        let semaphore = DispatchSemaphore(value: 0)
+        _ = try env.lookupUD(
+          e2eId: e2e.getId(),
+          udContact: try ud.getContact(),
+          lookupId: try contact.getId(),
+          callback: .init { result in
+            lookupResult = result
+            semaphore.signal()
+          }
+        )
+        semaphore.wait()
+        verifiedContact = try lookupResult.get()
+      } else {
+        var searchResult: Result<[Contact], NSError>!
+        let semaphore = DispatchSemaphore(value: 0)
+        _ = try env.searchUD(
+          e2eId: e2e.getId(),
+          udContact: try ud.getContact(),
+          facts: facts,
+          singleRequestParamsJSON: env.getSingleUseParams(),
+          callback: .init { result in
+            searchResult = result
+            semaphore.signal()
+          }
+        )
+        semaphore.wait()
+        verifiedContact = try searchResult.get().first
+      }
+
+      guard let verifiedContact = verifiedContact else {
+        return false
+      }
+
+      return try e2e.verifyOwnership(
+        received: contact,
+        verified: verifiedContact,
+        e2eId: e2e.getId()
+      )
     }
   }
 }
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index 85e53d0b99599b70136dfb1a5c30144e4564613c..9c4837748fa35628fdfd87bdbcc0e746d745f77e 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -15,6 +15,7 @@ public struct MessengerEnvironment {
   public var isRegisteredWithUD: IsRegisteredWithUD
   public var loadCMix: LoadCMix
   public var login: Login
+  public var lookupUD: LookupUD
   public var ndfEnvironment: NDFEnvironment
   public var newCMix: NewCMix
   public var newOrLoadUd: NewOrLoadUd
@@ -50,6 +51,7 @@ extension MessengerEnvironment {
       isRegisteredWithUD: .live,
       loadCMix: .live,
       login: .live,
+      lookupUD: .live,
       ndfEnvironment: .mainnet,
       newCMix: .live,
       newOrLoadUd: .live,
@@ -80,6 +82,7 @@ extension MessengerEnvironment {
     isRegisteredWithUD: .unimplemented,
     loadCMix: .unimplemented,
     login: .unimplemented,
+    lookupUD: .unimplemented,
     ndfEnvironment: .unimplemented,
     newCMix: .unimplemented,
     newOrLoadUd: .unimplemented,