diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs
--- a/services/identity/src/client_service.rs
+++ b/services/identity/src/client_service.rs
@@ -26,7 +26,8 @@
   RegistrationStartResponse, RemoveReservedUsernameRequest,
   ReservedRegistrationStartRequest, ReservedWalletRegistrationRequest,
   SecondaryDeviceKeysUploadRequest, VerifyUserAccessTokenRequest,
-  VerifyUserAccessTokenResponse, WalletAuthRequest,
+  VerifyUserAccessTokenResponse, WalletAuthRequest, GetFarcasterUsersRequest,
+  GetFarcasterUsersResponse
 };
 use crate::grpc_services::shared::get_value;
 use crate::grpc_utils::{
@@ -871,6 +872,13 @@
       is_reserved,
     }))
   }
+
+  async fn get_farcaster_users(
+    &self,
+    _request: tonic::Request<GetFarcasterUsersRequest>,
+  ) -> Result<tonic::Response<GetFarcasterUsersResponse>, tonic::Status> {
+    unimplemented!();
+  }
 }
 
 impl ClientService {
diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs
--- a/services/identity/src/grpc_services/authenticated.rs
+++ b/services/identity/src/grpc_services/authenticated.rs
@@ -18,10 +18,11 @@
   identity, identity_client_service_server::IdentityClientService,
   GetDeviceListRequest, GetDeviceListResponse, Identity, InboundKeyInfo,
   InboundKeysForUserRequest, InboundKeysForUserResponse, KeyserverKeysResponse,
-  OutboundKeyInfo, OutboundKeysForUserRequest, OutboundKeysForUserResponse,
-  RefreshUserPrekeysRequest, UpdateDeviceListRequest,
-  UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest,
-  UpdateUserPasswordStartResponse, UploadOneTimeKeysRequest,
+  LinkFarcasterAccountRequest, OutboundKeyInfo, OutboundKeysForUserRequest,
+  OutboundKeysForUserResponse, RefreshUserPrekeysRequest,
+  UpdateDeviceListRequest, UpdateUserPasswordFinishRequest,
+  UpdateUserPasswordStartRequest, UpdateUserPasswordStartResponse,
+  UploadOneTimeKeysRequest,
 };
 use super::protos::unauth::Empty;
 
@@ -394,6 +395,13 @@
 
     Ok(Response::new(Empty {}))
   }
+
+  async fn link_farcaster_account(
+    &self,
+    _request: tonic::Request<LinkFarcasterAccountRequest>,
+  ) -> Result<Response<Empty>, tonic::Status> {
+    unimplemented!();
+  }
 }
 
 // raw device list that can be serialized to JSON (and then signed in the future)
diff --git a/shared/protos/identity_auth.proto b/shared/protos/identity_auth.proto
--- a/shared/protos/identity_auth.proto
+++ b/shared/protos/identity_auth.proto
@@ -57,6 +57,12 @@
 
   rpc UpdateDeviceList(UpdateDeviceListRequest) returns
     (identity.unauth.Empty) {}
+
+  // Farcaster actions
+
+  // Called by an existing user to link their Farcaster account
+  rpc LinkFarcasterAccount(LinkFarcasterAccountRequest) returns
+    (identity.unauth.Empty) {}
 }
 
 // Helper types
@@ -192,3 +198,9 @@
   // }
   string new_device_list = 1;
 }
+
+// LinkFarcasterAccount
+
+message LinkFarcasterAccountRequest {
+  string farcaster_id = 1;
+}
diff --git a/shared/protos/identity_unauth.proto b/shared/protos/identity_unauth.proto
--- a/shared/protos/identity_unauth.proto
+++ b/shared/protos/identity_unauth.proto
@@ -57,6 +57,10 @@
 
   // Returns userID for given username or wallet address
   rpc FindUserID(FindUserIDRequest) returns (FindUserIDResponse) {}
+
+  // Farcaster actions
+  rpc GetFarcasterUsers(GetFarcasterUsersRequest) returns
+    (GetFarcasterUsersResponse) {}
 }
 
 // Helper types
