diff --git a/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt
index daa027576b89380992fd07412c4514d84387ca6f..089f04e6a4d1931eba92dc4d6339c9a6f1c4fc5c 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt
@@ -39,6 +39,7 @@ enum class Screen {
 interface BriarService {
     @Composable
     fun start(
+        contactManager: ContactManager,
         conversationManager: ConversationManager,
         messagingManager: MessagingManager
     )
@@ -46,7 +47,8 @@ interface BriarService {
     fun stop()
 }
 
-val CM = compositionLocalOf<ConversationManager> { error("Undefined ConversationManager") }
+val CVM = compositionLocalOf<ConversationManager> { error("Undefined ConversationManager") }
+val CTM = compositionLocalOf<ContactManager> { error("Undefined ContactManager") }
 val MM = compositionLocalOf<MessagingManager> { error("Undefined MessagingManager") }
 
 @Immutable
@@ -70,6 +72,7 @@ constructor(
 
     @Composable
     override fun start(
+        contactManager: ContactManager,
         conversationManager: ConversationManager,
         messagingManager: MessagingManager
     ) {
@@ -115,7 +118,8 @@ constructor(
 
                     else ->
                         CompositionLocalProvider(
-                            CM provides conversationManager,
+                            CVM provides conversationManager,
+                            CTM provides contactManager,
                             MM provides messagingManager
                         ) {
                             BriarUIStateManager(contacts)
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/UI.kt b/src/main/kotlin/org/briarproject/briar/desktop/UI.kt
index a95c4f856a5f7f615639f518a3b69a364dbd8d02..871be40b97446240de7204fa2080f84e228bdb91 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/UI.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/UI.kt
@@ -34,10 +34,10 @@ constructor(
 
     @Composable
     internal fun startBriar() {
-        briarService.start(conversationManager, messagingManager)
-    }
-
-    internal fun getContactManager(): ContactManager {
-        return contactManager
+        briarService.start(
+            contactManager,
+            conversationManager,
+            messagingManager
+        )
     }
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt
index 3d1c8a84d28cf1918e83d30d731ec990dfc1caaf..0810db51b33b2bfd9c2c7cc6d59f43dbeae4e9b0 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt
@@ -74,10 +74,17 @@ import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import org.briarproject.bramble.api.FormatException
 import org.briarproject.bramble.api.contact.Contact
 import org.briarproject.bramble.api.contact.ContactId
+import org.briarproject.bramble.api.contact.ContactManager
+import org.briarproject.bramble.api.db.ContactExistsException
+import org.briarproject.bramble.api.db.PendingContactExistsException
+import org.briarproject.bramble.api.identity.AuthorConstants
+import org.briarproject.bramble.util.StringUtils
 import org.briarproject.briar.api.conversation.ConversationMessageHeader
-import org.briarproject.briar.desktop.CM
+import org.briarproject.briar.desktop.CTM
+import org.briarproject.briar.desktop.CVM
 import org.briarproject.briar.desktop.MM
 import org.briarproject.briar.desktop.chat.Chat
 import org.briarproject.briar.desktop.chat.ChatHistoryConversationVisitor
@@ -94,6 +101,7 @@ import org.briarproject.briar.desktop.paul.theme.briarGreen
 import org.briarproject.briar.desktop.paul.theme.darkGray
 import org.briarproject.briar.desktop.paul.theme.divider
 import org.briarproject.briar.desktop.paul.theme.lightGray
+import java.security.GeneralSecurityException
 import java.time.Instant
 import java.time.LocalDateTime
 import java.time.ZoneId
@@ -142,12 +150,16 @@ fun PrivateMessageView(
 
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
-fun AddContactDialog(isVisible: Boolean, onCancel: (Boolean) -> Unit) {
+fun AddContactDialog(isVisible: Boolean, setDialogVisibility: (Boolean) -> Unit) {
     if (!isVisible) {
         return
     }
+    var contactAlias by remember { mutableStateOf("") }
+    var contactLink by remember { mutableStateOf("") }
+    val contactManager = CTM.current
+    val ownLink = CTM.current.handshakeLink
     AlertDialog(
-        onDismissRequest = { onCancel(false) },
+        onDismissRequest = { setDialogVisibility(false) },
         text = {
             Column(modifier = Modifier.fillMaxWidth()) {
                 Row(Modifier.fillMaxWidth().padding(vertical = 16.dp)) {
@@ -164,7 +176,7 @@ fun AddContactDialog(isVisible: Boolean, onCancel: (Boolean) -> Unit) {
                         Modifier.width(128.dp).align(Alignment.CenterVertically),
                         color = lightGray
                     )
-                    TextField("", onValueChange = {}, modifier = Modifier.fillMaxWidth())
+                    TextField(contactLink, onValueChange = { contactLink = it }, modifier = Modifier.fillMaxWidth())
                 }
                 Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
                     Text(
@@ -172,7 +184,7 @@ fun AddContactDialog(isVisible: Boolean, onCancel: (Boolean) -> Unit) {
                         Modifier.width(128.dp).align(Alignment.CenterVertically),
                         color = lightGray
                     )
-                    TextField("", onValueChange = {}, modifier = Modifier.fillMaxWidth())
+                    TextField(contactAlias, onValueChange = { contactAlias = it }, modifier = Modifier.fillMaxWidth())
                 }
                 Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
                     Text(
@@ -181,8 +193,7 @@ fun AddContactDialog(isVisible: Boolean, onCancel: (Boolean) -> Unit) {
                         color = lightGray
                     )
                     TextField(
-                        // TODO: use real code
-                        "briar://ksdjlfgakslhjgaklsjdhglkasjdlk3j12h4lk2j3tkj4",
+                        ownLink,
                         onValueChange = {},
                         modifier = Modifier.fillMaxWidth()
                     )
@@ -191,14 +202,23 @@ fun AddContactDialog(isVisible: Boolean, onCancel: (Boolean) -> Unit) {
         },
         confirmButton = {
             TextButton(
-                onClick = { onCancel(false) }, modifier = Modifier.background(briarGreen)
+                onClick = {
+                    if (ownLink.equals(contactLink)) {
+                        println("Please enter contact's link, not your own")
+                        setDialogVisibility(false)
+                        return@TextButton
+                    }
+                    addPendingContact(contactManager, contactAlias, contactLink)
+                    setDialogVisibility(false)
+                },
+                modifier = Modifier.background(briarGreen)
             ) {
                 Text("Add")
             }
         },
         dismissButton = {
             TextButton(
-                onClick = { onCancel(false) }, modifier = Modifier.background(briarBlack)
+                onClick = { setDialogVisibility(false) }, modifier = Modifier.background(briarBlack)
             ) {
                 Text("Cancel")
             }
@@ -210,6 +230,41 @@ fun AddContactDialog(isVisible: Boolean, onCancel: (Boolean) -> Unit) {
     )
 }
 
+private fun addPendingContact(contactManager: ContactManager, alias: String, link: String) {
+    if (aliasIsInvalid(alias)) {
+        println("Alias is invalid")
+        return
+    }
+    try {
+        contactManager.addPendingContact(link, alias)
+    } catch (e: FormatException) {
+        println("Link is invalid")
+        println(e.stackTrace)
+    } catch (e: GeneralSecurityException) {
+        println("Public key is invalid")
+        println(e.stackTrace)
+    }
+    /*
+    TODO: Warn user that the following two errors might be an attack
+
+     Use `e.pendingContact.id.bytes` and `e.pendingContact.alias` to implement the following logic:
+     https://code.briarproject.org/briar/briar-gtk/-/merge_requests/97
+
+    */
+    catch (e: ContactExistsException) {
+        println("Contact already exists")
+        println(e.stackTrace)
+    } catch (e: PendingContactExistsException) {
+        println("Pending Contact already exists")
+        println(e.stackTrace)
+    }
+}
+
+private fun aliasIsInvalid(alias: String): Boolean {
+    val aliasUtf8 = StringUtils.toUtf8(alias)
+    return aliasUtf8.isEmpty() || aliasUtf8.size > AuthorConstants.MAX_AUTHOR_NAME_LENGTH
+}
+
 @Composable
 fun SearchTextField(searchValue: String, onValueChange: (String) -> Unit, onContactAdd: (Boolean) -> Unit) {
     TextField(
@@ -279,7 +334,7 @@ fun ContactCard(
                     color = Color.White,
                     modifier = Modifier.align(Alignment.Start).padding(bottom = 2.dp)
                 )
-                val latestMsgTime = CM.current.getGroupCount(contact.id).latestMsgTime
+                val latestMsgTime = CVM.current.getGroupCount(contact.id).latestMsgTime
                 Text(
                     getFormattedTimestamp(latestMsgTime),
                     fontSize = 10.sp,
@@ -720,7 +775,7 @@ fun DrawMessageRow(
 fun ChatState(id: ContactId): MutableState<UiState<Chat>> {
     val state: MutableState<UiState<Chat>> = remember { mutableStateOf(UiState.Loading) }
     val messagingManager = MM.current
-    val conversationManager = CM.current
+    val conversationManager = CVM.current
 
     DisposableEffect(id) {
         state.value = UiState.Loading