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
@@ -60,6 +60,7 @@
 import type { LogInState } from './log-in-panel.react';
 import RegisterPanel from './register-panel.react';
 import type { RegisterState } from './register-panel.react';
+import SIWEPanel from './siwe-panel.react';
 
 let initialAppLoad = true;
 const safeAreaEdges = ['top', 'bottom'];
@@ -88,12 +89,13 @@
 } = Animated;
 /* eslint-enable import/no-named-as-default-member */
 
-type LoggedOutMode = 'loading' | 'prompt' | 'log-in' | 'register';
+type LoggedOutMode = 'loading' | 'prompt' | 'log-in' | 'register' | 'siwe';
 const modeNumbers: { [LoggedOutMode]: number } = {
   'loading': 0,
   'prompt': 1,
   'log-in': 2,
   'register': 3,
+  'siwe': 4,
 };
 function isPastPrompt(modeValue: Node) {
   return and(
@@ -452,7 +454,26 @@
   render() {
     let panel = null;
     let buttons = null;
-    if (this.state.mode === 'log-in') {
+    let siweButton = null;
+    if (__DEV__) {
+      siweButton = (
+        <TouchableOpacity
+          onPress={this.onPressSIWE}
+          style={styles.button}
+          activeOpacity={0.6}
+        >
+          <Text style={styles.buttonText}>SIWE</Text>
+        </TouchableOpacity>
+      );
+    }
+    if (this.state.mode === 'siwe') {
+      panel = (
+        <SIWEPanel
+          setActiveAlert={this.setActiveAlert}
+          opacityValue={this.panelOpacityValue}
+        />
+      );
+    } else if (this.state.mode === 'log-in') {
       panel = (
         <LogInPanel
           setActiveAlert={this.setActiveAlert}
@@ -472,6 +493,7 @@
       const opacityStyle = { opacity: this.buttonOpacity };
       buttons = (
         <Animated.View style={[styles.buttonContainer, opacityStyle]}>
+          {siweButton}
           <TouchableOpacity
             onPress={this.onPressLogIn}
             style={styles.button}
@@ -537,6 +559,10 @@
     );
   }
 
+  onPressSIWE = () => {
+    this.setMode('siwe');
+  };
+
   onPressLogIn = () => {
     if (Platform.OS !== 'ios') {
       // For some strange reason, iOS's password management logic doesn't
diff --git a/native/account/siwe-panel.react.js b/native/account/siwe-panel.react.js
new file mode 100644
--- /dev/null
+++ b/native/account/siwe-panel.react.js
@@ -0,0 +1,103 @@
+// @flow
+import * as React from 'react';
+import Animated from 'react-native-reanimated';
+import WebView from 'react-native-webview';
+
+import { registerActionTypes, register } from 'lib/actions/user-actions';
+import type {
+  RegisterInfo,
+  LogInExtraInfo,
+  RegisterResult,
+  LogInStartingPayload,
+} from 'lib/types/account-types';
+import {
+  useServerCall,
+  useDispatchActionPromise,
+  type DispatchActionPromise,
+} from 'lib/utils/action-utils';
+
+import { NavContext } from '../navigation/navigation-context';
+import { useSelector } from '../redux/redux-utils';
+import { nativeLogInExtraInfoSelector } from '../selectors/account-selectors';
+import { setNativeCredentials } from './native-credentials';
+const commSIWE = __DEV__
+  ? 'http://localhost/commlanding/siwe'
+  : 'https://comm.app/siwe';
+
+type BaseProps = {
+  +setActiveAlert: (activeAlert: boolean) => void,
+  +opacityValue: Animated.Node,
+};
+type Props = {
+  ...BaseProps,
+  // Redux state
+  +logInExtraInfo: () => LogInExtraInfo,
+  // Redux dispatch functions
+  +dispatchActionPromise: DispatchActionPromise,
+  // async functions that hit server APIs
+  +registerAction: (registerInfo: RegisterInfo) => Promise<RegisterResult>,
+};
+
+function SIWEPanel({
+  logInExtraInfo,
+  dispatchActionPromise,
+  registerAction,
+}: Props) {
+  const handleSIWE = React.useCallback(
+    ({ address, signature }) => {
+      // this is all mocked from register-panel
+      const extraInfo = logInExtraInfo();
+      dispatchActionPromise(
+        registerActionTypes,
+        registerAction({
+          username: address,
+          password: signature,
+          ...extraInfo,
+        }),
+        undefined,
+        ({ calendarQuery: extraInfo.calendarQuery }: LogInStartingPayload),
+      );
+      setNativeCredentials({ username: address, password: signature });
+    },
+    [logInExtraInfo, dispatchActionPromise, registerAction],
+  );
+  const handleMessage = React.useCallback(
+    event => {
+      const {
+        nativeEvent: { data },
+      } = event;
+      const { address, signature } = JSON.parse(data);
+      if (address && signature) {
+        handleSIWE({ address, signature });
+      }
+    },
+    [handleSIWE],
+  );
+  const source = React.useMemo(() => ({ uri: commSIWE }), []);
+  return <WebView source={source} onMessage={handleMessage} />;
+}
+const ConnectedSIWEPanel: React.ComponentType<BaseProps> = React.memo<BaseProps>(
+  function ConnectedRegisterPanel(props: BaseProps) {
+    const navContext = React.useContext(NavContext);
+    const logInExtraInfo = useSelector(state =>
+      nativeLogInExtraInfoSelector({
+        redux: state,
+        navContext,
+      }),
+    );
+
+    const dispatchActionPromise = useDispatchActionPromise();
+    const callRegister = useServerCall(register);
+
+    return (
+      <SIWEPanel
+        {...props}
+        logInExtraInfo={logInExtraInfo}
+        dispatchActionPromise={dispatchActionPromise}
+        registerAction={callRegister}
+      />
+    );
+  },
+);
+
+export default ConnectedSIWEPanel;