@@ -272,3 +276,19 @@
   // whether the user is registered with Identity Service (userID != null).
   bool is_reserved = 2;
 }
+
+// GetFarcasterUsers
+
+message GetFarcasterUsersRequest {
+  repeated string farcaster_ids = 1;
+}
+
+message GetFarcasterUsersResponse {
+  repeated FarcasterUser farcaster_users = 1;
+}
+
+message FarcasterUser {
+  string user_id = 1;
+  string farcaster_id = 2;
+  string username = 3;
+}
diff --git a/web/protobufs/identity-auth-client.cjs b/web/protobufs/identity-auth-client.cjs
--- a/web/protobufs/identity-auth-client.cjs
+++ b/web/protobufs/identity-auth-client.cjs
@@ -749,4 +749,65 @@
 };
 
 
+/**
+ * @const
+ * @type {!grpc.web.MethodDescriptor<
+ *   !proto.identity.auth.LinkFarcasterAccountRequest,
+ *   !proto.identity.unauth.Empty>}
+ */
+const methodDescriptor_IdentityClientService_LinkFarcasterAccount = new grpc.web.MethodDescriptor(
+  '/identity.auth.IdentityClientService/LinkFarcasterAccount',
+  grpc.web.MethodType.UNARY,
+  proto.identity.auth.LinkFarcasterAccountRequest,
+  identity_unauth_pb.Empty,
+  /**
+   * @param {!proto.identity.auth.LinkFarcasterAccountRequest} request
+   * @return {!Uint8Array}
+   */
+  function(request) {
+    return request.serializeBinary();
+  },
+  identity_unauth_pb.Empty.deserializeBinary
+);
+
+
+/**
+ * @param {!proto.identity.auth.LinkFarcasterAccountRequest} request The
+ *     request proto
+ * @param {?Object<string, string>} metadata User defined
+ *     call metadata
+ * @param {function(?grpc.web.RpcError, ?proto.identity.unauth.Empty)}
+ *     callback The callback function(error, response)
+ * @return {!grpc.web.ClientReadableStream<!proto.identity.unauth.Empty>|undefined}
+ *     The XHR Node Readable Stream
+ */
+proto.identity.auth.IdentityClientServiceClient.prototype.linkFarcasterAccount =
+    function(request, metadata, callback) {
+  return this.client_.rpcCall(this.hostname_ +
+      '/identity.auth.IdentityClientService/LinkFarcasterAccount',
+      request,
+      metadata || {},
+      methodDescriptor_IdentityClientService_LinkFarcasterAccount,
+      callback);
+};
+
+
+/**
+ * @param {!proto.identity.auth.LinkFarcasterAccountRequest} request The
+ *     request proto
+ * @param {?Object<string, string>=} metadata User defined
+ *     call metadata
+ * @return {!Promise<!proto.identity.unauth.Empty>}
+ *     Promise that resolves to the response
+ */
+proto.identity.auth.IdentityClientServicePromiseClient.prototype.linkFarcasterAccount =
+    function(request, metadata) {
+  return this.client_.unaryCall(this.hostname_ +
+      '/identity.auth.IdentityClientService/LinkFarcasterAccount',
+      request,
+      metadata || {},
+      methodDescriptor_IdentityClientService_LinkFarcasterAccount);
+};
+
+
 module.exports = proto.identity.auth;
diff --git a/web/protobufs/identity-auth-client.cjs.flow b/web/protobufs/identity-auth-client.cjs.flow
--- a/web/protobufs/identity-auth-client.cjs.flow
+++ b/web/protobufs/identity-auth-client.cjs.flow
@@ -87,6 +87,13 @@
     callback: (err: grpcWeb.RpcError,
                response: identityStructs.Empty) => void
   ): grpcWeb.ClientReadableStream<identityStructs.Empty>;
+
+  linkFarcasterAccount(
+    request: identityAuthStructs.LinkFarcasterAccountRequest,
+    metadata: grpcWeb.Metadata | void,
+    callback: (err: grpcWeb.RpcError,
+               response: identityStructs.Empty) => void
+  ): grpcWeb.ClientReadableStream<identityStructs.Empty>;
 }
 
 declare export class IdentityClientServicePromiseClient {
@@ -148,4 +155,9 @@
     request: identityAuthStructs.UpdateDeviceListRequest,
     metadata?: grpcWeb.Metadata
   ): Promise<identityStructs.Empty>;
