Skip to content

Commit 52c3bb3

Browse files
committed
contract endpoints POC
1 parent aa94efd commit 52c3bb3

File tree

34 files changed

+1552
-451
lines changed

34 files changed

+1552
-451
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[workspace]
2+
members = ["aa-core", "core", "executors", "server", "thirdweb-core", "twmq"]
23
resolver = "2"
3-
members = ["aa-core", "core", "executors", "server", "twmq"]

core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ tower = "0.5.2"
1616
tracing = "0.1.41"
1717
async-nats = "0.40.0"
1818
twmq = { version = "0.1.0", path = "../twmq" }
19+
thirdweb-core = { version = "0.1.0", path = "../thirdweb-core" }
1920
uuid = { version = "1.17.0", features = ["v4"] }

core/src/chain.rs

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,22 @@ use alloy::{
88
},
99
};
1010
use serde::{Deserialize, Serialize};
11+
use thirdweb_core::auth::ThirdwebAuth;
1112

1213
use crate::error::EngineError;
1314

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-
3815
#[derive(Clone, Debug, Serialize, Deserialize)]
3916
pub enum RpcCredentials {
40-
Thirdweb(ThirdwebRpcCredentials),
17+
Thirdweb(ThirdwebAuth),
4118
}
4219

4320
impl RpcCredentials {
4421
pub fn to_header_map(&self) -> Result<HeaderMap, EngineError> {
45-
match self {
46-
RpcCredentials::Thirdweb(creds) => creds.to_header_map(),
47-
}
22+
let header_map = match self {
23+
RpcCredentials::Thirdweb(creds) => creds.to_header_map()?,
24+
};
25+
26+
Ok(header_map)
4827
}
4928
}
5029

core/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use alloy::{
55
},
66
};
77
use serde::{Deserialize, Serialize};
8+
use thirdweb_core::error::ThirdwebError;
89
use thiserror::Error;
910
use twmq::error::TwmqError;
1011

