Skip to content

Commit 36b2c2b

Browse files
committed
async bundler
1 parent cf71472 commit 36b2c2b

File tree

12 files changed

+759
-58
lines changed

12 files changed

+759
-58
lines changed

drizzle/meta/0000_snapshot.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"id": "67d61535-ec60-44c0-a6b3-e1aea354e8d3",
2+
"id": "5fbb4f8e-28bd-4802-877f-23a45622e6c6",
33
"prevId": "00000000-0000-0000-0000-000000000000",
44
"version": "7",
55
"dialect": "postgresql",

drizzle/meta/_journal.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
{
66
"idx": 0,
77
"version": "7",
8-
"when": 1740357427731,
9-
"tag": "0000_deep_stephen_strange",
8+
"when": 1740684747056,
9+
"tag": "0000_broad_blacklash",
1010
"breakpoints": true
1111
}
1212
]

src/db/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ export type RevertDataSerialized = {
4040
};
4141

4242
export type ExecutionResult4337Serialized =
43+
| {
44+
status: "QUEUED";
45+
}
4346
| {
4447
status: "SUBMITTED";
4548
monitoringStatus: "WILL_MONITOR" | "CANNOT_MONITOR";

src/executors/execute/execute.ts

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
type EngineErr,
99
} from "../../lib/errors";
1010
import { execute as executeExternalBundler } from "../external-bundler";
11+
import { execute as executeExternalBundlerAsync } from "../external-bundler-async";
1112
import { getChain } from "../../lib/chain";
1213
import {
1314
getContract,
@@ -32,11 +33,13 @@ import SuperJSON from "superjson";
3233
import type {
3334
ExecutionParamsSerialized,
3435
TransactionParamsSerialized,
36+
ExecutionResult4337Serialized,
3537
} from "../../db/types";
3638
import "./external-bundler-confirm-handler";
39+
import "./external-bundler-send-handler";
3740

3841
function getExecutionAccountFromRequest(
39-
request: EncodedExecutionRequest,
42+
request: EncodedExecutionRequest
4043
): ResultAsync<
4144
| {
4245
signerAccount: Account;
@@ -57,7 +60,7 @@ function getExecutionAccountFromRequest(
5760
yield* getEngineAccount({
5861
address: request.from,
5962
encryptionPassword: request.encryptionPassword,
60-
}),
63+
})
6164
);
6265
}
6366

