Skip to content

Commit aa94efd

Browse files
committed
plumb it all together
1 parent 3aa3611 commit aa94efd

File tree

25 files changed

+2176
-491
lines changed

25 files changed

+2176
-491
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aa-core/src/userop/deployment.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub trait DeploymentCache: Send + Sync {
99
&self,
1010
chain_id: u64,
1111
account_address: &Address,
12-
) -> impl Future<Output = Option<bool>> + Send + Sync;
12+
) -> impl Future<Output = Option<bool>> + Send;
1313
}
1414

1515
pub enum AcquireLockResult {
@@ -26,15 +26,22 @@ pub trait DeploymentLock: Send + Sync {
2626
&self,
2727
chain_id: u64,
2828
account_address: &Address,
29-
) -> impl Future<Output = Option<(LockId, Duration)>> + Send + Sync;
29+
) -> impl Future<Output = Option<(LockId, Duration)>> + Send;
3030

3131
/// Try to acquire a deployment lock
3232
/// Returns true if successful, false if already locked
3333
fn acquire_lock(
3434
&self,
3535
chain_id: u64,
3636
account_address: &Address,
37-
) -> impl Future<Output = Result<AcquireLockResult, EngineError>> + Send + Sync;
37+
) -> impl Future<Output = Result<AcquireLockResult, EngineError>> + Send;
38+
39+
/// Release a deployment lock
40+
fn release_lock(
41+
&self,
42+
chain_id: u64,
43+
account_address: &Address,
44+
) -> impl Future<Output = Result<bool, EngineError>> + Send;
3845
}
3946

