diff --git a/src/cli/config/bundler.ts b/src/cli/config/bundler.ts index e45316ab..04ed426a 100644 --- a/src/cli/config/bundler.ts +++ b/src/cli/config/bundler.ts @@ -121,6 +121,7 @@ export const bundlerArgsSchema = z.object({ ), "refilling-wallets": z.boolean().default(true), "aa95-gas-multiplier": z.string().transform((val) => BigInt(val)), + "enable-fastlane": z.boolean(), "enable-instant-bundling-endpoint": z.boolean(), "enable-experimental-7702-endpoints": z.boolean() }) diff --git a/src/cli/config/options.ts b/src/cli/config/options.ts index 493f0b39..bd211aa0 100644 --- a/src/cli/config/options.ts +++ b/src/cli/config/options.ts @@ -208,6 +208,12 @@ export const bundlerOptions: CliCommandOptions = { description: "Amount to scale the gas estimations used for bundling", type: "string", default: "100" + }, + "enable-fastlane": { + description: + "Enable bundling v0.6 userOperations through the fastlane ERC-4337 mempool", + type: "boolean", + default: false } } diff --git a/src/executor/executor.ts b/src/executor/executor.ts index a415e874..dd43f2ad 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -48,6 +48,7 @@ import { import type { SendTransactionErrorType } from "viem" import type { AltoConfig } from "../createConfig" import type { SendTransactionOptions } from "./types" +import { sendPflConditional } from "./fastlane" export interface GasEstimateResult { preverificationGas: bigint @@ -58,6 +59,7 @@ export interface GasEstimateResult { export type HandleOpsTxParam = { ops: PackedUserOperation[] isUserOpVersion06: boolean + isReplacementTx: boolean entryPoint: Address } @@ -298,6 +300,7 @@ export class Executor { txParam = { isUserOpVersion06, + isReplacementTx: true, ops: userOps, entryPoint: transactionInfo.entryPoint } @@ -500,6 +503,24 @@ export class Executor { // Try sending the transaction and updating relevant fields if there is an error. while (attempts < maxAttempts) { try { + if ( + this.config.enableFastlane && + isUserOpVersion06 && + !txParam.isReplacementTx + ) { + const serializedTransaction = + await this.config.walletClient.signTransaction(request) + + transactionHash = await sendPflConditional({ + serializedTransaction, + publicClient: this.config.publicClient, + walletClient: this.config.walletClient, + logger: this.logger + }) + + break + } + transactionHash = await this.config.walletClient.sendTransaction(request) @@ -832,6 +853,7 @@ export class Executor { transactionHash = await this.sendHandleOpsTransaction({ txParam: { ops: userOps, + isReplacementTx: false, isUserOpVersion06, entryPoint }, diff --git a/src/executor/fastlane.ts b/src/executor/fastlane.ts new file mode 100644 index 00000000..bf50b827 --- /dev/null +++ b/src/executor/fastlane.ts @@ -0,0 +1,69 @@ +import { Logger } from "@alto/utils" +import { join } from "path" +import { + Hex, + createClient, + http, + SendRawTransactionReturnType, + Hash, + WalletClient, + PublicClient, + toHex, + BaseError +} from "viem" + +const pflClient = createClient({ + transport: http("https://polygon-rpc.fastlane.xyz") +}) + +export async function sendPflConditional({ + serializedTransaction, + publicClient, + walletClient, + logger +}: { + serializedTransaction: Hash + publicClient: PublicClient + walletClient: WalletClient + logger: Logger +}): Promise { + try { + const blockNumberMin = await publicClient.getBlockNumber() + const blockNumberMax = blockNumberMin + 25n + + const timestampMin = Date.now() / 1000 + const timestampMax = timestampMin + 500 + + const opts = { + //knownAccounts: {} + blockNumberMin: toHex(blockNumberMin), + blockNumberMax: toHex(blockNumberMax), + timestampMin: Math.floor(timestampMin), + timestampMax: Math.floor(timestampMax) + } + + const txHash = (await pflClient.request({ + // @ts-ignore + method: "pfl_sendRawTransactionConditional", + // @ts-ignore + params: [serializedTransaction, opts] + })) as Hex + + if (!txHash) { + const error = new BaseError( + "FastLane API returned empty transaction hash" + ) + error.details = + "PFL conditional transaction failed: No txHash in response" + throw error + } + + return txHash + } catch (e: unknown) { + logger.error( + "Error sending through pfl_sendRawTransactionConditional ", + (e as any)?.details + ) + return await walletClient.sendRawTransaction({ serializedTransaction }) + } +}