+
+  linkFarcasterAccount(
+    request: identityAuthStructs.LinkFarcasterAccountRequest,
+    metadata?: grpcWeb.Metadata
+  ): Promise<identityStructs.Empty>;
 }
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
@@ -33,6 +33,7 @@
 goog.exportSymbol('proto.identity.auth.InboundKeysForUserRequest', null, global);
 goog.exportSymbol('proto.identity.auth.InboundKeysForUserResponse', null, global);
 goog.exportSymbol('proto.identity.auth.KeyserverKeysResponse', null, global);
+goog.exportSymbol('proto.identity.auth.LinkFarcasterAccountRequest', null, global);
 goog.exportSymbol('proto.identity.auth.OutboundKeyInfo', null, global);
 goog.exportSymbol('proto.identity.auth.OutboundKeysForUserRequest', null, global);
 goog.exportSymbol('proto.identity.auth.OutboundKeysForUserResponse', null, global);
@@ -399,6 +400,27 @@
    */
   proto.identity.auth.UpdateDeviceListRequest.displayName = 'proto.identity.auth.UpdateDeviceListRequest';
 }
+/**
+ * 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.LinkFarcasterAccountRequest = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.identity.auth.LinkFarcasterAccountRequest, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.identity.auth.LinkFarcasterAccountRequest.displayName = 'proto.identity.auth.LinkFarcasterAccountRequest';
+}
 
 
 
@@ -3560,4 +3582,134 @@
 };
 
 
+
+
+
+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.LinkFarcasterAccountRequest.prototype.toObject = function(opt_includeInstance) {
+  return proto.identity.auth.LinkFarcasterAccountRequest.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.LinkFarcasterAccountRequest} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.auth.LinkFarcasterAccountRequest.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    farcasterId: jspb.Message.getFieldWithDefault(msg, 1, "")
+  };
+
+  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.LinkFarcasterAccountRequest}
+ */
+proto.identity.auth.LinkFarcasterAccountRequest.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.identity.auth.LinkFarcasterAccountRequest;
+  return proto.identity.auth.LinkFarcasterAccountRequest.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.identity.auth.LinkFarcasterAccountRequest} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.identity.auth.LinkFarcasterAccountRequest}
+ */
+proto.identity.auth.LinkFarcasterAccountRequest.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.setFarcasterId(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.identity.auth.LinkFarcasterAccountRequest.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.identity.auth.LinkFarcasterAccountRequest.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.LinkFarcasterAccountRequest} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.auth.LinkFarcasterAccountRequest.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = message.getFarcasterId();
+  if (f.length > 0) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string farcaster_id = 1;
+ * @return {string}
+ */
+proto.identity.auth.LinkFarcasterAccountRequest.prototype.getFarcasterId = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.identity.auth.LinkFarcasterAccountRequest} returns this
+ */
+proto.identity.auth.LinkFarcasterAccountRequest.prototype.setFarcasterId = function(value) {
+  return jspb.Message.setProto3StringField(this, 1, value);
+};
+
+
 goog.object.extend(exports, proto.identity.auth);
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
@@ -389,3 +389,18 @@
   newDeviceList: string,
 }
 
+declare export class LinkFarcasterAccountRequest extends Message {
+  getFarcasterId(): string;
+  setFarcasterId(value: string): LinkFarcasterAccountRequest;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): LinkFarcasterAccountRequestObject;
+  static toObject(includeInstance: boolean, msg: LinkFarcasterAccountRequest): LinkFarcasterAccountRequestObject;
+  static serializeBinaryToWriter(message: LinkFarcasterAccountRequest, writer: BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): LinkFarcasterAccountRequest;
+  static deserializeBinaryFromReader(message: LinkFarcasterAccountRequest, reader: BinaryReader): LinkFarcasterAccountRequest;
+}
+
+export type LinkFarcasterAccountRequestObject = {
+  farcasterId: string,
+}
diff --git a/web/protobufs/identity-unauth-structs.cjs b/web/protobufs/identity-unauth-structs.cjs
--- a/web/protobufs/identity-unauth-structs.cjs
+++ b/web/protobufs/identity-unauth-structs.cjs
@@ -27,10 +27,13 @@
 goog.exportSymbol('proto.identity.unauth.DeviceKeyUpload', null, global);
 goog.exportSymbol('proto.identity.unauth.DeviceType', null, global);
 goog.exportSymbol('proto.identity.unauth.Empty', null, global);
