diff --git a/keyserver/src/creators/account-creator.js b/keyserver/src/creators/account-creator.js
--- a/keyserver/src/creators/account-creator.js
+++ b/keyserver/src/creators/account-creator.js
@@ -94,9 +94,9 @@
     ? request.deviceTokenUpdateRequest.deviceToken
     : viewer.deviceToken;
   const [id] = await createIDs('users', 1);
-  const newUserRow = [id, request.username, hash, time];
+  const newUserRow = [id, request.username, hash, time, request.address];
   const newUserQuery = SQL`
-    INSERT INTO users(id, username, hash, creation_time)
+    INSERT INTO users(id, username, hash, creation_time, ethereum_address)
     VALUES ${[newUserRow]}
   `;
   const [userViewerData] = await Promise.all([
diff --git a/keyserver/src/endpoints.js b/keyserver/src/endpoints.js
--- a/keyserver/src/endpoints.js
+++ b/keyserver/src/endpoints.js
@@ -51,6 +51,7 @@
   logInResponder,
   oldPasswordUpdateResponder,
   updateUserSettingsResponder,
+  siweResponder,
 } from './responders/user-responders';
 import { codeVerificationResponder } from './responders/verification-responders';
 import { uploadDeletionResponder } from './uploads/uploads';
@@ -84,6 +85,7 @@
   send_password_reset_email: sendPasswordResetEmailResponder,
   send_verification_email: sendVerificationEmailResponder,
   set_thread_unread_status: threadSetUnreadStatusResponder,
+  siwe: siweResponder,
   update_account: passwordUpdateResponder,
   update_activity: updateActivityResponder,
   update_calendar_query: calendarQueryUpdateResponder,
diff --git a/keyserver/src/responders/user-responders.js b/keyserver/src/responders/user-responders.js
--- a/keyserver/src/responders/user-responders.js
+++ b/keyserver/src/responders/user-responders.js
@@ -1,6 +1,7 @@
 // @flow
 
 import invariant from 'invariant';
+import { SiweMessage, ErrorTypes } from 'siwe';
 import t from 'tcomb';
 import bcrypt from 'twin-bcrypt';
 
@@ -13,6 +14,8 @@
   RegisterRequest,
   LogInResponse,
   LogInRequest,
+  SIWERequest,
+  SIWEResponse,
   UpdatePasswordRequest,
   UpdateUserSettingsRequest,
 } from 'lib/types/account-types';
@@ -193,55 +196,18 @@
   source: t.maybe(t.enums.of(values(logInActionSources))),
 });
 