@@ -241,7 +244,7 @@ export function execute({
241244
const chain = await getChain(Number.parseInt(request.chainId));
242245

243246
const engineAccountResponse = yield* getExecutionAccountFromRequest(
244-
request,
247+
request
245248
);
246249

247250
if ("account" in engineAccountResponse) {
@@ -278,26 +281,74 @@ export function execute({
278281

279282
const idempotencyKey = request.idempotencyKey ?? randomUUID().toString();
280283

281-
const executionResult = await executeExternalBundler({
282-
id: idempotencyKey,
283-
chain,
284-
client,
285-
executionOptions,
286-
transactionParams: resolvedTransactionParams,
287-
});
284+
// Determine which executor to use based on request parameters
285+
// Currently, we use the async executor when no encryption password is provided
286+
// In the future, this can be expanded to support more executor types
287+
const executorType = !request.encryptionPassword ? "async" : "sync";
288+
289+
// Variables to track execution state
290+
let executionResult: ExecutionResult4337Serialized;
291+
let userOpHash: string | undefined;
292+
293+
// Execute using the appropriate executor
294+
if (executorType === "async") {
295+
// For async executor, we need to convert the execution options to the format it expects
296+
const asyncExecutionOptions = {
297+
signerAddress: executionOptions.signer.address as Address,
298+
entrypointAddress: executionOptions.entrypointAddress,
299+
accountFactoryAddress: executionOptions.accountFactoryAddress,
300+
sponsorGas: executionOptions.sponsorGas,
301+
smartAccountAddress: executionOptions.smartAccountAddress,
302+
accountSalt: executionOptions.accountSalt,
303+
};
304+
305+
// Execute using the async executor
306+
const asyncResult = await executeExternalBundlerAsync({
307+
id: idempotencyKey,
308+
executionOptions: asyncExecutionOptions,
309+
chainId: request.chainId,
310+
transactionParams: resolvedTransactionParams,
311+
});
312+
313+
// Handle errors from the async executor
314+
if (asyncResult.isErr()) {
315+
return errAsync(asyncResult.error);
316+
}
288317

289-
let userOpHash: Hex;
290-
let didConfirmationQueueJobError = false;
318+
// Async executor doesn't return userOpHash immediately, it's queued
319+
executionResult = {
320+
status: "QUEUED",
321+
};
322+
} else {
323+
// Execute using the sync executor
324+
const syncResult = await executeExternalBundler({
325+
id: idempotencyKey,
326+
chain,
327+
client,
328+
executionOptions,
329+
transactionParams: resolvedTransactionParams,
330+
});
291331

292-
if (executionResult.isErr()) {
293-
if (executionResult.error.kind === "queue") {
294-
didConfirmationQueueJobError = true;
295-
userOpHash = executionResult.error.userOpHash;
332+
// Handle errors from the sync executor
333+
if (syncResult.isErr()) {
334+
if (syncResult.error.kind === "queue") {
335+
userOpHash = syncResult.error.userOpHash;
336+
executionResult = {
337+
status: "SUBMITTED",
338+
monitoringStatus: "CANNOT_MONITOR",
339+
userOpHash,
340+
};
341+
} else {
342+
return errAsync(syncResult.error);
343+
}
296344
} else {
297-
return errAsync(executionResult.error);
345+
userOpHash = syncResult.value.data.userOpHash;
346+
executionResult = {
347+
status: "SUBMITTED",
348+
monitoringStatus: "WILL_MONITOR",
349+
userOpHash,
350+
};
298351
}
299-
} else {
300-
userOpHash = executionResult.value.data.userOpHash;
301352
}
302353

303354
const executionParams: ExecutionParamsSerialized = {
@@ -307,13 +358,9 @@ export function execute({
307358
signerAddress: executionOptions.signer.address,
308359
};
309360

310-
const executionResultSerialized = {
311-
status: "SUBMITTED" as const,
312-
monitoringStatus: didConfirmationQueueJobError
313-
? "CANNOT_MONITOR"
314-
: "WILL_MONITOR",
315-
userOpHash,
316-
} as const;
361+
// For database insertion, we need to ensure userOpHash is a string if status is SUBMITTED
362+
// If it's undefined, we'll use an empty string
363+
const dbExecutionResult = executionResult;
317364

318365
const dbTransactionEntry = yield* ResultAsync.fromPromise(
319366
db
@@ -325,23 +372,23 @@ export function execute({
325372
transactionParams: SuperJSON.serialize(resolvedTransactionParams)
326373
.json as TransactionParamsSerialized[],
327374
executionParams,
328-
executionResult: executionResultSerialized,
375+
executionResult: dbExecutionResult,
329376
from: executionParams.smartAccountAddress as Address,
330377
})
331378
.returning(),
332-
mapDbError,
379+
mapDbError
333380
).mapErr((e) =>
334381
buildTransactionDbEntryErr({
335382
error: e,
336383
executionParams,
337-
executionResult: executionResultSerialized,
338-
}),
384+
executionResult: dbExecutionResult,
385+
})
339386
);
340387

341388
return okAsync({
342-
executionResult: executionResultSerialized,
389+
executionResult,
343390
transactions: dbTransactionEntry,
344391
executionOptions,
345392
});
346393
});
347-
}
394+
}

src/executors/execute/external-bundler-confirm-handler.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import SuperJSON from "superjson";
22
import { db } from "../../db/connection";
33
import { transactions } from "../../db/schema";
44
import { registerCallback, type ConfirmationResult } from "../external-bundler";
5+
import { registerCallback as registerExternalBundlerAsyncConfirmCallback } from "../external-bundler-async";
56
import type {
67
ExecutionResult4337Serialized,
78
RevertDataSerialized,
@@ -10,12 +11,12 @@ import { and, eq } from "drizzle-orm";
1011
import { initializeLogger } from "../../lib/logger";
1112

1213
const confirmLogger = initializeLogger(
13-
"executor:external-bundler:confirm-handler",
14+
"executor:external-bundler:confirm-handler"
1415
);
1516

1617
// not using neverthrow here, this handler response doesn't go to the user
1718
export async function externalBundlerConfirmHandler(
18-
result: ConfirmationResult,
19+
result: ConfirmationResult
1920
) {
2021
const executionResult: ExecutionResult4337Serialized =
2122
result.onchainStatus === "REVERTED"
@@ -57,11 +58,12 @@ export async function externalBundlerConfirmHandler(
5758
})
5859
// 4337 transactions always create a single transaction in the db, with batchIndex 0
5960
.where(
60-
and(eq(transactions.id, result.id), eq(transactions.batchIndex, 0)),
61+
and(eq(transactions.id, result.id), eq(transactions.batchIndex, 0))
6162
);
6263
} catch (err) {
6364
confirmLogger.error("Failed to write confirmed transaction to DB", err);
6465
}
6566
}
6667

6768
registerCallback(externalBundlerConfirmHandler);
69+
registerExternalBundlerAsyncConfirmCallback(externalBundlerConfirmHandler);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { db } from "../../db/connection";
2+
import { transactions } from "../../db/schema";
3+
import { and, eq } from "drizzle-orm";
4+
import { initializeLogger } from "../../lib/logger";
5+
import { sendWorker, type SendResult } from "../external-bundler-async";
6+
import type { Job } from "bullmq";
7+
8+
const sendLogger = initializeLogger(
9+
"executor:external-bundler:send-handler"
10+
);
11+
12+
// This handler is called when a UserOp is sent by the async executor
13+
// It updates the transaction record with the userOpHash
14+
export async function externalBundlerSendHandler(
15+
_job: Job, // Unused parameter but required by BullMQ
16+
result: SendResult
17+
) {
18+
try {
19+
await db
20+
.update(transactions)
21+
.set({
22+
executionResult: {
23+
status: "SUBMITTED",
24+
monitoringStatus: "WILL_MONITOR",
25+
userOpHash: result.userOpHash,
26+
},
27+
})
28+
// 4337 transactions always create a single transaction in the db, with batchIndex 0
29+
.where(
30+
and(eq(transactions.id, result.id), eq(transactions.batchIndex, 0))
31+
);
32+
33+
sendLogger.info(`Updated transaction ${result.id} with userOpHash ${result.userOpHash}`);
34+
} catch (err) {
35+
sendLogger.error("Failed to update transaction with userOpHash", err, {
36+
id: result.id,
37+
userOpHash: result.userOpHash,
38+
});
39+
}
40+
}
41+
42+
// Register the send handler with the async executor's send worker
43+
sendWorker.on("completed", externalBundlerSendHandler);

0 commit comments

Comments
 (0)