diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs
--- a/services/identity/src/constants.rs
+++ b/services/identity/src/constants.rs
@@ -56,6 +56,7 @@
 pub const USERS_TABLE_FARCASTER_ID_ATTRIBUTE_NAME: &str = "farcasterID";
 pub const USERS_TABLE_USERNAME_INDEX: &str = "username-index";
 pub const USERS_TABLE_WALLET_ADDRESS_INDEX: &str = "walletAddress-index";
+pub const USERS_TABLE_FARCASTER_ID_INDEX: &str = "farcasterID-index";
 
 pub const ACCESS_TOKEN_TABLE: &str = "identity-tokens";
 pub const ACCESS_TOKEN_TABLE_PARTITION_KEY: &str = "userID";
diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs
--- a/services/identity/src/database.rs
+++ b/services/identity/src/database.rs
@@ -58,6 +58,7 @@
 pub use grpc_clients::identity::DeviceType;
 
 mod device_list;
+mod farcaster;
 mod workflows;
 pub use device_list::{DeviceListRow, DeviceListUpdate, DeviceRow};
 
diff --git a/services/identity/src/database/farcaster.rs b/services/identity/src/database/farcaster.rs
new file mode 100644
--- /dev/null
+++ b/services/identity/src/database/farcaster.rs
@@ -0,0 +1,88 @@
+use comm_lib::aws::ddb::types::AttributeValue;
+use comm_lib::database::AttributeExtractor;
+use comm_lib::database::AttributeMap;
+use comm_lib::database::DBItemAttributeError;
+use comm_lib::database::DBItemError;
+use comm_lib::database::Value;
+use grpc_clients::identity::protos::unauth::FarcasterUser;
+use tracing::error;
+
+use crate::constants::USERS_TABLE;
+use crate::constants::USERS_TABLE_FARCASTER_ID_ATTRIBUTE_NAME;
+use crate::constants::USERS_TABLE_FARCASTER_ID_INDEX;
+use crate::constants::USERS_TABLE_PARTITION_KEY;
+use crate::constants::USERS_TABLE_USERNAME_ATTRIBUTE;
+use crate::constants::USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE;
+
+use super::DatabaseClient;
+use super::Error;
+
+struct FarcasterUserData(FarcasterUser);
+
+impl DatabaseClient {
+  async fn get_farcaster_users(
+    &self,
+    farcaster_ids: Vec<String>,
+  ) -> Result<Vec<FarcasterUserData>, Error> {
+    let mut users: Vec<FarcasterUserData> = Vec::new();
+
+    for id in farcaster_ids {
+      let query_response = self
+        .client
+        .query()
+        .table_name(USERS_TABLE)
+        .index_name(USERS_TABLE_FARCASTER_ID_INDEX)
+        .key_condition_expression(format!(
+          "{} = :val",
+          USERS_TABLE_FARCASTER_ID_ATTRIBUTE_NAME
+        ))
+        .expression_attribute_values(":val", AttributeValue::S(id))
+        .send()
+        .await
+        .map_err(|e| {
+          error!("Failed to query users by farcasterID: {:?}", e);
+          Error::AwsSdk(e.into())
+        })?
+        .items
+        .and_then(|mut items| items.pop())
+        .map(FarcasterUserData::try_from)
+        .transpose()
+        .map_err(Error::from)?;
+      if let Some(data) = query_response {
+        users.push(data);
+      }
+    }
+
+    Ok(users)
+  }
+}
+
+impl TryFrom<AttributeMap> for FarcasterUserData {
+  type Error = DBItemError;
+
+  fn try_from(mut attrs: AttributeMap) -> Result<Self, Self::Error> {
+    let user_id = attrs.take_attr(USERS_TABLE_PARTITION_KEY)?;
+    let maybe_username = attrs.take_attr(USERS_TABLE_USERNAME_ATTRIBUTE)?;
+    let maybe_wallet_address =
+      attrs.take_attr(USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE)?;
+    let username = match (maybe_username, maybe_wallet_address) {
+      (Some(u), _) => u,
+      (_, Some(w)) => w,
+      (_, _) => {
+        return Err(DBItemError {
+          attribute_name: USERS_TABLE_USERNAME_ATTRIBUTE.to_string(),
+          attribute_value: Value::AttributeValue(None),
+          attribute_error: DBItemAttributeError::Missing,
+        });
+      }
+    };
+    let farcaster_id =
+      attrs.take_attr(USERS_TABLE_FARCASTER_ID_ATTRIBUTE_NAME)?;
+
+    Ok(Self(FarcasterUser {
+      user_id,
+      username,
+      farcaster_id,
+    }))
+  }
+}