-async function logInResponder(
-  viewer: Viewer,
-  input: any,
-): Promise<LogInResponse> {
-  await validateInput(viewer, logInRequestInputValidator, input);
-  const request: LogInRequest = input;
-
+async function logInQueries(viewer: Viewer, input: any, userId: string) {
+  const request: LogInRequest | SIWERequest = input;
   const calendarQuery = request.calendarQuery
     ? normalizeCalendarQuery(request.calendarQuery)
     : null;
-  const promises = {};
-  if (calendarQuery) {
-    promises.verifyCalendarQueryThreadIDs = verifyCalendarQueryThreadIDs(
-      calendarQuery,
-    );
-  }
-  const username = request.username ?? request.usernameOrEmail;
-  if (!username) {
-    throw new ServerError('invalid_parameters');
-  }
-  const userQuery = SQL`
-    SELECT id, hash, username
-    FROM users
-    WHERE LCASE(username) = LCASE(${username})
-  `;
-  promises.userQuery = dbQuery(userQuery);
-  const {
-    userQuery: [userResult],
-  } = await promiseAll(promises);
-
-  if (userResult.length === 0) {
-    throw new ServerError('invalid_parameters');
-  }
-  const userRow = userResult[0];
-  if (!userRow.hash || !bcrypt.compareSync(request.password, userRow.hash)) {
-    if (hasMinCodeVersion(viewer.platformDetails, 99999)) {
-      throw new ServerError('invalid_parameters');
-    } else {
-      throw new ServerError('invalid_credentials');
-    }
-  }
-  const id = userRow.id.toString();
 
   const newServerTime = Date.now();
   const deviceToken = request.deviceTokenUpdateRequest
     ? request.deviceTokenUpdateRequest.deviceToken
     : viewer.deviceToken;
   const [userViewerData] = await Promise.all([
-    createNewUserCookie(id, {
+    createNewUserCookie(userId, {
       platformDetails: request.platformDetails,
       deviceToken,
     }),
@@ -290,6 +256,116 @@
   return response;
 }
 
+async function logInResponder(
+  viewer: Viewer,
+  input: any,
+): Promise<LogInResponse> {
+  await validateInput(viewer, logInRequestInputValidator, input);
+  const request: LogInRequest = input;
+
+  const calendarQuery = request.calendarQuery
+    ? normalizeCalendarQuery(request.calendarQuery)
+    : null;
+  const promises = {};
+  if (calendarQuery) {
+    promises.verifyCalendarQueryThreadIDs = verifyCalendarQueryThreadIDs(
+      calendarQuery,
+    );
+  }
+  const username = request.username ?? request.usernameOrEmail;
+  if (!username) {
+    throw new ServerError('invalid_parameters');
+  }
+  const userQuery = SQL`
+    SELECT id, hash, username
+    FROM users
+    WHERE LCASE(username) = LCASE(${username})
+  `;
+  promises.userQuery = dbQuery(userQuery);
+  const {
+    userQuery: [userResult],
+  } = await promiseAll(promises);
+
+  if (userResult.length === 0) {
+    throw new ServerError('invalid_parameters');
+  }
+  const userRow = userResult[0];
+  if (!userRow.hash || !bcrypt.compareSync(request.password, userRow.hash)) {
+    if (hasMinCodeVersion(viewer.platformDetails, 99999)) {
+      throw new ServerError('invalid_parameters');
+    } else {
+      throw new ServerError('invalid_credentials');
+    }
+  }
+  const id = userRow.id.toString();
+
+  return await logInQueries(viewer, input, id);
+}
+
+const siweRequestInputValidator = tShape({
+  address: t.String,
+  signature: t.String,
+  message: t.String,
+  watchedIDs: t.list(t.String),
+  calendarQuery: t.maybe(entryQueryInputValidator),
+  deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator),
+  platformDetails: tPlatformDetails,
+  source: t.maybe(t.enums.of(values(logInActionSources))),
+});
+
+async function siweResponder(
+  viewer: Viewer,
+  input: any,
+): Promise<SIWEResponse> {
+  await validateInput(viewer, siweRequestInputValidator, input);
+  const request: SIWERequest = input;
+
+  const { address, message, signature } = request;
+  if (!address) {
+    throw new ServerError('invalid_parameters');
+  }
+
+  try {
+    const siweMessage = new SiweMessage(message);
+    await siweMessage.validate(signature);
+  } catch (error) {
+    switch (error) {
+      case ErrorTypes.EXPIRED_MESSAGE:
+        throw new ServerError('expired_signature', { status: 440 });
+      case ErrorTypes.INVALID_SIGNATURE:
+        throw new ServerError('invalid_signature', { status: 422 });
+      default:
+        throw new ServerError('oops', { status: 500 });
+    }
+  }
+  // addresses are case insensitive to the network but not to sql queries
+  // lowercasing just in case
+  const userQuery = SQL`
+    SELECT id, hash, username
+    FROM users
+    WHERE LCASE(ethereum_address) = LCASE(${address})
+  `;
+  const [userResult] = await dbQuery(userQuery);
+  if (userResult.length === 0) {
+    // broke out vars for flow's sake - no thread IDs to watch on a new account
+    const {
+      message: noop,
+      signature: noop2,
+      watchedIDs: noop3,
+      ...rest
+    } = request;
+    return await createAccount(viewer, {
+      username: address,
+      password: signature,
+      ...rest,
+    });
+  }
+  const userRow = userResult[0];
+  const id = userRow.id.toString();
+
+  return await logInQueries(viewer, input, id);
+}
+
 const updatePasswordRequestInputValidator = tShape({
   code: t.String,
   password: tPassword,
@@ -339,4 +415,5 @@
   logInResponder,
   oldPasswordUpdateResponder,
   updateUserSettingsResponder,
+  siweResponder,
 };
diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js
--- a/lib/actions/user-actions.js
+++ b/lib/actions/user-actions.js
@@ -7,6 +7,8 @@
   LogInResult,
   RegisterResult,
   RegisterInfo,
+  SIWEServerCall,
+  SIWEResult,
   UpdateUserSettingsRequest,
 } from '../types/account-types';
 import type { GetSessionPublicKeysArgs } from '../types/request-types';
@@ -91,6 +93,33 @@
   };
 };
 
+const siweActionTypes = Object.freeze({
+  started: 'SIWE_STARTED',
+  success: 'SIWE_SUCCESS',
+  failed: 'SIWE_FAILED',
+});
+const siwe = (
+  callServerEndpoint: CallServerEndpoint,
+): ((siweInfo: SIWEServerCall) => Promise<SIWEResult>) => async siweInfo => {
+  const watchedIDs = threadWatcher.getWatchedIDs();
+  const response = await callServerEndpoint(
+    'siwe',
+    {
+      ...siweInfo,
+      watchedIDs,
+      platformDetails: getConfig().platformDetails,
+    },
+    registerCallServerEndpointOptions,
+  );
+  return {
+    currentUserInfo: response.currentUserInfo,
+    rawMessageInfos: response.rawMessageInfos,
+    threadInfos: response.cookieChange.threadInfos,
+    userInfos: response.cookieChange.userInfos,
+    calendarQuery: siweInfo.calendarQuery,
+  };
+};
+
 function mergeUserInfos(...userInfoArrays: UserInfo[][]): UserInfo[] {
   const merged = {};
   for (const userInfoArray of userInfoArrays) {
@@ -238,6 +267,8 @@
   searchUsersActionTypes,
   setUserSettings,
   setUserSettingsActionTypes,
+  siwe,
+  siweActionTypes,
   updateSubscription,
   updateSubscriptionActionTypes,
 };
diff --git a/lib/reducers/data-loaded-reducer.js b/lib/reducers/data-loaded-reducer.js
--- a/lib/reducers/data-loaded-reducer.js
+++ b/lib/reducers/data-loaded-reducer.js
@@ -5,6 +5,7 @@
   deleteAccountActionTypes,
   logInActionTypes,
   registerActionTypes,
+  siweActionTypes,
 } from '../actions/user-actions';
 import type { BaseAction } from '../types/redux-types';
 import { setNewSessionActionType } from '../utils/action-utils';
@@ -15,7 +16,8 @@
 ): boolean {
   if (
     action.type === logInActionTypes.success ||
-    action.type === registerActionTypes.success
+    action.type === registerActionTypes.success ||
+    action.type === siweActionTypes.success
   ) {
     return true;
   } else if (
diff --git a/lib/reducers/user-reducer.js b/lib/reducers/user-reducer.js
--- a/lib/reducers/user-reducer.js
+++ b/lib/reducers/user-reducer.js
@@ -13,6 +13,7 @@
   logInActionTypes,
   registerActionTypes,
   setUserSettingsActionTypes,
+  siweActionTypes,
 } from '../actions/user-actions';
 import type { BaseAction } from '../types/redux-types';
 import {
@@ -45,6 +46,7 @@
   if (
     action.type === logInActionTypes.success ||
     action.type === registerActionTypes.success ||
+    action.type === siweActionTypes.success ||
     action.type === logOutActionTypes.success ||
     action.type === deleteAccountActionTypes.success
   ) {
diff --git a/lib/types/account-types.js b/lib/types/account-types.js
--- a/lib/types/account-types.js
+++ b/lib/types/account-types.js
@@ -50,6 +50,7 @@
   +calendarQuery?: ?CalendarQuery,
   +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest,
   +platformDetails: PlatformDetails,
+  +address?: ?string,
 };
 
 export type RegisterResponse = {
@@ -140,6 +141,26 @@
   +logInActionSource: LogInActionSource,
 };
 
+export type SIWERequest = {
+  +address: string,
+  +message: string,
+  +signature: string,
+  +calendarQuery?: ?CalendarQuery,
+  +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest,
+  +platformDetails: PlatformDetails,
+  +watchedIDs: $ReadOnlyArray<string>,
+};
+
+export type SIWEServerCall = {
+  +address: string,
+  +message: string,
+  +signature: string,
+  ...LogInExtraInfo,
+};
+
+export type SIWEResponse = RegisterResponse | LogInResponse;
+
+export type SIWEResult = RegisterResult | LogInResult;
 export type UpdatePasswordRequest = {
   code: string,
   password: string,
diff --git a/lib/types/endpoints.js b/lib/types/endpoints.js
--- a/lib/types/endpoints.js
+++ b/lib/types/endpoints.js
@@ -24,6 +24,7 @@
   CREATE_ACCOUNT: 'create_account',
   LOG_IN: 'log_in',
   UPDATE_PASSWORD: 'update_password',
+  SIWE: 'siwe',
 });
 type SessionChangingEndpoint = $Values<typeof sessionChangingEndpoints>;
 
diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js
--- a/lib/types/redux-types.js
+++ b/lib/types/redux-types.js
@@ -7,6 +7,7 @@
   LogInResult,
   RegisterResult,
   DefaultNotificationPayload,
+  SIWEResult,
 } from './account-types';
 import type {
   ActivityUpdateSuccessPayload,
@@ -818,6 +819,22 @@
       +error: true,
       +payload: Error,
       +loadingInfo: LoadingInfo,
+    }
+  | {
+      +type: 'SIWE_STARTED',
+      +payload?: void,
+      +loadingInfo: LoadingInfo,
+    }
+  | {
+      +type: 'SIWE_SUCCESS',
+      +payload: SIWEResult,
+      +loadingInfo: LoadingInfo,
+    }
+  | {
+      +type: 'SIWE_FAILED',
+      +error: true,
+      +payload: Error,
+      +loadingInfo: LoadingInfo,
     };
 
 export type ActionPayload = ?(Object | Array<*> | $ReadOnlyArray<*> | string);
diff --git a/native/account/logged-out-modal.react.js b/native/account/logged-out-modal.react.js
--- a/native/account/logged-out-modal.react.js
+++ b/native/account/logged-out-modal.react.js
@@ -320,12 +320,14 @@
     const promptButtonsSize = Platform.OS === 'ios' ? 40 : 61;
     const logInContainerSize = 140;
     const registerPanelSize = Platform.OS === 'ios' ? 181 : 180;
+    const siwePanelSize = 250;
 
     const containerSize = add(
       headerHeight,
       cond(not(isPastPrompt(this.modeValue)), promptButtonsSize, 0),
       cond(eq(this.modeValue, modeNumbers['log-in']), logInContainerSize, 0),
       cond(eq(this.modeValue, modeNumbers['register']), registerPanelSize, 0),
+      cond(eq(this.modeValue, modeNumbers['siwe']), siwePanelSize, 0),
     );
     const potentialPanelPaddingTop = divide(
       max(sub(this.contentHeight, this.keyboardHeightValue, containerSize), 0),
diff --git a/native/account/siwe-panel.react.js b/native/account/siwe-panel.react.js
--- a/native/account/siwe-panel.react.js
+++ b/native/account/siwe-panel.react.js
@@ -3,11 +3,11 @@
 import Animated from 'react-native-reanimated';
 import WebView from 'react-native-webview';
 
-import { registerActionTypes, register } from 'lib/actions/user-actions';
+import { siweActionTypes, siwe } from 'lib/actions/user-actions';
 import type {
-  RegisterInfo,
+  SIWEServerCall,
+  SIWEResult,
   LogInExtraInfo,
-  RegisterResult,
   LogInStartingPayload,
 } from 'lib/types/account-types';
 import {
@@ -20,7 +20,6 @@
 import { useSelector } from '../redux/redux-utils';
 import { nativeLogInExtraInfoSelector } from '../selectors/account-selectors';
 import { defaultLandingURLPrefix } from '../utils/url-utils';
-import { setNativeCredentials } from './native-credentials';
 
 const commSIWE = `${defaultLandingURLPrefix}/siwe`;
 
@@ -35,40 +34,40 @@
   // Redux dispatch functions
   +dispatchActionPromise: DispatchActionPromise,
   // async functions that hit server APIs
-  +registerAction: (registerInfo: RegisterInfo) => Promise<RegisterResult>,
+  +siweAction: (siweInfo: SIWEServerCall) => Promise<SIWEResult>,
 };
 
 function SIWEPanel({
   logInExtraInfo,
   dispatchActionPromise,
-  registerAction,
+  siweAction,
 }: Props) {
   const handleSIWE = React.useCallback(
-    ({ address, signature }) => {
+    ({ address, message, signature }) => {
       // this is all mocked from register-panel
       const extraInfo = logInExtraInfo();
       dispatchActionPromise(
-        registerActionTypes,
-        registerAction({
-          username: address,
-          password: signature,
+        siweActionTypes,
+        siweAction({
+          address,
+          message,
+          signature,
           ...extraInfo,
         }),
         undefined,
         ({ calendarQuery: extraInfo.calendarQuery }: LogInStartingPayload),
       );
-      setNativeCredentials({ username: address, password: signature });
     },
-    [logInExtraInfo, dispatchActionPromise, registerAction],
+    [logInExtraInfo, dispatchActionPromise, siweAction],
   );
   const handleMessage = React.useCallback(
     event => {
       const {
         nativeEvent: { data },
       } = event;
-      const { address, signature } = JSON.parse(data);
+      const { address, message, signature } = JSON.parse(data);
       if (address && signature) {
-        handleSIWE({ address, signature });
+        handleSIWE({ address, message, signature });
       }
     },
     [handleSIWE],
@@ -87,14 +86,14 @@
     );
 
     const dispatchActionPromise = useDispatchActionPromise();
-    const callRegister = useServerCall(register);
+    const callSiwe = useServerCall(siwe);
 
     return (
       <SIWEPanel
         {...props}
         logInExtraInfo={logInExtraInfo}
         dispatchActionPromise={dispatchActionPromise}
-        registerAction={callRegister}
+        siweAction={callSiwe}
       />
     );
   },
diff --git a/native/utils/url-utils.js b/native/utils/url-utils.js
--- a/native/utils/url-utils.js
+++ b/native/utils/url-utils.js
@@ -40,7 +40,13 @@
   return getDevNodeServerURLFromHostname(hostname);
 }
 
+// the wallet SIWE prompt hangs indefinitely if it doesn't originate from HTTPs
+const canRainbowKitSignOverHTTPYet = false;
+
 function getDevLandingURL(): string {
+  if (!canRainbowKitSignOverHTTPYet) {
+    return productionLandingURL;
+  }
   invariant(__DEV__, 'getDevLandingURL called from production');
   const hostname = getDevServerHostname();
   return getDevLandingURLFromHostname(hostname);