+goog.exportSymbol('proto.identity.unauth.FarcasterUser', null, global);
 goog.exportSymbol('proto.identity.unauth.FindUserIDRequest', null, global);
 goog.exportSymbol('proto.identity.unauth.FindUserIDRequest.IdentifierCase', null, global);
 goog.exportSymbol('proto.identity.unauth.FindUserIDResponse', null, global);
 goog.exportSymbol('proto.identity.unauth.GenerateNonceResponse', null, global);
+goog.exportSymbol('proto.identity.unauth.GetFarcasterUsersRequest', null, global);
+goog.exportSymbol('proto.identity.unauth.GetFarcasterUsersResponse', null, global);
 goog.exportSymbol('proto.identity.unauth.IdentityKeyInfo', null, global);
 goog.exportSymbol('proto.identity.unauth.OpaqueLoginFinishRequest', null, global);
 goog.exportSymbol('proto.identity.unauth.OpaqueLoginStartRequest', null, global);
@@ -508,6 +511,69 @@
    */
   proto.identity.unauth.FindUserIDResponse.displayName = 'proto.identity.unauth.FindUserIDResponse';
 }
+/**
+ * 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.unauth.GetFarcasterUsersRequest = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, proto.identity.unauth.GetFarcasterUsersRequest.repeatedFields_, null);
+};
+goog.inherits(proto.identity.unauth.GetFarcasterUsersRequest, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.identity.unauth.GetFarcasterUsersRequest.displayName = 'proto.identity.unauth.GetFarcasterUsersRequest';
+}
+/**
+ * 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.unauth.GetFarcasterUsersResponse = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, proto.identity.unauth.GetFarcasterUsersResponse.repeatedFields_, null);
+};
+goog.inherits(proto.identity.unauth.GetFarcasterUsersResponse, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.identity.unauth.GetFarcasterUsersResponse.displayName = 'proto.identity.unauth.GetFarcasterUsersResponse';
+}
+/**
+ * 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.unauth.FarcasterUser = function(opt_data) {
+  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
+};
+goog.inherits(proto.identity.unauth.FarcasterUser, jspb.Message);
+if (goog.DEBUG && !COMPILED) {
+  /**
+   * @public
+   * @override
+   */
+  proto.identity.unauth.FarcasterUser.displayName = 'proto.identity.unauth.FarcasterUser';
+}
 
 
 
@@ -5033,6 +5099,512 @@
 };
 
 