4047
pub enum DeploymentStatus {

core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ edition = "2024"
55

66
[dependencies]
77
aide = { version = "0.14.2", features = ["axum"] }
8-
alloy = { version = "1.0.8", features = ["serde"] }
8+
alloy = { version = "1.0.8", features = ["serde", "json-rpc"] }
99
schemars = "0.8.22"
1010
serde = "1.0.219"
1111
serde_json = "1.0.140"

core/src/chain.rs

Lines changed: 123 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,53 @@
1-
use crate::rpc_clients::{BundlerClient, PaymasterClient};
1+
use crate::rpc_clients::{BundlerClient, PaymasterClient, transport::SharedClientTransportBuilder};
22
use alloy::{
33
providers::{ProviderBuilder, RootProvider},
4-
transports::http::{
5-
Http,
6-
reqwest::{
7-
ClientBuilder as HttpClientBuilder, Url,
8-
header::{HeaderMap, HeaderValue},
9-
},
4+
rpc::client::RpcClient,
5+
transports::http::reqwest::{
6+
ClientBuilder as HttpClientBuilder, Url,
7+
header::{HeaderMap, HeaderValue},
108
},
119
};
10+
use serde::{Deserialize, Serialize};
1211

1312
use crate::error::EngineError;
1413

14+
#[derive(Clone, Debug, Serialize, Deserialize)]
15+
pub struct ThirdwebClientIdAndServiceKey {
16+
pub client_id: String,
17+
pub service_key: String,
18+
}
19+
20+
#[derive(Clone, Debug, Serialize, Deserialize)]
21+
pub enum ThirdwebRpcCredentials {
22+
ClientIdServiceKey(ThirdwebClientIdAndServiceKey),
23+
}
24+
25+
impl ThirdwebRpcCredentials {
26+
pub fn to_header_map(&self) -> Result<HeaderMap, EngineError> {
27+
match self {
28+
ThirdwebRpcCredentials::ClientIdServiceKey(creds) => {
29+
let mut headers = HeaderMap::new();
30+
headers.insert("x-client-id", HeaderValue::from_str(&creds.client_id)?);
31+
headers.insert("x-service-key", HeaderValue::from_str(&creds.service_key)?);
32+
Ok(headers)
33+
}
34+
}
35+
}
36+
}
37+
38+
#[derive(Clone, Debug, Serialize, Deserialize)]
39+
pub enum RpcCredentials {
40+
Thirdweb(ThirdwebRpcCredentials),
41+
}
42+
43+
impl RpcCredentials {
44+
pub fn to_header_map(&self) -> Result<HeaderMap, EngineError> {
45+
match self {
46+
RpcCredentials::Thirdweb(creds) => creds.to_header_map(),
47+
}
48+
}
49+
}
50+
1551
pub trait Chain: Send + Sync {
1652
fn chain_id(&self) -> u64;
1753
fn rpc_url(&self) -> Url;
@@ -21,6 +57,11 @@ pub trait Chain: Send + Sync {
2157
fn provider(&self) -> &RootProvider;
2258
fn bundler_client(&self) -> &BundlerClient;
2359
fn paymaster_client(&self) -> &PaymasterClient;
60+
61+
fn bundler_client_with_headers(&self, headers: HeaderMap) -> BundlerClient;
62+
fn paymaster_client_with_headers(&self, headers: HeaderMap) -> PaymasterClient;
63+
64+
fn with_new_default_headers(&self, headers: HeaderMap) -> Self;
2465
}
2566

2667
pub struct ThirdwebChainConfig<'a> {
@@ -32,15 +73,22 @@ pub struct ThirdwebChainConfig<'a> {
3273
pub client_id: &'a str,
3374
}
3475

76+
#[derive(Clone)]
3577
pub struct ThirdwebChain {
78+
transport_builder: SharedClientTransportBuilder,
79+
3680
chain_id: u64,
3781
rpc_url: Url,
3882
bundler_url: Url,
3983
paymaster_url: Url,
4084

85+
/// Default clients (these also use the shared connection pool)
4186
pub bundler_client: BundlerClient,
4287
pub paymaster_client: PaymasterClient,
4388
pub provider: RootProvider,
89+
90+
pub sensitive_bundler_client: BundlerClient,
91+
pub sensitive_paymaster_client: PaymasterClient,
4492
}
4593

4694
impl Chain for ThirdwebChain {
@@ -71,6 +119,30 @@ impl Chain for ThirdwebChain {
71119
fn paymaster_client(&self) -> &PaymasterClient {
72120
&self.paymaster_client
73121
}
122+
123+
fn bundler_client_with_headers(&self, headers: HeaderMap) -> BundlerClient {
124+
let transport = self
125+
.transport_builder
126+
.with_headers(self.bundler_url.clone(), headers);
127+
let rpc_client = RpcClient::builder().transport(transport, false);
128+
BundlerClient { inner: rpc_client }
129+
}
130+
131+
fn paymaster_client_with_headers(&self, headers: HeaderMap) -> PaymasterClient {
132+
let transport = self
133+
.transport_builder
134+
.with_headers(self.paymaster_url.clone(), headers);
135+
let rpc_client = RpcClient::builder().transport(transport, false);
136+
PaymasterClient { inner: rpc_client }
137+
}
138+
139+
fn with_new_default_headers(&self, headers: HeaderMap) -> Self {
140+
let mut new_self = self.to_owned();
141+
new_self.bundler_client = self.bundler_client_with_headers(headers.clone());
142+
new_self.paymaster_client = self.paymaster_client_with_headers(headers.clone());
143+
144+
new_self
145+
}
74146
}
75147

76148
impl<'a> ThirdwebChainConfig<'a> {
@@ -103,36 +175,67 @@ impl<'a> ThirdwebChainConfig<'a> {
103175
message: format!("Failed to parse Paymaster URL: {}", e.to_string()),
104176
})?;
105177

106-
let mut headers = HeaderMap::new();
107-
headers.insert(
178+
let mut sensitive_headers = HeaderMap::new();
179+
sensitive_headers.insert(
108180
"x-client-id",
109181
HeaderValue::from_str(&self.client_id).map_err(|e| EngineError::RpcConfigError {
110182
message: format!("Unserialisable client-id used: {e}"),
111183
})?,
112184
);
113185

114-
headers.insert(
186+
sensitive_headers.insert(
115187
"x-secret-key",
116188
HeaderValue::from_str(&self.secret_key).map_err(|e| EngineError::RpcConfigError {
117189
message: format!("Unserialisable secret-key used: {e}"),
118190
})?,
119191
);
120192

121-
let reqwest_client = HttpClientBuilder::new()
122-
.default_headers(headers)
123-
.build()
124-
.map_err(|e| EngineError::RpcConfigError {
125-
message: format!("Failed to build HTTP client: {e}"),
126-
})?;
193+
let reqwest_client =
194+
HttpClientBuilder::new()
195+
.build()
196+
.map_err(|e| EngineError::RpcConfigError {
197+
message: format!("Failed to build HTTP client: {e}"),
198+
})?;
199+
200+
let transport_builder = SharedClientTransportBuilder::new(reqwest_client.clone());
127201

128-
let paymaster_transport = Http::with_client(reqwest_client.clone(), paymaster_url.clone());
129-
let bundler_transport = Http::with_client(reqwest_client, bundler_url.clone());
202+
let paymaster_transport = transport_builder.default_transport(paymaster_url.clone());
203+
let bundler_transport = transport_builder.default_transport(bundler_url.clone());
204+
205+
let sensitive_bundler_transport =
206+
transport_builder.with_headers(bundler_url.clone(), sensitive_headers.clone());
207+
let sensitive_paymaster_transport =
208+
transport_builder.with_headers(paymaster_url.clone(), sensitive_headers);
209+
210+
let paymaster_rpc_client = RpcClient::builder().transport(paymaster_transport, false);
211+
let bundler_rpc_client = RpcClient::builder().transport(bundler_transport, false);
212+
213+
let sensitive_bundler_rpc_client =
214+
RpcClient::builder().transport(sensitive_bundler_transport, false);
215+
let sensitive_paymaster_rpc_client =
216+
RpcClient::builder().transport(sensitive_paymaster_transport, false);
130217

131218
Ok(ThirdwebChain {
219+
transport_builder,
220+
132221
chain_id: self.chain_id,
133222
rpc_url: rpc_url.clone(),
134-
bundler_client: BundlerClient::new(bundler_transport),
135-
paymaster_client: PaymasterClient::new(paymaster_transport),
223+
224+
bundler_client: BundlerClient {
225+
inner: bundler_rpc_client,
226+
},
227+
paymaster_client: PaymasterClient {
228+
inner: paymaster_rpc_client,
229+
},
230+
231+
sensitive_bundler_client: BundlerClient {
232+
inner: sensitive_bundler_rpc_client,
233+
},
234+
235+
sensitive_paymaster_client: PaymasterClient {
236+
inner: sensitive_paymaster_rpc_client,
237+
},
238+
136239
provider: ProviderBuilder::new()
137240
.disable_recommended_fillers()
138241
.connect_http(rpc_url)

core/src/error.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use alloy::{
22
primitives::Address,
3-
transports::{RpcError as AlloyRpcError, TransportErrorKind},
3+
transports::{
4+
RpcError as AlloyRpcError, TransportErrorKind, http::reqwest::header::InvalidHeaderValue,
5+
},
46
};
57
use serde::{Deserialize, Serialize};
68
use thiserror::Error;
9+
use twmq::error::TwmqError;
710

811
use crate::chain::Chain;
912

@@ -179,6 +182,20 @@ pub enum EngineError {
179182
/// Specific error kind
180183
kind: ContractInteractionErrorKind,
181184
},
185+
186+
#[error("Validation error: {message}")]
187+
ValidationError { message: String },
188+
189+
#[error("Internal error: {0}")]
190+
InternalError(String),
191+
}
192+
193+
impl From<InvalidHeaderValue> for EngineError {
194+
fn from(err: InvalidHeaderValue) -> Self {
195+
EngineError::ValidationError {
196+
message: err.to_string(),
197+
}
198+
}
182199
}
183200

184201
pub trait AlloyRpcErrorToEngineError {
@@ -299,3 +316,15 @@ impl ContractErrorToEngineError for alloy::contract::Error {
299316
}
300317
}
301318
}
319+
320+
impl From<twmq::redis::RedisError> for EngineError {
321+
fn from(error: twmq::redis::RedisError) -> Self {
322+
EngineError::InternalError(error.to_string())
323+
}
324+
}
325+
326+
impl From<TwmqError> for EngineError {
327+
fn from(error: TwmqError) -> Self {
328+
EngineError::InternalError(error.to_string())
329+
}
330+
}

core/src/execution_options/mod.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use serde::{Deserialize, Serialize};
2+
3+
use crate::transaction::InnerTransaction;
24
pub mod aa;
35

46
#[derive(Debug, Clone, Serialize, Deserialize)]
57
#[serde(rename_all = "camelCase")]
68
pub struct BaseExecutionOptions {
7-
chain_id: u64,
9+
pub chain_id: u64,
810
#[serde(default = "default_idempotency_key")]
9-
idempotency_key: String,
11+
pub idempotency_key: String,
1012
}
1113

1214
fn default_idempotency_key() -> String {
@@ -26,3 +28,50 @@ pub struct ExecutionOptions {
2628
#[serde(flatten)]
2729
pub specific: SpecificExecutionOptions,
2830
}
31+
32+
#[derive(Debug, Clone, Serialize, Deserialize)]
33+
#[serde(rename_all = "camelCase")]
34+
pub struct TransactionRequest {
35+
#[serde(flatten)]
36+
pub execution_options: ExecutionOptions,
37+
pub params: Vec<InnerTransaction>,
38+
pub webhook_url: Option<String>,
39+
}
40+
41+
#[derive(Debug, Clone, Serialize, Deserialize)]
42+
#[serde(rename_all = "camelCase")]
43+
pub struct TransactionResponse {
44+
pub transaction_id: String,
45+
}
46+
47+
#[derive(Debug, Clone, Serialize, Deserialize)]
48+
#[serde(rename_all = "camelCase")]
49+
pub struct QueuedNotification {
50+
pub transaction_id: String,
51+
pub executor_type: ExecutorType,
52+
pub execution_options: ExecutionOptions,
53+
pub timestamp: u64,
54+
pub queue_name: String,
55+
}
56+
57+
#[derive(Debug, Clone, Serialize, Deserialize)]
58+
#[serde(rename_all = "snake_case")]
59+
pub enum ExecutorType {
60+
Erc4337,
61+
}
62+
63+
impl ExecutionOptions {
64+
pub fn executor_type(&self) -> ExecutorType {
65+
match &self.specific {
66+
SpecificExecutionOptions::ERC4337(_) => ExecutorType::Erc4337,
67+
}
68+
}
69+
70+
pub fn chain_id(&self) -> u64 {
71+
self.base.chain_id
72+
}
73+
74+
pub fn transaction_id(&self) -> &str {
75+
&self.base.idempotency_key
76+
}
77+
}

0 commit comments

Comments
 (0)