Skip to content

Commit

Permalink
feat: wallet service use JSON-RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
chris13524 committed Oct 1, 2024
1 parent fa4db4c commit e9b51f7
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 84 deletions.
139 changes: 139 additions & 0 deletions src/handlers/wallet/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use crate::error::RpcError;
use crate::json_rpc::{
ErrorResponse, JsonRpcError, JsonRpcRequest, JsonRpcResponse, JsonRpcResult,
};
use crate::{handlers::HANDLER_TASK_METRICS, state::AppState};
use axum::extract::Query;
use axum::{extract::State, Json};
use serde::Deserialize;
use std::sync::Arc;
use thiserror::Error;
use tracing::error;
use wc::future::FutureExt;

use super::prepare_calls::{self, PrepareCallsError};
use super::send_prepared_calls::{self, SendPreparedCallsError};

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct WalletQueryParams {
pub project_id: String,
}

pub async fn handler(
state: State<Arc<AppState>>,
query: Query<WalletQueryParams>,
Json(request_payload): Json<JsonRpcRequest>,
) -> Json<JsonRpcResponse> {
handler_internal(state, query, request_payload)
.with_metrics(HANDLER_TASK_METRICS.with_name("wallet"))
.await
}

#[tracing::instrument(skip(state), level = "debug")]
async fn handler_internal(
state: State<Arc<AppState>>,
query: Query<WalletQueryParams>,
request: JsonRpcRequest,
) -> Json<JsonRpcResponse> {
match handle_rpc(state, query, request.method, request.params).await {
Ok(result) => Json(JsonRpcResponse::Result(JsonRpcResult::new(
request.id, result,
))),
Err(e) => {
if matches!(e, Error::Internal(_)) {
error!("Internal server error handling wallet RPC request: {e:?}");
}
Json(JsonRpcResponse::Error(JsonRpcError::new(
request.id,
ErrorResponse {
code: e.to_json_rpc_error_code(),
message: e.to_string().into(),
data: None,
},
)))
}
}
}

const WALLET_PREPARE_CALLS: &str = "wallet_prepareCalls";
const WALLET_SEND_PREPARED_CALLS: &str = "wallet_sendPreparedCalls";

#[derive(Debug, Error)]
enum Error {
#[error("Invalid project ID: {0}")]
InvalidProjectId(RpcError),

#[error("{WALLET_PREPARE_CALLS}: {0}")]
PrepareCalls(PrepareCallsError),

#[error("{WALLET_SEND_PREPARED_CALLS}: {0}")]
SendPreparedCalls(SendPreparedCallsError),

#[error("Method not found")]
MethodNotFound,

#[error("Invalid params: {0}")]
InvalidParams(serde_json::Error),

#[error("Internal error")]
Internal(InternalError),
}

#[derive(Debug, Error)]
enum InternalError {
#[error("Serializing response: {0}")]
SerializeResponse(serde_json::Error),
}

impl Error {
fn to_json_rpc_error_code(&self) -> i32 {
match self {
Error::InvalidProjectId(_) => -1,
Error::PrepareCalls(_) => -2, // TODO more specific codes
Error::SendPreparedCalls(_) => -3, // TODO more specific codes
Error::MethodNotFound => -32601,
Error::InvalidParams(_) => -32602,
Error::Internal(_) => -32000,
}
}
}

