From b95ae9c70f4ad3439384cf1414a197b5332a8955 Mon Sep 17 00:00:00 2001 From: mous <97399882+mouseless0x@users.noreply.github.com> Date: Thu, 23 Jan 2025 19:38:07 +0000 Subject: [PATCH] Feat/pfl send raw transaction conditional (#414) * support pfl_sendRawTransactionConditional * fix * update pfl conditions * clarify option --------- Co-authored-by: mouseless <97399882+mouseless-eth@users.noreply.github.com> --- src/cli/config/bundler.ts | 1 + src/cli/config/options.ts | 6 ++++ src/executor/executor.ts | 30 +++++++++++++---- src/executor/fastlane.ts | 68 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 src/executor/fastlane.ts 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..01eb59b9 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 using the pfl_sendRawTransactionConditional endpoint", + type: "boolean", + default: false } } diff --git a/src/executor/executor.ts b/src/executor/executor.ts index a415e874..944e2f99 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,13 +503,31 @@ 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 && + attempts === 0 + ) { + 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) break } catch (e: unknown) { isTransactionUnderPriced = false - let isErrorHandled = false if (e instanceof BaseError) { if (isTransactionUnderpricedError(e)) { @@ -520,7 +541,6 @@ export class Executor { request.maxPriorityFeePerGas, 150n ) - isErrorHandled = true isTransactionUnderPriced = true } } @@ -540,13 +560,11 @@ export class Executor { address: request.from, blockTag: "pending" }) - isErrorHandled = true } if (cause instanceof IntrinsicGasTooLowError) { this.logger.warn("Intrinsic gas too low, retrying") request.gas = scaleBigIntByPercent(request.gas, 150n) - isErrorHandled = true } // This is thrown by OP-Stack chains that use proxyd. @@ -556,11 +574,10 @@ export class Executor { "no backends avaiable error, retrying after 500ms" ) await new Promise((resolve) => setTimeout(resolve, 500)) - isErrorHandled = true } } - if (attempts === maxAttempts || !isErrorHandled) { + if (attempts === maxAttempts) { throw error } @@ -832,6 +849,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..0f8b2156 --- /dev/null +++ b/src/executor/fastlane.ts @@ -0,0 +1,68 @@ +import { Logger } from "@alto/utils" +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 + 30n + + const timestampMin = Date.now() / 1000 + const timestampMax = timestampMin + 60 + + 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 }) + } +}