+
+/**
+ * List of repeated fields within this message type.
+ * @private {!Array<number>}
+ * @const
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.repeatedFields_ = [1];
+
+
+
+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.unauth.GetFarcasterUsersRequest.prototype.toObject = function(opt_includeInstance) {
+  return proto.identity.unauth.GetFarcasterUsersRequest.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.unauth.GetFarcasterUsersRequest} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    farcasterIdsList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : 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.unauth.GetFarcasterUsersRequest}
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.identity.unauth.GetFarcasterUsersRequest;
+  return proto.identity.unauth.GetFarcasterUsersRequest.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.identity.unauth.GetFarcasterUsersRequest} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.identity.unauth.GetFarcasterUsersRequest}
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.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.addFarcasterIds(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.identity.unauth.GetFarcasterUsersRequest.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.unauth.GetFarcasterUsersRequest} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = message.getFarcasterIdsList();
+  if (f.length > 0) {
+    writer.writeRepeatedString(
+      1,
+      f
+    );
+  }
+};
+
+
+/**
+ * repeated string farcaster_ids = 1;
+ * @return {!Array<string>}
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.prototype.getFarcasterIdsList = function() {
+  return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 1));
+};
+
+
+/**
+ * @param {!Array<string>} value
+ * @return {!proto.identity.unauth.GetFarcasterUsersRequest} returns this
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.prototype.setFarcasterIdsList = function(value) {
+  return jspb.Message.setField(this, 1, value || []);
+};
+
+
+/**
+ * @param {string} value
+ * @param {number=} opt_index
+ * @return {!proto.identity.unauth.GetFarcasterUsersRequest} returns this
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.prototype.addFarcasterIds = function(value, opt_index) {
+  return jspb.Message.addToRepeatedField(this, 1, value, opt_index);
+};
+
+
+/**
+ * Clears the list making it empty but non-null.
+ * @return {!proto.identity.unauth.GetFarcasterUsersRequest} returns this
+ */
+proto.identity.unauth.GetFarcasterUsersRequest.prototype.clearFarcasterIdsList = function() {
+  return this.setFarcasterIdsList([]);
+};
+
+
+
+/**
+ * List of repeated fields within this message type.
+ * @private {!Array<number>}
+ * @const
+ */
+proto.identity.unauth.GetFarcasterUsersResponse.repeatedFields_ = [1];
+
+
+
+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.unauth.GetFarcasterUsersResponse.prototype.toObject = function(opt_includeInstance) {
+  return proto.identity.unauth.GetFarcasterUsersResponse.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.unauth.GetFarcasterUsersResponse} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.unauth.GetFarcasterUsersResponse.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    farcasterUsersList: jspb.Message.toObjectList(msg.getFarcasterUsersList(),
+    proto.identity.unauth.FarcasterUser.toObject, includeInstance)
+  };
+
+  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.unauth.GetFarcasterUsersResponse}
+ */
+proto.identity.unauth.GetFarcasterUsersResponse.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.identity.unauth.GetFarcasterUsersResponse;
+  return proto.identity.unauth.GetFarcasterUsersResponse.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.identity.unauth.GetFarcasterUsersResponse} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.identity.unauth.GetFarcasterUsersResponse}
+ */
+proto.identity.unauth.GetFarcasterUsersResponse.deserializeBinaryFromReader = function(msg, reader) {
+  while (reader.nextField()) {
+    if (reader.isEndGroup()) {
+      break;
+    }
+    var field = reader.getFieldNumber();
+    switch (field) {
+    case 1:
+      var value = new proto.identity.unauth.FarcasterUser;
+      reader.readMessage(value,proto.identity.unauth.FarcasterUser.deserializeBinaryFromReader);
+      msg.addFarcasterUsers(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.identity.unauth.GetFarcasterUsersResponse.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.identity.unauth.GetFarcasterUsersResponse.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.unauth.GetFarcasterUsersResponse} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.unauth.GetFarcasterUsersResponse.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = message.getFarcasterUsersList();
+  if (f.length > 0) {
+    writer.writeRepeatedMessage(
+      1,
+      f,
+      proto.identity.unauth.FarcasterUser.serializeBinaryToWriter
+    );
+  }
+};
+
+
+/**
+ * repeated FarcasterUser farcaster_users = 1;
+ * @return {!Array<!proto.identity.unauth.FarcasterUser>}
+ */
+proto.identity.unauth.GetFarcasterUsersResponse.prototype.getFarcasterUsersList = function() {
+  return /** @type{!Array<!proto.identity.unauth.FarcasterUser>} */ (
+    jspb.Message.getRepeatedWrapperField(this, proto.identity.unauth.FarcasterUser, 1));
+};
+
+
+/**
+ * @param {!Array<!proto.identity.unauth.FarcasterUser>} value
+ * @return {!proto.identity.unauth.GetFarcasterUsersResponse} returns this
+*/
+proto.identity.unauth.GetFarcasterUsersResponse.prototype.setFarcasterUsersList = function(value) {
+  return jspb.Message.setRepeatedWrapperField(this, 1, value);
+};
+
+
+/**
+ * @param {!proto.identity.unauth.FarcasterUser=} opt_value
+ * @param {number=} opt_index
+ * @return {!proto.identity.unauth.FarcasterUser}
+ */
+proto.identity.unauth.GetFarcasterUsersResponse.prototype.addFarcasterUsers = function(opt_value, opt_index) {
+  return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.identity.unauth.FarcasterUser, opt_index);
+};
+
+
+/**
+ * Clears the list making it empty but non-null.
+ * @return {!proto.identity.unauth.GetFarcasterUsersResponse} returns this
+ */
+proto.identity.unauth.GetFarcasterUsersResponse.prototype.clearFarcasterUsersList = function() {
+  return this.setFarcasterUsersList([]);
+};
+
+
+
+
+
+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.unauth.FarcasterUser.prototype.toObject = function(opt_includeInstance) {
+  return proto.identity.unauth.FarcasterUser.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.unauth.FarcasterUser} msg The msg instance to transform.
+ * @return {!Object}
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.unauth.FarcasterUser.toObject = function(includeInstance, msg) {
+  var f, obj = {
+    userId: jspb.Message.getFieldWithDefault(msg, 1, ""),
+    farcasterId: jspb.Message.getFieldWithDefault(msg, 2, ""),
+    username: jspb.Message.getFieldWithDefault(msg, 3, "")
+  };
+
+  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.unauth.FarcasterUser}
+ */
+proto.identity.unauth.FarcasterUser.deserializeBinary = function(bytes) {
+  var reader = new jspb.BinaryReader(bytes);
+  var msg = new proto.identity.unauth.FarcasterUser;
+  return proto.identity.unauth.FarcasterUser.deserializeBinaryFromReader(msg, reader);
+};
+
+
+/**
+ * Deserializes binary data (in protobuf wire format) from the
+ * given reader into the given message object.
+ * @param {!proto.identity.unauth.FarcasterUser} msg The message object to deserialize into.
+ * @param {!jspb.BinaryReader} reader The BinaryReader to use.
+ * @return {!proto.identity.unauth.FarcasterUser}
+ */
+proto.identity.unauth.FarcasterUser.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.setUserId(value);
+      break;
+    case 2:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setFarcasterId(value);
+      break;
+    case 3:
+      var value = /** @type {string} */ (reader.readString());
+      msg.setUsername(value);
+      break;
+    default:
+      reader.skipField();
+      break;
+    }
+  }
+  return msg;
+};
+
+
+/**
+ * Serializes the message to binary data (in protobuf wire format).
+ * @return {!Uint8Array}
+ */
+proto.identity.unauth.FarcasterUser.prototype.serializeBinary = function() {
+  var writer = new jspb.BinaryWriter();
+  proto.identity.unauth.FarcasterUser.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.unauth.FarcasterUser} message
+ * @param {!jspb.BinaryWriter} writer
+ * @suppress {unusedLocalVariables} f is only used for nested messages
+ */
+proto.identity.unauth.FarcasterUser.serializeBinaryToWriter = function(message, writer) {
+  var f = undefined;
+  f = message.getUserId();
+  if (f.length > 0) {
+    writer.writeString(
+      1,
+      f
+    );
+  }
+  f = message.getFarcasterId();
+  if (f.length > 0) {
+    writer.writeString(
+      2,
+      f
+    );
+  }
+  f = message.getUsername();
+  if (f.length > 0) {
+    writer.writeString(
+      3,
+      f
+    );
+  }
+};
+
+
+/**
+ * optional string user_id = 1;
+ * @return {string}
+ */
+proto.identity.unauth.FarcasterUser.prototype.getUserId = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.identity.unauth.FarcasterUser} returns this
+ */
+proto.identity.unauth.FarcasterUser.prototype.setUserId = function(value) {
+  return jspb.Message.setProto3StringField(this, 1, value);
+};
+
+
+/**
+ * optional string farcaster_id = 2;
+ * @return {string}
+ */
+proto.identity.unauth.FarcasterUser.prototype.getFarcasterId = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.identity.unauth.FarcasterUser} returns this
+ */
+proto.identity.unauth.FarcasterUser.prototype.setFarcasterId = function(value) {
+  return jspb.Message.setProto3StringField(this, 2, value);
+};
+
+
+/**
+ * optional string username = 3;
+ * @return {string}
+ */
+proto.identity.unauth.FarcasterUser.prototype.getUsername = function() {
+  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
+};
+
+
+/**
+ * @param {string} value
+ * @return {!proto.identity.unauth.FarcasterUser} returns this
+ */
+proto.identity.unauth.FarcasterUser.prototype.setUsername = function(value) {
+  return jspb.Message.setProto3StringField(this, 3, value);
+};
+
+
 /**
  * @enum {number}
  */