@@ -186,6 +187,9 @@ pub enum EngineError {
186187
#[error("Validation error: {message}")]
187188
ValidationError { message: String },
188189

190+
#[error("Thirdweb error: {0}")]
191+
ThirdwebError(#[from] ThirdwebError),
192+
189193
#[error("Internal error: {0}")]
190194
InternalError(String),
191195
}

core/src/execution_options/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ pub struct ExecutionOptions {
2929
pub specific: SpecificExecutionOptions,
3030
}
3131

32+
#[derive(Serialize, Deserialize, Debug, Clone)]
33+
pub struct WebhookOptions {
34+
pub url: String,
35+
}
36+
3237
#[derive(Debug, Clone, Serialize, Deserialize)]
3338
#[serde(rename_all = "camelCase")]
3439
pub struct TransactionRequest {
35-
#[serde(flatten)]
3640
pub execution_options: ExecutionOptions,
3741
pub params: Vec<InnerTransaction>,
38-
pub webhook_url: Option<String>,
42+
pub webhook_options: Option<WebhookOptions>,
3943
}
40-
4144
#[derive(Debug, Clone, Serialize, Deserialize)]
4245
#[serde(rename_all = "camelCase")]
4346
pub struct TransactionResponse {

core/src/rpc_clients/bundler.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use alloy::consensus::{Receipt, ReceiptWithBloom};
12
use alloy::primitives::{Address, Bytes, U256};
23
use alloy::rpc::client::RpcClient;
3-
use alloy::rpc::types::UserOperationReceipt;
4+
use alloy::rpc::types::{Log, TransactionReceipt};
45
use alloy::transports::{IntoBoxTransport, TransportResult};
56
use serde::{Deserialize, Serialize};
67
use std::collections::HashMap;
@@ -28,6 +29,37 @@ pub struct UseropGasEstimation {
2829
pub paymaster_post_op_gas_limit: Option<U256>,
2930
}
3031

32+
/// Represents the receipt of a user operation.
33+
// Not using UserOperationReceipt from alloy because it uses `TransactionReceipt<ReceiptEnvelope<Log>>`, which depends on a tagged enum by `type` field
34+
// RPC response for receipt does not return `type` field, so we cannot use `TransactionReceipt<ReceiptEnvelope<Log>>` directly
35+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36+
#[serde(rename_all = "camelCase")]
37+
pub struct UserOperationReceipt {
38+
/// The hash of the user operation.
39+
pub user_op_hash: Bytes,
40+
/// The entry point address for the user operation.
41+
pub entry_point: Address,
42+
/// The address of the sender of the user operation.
43+
pub sender: Address,
44+
/// The nonce of the user operation.
45+
pub nonce: U256,
46+
/// The address of the paymaster, if any.
47+
pub paymaster: Address,
48+
/// The actual gas cost incurred by the user operation.
49+
pub actual_gas_cost: U256,
50+
/// The actual gas used by the user operation.
51+
pub actual_gas_used: U256,
52+
/// Indicates whether the user operation was successful.
53+
pub success: bool,
54+
/// The reason for failure, if any.
55+
#[serde(skip_serializing_if = "Option::is_none")]
56+
pub reason: Option<Bytes>,
57+
/// The logs generated by the user operation.
58+
pub logs: Vec<Log>,
59+
/// The transaction receipt of the user operation.
60+
pub receipt: TransactionReceipt<ReceiptWithBloom<Receipt<Log>>>,
61+
}
62+
3163
impl BundlerClient {
3264
/// Create a new bundler client with the given transport
3365
pub fn new(transport: impl IntoBoxTransport) -> Self {

executors/src/external_bundler/confirm.rs

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
use alloy::{
2-
primitives::{Address, Bytes, U256},
3-
rpc::types::UserOperationReceipt,
4-
};
1+
use alloy::primitives::{Address, Bytes, U256};
52
use engine_core::{
6-
chain::{Chain, ChainService},
3+
chain::{Chain, ChainService, RpcCredentials},
74
error::AlloyRpcErrorToEngineError,
5+
execution_options::WebhookOptions,
6+
rpc_clients::UserOperationReceipt,
87
};
98
use serde::{Deserialize, Serialize};
109
use std::{sync::Arc, time::Duration};
1110
use twmq::{
1211
DurableExecution, FailHookData, NackHookData, Queue, SuccessHookData,
12+
error::TwmqError,
1313
hooks::TransactionContext,
1414
job::{Job, JobResult, RequeuePosition, ToJobResult},
1515
};
@@ -19,7 +19,7 @@ use crate::webhook::{
1919
envelope::{ExecutorStage, HasWebhookOptions, WebhookCapable},
2020
};
2121

22-
use super::{deployment::RedisDeploymentLock, send::WebhookOptions};
22+
use super::deployment::RedisDeploymentLock;
2323

2424
// --- Job Payload ---
2525
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -32,8 +32,8 @@ pub struct UserOpConfirmationJobData {
3232
pub nonce: U256,
3333
pub deployment_lock_acquired: bool,
3434
pub webhook_options: Option<WebhookOptions>,
35-
pub thirdweb_client_id: Option<String>,
36-
pub thirdweb_service_key: Option<String>,
35+
36+
pub rpc_credentials: RpcCredentials,
3737
}
3838

3939
// --- Success Result ---
@@ -47,7 +47,7 @@ pub struct UserOpConfirmationResult {
4747

4848
// --- Error Types ---
4949
#[derive(Serialize, Deserialize, Debug, Clone, thiserror::Error)]
50-
#[serde(rename_all = "camelCase", tag = "errorCode")]
50+
#[serde(rename_all = "SCREAMING_SNAKE_CASE", tag = "errorCode")]
5151
pub enum UserOpConfirmationError {
5252
#[error("Chain service error for chainId {chain_id}: {message}")]
5353
ChainServiceError { chain_id: u64, message: String },
@@ -63,13 +63,21 @@ pub enum UserOpConfirmationError {
6363
user_op_hash: Bytes,
6464
message: String,
6565
#[serde(skip_serializing_if = "Option::is_none")]
66-
technical_details: Option<String>,
66+
inner_error: Option<serde_json::Value>,
6767
},
6868

6969
#[error("Internal error: {message}")]
7070
InternalError { message: String },
7171
}
7272

73+
impl From<TwmqError> for UserOpConfirmationError {
74+
fn from(error: TwmqError) -> Self {
75+
UserOpConfirmationError::InternalError {
76+
message: format!("Deserialization error for job data: {}", error.to_string()),
77+
}
78+
}
79+
}
80+
7381
// --- Handler ---
7482
pub struct UserOpConfirmationHandler<CS>
7583
where
@@ -115,6 +123,7 @@ where
115123
type ErrorData = UserOpConfirmationError;
116124
type JobData = UserOpConfirmationJobData;
117125

126+
#[tracing::instrument(skip(self, job), fields(transaction_id = job.id, stage = Self::stage_name(), executor = Self::executor_name()))]
118127
async fn process(&self, job: &Job<Self::JobData>) -> JobResult<Self::Output, Self::ErrorData> {
119128
let job_data = &job.data;
120129

@@ -126,28 +135,29 @@ where
126135
chain_id: job_data.chain_id,
127136
message: format!("Failed to get chain instance: {}", e),
128137
})
129-
.fail_err()?;
138+
.map_err_fail()?;
139+
140+
let chain = chain.with_new_default_headers(
141+
job.data
142+
.rpc_credentials
143+
.to_header_map()
144+
.map_err(|e| UserOpConfirmationError::InternalError {
145+
message: format!("Bad RPC Credential values, unserialisable into headers: {e}"),
146+
})
147+
.map_err_fail()?,
148+
);
130149

131150
// 2. Query for User Operation Receipt
132-
let receipt_result = chain
151+
let receipt_option = chain
133152
.bundler_client()
134153
.get_user_op_receipt(job_data.user_op_hash.clone())
135154
.await
136155
.map_err(|e| UserOpConfirmationError::ReceiptQueryFailed {
137156
user_op_hash: job_data.user_op_hash.clone(),
138157
message: e.to_string(),
139-
technical_details: Some(
140-
serde_json::to_string(&e.to_engine_bundler_error(&chain)).unwrap_or_default(),
141-
),
142-
});
143-
144-
let receipt_option = match receipt_result {
145-
Ok(opt) => opt,
146-
Err(e) => {
147-
// Network/RPC errors might be temporary, retry
148-
return Err(e).nack_err(Some(self.confirmation_retry_delay), RequeuePosition::Last);
149-
}
150-
};
158+
inner_error: serde_json::to_value(&e.to_engine_bundler_error(&chain)).ok(),
159+
})
160+
.map_err_nack(Some(self.confirmation_retry_delay), RequeuePosition::Last)?;
151161

152162
let receipt = match receipt_option {
153163
Some(receipt) => receipt,
@@ -158,14 +168,14 @@ where
158168
user_op_hash: job_data.user_op_hash.clone(),
159169
attempt_number: job.attempts,
160170
})
161-
.fail_err(); // FAIL - triggers on_fail hook which will release lock
171+
.map_err_fail(); // FAIL - triggers on_fail hook which will release lock
162172
}
163173

164174
return Err(UserOpConfirmationError::ReceiptNotAvailable {
165175
user_op_hash: job_data.user_op_hash.clone(),
166176
attempt_number: job.attempts,
167177
})
168-
.nack_err(Some(self.confirmation_retry_delay), RequeuePosition::Last);
178+
.map_err_nack(Some(self.confirmation_retry_delay), RequeuePosition::Last);
169179
// NACK - triggers on_nack hook which keeps lock for retry
170180
}
171181
};
@@ -299,13 +309,7 @@ where
299309

300310
impl HasWebhookOptions for UserOpConfirmationJobData {
301311
fn webhook_url(&self) -> Option<String> {
302-
self.webhook_options
303-
.as_ref()
304-
.map(|opts| opts.webhook_url.clone())
305-
}
306-
307-
fn transaction_id(&self) -> String {
308-
self.transaction_id.clone()
312+
self.webhook_options.as_ref().map(|opts| opts.url.clone())
309313
}
310314
}
311315

0 commit comments

Comments
 (0)