diff --git a/lib/actions/message-actions.js b/lib/actions/message-actions.js
--- a/lib/actions/message-actions.js
+++ b/lib/actions/message-actions.js
@@ -15,6 +15,10 @@
   SearchMessagesResponse,
 } from '../types/message-types.js';
 import type { MediaMessageServerDBContent } from '../types/messages/media.js';
+import type {
+  ToggleMessagePinRequest,
+  ToggleMessagePinResult,
+} from '../types/thread-types.js';
 import {
   extractKeyserverIDFromID,
   sortThreadIDsPerKeyserver,
@@ -456,6 +460,36 @@
   return useKeyserverCall(searchMessages);
 }
 
+const toggleMessagePinActionTypes = Object.freeze({
+  started: 'TOGGLE_MESSAGE_PIN_STARTED',
+  success: 'TOGGLE_MESSAGE_PIN_SUCCESS',
+  failed: 'TOGGLE_MESSAGE_PIN_FAILED',
+});
+const toggleMessagePin =
+  (
+    callKeyserverEndpoint: CallKeyserverEndpoint,
+  ): ((input: ToggleMessagePinRequest) => Promise<ToggleMessagePinResult>) =>
+  async input => {
+    const keyserverID = extractKeyserverIDFromID(input.messageID);
+    const requests = { [keyserverID]: input };
+
+    const responses = await callKeyserverEndpoint(
+      'toggle_message_pin',
+      requests,
+    );
+    const response = responses[keyserverID];
+    return {
+      newMessageInfos: response.newMessageInfos,
+      threadID: response.threadID,
+    };
+  };
+
+function useToggleMessagePin(): (
+  input: ToggleMessagePinRequest,
+) => Promise<ToggleMessagePinResult> {
+  return useKeyserverCall(toggleMessagePin);
+}
+
 export {
   fetchMessagesBeforeCursorActionTypes,
   useFetchMessagesBeforeCursor,
@@ -480,4 +514,6 @@
   useSendEditMessage,
   useFetchPinnedMessages,
   fetchPinnedMessageActionTypes,
+  toggleMessagePinActionTypes,
+  useToggleMessagePin,
 };
diff --git a/lib/actions/thread-actions.js b/lib/actions/thread-actions.js
--- a/lib/actions/thread-actions.js
+++ b/lib/actions/thread-actions.js
@@ -2,6 +2,7 @@
 
 import invariant from 'invariant';
 
+import genesis from '../facts/genesis.js';
 import type {
   ChangeThreadSettingsPayload,
   LeaveThreadPayload,
@@ -12,14 +13,15 @@
   ThreadJoinPayload,
   ThreadFetchMediaRequest,
   ThreadFetchMediaResult,
-  ToggleMessagePinRequest,
-  ToggleMessagePinResult,
   RoleModificationRequest,
   RoleModificationPayload,
   RoleDeletionRequest,
   RoleDeletionPayload,
 } from '../types/thread-types.js';
+import { extractKeyserverIDFromID } from '../utils/action-utils.js';
 import type { CallServerEndpoint } from '../utils/call-server-endpoint.js';
+import type { CallKeyserverEndpoint } from '../utils/keyserver-call';
+import { useKeyserverCall } from '../utils/keyserver-call.js';
 import { values } from '../utils/objects.js';
 
 const deleteThreadActionTypes = Object.freeze({
@@ -66,6 +68,11 @@
     };
   };
 
+export type RemoveUsersFromThreadInput = {
+  +threadID: string,
+  +memberIDs: $ReadOnlyArray<string>,
+};
+
 const removeUsersFromThreadActionTypes = Object.freeze({
   started: 'REMOVE_USERS_FROM_THREAD_STARTED',
   success: 'REMOVE_USERS_FROM_THREAD_SUCCESS',
@@ -73,23 +80,35 @@
 });
 const removeUsersFromThread =
   (
-    callServerEndpoint: CallServerEndpoint,
+    callKeyserverEndpoint: CallKeyserverEndpoint,
   ): ((
-    threadID: string,
-    memberIDs: $ReadOnlyArray<string>,
+    input: RemoveUsersFromThreadInput,
   ) => Promise<ChangeThreadSettingsPayload>) =>