diff --git a/web/protobufs/identity-unauth-structs.cjs.flow b/web/protobufs/identity-unauth-structs.cjs.flow
--- a/web/protobufs/identity-unauth-structs.cjs.flow
+++ b/web/protobufs/identity-unauth-structs.cjs.flow
@@ -553,4 +553,64 @@
   isReserved: boolean,
 }
 
+declare export class GetFarcasterUsersRequest extends Message {
+  getFarcasterIdsList(): Array<string>;
+  setFarcasterIdsList(value: Array<string>): GetFarcasterUsersRequest;
+  clearFarcasterIdsList(): GetFarcasterUsersRequest;
+  addFarcasterIds(value: string, index?: number): GetFarcasterUsersRequest;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): GetFarcasterUsersRequestObject;
+  static toObject(includeInstance: boolean, msg: GetFarcasterUsersRequest): GetFarcasterUsersRequestObject;
+  static serializeBinaryToWriter(message: GetFarcasterUsersRequest, writer: BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): GetFarcasterUsersRequest;
+  static deserializeBinaryFromReader(message: GetFarcasterUsersRequest, reader: BinaryReader): GetFarcasterUsersRequest;
+}
+
+export type GetFarcasterUsersRequestObject = {
+  farcasterIdsList: Array<string>,
+}
+
+declare export class GetFarcasterUsersResponse extends Message {
+  getFarcasterUsersList(): Array<FarcasterUser>;
+  setFarcasterUsersList(value: Array<FarcasterUser>): GetFarcasterUsersResponse;
+  clearFarcasterUsersList(): GetFarcasterUsersResponse;
+  addFarcasterUsers(value?: FarcasterUser, index?: number): FarcasterUser;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): GetFarcasterUsersResponseObject;
+  static toObject(includeInstance: boolean, msg: GetFarcasterUsersResponse): GetFarcasterUsersResponseObject;
+  static serializeBinaryToWriter(message: GetFarcasterUsersResponse, writer: BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): GetFarcasterUsersResponse;
+  static deserializeBinaryFromReader(message: GetFarcasterUsersResponse, reader: BinaryReader): GetFarcasterUsersResponse;
+}
+
+export type GetFarcasterUsersResponseObject = {
+  farcasterUsersList: Array<FarcasterUserObject>,
+}
+
+declare export class FarcasterUser extends Message {
+  getUserId(): string;
+  setUserId(value: string): FarcasterUser;
+
+  getFarcasterId(): string;
+  setFarcasterId(value: string): FarcasterUser;
+
+  getUsername(): string;
+  setUsername(value: string): FarcasterUser;
+
+  serializeBinary(): Uint8Array;
+  toObject(includeInstance?: boolean): FarcasterUserObject;
+  static toObject(includeInstance: boolean, msg: FarcasterUser): FarcasterUserObject;
+  static serializeBinaryToWriter(message: FarcasterUser, writer: BinaryWriter): void;
+  static deserializeBinary(bytes: Uint8Array): FarcasterUser;
+  static deserializeBinaryFromReader(message: FarcasterUser, reader: BinaryReader): FarcasterUser;
+}
+
+export type FarcasterUserObject = {
+  userId: string,
+  farcasterId: string,
+  username: string,
+}
+
 export type DeviceType = 0 | 1 | 2 | 3 | 4 | 5;
