diff --git a/web/protobufs/identity-auth-structs.cjs b/web/protobufs/identity-auth-structs.cjs
--- a/web/protobufs/identity-auth-structs.cjs
+++ b/web/protobufs/identity-auth-structs.cjs
@@ -24,11 +24,14 @@
 
 var identity_unauth_pb = require('./identity-unauth-structs.cjs');
 goog.object.extend(proto, identity_unauth_pb);
+goog.exportSymbol('proto.identity.auth.EthereumIdentity', null, global);
 goog.exportSymbol('proto.identity.auth.FindUserIDRequest', null, global);
 goog.exportSymbol('proto.identity.auth.FindUserIDRequest.IdentifierCase', null, global);
 goog.exportSymbol('proto.identity.auth.FindUserIDResponse', null, global);
 goog.exportSymbol('proto.identity.auth.GetDeviceListRequest', null, global);
 goog.exportSymbol('proto.identity.auth.GetDeviceListResponse', null, global);
+goog.exportSymbol('proto.identity.auth.Identity', null, global);
+goog.exportSymbol('proto.identity.auth.Identity.IdentityInfoCase', null, global);
 goog.exportSymbol('proto.identity.auth.InboundKeyInfo', null, global);
 goog.exportSymbol('proto.identity.auth.InboundKeysForUserRequest', null, global);
 goog.exportSymbol('proto.identity.auth.InboundKeysForUserResponse', null, global);
