@pimlico/alto 0.0.0-main.20250203T212650 → 0.0.0-main.20250205T142138

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/esm/cli/config/bundler.d.ts +0 -6
  2. package/esm/cli/config/bundler.js +0 -1
  3. package/esm/cli/config/bundler.js.map +1 -1
  4. package/esm/cli/config/options.js +0 -6
  5. package/esm/cli/config/options.js.map +1 -1
  6. package/esm/cli/setupServer.js +5 -5
  7. package/esm/cli/setupServer.js.map +1 -1
  8. package/esm/executor/executor.d.ts +34 -44
  9. package/esm/executor/executor.js +103 -463
  10. package/esm/executor/executor.js.map +1 -1
  11. package/esm/executor/executorManager.d.ts +20 -10
  12. package/esm/executor/executorManager.js +371 -310
  13. package/esm/executor/executorManager.js.map +1 -1
  14. package/esm/executor/filterOpsAndEStimateGas.d.ts +28 -0
  15. package/esm/executor/filterOpsAndEStimateGas.js +191 -0
  16. package/esm/executor/filterOpsAndEStimateGas.js.map +1 -0
  17. package/esm/executor/senderManager.d.ts +2 -0
  18. package/esm/executor/senderManager.js +32 -0
  19. package/esm/executor/senderManager.js.map +1 -1
  20. package/esm/executor/utils.d.ts +21 -20
  21. package/esm/executor/utils.js +46 -185
  22. package/esm/executor/utils.js.map +1 -1
  23. package/esm/handlers/eventManager.d.ts +4 -1
  24. package/esm/handlers/eventManager.js +10 -8
  25. package/esm/handlers/eventManager.js.map +1 -1
  26. package/esm/mempool/mempool.d.ts +15 -11
  27. package/esm/mempool/mempool.js +207 -176
  28. package/esm/mempool/mempool.js.map +1 -1
  29. package/esm/mempool/store.d.ts +7 -7
  30. package/esm/mempool/store.js +13 -12
  31. package/esm/mempool/store.js.map +1 -1
  32. package/esm/rpc/nonceQueuer.js +2 -2
  33. package/esm/rpc/nonceQueuer.js.map +1 -1
  34. package/esm/rpc/rpcHandler.js +27 -31
  35. package/esm/rpc/rpcHandler.js.map +1 -1
  36. package/esm/types/mempool.d.ts +40 -41
  37. package/esm/types/mempool.js.map +1 -1
  38. package/esm/types/schemas.d.ts +0 -4
  39. package/esm/types/schemas.js.map +1 -1
  40. package/esm/utils/userop.d.ts +8 -3
  41. package/esm/utils/userop.js +5 -3
  42. package/esm/utils/userop.js.map +1 -1
  43. package/lib/cli/config/bundler.d.ts +0 -6
  44. package/lib/cli/config/bundler.js +0 -1
  45. package/lib/cli/config/bundler.js.map +1 -1
  46. package/lib/cli/config/options.js +0 -6
  47. package/lib/cli/config/options.js.map +1 -1
  48. package/lib/cli/setupServer.js +5 -5
  49. package/lib/cli/setupServer.js.map +1 -1
  50. package/lib/executor/executor.d.ts +34 -44
  51. package/lib/executor/executor.js +100 -460
  52. package/lib/executor/executor.js.map +1 -1
  53. package/lib/executor/executorManager.d.ts +20 -10
  54. package/lib/executor/executorManager.js +370 -309
  55. package/lib/executor/executorManager.js.map +1 -1
  56. package/lib/executor/filterOpsAndEStimateGas.d.ts +28 -0
  57. package/lib/executor/filterOpsAndEStimateGas.js +218 -0
  58. package/lib/executor/filterOpsAndEStimateGas.js.map +1 -0
  59. package/lib/executor/senderManager.d.ts +2 -0
  60. package/lib/executor/senderManager.js +32 -0
  61. package/lib/executor/senderManager.js.map +1 -1
  62. package/lib/executor/utils.d.ts +21 -20
  63. package/lib/executor/utils.js +49 -186
  64. package/lib/executor/utils.js.map +1 -1
  65. package/lib/handlers/eventManager.d.ts +4 -1
  66. package/lib/handlers/eventManager.js +10 -8
  67. package/lib/handlers/eventManager.js.map +1 -1
  68. package/lib/mempool/mempool.d.ts +15 -11
  69. package/lib/mempool/mempool.js +206 -175
  70. package/lib/mempool/mempool.js.map +1 -1
  71. package/lib/mempool/store.d.ts +7 -7
  72. package/lib/mempool/store.js +13 -12
  73. package/lib/mempool/store.js.map +1 -1
  74. package/lib/rpc/nonceQueuer.js +1 -1
  75. package/lib/rpc/nonceQueuer.js.map +1 -1
  76. package/lib/rpc/rpcHandler.js +26 -30
  77. package/lib/rpc/rpcHandler.js.map +1 -1
  78. package/lib/types/mempool.d.ts +40 -41
  79. package/lib/types/mempool.js.map +1 -1
  80. package/lib/types/schemas.d.ts +0 -4
  81. package/lib/types/schemas.js.map +1 -1
  82. package/lib/utils/userop.d.ts +8 -3
  83. package/lib/utils/userop.js +7 -5
  84. package/lib/utils/userop.js.map +1 -1
  85. package/package.json +1 -1
  86. package/esm/executor/types.d.ts +0 -25
  87. package/esm/executor/types.js +0 -2
  88. package/esm/executor/types.js.map +0 -1
  89. package/lib/executor/types.d.ts +0 -25
  90. package/lib/executor/types.js +0 -3
  91. package/lib/executor/types.js.map +0 -1
