diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerLookupContacts.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerLookupContacts.swift
new file mode 100644
index 0000000000000000000000000000000000000000..77a667db6d9b0c15a6d4bf97067879fe8c48b791
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerLookupContacts.swift
@@ -0,0 +1,51 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerLookupContacts {
+  public enum Error: Swift.Error, Equatable {
+    case notConnected
+    case notLoggedIn
+  }
+
+  public var run: ([Data]) throws -> UdMultiLookupCallback.Result
+
+  public func callAsFunction(ids: [Data]) throws -> UdMultiLookupCallback.Result {
+    try run(ids)
+  }
+}
+
+extension MessengerLookupContacts {
+  public static func live(_ env: MessengerEnvironment) -> MessengerLookupContacts {
+    MessengerLookupContacts { ids in
+      guard let e2e = env.e2e() else {
+        throw Error.notConnected
+      }
+      guard let ud = env.ud() else {
+        throw Error.notLoggedIn
+      }
+      var callbackResult: UdMultiLookupCallback.Result!
+      let semaphore = DispatchSemaphore(value: 0)
+      _ = try env.multiLookupUD(
+        params: MultiLookupUD.Params(
+          e2eId: e2e.getId(),
+          udContact: try ud.getContact(),
+          lookupIds: ids,
+          singleRequestParams: env.getSingleUseParams()
+        ),
+        callback: UdMultiLookupCallback { result in
+          callbackResult = result
+          semaphore.signal()
+        }
+      )
+      semaphore.wait()
+      return callbackResult
+    }
+  }
+}
+
+extension MessengerLookupContacts {
+  public static let unimplemented = MessengerLookupContacts(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
index b4ff607ed6238450cdbf05aec918376c833060cb..f125c1ef3ae705a1dde5d8e9ff1dc4467e25044c 100644
--- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
+++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift
@@ -17,6 +17,7 @@ public struct MessengerEnvironment {
   public var login: Login
   public var lookupUD: LookupUD
   public var messageListeners: ListenersRegistry
+  public var multiLookupUD: MultiLookupUD
   public var ndfEnvironment: NDFEnvironment
   public var newCMix: NewCMix
   public var newOrLoadUd: NewOrLoadUd
@@ -54,6 +55,7 @@ extension MessengerEnvironment {
       login: .live,
       lookupUD: .live,
       messageListeners: .live(),
+      multiLookupUD: .live(),
       ndfEnvironment: .mainnet,
       newCMix: .live,
       newOrLoadUd: .live,
@@ -86,6 +88,7 @@ extension MessengerEnvironment {
     login: .unimplemented,
     lookupUD: .unimplemented,
     messageListeners: .unimplemented,
+    multiLookupUD: .unimplemented,
     ndfEnvironment: .unimplemented,
     newCMix: .unimplemented,
     newOrLoadUd: .unimplemented,
diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerLookupContactsTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerLookupContactsTests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9f9031103eefa21bb4e85434a6efa436b681b9c4
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerLookupContactsTests.swift
@@ -0,0 +1,93 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerLookupContactsTests: XCTestCase {
+  func testLookup() throws {
+    let contactIds = ["contact-id".data(using: .utf8)!]
+    let e2eId = 123
+    let udContact = Contact.unimplemented("ud-contact".data(using: .utf8)!)
+    let singleRequestParams = "single-request-params".data(using: .utf8)!
+    let contacts = [Contact.unimplemented("contact".data(using: .utf8)!)]
+
+    var didMultiLookupWithParams: [MultiLookupUD.Params] = []
+
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { e2eId }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getContact.run = { udContact }
+      return ud
+    }
+    env.getSingleUseParams.run = { singleRequestParams }
+    env.multiLookupUD.run = { params, callback in
+      didMultiLookupWithParams.append(params)
+      callback.handle(.init(
+        contacts: contacts,
+        failedIds: [],
+        errors: []
+      ))
+    }
+    let lookup: MessengerLookupContacts = .live(env)
+
+    let result = try lookup(ids: contactIds)
+
+    XCTAssertNoDifference(didMultiLookupWithParams, [.init(
+      e2eId: e2eId,
+      udContact: udContact,
+      lookupIds: contactIds,
+      singleRequestParams: singleRequestParams
+    )])
+    XCTAssertNoDifference(result, .init(contacts: contacts, failedIds: [], errors: []))
+  }
+
+  func testLookupWhenNotConnected() {
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = { nil }
+    let lookup: MessengerLookupContacts = .live(env)
+
+    XCTAssertThrowsError(try lookup(ids: [])) { error in
+      XCTAssertEqual(error as? MessengerLookupContacts.Error, .notConnected)
+    }
+  }
+
+  func testLookupWhenNotLoggedIn() {
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = { .unimplemented }
+    env.ud.get = { nil }
+    let lookup: MessengerLookupContacts = .live(env)
+
+    XCTAssertThrowsError(try lookup(ids: [])) { error in
+      XCTAssertEqual(error as? MessengerLookupContacts.Error, .notLoggedIn)
+    }
+  }
+
+  func testLookupFailure() {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+
+    var env: MessengerEnvironment = .unimplemented
+    env.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getId.run = { 0 }
+      return e2e
+    }
+    env.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.getContact.run = { .unimplemented(Data()) }
+      return ud
+    }
+    env.getSingleUseParams.run = { Data() }
+    env.multiLookupUD.run = { _, _ in throw failure }
+    let lookup: MessengerLookupContacts = .live(env)
+
+    XCTAssertThrowsError(try lookup(ids: [])) { error in
+      XCTAssertEqual(error as? Failure, failure)
+    }
+  }
+}