@@ -41,6 +44,48 @@
 goog.exportSymbol('proto.identity.auth.UpdateUserPasswordStartRequest', null, global);
 goog.exportSymbol('proto.identity.auth.UpdateUserPasswordStartResponse', null, global);
 goog.exportSymbol('proto.identity.auth.UploadOneTimeKeysRequest', null, global);
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.identity.auth.EthereumIdentity = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.identity.auth.EthereumIdentity, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.identity.auth.EthereumIdentity.displayName = 'proto.identity.auth.EthereumIdentity';
+}
+/**
+ * Generated by JsPbCodeGenerator.
+ * @param {Array=} opt_data Optional initial data array, typically from a
+ * server response, or constructed directly in Javascript. The array is used
+ * in place and becomes part of the constructed object. It is not cloned.
+ * If no data is provided, the constructed object will be empty, but still
+ * valid.
+ * @extends {jspb.Message}
+ * @constructor
+ */
+proto.identity.auth.Identity = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, proto.identity.auth.Identity.oneofGroups_);
+};
+goog.inherits(proto.identity.auth.Identity, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.identity.auth.Identity.displayName = 'proto.identity.auth.Identity';
+}
 /**
  * Generated by JsPbCodeGenerator.
  * @param {Array=} opt_data Optional initial data array, typically from a
@@ -378,6 +423,391 @@
   proto.identity.auth.GetDeviceListResponse.displayName = 'proto.identity.auth.GetDeviceListResponse';
 }
 
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.identity.auth.EthereumIdentity.prototype.toObject = function(opt_includeInstance) {
+  return proto.identity.auth.EthereumIdentity.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.identity.auth.EthereumIdentity} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.auth.EthereumIdentity.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    walletAddress: jspb.Message.getFieldWithDefault(msg, 1, ""),
+    socialProof: jspb.Message.getFieldWithDefault(msg, 2, "")
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.identity.auth.EthereumIdentity}
+ */
+proto.identity.auth.EthereumIdentity.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.identity.auth.EthereumIdentity;
+  return proto.identity.auth.EthereumIdentity.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.identity.auth.EthereumIdentity} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.identity.auth.EthereumIdentity}
+ */
+proto.identity.auth.EthereumIdentity.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setWalletAddress(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setSocialProof(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.identity.auth.EthereumIdentity.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.identity.auth.EthereumIdentity.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.identity.auth.EthereumIdentity} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.auth.EthereumIdentity.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = message.getWalletAddress();
+  if (f.length > 0) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = message.getSocialProof();
+  if (f.length > 0) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string wallet_address = 1;
+ * @return {string}
+ */
+proto.identity.auth.EthereumIdentity.prototype.getWalletAddress = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.identity.auth.EthereumIdentity} returns this
+ */
+proto.identity.auth.EthereumIdentity.prototype.setWalletAddress = function(value) {
+  return jspb.Message.setProto3StringField(this, 1, value);
+};
+
+
+/**
+ * optional string social_proof = 2;
+ * @return {string}
+ */
+proto.identity.auth.EthereumIdentity.prototype.getSocialProof = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.identity.auth.EthereumIdentity} returns this
+ */
+proto.identity.auth.EthereumIdentity.prototype.setSocialProof = function(value) {
+  return jspb.Message.setProto3StringField(this, 2, value);
+};
+
+
+
+/**
+ * Oneof group definitions for this message. Each group defines the field
+ * numbers belonging to that group. When of these fields' value is set, all
+ * other fields in the group are cleared. During deserialization, if multiple
+ * fields are encountered for a group, only the last value seen will be kept.
+ * @private {!Array<!Array<number>>}
+ * @const
+ */
+proto.identity.auth.Identity.oneofGroups_ = [[1,2]];
+
+/**
+ * @enum {number}
+ */
+proto.identity.auth.Identity.IdentityInfoCase = {
+  IDENTITY_INFO_NOT_SET: 0,
+  USERNAME: 1,
+  ETH_IDENTITY: 2
+};
+
+/**
+ * @return {proto.identity.auth.Identity.IdentityInfoCase}
+ */
+proto.identity.auth.Identity.prototype.getIdentityInfoCase = function() {
+  return /** @type {proto.identity.auth.Identity.IdentityInfoCase} */(jspb.Message.computeOneofCase(this, proto.identity.auth.Identity.oneofGroups_[0]));
+};
+
+
+
+if (jspb.Message.GENERATE_TO_OBJECT) {
+/**
+ * Creates an object representation of this proto.
+ * Field names that are reserved in JavaScript and will be renamed to pb_name.
+ * Optional fields that are not set will be set to undefined.
+ * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
+ * For the list of reserved names please see:
+ *     net/proto2/compiler/js/internal/generator.cc#kKeyword.
+ * @param {boolean=} opt_includeInstance Deprecated. whether to include the
+ *     JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @return {!Object}
+ */
+proto.identity.auth.Identity.prototype.toObject = function(opt_includeInstance) {
+  return proto.identity.auth.Identity.toObject(opt_includeInstance, this);
+};
+
+
+/**
+ * Static version of the {@see toObject} method.
+ * @param {boolean|undefined} includeInstance Deprecated. Whether to include
+ *     the JSPB instance for transitional soy proto support:
+ *     http://goto/soy-param-migration
+ * @param {!proto.identity.auth.Identity} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.auth.Identity.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    username: jspb.Message.getFieldWithDefault(msg, 1, ""),
+    ethIdentity: (f = msg.getEthIdentity()) && proto.identity.auth.EthereumIdentity.toObject(includeInstance, f)
+  };
+
+  if (includeInstance) {
+    obj.$jspbMessageInstance = msg;
+  }
+  return obj;
+};
+}
+
+
+/**
+ * Deserializes binary data (in protobuf wire format).
+ * @param {jspb.ByteSource} bytes The bytes to deserialize.
+ * @return {!proto.identity.auth.Identity}
+ */
+proto.identity.auth.Identity.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.identity.auth.Identity;
+  return proto.identity.auth.Identity.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.identity.auth.Identity} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.identity.auth.Identity}
+ */
+proto.identity.auth.Identity.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setUsername(value);
+      break;
+    case 2:
+      var value = new proto.identity.auth.EthereumIdentity;
+      reader.readMessage(value,proto.identity.auth.EthereumIdentity.deserializeBinaryFromReader);
+      msg.setEthIdentity(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.identity.auth.Identity.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.identity.auth.Identity.serializeBinaryToWriter(this, writer);
+  return writer.getResultBuffer();
+};
+
+
+/**
+ * Serializes the given message to binary data (in protobuf wire
+ * format), writing to the given BinaryWriter.
+ * @param {!proto.identity.auth.Identity} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.auth.Identity.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = /** @type {string} */ (jspb.Message.getField(message, 1));
+  if (f != null) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = message.getEthIdentity();
+  if (f != null) {
+    writer.writeMessage(
+      2,
+      f,
+      proto.identity.auth.EthereumIdentity.serializeBinaryToWriter
+    );
+  }
+};
+
+
+/**
+ * optional string username = 1;
+ * @return {string}
+ */
+proto.identity.auth.Identity.prototype.getUsername = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.identity.auth.Identity} returns this
+ */
+proto.identity.auth.Identity.prototype.setUsername = function(value) {
+  return jspb.Message.setOneofField(this, 1, proto.identity.auth.Identity.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the field making it undefined.
+ * @return {!proto.identity.auth.Identity} returns this
+ */
+proto.identity.auth.Identity.prototype.clearUsername = function() {
+  return jspb.Message.setOneofField(this, 1, proto.identity.auth.Identity.oneofGroups_[0], undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.identity.auth.Identity.prototype.hasUsername = function() {
+  return jspb.Message.getField(this, 1) != null;
+};
+
+
+/**
+ * optional EthereumIdentity eth_identity = 2;
+ * @return {?proto.identity.auth.EthereumIdentity}
+ */
+proto.identity.auth.Identity.prototype.getEthIdentity = function() {
+  return /** @type{?proto.identity.auth.EthereumIdentity} */ (
+    jspb.Message.getWrapperField(this, proto.identity.auth.EthereumIdentity, 2));
+};
+
+
+/**
+ * @param {?proto.identity.auth.EthereumIdentity|undefined} value
+ * @return {!proto.identity.auth.Identity} returns this
+*/
+proto.identity.auth.Identity.prototype.setEthIdentity = function(value) {
+  return jspb.Message.setOneofWrapperField(this, 2, proto.identity.auth.Identity.oneofGroups_[0], value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.identity.auth.Identity} returns this
+ */
+proto.identity.auth.Identity.prototype.clearEthIdentity = function() {
+  return this.setEthIdentity(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.identity.auth.Identity.prototype.hasEthIdentity = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
+
 /**
  * List of repeated fields within this message type.
  * @private {!Array<number>}
@@ -1833,7 +2263,8 @@
  */
 proto.identity.auth.InboundKeysForUserResponse.toObject = function(includeInstance, msg) {
   var f, obj = {
-    devicesMap: (f = msg.getDevicesMap()) ? f.toObject(includeInstance, proto.identity.auth.InboundKeyInfo.toObject) : []
+    devicesMap: (f = msg.getDevicesMap()) ? f.toObject(includeInstance, proto.identity.auth.InboundKeyInfo.toObject) : [],
+    identity: (f = msg.getIdentity()) && proto.identity.auth.Identity.toObject(includeInstance, f)
   };
 
   if (includeInstance) {
@@ -1876,6 +2307,11 @@
         jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readMessage, proto.identity.auth.InboundKeyInfo.deserializeBinaryFromReader, "", new proto.identity.auth.InboundKeyInfo());
          });
       break;
+    case 2:
+      var value = new proto.identity.auth.Identity;
+      reader.readMessage(value,proto.identity.auth.Identity.deserializeBinaryFromReader);
+      msg.setIdentity(value);
+      break;
     default:
       reader.skipField();
       break;
@@ -1909,6 +2345,14 @@
   if (f && f.getLength() > 0) {
     f.serializeBinary(1, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeMessage, proto.identity.auth.InboundKeyInfo.serializeBinaryToWriter);
   }
+  f = message.getIdentity();
+  if (f != null) {
+    writer.writeMessage(
+      2,
+      f,
+      proto.identity.auth.Identity.serializeBinaryToWriter
+    );
+  }
 };
 
 
@@ -1935,6 +2379,43 @@
 };
 
 
+/**
+ * optional Identity identity = 2;
+ * @return {?proto.identity.auth.Identity}
+ */
+proto.identity.auth.InboundKeysForUserResponse.prototype.getIdentity = function() {
+  return /** @type{?proto.identity.auth.Identity} */ (
+    jspb.Message.getWrapperField(this, proto.identity.auth.Identity, 2));
+};
+
+
+/**
+ * @param {?proto.identity.auth.Identity|undefined} value
+ * @return {!proto.identity.auth.InboundKeysForUserResponse} returns this
+*/
+proto.identity.auth.InboundKeysForUserResponse.prototype.setIdentity = function(value) {
+  return jspb.Message.setWrapperField(this, 2, value);
+};
+
+
+/**
+ * Clears the message field making it undefined.
+ * @return {!proto.identity.auth.InboundKeysForUserResponse} returns this
+ */
+proto.identity.auth.InboundKeysForUserResponse.prototype.clearIdentity = function() {
+  return this.setIdentity(undefined);
+};
+
+
+/**
+ * Returns whether this field is set.
+ * @return {boolean}
+ */
+proto.identity.auth.InboundKeysForUserResponse.prototype.hasIdentity = function() {
+  return jspb.Message.getField(this, 2) != null;
+};
+
+
 
 
 
diff --git a/web/protobufs/identity-auth-structs.cjs.flow b/web/protobufs/identity-auth-structs.cjs.flow
--- a/web/protobufs/identity-auth-structs.cjs.flow
+++ b/web/protobufs/identity-auth-structs.cjs.flow
@@ -9,6 +9,54 @@
 
 import * as identityStructs from './identity-unauth-structs.cjs';
 
+declare export class EthereumIdentity extends Message {
+  getWalletAddress(): string;
+  setWalletAddress(value: string): EthereumIdentity;
+
+  getSocialProof(): string;
+  setSocialProof(value: string): EthereumIdentity;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): EthereumIdentityObject;
+  static toObject(includeInstance: boolean, msg: EthereumIdentity): EthereumIdentityObject;
+  static serializeBinaryToWriter(message: EthereumIdentity, writer: BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): EthereumIdentity;
+  static deserializeBinaryFromReader(message: EthereumIdentity, reader: BinaryReader): EthereumIdentity;
+}
+
+export type EthereumIdentityObject = {
+  walletAddress: string,
+  socialProof: string,
+}
+
+export type IdentityInfoCase = 0 | 1 | 2;
+
+declare export class Identity extends Message {
+  getUsername(): string | void;
+  setUsername(value: string): Identity;
+  hasUsername(): boolean;
+  clearUsername(): Identity;
+
+  getEthIdentity(): EthereumIdentity | void;
+  setEthIdentity(value?: EthereumIdentity): Identity;
+  hasEthIdentity(): boolean;
+  clearEthIdentity(): Identity;
+
+  getIdentityInfoCase(): IdentityInfoCase;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): IdentityObject;
+  static toObject(includeInstance: boolean, msg: Identity): IdentityObject;
+  static serializeBinaryToWriter(message: Identity, writer: BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): Identity;
+  static deserializeBinaryFromReader(message: Identity, reader: BinaryReader): Identity;
+}
+
+export type IdentityObject = {
+  username?: string,
+  ethIdentity?: EthereumIdentityObject,
+}
+
 declare export class UploadOneTimeKeysRequest extends Message {
   getContentOneTimePrekeysList(): Array<string>;
   setContentOneTimePrekeysList(value: Array<string>): UploadOneTimeKeysRequest;
@@ -183,6 +231,11 @@
   getDevicesMap(): ProtoMap<string, InboundKeyInfo>;
   clearDevicesMap(): InboundKeysForUserResponse;
 
+  getIdentity(): Identity | void;
+  setIdentity(value?: Identity): InboundKeysForUserResponse;
+  hasIdentity(): boolean;
+  clearIdentity(): InboundKeysForUserResponse;
+
   serializeBinary(): Uint8Array;
   toObject(includeInstance?: boolean): InboundKeysForUserResponseObject;
   static toObject(includeInstance: boolean, msg: InboundKeysForUserResponse): InboundKeysForUserResponseObject;
@@ -193,6 +246,7 @@
 
 export type InboundKeysForUserResponseObject = {
   devicesMap: Array<[string, InboundKeyInfoObject]>,
+  identity?: IdentityObject,
 }
 
 declare export class InboundKeysForUserRequest extends Message {
@@ -360,4 +414,3 @@
 export type GetDeviceListResponseObject = {
   deviceListUpdatesList: Array<string>,
 }
-