@@ -4,16 +4,19 @@ exports.ExecutorManager = void 0;
4
4
  const types_1 = require("../types/index.js");
5
5
  const utils_1 = require("../utils/index.js");
6
6
  const viem_1 = require("viem");
7
- function getTransactionsFromUserOperationEntries(entries) {
8
- return Array.from(new Set(entries.map((entry) => {
9
- return entry.transactionInfo;
10
- })));
7
+ const abitype_1 = require("abitype");
8
+ const utils_2 = require("./utils.js");
9
+ function getTransactionsFromUserOperationEntries(submittedOps) {
10
+ const transactionInfos = submittedOps.map((userOpInfo) => userOpInfo.transactionInfo);
11
+ // Remove duplicates
12
+ return Array.from(new Set(transactionInfos));
11
13
  }
12
14
  const MIN_INTERVAL = 100; // 0.1 seconds (100ms)
13
15
  const MAX_INTERVAL = 1000; // Capped at 1 second (1000ms)
14
16
  const SCALE_FACTOR = 10; // Interval increases by 5ms per task per minute
15
17
  const RPM_WINDOW = 60000; // 1 minute window in ms
16
18
  class ExecutorManager {
19
+ senderManager;
17
20
  config;
18
21
  executor;
19
22
  mempool;
@@ -27,7 +30,7 @@ class ExecutorManager {
27
30
  eventManager;
28
31
  opsCount = [];
29
32
  bundlingMode;
30
- constructor({ config, executor, mempool, monitor, reputationManager, metrics, gasPriceManager, eventManager }) {
33
+ constructor({ config, executor, mempool, monitor, reputationManager, metrics, gasPriceManager, eventManager, senderManager }) {
31
34
  this.config = config;
32
35
  this.reputationManager = reputationManager;
33
36
  this.executor = executor;
@@ -39,6 +42,7 @@ class ExecutorManager {
39
42
  this.metrics = metrics;
40
43
  this.gasPriceManager = gasPriceManager;
41
44
  this.eventManager = eventManager;
45
+ this.senderManager = senderManager;
42
46
  this.bundlingMode = this.config.bundleMode;
43
47
  if (this.bundlingMode === "auto") {
44
48
  this.autoScalingBundling();
@@ -56,12 +60,18 @@ class ExecutorManager {
56
60
  async autoScalingBundling() {
57
61
  const now = Date.now();
58
62
  this.opsCount = this.opsCount.filter((timestamp) => now - timestamp < RPM_WINDOW);
59
- const opsToBundle = await this.getOpsToBundle();
60
- if (opsToBundle.length > 0) {
61
- const opsCount = opsToBundle.length;
63
+ const bundles = await this.mempool.getBundles();
64
+ if (bundles.length > 0) {
65
+ const opsCount = bundles
66
+ .map(({ userOps }) => userOps.length)
67
+ .reduce((a, b) => a + b);
68
+ // Add timestamps for each task
62
69
  const timestamp = Date.now();
63
- this.opsCount.push(...Array(opsCount).fill(timestamp)); // Add timestamps for each task
64
- await this.bundle(opsToBundle);
70
+ this.opsCount.push(...Array(opsCount).fill(timestamp));
71
+ // Send bundles to executor
72
+ await Promise.all(bundles.map(async (bundle) => {
73
+ await this.sendBundleToExecutor(bundle);
74
+ }));
65
75
  }
66
76
  const rpm = this.opsCount.length;
67
77
  // Calculate next interval with linear scaling
@@ -72,145 +82,106 @@ class ExecutorManager {
72
82
  setTimeout(this.autoScalingBundling.bind(this), nextInterval);
73
83
  }
74
84
  }
75
- async getOpsToBundle() {
76
- const opsToBundle = [];
77
- while (true) {
78
- const ops = await this.mempool.process(this.config.maxGasPerBundle, 1);
79
- if (ops?.length > 0) {
80
- opsToBundle.push(ops);
81
- }
82
- else {
83
- break;
84
- }
85
- }
86
- if (opsToBundle.length === 0) {
87
- return [];
88
- }
89
- return opsToBundle;
90
- }
91
- async bundleNow() {
92
- const ops = await this.mempool.process(this.config.maxGasPerBundle, 1);
93
- if (ops.length === 0) {
85
+ // Debug endpoint
86
+ async sendBundleNow() {
87
+ const bundles = await this.mempool.getBundles(1);
88
+ const bundle = bundles[0];
89
+ if (bundle.userOps.length === 0) {
94
90
  throw new Error("no ops to bundle");
95
91
  }
96
- const opEntryPointMap = new Map();
97
- for (const op of ops) {
98
- if (!opEntryPointMap.has(op.entryPoint)) {
99
- opEntryPointMap.set(op.entryPoint, []);
100
- }
101
- opEntryPointMap.get(op.entryPoint)?.push(op.userOperation);
102
- }
103
- const txHashes = [];
104
- await Promise.all(this.config.entrypoints.map(async (entryPoint) => {
105
- const ops = opEntryPointMap.get(entryPoint);
106
- if (ops) {
107
- const txHash = await this.sendToExecutor(entryPoint, ops);
108
- if (!txHash) {
109
- throw new Error("no tx hash");
110
- }
111
- txHashes.push(txHash);
112
- }
113
- else {
114
- this.logger.warn({ entryPoint }, "no user operations for entry point");
115
- }
116
- }));
117
- return txHashes;
118
- }
119
- async sendToExecutor(entryPoint, mempoolOps) {
120
- const ops = mempoolOps.map((op) => op);
121
- const bundles = [];
122
- if (ops.length > 0) {
123
- bundles.push(await this.executor.bundle(entryPoint, ops));
124
- }
125
- for (const bundle of bundles) {
126
- const isBundleSuccess = bundle.every((result) => result.status === "success");
127
- const isBundleResubmit = bundle.every((result) => result.status === "resubmit");
128
- const isBundleFailed = bundle.every((result) => result.status === "failure");
129
- if (isBundleSuccess) {
130
- this.metrics.bundlesSubmitted
131
- .labels({ status: "success" })
132
- .inc();
133
- }
134
- if (isBundleResubmit) {
135
- this.metrics.bundlesSubmitted
136
- .labels({ status: "resubmit" })
137
- .inc();
138
- }
139
- if (isBundleFailed) {
140
- this.metrics.bundlesSubmitted.labels({ status: "failed" }).inc();
141
- }
142
- }
143
- const results = bundles.flat();
144
- const filteredOutOps = mempoolOps.length - results.length;
145
- if (filteredOutOps > 0) {
146
- this.logger.debug({ filteredOutOps }, "user operations filtered out");
147
- this.metrics.userOperationsSubmitted
148
- .labels({ status: "filtered" })
149
- .inc(filteredOutOps);
150
- }
151
- let txHash = undefined;
152
- for (const result of results) {
153
- if (result.status === "success") {
154
- const res = result.value;
155
- this.mempool.markSubmitted(res.userOperation.userOperationHash, res.transactionInfo);
156
- this.monitor.setUserOperationStatus(res.userOperation.userOperationHash, {
157
- status: "submitted",
158
- transactionHash: res.transactionInfo.transactionHash
159
- });
160
- txHash = res.transactionInfo.transactionHash;
161
- this.startWatchingBlocks(this.handleBlock.bind(this));
162
- this.metrics.userOperationsSubmitted
163
- .labels({ status: "success" })
164
- .inc();
165
- }
166
- if (result.status === "failure") {
167
- const { userOpHash, reason } = result.error;
168
- this.mempool.removeProcessing(userOpHash);
169
- this.eventManager.emitDropped(userOpHash, reason, (0, utils_1.getAAError)(reason));
170
- this.monitor.setUserOperationStatus(userOpHash, {
171
- status: "rejected",
172
- transactionHash: null
173
- });
174
- this.logger.warn({
175
- userOperation: JSON.stringify(result.error.userOperation, (_k, v) => typeof v === "bigint" ? v.toString() : v),
176
- userOpHash,
177
- reason
178
- }, "user operation rejected");
179
- this.metrics.userOperationsSubmitted
180
- .labels({ status: "failed" })
181
- .inc();
182
- }
183
- if (result.status === "resubmit") {
184
- this.logger.info({
185
- userOpHash: result.info.userOpHash,
186
- reason: result.info.reason
187
- }, "resubmitting user operation");
188
- this.mempool.removeProcessing(result.info.userOpHash);
189
- this.mempool.add(result.info.userOperation, result.info.entryPoint);
190
- this.metrics.userOperationsResubmitted.inc();
191
- }
92
+ const txHash = await this.sendBundleToExecutor(bundle);
93
+ if (!txHash) {
94
+ throw new Error("no tx hash");
192
95
  }
193
96
  return txHash;
194
97
  }
195
- async bundle(opsToBundle = []) {
196
- await Promise.all(opsToBundle.map(async (ops) => {
197
- const opEntryPointMap = new Map();
198
- for (const op of ops) {
199
- if (!opEntryPointMap.has(op.entryPoint)) {
200
- opEntryPointMap.set(op.entryPoint, []);
201
- }
202
- opEntryPointMap.get(op.entryPoint)?.push(op.userOperation);
203
- }
204
- await Promise.all(this.config.entrypoints.map(async (entryPoint) => {
205
- const userOperations = opEntryPointMap.get(entryPoint);
206
- if (userOperations) {
207
- await this.sendToExecutor(entryPoint, userOperations);
208
- }
209
- else {
210
- this.logger.warn({ entryPoint }, "no user operations for entry point");
211
- }
212
- }));
213
- }));
98
+ async sendBundleToExecutor(userOpBundle) {
99
+ const { entryPoint, userOps, version } = userOpBundle;
100
+ if (userOps.length === 0) {
101
+ return undefined;
102
+ }
103
+ const wallet = await this.senderManager.getWallet();
104
+ const [gasPriceParams, nonce] = await Promise.all([
105
+ this.gasPriceManager.tryGetNetworkGasPrice(),
106
+ this.config.publicClient.getTransactionCount({
107
+ address: wallet.address,
108
+ blockTag: "latest"
109
+ })
110
+ ]).catch((_) => {
111
+ return [];
112
+ });
113
+ if (!gasPriceParams || nonce === undefined) {
114
+ this.resubmitUserOperations(userOps, entryPoint, "Failed to get nonce and gas parameters for bundling");
115
+ // Free executor if failed to get initial params.
116
+ this.senderManager.markWalletProcessed(wallet);
117
+ return undefined;
118
+ }
119
+ const bundleResult = await this.executor.bundle({
120
+ executor: wallet,
121
+ userOpBundle,
122
+ nonce,
123
+ gasPriceParams,
124
+ isReplacementTx: false
125
+ });
126
+ // Free wallet if no bundle was sent.
127
+ if (bundleResult.status !== "bundle_success") {
128
+ this.senderManager.markWalletProcessed(wallet);
129
+ }
130
+ // All ops failed simulation, drop them and return.
131
+ if (bundleResult.status === "all_ops_failed_simulation") {
132
+ const { rejectedUserOps } = bundleResult;
133
+ this.dropUserOps(rejectedUserOps);
134
+ return undefined;
135
+ }
136
+ // Unhandled error during simulation, drop all ops.
137
+ if (bundleResult.status === "unhandled_simulation_failure") {
138
+ const { rejectedUserOps } = bundleResult;
139
+ this.dropUserOps(rejectedUserOps);
140
+ this.metrics.bundlesSubmitted.labels({ status: "failed" }).inc();
141
+ return undefined;
142
+ }
143
+ // Resubmit if executor has insufficient funds.
144
+ if (bundleResult.status === "bundle_submission_failure" &&
145
+ bundleResult.reason instanceof viem_1.InsufficientFundsError) {
146
+ const { reason, userOpsToBundle, rejectedUserOps } = bundleResult;
147
+ this.dropUserOps(rejectedUserOps);
148
+ this.resubmitUserOperations(userOpsToBundle, entryPoint, reason.name);
149
+ this.metrics.bundlesSubmitted.labels({ status: "resubmit" }).inc();
150
+ return undefined;
151
+ }
152
+ // Encountered unhandled error during bundle simulation.
153
+ if (bundleResult.status === "bundle_submission_failure") {
154
+ const { rejectedUserOps, userOpsToBundle, reason } = bundleResult;
155
+ this.dropUserOps(rejectedUserOps);
156
+ // NOTE: these ops passed validation, so we can try resubmitting them
157
+ this.resubmitUserOperations(userOpsToBundle, entryPoint, reason instanceof abitype_1.BaseError
158
+ ? reason.name
159
+ : "Encountered unhandled error during bundle simulation");
160
+ this.metrics.bundlesSubmitted.labels({ status: "failed" }).inc();
161
+ return undefined;
162
+ }
163
+ if (bundleResult.status === "bundle_success") {
164
+ const { userOpsBundled, rejectedUserOps, transactionRequest, transactionHash } = bundleResult;
165
+ const transactionInfo = {
166
+ executor: wallet,
167
+ transactionHash,
168
+ transactionRequest,
169
+ bundle: {
170
+ entryPoint,
171
+ version,
172
+ userOps: userOpsBundled
173
+ },
174
+ previousTransactionHashes: [],
175
+ lastReplaced: Date.now(),
176
+ firstSubmitted: Date.now(),
177
+ timesPotentiallyIncluded: 0
178
+ };
179
+ this.markUserOperationsAsSubmitted(userOpsBundled, transactionInfo);
180
+ this.dropUserOps(rejectedUserOps);
181
+ this.metrics.bundlesSubmitted.labels({ status: "success" }).inc();
182
+ return transactionHash;
183
+ }
184
+ return undefined;
214
185
  }
215
186
  startWatchingBlocks(handleBlock) {
216
187
  if (this.unWatch) {
@@ -218,18 +189,6 @@ class ExecutorManager {
218
189
  }
219
190
  this.unWatch = this.config.publicClient.watchBlocks({
220
191
  onBlock: handleBlock,
221
- // onBlock: async (block) => {
222
- // // Use an arrow function to ensure correct binding of `this`
223
- // this.checkAndReplaceTransactions(block)
224
- // .then(() => {
225
- // this.logger.trace("block handled")
226
- // // Handle the resolution of the promise here, if needed
227
- // })
228
- // .catch((error) => {
229
- // // Handle any errors that occur during the execution of the promise
230
- // this.logger.error({ error }, "error while handling block")
231
- // })
232
- // },
233
192
  onError: (error) => {
234
193
  this.logger.error({ error }, "error while watching blocks");
235
194
  },
@@ -247,94 +206,71 @@ class ExecutorManager {
247
206
  }
248
207
  }
249
208
  // update the current status of the bundling transaction/s
250
- async refreshTransactionStatus(entryPoint, transactionInfo) {
251
- const { transactionHash: currentTransactionHash, userOperationInfos: opInfos, previousTransactionHashes, isVersion06 } = transactionInfo;
252
- const txHashesToCheck = [
253
- currentTransactionHash,
254
- ...previousTransactionHashes
255
- ];
209
+ async refreshTransactionStatus(transactionInfo) {
210
+ const { transactionHash: currentTxhash, bundle, previousTransactionHashes } = transactionInfo;
211
+ const { userOps, entryPoint } = bundle;
212
+ const txHashesToCheck = [currentTxhash, ...previousTransactionHashes];
256
213
  const transactionDetails = await Promise.all(txHashesToCheck.map(async (transactionHash) => ({
257
214
  transactionHash,
258
- ...(await (0, utils_1.getBundleStatus)(isVersion06, transactionHash, this.config.publicClient, this.logger, entryPoint))
215
+ ...(await (0, utils_1.getBundleStatus)({
216
+ transactionHash,
217
+ bundle: transactionInfo.bundle,
218
+ publicClient: this.config.publicClient,
219
+ logger: this.logger
220
+ }))
259
221
  })));
260
222
  // first check if bundling txs returns status "mined", if not, check for reverted
261
223
  const mined = transactionDetails.find(({ bundlingStatus }) => bundlingStatus.status === "included");
262
224
  const reverted = transactionDetails.find(({ bundlingStatus }) => bundlingStatus.status === "reverted");
263
225
  const finalizedTransaction = mined ?? reverted;
264
226
  if (!finalizedTransaction) {
265
- for (const { userOperationHash } of opInfos) {
266
- this.logger.trace({
267
- userOperationHash,
268
- currentTransactionHash
269
- }, "user op still pending");
270
- }
271
227
  return;
272
228
  }
273
229
  const { bundlingStatus, transactionHash, blockNumber } = finalizedTransaction;
274
- if (bundlingStatus.status === "included") {
275
- this.metrics.userOperationsOnChain
276
- .labels({ status: bundlingStatus.status })
277
- .inc(opInfos.length);
278
- const { userOperationDetails } = bundlingStatus;
279
- opInfos.map((opInfo) => {
280
- const { userOperation, userOperationHash, entryPoint, firstSubmitted } = opInfo;
281
- const opDetails = userOperationDetails[userOperationHash];
282
- this.metrics.userOperationInclusionDuration.observe((Date.now() - firstSubmitted) / 1000);
283
- this.mempool.removeSubmitted(userOperationHash);
284
- this.reputationManager.updateUserOperationIncludedStatus(userOperation, entryPoint, opDetails.accountDeployed);
285
- if (opDetails.status === "succesful") {
286
- this.eventManager.emitIncludedOnChain(userOperationHash, transactionHash, blockNumber);
287
- }
288
- else {
289
- this.eventManager.emitExecutionRevertedOnChain(userOperationHash, transactionHash, opDetails.revertReason || "0x", blockNumber);
290
- }
291
- this.monitor.setUserOperationStatus(userOperationHash, {
292
- status: "included",
293
- transactionHash
294
- });
295
- this.logger.info({
296
- userOperationHash,
297
- transactionHash
298
- }, "user op included");
299
- });
300
- this.executor.markWalletProcessed(transactionInfo.executor);
301
- }
302
- else if (bundlingStatus.status === "reverted" &&
303
- bundlingStatus.isAA95) {
230
+ // TODO: there has to be a better way of solving onchain AA95 errors.
231
+ if (bundlingStatus.status === "reverted" && bundlingStatus.isAA95) {
304
232
  // resubmit with more gas when bundler encounters AA95
305
233
  transactionInfo.transactionRequest.gas = (0, utils_1.scaleBigIntByPercent)(transactionInfo.transactionRequest.gas, this.config.aa95GasMultiplier);
306
234
  transactionInfo.transactionRequest.nonce += 1;
307
235
  await this.replaceTransaction(transactionInfo, "AA95");
236
+ return;
237
+ }
238
+ // Free executor if tx landed onchain
239
+ if (bundlingStatus.status !== "not_found") {
240
+ this.senderManager.markWalletProcessed(transactionInfo.executor);
308
241
  }
309
- else {
310
- await Promise.all(opInfos.map(({ userOperationHash }) => {
242
+ if (bundlingStatus.status === "included") {
243
+ const { userOperationDetails } = bundlingStatus;
244
+ this.markUserOpsIncluded(userOps, entryPoint, blockNumber, transactionHash, userOperationDetails);
245
+ }
246
+ if (bundlingStatus.status === "reverted") {
247
+ await Promise.all(userOps.map((userOpInfo) => {
248
+ const { userOpHash } = userOpInfo;
311
249
  this.checkFrontrun({
312
- userOperationHash,
250
+ userOpHash,
313
251
  transactionHash,
314
252
  blockNumber
315
253
  });
316
254
  }));
317
- opInfos.map(({ userOperationHash }) => {
318
- this.mempool.removeSubmitted(userOperationHash);
319
- });
320
- this.executor.markWalletProcessed(transactionInfo.executor);
255
+ this.removeSubmitted(userOps);
321
256
  }
322
257
  }
323
- checkFrontrun({ userOperationHash, transactionHash, blockNumber }) {
258
+ checkFrontrun({ userOpHash, transactionHash, blockNumber }) {
324
259
  const unwatch = this.config.publicClient.watchBlockNumber({
325
260
  onBlockNumber: async (currentBlockNumber) => {
326
261
  if (currentBlockNumber > blockNumber + 1n) {
327
- const userOperationReceipt = await this.getUserOperationReceipt(userOperationHash);
262
+ const userOperationReceipt = await this.getUserOperationReceipt(userOpHash);
328
263
  if (userOperationReceipt) {
329
264
  const transactionHash = userOperationReceipt.receipt.transactionHash;
330
265
  const blockNumber = userOperationReceipt.receipt.blockNumber;
331
- this.monitor.setUserOperationStatus(userOperationHash, {
266
+ this.mempool.removeSubmitted(userOpHash);
267
+ this.monitor.setUserOperationStatus(userOpHash, {
332
268
  status: "included",
333
269
  transactionHash
334
270
  });
335
- this.eventManager.emitFrontranOnChain(userOperationHash, transactionHash, blockNumber);
271
+ this.eventManager.emitFrontranOnChain(userOpHash, transactionHash, blockNumber);
336
272
  this.logger.info({
337
- userOpHash: userOperationHash,
273
+ userOpHash,
338
274
  transactionHash
339
275
  }, "user op frontrun onchain");
340
276
  this.metrics.userOperationsOnChain
@@ -342,13 +278,13 @@ class ExecutorManager {
342
278
  .inc(1);
343
279
  }
344
280
  else {
345
- this.monitor.setUserOperationStatus(userOperationHash, {
346
- status: "rejected",
281
+ this.monitor.setUserOperationStatus(userOpHash, {
282
+ status: "failed",
347
283
  transactionHash
348
284
  });
349
- this.eventManager.emitFailedOnChain(userOperationHash, transactionHash, blockNumber);
285
+ this.eventManager.emitFailedOnChain(userOpHash, transactionHash, blockNumber);
350
286
  this.logger.info({
351
- userOpHash: userOperationHash,
287
+ userOpHash,
352
288
  transactionHash
353
289
  }, "user op failed onchain");
354
290
  this.metrics.userOperationsOnChain
@@ -451,28 +387,6 @@ class ExecutorManager {
451
387
  const userOperationReceipt = (0, utils_1.parseUserOperationReceipt)(userOperationHash, receipt);
452
388
  return userOperationReceipt;
453
389
  }
454
- async refreshUserOperationStatuses() {
455
- const ops = this.mempool.dumpSubmittedOps();
456
- const opEntryPointMap = new Map();
457
- for (const op of ops) {
458
- if (!opEntryPointMap.has(op.userOperation.entryPoint)) {
459
- opEntryPointMap.set(op.userOperation.entryPoint, []);
460
- }
461
- opEntryPointMap.get(op.userOperation.entryPoint)?.push(op);
462
- }
463
- await Promise.all(this.config.entrypoints.map(async (entryPoint) => {
464
- const ops = opEntryPointMap.get(entryPoint);
465
- if (ops) {
466
- const txs = getTransactionsFromUserOperationEntries(ops);
467
- await Promise.all(txs.map(async (txInfo) => {
468
- await this.refreshTransactionStatus(entryPoint, txInfo);
469
- }));
470
- }
471
- else {
472
- this.logger.warn({ entryPoint }, "no user operations for entry point");
473
- }
474
- }));
475
- }
476
390
  async handleBlock(block) {
477
391
  if (this.currentlyHandlingBlock) {
478
392
  return;
@@ -486,102 +400,249 @@ class ExecutorManager {
486
400
  return;
487
401
  }
488
402
  // refresh op statuses
489
- await this.refreshUserOperationStatuses();
403
+ const ops = this.mempool.dumpSubmittedOps();
404
+ const txs = getTransactionsFromUserOperationEntries(ops);
405
+ await Promise.all(txs.map((txInfo) => this.refreshTransactionStatus(txInfo)));
490
406
  // for all still not included check if needs to be replaced (based on gas price)
491
- let gasPriceParameters;
492
- try {
493
- gasPriceParameters =
494
- await this.gasPriceManager.tryGetNetworkGasPrice();
495
- }
496
- catch {
497
- gasPriceParameters = {
498
- maxFeePerGas: 0n,
499
- maxPriorityFeePerGas: 0n
500
- };
501
- }
502
- this.logger.trace({ gasPriceParameters }, "fetched gas price parameters");
407
+ const gasPriceParameters = await this.gasPriceManager
408
+ .tryGetNetworkGasPrice()
409
+ .catch(() => ({
410
+ maxFeePerGas: 0n,
411
+ maxPriorityFeePerGas: 0n
412
+ }));
503
413
  const transactionInfos = getTransactionsFromUserOperationEntries(this.mempool.dumpSubmittedOps());
504
414
  await Promise.all(transactionInfos.map(async (txInfo) => {
505
- if (txInfo.transactionRequest.maxFeePerGas >=
506
- gasPriceParameters.maxFeePerGas &&
507
- txInfo.transactionRequest.maxPriorityFeePerGas >=
508
- gasPriceParameters.maxPriorityFeePerGas) {
415
+ const { transactionRequest } = txInfo;
416
+ const { maxFeePerGas, maxPriorityFeePerGas } = transactionRequest;
417
+ const isMaxFeeTooLow = maxFeePerGas < gasPriceParameters.maxFeePerGas;
418
+ const isPriorityFeeTooLow = maxPriorityFeePerGas <
419
+ gasPriceParameters.maxPriorityFeePerGas;
420
+ const isStuck = Date.now() - txInfo.lastReplaced >
421
+ this.config.resubmitStuckTimeout;
422
+ if (isMaxFeeTooLow || isPriorityFeeTooLow) {
423
+ await this.replaceTransaction(txInfo, "gas_price");
509
424
  return;
510
425
  }
511
- await this.replaceTransaction(txInfo, "gas_price");
512
- }));
513
- // for any left check if enough time has passed, if so replace
514
- const transactionInfos2 = getTransactionsFromUserOperationEntries(this.mempool.dumpSubmittedOps());
515
- await Promise.all(transactionInfos2.map(async (txInfo) => {
516
- if (Date.now() - txInfo.lastReplaced <
517
- this.config.resubmitStuckTimeout) {
426
+ if (isStuck) {
427
+ await this.replaceTransaction(txInfo, "stuck");
518
428
  return;
519
429
  }
520
- await this.replaceTransaction(txInfo, "stuck");
521
430
  }));
522
431
  this.currentlyHandlingBlock = false;
523
432
  }
524
433
  async replaceTransaction(txInfo, reason) {
525
- let replaceResult = undefined;
526
- try {
527
- replaceResult = await this.executor.replaceTransaction(txInfo);
528
- }
529
- finally {
530
- this.metrics.replacedTransactions
531
- .labels({ reason, status: replaceResult?.status || "failed" })
532
- .inc();
533
- }
534
- if (replaceResult.status === "failed") {
535
- txInfo.userOperationInfos.map((opInfo) => {
536
- const userOperation = opInfo.userOperation;
537
- this.eventManager.emitDropped(opInfo.userOperationHash, "Failed to replace transaction");
538
- this.logger.warn({
539
- userOperation: JSON.stringify(userOperation, (_k, v) => typeof v === "bigint" ? v.toString() : v),
540
- userOpHash: opInfo.userOperationHash,
541
- reason
542
- }, "user operation rejected");
543
- this.mempool.removeSubmitted(opInfo.userOperationHash);
434
+ // Setup vars
435
+ const { bundle, executor, transactionRequest, transactionHash: oldTxHash } = txInfo;
436
+ const { userOps } = bundle;
437
+ const gasPriceParameters = await this.gasPriceManager
438
+ .tryGetNetworkGasPrice()
439
+ .catch((_) => {
440
+ return undefined;
441
+ });
442
+ if (!gasPriceParameters) {
443
+ const rejectedUserOps = userOps.map((userOpInfo) => ({
444
+ ...userOpInfo,
445
+ reason: "Failed to get network gas price during replacement"
446
+ }));
447
+ this.failedToReplaceTransaction({
448
+ rejectedUserOps,
449
+ oldTxHash,
450
+ reason: "Failed to get network gas price during replacement"
544
451
  });
545
- this.logger.warn({ oldTxHash: txInfo.transactionHash, reason }, "failed to replace transaction");
452
+ // Free executor if failed to get initial params.
453
+ this.senderManager.markWalletProcessed(txInfo.executor);
546
454
  return;
547
455
  }
548
- if (replaceResult.status === "potentially_already_included") {
549
- this.logger.info({ oldTxHash: txInfo.transactionHash, reason }, "transaction potentially already included");
550
- txInfo.timesPotentiallyIncluded += 1;
456
+ const bundleResult = await this.executor.bundle({
457
+ executor: executor,
458
+ userOpBundle: bundle,
459
+ nonce: transactionRequest.nonce,
460
+ gasPriceParams: {
461
+ maxFeePerGas: (0, utils_1.scaleBigIntByPercent)(gasPriceParameters.maxFeePerGas, 115n),
462
+ maxPriorityFeePerGas: (0, utils_1.scaleBigIntByPercent)(gasPriceParameters.maxPriorityFeePerGas, 115n)
463
+ },
464
+ gasLimitSuggestion: transactionRequest.gas,
465
+ isReplacementTx: true
466
+ });
467
+ // Free wallet and return if potentially included too many times.
468
+ if (txInfo.timesPotentiallyIncluded >= 3) {
551
469
  if (txInfo.timesPotentiallyIncluded >= 3) {
552
- txInfo.userOperationInfos.map((opInfo) => {
553
- this.mempool.removeSubmitted(opInfo.userOperationHash);
554
- });
555
- this.executor.markWalletProcessed(txInfo.executor);
556
- this.logger.warn({ oldTxHash: txInfo.transactionHash, reason }, "transaction potentially already included too many times, removing");
470
+ this.removeSubmitted(bundle.userOps);
471
+ this.logger.warn({
472
+ oldTxHash,
473
+ userOps: (0, utils_2.getUserOpHashes)(bundleResult.rejectedUserOps)
474
+ }, "transaction potentially already included too many times, removing");
557
475
  }
476
+ this.senderManager.markWalletProcessed(txInfo.executor);
558
477
  return;
559
478
  }
560
- const newTxInfo = replaceResult.transactionInfo;
561
- const missingOps = txInfo.userOperationInfos.filter((info) => !newTxInfo.userOperationInfos
562
- .map((ni) => ni.userOperationHash)
563
- .includes(info.userOperationHash));
564
- const matchingOps = txInfo.userOperationInfos.filter((info) => newTxInfo.userOperationInfos
565
- .map((ni) => ni.userOperationHash)
566
- .includes(info.userOperationHash));
567
- matchingOps.map((opInfo) => {
568
- this.mempool.replaceSubmitted(opInfo, newTxInfo);
569
- });
570
- missingOps.map((opInfo) => {
571
- this.mempool.removeSubmitted(opInfo.userOperationHash);
572
- this.logger.warn({
573
- oldTxHash: txInfo.transactionHash,
574
- newTxHash: newTxInfo.transactionHash,
575
- reason
576
- }, "missing op in new tx");
479
+ // Free wallet if no bundle was sent or potentially included.
480
+ if (bundleResult.status !== "bundle_success") {
481
+ this.senderManager.markWalletProcessed(txInfo.executor);
482
+ }
483
+ // Check if the transaction is potentially included.
484
+ const nonceTooLow = bundleResult.status === "bundle_submission_failure" &&
485
+ bundleResult.reason instanceof viem_1.NonceTooLowError;
486
+ const allOpsFailedSimulation = bundleResult.status === "all_ops_failed_simulation" &&
487
+ bundleResult.rejectedUserOps.every((op) => op.reason === "AA25 invalid account nonce" ||
488
+ op.reason === "AA10 sender already constructed");
489
+ const potentiallyIncluded = nonceTooLow || allOpsFailedSimulation;
490
+ // log metrics
491
+ const replaceStatus = (() => {
492
+ switch (true) {
493
+ case potentiallyIncluded:
494
+ return "potentially_already_included";
495
+ case bundleResult?.status === "bundle_success":
496
+ return "replaced";
497
+ default:
498
+ return "failed";
499
+ }
500
+ })();
501
+ this.metrics.replacedTransactions
502
+ .labels({ reason, status: replaceStatus })
503
+ .inc();
504
+ if (potentiallyIncluded) {
505
+ this.logger.info({
506
+ oldTxHash,
507
+ userOpHashes: (0, utils_2.getUserOpHashes)(bundleResult.rejectedUserOps)
508
+ }, "transaction potentially already included");
509
+ txInfo.timesPotentiallyIncluded += 1;
510
+ return;
511
+ }
512
+ if (bundleResult.status === "unhandled_simulation_failure") {
513
+ const { rejectedUserOps, reason } = bundleResult;
514
+ this.failedToReplaceTransaction({
515
+ oldTxHash,
516
+ reason,
517
+ rejectedUserOps
518
+ });
519
+ return;
520
+ }
521
+ if (bundleResult.status === "all_ops_failed_simulation") {
522
+ this.failedToReplaceTransaction({
523
+ oldTxHash,
524
+ reason: "all ops failed simulation",
525
+ rejectedUserOps: bundleResult.rejectedUserOps
526
+ });
527
+ return;
528
+ }
529
+ if (bundleResult.status === "bundle_submission_failure") {
530
+ const { reason, rejectedUserOps } = bundleResult;
531
+ const submissionFailureReason = reason instanceof abitype_1.BaseError ? reason.name : "INTERNAL FAILURE";
532
+ this.failedToReplaceTransaction({
533
+ oldTxHash,
534
+ rejectedUserOps,
535
+ reason: submissionFailureReason
536
+ });
537
+ return;
538
+ }
539
+ const { rejectedUserOps, userOpsBundled, transactionRequest: newTransactionRequest, transactionHash: newTxHash } = bundleResult;
540
+ const userOpsReplaced = userOpsBundled;
541
+ const newTxInfo = {
542
+ ...txInfo,
543
+ transactionRequest: newTransactionRequest,
544
+ transactionHash: newTxHash,
545
+ previousTransactionHashes: [
546
+ txInfo.transactionHash,
547
+ ...txInfo.previousTransactionHashes
548
+ ],
549
+ lastReplaced: Date.now(),
550
+ bundle: {
551
+ ...txInfo.bundle,
552
+ userOps: userOpsReplaced
553
+ }
554
+ };
555
+ userOpsReplaced.map((userOp) => {
556
+ this.mempool.replaceSubmitted(userOp, newTxInfo);
577
557
  });
558
+ // Drop all userOperations that were rejected during simulation.
559
+ this.dropUserOps(rejectedUserOps);
578
560
  this.logger.info({
579
- oldTxHash: txInfo.transactionHash,
580
- newTxHash: newTxInfo.transactionHash,
561
+ oldTxHash,
562
+ newTxHash,
581
563
  reason
582
564
  }, "replaced transaction");
583
565
  return;
584
566
  }
567
+ markUserOperationsAsSubmitted(userOpInfos, transactionInfo) {
568
+ userOpInfos.map((userOpInfo) => {
569
+ const { userOpHash } = userOpInfo;
570
+ this.mempool.markSubmitted(userOpHash, transactionInfo);
571
+ this.startWatchingBlocks(this.handleBlock.bind(this));
572
+ this.metrics.userOperationsSubmitted
573
+ .labels({ status: "success" })
574
+ .inc();
575
+ });
576
+ }
577
+ resubmitUserOperations(userOps, entryPoint, reason) {
578
+ userOps.map((userOpInfo) => {
579
+ const { userOpHash, userOp } = userOpInfo;
580
+ this.logger.info({
581
+ userOpHash,
582
+ reason
583
+ }, "resubmitting user operation");
584
+ this.mempool.removeProcessing(userOpHash);
585
+ this.mempool.add(userOp, entryPoint);
586
+ this.metrics.userOperationsResubmitted.inc();
587
+ });
588
+ }
589
+ failedToReplaceTransaction({ oldTxHash, rejectedUserOps, reason }) {
590
+ this.logger.warn({ oldTxHash, reason }, "failed to replace transaction");
591
+ this.dropUserOps(rejectedUserOps);
592
+ }
593
+ removeSubmitted(userOps) {
594
+ userOps.map((userOpInfo) => {
595
+ const { userOpHash } = userOpInfo;
596
+ this.mempool.removeSubmitted(userOpHash);
597
+ });
598
+ }
599
+ markUserOpsIncluded(userOps, entryPoint, blockNumber, transactionHash, userOperationDetails) {
600
+ userOps.map((userOpInfo) => {
601
+ this.metrics.userOperationsOnChain
602
+ .labels({ status: "included" })
603
+ .inc();
604
+ const { userOpHash, userOp } = userOpInfo;
605
+ const opDetails = userOperationDetails[userOpHash];
606
+ const firstSubmitted = userOpInfo.addedToMempool;
607
+ this.metrics.userOperationInclusionDuration.observe((Date.now() - firstSubmitted) / 1000);
608
+ this.mempool.removeSubmitted(userOpHash);
609
+ this.reputationManager.updateUserOperationIncludedStatus(userOp, entryPoint, opDetails.accountDeployed);
610
+ if (opDetails.status === "succesful") {
611
+ this.eventManager.emitIncludedOnChain(userOpHash, transactionHash, blockNumber);
612
+ }
613
+ else {
614
+ this.eventManager.emitExecutionRevertedOnChain(userOpHash, transactionHash, opDetails.revertReason || "0x", blockNumber);
615
+ }
616
+ this.monitor.setUserOperationStatus(userOpHash, {
617
+ status: "included",
618
+ transactionHash
619
+ });
620
+ this.logger.info({
621
+ opHash: userOpHash,
622
+ transactionHash
623
+ }, "user op included");
624
+ });
625
+ }
626
+ dropUserOps(rejectedUserOps) {
627
+ rejectedUserOps.map((rejectedUserOp) => {
628
+ const { userOp, reason, userOpHash } = rejectedUserOp;
629
+ this.mempool.removeProcessing(userOpHash);
630
+ this.mempool.removeSubmitted(userOpHash);
631
+ this.eventManager.emitDropped(userOpHash, reason, (0, utils_1.getAAError)(reason));
632
+ this.monitor.setUserOperationStatus(userOpHash, {
633
+ status: "rejected",
634
+ transactionHash: null
635
+ });
636
+ this.logger.warn({
637
+ userOperation: JSON.stringify(userOp, (_k, v) => typeof v === "bigint" ? v.toString() : v),
638
+ userOpHash,
639
+ reason
640
+ }, "user operation rejected");
641
+ this.metrics.userOperationsSubmitted
642
+ .labels({ status: "failed" })
643
+ .inc();
644
+ });
645
+ }
585
646
  }
586
647
  exports.ExecutorManager = ExecutorManager;
587
648
  //# sourceMappingURL=executorManager.js.map