diff --git a/web/protobufs/identity-unauth.cjs b/web/protobufs/identity-unauth.cjs
--- a/web/protobufs/identity-unauth.cjs
+++ b/web/protobufs/identity-unauth.cjs
@@ -991,5 +991,65 @@
 };
 
 
-module.exports = proto.identity.unauth;
+/**
+ * @const
+ * @type {!grpc.web.MethodDescriptor<
+ *   !proto.identity.unauth.GetFarcasterUsersRequest,
+ *   !proto.identity.unauth.GetFarcasterUsersResponse>}
+ */
+const methodDescriptor_IdentityClientService_GetFarcasterUsers = new grpc.web.MethodDescriptor(
+  '/identity.unauth.IdentityClientService/GetFarcasterUsers',
+  grpc.web.MethodType.UNARY,
+  proto.identity.unauth.GetFarcasterUsersRequest,
+  proto.identity.unauth.GetFarcasterUsersResponse,
+  /**
+   * @param {!proto.identity.unauth.GetFarcasterUsersRequest} request
+   * @return {!Uint8Array}
+   */
+  function(request) {
+    return request.serializeBinary();
+  },
+  proto.identity.unauth.GetFarcasterUsersResponse.deserializeBinary
+);
+
 
+/**
+ * @param {!proto.identity.unauth.GetFarcasterUsersRequest} request The
+ *     request proto
+ * @param {?Object<string, string>} metadata User defined
+ *     call metadata
+ * @param {function(?grpc.web.RpcError, ?proto.identity.unauth.GetFarcasterUsersResponse)}
+ *     callback The callback function(error, response)
+ * @return {!grpc.web.ClientReadableStream<!proto.identity.unauth.GetFarcasterUsersResponse>|undefined}
+ *     The XHR Node Readable Stream
+ */
+proto.identity.unauth.IdentityClientServiceClient.prototype.getFarcasterUsers =
+    function(request, metadata, callback) {
+  return this.client_.rpcCall(this.hostname_ +
+      '/identity.unauth.IdentityClientService/GetFarcasterUsers',
+      request,
+      metadata || {},
+      methodDescriptor_IdentityClientService_GetFarcasterUsers,
+      callback);
+};
+
+
+/**
+ * @param {!proto.identity.unauth.GetFarcasterUsersRequest} request The
+ *     request proto
+ * @param {?Object<string, string>=} metadata User defined
+ *     call metadata
+ * @return {!Promise<!proto.identity.unauth.GetFarcasterUsersResponse>}
+ *     Promise that resolves to the response
+ */
+proto.identity.unauth.IdentityClientServicePromiseClient.prototype.getFarcasterUsers =
+    function(request, metadata) {
+  return this.client_.unaryCall(this.hostname_ +
+      '/identity.unauth.IdentityClientService/GetFarcasterUsers',
+      request,
+      metadata || {},
+      methodDescriptor_IdentityClientService_GetFarcasterUsers);
+};
+
+
+module.exports = proto.identity.unauth;
diff --git a/web/protobufs/identity-unauth.cjs.flow b/web/protobufs/identity-unauth.cjs.flow
--- a/web/protobufs/identity-unauth.cjs.flow
+++ b/web/protobufs/identity-unauth.cjs.flow
@@ -114,6 +114,13 @@
                response: identityStructs.FindUserIDResponse) => void
   ): grpcWeb.ClientReadableStream<identityStructs.FindUserIDResponse>;
 
+  getFarcasterUsers(
+    request: identityStructs.GetFarcasterUsersRequest,
+    metadata: grpcWeb.Metadata | void,
+    callback: (err: grpcWeb.RpcError,
+               response: identityStructs.GetFarcasterUsersResponse) => void
+  ): grpcWeb.ClientReadableStream<identityStructs.GetFarcasterUsersResponse>;
+
 }
 
 declare export class IdentityClientServicePromiseClient {
@@ -195,4 +202,9 @@
     metadata?: grpcWeb.Metadata
   ): Promise<identityStructs.FindUserIDResponse>;
 
+  getFarcasterUsers(
+    request: identityStructs.GetFarcasterUsersRequest,
+    metadata?: grpcWeb.Metadata
+  ): Promise<identityStructs.GetFarcasterUsersResponse>;
+
 }