diff --git a/keyserver/addons/rust-node-addon/src/identity_client/delete_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/delete_user.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/delete_user.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/delete_user.rs
@@ -3,15 +3,7 @@
 #[napi]
 #[instrument(skip_all)]
 pub async fn delete_user(user_id: String) -> Result<()> {
-  let channel = Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR)
-    .connect()
-    .await
-    .map_err(|_| {
-      Error::new(
-        Status::GenericFailure,
-        "Unable to connect to identity service".to_string(),
-      )
-    })?;
+  let channel = get_identity_service_channel().await?;
   let token: MetadataValue<_> = AUTH_TOKEN
     .parse()
     .map_err(|_| Error::from_status(Status::GenericFailure))?;
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs
@@ -1,5 +1,64 @@
 use super::*;
 
+#[napi]
+#[instrument(skip_all)]
+async fn login_user_wallet(
+  user_id: String,
+  signing_public_key: String,
+  siwe_message: String,
+  siwe_signature: String,
+  mut session_initialization_info: HashMap<String, String>,
+  social_proof: String,
+) -> Result<String> {
+  let channel = get_identity_service_channel().await?;
+  let token: MetadataValue<_> = AUTH_TOKEN
+    .parse()
+    .map_err(|_| Error::from_status(Status::GenericFailure))?;
+  let mut identity_client =
+    IdentityServiceClient::with_interceptor(channel, |mut req: Request<()>| {
+      req.metadata_mut().insert("authorization", token.clone());
+      Ok(req)
+    });
+
+  // Create a LoginRequest channel and use ReceiverStream to turn the
+  // MPSC receiver into a Stream for outbound messages
+  let (tx, rx) = mpsc::channel(1);
+  let stream = ReceiverStream::new(rx);
+  let request = Request::new(stream);
+
+  let mut response_stream = identity_client
+    .login_user(request)
+    .await
+    .map_err(|_| Error::from_status(Status::GenericFailure))?
+    .into_inner();
+
+  // Start wallet login on client and send initial login request to Identity
+  // service
+  session_initialization_info.insert("socialProof".to_string(), social_proof);
+  let login_request = LoginRequest {
+    data: Some(WalletLoginRequest(WalletLoginRequestStruct {
+      user_id,
+      signing_public_key,
+      siwe_message,
+      siwe_signature,
+      session_initialization_info: Some(SessionInitializationInfo {
+        info: session_initialization_info,
+      }),
+    })),
+  };
+  if let Err(e) = tx.send(login_request).await {
+    error!("Response was dropped: {}", e);
+    return Err(Error::from_status(Status::GenericFailure));
+  }
+
+  // Return access token
+  let message = response_stream.message().await.map_err(|e| {
+    error!("Received an error from inbound message stream: {}", e);
+    Error::from_status(Status::GenericFailure)
+  })?;
+  get_wallet_access_token(message)
+}
+
 #[napi]
 #[instrument(skip_all)]
 async fn login_user_pake(
@@ -8,15 +67,7 @@
   password: String,
   session_initialization_info: HashMap<String, String>,
 ) -> Result<String> {
-  let channel = Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR)
-    .connect()
-    .await
-    .map_err(|_| {
-      Error::new(
-        Status::GenericFailure,
-        "Unable to connect to identity service".to_string(),
-      )
-    })?;
+  let channel = get_identity_service_channel().await?;
   let token: MetadataValue<_> = AUTH_TOKEN
     .parse()
     .map_err(|_| Error::from_status(Status::GenericFailure))?;
@@ -135,3 +186,16 @@
     Err(handle_unexpected_response(message))
   }
 }
+
+fn get_wallet_access_token(
+  message: Option<LoginResponse>,
+) -> Result<String, Status> {
+  if let Some(LoginResponse {
+    data: Some(WalletLoginResponse(WalletLoginResponseStruct { access_token })),
+  }) = message
+  {
+    Ok(access_token)
+  } else {
+    Err(handle_unexpected_response(message))
+  }
+}
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs
@@ -9,7 +9,9 @@
 use identity::identity_service_client::IdentityServiceClient;
 use identity::{
   login_request::Data::PakeLoginRequest,
+  login_request::Data::WalletLoginRequest,
   login_response::Data::PakeLoginResponse as LoginPakeLoginResponse,
+  login_response::Data::WalletLoginResponse,
   pake_login_request::Data::PakeCredentialFinalization as LoginPakeCredentialFinalization,
   pake_login_request::Data::PakeCredentialRequestAndUserId,
   pake_login_response::Data::AccessToken,
@@ -26,7 +28,8 @@
   PakeRegistrationRequestAndUserId as PakeRegistrationRequestAndUserIdStruct,
   PakeRegistrationUploadAndCredentialRequest as PakeRegistrationUploadAndCredentialRequestStruct,
   RegistrationRequest, RegistrationResponse as RegistrationResponseMessage,
-  SessionInitializationInfo,
+  SessionInitializationInfo, WalletLoginRequest as WalletLoginRequestStruct,
+  WalletLoginResponse as WalletLoginResponseStruct,
 };
 use lazy_static::lazy_static;
 use napi::bindgen_prelude::*;
@@ -100,3 +103,15 @@
     })
     .map(|res| res.message)
 }
+
+async fn get_identity_service_channel() -> Result<Channel> {
+  Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR)
+    .connect()
+    .await
+    .map_err(|_| {
+      Error::new(
+        Status::GenericFailure,
+        "Unable to connect to identity service".to_string(),
+      )
+    })
+}
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs
@@ -9,15 +9,7 @@
   password: String,
   session_initialization_info: HashMap<String, String>,
 ) -> Result<String> {
-  let channel = Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR)
-    .connect()
-    .await
-    .map_err(|_| {
-      Error::new(
-        Status::GenericFailure,
-        "Unable to connect to identity service".to_string(),
-      )
-    })?;
+  let channel = get_identity_service_channel().await?;
   let token: MetadataValue<_> = AUTH_TOKEN
     .parse()
     .map_err(|_| Error::from_status(Status::GenericFailure))?;
diff --git a/lib/types/rust-binding-types.js b/lib/types/rust-binding-types.js
--- a/lib/types/rust-binding-types.js
+++ b/lib/types/rust-binding-types.js
@@ -29,6 +29,14 @@
     password: string,
     sessionInitializationInfo: SignedIdentityKeysBlob,
   ) => Promise<string>,
+  +loginUserWallet: (
+    userId: string,
+    signingPublicKey: string,
+    siweMessage: string,
+    siweSignature: string,
+    sessionInitializationInfo: SignedIdentityKeysBlob,
+    socialProof: string,
+  ) => Promise<string>,
   +deleteUser: (userId: string) => Promise<boolean>,
   +TunnelbrokerClient: Class<TunnelbrokerClientClass>,
 };