-  async (threadID, memberIDs) => {
-    const response = await callServerEndpoint('remove_members', {
-      threadID,
-      memberIDs,
-    });
+  async input => {
+    const keyserverID = extractKeyserverIDFromID(input.threadID);
+    const requests = { [keyserverID]: input };
+
+    const responses = await callKeyserverEndpoint('remove_members', requests);
+    const response = responses[keyserverID];
     return {
-      threadID,
+      threadID: input.threadID,
       updatesResult: response.updatesResult,
       newMessageInfos: response.newMessageInfos,
     };
   };
 
+function useRemoveUsersFromThread(): (
+  input: RemoveUsersFromThreadInput,
+) => Promise<ChangeThreadSettingsPayload> {
+  return useKeyserverCall(removeUsersFromThread);
+}
+
+export type ChangeThreadMemberRolesInput = {
+  +threadID: string,
+  +memberIDs: $ReadOnlyArray<string>,
+  +newRole: string,
+};
+
 const changeThreadMemberRolesActionTypes = Object.freeze({
   started: 'CHANGE_THREAD_MEMBER_ROLES_STARTED',
   success: 'CHANGE_THREAD_MEMBER_ROLES_SUCCESS',
@@ -97,18 +116,23 @@
 });
 const changeThreadMemberRoles =
   (
-    callServerEndpoint: CallServerEndpoint,
+    callKeyserverEndpoint: CallKeyserverEndpoint,
   ): ((
-    threadID: string,
-    memberIDs: $ReadOnlyArray<string>,
-    newRole: string,
+    input: ChangeThreadMemberRolesInput,
   ) => Promise<ChangeThreadSettingsPayload>) =>
-  async (threadID, memberIDs, newRole) => {
-    const response = await callServerEndpoint('update_role', {
-      threadID,
-      memberIDs,
-      role: newRole,
-    });
+  async input => {
+    const { threadID, memberIDs, newRole } = input;
+    const keyserverID = extractKeyserverIDFromID(input.threadID);
+    const requests = {
+      [keyserverID]: {
+        threadID,
+        memberIDs,
+        role: newRole,
+      },
+    };
+
+    const responses = await callKeyserverEndpoint('update_role', requests);
+    const response = responses[keyserverID];
     return {
       threadID,
       updatesResult: response.updatesResult,
@@ -116,6 +140,12 @@
     };
   };
 
+function useChangeThreadMemberRoles(): (
+  input: ChangeThreadMemberRolesInput,
+) => Promise<ChangeThreadSettingsPayload> {
+  return useKeyserverCall(changeThreadMemberRoles);
+}
+
 const newThreadActionTypes = Object.freeze({
   started: 'NEW_THREAD_STARTED',
   success: 'NEW_THREAD_SUCCESS',
@@ -123,10 +153,16 @@
 });
 const newThread =
   (
-    callServerEndpoint: CallServerEndpoint,
-  ): ((request: ClientNewThreadRequest) => Promise<NewThreadResult>) =>
-  async request => {
-    const response = await callServerEndpoint('create_thread', request);
+    callKeyserverEndpoint: CallKeyserverEndpoint,
+  ): ((input: ClientNewThreadRequest) => Promise<NewThreadResult>) =>
+  async input => {
+    const parentThreadID = input.parentThreadID ?? genesis.id;
+    const keyserverID = extractKeyserverIDFromID(parentThreadID);
+    const requests = { [keyserverID]: input };
+
+    const responses = await callKeyserverEndpoint('create_thread', requests);
+    const response = responses[keyserverID];
+
     return {
       newThreadID: response.newThreadID,
       updatesResult: response.updatesResult,
@@ -135,6 +171,12 @@
     };
   };
 
+function useNewThread(): (
+  input: ClientNewThreadRequest,
+) => Promise<NewThreadResult> {
+  return useKeyserverCall(newThread);
+}
+
 const joinThreadActionTypes = Object.freeze({
   started: 'JOIN_THREAD_STARTED',
   success: 'JOIN_THREAD_SUCCESS',
@@ -142,10 +184,14 @@
 });
 const joinThread =
   (
-    callServerEndpoint: CallServerEndpoint,
-  ): ((request: ClientThreadJoinRequest) => Promise<ThreadJoinPayload>) =>
-  async request => {
-    const response = await callServerEndpoint('join_thread', request);
+    callKeyserverEndpoint: CallKeyserverEndpoint,
+  ): ((input: ClientThreadJoinRequest) => Promise<ThreadJoinPayload>) =>
+  async input => {
+    const keyserverID = extractKeyserverIDFromID(input.threadID);
+    const requests = { [keyserverID]: input };
+
+    const responses = await callKeyserverEndpoint('join_thread', requests);
+    const response = responses[keyserverID];
     const userInfos = values(response.userInfos);
     return {
       updatesResult: response.updatesResult,
@@ -155,6 +201,15 @@
     };
   };
 
+function useJoinThread(): (
+  input: ClientThreadJoinRequest,
+) => Promise<ThreadJoinPayload> {
+  return useKeyserverCall(joinThread);
+}
+
+export type LeaveThreadInput = {
+  +threadID: string,
+};
 const leaveThreadActionTypes = Object.freeze({
   started: 'LEAVE_THREAD_STARTED',
   success: 'LEAVE_THREAD_SUCCESS',
@@ -162,41 +217,47 @@
 });
 const leaveThread =
   (
-    callServerEndpoint: CallServerEndpoint,
-  ): ((threadID: string) => Promise<LeaveThreadPayload>) =>
-  async threadID => {
-    const response = await callServerEndpoint('leave_thread', { threadID });
+    callKeyserverEndpoint: CallKeyserverEndpoint,
+  ): ((input: LeaveThreadInput) => Promise<LeaveThreadPayload>) =>
+  async input => {
+    const keyserverID = extractKeyserverIDFromID(input.threadID);
+    const requests = { [keyserverID]: input };
+
+    const responses = await callKeyserverEndpoint('leave_thread', requests);
+    const response = responses[keyserverID];
     return {
       updatesResult: response.updatesResult,
     };
   };
 
+function useLeaveThread(): (
+  input: LeaveThreadInput,
+) => Promise<LeaveThreadPayload> {
+  return useKeyserverCall(leaveThread);
+}
+
 const fetchThreadMedia =
   (
-    callServerEndpoint: CallServerEndpoint,
-  ): ((request: ThreadFetchMediaRequest) => Promise<ThreadFetchMediaResult>) =>
-  async request => {
-    const response = await callServerEndpoint('fetch_thread_media', request);
-    return response;
-  };
+    callKeyserverEndpoint: CallKeyserverEndpoint,
+  ): ((input: ThreadFetchMediaRequest) => Promise<ThreadFetchMediaResult>) =>
+  async input => {
+    const keyserverID = extractKeyserverIDFromID(input.threadID);
+    const requests = { [keyserverID]: input };
 
-const toggleMessagePinActionTypes = Object.freeze({
-  started: 'TOGGLE_MESSAGE_PIN_STARTED',
-  success: 'TOGGLE_MESSAGE_PIN_SUCCESS',
-  failed: 'TOGGLE_MESSAGE_PIN_FAILED',
-});
-const toggleMessagePin =
-  (
-    callServerEndpoint: CallServerEndpoint,
-  ): ((request: ToggleMessagePinRequest) => Promise<ToggleMessagePinResult>) =>
-  async request => {
-    const response = await callServerEndpoint('toggle_message_pin', request);
-    return {
-      newMessageInfos: response.newMessageInfos,
-      threadID: response.threadID,
-    };
+    const responses = await callKeyserverEndpoint(
+      'fetch_thread_media',
+      requests,
+    );
+    const response = responses[keyserverID];
+    return { media: response.media };
   };
 
+function useFetchThreadMedia(): (
+  input: ThreadFetchMediaRequest,
+) => Promise<ThreadFetchMediaResult> {
+  return useKeyserverCall(fetchThreadMedia);
+}
+
 const modifyCommunityRoleActionTypes = Object.freeze({
   started: 'MODIFY_COMMUNITY_ROLE_STARTED',
   success: 'MODIFY_COMMUNITY_ROLE_SUCCESS',
@@ -204,16 +265,29 @@
 });
 const modifyCommunityRole =
   (
-    callServerEndpoint: CallServerEndpoint,
-  ): ((request: RoleModificationRequest) => Promise<RoleModificationPayload>) =>
-  async request => {
-    const response = await callServerEndpoint('modify_community_role', request);
+    callKeyserverEndpoint: CallKeyserverEndpoint,
+  ): ((input: RoleModificationRequest) => Promise<RoleModificationPayload>) =>
+  async input => {
+    const keyserverID = extractKeyserverIDFromID(input.community);
+    const requests = { [keyserverID]: input };
+
+    const responses = await callKeyserverEndpoint(
+      'modify_community_role',
+      requests,
+    );
+    const response = responses[keyserverID];
     return {
       threadInfo: response.threadInfo,
       updatesResult: response.updatesResult,
     };
   };
 
+function useModifyCommunityRole(): (
+  input: RoleModificationRequest,
+) => Promise<RoleModificationPayload> {
+  return useKeyserverCall(modifyCommunityRole);
+}
+
 const deleteCommunityRoleActionTypes = Object.freeze({
   started: 'DELETE_COMMUNITY_ROLE_STARTED',
   success: 'DELETE_COMMUNITY_ROLE_SUCCESS',
@@ -221,36 +295,47 @@
 });
 const deleteCommunityRole =
   (
-    callServerEndpoint: CallServerEndpoint,
-  ): ((request: RoleDeletionRequest) => Promise<RoleDeletionPayload>) =>
-  async request => {
-    const response = await callServerEndpoint('delete_community_role', request);
+    callKeyserverEndpoint: CallKeyserverEndpoint,
+  ): ((input: RoleDeletionRequest) => Promise<RoleDeletionPayload>) =>
+  async input => {
+    const keyserverID = extractKeyserverIDFromID(input.community);
+    const requests = { [keyserverID]: input };
+
+    const responses = await callKeyserverEndpoint(
+      'delete_community_role',
+      requests,
+    );
+    const response = responses[keyserverID];
     return {
       threadInfo: response.threadInfo,
       updatesResult: response.updatesResult,
     };
   };
 
+function useDeleteCommunityRole(): (
+  input: RoleDeletionRequest,
+) => Promise<RoleDeletionPayload> {
+  return useKeyserverCall(deleteCommunityRole);
+}
+
 export {
   deleteThreadActionTypes,
   deleteThread,
   changeThreadSettingsActionTypes,
   changeThreadSettings,
   removeUsersFromThreadActionTypes,
-  removeUsersFromThread,
+  useRemoveUsersFromThread,
   changeThreadMemberRolesActionTypes,
-  changeThreadMemberRoles,
+  useChangeThreadMemberRoles,
   newThreadActionTypes,
-  newThread,
+  useNewThread,
   joinThreadActionTypes,
-  joinThread,
+  useJoinThread,
   leaveThreadActionTypes,
-  leaveThread,
-  fetchThreadMedia,
-  toggleMessagePinActionTypes,
-  toggleMessagePin,
+  useLeaveThread,
+  useFetchThreadMedia,
   modifyCommunityRoleActionTypes,
-  modifyCommunityRole,
+  useModifyCommunityRole,
   deleteCommunityRoleActionTypes,
-  deleteCommunityRole,
+  useDeleteCommunityRole,
 };
diff --git a/lib/reducers/message-reducer.js b/lib/reducers/message-reducer.js
--- a/lib/reducers/message-reducer.js
+++ b/lib/reducers/message-reducer.js
@@ -21,6 +21,7 @@
   restoreEntryActionTypes,
 } from '../actions/entry-actions.js';
 import {
+  toggleMessagePinActionTypes,
   fetchMessagesBeforeCursorActionTypes,
   fetchMostRecentMessagesActionTypes,
   sendTextMessageActionTypes,
@@ -43,7 +44,6 @@
   removeUsersFromThreadActionTypes,
   changeThreadMemberRolesActionTypes,
   joinThreadActionTypes,
-  toggleMessagePinActionTypes,
 } from '../actions/thread-actions.js';
 import { updateMultimediaMessageMediaActionType } from '../actions/upload-actions.js';
 import {
diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js
--- a/lib/shared/thread-utils.js
+++ b/lib/shared/thread-utils.js
@@ -16,6 +16,7 @@
   fetchMostRecentMessagesActionTypes,
   useFetchMostRecentMessages,
 } from '../actions/message-actions.js';
+import type { RemoveUsersFromThreadInput } from '../actions/thread-actions';
 import {
   changeThreadMemberRolesActionTypes,
   newThreadActionTypes,
@@ -1432,14 +1433,16 @@
   memberInfo: RelativeMemberInfo,
   dispatchActionPromise: DispatchActionPromise,
   removeUserFromThreadServerCall: (
-    threadID: string,
-    memberIDs: $ReadOnlyArray<string>,
+    input: RemoveUsersFromThreadInput,
   ) => Promise<ChangeThreadSettingsPayload>,
 ) {
   const customKeyName = `${removeUsersFromThreadActionTypes.started}:${memberInfo.id}`;
   dispatchActionPromise(
     removeUsersFromThreadActionTypes,
-    removeUserFromThreadServerCall(threadInfo.id, [memberInfo.id]),
+    removeUserFromThreadServerCall({
+      threadID: threadInfo.id,
+      memberIDs: [memberInfo.id],
+    }),
     { customKeyName },
   );
 }
diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js
--- a/native/chat/chat-input-bar.react.js
+++ b/native/chat/chat-input-bar.react.js
@@ -28,7 +28,7 @@
 } from 'lib/actions/draft-actions.js';
 import {
   joinThreadActionTypes,
-  joinThread,
+  useJoinThread,
   newThreadActionTypes,
 } from 'lib/actions/thread-actions.js';
 import {
@@ -83,7 +83,6 @@
 import { type UserInfos } from 'lib/types/user-types.js';
 import {
   type DispatchActionPromise,
-  useServerCall,
   useDispatchActionPromise,
 } from 'lib/utils/action-utils.js';
 
@@ -1264,7 +1263,7 @@
 
   const dispatch = useDispatch();
   const dispatchActionPromise = useDispatchActionPromise();
-  const callJoinThread = useServerCall(joinThread);
+  const callJoinThread = useJoinThread();
 
   const userSearchIndex = useSelector(userStoreMentionSearchIndex);
 
diff --git a/native/chat/compose-subchannel.react.js b/native/chat/compose-subchannel.react.js
--- a/native/chat/compose-subchannel.react.js
+++ b/native/chat/compose-subchannel.react.js
@@ -7,7 +7,10 @@
 import * as React from 'react';
 import { View, Text } from 'react-native';
 
-import { newThreadActionTypes, newThread } from 'lib/actions/thread-actions.js';
+import {
+  newThreadActionTypes,
+  useNewThread,
+} from 'lib/actions/thread-actions.js';
 import { useENSNames } from 'lib/hooks/ens-cache.js';
 import { threadInfoSelector } from 'lib/selectors/thread-selectors.js';
 import {
@@ -19,10 +22,7 @@
 import { type ThreadType, threadTypes } from 'lib/types/thread-types-enum.js';
 import { type ThreadInfo } from 'lib/types/thread-types.js';
 import { type AccountUserInfo } from 'lib/types/user-types.js';
-import {
-  useServerCall,
-  useDispatchActionPromise,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import type { ChatNavigationProp } from './chat.react.js';
 import { useNavigateToThread } from './message-list-types.js';
@@ -74,7 +74,7 @@
 
   const { threadType, parentThreadInfo } = props.route.params;
   const userInfoInputIDs = userInfoInputArray.map(userInfo => userInfo.id);
-  const callNewThread = useServerCall(newThread);
+  const callNewThread = useNewThread();
   const calendarQuery = useCalendarQuery();
   const newChatThreadAction = React.useCallback(async () => {
     try {
diff --git a/native/chat/settings/thread-settings-leave-thread.react.js b/native/chat/settings/thread-settings-leave-thread.react.js
--- a/native/chat/settings/thread-settings-leave-thread.react.js
+++ b/native/chat/settings/thread-settings-leave-thread.react.js
@@ -6,8 +6,9 @@
 
 import {
   leaveThreadActionTypes,
-  leaveThread,
+  useLeaveThread,
 } from 'lib/actions/thread-actions.js';
+import type { LeaveThreadInput } from 'lib/actions/thread-actions.js';
 import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
 import { otherUsersButNoOtherAdmins } from 'lib/selectors/thread-selectors.js';
 import { identifyInvalidatedThreads } from 'lib/shared/thread-utils.js';
@@ -15,7 +16,6 @@
 import type { ThreadInfo, LeaveThreadPayload } from 'lib/types/thread-types.js';
 import {
   type DispatchActionPromise,
-  useServerCall,
   useDispatchActionPromise,
 } from 'lib/utils/action-utils.js';
 
@@ -44,7 +44,7 @@
   // Redux dispatch functions
   +dispatchActionPromise: DispatchActionPromise,
   // async functions that hit server APIs
-  +leaveThread: (threadID: string) => Promise<LeaveThreadPayload>,
+  +leaveThread: (input: LeaveThreadInput) => Promise<LeaveThreadPayload>,
   // withNavContext
   +navContext: ?NavContextType,
 };
@@ -113,7 +113,7 @@
       payload: { threadIDs: [threadID] },
     });
     try {
-      const result = await this.props.leaveThread(threadID);
+      const result = await this.props.leaveThread({ threadID });
       const invalidated = identifyInvalidatedThreads(
         result.updatesResult.newUpdates,
       );
@@ -165,7 +165,7 @@
     const colors = useColors();
     const styles = useStyles(unboundStyles);
     const dispatchActionPromise = useDispatchActionPromise();
-    const callLeaveThread = useServerCall(leaveThread);
+    const callLeaveThread = useLeaveThread();
     const navContext = React.useContext(NavContext);
     return (
       <ThreadSettingsLeaveThread
diff --git a/native/chat/settings/thread-settings-media-gallery.react.js b/native/chat/settings/thread-settings-media-gallery.react.js
--- a/native/chat/settings/thread-settings-media-gallery.react.js
+++ b/native/chat/settings/thread-settings-media-gallery.react.js
@@ -6,9 +6,8 @@
 import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
 import { FlatList } from 'react-native-gesture-handler';
 
-import { fetchThreadMedia } from 'lib/actions/thread-actions.js';
+import { useFetchThreadMedia } from 'lib/actions/thread-actions.js';
 import type { MediaInfo, Media } from 'lib/types/media-types';
-import { useServerCall } from 'lib/utils/action-utils.js';
 
 import GestureTouchableOpacity from '../../components/gesture-touchable-opacity.react.js';
 import Multimedia from '../../media/multimedia.react.js';
@@ -50,7 +49,7 @@
     (width - 32 - (numColumns - 1) * galleryItemGap) / numColumns;
   const { threadID, limit, verticalBounds, offset, activeTab } = props;
   const [mediaInfos, setMediaInfos] = React.useState([]);
-  const callFetchThreadMedia = useServerCall(fetchThreadMedia);
+  const callFetchThreadMedia = useFetchThreadMedia();
 
   React.useEffect(() => {
     const fetchData = async () => {
diff --git a/native/chat/settings/thread-settings-member-tooltip-modal.react.js b/native/chat/settings/thread-settings-member-tooltip-modal.react.js
--- a/native/chat/settings/thread-settings-member-tooltip-modal.react.js
+++ b/native/chat/settings/thread-settings-member-tooltip-modal.react.js
@@ -2,14 +2,11 @@
 
 import * as React from 'react';
 
-import { removeUsersFromThread } from 'lib/actions/thread-actions.js';
+import { useRemoveUsersFromThread } from 'lib/actions/thread-actions.js';
 import { removeMemberFromThread } from 'lib/shared/thread-utils.js';
 import { stringForUser } from 'lib/shared/user-utils.js';
 import type { ThreadInfo, RelativeMemberInfo } from 'lib/types/thread-types.js';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import ThreadSettingsMemberTooltipButton from './thread-settings-member-tooltip-button.react.js';
 import type { AppNavigationProp } from '../../navigation/app-navigator.react';
@@ -32,7 +29,7 @@
   route: TooltipRoute<'ThreadSettingsMemberTooltipModal'>,
 ) {
   const { memberInfo, threadInfo } = route.params;
-  const boundRemoveUsersFromThread = useServerCall(removeUsersFromThread);
+  const boundRemoveUsersFromThread = useRemoveUsersFromThread();
   const dispatchActionPromise = useDispatchActionPromise();
 
   const onConfirmRemoveUser = React.useCallback(
diff --git a/native/chat/toggle-pin-modal.react.js b/native/chat/toggle-pin-modal.react.js
--- a/native/chat/toggle-pin-modal.react.js
+++ b/native/chat/toggle-pin-modal.react.js
@@ -5,15 +5,12 @@
 import { Text, View } from 'react-native';
 
 import {
-  toggleMessagePin,
+  useToggleMessagePin,
   toggleMessagePinActionTypes,
-} from 'lib/actions/thread-actions.js';
+} from 'lib/actions/message-actions.js';
 import type { RawMessageInfo } from 'lib/types/message-types.js';
 import { type ThreadInfo } from 'lib/types/thread-types.js';
-import {
-  useServerCall,
-  useDispatchActionPromise,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import MessageResult from './message-result.react.js';
 import Button from '../components/button.react.js';
@@ -39,7 +36,7 @@
   const { messageInfo, isPinned } = item;
   const styles = useStyles(unboundStyles);
 
-  const callToggleMessagePin = useServerCall(toggleMessagePin);
+  const callToggleMessagePin = useToggleMessagePin();
   const dispatchActionPromise = useDispatchActionPromise();
 
   const modalInfo = React.useMemo(() => {
diff --git a/native/community-creation/community-configuration.react.js b/native/community-creation/community-configuration.react.js
--- a/native/community-creation/community-configuration.react.js
+++ b/native/community-creation/community-configuration.react.js
@@ -3,15 +3,15 @@
 import * as React from 'react';
 import { Text, View } from 'react-native';
 
-import { newThread, newThreadActionTypes } from 'lib/actions/thread-actions.js';
+import {
+  useNewThread,
+  newThreadActionTypes,
+} from 'lib/actions/thread-actions.js';
 import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
 import type { LoadingStatus } from 'lib/types/loading-types.js';
 import { threadTypes } from 'lib/types/thread-types-enum.js';
 import type { NewThreadResult } from 'lib/types/thread-types.js';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import CommunityCreationKeyserverLabel from './community-creation-keyserver-label.react.js';
 import type { CommunityCreationNavigationProp } from './community-creation-navigator.react.js';
@@ -50,7 +50,7 @@
 
   const dispatchActionPromise = useDispatchActionPromise();
 
-  const callNewThread = useServerCall(newThread);
+  const callNewThread = useNewThread();
   const calendarQueryFunc = useCalendarQuery();
 
   const createNewCommunityLoadingStatus: LoadingStatus = useSelector(
diff --git a/native/input/input-state-container.react.js b/native/input/input-state-container.react.js
--- a/native/input/input-state-container.react.js
+++ b/native/input/input-state-container.react.js
@@ -19,7 +19,7 @@
   SendTextMessageInput,
 } from 'lib/actions/message-actions.js';
 import { queueReportsActionType } from 'lib/actions/report-actions.js';
-import { newThread } from 'lib/actions/thread-actions.js';
+import { useNewThread } from 'lib/actions/thread-actions.js';
 import {
   uploadMultimedia,
   uploadMediaMetadata,
@@ -1697,7 +1697,7 @@
     const callBlobServiceUpload = useServerCall(blobServiceUpload);
     const callSendMultimediaMessage = useSendMultimediaMessage();
     const callSendTextMessage = useSendTextMessage();
-    const callNewThread = useServerCall(newThread);
+    const callNewThread = useNewThread();
     const dispatchActionPromise = useDispatchActionPromise();
     const dispatch = useDispatch();
     const mediaReportsEnabled = useIsReportEnabled('mediaReports');
diff --git a/native/navigation/invite-link-modal.react.js b/native/navigation/invite-link-modal.react.js
--- a/native/navigation/invite-link-modal.react.js
+++ b/native/navigation/invite-link-modal.react.js
@@ -5,15 +5,12 @@
 import { View, Text, ActivityIndicator } from 'react-native';
 
 import {
-  joinThread,
+  useJoinThread,
   joinThreadActionTypes,
 } from 'lib/actions/thread-actions.js';
 import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
 import type { InviteLinkVerificationResponse } from 'lib/types/link-types.js';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import { nonThreadCalendarQuery } from './nav-selectors.js';
 import { NavContext } from './navigation-context.js';
@@ -72,7 +69,7 @@
     styles.invitation,
   ]);
 
-  const callJoinThread = useServerCall(joinThread);
+  const callJoinThread = useJoinThread();
   const navContext = React.useContext(NavContext);
   const calendarQuery = useSelector(state =>
     nonThreadCalendarQuery({
diff --git a/native/roles/change-roles-header-right-button.react.js b/native/roles/change-roles-header-right-button.react.js
--- a/native/roles/change-roles-header-right-button.react.js
+++ b/native/roles/change-roles-header-right-button.react.js
@@ -7,13 +7,10 @@
 import { TouchableOpacity } from 'react-native-gesture-handler';
 
 import {
-  changeThreadMemberRoles,
+  useChangeThreadMemberRoles,
   changeThreadMemberRolesActionTypes,
 } from 'lib/actions/thread-actions.js';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import type { NavigationRoute } from '../navigation/route-names';
 import { useColors } from '../themes/colors.js';
@@ -31,7 +28,7 @@
   invariant(selectedRole, 'Expected selected role to be defined');
   const navigation = useNavigation();
 
-  const callChangeThreadMemberRoles = useServerCall(changeThreadMemberRoles);
+  const callChangeThreadMemberRoles = useChangeThreadMemberRoles();
   const dispatchActionPromise = useDispatchActionPromise();
 
   const { disabledButton, purpleLink } = useColors();
@@ -51,7 +48,11 @@
 
     dispatchActionPromise(
       changeThreadMemberRolesActionTypes,
-      callChangeThreadMemberRoles(threadInfo.id, [memberInfo.id], selectedRole),
+      callChangeThreadMemberRoles({
+        threadID: threadInfo.id,
+        memberIDs: [memberInfo.id],
+        newRole: selectedRole,
+      }),
     );
 
     navigation.goBack();
diff --git a/native/roles/create-roles-header-right-button.react.js b/native/roles/create-roles-header-right-button.react.js
--- a/native/roles/create-roles-header-right-button.react.js
+++ b/native/roles/create-roles-header-right-button.react.js
@@ -6,13 +6,10 @@
 import { TouchableOpacity, Text } from 'react-native';
 
 import {
-  modifyCommunityRole,
+  useModifyCommunityRole,
   modifyCommunityRoleActionTypes,
 } from 'lib/actions/thread-actions.js';
-import {
-  useServerCall,
-  useDispatchActionPromise,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 import { values } from 'lib/utils/objects.js';
 
 import type { NavigationRoute } from '../navigation/route-names';
@@ -30,7 +27,7 @@
   const navigation = useNavigation();
   const styles = useStyles(unboundStyles);
 
-  const callModifyCommunityRole = useServerCall(modifyCommunityRole);
+  const callModifyCommunityRole = useModifyCommunityRole();
   const dispatchActionPromise = useDispatchActionPromise();
 
   const threadRoleNames = React.useMemo(
diff --git a/native/roles/role-utils.react.js b/native/roles/role-utils.react.js
--- a/native/roles/role-utils.react.js
+++ b/native/roles/role-utils.react.js
@@ -3,14 +3,11 @@
 import * as React from 'react';
 
 import {
-  deleteCommunityRole,
+  useDeleteCommunityRole,
   deleteCommunityRoleActionTypes,
 } from 'lib/actions/thread-actions.js';
 import type { ThreadInfo } from 'lib/types/thread-types.js';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 import { constructRoleDeletionMessagePrompt } from 'lib/utils/role-utils.js';
 
 import Alert from '../utils/alert.js';
@@ -22,7 +19,7 @@
   memberCount: number,
 ): () => void {
   const defaultRoleName = threadInfo.roles[defaultRoleID].name;
-  const callDeleteCommunityRole = useServerCall(deleteCommunityRole);
+  const callDeleteCommunityRole = useDeleteCommunityRole();
   const dispatchActionPromise = useDispatchActionPromise();
 
   const onDeleteRole = React.useCallback(() => {
diff --git a/web/chat/chat-input-bar.react.js b/web/chat/chat-input-bar.react.js
--- a/web/chat/chat-input-bar.react.js
+++ b/web/chat/chat-input-bar.react.js
@@ -6,7 +6,7 @@
 
 import {
   joinThreadActionTypes,
-  joinThread,
+  useJoinThread,
   newThreadActionTypes,
 } from 'lib/actions/thread-actions.js';
 import SWMansionIcon from 'lib/components/SWMansionIcon.react.js';
@@ -45,7 +45,6 @@
 import { type UserInfos } from 'lib/types/user-types.js';
 import {
   type DispatchActionPromise,
-  useServerCall,
   useDispatchActionPromise,
 } from 'lib/utils/action-utils.js';
 
@@ -582,7 +581,7 @@
     const threadCreationInProgress = createThreadLoadingStatus === 'loading';
     const calendarQuery = useSelector(nonThreadCalendarQuery);
     const dispatchActionPromise = useDispatchActionPromise();
-    const callJoinThread = useServerCall(joinThread);
+    const callJoinThread = useJoinThread();
     const userSearchIndex = useSelector(userStoreMentionSearchIndex);
     const { getChatMentionSearchIndex } = useChatMentionContext();
     const chatMentionSearchIndex = getChatMentionSearchIndex(props.threadInfo);
diff --git a/web/chat/thread-menu.react.js b/web/chat/thread-menu.react.js
--- a/web/chat/thread-menu.react.js
+++ b/web/chat/thread-menu.react.js
@@ -3,7 +3,7 @@
 import * as React from 'react';
 
 import {
-  leaveThread,
+  useLeaveThread,
   leaveThreadActionTypes,
 } from 'lib/actions/thread-actions.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
@@ -18,10 +18,7 @@
 import { threadPermissions } from 'lib/types/thread-permission-types.js';
 import { threadTypes } from 'lib/types/thread-types-enum.js';
 import { type ThreadInfo } from 'lib/types/thread-types.js';
-import {
-  useServerCall,
-  useDispatchActionPromise,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import css from './thread-menu.css';
 import MenuItem from '../components/menu-item.react.js';
@@ -195,12 +192,12 @@
   }, [canCreateSubchannels, onClickCreateSubchannel]);
 
   const dispatchActionPromise = useDispatchActionPromise();
-  const callLeaveThread = useServerCall(leaveThread);
+  const callLeaveThread = useLeaveThread();
 
   const onConfirmLeaveThread = React.useCallback(() => {
     dispatchActionPromise(
       leaveThreadActionTypes,
-      callLeaveThread(threadInfo.id),
+      callLeaveThread({ threadID: threadInfo.id }),
     );
     popModal();
   }, [callLeaveThread, popModal, dispatchActionPromise, threadInfo.id]);
diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js
--- a/web/input/input-state-container.react.js
+++ b/web/input/input-state-container.react.js
@@ -23,7 +23,7 @@
   SendTextMessageInput,
 } from 'lib/actions/message-actions.js';
 import { queueReportsActionType } from 'lib/actions/report-actions.js';
-import { newThread } from 'lib/actions/thread-actions.js';
+import { useNewThread } from 'lib/actions/thread-actions.js';
 import {
   uploadMultimedia,
   uploadMediaMetadata,
@@ -1616,7 +1616,7 @@
     const callDeleteUpload = useServerCall(deleteUpload);
     const callSendMultimediaMessage = useLegacySendMultimediaMessage();
     const callSendTextMessage = useSendTextMessage();
-    const callNewThread = useServerCall(newThread);
+    const callNewThread = useNewThread();
     const dispatch = useDispatch();
     const dispatchActionPromise = useDispatchActionPromise();
     const modalContext = useModalContext();
diff --git a/web/invite-links/accept-invite-modal.react.js b/web/invite-links/accept-invite-modal.react.js
--- a/web/invite-links/accept-invite-modal.react.js
+++ b/web/invite-links/accept-invite-modal.react.js
@@ -4,17 +4,14 @@
 import * as React from 'react';
 
 import {
-  joinThread,
+  useJoinThread,
   joinThreadActionTypes,
 } from 'lib/actions/thread-actions.js';
 import ModalOverlay from 'lib/components/modal-overlay.react.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
 import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
 import { type InviteLinkVerificationResponse } from 'lib/types/link-types.js';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import css from './accept-invite-modal.css';
 import Button, { buttonThemes } from '../components/button.react.js';
@@ -39,7 +36,7 @@
     }
   }, [popModal, verificationResponse.status]);
 
-  const callJoinThread = useServerCall(joinThread);
+  const callJoinThread = useJoinThread();
   const calendarQuery = useSelector(nonThreadCalendarQuery);
   const communityID = verificationResponse.community?.id;
   const createJoinCommunityAction = React.useCallback(async () => {
diff --git a/web/modals/chat/toggle-pin-modal.react.js b/web/modals/chat/toggle-pin-modal.react.js
--- a/web/modals/chat/toggle-pin-modal.react.js
+++ b/web/modals/chat/toggle-pin-modal.react.js
@@ -4,18 +4,15 @@
 import * as React from 'react';
 
 import {
-  toggleMessagePin,
+  useToggleMessagePin,
   toggleMessagePinActionTypes,
-} from 'lib/actions/thread-actions.js';
+} from 'lib/actions/message-actions.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
 import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js';
 import { modifyItemForResultScreen } from 'lib/shared/message-utils.js';
 import type { RawMessageInfo } from 'lib/types/message-types.js';
 import type { ThreadInfo } from 'lib/types/thread-types.js';
-import {
-  useServerCall,
-  useDispatchActionPromise,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import css from './toggle-pin-modal.css';
 import Button, { buttonThemes } from '../../components/button.react.js';
@@ -32,7 +29,7 @@
   const { messageInfo, isPinned } = item;
   const { popModal } = useModalContext();
 
-  const callToggleMessagePin = useServerCall(toggleMessagePin);
+  const callToggleMessagePin = useToggleMessagePin();
   const dispatchActionPromise = useDispatchActionPromise();
 
   const modalInfo = React.useMemo(() => {
diff --git a/web/modals/threads/create/compose-subchannel-modal.react.js b/web/modals/threads/create/compose-subchannel-modal.react.js
--- a/web/modals/threads/create/compose-subchannel-modal.react.js
+++ b/web/modals/threads/create/compose-subchannel-modal.react.js
@@ -2,14 +2,14 @@
 import * as React from 'react';
 import { useDispatch, useSelector } from 'react-redux';
 
-import { newThread, newThreadActionTypes } from 'lib/actions/thread-actions.js';
+import {
+  useNewThread,
+  newThreadActionTypes,
+} from 'lib/actions/thread-actions.js';
 import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
 import { threadTypes } from 'lib/types/thread-types-enum.js';
 import type { ThreadInfo } from 'lib/types/thread-types.js';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js';
 import { trimText } from 'lib/utils/text-utils.js';
 
@@ -78,7 +78,7 @@
   const [errorMessage, setErrorMessage] = React.useState<string>('');
 
   const calendarQuery = useSelector(nonThreadCalendarQuery);
-  const callNewThread = useServerCall(newThread);
+  const callNewThread = useNewThread();
 
   const dispatchActionPromise = useDispatchActionPromise();
   const dispatch = useDispatch();
diff --git a/web/modals/threads/gallery/thread-settings-media-gallery.react.js b/web/modals/threads/gallery/thread-settings-media-gallery.react.js
--- a/web/modals/threads/gallery/thread-settings-media-gallery.react.js
+++ b/web/modals/threads/gallery/thread-settings-media-gallery.react.js
@@ -2,7 +2,7 @@
 
 import * as React from 'react';
 
-import { fetchThreadMedia } from 'lib/actions/thread-actions.js';
+import { useFetchThreadMedia } from 'lib/actions/thread-actions.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
 import {
   encryptedMediaBlobURI,
@@ -10,7 +10,6 @@
 } from 'lib/media/media-utils.js';
 import type { Media } from 'lib/types/media-types.js';
 import type { ThreadInfo } from 'lib/types/thread-types.js';
-import { useServerCall } from 'lib/utils/action-utils.js';
 
 import GalleryItem from './thread-settings-media-gallery-item.react.js';
 import css from './thread-settings-media-gallery.css';
@@ -35,7 +34,7 @@
   const { id: threadID } = parentThreadInfo;
   const modalName = 'Media';
 
-  const callFetchThreadMedia = useServerCall(fetchThreadMedia);
+  const callFetchThreadMedia = useFetchThreadMedia();
   const [mediaInfos, setMediaInfos] = React.useState([]);
   const [tab, setTab] = React.useState<MediaGalleryTab>(activeTab);
 
diff --git a/web/modals/threads/members/change-member-role-modal.react.js b/web/modals/threads/members/change-member-role-modal.react.js
--- a/web/modals/threads/members/change-member-role-modal.react.js
+++ b/web/modals/threads/members/change-member-role-modal.react.js
@@ -4,7 +4,7 @@
 import * as React from 'react';
 
 import {
-  changeThreadMemberRoles,
+  useChangeThreadMemberRoles,
   changeThreadMemberRolesActionTypes,
 } from 'lib/actions/thread-actions.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
@@ -12,10 +12,7 @@
 import { otherUsersButNoOtherAdmins } from 'lib/selectors/thread-selectors.js';
 import { roleIsAdminRole } from 'lib/shared/thread-utils.js';
 import type { RelativeMemberInfo, ThreadInfo } from 'lib/types/thread-types';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 import { values } from 'lib/utils/objects.js';
 
 import css from './change-member-role-modal.css';
@@ -35,7 +32,7 @@
   const { memberInfo, threadInfo } = props;
   const { pushModal, popModal } = useModalContext();
   const dispatchActionPromise = useDispatchActionPromise();
-  const callChangeThreadMemberRoles = useServerCall(changeThreadMemberRoles);
+  const callChangeThreadMemberRoles = useChangeThreadMemberRoles();
   const otherUsersButNoOtherAdminsValue = useSelector(
     otherUsersButNoOtherAdmins(threadInfo.id),
   );
@@ -89,7 +86,11 @@
     }
 
     const createChangeThreadMemberRolesPromise = () =>
-      callChangeThreadMemberRoles(threadInfo.id, [memberInfo.id], selectedRole);
+      callChangeThreadMemberRoles({
+        threadID: threadInfo.id,
+        memberIDs: [memberInfo.id],
+        newRole: selectedRole,
+      });
 
     dispatchActionPromise(
       changeThreadMemberRolesActionTypes,
diff --git a/web/modals/threads/members/member.react.js b/web/modals/threads/members/member.react.js
--- a/web/modals/threads/members/member.react.js
+++ b/web/modals/threads/members/member.react.js
@@ -2,7 +2,7 @@
 
 import * as React from 'react';
 
-import { removeUsersFromThread } from 'lib/actions/thread-actions.js';
+import { useRemoveUsersFromThread } from 'lib/actions/thread-actions.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
 import SWMansionIcon from 'lib/components/SWMansionIcon.react.js';
 import {
@@ -15,10 +15,7 @@
   type RelativeMemberInfo,
   type ThreadInfo,
 } from 'lib/types/thread-types.js';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 import { useRolesFromCommunityThreadInfo } from 'lib/utils/role-utils.js';
 
 import ChangeMemberRoleModal from './change-member-role-modal.react.js';
@@ -58,7 +55,7 @@
   );
 
   const dispatchActionPromise = useDispatchActionPromise();
-  const boundRemoveUsersFromThread = useServerCall(removeUsersFromThread);
+  const boundRemoveUsersFromThread = useRemoveUsersFromThread();
 
   const onClickRemoveUser = React.useCallback(
     () =>
diff --git a/web/roles/create-roles-modal.react.js b/web/roles/create-roles-modal.react.js
--- a/web/roles/create-roles-modal.react.js
+++ b/web/roles/create-roles-modal.react.js
@@ -5,7 +5,7 @@
 import * as React from 'react';
 
 import {
-  modifyCommunityRole,
+  useModifyCommunityRole,
   modifyCommunityRoleActionTypes,
 } from 'lib/actions/thread-actions.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
@@ -19,10 +19,7 @@
   ThreadInfo,
   RoleModificationRequest,
 } from 'lib/types/thread-types.js';
-import {
-  useServerCall,
-  useDispatchActionPromise,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 import { values } from 'lib/utils/objects.js';
 import { useFilterPermissionOptionsByThreadType } from 'lib/utils/role-utils.js';
 
@@ -55,7 +52,7 @@
     props;
   const modalName = action === 'create_role' ? 'Create role' : 'Edit role';
 
-  const callModifyCommunityRole = useServerCall(modifyCommunityRole);
+  const callModifyCommunityRole = useModifyCommunityRole();
   const dispatchActionPromise = useDispatchActionPromise();
 
   const createRolesLoadingStatus: LoadingStatus = useSelector(
diff --git a/web/roles/delete-role-modal.react.js b/web/roles/delete-role-modal.react.js
--- a/web/roles/delete-role-modal.react.js
+++ b/web/roles/delete-role-modal.react.js
@@ -3,7 +3,7 @@
 import * as React from 'react';
 
 import {
-  deleteCommunityRole,
+  useDeleteCommunityRole,
   deleteCommunityRoleActionTypes,
 } from 'lib/actions/thread-actions.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
@@ -11,10 +11,7 @@
 import { useRoleMemberCountsForCommunity } from 'lib/shared/thread-utils.js';
 import type { LoadingStatus } from 'lib/types/loading-types.js';
 import type { ThreadInfo } from 'lib/types/thread-types.js';
-import {
-  useServerCall,
-  useDispatchActionPromise,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 import { constructRoleDeletionMessagePrompt } from 'lib/utils/role-utils.js';
 
 import css from './delete-role-modal.css';
@@ -37,7 +34,7 @@
   const { threadInfo, defaultRoleID, roleID } = props;
   const { popModal } = useModalContext();
 
-  const callDeleteCommunityRole = useServerCall(deleteCommunityRole);
+  const callDeleteCommunityRole = useDeleteCommunityRole();
   const dispatchActionPromise = useDispatchActionPromise();
 
   const deleteRoleLoadingStatus: LoadingStatus = useSelector(
diff --git a/web/sidebar/community-creation/community-creation-modal.react.js b/web/sidebar/community-creation/community-creation-modal.react.js
--- a/web/sidebar/community-creation/community-creation-modal.react.js
+++ b/web/sidebar/community-creation/community-creation-modal.react.js
@@ -3,16 +3,16 @@
 import * as React from 'react';
 import { useDispatch } from 'react-redux';
 
-import { newThread, newThreadActionTypes } from 'lib/actions/thread-actions.js';
+import {
+  useNewThread,
+  newThreadActionTypes,
+} from 'lib/actions/thread-actions.js';
 import { useModalContext } from 'lib/components/modal-provider.react.js';
 import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
 import type { LoadingStatus } from 'lib/types/loading-types.js';
 import { threadTypes } from 'lib/types/thread-types-enum.js';
 import type { NewThreadResult } from 'lib/types/thread-types.js';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils.js';
+import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
 
 import CommunityCreationKeyserverLabel from './community-creation-keyserver-label.react.js';
 import CommunityCreationMembersModal from './community-creation-members-modal.react.js';
@@ -48,7 +48,7 @@
   const dispatch = useDispatch();
   const dispatchActionPromise = useDispatchActionPromise();
 
-  const callNewThread = useServerCall(newThread);
+  const callNewThread = useNewThread();
   const calendarQueryFunc = useSelector(nonThreadCalendarQuery);
 
   const [errorMessage, setErrorMessage] = React.useState<?string>();