diff --git a/briar b/briar
index 85dc27ed77e8b1a94ce4bccf2e9dd0e4e9660e00..166433dcc8f9aeb6bd05ef51a575eb2881a1730f 160000
--- a/briar
+++ b/briar
@@ -1 +1 @@
-Subproject commit 85dc27ed77e8b1a94ce4bccf2e9dd0e4e9660e00
+Subproject commit 166433dcc8f9aeb6bd05ef51a575eb2881a1730f
diff --git a/build.gradle.kts b/build.gradle.kts
index b5de178228eb2621bdefd4ac945b4213cacb7bd5..bb4f9e6981971c534837dbdb43face5e18f387b1 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -21,6 +21,8 @@ buildscript {
     // keep version here in sync when updating briar
     extra.apply {
         set("dagger_version", "2.33")
+        set("tor_version", "0.3.5.17")
+        set("obfs4proxy_version", "0.0.12-dev-40245c4a")
         set("junit_version", "4.13.2")
         set("jmock_version", "2.12.0")
     }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt b/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt
index b388c8c358af83c2462410068a445b1c2502a52c..c5756288c17207ed5da5754fe5426d11b7cdbec7 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt
@@ -30,11 +30,11 @@ import org.briarproject.bramble.system.DesktopSecureRandomModule
 import org.briarproject.bramble.system.JavaSystemModule
 import org.briarproject.bramble.util.OsUtils.isLinux
 import org.briarproject.bramble.util.OsUtils.isMac
+import org.briarproject.briar.desktop.threading.BriarExecutors
+import org.briarproject.briar.desktop.threading.BriarExecutorsImpl
+import org.briarproject.briar.desktop.threading.UiExecutor
 import org.briarproject.briar.desktop.ui.BriarUi
 import org.briarproject.briar.desktop.ui.BriarUiImpl
-import org.briarproject.briar.desktop.viewmodel.BriarExecutors
-import org.briarproject.briar.desktop.viewmodel.BriarExecutorsImpl
-import org.briarproject.briar.desktop.viewmodel.UiExecutor
 import org.briarproject.briar.desktop.viewmodel.ViewModelModule
 import java.io.File
 import java.nio.file.Path
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt
index b8e8f52fad19d615167d973c4db03d86cb01b97b..d4b8746edcf03768dac204f63d13f5bf7e4b674e 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager
 import org.briarproject.briar.api.conversation.ConversationManager
 import org.briarproject.briar.api.conversation.event.ConversationMessageTrackedEvent
 import org.briarproject.briar.desktop.conversation.ConversationMessagesReadEvent
-import org.briarproject.briar.desktop.viewmodel.BriarExecutors
+import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.viewmodel.asState
 import javax.inject.Inject
 
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
index 12ab6f2b6fb3423e628bf5db5bd57a8abedbb7e7..564d9b2d5177e013088be1025c4df13d1d488efd 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt
@@ -14,12 +14,11 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager
 import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent
 import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent
 import org.briarproject.briar.api.conversation.ConversationManager
+import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.utils.clearAndAddAll
 import org.briarproject.briar.desktop.utils.removeFirst
 import org.briarproject.briar.desktop.utils.replaceFirst
-import org.briarproject.briar.desktop.viewmodel.BriarExecutors
 import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel
-import org.briarproject.briar.desktop.viewmodel.UiExecutor
 
 abstract class ContactsViewModel(
     protected val contactManager: ContactManager,
@@ -50,14 +49,13 @@ abstract class ContactsViewModel(
                 conversationManager.getGroupCount(txn, contact.id),
             )
         }
-        _fullContactList.postUpdate {
-            it.clearAndAddAll(contactList)
+        txn.attach {
+            _fullContactList.clearAndAddAll(contactList)
             updateFilteredList()
         }
     }
 
     // todo: when migrated to StateFlow, this could be done implicitly instead
-    @UiExecutor
     protected open fun updateFilteredList() {
         _filteredContactList.clearAndAddAll(
             _fullContactList.filter(::filterContactItem).sortedByDescending { it.timestamp }
@@ -85,13 +83,11 @@ abstract class ContactsViewModel(
         }
     }
 
-    @UiExecutor
     protected open fun updateItem(contactId: ContactId, update: (ContactItem) -> ContactItem) {
         _fullContactList.replaceFirst({ it.contactId == contactId }, update)
         updateFilteredList()
     }
 
-    @UiExecutor
     protected open fun removeItem(contactId: ContactId) {
         _fullContactList.removeFirst { it.contactId == contactId }
         updateFilteredList()
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt
index d751e072c9d0b968d83953072d3d4680b8fc400b..1ce5e996ca81a2688f7c8a1b93f6155be72b8f45 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.db.TransactionManager
 import org.briarproject.bramble.api.identity.AuthorConstants
 import org.briarproject.bramble.api.lifecycle.LifecycleManager
 import org.briarproject.bramble.util.StringUtils
-import org.briarproject.briar.desktop.viewmodel.BriarExecutors
+import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.viewmodel.DbViewModel
 import org.briarproject.briar.desktop.viewmodel.asState
 import java.security.GeneralSecurityException
@@ -26,6 +26,7 @@ constructor(
 ) : DbViewModel(briarExecutors, lifecycleManager, db) {
 
     override fun onInit() {
+        super.onInit()
         fetchHandshakeLink()
     }
 
@@ -45,9 +46,9 @@ constructor(
         _remoteHandshakeLink.value = link
     }
 
-    private fun fetchHandshakeLink() = runOnDbThread {
-        val link = contactManager.handshakeLink
-        _handshakeLink.postValue(link)
+    private fun fetchHandshakeLink() = runOnDbThreadWithTransaction(true) { txn ->
+        val link = contactManager.getHandshakeLink(txn)
+        txn.attach { _handshakeLink.value = link }
     }
 
     fun onSubmitAddContactDialog() {
@@ -70,9 +71,9 @@ constructor(
             return
         }
 
-        runOnDbThread {
+        runOnDbThreadWithTransaction(false) { txn ->
             try {
-                contactManager.addPendingContact(link, alias)
+                contactManager.addPendingContact(txn, link, alias)
             } catch (e: FormatException) {
                 println("Link is invalid")
                 println(e.stackTrace)
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt
index e1215b71091517583ef311250333eb219eb1bd17..b4356dc1cac2ec71607efd126e3618d0591fab00 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt
@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.connection.ConnectionRegistry
 import org.briarproject.bramble.api.contact.ContactId
 import org.briarproject.bramble.api.contact.ContactManager
 import org.briarproject.bramble.api.contact.event.ContactRemovedEvent
-import org.briarproject.bramble.api.db.DatabaseExecutor
 import org.briarproject.bramble.api.db.DbException
 import org.briarproject.bramble.api.db.NoSuchContactException
 import org.briarproject.bramble.api.db.Transaction
@@ -34,13 +33,12 @@ import org.briarproject.briar.api.messaging.PrivateMessage
 import org.briarproject.briar.api.messaging.PrivateMessageFactory
 import org.briarproject.briar.api.messaging.PrivateMessageHeader
 import org.briarproject.briar.desktop.contact.ContactItem
+import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.utils.KLoggerUtils.logDuration
 import org.briarproject.briar.desktop.utils.clearAndAddAll
 import org.briarproject.briar.desktop.utils.replaceIf
 import org.briarproject.briar.desktop.utils.replaceIfIndexed
-import org.briarproject.briar.desktop.viewmodel.BriarExecutors
 import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel
-import org.briarproject.briar.desktop.viewmodel.UiExecutor
 import org.briarproject.briar.desktop.viewmodel.asList
 import org.briarproject.briar.desktop.viewmodel.asState
 import javax.inject.Inject
@@ -74,7 +72,6 @@ constructor(
 
     val newMessage = _newMessage.asState()
 
-    @UiExecutor
     fun setContactId(id: ContactId) {
         if (_contactId.value == id)
             return
@@ -89,7 +86,6 @@ constructor(
         setNewMessage("")
     }
 
-    @UiExecutor
     fun setNewMessage(msg: String) {
         _newMessage.value = msg
     }
@@ -106,9 +102,7 @@ constructor(
         runOnDbThreadWithTransaction(false) { txn ->
             try {
                 val start = LogUtils.now()
-                val groupId = messagingManager.getContactGroup(contactManager.getContact(txn, contactId)).id
-                // todo: following would be easier if function with txn would exist
-                // val groupId = messagingManager.getConversationId(contactId)
+                val groupId = messagingManager.getConversationId(txn, contactId)
                 val m = createMessage(txn, contactId, groupId, text)
                 messagingManager.addLocalMessage(txn, m)
                 LOG.logDuration("Storing message", start)
@@ -120,8 +114,9 @@ constructor(
                     m.hasText(), m.attachmentHeaders,
                     m.autoDeleteTimer
                 )
+                val msg = messageHeaderToItem(txn, h)
                 txn.attach {
-                    _messages.add(0, messageHeaderToItem(h))
+                    _messages.add(0, msg)
                 }
             } catch (e: UnexpectedTimerException) {
                 // todo: handle this properly
@@ -138,15 +133,14 @@ constructor(
     fun markMessagesRead(untilIndex: Int) {
         val id = _contactId.value!!
         val messages = _messages.toList()
-        runOnDbThread {
+        runOnDbThreadWithTransaction(false) { txn ->
             var count = 0
             messages.filterIndexed { idx, it -> idx >= untilIndex && !it.isRead }.forEach {
-                // todo: might be more performant when only using one transaction for all those calls
-                conversationManager.setReadFlag(it.groupId, it.id, true)
+                conversationManager.setReadFlag(txn, it.groupId, it.id, true)
                 count++
             }
-            _messages.postUpdate { list ->
-                list.replaceIfIndexed({ idx, it -> idx >= untilIndex && !it.isRead }) { _, it ->
+            txn.attach {
+                _messages.replaceIfIndexed({ idx, it -> idx >= untilIndex && !it.isRead }) { _, it ->
                     it.markRead()
                 }
             }
@@ -154,7 +148,6 @@ constructor(
         }
     }
 
-    @DatabaseExecutor
     @Throws(DbException::class)
     private fun createMessage(txn: Transaction, contactId: ContactId, groupId: GroupId, text: String): PrivateMessage {
         val timestamp = conversationManager.getTimestampForOutgoingMessage(txn, contactId)
@@ -176,49 +169,45 @@ constructor(
                 conversationManager.getGroupCount(txn, id),
             )
             LOG.logDuration("Loading contact", start)
-            _contactItem.postValue(contactItem)
-            // todo: or the following?
-            // txn.attach { _contactItem.value = contactItem }
+            txn.attach { _contactItem.value = contactItem }
         } catch (e: NoSuchContactException) {
             // todo: handle this properly
             LOG.warn(e) {}
         }
     }
 
-    private fun loadMessages(id: ContactId) = runOnDbThread {
+    private fun loadMessages(id: ContactId) = runOnDbThreadWithTransaction(true) { txn ->
         try {
             var start = LogUtils.now()
-            val headers = conversationManager.getMessageHeaders(id)
+            val headers = conversationManager.getMessageHeaders(txn, id)
             LOG.logDuration("Loading message headers", start)
             // Sort headers by timestamp in *descending* order
             val sorted = headers.sortedByDescending { it.timestamp }
             // todo: use ConversationVisitor to also display Request and Notice Messages
             start = LogUtils.now()
-            val messages = sorted.filterIsInstance<PrivateMessageHeader>().map(::messageHeaderToItem)
+            val messages = sorted.filterIsInstance<PrivateMessageHeader>().map { messageHeaderToItem(txn, it) }
             LOG.logDuration("Loading messages", start)
-            _messages.postUpdate { it.clearAndAddAll(messages) }
+            txn.attach { _messages.clearAndAddAll(messages) }
         } catch (e: NoSuchContactException) {
             // todo: handle this properly
             LOG.warn(e) {}
         }
     }
 
-    @DatabaseExecutor
-    private fun messageHeaderToItem(h: PrivateMessageHeader): ConversationMessageItem {
+    private fun messageHeaderToItem(txn: Transaction, h: PrivateMessageHeader): ConversationMessageItem {
         // todo: use ConversationVisitor instead and support other MessageHeader
         val item = ConversationMessageItem(h)
         if (h.hasText()) {
-            item.text = loadMessageText(h.id)
+            item.text = loadMessageText(txn, h.id)
         } else {
             LOG.warn { "private message without text" }
         }
         return item
     }
 
-    @DatabaseExecutor
-    private fun loadMessageText(m: MessageId): String? {
+    private fun loadMessageText(txn: Transaction, m: MessageId): String? {
         try {
-            return messagingManager.getMessageText(m)
+            return messagingManager.getMessageText(txn, m)
         } catch (e: DbException) {
             LOG.warn(e) {}
         }
@@ -239,7 +228,10 @@ constructor(
                     val h = e.messageHeader
                     if (h is PrivateMessageHeader) {
                         // insert at start of list according to descending sort order
-                        _messages.add(0, messageHeaderToItem(h))
+                        runOnDbThreadWithTransaction(true) { txn ->
+                            val msg = messageHeaderToItem(txn, h)
+                            txn.attach { _messages.add(0, msg) }
+                        }
                     }
                 }
             }
@@ -282,7 +274,6 @@ constructor(
         }
     }
 
-    @UiExecutor
     private fun markMessages(
         messageIds: Collection<MessageId>,
         sent: Boolean,
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt
index 1d155da27479a24aa9bd835d297fb0f4d4a20b47..4165c9128b11c948ee0ca9a61e6ba850f5528a89 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt
@@ -9,7 +9,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager
 import org.briarproject.briar.api.conversation.ConversationManager
 import org.briarproject.briar.desktop.contact.ContactItem
 import org.briarproject.briar.desktop.contact.ContactsViewModel
-import org.briarproject.briar.desktop.viewmodel.BriarExecutors
+import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.viewmodel.asState
 import javax.inject.Inject
 
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt
index 4452430e78fd852371101d557955c15e3eb83a1a..49f1f05b0c8dc6ea14b21c24852ffc57027ef053 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt
@@ -16,9 +16,9 @@ import org.briarproject.briar.desktop.login.LoginViewModel.LoginState.SIGNED_OUT
 import org.briarproject.briar.desktop.login.LoginViewModel.LoginState.SIGNING_IN
 import org.briarproject.briar.desktop.login.LoginViewModel.LoginState.STARTED
 import org.briarproject.briar.desktop.login.LoginViewModel.LoginState.STARTING
-import org.briarproject.briar.desktop.viewmodel.BriarExecutors
+import org.briarproject.briar.desktop.threading.BriarExecutors
+import org.briarproject.briar.desktop.threading.UiExecutor
 import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel
-import org.briarproject.briar.desktop.viewmodel.UiExecutor
 import org.briarproject.briar.desktop.viewmodel.asState
 import javax.inject.Inject
 
@@ -28,7 +28,7 @@ constructor(
     private val accountManager: AccountManager,
     private val briarExecutors: BriarExecutors,
     private val lifecycleManager: LifecycleManager,
-    private val eventBus: EventBus,
+    eventBus: EventBus,
     db: TransactionManager,
 ) : EventListenerDbViewModel(briarExecutors, lifecycleManager, db, eventBus) {
 
@@ -53,7 +53,6 @@ constructor(
         }
     }
 
-    @UiExecutor
     private fun updateState(s: LifecycleState) {
         _state.value =
             if (accountManager.hasDatabaseKey()) {
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/BriarExecutors.kt b/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutors.kt
similarity index 85%
rename from src/main/kotlin/org/briarproject/briar/desktop/viewmodel/BriarExecutors.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutors.kt
index 3ef59afced267e446421734bed212bb35cfbb992..6d5cf36c6a0745ef2ed1a776cb4dd9bad163a1e6 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/BriarExecutors.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutors.kt
@@ -1,4 +1,4 @@
-package org.briarproject.briar.desktop.viewmodel
+package org.briarproject.briar.desktop.threading
 
 import org.briarproject.bramble.api.db.DatabaseExecutor
 import org.briarproject.bramble.api.lifecycle.IoExecutor
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/BriarExecutorsImpl.kt b/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutorsImpl.kt
similarity index 93%
rename from src/main/kotlin/org/briarproject/briar/desktop/viewmodel/BriarExecutorsImpl.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutorsImpl.kt
index e4170489ba39930676458723f3af8499ecbfd3c9..4bbdf9efc67853d2b616525e83c0845c3ce0b2cc 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/BriarExecutorsImpl.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutorsImpl.kt
@@ -1,4 +1,4 @@
-package org.briarproject.briar.desktop.viewmodel
+package org.briarproject.briar.desktop.threading
 
 import org.briarproject.bramble.api.db.DatabaseExecutor
 import org.briarproject.bramble.api.lifecycle.IoExecutor
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/UiExecutor.kt b/src/main/kotlin/org/briarproject/briar/desktop/threading/UiExecutor.kt
similarity index 90%
rename from src/main/kotlin/org/briarproject/briar/desktop/viewmodel/UiExecutor.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/threading/UiExecutor.kt
index e8e19ef8374bf92c07d5bd9b8e1ee19962e3c7d4..b3596b1b1a4fb84a8af8e6f08677ba553f8b3266 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/UiExecutor.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/threading/UiExecutor.kt
@@ -1,4 +1,4 @@
-package org.briarproject.briar.desktop.viewmodel
+package org.briarproject.briar.desktop.threading
 
 import javax.inject.Qualifier
 
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt
index 5e02198cd71aef9c6f399a6d8be92a0531294296..c5a157c4887647560c216ee913b6b53d6928809d 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt
@@ -1,13 +1,11 @@
 package org.briarproject.briar.desktop.viewmodel
 
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.State
-import androidx.compose.runtime.snapshots.SnapshotStateList
 import mu.KotlinLogging
 import org.briarproject.bramble.api.db.DatabaseExecutor
 import org.briarproject.bramble.api.db.Transaction
 import org.briarproject.bramble.api.db.TransactionManager
 import org.briarproject.bramble.api.lifecycle.LifecycleManager
+import org.briarproject.briar.desktop.threading.BriarExecutors
 
 abstract class DbViewModel(
     private val briarExecutors: BriarExecutors,
@@ -21,9 +19,9 @@ abstract class DbViewModel(
 
     /**
      * Waits for the DB to open and runs the given [task] on the [DatabaseExecutor].
-     *
-     * For thread-safety, do not access composable [State] inside [task],
-     * but use local variables and [MutableState.postValue] instead.
+     * To avoid inconsistent state between the database and the UI
+     * whenever the UI should react to a successful transaction,
+     * strongly consider using [runOnDbThreadWithTransaction] instead.
      */
     protected fun runOnDbThread(task: () -> Unit) = briarExecutors.onDbThread {
         try {
@@ -40,9 +38,8 @@ abstract class DbViewModel(
     /**
      * Waits for the DB to open and runs the given [task] on the [DatabaseExecutor],
      * providing a [Transaction], that may be [readOnly] or not, to the task.
-     *
-     * For thread-safety, do not access composable [State] inside [task],
-     * but use local variables and [MutableState.postValue] instead.
+     * Updates to the UI that depend on a successful transaction in the database
+     * should be attached to the transaction via [Transaction.attach].
      */
     protected fun runOnDbThreadWithTransaction(
         readOnly: Boolean,
@@ -64,12 +61,4 @@ abstract class DbViewModel(
             LOG.warn(e) { "Unhandled exception in database executor" }
         }
     }
-
-    fun <T> MutableState<T>.postValue(value: T) = briarExecutors.onUiThread {
-        this.value = value
-    }
-
-    fun <T> SnapshotStateList<T>.postUpdate(update: (SnapshotStateList<T>) -> Unit) = briarExecutors.onUiThread {
-        update(this)
-    }
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/EventListenerDbViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/EventListenerDbViewModel.kt
index cff983c625584a03e310bd48db61900742ec25aa..eea488c117dc26d26e58c02945d7c679ad31db66 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/EventListenerDbViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/EventListenerDbViewModel.kt
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.db.TransactionManager
 import org.briarproject.bramble.api.event.EventBus
 import org.briarproject.bramble.api.event.EventListener
 import org.briarproject.bramble.api.lifecycle.LifecycleManager
+import org.briarproject.briar.desktop.threading.BriarExecutors
 
 abstract class EventListenerDbViewModel(
     briarExecutors: BriarExecutors,
diff --git a/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt b/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt
index 2e779186ae647894339762124385af0df447ed45..101340c5276c0b27a7d32e3296b8cb8a88238209 100644
--- a/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt
+++ b/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt
@@ -33,11 +33,11 @@ import org.briarproject.bramble.util.OsUtils.isMac
 import org.briarproject.briar.api.test.TestAvatarCreator
 import org.briarproject.briar.desktop.testdata.DeterministicTestDataCreator
 import org.briarproject.briar.desktop.testdata.DeterministicTestDataCreatorImpl
+import org.briarproject.briar.desktop.threading.BriarExecutors
+import org.briarproject.briar.desktop.threading.BriarExecutorsImpl
+import org.briarproject.briar.desktop.threading.UiExecutor
 import org.briarproject.briar.desktop.ui.BriarUi
 import org.briarproject.briar.desktop.ui.BriarUiImpl
-import org.briarproject.briar.desktop.viewmodel.BriarExecutors
-import org.briarproject.briar.desktop.viewmodel.BriarExecutorsImpl
-import org.briarproject.briar.desktop.viewmodel.UiExecutor
 import org.briarproject.briar.desktop.viewmodel.ViewModelModule
 import org.briarproject.briar.test.TestModule
 import java.io.File
diff --git a/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt b/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt
index fdc4d3c589326c58ba94cfae267607c533d7222b..b75891546e680b61b1b0741b972dcf24c4cde003 100644
--- a/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt
+++ b/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt
@@ -197,7 +197,7 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor(
             // Tor
             val tor = TransportProperties()
             val torAddress = randomTorAddress
-            tor[TorConstants.PROP_ONION_V2] = torAddress
+            tor[TorConstants.PROP_ONION_V3] = torAddress
             props[TorConstants.ID] = tor
             return props
         }