#[tracing::instrument(skip(state), level = "debug")]
async fn handle_rpc(
state: State<Arc<AppState>>,
Query(query): Query<WalletQueryParams>,
method: Arc<str>,
params: serde_json::Value,
) -> Result<serde_json::Value, Error> {
let project_id = query.project_id;
state
.validate_project_access_and_quota(&project_id)
.await
// TODO refactor to differentiate between user and server errors
.map_err(Error::InvalidProjectId)?;

match method.as_ref() {
WALLET_PREPARE_CALLS => serde_json::to_value(
&prepare_calls::handler(
state,
project_id,
serde_json::from_value(params).map_err(Error::InvalidParams)?,
)
.await
.map_err(Error::PrepareCalls)?,
)
.map_err(|e| Error::Internal(InternalError::SerializeResponse(e))),
WALLET_SEND_PREPARED_CALLS => serde_json::to_value(
&send_prepared_calls::handler(
state,
project_id,
serde_json::from_value(params).map_err(Error::InvalidParams)?,
)
.await
.map_err(Error::SendPreparedCalls)?,
)
.map_err(|e| Error::Internal(InternalError::SerializeResponse(e))),
_ => Err(Error::MethodNotFound),
}
}
1 change: 1 addition & 0 deletions src/handlers/wallet/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod prepare_calls;
pub mod send_prepared_calls;
mod types;
pub mod handler;
33 changes: 10 additions & 23 deletions src/handlers/wallet/prepare_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use alloy::providers::{Provider, ReqwestProvider};
use alloy::sol_types::SolCall;
use alloy::sol_types::SolValue;
use alloy::transports::Transport;
use axum::extract::Query;
use axum::{
extract::State,
response::{IntoResponse, Response},
Expand All @@ -40,12 +39,6 @@ use yttrium::{
user_operation::{user_operation_hash::UserOperationHash, UserOperationV07},
};

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PrepareCallsQueryParams {
pub project_id: String,
}

pub type PrepareCallsRequest = Vec<PrepareCallsRequestItem>;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -181,26 +174,20 @@ impl IntoResponse for PrepareCallsError {

pub async fn handler(
state: State<Arc<AppState>>,
query: Query<PrepareCallsQueryParams>,
Json(request_payload): Json<PrepareCallsRequest>,
) -> Result<Response, PrepareCallsError> {
handler_internal(state, query, request_payload)
project_id: String,
request: PrepareCallsRequest,
) -> Result<PrepareCallsResponse, PrepareCallsError> {
handler_internal(state, project_id, request)
.with_metrics(HANDLER_TASK_METRICS.with_name("wallet_prepare_calls"))
.await
}

#[tracing::instrument(skip(state), level = "debug")]
async fn handler_internal(
state: State<Arc<AppState>>,
query: Query<PrepareCallsQueryParams>,
project_id: String,
request: PrepareCallsRequest,
) -> Result<Response, PrepareCallsError> {
// TODO refactor to differentiate between user and server errors
state
.validate_project_access_and_quota(&query.project_id)
.await
.map_err(PrepareCallsError::InvalidProjectId)?;

) -> Result<PrepareCallsResponse, PrepareCallsError> {
let mut response = Vec::with_capacity(request.len());
for request in request {
let chain_id = ChainId::new_eip155(request.chain_id.to::<u64>());
Expand Down Expand Up @@ -235,7 +222,7 @@ async fn handler_internal(
format!(
"https://rpc.walletconnect.com/v1?chainId={}&projectId={}&source={}",
chain_id.caip2_identifier(),
query.project_id,
project_id,
MessageSource::WalletPrepareCalls,
)
.parse()
Expand Down Expand Up @@ -285,7 +272,7 @@ async fn handler_internal(
format!(
"https://rpc.walletconnect.com/v1/bundler?chainId={}&projectId={}&bundler=pimlico",
chain_id.caip2_identifier(),
query.project_id,
project_id,
)
.parse()
.unwrap(),
Expand Down Expand Up @@ -324,7 +311,7 @@ async fn handler_internal(
format!(
"https://rpc.walletconnect.com/v1/bundler?chainId={}&projectId={}&bundler=pimlico",
chain_id.caip2_identifier(),
query.project_id,
project_id,
)
.parse()
.unwrap(),
Expand Down Expand Up @@ -373,7 +360,7 @@ async fn handler_internal(
});
}

Ok(Json(response).into_response())
Ok(response)
}

pub fn split_permissions_context_and_check_validator(
Expand Down
61 changes: 32 additions & 29 deletions src/handlers/wallet/send_prepared_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ use yttrium::{
user_operation::UserOperationV07,
};

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SendPreparedCallsQueryParams {
pub project_id: String,
}

pub type SendPreparedCallsRequest = Vec<SendPreparedCallsRequestItem>;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -74,7 +68,7 @@ pub enum SendPreparedCallsError {
#[error("Invalid chain ID")]
InvalidChainId,
#[error("Cosign error: {0}")]
Cosign(RpcError),
Cosign(String),

#[error("Permission not found")]
PermissionNotFound,
Expand Down Expand Up @@ -122,7 +116,7 @@ pub enum SendPreparedCallsInternalError {
IrnNotConfigured,

#[error("Cosign: {0}")]
Cosign(RpcError),
Cosign(String),

#[error("Cosign unsuccessful: {0:?}")]
CosignUnsuccessful(std::result::Result<hyper::body::Bytes, axum::Error>),
Expand Down Expand Up @@ -177,26 +171,20 @@ impl IntoResponse for SendPreparedCallsError {

pub async fn handler(
state: State<Arc<AppState>>,
query: Query<SendPreparedCallsQueryParams>,
Json(request_payload): Json<SendPreparedCallsRequest>,
) -> Result<Response, SendPreparedCallsError> {
handler_internal(state, query, request_payload)
project_id: String,
request: SendPreparedCallsRequest,
) -> Result<SendPreparedCallsResponse, SendPreparedCallsError> {
handler_internal(state, project_id, request)
.with_metrics(HANDLER_TASK_METRICS.with_name("wallet_send_prepared_calls"))
.await
}

#[tracing::instrument(skip(state), level = "debug")]
async fn handler_internal(
state: State<Arc<AppState>>,
query: Query<SendPreparedCallsQueryParams>,
project_id: String,
request: SendPreparedCallsRequest,
) -> Result<Response, SendPreparedCallsError> {
// TODO refactor to differentiate between user and server errors
state
.validate_project_access_and_quota(&query.project_id)
.await
.map_err(SendPreparedCallsError::InvalidProjectId)?;

) -> Result<SendPreparedCallsResponse, SendPreparedCallsError> {
let mut response = Vec::with_capacity(request.len());
for request in request {
let chain_id = ChainId::new_eip155(request.prepared_calls.chain_id.to::<u64>());
Expand Down Expand Up @@ -306,12 +294,27 @@ async fn handler_internal(
.await
{
Ok(response) => response,
Err(e) => return Ok(e.into_response()),
// if e.clone().into_response().status().is_server_error() {
// SendPreparedCallsError::InternalError(SendPreparedCallsInternalError::Cosign(e))
// } else {
// SendPreparedCallsError::Cosign(e)
// }
Err(e) => {
let response = e.into_response();
let status = response.status();
let response = String::from_utf8(
to_bytes(response.into_body())
.await
// Lazy error handling here for now. We will refactor soon to avoid all this
.unwrap_or_default()
.to_vec(),
)
// Lazy error handling here for now. We will refactor soon to avoid all this
.unwrap_or_default();
let e = if status.is_server_error() {
SendPreparedCallsError::InternalError(
SendPreparedCallsInternalError::Cosign(response),
)
} else {
SendPreparedCallsError::Cosign(response)
};
return Err(e);
}
};
if !response.status().is_success() {
return Err(SendPreparedCallsError::InternalError(
Expand Down Expand Up @@ -374,7 +377,7 @@ async fn handler_internal(
format!(
"https://rpc.walletconnect.com/v1?chainId={}&projectId={}&source={}",
chain_id.caip2_identifier(),
query.project_id,
project_id,
MessageSource::WalletSendPreparedCalls,
)
.parse()
Expand Down Expand Up @@ -441,7 +444,7 @@ async fn handler_internal(
format!(
"https://rpc.walletconnect.com/v1/bundler?chainId={}&projectId={}&bundler=pimlico",
chain_id.caip2_identifier(),
query.project_id,
project_id,
)
.parse()
.unwrap(),
Expand All @@ -459,5 +462,5 @@ async fn handler_internal(
response.push(SendPreparedCallsResponseItem { user_op_hash });
}

Ok(Json(response).into_response())
Ok(response)
}
Loading

0 comments on commit e9b51f7

Please sign in to comment.