diff --git a/src/cli/deploySimulationsContract.ts b/src/cli/deploySimulationsContract.ts new file mode 100644 index 00000000..d7220465 --- /dev/null +++ b/src/cli/deploySimulationsContract.ts @@ -0,0 +1,49 @@ +import { PimlicoEntryPointSimulationsDeployBytecode } from "@alto/types" +import { + type Chain, + createWalletClient, + type Hex, + http, + type PublicClient, + type Transport +} from "viem" +import type { CamelCasedProperties } from "./parseArgs" +import type { IOptions } from "@alto/cli" + +export const deploySimulationsContract = async ({ + args, + publicClient +}: { + args: CamelCasedProperties + publicClient: PublicClient +}): Promise => { + const utilityPrivateKey = args.utilityPrivateKey + if (!utilityPrivateKey) { + throw new Error( + "Cannot deploy entryPoint simulations without utility-private-key" + ) + } + + const walletClient = createWalletClient({ + transport: http(args.rpcUrl), + account: utilityPrivateKey + }) + + const deployHash = await walletClient.deployContract({ + chain: publicClient.chain, + abi: [], + bytecode: PimlicoEntryPointSimulationsDeployBytecode + }) + + const receipt = await publicClient.getTransactionReceipt({ + hash: deployHash + }) + + const simulationsContract = receipt.contractAddress + + if (simulationsContract === null || simulationsContract === undefined) { + throw new Error("Failed to deploy simulationsContract") + } + + return simulationsContract +} diff --git a/src/cli/handler.ts b/src/cli/handler.ts index 01e673b3..1f4d195d 100644 --- a/src/cli/handler.ts +++ b/src/cli/handler.ts @@ -1,50 +1,28 @@ import { SenderManager } from "@alto/executor" import { GasPriceManager } from "@alto/handlers" import { - type Logger, createMetrics, initDebugLogger, initProductionLogger } from "@alto/utils" import { Registry } from "prom-client" import { - http, type Chain, - type PublicClient, - type Transport, createPublicClient, createWalletClient, formatEther } from "viem" -import { fromZodError } from "zod-validation-error" import { UtilityWalletMonitor } from "../executor/utilityWalletMonitor" -import { PimlicoEntryPointSimulationsDeployBytecode } from "../types/contracts" -import { - type IBundlerArgs, - type IOptions, - type IOptionsInput, - optionArgsSchema -} from "./config" +import type { IOptionsInput } from "./config" import { customTransport } from "./customTransport" import { setupServer } from "./setupServer" +import { type AltoConfig, createConfig } from "../createConfig" +import { parseArgs } from "./parseArgs" +import { deploySimulationsContract } from "./deploySimulationsContract" -const parseArgs = (args: IOptionsInput): IOptions => { - // validate every arg, make type safe so if i add a new arg i have to validate it - const parsing = optionArgsSchema.safeParse(args) - if (!parsing.success) { - const error = fromZodError(parsing.error) - throw new Error(error.message) - } - - return parsing.data -} - -const preFlightChecks = async ( - publicClient: PublicClient, - parsedArgs: IBundlerArgs -): Promise => { - for (const entrypoint of parsedArgs.entrypoints) { - const entryPointCode = await publicClient.getBytecode({ +const preFlightChecks = async (config: AltoConfig): Promise => { + for (const entrypoint of config.entrypoints) { + const entryPointCode = await config.publicClient.getBytecode({ address: entrypoint }) if (entryPointCode === "0x") { @@ -52,9 +30,9 @@ const preFlightChecks = async ( } } - if (parsedArgs["entrypoint-simulation-contract"]) { - const simulations = parsedArgs["entrypoint-simulation-contract"] - const simulationsCode = await publicClient.getBytecode({ + if (config.entrypointSimulationContract) { + const simulations = config.entrypointSimulationContract + const simulationsCode = await config.publicClient.getBytecode({ address: simulations }) if (simulationsCode === undefined || simulationsCode === "0x") { @@ -65,115 +43,77 @@ const preFlightChecks = async ( } } -export async function bundlerHandler(args: IOptionsInput): Promise { - const parsedArgs = parseArgs(args) - - let logger: Logger - if (parsedArgs.json) { - logger = initProductionLogger(parsedArgs["log-level"]) - } else { - logger = initDebugLogger(parsedArgs["log-level"]) - } - - const rootLogger = logger.child( - { module: "root" }, - { level: parsedArgs["log-level"] } - ) +export async function bundlerHandler(args_: IOptionsInput): Promise { + const args = parseArgs(args_) + const logger = args.json + ? initProductionLogger(args.logLevel) + : initDebugLogger(args.logLevel) const getChainId = async () => { const client = createPublicClient({ - transport: customTransport(parsedArgs["rpc-url"], { + transport: customTransport(args.rpcUrl, { logger: logger.child( { module: "public_client" }, { - level: - parsedArgs["public-client-log-level"] || - parsedArgs["log-level"] + level: args.publicClientLogLevel || args.logLevel } ) }) }) return await client.getChainId() } + const chainId = await getChainId() const chain: Chain = { id: chainId, - name: args["network-name"], + name: args.networkName, nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, rpcUrls: { - default: { http: [args["rpc-url"]] }, - public: { http: [args["rpc-url"]] } + default: { http: [args.rpcUrl] }, + public: { http: [args.rpcUrl] } } } - const client = createPublicClient({ - transport: customTransport(args["rpc-url"], { + const publicClient = createPublicClient({ + transport: customTransport(args.rpcUrl, { logger: logger.child( { module: "public_client" }, { - level: - parsedArgs["public-client-log-level"] || - parsedArgs["log-level"] + level: args.publicClientLogLevel || args.logLevel } ) }), chain }) - // if flag is set, use utility wallet to deploy the simulations contract - if (parsedArgs["deploy-simulations-contract"]) { - if (!parsedArgs["utility-private-key"]) { - throw new Error( - "Cannot deploy entryPoint simulations without utility-private-key" + const walletClient = createWalletClient({ + transport: customTransport(args.sendTransactionRpcUrl ?? args.rpcUrl, { + logger: logger.child( + { module: "wallet_client" }, + { + level: args.walletClientLogLevel || args.logLevel + } ) - } - - const walletClient = createWalletClient({ - transport: http(args["rpc-url"]), - account: parsedArgs["utility-private-key"] - }) - - const deployHash = await walletClient.deployContract({ - chain, - abi: [], - bytecode: PimlicoEntryPointSimulationsDeployBytecode - }) + }), + chain + }) - const receipt = await client.getTransactionReceipt({ - hash: deployHash + // if flag is set, use utility wallet to deploy the simulations contract + if (args.deploySimulationsContract) { + args.entrypointSimulationContract = await deploySimulationsContract({ + args, + publicClient }) - - const simulationsContract = receipt.contractAddress - - if (simulationsContract === null) { - throw new Error("Failed to deploy simulationsContract") - } - - parsedArgs["entrypoint-simulation-contract"] = simulationsContract } - const gasPriceManager = new GasPriceManager( - chain, - client, - parsedArgs["legacy-transactions"], - logger.child( - { module: "gas_price_manager" }, - { - level: - parsedArgs["public-client-log-level"] || - parsedArgs["log-level"] - } - ), - parsedArgs["gas-price-bump"], - parsedArgs["gas-price-expiry"], - parsedArgs["gas-price-refresh-interval"], - parsedArgs["chain-type"] - ) + const config = createConfig({ ...args, logger, publicClient, walletClient }) + + const gasPriceManager = new GasPriceManager(config) await gasPriceManager.init() @@ -184,70 +124,32 @@ export async function bundlerHandler(args: IOptionsInput): Promise { }) const metrics = createMetrics(registry) - await preFlightChecks(client, parsedArgs) + await preFlightChecks(config) - const walletClient = createWalletClient({ - transport: customTransport( - parsedArgs["send-transaction-rpc-url"] ?? args["rpc-url"], - { - logger: logger.child( - { module: "wallet_client" }, - { - level: - parsedArgs["wallet-client-log-level"] || - parsedArgs["log-level"] - } - ) - } - ), - chain - }) - - const senderManager = new SenderManager( - parsedArgs["executor-private-keys"], - parsedArgs["utility-private-key"], - logger.child( - { module: "executor" }, - { - level: - parsedArgs["executor-log-level"] || parsedArgs["log-level"] - } - ), + const senderManager = new SenderManager({ + config, metrics, - parsedArgs["legacy-transactions"], - gasPriceManager, - parsedArgs["max-executors"] - ) + gasPriceManager + }) - const utilityWalletAddress = parsedArgs["utility-private-key"]?.address + const utilityWalletAddress = config.utilityPrivateKey?.address - if (utilityWalletAddress && parsedArgs["utility-wallet-monitor"]) { - const utilityWalletMonitor = new UtilityWalletMonitor( - client, - parsedArgs["utility-wallet-monitor-interval"], - utilityWalletAddress, + if (utilityWalletAddress && config.utilityWalletMonitor) { + const utilityWalletMonitor = new UtilityWalletMonitor({ + config, metrics, - logger.child( - { module: "utility_wallet_monitor" }, - { - level: parsedArgs["log-level"] - } - ) - ) + utilityWalletAddress + }) await utilityWalletMonitor.start() } metrics.executorWalletsMinBalance.set( - Number.parseFloat(formatEther(parsedArgs["min-executor-balance"] || 0n)) + Number.parseFloat(formatEther(config.minExecutorBalance || 0n)) ) await setupServer({ - client, - walletClient, - parsedArgs, - logger, - rootLogger, + config, registry, metrics, senderManager, diff --git a/src/cli/parseArgs.ts b/src/cli/parseArgs.ts new file mode 100644 index 00000000..f7a5d259 --- /dev/null +++ b/src/cli/parseArgs.ts @@ -0,0 +1,47 @@ +import { type IOptions, optionArgsSchema, type IOptionsInput } from "@alto/cli" +import { fromZodError } from "zod-validation-error" + +type CamelCase = + S extends `${infer P1}-${infer P2}${infer P3}` + ? `${P1}${Uppercase}${CamelCase}` + : S extends `${infer P1}_${infer P2}${infer P3}` + ? `${P1}${Uppercase}${CamelCase}` + : S + +export type CamelCasedProperties = { + [K in keyof T as CamelCase>]: T[K] +} + +function toCamelCase(str: string): string { + return str.replace(/([-_][a-z])/g, (group) => + group.toUpperCase().replace("-", "").replace("_", "") + ) +} + +function convertKeysToCamelCase( + obj: T +): CamelCasedProperties { + return Object.keys(obj).reduce( + (acc, key) => { + const camelCaseKey = toCamelCase( + key + ) as keyof CamelCasedProperties + ;(acc as any)[camelCaseKey] = obj[key as keyof T] + + return acc + }, + {} as CamelCasedProperties + ) +} + +export const parseArgs = ( + args: IOptionsInput +): CamelCasedProperties => { + const parsing = optionArgsSchema.safeParse(args) + if (!parsing.success) { + const error = fromZodError(parsing.error) + throw new Error(error.message) + } + + return convertKeysToCamelCase(parsing.data) +} diff --git a/src/cli/setupServer.ts b/src/cli/setupServer.ts index aaaf6c1b..281b0f58 100644 --- a/src/cli/setupServer.ts +++ b/src/cli/setupServer.ts @@ -20,102 +20,43 @@ import { UnsafeValidator } from "@alto/rpc" import type { InterfaceValidator } from "@alto/types" -import type { Logger, Metrics } from "@alto/utils" +import type { Metrics } from "@alto/utils" import type { Registry } from "prom-client" -import type { Chain, PublicClient, Transport, WalletClient } from "viem" -import type { IBundleCompressionArgs, IOptions } from "./config" +import type { AltoConfig } from "../createConfig" -const getReputationManager = ({ - client, - parsedArgs, - logger -}: { - client: PublicClient - parsedArgs: IOptions - logger: Logger -}): InterfaceReputationManager => { - if (parsedArgs["safe-mode"]) { - return new ReputationManager( - client, - parsedArgs.entrypoints, - BigInt(parsedArgs["min-entity-stake"]), - BigInt(parsedArgs["min-entity-unstake-delay"]), - logger.child( - { module: "reputation_manager" }, - { - level: - parsedArgs["reputation-manager-log-level"] || - parsedArgs["log-level"] - } - ) - ) +const getReputationManager = ( + config: AltoConfig +): InterfaceReputationManager => { + if (config.safeMode) { + return new ReputationManager(config) } return new NullReputationManager() } const getValidator = ({ - client, - parsedArgs, - logger, + config, senderManager, metrics, gasPriceManager }: { - client: PublicClient - parsedArgs: IOptions - logger: Logger + config: AltoConfig senderManager: SenderManager metrics: Metrics gasPriceManager: GasPriceManager - walletClient: WalletClient }): InterfaceValidator => { - const utilityWalletAddress = - parsedArgs["utility-private-key"]?.address || - "0x4337000c2828F5260d8921fD25829F606b9E8680" - - if (parsedArgs["safe-mode"]) { - return new SafeValidator( - client, + if (config.safeMode) { + return new SafeValidator({ + config, senderManager, - logger.child( - { module: "rpc" }, - { - level: - parsedArgs["rpc-log-level"] || parsedArgs["log-level"] - } - ), metrics, - gasPriceManager, - parsedArgs["chain-type"], - parsedArgs["block-tag-support"], - utilityWalletAddress, - parsedArgs["binary-search-tolerance-delta"], - parsedArgs["binary-search-gas-allowance"], - parsedArgs["entrypoint-simulation-contract"], - parsedArgs["fixed-gas-limit-for-estimation"], - parsedArgs.tenderly, - parsedArgs["balance-override"] - ) + gasPriceManager + }) } - return new UnsafeValidator( - client, - logger.child( - { module: "rpc" }, - { level: parsedArgs["rpc-log-level"] || parsedArgs["log-level"] } - ), + return new UnsafeValidator({ + config, metrics, - gasPriceManager, - parsedArgs["chain-type"], - parsedArgs["block-tag-support"], - utilityWalletAddress, - parsedArgs["binary-search-tolerance-delta"], - parsedArgs["binary-search-gas-allowance"], - parsedArgs["entrypoint-simulation-contract"], - parsedArgs["fixed-gas-limit-for-estimation"], - parsedArgs.tenderly, - parsedArgs["balance-override"], - parsedArgs["expiration-check"] - ) + gasPriceManager + }) } const getMonitor = (): Monitor => { @@ -123,209 +64,134 @@ const getMonitor = (): Monitor => { } const getMempool = ({ + config, monitor, reputationManager, validator, - client, - parsedArgs, - logger, metrics, eventManager }: { + config: AltoConfig monitor: Monitor reputationManager: InterfaceReputationManager validator: InterfaceValidator - client: PublicClient - parsedArgs: IOptions - logger: Logger metrics: Metrics eventManager: EventManager }): MemoryMempool => { - return new MemoryMempool( + return new MemoryMempool({ + config, monitor, reputationManager, validator, - client, - parsedArgs["safe-mode"], - logger.child( - { module: "mempool" }, - { - level: - parsedArgs["mempool-log-level"] || parsedArgs["log-level"] - } - ), metrics, - parsedArgs["mempool-max-parallel-ops"], - parsedArgs["mempool-max-queued-ops"], - parsedArgs["enforce-unique-senders-per-bundle"], eventManager - ) + }) } const getEventManager = ({ - endpoint, - chainId, - logger, + config, metrics }: { - endpoint?: string - chainId: number - logger: Logger + config: AltoConfig metrics: Metrics }) => { - return new EventManager(endpoint, chainId, logger, metrics) + return new EventManager({ config, metrics }) } -const getCompressionHandler = async ({ - client, - parsedArgs -}: { - client: PublicClient - parsedArgs: IBundleCompressionArgs -}): Promise => { +const getCompressionHandler = async ( + config: AltoConfig +): Promise => { let compressionHandler: CompressionHandler | null = null if ( - parsedArgs["bundle-bulker-address"] !== undefined && - parsedArgs["per-op-inflator-address"] !== undefined + config.bundleBulkerAddress !== undefined && + config.perOpInflatorAddress !== undefined ) { compressionHandler = await CompressionHandler.createAsync( - parsedArgs["bundle-bulker-address"], - parsedArgs["per-op-inflator-address"], - client + config.bundleBulkerAddress, + config.perOpInflatorAddress, + config.publicClient ) } return compressionHandler } const getExecutor = ({ - client, - walletClient, + config, senderManager, reputationManager, - parsedArgs, - logger, metrics, compressionHandler, gasPriceManager, eventManager }: { - client: PublicClient - walletClient: WalletClient + config: AltoConfig senderManager: SenderManager reputationManager: InterfaceReputationManager - parsedArgs: IOptions - logger: Logger metrics: Metrics compressionHandler: CompressionHandler | null gasPriceManager: GasPriceManager eventManager: EventManager }): Executor => { - return new Executor( - client, - walletClient, + return new Executor({ + config, senderManager, reputationManager, - parsedArgs.entrypoints, - logger.child( - { module: "executor" }, - { - level: - parsedArgs["executor-log-level"] || parsedArgs["log-level"] - } - ), metrics, compressionHandler, gasPriceManager, - eventManager, - !parsedArgs.tenderly, - parsedArgs["legacy-transactions"], - parsedArgs["fixed-gas-limit-for-estimation"], - parsedArgs["block-tag-support"], - parsedArgs["local-gas-limit-calculation"], - parsedArgs["no-profit-bundling"] - ) + eventManager + }) } const getExecutorManager = ({ + config, executor, mempool, monitor, reputationManager, - client, - parsedArgs, - logger, metrics, gasPriceManager, eventManager }: { + config: AltoConfig executor: Executor mempool: MemoryMempool monitor: Monitor reputationManager: InterfaceReputationManager - client: PublicClient - parsedArgs: IOptions - logger: Logger metrics: Metrics gasPriceManager: GasPriceManager eventManager: EventManager }) => { - return new ExecutorManager( + return new ExecutorManager({ + config, executor, - parsedArgs.entrypoints, mempool, monitor, reputationManager, - client, - parsedArgs["polling-interval"], - logger.child( - { module: "executor" }, - { - level: - parsedArgs["executor-log-level"] || parsedArgs["log-level"] - } - ), metrics, - parsedArgs["bundle-mode"], - parsedArgs["max-bundle-wait"], - parsedArgs["max-gas-per-bundle"], gasPriceManager, - eventManager, - parsedArgs["aa95-gas-multiplier"], - parsedArgs["max-block-range"] - ) + eventManager + }) } const getNonceQueuer = ({ + config, mempool, - client, - parsedArgs, - logger, eventManager }: { + config: AltoConfig mempool: MemoryMempool - client: PublicClient - parsedArgs: IOptions - logger: Logger eventManager: EventManager }) => { - return new NonceQueuer( + return new NonceQueuer({ + config, mempool, - client, - logger.child( - { module: "nonce_queuer" }, - { - level: - parsedArgs["nonce-queuer-log-level"] || - parsedArgs["log-level"] - } - ), - parsedArgs["block-tag-support"], eventManager - ) + }) } const getRpcHandler = ({ - client, + config, validator, mempool, executor, @@ -333,14 +199,12 @@ const getRpcHandler = ({ nonceQueuer, executorManager, reputationManager, - parsedArgs, - logger, metrics, compressionHandler, gasPriceManager, eventManager }: { - client: PublicClient + config: AltoConfig validator: InterfaceValidator mempool: MemoryMempool executor: Executor @@ -348,16 +212,13 @@ const getRpcHandler = ({ nonceQueuer: NonceQueuer executorManager: ExecutorManager reputationManager: InterfaceReputationManager - parsedArgs: IOptions - logger: Logger metrics: Metrics compressionHandler: CompressionHandler | null eventManager: EventManager gasPriceManager: GasPriceManager }) => { - return new RpcHandler( - parsedArgs.entrypoints, - client, + return new RpcHandler({ + config, validator, mempool, executor, @@ -365,139 +226,82 @@ const getRpcHandler = ({ nonceQueuer, executorManager, reputationManager, - parsedArgs.tenderly ?? false, - parsedArgs["max-block-range"], - logger.child( - { module: "rpc" }, - { level: parsedArgs["rpc-log-level"] || parsedArgs["log-level"] } - ), metrics, - parsedArgs["enable-debug-endpoints"], compressionHandler, - parsedArgs["legacy-transactions"], gasPriceManager, - parsedArgs["gas-price-multipliers"], - parsedArgs["chain-type"], - parsedArgs["paymaster-gas-limit-multiplier"], - eventManager, - parsedArgs["enable-instant-bundling-endpoint"], - parsedArgs["dangerous-skip-user-operation-validation"] - ) + eventManager + }) } const getServer = ({ + config, rpcEndpoint, - parsedArgs, - logger, registry, metrics }: { + config: AltoConfig rpcEndpoint: RpcHandler - parsedArgs: IOptions - logger: Logger registry: Registry metrics: Metrics }) => { - return new Server( + return new Server({ + config, rpcEndpoint, - parsedArgs["api-version"], - parsedArgs["default-api-version"], - parsedArgs.port, - parsedArgs.timeout, - parsedArgs["websocket-max-payload-size"], - parsedArgs.websocket, - logger.child( - { module: "rpc" }, - { level: parsedArgs["rpc-log-level"] || parsedArgs["log-level"] } - ), registry, - metrics, - parsedArgs["rpc-methods"] - ) + metrics + }) } export const setupServer = async ({ - client, - walletClient, - parsedArgs, - logger, - rootLogger, + config, registry, metrics, senderManager, gasPriceManager }: { - client: PublicClient - walletClient: WalletClient - parsedArgs: IOptions - logger: Logger - rootLogger: Logger + config: AltoConfig registry: Registry metrics: Metrics senderManager: SenderManager gasPriceManager: GasPriceManager }) => { const validator = getValidator({ - client, - logger, - parsedArgs, + config, senderManager, metrics, - gasPriceManager, - walletClient - }) - const reputationManager = getReputationManager({ - client, - parsedArgs, - logger + gasPriceManager }) + const reputationManager = getReputationManager(config) + + const compressionHandler = await getCompressionHandler(config) - const compressionHandler = await getCompressionHandler({ - client, - parsedArgs - }) const eventManager = getEventManager({ - endpoint: parsedArgs["redis-queue-endpoint"], - chainId: client.chain.id, - logger, + config, metrics }) - if (parsedArgs["refilling-wallets"]) { - await senderManager.validateAndRefillWallets( - client, - walletClient, - parsedArgs["min-executor-balance"] - ) + if (config.refillingWallets) { + await senderManager.validateAndRefillWallets() setInterval(async () => { - await senderManager.validateAndRefillWallets( - client, - walletClient, - parsedArgs["min-executor-balance"] - ) - }, parsedArgs["executor-refill-interval"] * 1000) + await senderManager.validateAndRefillWallets() + }, config.executorRefillInterval * 1000) } const monitor = getMonitor() const mempool = getMempool({ + config, monitor, reputationManager, validator, - client, - parsedArgs, - logger, metrics, eventManager }) const executor = getExecutor({ - client, - walletClient, + config, senderManager, reputationManager, - parsedArgs, - logger, metrics, compressionHandler, gasPriceManager, @@ -505,28 +309,24 @@ export const setupServer = async ({ }) const executorManager = getExecutorManager({ + config, executor, mempool, monitor, reputationManager, - client, - parsedArgs, - logger, metrics, gasPriceManager, eventManager }) const nonceQueuer = getNonceQueuer({ + config, mempool, - client, - parsedArgs, - logger, eventManager }) const rpcEndpoint = getRpcHandler({ - client, + config, validator, mempool, executor, @@ -534,26 +334,28 @@ export const setupServer = async ({ nonceQueuer, executorManager, reputationManager, - parsedArgs, - logger, metrics, compressionHandler, gasPriceManager, eventManager }) - if (parsedArgs["flush-stuck-transactions-during-startup"]) { + if (config.flushStuckTransactionsDuringStartup) { executor.flushStuckTransactions() } + const rootLogger = config.getLogger( + { module: "root" }, + { level: config.logLevel } + ) + rootLogger.info( `Initialized ${senderManager.wallets.length} executor wallets` ) const server = getServer({ + config, rpcEndpoint, - parsedArgs, - logger, registry, metrics }) diff --git a/src/createConfig.ts b/src/createConfig.ts new file mode 100644 index 00000000..6b4e8335 --- /dev/null +++ b/src/createConfig.ts @@ -0,0 +1,28 @@ +import type { IOptions } from "@alto/cli" +import type { CamelCasedProperties } from "./cli/parseArgs" +import type { Bindings, ChildLoggerOptions, Logger } from "pino" +import type { Chain, PublicClient, Transport, WalletClient } from "viem" + +export type AltoConfig = Readonly> & { + getLogger: ( + bindings: Bindings, + options?: ChildLoggerOptions + ) => Logger + readonly publicClient: PublicClient + readonly walletClient: WalletClient +} + +export function createConfig( + config: CamelCasedProperties & { + logger: Logger + publicClient: PublicClient + walletClient: WalletClient + } +): AltoConfig { + const { logger, ...rest } = config + + return { + ...rest, + getLogger: (bindings, options) => logger.child(bindings, options) + } +} diff --git a/src/executor/executor.ts b/src/executor/executor.ts index eafc3bbd..59e4dcf8 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -32,19 +32,14 @@ import { parseViemError, toPackedUserOperation } from "@alto/utils" -// biome-ignore lint/style/noNamespaceImport: explicitly make it clear when sentry is used import * as sentry from "@sentry/node" import { Mutex } from "async-mutex" import { type Account, - type Chain, FeeCapTooLowError, InsufficientFundsError, IntrinsicGasTooLowError, NonceTooLowError, - type PublicClient, - type Transport, - type WalletClient, encodeFunctionData, getContract } from "viem" @@ -55,6 +50,7 @@ import { flushStuckTransaction, simulatedOpsToResults } from "./utils" +import type { AltoConfig } from "../createConfig" export interface GasEstimateResult { preverificationGas: bigint @@ -76,59 +72,46 @@ export type ReplaceTransactionResult = export class Executor { // private unWatch: WatchBlocksReturnType | undefined - - publicClient: PublicClient - walletClient: WalletClient - entryPoints: Address[] + config: AltoConfig senderManager: SenderManager logger: Logger metrics: Metrics - simulateTransaction: boolean - legacyTransactions: boolean - fixedGasLimitForEstimation?: bigint - localGasLimitCalculation: boolean reputationManager: InterfaceReputationManager compressionHandler: CompressionHandler | null gasPriceManager: GasPriceManager - blockTagSupport: boolean mutex: Mutex eventManager: EventManager - noProfitBundling: boolean // if true, bundle such that all beneficiary fees go towards tx gasFees - - constructor( - publicClient: PublicClient, - walletClient: WalletClient, - senderManager: SenderManager, - reputationManager: InterfaceReputationManager, - entryPoints: Address[], - logger: Logger, - metrics: Metrics, - compressionHandler: CompressionHandler | null, - gasPriceManager: GasPriceManager, - eventManager: EventManager, - simulateTransaction = false, - legacyTransactions = false, - fixedGasLimitForEstimation?: bigint, - blockTagSupport = true, - localGasLimitCalculation = false, - noProfitBundling = false - ) { - this.publicClient = publicClient - this.walletClient = walletClient + + constructor({ + config, + senderManager, + reputationManager, + metrics, + compressionHandler, + gasPriceManager, + eventManager + }: { + config: AltoConfig + senderManager: SenderManager + reputationManager: InterfaceReputationManager + metrics: Metrics + compressionHandler: CompressionHandler | null + gasPriceManager: GasPriceManager + eventManager: EventManager + }) { + this.config = config this.senderManager = senderManager this.reputationManager = reputationManager - this.logger = logger + this.logger = config.getLogger( + { module: "executor" }, + { + level: config.executorLogLevel || config.logLevel + } + ) this.metrics = metrics - this.simulateTransaction = simulateTransaction - this.legacyTransactions = legacyTransactions - this.fixedGasLimitForEstimation = fixedGasLimitForEstimation - this.localGasLimitCalculation = localGasLimitCalculation this.compressionHandler = compressionHandler this.gasPriceManager = gasPriceManager this.eventManager = eventManager - this.blockTagSupport = blockTagSupport - this.entryPoints = entryPoints - this.noProfitBundling = noProfitBundling this.mutex = new Mutex() } @@ -179,7 +162,7 @@ export class Executor { userOperationHash: getUserOperationHash( op, transactionInfo.entryPoint, - this.walletClient.chain.id + this.config.walletClient.chain.id ), entryPoint: opInfo.entryPoint } @@ -218,8 +201,8 @@ export class Executor { abi: isUserOpVersion06 ? EntryPointV06Abi : EntryPointV07Abi, address: entryPoint, client: { - public: this.publicClient, - wallet: this.walletClient + public: this.config.publicClient, + wallet: this.config.walletClient } }) @@ -231,7 +214,7 @@ export class Executor { const compressionHandler = this.getCompressionHandler() callContext = { - publicClient: this.publicClient, + publicClient: this.config.publicClient, bundleBulker: compressionHandler.bundleBulkerAddress, perOpInflatorId: compressionHandler.perOpInflatorId, type: "compressed" @@ -246,9 +229,9 @@ export class Executor { newRequest.nonce, newRequest.maxFeePerGas, newRequest.maxPriorityFeePerGas, - this.blockTagSupport ? "latest" : undefined, - this.legacyTransactions, - this.fixedGasLimitForEstimation, + this.config.blockTagSupport ? "latest" : undefined, + this.config.legacyTransactions, + this.config.fixedGasLimitForEstimation, this.reputationManager, this.logger ) @@ -297,7 +280,7 @@ export class Executor { return opInfo }) - if (this.localGasLimitCalculation) { + if (this.config.localGasLimitCalculation) { gasLimit = opsToBundle.reduce((acc, opInfo) => { const userOperation = deriveUserOperation( opInfo.mempoolUserOperation @@ -399,8 +382,8 @@ export class Executor { "replacing transaction" ) - const txHash = await this.walletClient.sendTransaction( - this.legacyTransactions + const txHash = await this.config.walletClient.sendTransaction( + this.config.legacyTransactions ? { ...newRequest, gasPrice: newRequest.maxFeePerGas, @@ -414,7 +397,7 @@ export class Executor { opsToBundle.map((opToBundle) => { const op = deriveUserOperation(opToBundle.mempoolUserOperation) - const chainId = this.publicClient.chain?.id + const chainId = this.config.publicClient.chain?.id const opHash = getUserOperationHash( op, opToBundle.entryPoint, @@ -503,8 +486,8 @@ export class Executor { const gasPrice = await this.gasPriceManager.getGasPrice() const promises = wallets.map((wallet) => { flushStuckTransaction( - this.publicClient, - this.walletClient, + this.config.publicClient, + this.config.walletClient, wallet, gasPrice.maxFeePerGas * 5n, this.logger @@ -526,7 +509,7 @@ export class Executor { userOperationHash: getUserOperationHash( op, entryPoint, - this.walletClient.chain.id + this.config.walletClient.chain.id ) } }) @@ -550,8 +533,8 @@ export class Executor { abi: isUserOpVersion06 ? EntryPointV06Abi : EntryPointV07Abi, address: entryPoint, client: { - public: this.publicClient, - wallet: this.walletClient + public: this.config.publicClient, + wallet: this.config.walletClient } }) @@ -564,7 +547,7 @@ export class Executor { const gasPriceParameters = await this.gasPriceManager.getGasPrice() childLogger.debug({ gasPriceParameters }, "got gas price") - const nonce = await this.publicClient.getTransactionCount({ + const nonce = await this.config.publicClient.getTransactionCount({ address: wallet.address, blockTag: "pending" }) @@ -583,9 +566,9 @@ export class Executor { nonce, gasPriceParameters.maxFeePerGas, gasPriceParameters.maxPriorityFeePerGas, - this.blockTagSupport ? "pending" : undefined, - this.legacyTransactions, - this.fixedGasLimitForEstimation, + this.config.blockTagSupport ? "pending" : undefined, + this.config.legacyTransactions, + this.config.fixedGasLimitForEstimation, this.reputationManager, childLogger ) @@ -659,7 +642,7 @@ export class Executor { let transactionHash: HexData32 try { - const isLegacyTransaction = this.legacyTransactions + const isLegacyTransaction = this.config.legacyTransactions const gasOptions = isLegacyTransaction ? { gasPrice: gasPriceParameters.maxFeePerGas } @@ -669,7 +652,7 @@ export class Executor { gasPriceParameters.maxPriorityFeePerGas } - if (this.noProfitBundling) { + if (this.config.noProfitBundling) { const gasPrice = totalBeneficiaryFees / gasLimit if (isLegacyTransaction) { gasOptions.gasPrice = gasPrice @@ -814,7 +797,7 @@ export class Executor { ] }), gas: gasLimit, - chain: this.walletClient.chain, + chain: this.config.walletClient.chain, maxFeePerGas: gasPriceParameters.maxFeePerGas, maxPriorityFeePerGas: gasPriceParameters.maxPriorityFeePerGas, nonce: nonce @@ -864,14 +847,14 @@ export class Executor { const gasPriceParameters = await this.gasPriceManager.getGasPrice() childLogger.debug({ gasPriceParameters }, "got gas price") - const nonce = await this.publicClient.getTransactionCount({ + const nonce = await this.config.publicClient.getTransactionCount({ address: wallet.address, blockTag: "pending" }) childLogger.trace({ nonce }, "got nonce") const callContext: CompressedFilterOpsAndEstimateGasParams = { - publicClient: this.publicClient, + publicClient: this.config.publicClient, bundleBulker: compressionHandler.bundleBulkerAddress, perOpInflatorId: compressionHandler.perOpInflatorId, type: "compressed" @@ -887,16 +870,16 @@ export class Executor { userOperationHash: getUserOperationHash( compressedOp.inflatedOp, entryPoint, - this.walletClient.chain.id + this.config.walletClient.chain.id ) } }), nonce, gasPriceParameters.maxFeePerGas, gasPriceParameters.maxPriorityFeePerGas, - this.blockTagSupport ? "pending" : undefined, - this.legacyTransactions, - this.fixedGasLimitForEstimation, + this.config.blockTagSupport ? "pending" : undefined, + this.config.legacyTransactions, + this.config.fixedGasLimitForEstimation, this.reputationManager, childLogger ) @@ -910,7 +893,7 @@ export class Executor { const userOpHash = getUserOperationHash( compressedOp.inflatedOp, entryPoint, - this.walletClient.chain.id + this.config.walletClient.chain.id ) return { @@ -950,7 +933,7 @@ export class Executor { let transactionHash: HexData32 try { - const gasOptions = this.legacyTransactions + const gasOptions = this.config.legacyTransactions ? { gasPrice: gasPriceParameters.maxFeePerGas } @@ -968,7 +951,7 @@ export class Executor { ) // need to use sendTransaction to target BundleBulker's fallback - transactionHash = await this.walletClient.sendTransaction({ + transactionHash = await this.config.walletClient.sendTransaction({ account: wallet, to: compressionHandler.bundleBulkerAddress, data: createCompressedCalldata( @@ -1032,7 +1015,7 @@ export class Executor { ), gas: gasLimit, account: wallet, - chain: this.walletClient.chain, + chain: this.config.walletClient.chain, maxFeePerGas: gasPriceParameters.maxFeePerGas, maxPriorityFeePerGas: gasPriceParameters.maxPriorityFeePerGas, nonce: nonce diff --git a/src/executor/executorManager.ts b/src/executor/executorManager.ts index 58924f49..ec897064 100644 --- a/src/executor/executorManager.ts +++ b/src/executor/executorManager.ts @@ -27,16 +27,14 @@ import { import { type Address, type Block, - type Chain, type Hash, - type PublicClient, type TransactionReceipt, TransactionReceiptNotFoundError, - type Transport, type WatchBlocksReturnType, getAbiItem } from "viem" import type { Executor, ReplaceTransactionResult } from "./executor" +import type { AltoConfig } from "../createConfig" function getTransactionsFromUserOperationEntries( entries: SubmittedUserOperation[] @@ -51,63 +49,58 @@ function getTransactionsFromUserOperationEntries( } export class ExecutorManager { - private entryPoints: Address[] + private config: AltoConfig private executor: Executor private mempool: MemoryMempool private monitor: Monitor - private publicClient: PublicClient - private pollingInterval: number private logger: Logger private metrics: Metrics private reputationManager: InterfaceReputationManager private unWatch: WatchBlocksReturnType | undefined private currentlyHandlingBlock = false private timer?: NodeJS.Timer - private bundlerFrequency: number - private maxGasLimitPerBundle: bigint private gasPriceManager: GasPriceManager private eventManager: EventManager - private aa95ResubmitMultiplier: bigint rpcMaxBlockRange: number | undefined - constructor( - executor: Executor, - entryPoints: Address[], - mempool: MemoryMempool, - monitor: Monitor, - reputationManager: InterfaceReputationManager, - publicClient: PublicClient, - pollingInterval: number, - logger: Logger, - metrics: Metrics, - bundleMode: BundlingMode, - bundlerFrequency: number, - maxGasLimitPerBundle: bigint, - gasPriceManager: GasPriceManager, - eventManager: EventManager, - aa95ResubmitMultiplier: bigint, - rpcMaxBlockRange: number | undefined - ) { - this.entryPoints = entryPoints + constructor({ + config, + executor, + mempool, + monitor, + reputationManager, + metrics, + gasPriceManager, + eventManager + }: { + config: AltoConfig + executor: Executor + mempool: MemoryMempool + monitor: Monitor + reputationManager: InterfaceReputationManager + metrics: Metrics + gasPriceManager: GasPriceManager + eventManager: EventManager + }) { + this.config = config this.reputationManager = reputationManager this.executor = executor this.mempool = mempool this.monitor = monitor - this.publicClient = publicClient - this.pollingInterval = pollingInterval - this.logger = logger + this.logger = config.getLogger( + { module: "executor_manager" }, + { + level: config.executorLogLevel || config.logLevel + } + ) this.metrics = metrics - this.bundlerFrequency = bundlerFrequency - this.maxGasLimitPerBundle = maxGasLimitPerBundle this.gasPriceManager = gasPriceManager this.eventManager = eventManager - this.aa95ResubmitMultiplier = aa95ResubmitMultiplier - this.rpcMaxBlockRange = rpcMaxBlockRange - if (bundleMode === "auto") { + if (this.config.bundleMode === "auto") { this.timer = setInterval(async () => { await this.bundle() - }, bundlerFrequency) as NodeJS.Timer + }, this.config.maxBundleWait) as NodeJS.Timer } } @@ -115,7 +108,7 @@ export class ExecutorManager { if (bundleMode === "auto" && !this.timer) { this.timer = setInterval(async () => { await this.bundle() - }, this.bundlerFrequency) as NodeJS.Timer + }, this.config.maxBundleWait) as NodeJS.Timer } else if (bundleMode === "manual" && this.timer) { clearInterval(this.timer) this.timer = undefined @@ -123,7 +116,7 @@ export class ExecutorManager { } async bundleNow(): Promise { - const ops = await this.mempool.process(this.maxGasLimitPerBundle, 1) + const ops = await this.mempool.process(this.config.maxGasPerBundle, 1) if (ops.length === 0) { throw new Error("no ops to bundle") } @@ -140,7 +133,7 @@ export class ExecutorManager { const txHashes: Hash[] = [] await Promise.all( - this.entryPoints.map(async (entryPoint) => { + this.config.entrypoints.map(async (entryPoint) => { const ops = opEntryPointMap.get(entryPoint) if (ops) { const txHash = await this.sendToExecutor(entryPoint, ops) @@ -297,7 +290,10 @@ export class ExecutorManager { const opsToBundle: UserOperationInfo[][] = [] while (true) { - const ops = await this.mempool.process(this.maxGasLimitPerBundle, 1) + const ops = await this.mempool.process( + this.config.maxGasPerBundle, + 1 + ) if (ops?.length > 0) { opsToBundle.push(ops) } else { @@ -326,7 +322,7 @@ export class ExecutorManager { } await Promise.all( - this.entryPoints.map(async (entryPoint) => { + this.config.entrypoints.map(async (entryPoint) => { const userOperations = opEntryPointMap.get(entryPoint) if (userOperations) { await this.sendToExecutor( @@ -349,7 +345,7 @@ export class ExecutorManager { if (this.unWatch) { return } - this.unWatch = this.publicClient.watchBlocks({ + this.unWatch = this.config.publicClient.watchBlocks({ onBlock: handleBlock, // onBlock: async (block) => { // // Use an arrow function to ensure correct binding of `this` @@ -368,7 +364,7 @@ export class ExecutorManager { }, emitMissed: false, includeTransactions: false, - pollingInterval: this.pollingInterval + pollingInterval: this.config.pollingInterval }) this.logger.debug("started watching blocks") @@ -405,7 +401,7 @@ export class ExecutorManager { ...(await getBundleStatus( isVersion06, transactionHash, - this.publicClient, + this.config.publicClient, this.logger, entryPoint )) @@ -498,7 +494,7 @@ export class ExecutorManager { bundlingStatus.isAA95 ) { // resubmit with more gas when bundler encounters AA95 - const multiplier = this.aa95ResubmitMultiplier + const multiplier = this.config.aa95GasMultiplier transactionInfo.transactionRequest.gas = (transactionInfo.transactionRequest.gas * multiplier) / 100n transactionInfo.transactionRequest.nonce += 1 @@ -535,7 +531,7 @@ export class ExecutorManager { transactionHash: Hash blockNumber: bigint }) { - const unwatch = this.publicClient.watchBlockNumber({ + const unwatch = this.config.publicClient.watchBlockNumber({ onBlockNumber: async (currentBlockNumber) => { if (currentBlockNumber > blockNumber + 1n) { const userOperationReceipt = @@ -606,7 +602,7 @@ export class ExecutorManager { let fromBlock: bigint | undefined = undefined let toBlock: "latest" | undefined = undefined if (this.rpcMaxBlockRange !== undefined) { - const latestBlock = await this.publicClient.getBlockNumber() + const latestBlock = await this.config.publicClient.getBlockNumber() fromBlock = latestBlock - BigInt(this.rpcMaxBlockRange) if (fromBlock < 0n) { fromBlock = 0n @@ -614,8 +610,8 @@ export class ExecutorManager { toBlock = "latest" } - const filterResult = await this.publicClient.getLogs({ - address: this.entryPoints, + const filterResult = await this.config.publicClient.getLogs({ + address: this.config.entrypoints, event: userOperationEventAbiItem, fromBlock, toBlock, @@ -665,7 +661,7 @@ export class ExecutorManager { while (true) { try { const transactionReceipt = - await this.publicClient.getTransactionReceipt({ + await this.config.publicClient.getTransactionReceipt({ hash: txHash }) @@ -675,9 +671,10 @@ export class ExecutorManager { undefined if (effectiveGasPrice === undefined) { - const tx = await this.publicClient.getTransaction({ - hash: txHash - }) + const tx = + await this.config.publicClient.getTransaction({ + hash: txHash + }) effectiveGasPrice = tx.gasPrice ?? undefined } @@ -735,7 +732,7 @@ export class ExecutorManager { } await Promise.all( - this.entryPoints.map(async (entryPoint) => { + this.config.entrypoints.map(async (entryPoint) => { const ops = opEntryPointMap.get(entryPoint) if (ops) { diff --git a/src/executor/senderManager.ts b/src/executor/senderManager.ts index 4b545806..4bd7a2a8 100644 --- a/src/executor/senderManager.ts +++ b/src/executor/senderManager.ts @@ -9,14 +9,12 @@ import type { Logger, Metrics } from "@alto/utils" import { Semaphore } from "async-mutex" import { type Account, - type Chain, type PublicClient, type TransactionReceipt, - type Transport, - type WalletClient, formatEther, getContract } from "viem" +import type { AltoConfig } from "../createConfig" const waitForTransactionReceipt = async ( publicClient: PublicClient, @@ -30,24 +28,35 @@ const waitForTransactionReceipt = async ( } export class SenderManager { + private config: AltoConfig wallets: Account[] utilityAccount: Account | undefined availableWallets: Account[] - private logger: Logger private metrics: Metrics - private legacyTransactions: boolean private semaphore: Semaphore private gasPriceManager: GasPriceManager + private logger: Logger + + constructor({ + config, + metrics, + gasPriceManager + }: { + config: AltoConfig + metrics: Metrics + gasPriceManager: GasPriceManager + }) { + this.config = config + this.logger = config.getLogger( + { module: "executor" }, + { + level: config.executorLogLevel || config.logLevel + } + ) + + const maxSigners = config.maxExecutors + const wallets = config.executorPrivateKeys - constructor( - wallets: Account[], - utilityAccount: Account | undefined, - logger: Logger, - metrics: Metrics, - legacyTransactions: boolean, - gasPriceManager: GasPriceManager, - maxSigners?: number - ) { if (maxSigners !== undefined && wallets.length > maxSigners) { this.wallets = wallets.slice(0, maxSigners) this.availableWallets = wallets.slice(0, maxSigners) @@ -56,10 +65,8 @@ export class SenderManager { this.availableWallets = wallets } - this.utilityAccount = utilityAccount - this.logger = logger + this.utilityAccount = this.config.utilityPrivateKey this.metrics = metrics - this.legacyTransactions = legacyTransactions metrics.walletsAvailable.set(this.availableWallets.length) metrics.walletsTotal.set(this.wallets.length) this.semaphore = new Semaphore(this.availableWallets.length) @@ -67,16 +74,14 @@ export class SenderManager { } // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: - async validateAndRefillWallets( - publicClient: PublicClient, - walletClient: WalletClient, - minBalance?: bigint - ): Promise { + async validateAndRefillWallets(): Promise { + const minBalance = this.config.minExecutorBalance + if (!(minBalance && this.utilityAccount)) { return } - const utilityWalletBalance = await publicClient.getBalance({ + const utilityWalletBalance = await this.config.publicClient.getBalance({ address: this.utilityAccount.address }) @@ -84,7 +89,7 @@ export class SenderManager { const balanceRequestPromises = this.availableWallets.map( async (wallet) => { - const balance = await publicClient.getBalance({ + const balance = await this.config.publicClient.getBalance({ address: wallet.address }) @@ -136,9 +141,9 @@ export class SenderManager { await this.gasPriceManager.getGasPrice() if ( - walletClient.chain.id === 59140 || - walletClient.chain.id === 137 || - walletClient.chain.id === 10 + this.config.walletClient.chain.id === 59140 || + this.config.walletClient.chain.id === 137 || + this.config.walletClient.chain.id === 10 ) { const instructions = [] for (const [address, missingBalance] of Object.entries( @@ -152,9 +157,9 @@ export class SenderManager { } let refillAddress: `0x${string}` - if (walletClient.chain.id === 59140) { + if (this.config.walletClient.chain.id === 59140) { refillAddress = "0xEad1aC3DF6F96b91491d6396F4d1610C5638B4Db" - } else if (walletClient.chain.id === 137) { + } else if (this.config.walletClient.chain.id === 137) { refillAddress = "0x3402DB43152dAB9ab72fa805fdD5f391cD3E3822" } else { refillAddress = "0x3402DB43152dAB9ab72fa805fdD5f391cD3E3822" @@ -164,8 +169,8 @@ export class SenderManager { abi: CallEngineAbi, address: refillAddress, client: { - public: publicClient, - wallet: walletClient + public: this.config.publicClient, + wallet: this.config.walletClient } }) const tx = await callEngine.write.execute([instructions], { @@ -175,7 +180,7 @@ export class SenderManager { maxPriorityFeePerGas: maxPriorityFeePerGas * 2n }) - await waitForTransactionReceipt(publicClient, tx) + await waitForTransactionReceipt(this.config.publicClient, tx) for (const [address, missingBalance] of Object.entries( balancesMissing @@ -189,23 +194,26 @@ export class SenderManager { for (const [address, missingBalance] of Object.entries( balancesMissing )) { - const tx = await walletClient.sendTransaction({ + const tx = await this.config.walletClient.sendTransaction({ account: this.utilityAccount, // @ts-ignore to: address, value: missingBalance, - maxFeePerGas: this.legacyTransactions + maxFeePerGas: this.config.legacyTransactions ? undefined : maxFeePerGas, - maxPriorityFeePerGas: this.legacyTransactions + maxPriorityFeePerGas: this.config.legacyTransactions ? undefined : maxPriorityFeePerGas, - gasPrice: this.legacyTransactions + gasPrice: this.config.legacyTransactions ? maxFeePerGas : undefined }) - await waitForTransactionReceipt(publicClient, tx) + await waitForTransactionReceipt( + this.config.publicClient, + tx + ) this.logger.info( { tx, executor: address, missingBalance }, "refilled wallet" diff --git a/src/executor/utilityWalletMonitor.ts b/src/executor/utilityWalletMonitor.ts index 306f5c55..41f92ae7 100644 --- a/src/executor/utilityWalletMonitor.ts +++ b/src/executor/utilityWalletMonitor.ts @@ -1,31 +1,38 @@ import type { Logger, Metrics } from "@alto/utils" -import { type Hex, type PublicClient, formatEther } from "viem" +import { type Hex, formatEther } from "viem" +import type { AltoConfig } from "../createConfig" +import type { Address } from "abitype" export class UtilityWalletMonitor { - private publicClient: PublicClient - private monitorInterval: number + private config: AltoConfig private utilityWalletAddress: Hex private timer: NodeJS.Timer | undefined private metrics: Metrics private logger: Logger - constructor( - publicClient: PublicClient, - monitorInterval: number, - utilityWalletAddress: Hex, - metrics: Metrics, - logger: Logger - ) { - this.publicClient = publicClient - this.monitorInterval = monitorInterval + constructor({ + config, + metrics, + utilityWalletAddress + }: { + config: AltoConfig + metrics: Metrics + utilityWalletAddress: Address + }) { + this.config = config this.utilityWalletAddress = utilityWalletAddress this.metrics = metrics - this.logger = logger + this.logger = config.getLogger( + { module: "utility_wallet_monitor" }, + { + level: config.logLevel + } + ) } private async updateMetrics() { try { - const balance = await this.publicClient.getBalance({ + const balance = await this.config.publicClient.getBalance({ address: this.utilityWalletAddress }) @@ -49,7 +56,7 @@ export class UtilityWalletMonitor { this.timer = setInterval( this.updateMetrics.bind(this), - this.monitorInterval + this.config.utilityWalletMonitorInterval ) as NodeJS.Timer } diff --git a/src/handlers/eventManager.ts b/src/handlers/eventManager.ts index 94cdfeb4..e5a6589f 100644 --- a/src/handlers/eventManager.ts +++ b/src/handlers/eventManager.ts @@ -1,28 +1,35 @@ import type { Logger, Metrics } from "@alto/utils" -// biome-ignore lint/style/noNamespaceImport: explicitly make it clear when sentry is used import * as sentry from "@sentry/node" import Redis from "ioredis" import type { Hex } from "viem" import type { OpEventType } from "../types/schemas" +import type { AltoConfig } from "../createConfig" export class EventManager { - private redis: Redis | undefined private chainId: number + private redis: Redis | undefined private logger: Logger private metrics: Metrics - constructor( - endpoint: string | undefined, - chainId: number, - logger: Logger, + constructor({ + config, + metrics + }: { + config: AltoConfig metrics: Metrics - ) { - this.chainId = chainId - this.logger = logger + }) { + this.chainId = config.publicClient.chain.id + + this.logger = config.getLogger( + { module: "event_manager" }, + { + level: config.logLevel + } + ) this.metrics = metrics - if (endpoint) { - this.redis = new Redis(endpoint) + if (config.redisQueueEndpoint) { + this.redis = new Redis(config.redisQueueEndpoint) return } diff --git a/src/handlers/gasPriceManager.ts b/src/handlers/gasPriceManager.ts index d9bf34bd..4d0094ba 100644 --- a/src/handlers/gasPriceManager.ts +++ b/src/handlers/gasPriceManager.ts @@ -1,12 +1,11 @@ import { - type ChainType, type GasPriceParameters, RpcError, gasStationResult } from "@alto/types" import { type Logger, maxBigInt, minBigInt } from "@alto/utils" import * as sentry from "@sentry/node" -import { type Chain, type PublicClient, maxUint128, parseGwei } from "viem" +import { type PublicClient, maxUint128, parseGwei } from "viem" import { avalanche, celo, @@ -15,6 +14,7 @@ import { polygon, polygonMumbai } from "viem/chains" +import type { AltoConfig } from "../createConfig" enum ChainId { Goerli = 5, @@ -129,10 +129,7 @@ class ArbitrumManager { } export class GasPriceManager { - private chain: Chain - private publicClient: PublicClient - private legacyTransactions: boolean - private logger: Logger + private readonly config: AltoConfig private queueBaseFeePerGas: { timestamp: number; baseFeePerGas: bigint }[] = [] // Store pairs of [price, timestamp] private queueMaxFeePerGas: { timestamp: number; maxFeePerGas: bigint }[] = @@ -141,40 +138,29 @@ export class GasPriceManager { timestamp: number maxPriorityFeePerGas: bigint }[] = [] // Store pairs of [price, timestamp] - private maxQueueSize - private gasBumpMultiplier: bigint - private gasPriceRefreshIntervalInSeconds: number - private chainType: ChainType public arbitrumManager: ArbitrumManager + private maxQueueSize: number + private logger: Logger - constructor( - chain: Chain, - publicClient: PublicClient, - legacyTransactions: boolean, - logger: Logger, - gasBumpMultiplier: bigint, - gasPriceTimeValidityInSeconds: number, - gasPriceRefreshIntervalInSeconds: number, - chainType: ChainType - ) { - this.maxQueueSize = gasPriceTimeValidityInSeconds - this.chain = chain - this.publicClient = publicClient - this.legacyTransactions = legacyTransactions - this.logger = logger - this.gasBumpMultiplier = gasBumpMultiplier - this.gasPriceRefreshIntervalInSeconds = gasPriceRefreshIntervalInSeconds - this.chainType = chainType + constructor(config: AltoConfig) { + this.config = config + this.logger = config.getLogger( + { module: "gas_price_manager" }, + { + level: config.publicClientLogLevel || config.logLevel + } + ) + this.maxQueueSize = this.config.gasPriceExpiry // Periodically update gas prices if specified - if (this.gasPriceRefreshIntervalInSeconds > 0) { + if (this.config.gasPriceRefreshInterval > 0) { setInterval(() => { - if (this.legacyTransactions === false) { + if (this.config.legacyTransactions === false) { this.updateBaseFee() } this.updateGasPrice() - }, this.gasPriceRefreshIntervalInSeconds * 1000) + }, this.config.gasPriceRefreshInterval * 1000) } this.arbitrumManager = new ArbitrumManager(this.maxQueueSize) @@ -183,7 +169,7 @@ export class GasPriceManager { public init() { return Promise.all([ this.updateGasPrice(), - this.legacyTransactions === false + this.config.legacyTransactions === false ? this.updateBaseFee() : Promise.resolve() ]) @@ -203,7 +189,9 @@ export class GasPriceManager { } private async getPolygonGasPriceParameters(): Promise { - const gasStationUrl = getGasStationUrl(this.chain.id) + const gasStationUrl = getGasStationUrl( + this.config.publicClient.chain.id + ) try { const data = await (await fetch(gasStationUrl)).json() // take the standard speed here, SDK options will define the extra tip @@ -222,11 +210,11 @@ export class GasPriceManager { private bumpTheGasPrice( gasPriceParameters: GasPriceParameters ): GasPriceParameters { - const bumpAmount = this.gasBumpMultiplier + const bumpAmount = this.config.gasPriceBump const maxPriorityFeePerGas = maxBigInt( gasPriceParameters.maxPriorityFeePerGas, - this.getDefaultGasFee(this.chain.id) + this.getDefaultGasFee(this.config.publicClient.chain.id) ) const maxFeePerGas = maxBigInt( gasPriceParameters.maxFeePerGas, @@ -238,7 +226,10 @@ export class GasPriceManager { maxPriorityFeePerGas: (maxPriorityFeePerGas * bumpAmount) / 100n } - if (this.chain.id === celo.id || this.chain.id === celoAlfajores.id) { + if ( + this.config.publicClient.chain.id === celo.id || + this.config.publicClient.chain.id === celoAlfajores.id + ) { const maxFee = maxBigInt( result.maxFeePerGas, result.maxPriorityFeePerGas @@ -249,7 +240,7 @@ export class GasPriceManager { } } - if (this.chain.id === dfk.id) { + if (this.config.publicClient.chain.id === dfk.id) { const maxFeePerGas = maxBigInt(5_000_000_000n, result.maxFeePerGas) const maxPriorityFeePerGas = maxBigInt( 5_000_000_000n, @@ -263,7 +254,7 @@ export class GasPriceManager { } // set a minimum maxPriorityFee & maxFee to 1.5gwei on avalanche (because eth_maxPriorityFeePerGas returns 0) - if (this.chain.id === avalanche.id) { + if (this.config.publicClient.chain.id === avalanche.id) { const maxFeePerGas = maxBigInt( parseGwei("1.5"), result.maxFeePerGas @@ -332,8 +323,8 @@ export class GasPriceManager { private async getLegacyTransactionGasPrice(): Promise { let gasPrice: bigint | undefined try { - const gasInfo = await this.publicClient.estimateFeesPerGas({ - chain: this.chain, + const gasInfo = await this.config.publicClient.estimateFeesPerGas({ + chain: this.config.publicClient.chain, type: "legacy" }) gasPrice = gasInfo.gasPrice @@ -349,7 +340,7 @@ export class GasPriceManager { if (gasPrice === undefined) { this.logger.warn("gasPrice is undefined, using fallback value") try { - gasPrice = await this.publicClient.getGasPrice() + gasPrice = await this.config.publicClient.getGasPrice() } catch (e) { this.logger.error("failed to get fallback gasPrice") sentry.captureException(e) @@ -368,8 +359,8 @@ export class GasPriceManager { let maxPriorityFeePerGas: bigint | undefined try { - const fees = await this.publicClient.estimateFeesPerGas({ - chain: this.chain + const fees = await this.config.publicClient.estimateFeesPerGas({ + chain: this.config.publicClient.chain }) maxFeePerGas = fees.maxFeePerGas maxPriorityFeePerGas = fees.maxPriorityFeePerGas @@ -390,7 +381,7 @@ export class GasPriceManager { try { maxPriorityFeePerGas = await this.getFallBackMaxPriorityFeePerGas( - this.publicClient, + this.config.publicClient, maxFeePerGas ?? 0n ) } catch (e) { @@ -404,7 +395,7 @@ export class GasPriceManager { this.logger.warn("maxFeePerGas is undefined, using fallback value") try { maxFeePerGas = - (await this.getNextBaseFee(this.publicClient)) + + (await this.getNextBaseFee(this.config.publicClient)) + maxPriorityFeePerGas } catch (e) { this.logger.error("failed to get fallback maxFeePerGas") @@ -481,8 +472,8 @@ export class GasPriceManager { let maxPriorityFeePerGas = 0n if ( - this.chain.id === polygon.id || - this.chain.id === polygonMumbai.id + this.config.publicClient.chain.id === polygon.id || + this.config.publicClient.chain.id === polygonMumbai.id ) { const polygonEstimate = await this.getPolygonGasPriceParameters() if (polygonEstimate) { @@ -504,7 +495,7 @@ export class GasPriceManager { } } - if (this.legacyTransactions) { + if (this.config.legacyTransactions) { const gasPrice = this.bumpTheGasPrice( await this.getLegacyTransactionGasPrice() ) @@ -536,7 +527,7 @@ export class GasPriceManager { } private async updateBaseFee(): Promise { - const latestBlock = await this.publicClient.getBlock() + const latestBlock = await this.config.publicClient.getBlock() if (latestBlock.baseFeePerGas === null) { throw new RpcError("block does not have baseFeePerGas") } @@ -548,13 +539,13 @@ export class GasPriceManager { } public getBaseFee() { - if (this.legacyTransactions) { + if (this.config.legacyTransactions) { throw new RpcError( "baseFee is not available for legacy transactions" ) } - if (this.gasPriceRefreshIntervalInSeconds === 0) { + if (this.config.gasPriceRefreshInterval === 0) { return this.updateBaseFee() } @@ -579,7 +570,7 @@ export class GasPriceManager { } public getGasPrice() { - if (this.gasPriceRefreshIntervalInSeconds === 0) { + if (this.config.gasPriceRefreshInterval === 0) { return this.updateGasPrice() } @@ -634,7 +625,7 @@ export class GasPriceManager { let lowestMaxFeePerGas = await this.getMinMaxFeePerGas() let lowestMaxPriorityFeePerGas = await this.getMinMaxPriorityFeePerGas() - if (this.chainType === "hedera") { + if (this.config.chainType === "hedera") { lowestMaxFeePerGas /= 10n ** 9n lowestMaxPriorityFeePerGas /= 10n ** 9n } diff --git a/src/mempool/mempool.ts b/src/mempool/mempool.ts index 63089c26..6539b05b 100644 --- a/src/mempool/mempool.ts +++ b/src/mempool/mempool.ts @@ -28,60 +28,52 @@ import { isVersion06, isVersion07 } from "@alto/utils" -import { - type Address, - type Chain, - type PublicClient, - type Transport, - getAddress, - getContract -} from "viem" +import { type Address, getAddress, getContract } from "viem" import type { Monitor } from "./monitoring" import { type InterfaceReputationManager, ReputationStatuses } from "./reputationManager" import { MemoryStore } from "./store" +import type { AltoConfig } from "../createConfig" export class MemoryMempool { + private config: AltoConfig private monitor: Monitor - private publicClient: PublicClient private reputationManager: InterfaceReputationManager private store: MemoryStore private throttledEntityBundleCount: number private logger: Logger private validator: InterfaceValidator - private safeMode: boolean - private parallelUserOpsMaxSize: number - private queuedUserOpsMaxSize: number - private onlyUniqueSendersPerBundle: boolean private eventManager: EventManager - constructor( - monitor: Monitor, - reputationManager: InterfaceReputationManager, - validator: InterfaceValidator, - publicClient: PublicClient, - safeMode: boolean, - logger: Logger, - metrics: Metrics, - parallelUserOpsMaxSize: number, - queuedUserOpsMaxSize: number, - onlyUniqueSendersPerBundle: boolean, - eventManager: EventManager, - throttledEntityBundleCount?: number - ) { + constructor({ + config, + monitor, + reputationManager, + validator, + metrics, + eventManager + }: { + config: AltoConfig + monitor: Monitor + reputationManager: InterfaceReputationManager + validator: InterfaceValidator + metrics: Metrics + eventManager: EventManager + }) { + this.config = config this.reputationManager = reputationManager this.monitor = monitor this.validator = validator - this.publicClient = publicClient - this.safeMode = safeMode - this.logger = logger - this.store = new MemoryStore(logger, metrics) - this.parallelUserOpsMaxSize = parallelUserOpsMaxSize - this.queuedUserOpsMaxSize = queuedUserOpsMaxSize - this.onlyUniqueSendersPerBundle = onlyUniqueSendersPerBundle - this.throttledEntityBundleCount = throttledEntityBundleCount ?? 4 + this.logger = config.getLogger( + { module: "mempool" }, + { + level: config.logLevel + } + ) + this.store = new MemoryStore(this.logger, metrics) + this.throttledEntityBundleCount = 4 // we don't have any config for this as of now this.eventManager = eventManager } @@ -152,11 +144,11 @@ export class MemoryMempool { this.store.removeProcessing(userOpHash) } - // biome-ignore lint/suspicious/useAwait: keep async to adhere to interface - async checkEntityMultipleRoleViolation(op: UserOperation): Promise { - if (!this.safeMode) { - return + checkEntityMultipleRoleViolation(op: UserOperation): Promise { + if (!this.config.safeMode) { + return Promise.resolve() } + const knownEntities = this.getKnownEntities() if ( @@ -197,6 +189,7 @@ export class MemoryMempool { ValidationErrors.OpcodeValidation ) } + return Promise.resolve() } getKnownEntities(): { @@ -253,7 +246,7 @@ export class MemoryMempool { const opHash = getUserOperationHash( op, entryPoint, - this.publicClient.chain.id + this.config.publicClient.chain.id ) const outstandingOps = [...this.store.dumpOutstanding()] @@ -374,7 +367,7 @@ export class MemoryMempool { return userOp.sender === op.sender }).length - if (parallelUserOperationsCount > this.parallelUserOpsMaxSize) { + if (parallelUserOperationsCount > this.config.mempoolMaxParallelOps) { return [ false, "AA25 invalid account nonce: Maximum number of parallel user operations for that is allowed for this sender reached" @@ -394,7 +387,7 @@ export class MemoryMempool { return userOp.sender === op.sender && opNonceKey === nonceKey }).length - if (queuedUserOperationsCount > this.queuedUserOpsMaxSize) { + if (queuedUserOperationsCount > this.config.mempoolMaxQueuedOps) { return [ false, "AA25 invalid account nonce: Maximum number of queued user operations reached for this sender and nonce key" @@ -443,7 +436,7 @@ export class MemoryMempool { storageMap: StorageMap }> { const op = deriveUserOperation(opInfo.mempoolUserOperation) - if (!this.safeMode) { + if (!this.config.safeMode) { return { skip: false, paymasterDeposit, @@ -530,7 +523,10 @@ export class MemoryMempool { } } - if (senders.has(op.sender) && this.onlyUniqueSendersPerBundle) { + if ( + senders.has(op.sender) && + this.config.enforceUniqueSendersPerBundle + ) { this.logger.trace( { sender: op.sender, @@ -614,7 +610,7 @@ export class MemoryMempool { abi: isUserOpV06 ? EntryPointV06Abi : EntryPointV07Abi, address: opInfo.entryPoint, client: { - public: this.publicClient + public: this.config.publicClient } }) paymasterDeposit[paymaster] = @@ -776,7 +772,7 @@ export class MemoryMempool { ? EntryPointV06Abi : EntryPointV07Abi, client: { - public: this.publicClient + public: this.config.publicClient } }) diff --git a/src/mempool/reputationManager.ts b/src/mempool/reputationManager.ts index 042d7006..c0c9235e 100644 --- a/src/mempool/reputationManager.ts +++ b/src/mempool/reputationManager.ts @@ -12,7 +12,8 @@ import { getAddressFromInitCodeOrPaymasterAndData, isVersion06 } from "@alto/utils" -import { type Address, type PublicClient, getAddress, getContract } from "viem" +import { type Address, getAddress, getContract } from "viem" +import type { AltoConfig } from "../createConfig" export interface InterfaceReputationManager { checkReputation( @@ -172,9 +173,7 @@ export class NullReputationManager implements InterfaceReputationManager { } export class ReputationManager implements InterfaceReputationManager { - private publicClient: PublicClient - private minStake: bigint - private minUnstakeDelay: bigint + private config: AltoConfig private entityCount: { [address: Address]: bigint } = {} private throttledEntityMinMempoolCount: bigint private maxMempoolUserOperationsPerSender: bigint @@ -188,40 +187,28 @@ export class ReputationManager implements InterfaceReputationManager { private bundlerReputationParams: ReputationParams private logger: Logger - constructor( - publicClient: PublicClient, - entryPoints: Address[], - minStake: bigint, - minUnstakeDelay: bigint, - logger: Logger, - maxMempoolUserOperationsPerNewUnstakedEntity?: bigint, - throttledEntityMinMempoolCount?: bigint, - inclusionRateFactor?: bigint, - maxMempoolUserOperationsPerSender?: bigint, - blackList?: Address[], - whiteList?: Address[], - bundlerReputationParams?: ReputationParams - ) { - this.publicClient = publicClient - this.minStake = minStake - this.minUnstakeDelay = minUnstakeDelay - this.logger = logger - this.maxMempoolUserOperationsPerNewUnstakedEntity = - maxMempoolUserOperationsPerNewUnstakedEntity ?? 10n - this.inclusionRateFactor = inclusionRateFactor ?? 10n - this.throttledEntityMinMempoolCount = - throttledEntityMinMempoolCount ?? 4n - this.maxMempoolUserOperationsPerSender = - maxMempoolUserOperationsPerSender ?? 4n - this.bundlerReputationParams = - bundlerReputationParams ?? BundlerReputationParams - for (const address of blackList || []) { - this.blackList.add(address) - } - for (const address of whiteList || []) { - this.whitelist.add(address) - } - for (const entryPoint of entryPoints) { + constructor(config: AltoConfig) { + this.config = config + this.logger = config.getLogger( + { module: "reputation_manager" }, + { + level: config.reputationManagerLogLevel || config.logLevel + } + ) + this.maxMempoolUserOperationsPerNewUnstakedEntity = 10n + this.inclusionRateFactor = 10n + this.throttledEntityMinMempoolCount = 4n + this.maxMempoolUserOperationsPerSender = 4n + this.bundlerReputationParams = BundlerReputationParams + + // Currently we don't have any args for blacklist and whitelist + // for (const address of blackList || []) { + // this.blackList.add(address) + // } + // for (const address of whiteList || []) { + // this.whitelist.add(address) + // } + for (const entryPoint of config.entrypoints) { this.entries[entryPoint] = {} } } @@ -279,7 +266,7 @@ export class ReputationManager implements InterfaceReputationManager { abi: EntryPointV06Abi, address: entryPoint, client: { - public: this.publicClient + public: this.config.publicClient } }) const stakeInfo = await entryPointContract.read.getDepositInfo([ @@ -290,7 +277,8 @@ export class ReputationManager implements InterfaceReputationManager { const unstakeDelaySec = BigInt(stakeInfo.unstakeDelaySec) const isStaked = - stake >= this.minStake && unstakeDelaySec >= this.minUnstakeDelay + stake >= this.config.minEntityStake && + unstakeDelaySec >= this.config.minEntityUnstakeDelay return { stakeInfo: { @@ -663,10 +651,10 @@ export class ReputationManager implements InterfaceReputationManager { } this.checkBanned(entryPoint, entityType, stakeInfo) - if (stakeInfo.stake < this.minStake) { + if (stakeInfo.stake < this.config.minEntityStake) { if (stakeInfo.stake === 0n) { throw new RpcError( - `${entityType} ${stakeInfo.addr} is unstaked and must stake minimum ${this.minStake} to use pimlico`, + `${entityType} ${stakeInfo.addr} is unstaked and must stake minimum ${this.config.minEntityStake} to use pimlico`, ValidationErrors.InsufficientStake ) } @@ -677,7 +665,7 @@ export class ReputationManager implements InterfaceReputationManager { ) } - if (stakeInfo.unstakeDelaySec < this.minUnstakeDelay) { + if (stakeInfo.unstakeDelaySec < this.config.minEntityUnstakeDelay) { throw new RpcError( `${entityType} ${stakeInfo.addr} does not have enough unstake delay to use pimlico`, ValidationErrors.InsufficientStake diff --git a/src/rpc/estimation/gasEstimationHandler.ts b/src/rpc/estimation/gasEstimationHandler.ts index 09041b5f..201bf3de 100644 --- a/src/rpc/estimation/gasEstimationHandler.ts +++ b/src/rpc/estimation/gasEstimationHandler.ts @@ -1,11 +1,12 @@ -import type { ChainType, UserOperation } from "@alto/types" +import type { UserOperation } from "@alto/types" import type { StateOverrides, UserOperationV07 } from "@alto/types" import { deepHexlify, isVersion06 } from "@alto/utils" import type { Hex } from "viem" -import { type Address, type PublicClient, toHex } from "viem" +import { type Address, toHex } from "viem" import { GasEstimatorV06 } from "./gasEstimationsV06" import { GasEstimatorV07 } from "./gasEstimationsV07" import type { SimulateHandleOpResult } from "./types" +import type { AltoConfig } from "../../createConfig" export const EXECUTE_SIMULATOR_BYTECODE = "0x60806040526004361061012e5760003560e01c806372b37bca116100ab578063b760faf91161006f578063b760faf914610452578063bb9fe6bf14610465578063c23a5cea1461047a578063d6383f941461049a578063ee219423146104ba578063fc7e286d146104da57600080fd5b806372b37bca146103bd5780638f41ec5a146103dd578063957122ab146103f25780639b249f6914610412578063a61935311461043257600080fd5b8063205c2878116100f2578063205c28781461020157806335567e1a146102215780634b1d7cf5146102415780635287ce121461026157806370a082311461037e57600080fd5b80630396cb60146101435780630bd28e3b146101565780631b2e01b8146101765780631d732756146101c15780631fad948c146101e157600080fd5b3661013e5761013c3361058f565b005b600080fd5b61013c6101513660046131c9565b6105f6565b34801561016257600080fd5b5061013c61017136600461320b565b610885565b34801561018257600080fd5b506101ae610191366004613246565b600160209081526000928352604080842090915290825290205481565b6040519081526020015b60405180910390f35b3480156101cd57600080fd5b506101ae6101dc366004613440565b6108bc565b3480156101ed57600080fd5b5061013c6101fc366004613549565b610a2f565b34801561020d57600080fd5b5061013c61021c36600461359f565b610bab565b34801561022d57600080fd5b506101ae61023c366004613246565b610d27565b34801561024d57600080fd5b5061013c61025c366004613549565b610d6d565b34801561026d57600080fd5b5061032661027c3660046135cb565b6040805160a081018252600080825260208201819052918101829052606081018290526080810191909152506001600160a01b031660009081526020818152604091829020825160a08101845281546001600160701b038082168352600160701b820460ff16151594830194909452600160781b90049092169282019290925260019091015463ffffffff81166060830152640100000000900465ffffffffffff16608082015290565b6040805182516001600160701b03908116825260208085015115159083015283830151169181019190915260608083015163ffffffff169082015260809182015165ffffffffffff169181019190915260a0016101b8565b34801561038a57600080fd5b506101ae6103993660046135cb565b6001600160a01b03166000908152602081905260409020546001600160701b031690565b3480156103c957600080fd5b5061013c6103d83660046135e8565b6111b0565b3480156103e957600080fd5b506101ae600181565b3480156103fe57600080fd5b5061013c61040d366004613643565b611289565b34801561041e57600080fd5b5061013c61042d3660046136c7565b611386565b34801561043e57600080fd5b506101ae61044d366004613721565b611441565b61013c6104603660046135cb565b61058f565b34801561047157600080fd5b5061013c611483565b34801561048657600080fd5b5061013c6104953660046135cb565b6115ac565b3480156104a657600080fd5b5061013c6104b5366004613755565b6117e4565b3480156104c657600080fd5b5061013c6104d5366004613721565b6118df565b3480156104e657600080fd5b506105496104f53660046135cb565b600060208190529081526040902080546001909101546001600160701b0380831692600160701b810460ff1692600160781b9091049091169063ffffffff811690640100000000900465ffffffffffff1685565b604080516001600160701b0396871681529415156020860152929094169183019190915263ffffffff16606082015265ffffffffffff909116608082015260a0016101b8565b6105998134611abb565b6001600160a01b03811660008181526020818152604091829020805492516001600160701b03909316835292917f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c491015b60405180910390a25050565b33600090815260208190526040902063ffffffff821661065d5760405162461bcd60e51b815260206004820152601a60248201527f6d757374207370656369667920756e7374616b652064656c617900000000000060448201526064015b60405180910390fd5b600181015463ffffffff90811690831610156106bb5760405162461bcd60e51b815260206004820152601c60248201527f63616e6e6f7420646563726561736520756e7374616b652074696d65000000006044820152606401610654565b80546000906106db903490600160781b90046001600160701b03166137cc565b9050600081116107225760405162461bcd60e51b81526020600482015260126024820152711b9bc81cdd185ad9481cdc1958da599a595960721b6044820152606401610654565b6001600160701b0381111561076a5760405162461bcd60e51b815260206004820152600e60248201526d7374616b65206f766572666c6f7760901b6044820152606401610654565b6040805160a08101825283546001600160701b0390811682526001602080840182815286841685870190815263ffffffff808b16606088019081526000608089018181523380835296829052908a902098518954955194518916600160781b02600160781b600160e81b0319951515600160701b026effffffffffffffffffffffffffffff199097169190991617949094179290921695909517865551949092018054925165ffffffffffff166401000000000269ffffffffffffffffffff19909316949093169390931717905590517fa5ae833d0bb1dcd632d98a8b70973e8516812898e19bf27b70071ebc8dc52c0190610878908490879091825263ffffffff16602082015260400190565b60405180910390a2505050565b3360009081526001602090815260408083206001600160c01b038516845290915281208054916108b4836137df565b919050555050565b6000805a90503330146109115760405162461bcd60e51b815260206004820152601760248201527f4141393220696e7465726e616c2063616c6c206f6e6c790000000000000000006044820152606401610654565b8451604081015160608201518101611388015a101561093b5763deaddead60e01b60005260206000fd5b8751600090156109cf576000610958846000015160008c86611b57565b9050806109cd57600061096c610800611b6f565b8051909150156109c75784600001516001600160a01b03168a602001517f1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a2018760200151846040516109be929190613848565b60405180910390a35b60019250505b505b600088608001515a8603019050610a216000838b8b8b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250889250611b9b915050565b9a9950505050505050505050565b610a37611e92565b816000816001600160401b03811115610a5257610a5261327b565b604051908082528060200260200182016040528015610a8b57816020015b610a7861313f565b815260200190600190039081610a705790505b50905060005b82811015610b04576000828281518110610aad57610aad613861565b60200260200101519050600080610ae8848a8a87818110610ad057610ad0613861565b9050602002810190610ae29190613877565b85611ee9565b91509150610af984838360006120d4565b505050600101610a91565b506040516000907fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972908290a160005b83811015610b8e57610b8281888884818110610b5157610b51613861565b9050602002810190610b639190613877565b858481518110610b7557610b75613861565b6020026020010151612270565b90910190600101610b33565b50610b998482612397565b505050610ba66001600255565b505050565b33600090815260208190526040902080546001600160701b0316821115610c145760405162461bcd60e51b815260206004820152601960248201527f576974686472617720616d6f756e7420746f6f206c61726765000000000000006044820152606401610654565b8054610c2a9083906001600160701b0316613898565b81546001600160701b0319166001600160701b0391909116178155604080516001600160a01b03851681526020810184905233917fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb910160405180910390a26000836001600160a01b03168360405160006040518083038185875af1925050503d8060008114610cd6576040519150601f19603f3d011682016040523d82523d6000602084013e610cdb565b606091505b5050905080610d215760405162461bcd60e51b81526020600482015260126024820152716661696c656420746f20776974686472617760701b6044820152606401610654565b50505050565b6001600160a01b03821660009081526001602090815260408083206001600160c01b038516845290915290819020549082901b67ffffffffffffffff1916175b92915050565b610d75611e92565b816000805b82811015610ee95736868683818110610d9557610d95613861565b9050602002810190610da791906138ab565b9050366000610db683806138c1565b90925090506000610dcd60408501602086016135cb565b90506000196001600160a01b03821601610e295760405162461bcd60e51b815260206004820152601760248201527f4141393620696e76616c69642061676772656761746f720000000000000000006044820152606401610654565b6001600160a01b03811615610ec6576001600160a01b03811663e3563a4f8484610e56604089018961390a565b6040518563ffffffff1660e01b8152600401610e759493929190613ab5565b60006040518083038186803b158015610e8d57600080fd5b505afa925050508015610e9e575060015b610ec65760405163086a9f7560e41b81526001600160a01b0382166004820152602401610654565b610ed082876137cc565b9550505050508080610ee1906137df565b915050610d7a565b506000816001600160401b03811115610f0457610f0461327b565b604051908082528060200260200182016040528015610f3d57816020015b610f2a61313f565b815260200190600190039081610f225790505b506040519091507fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f97290600090a16000805b848110156110525736888883818110610f8957610f89613861565b9050602002810190610f9b91906138ab565b9050366000610faa83806138c1565b90925090506000610fc160408501602086016135cb565b90508160005b81811015611039576000898981518110610fe357610fe3613861565b602002602001015190506000806110068b898987818110610ad057610ad0613861565b91509150611016848383896120d4565b8a611020816137df565b9b50505050508080611031906137df565b915050610fc7565b505050505050808061104a906137df565b915050610f6e565b50600080915060005b8581101561116b573689898381811061107657611076613861565b905060200281019061108891906138ab565b905061109a60408201602083016135cb565b6001600160a01b03167f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d60405160405180910390a23660006110dc83806138c1565b90925090508060005b81811015611153576111278885858481811061110357611103613861565b90506020028101906111159190613877565b8b8b81518110610b7557610b75613861565b61113190886137cc565b96508761113d816137df565b985050808061114b906137df565b9150506110e5565b50505050508080611163906137df565b91505061105b565b506040516000907f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d908290a26111a18682612397565b5050505050610ba66001600255565b735ff137d4b0fdcd49dca30c7cf57e578a026d278933146111d057600080fd5b60005a9050600080866001600160a01b03168487876040516111f3929190613b32565b60006040518083038160008787f1925050503d8060008114611231576040519150601f19603f3d011682016040523d82523d6000602084013e611236565b606091505b509150915060005a6112489085613898565b90506000836112575782611268565b604051806020016040528060008152505b9050838183604051636c6238f160e01b815260040161065493929190613b42565b8315801561129f57506001600160a01b0383163b155b156112ec5760405162461bcd60e51b815260206004820152601960248201527f41413230206163636f756e74206e6f74206465706c6f796564000000000000006044820152606401610654565b601481106113645760006113036014828486613b6d565b61130c91613b97565b60601c9050803b6000036113625760405162461bcd60e51b815260206004820152601b60248201527f41413330207061796d6173746572206e6f74206465706c6f79656400000000006044820152606401610654565b505b60405162461bcd60e51b81526020600482015260006024820152604401610654565b604051632b870d1b60e11b81526000906001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063570e1a36906113d79086908690600401613bcc565b6020604051808303816000875af11580156113f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061141a9190613be0565b604051633653dc0360e11b81526001600160a01b0382166004820152909150602401610654565b600061144c82612490565b6040805160208101929092523090820152466060820152608001604051602081830303815290604052805190602001209050919050565b3360009081526020819052604081206001810154909163ffffffff90911690036114dc5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd081cdd185ad95960b21b6044820152606401610654565b8054600160701b900460ff166115285760405162461bcd60e51b8152602060048201526011602482015270616c726561647920756e7374616b696e6760781b6044820152606401610654565b60018101546000906115409063ffffffff1642613bfd565b60018301805469ffffffffffff00000000191664010000000065ffffffffffff841690810291909117909155835460ff60701b1916845560405190815290915033907ffa9b3c14cc825c412c9ed81b3ba365a5b459439403f18829e572ed53a4180f0a906020016105ea565b3360009081526020819052604090208054600160781b90046001600160701b0316806116115760405162461bcd60e51b81526020600482015260146024820152734e6f207374616b6520746f20776974686472617760601b6044820152606401610654565b6001820154640100000000900465ffffffffffff166116725760405162461bcd60e51b815260206004820152601d60248201527f6d7573742063616c6c20756e6c6f636b5374616b6528292066697273740000006044820152606401610654565b60018201544264010000000090910465ffffffffffff1611156116d75760405162461bcd60e51b815260206004820152601b60248201527f5374616b65207769746864726177616c206973206e6f742064756500000000006044820152606401610654565b60018201805469ffffffffffffffffffff191690558154600160781b600160e81b0319168255604080516001600160a01b03851681526020810183905233917fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda3910160405180910390a26000836001600160a01b03168260405160006040518083038185875af1925050503d806000811461178e576040519150601f19603f3d011682016040523d82523d6000602084013e611793565b606091505b5050905080610d215760405162461bcd60e51b815260206004820152601860248201527f6661696c656420746f207769746864726177207374616b6500000000000000006044820152606401610654565b6117ec61313f565b6117f5856124a9565b60008061180460008885611ee9565b9150915060006118148383612583565b905061181f43600052565b600061182d60008a87612270565b905061183843600052565b600060606001600160a01b038a16156118ae57896001600160a01b03168989604051611865929190613b32565b6000604051808303816000865af19150503d80600081146118a2576040519150601f19603f3d011682016040523d82523d6000602084013e6118a7565b606091505b5090925090505b866080015183856020015186604001518585604051630116f59360e71b815260040161065496959493929190613c23565b6118e761313f565b6118f0826124a9565b6000806118ff60008585611ee9565b915091506000611916846000015160a0015161264f565b8451519091506000906119289061264f565b9050611947604051806040016040528060008152602001600081525090565b36600061195760408a018a61390a565b90925090506000601482101561196e576000611989565b61197c601460008486613b6d565b61198591613b97565b60601c5b90506119948161264f565b935050505060006119a58686612583565b9050600081600001519050600060016001600160a01b0316826001600160a01b031614905060006040518060c001604052808b6080015181526020018b6040015181526020018315158152602001856020015165ffffffffffff168152602001856040015165ffffffffffff168152602001611a228c6060015190565b905290506001600160a01b03831615801590611a4857506001600160a01b038316600114155b15611a9a5760006040518060400160405280856001600160a01b03168152602001611a728661264f565b81525090508187878a84604051633ebb2d3960e21b8152600401610654959493929190613cc5565b8086868960405163e0cff05f60e01b81526004016106549493929190613d45565b6001600160a01b03821660009081526020819052604081208054909190611aec9084906001600160701b03166137cc565b90506001600160701b03811115611b385760405162461bcd60e51b815260206004820152601060248201526f6465706f736974206f766572666c6f7760801b6044820152606401610654565b81546001600160701b0319166001600160701b03919091161790555050565b6000806000845160208601878987f195945050505050565b60603d82811115611b7d5750815b604051602082018101604052818152816000602083013e9392505050565b6000805a855190915060009081611bb18261269e565b60a08301519091506001600160a01b038116611bd05782519350611d77565b809350600088511115611d7757868202955060028a6002811115611bf657611bf6613d9c565b14611c6857606083015160405163a9a2340960e01b81526001600160a01b0383169163a9a2340991611c30908e908d908c90600401613db2565b600060405180830381600088803b158015611c4a57600080fd5b5087f1158015611c5e573d6000803e3d6000fd5b5050505050611d77565b606083015160405163a9a2340960e01b81526001600160a01b0383169163a9a2340991611c9d908e908d908c90600401613db2565b600060405180830381600088803b158015611cb757600080fd5b5087f193505050508015611cc9575060015b611d7757611cd5613de9565b806308c379a003611d2e5750611ce9613e05565b80611cf45750611d30565b8b81604051602001611d069190613e8e565b60408051601f1981840301815290829052631101335b60e11b82526106549291600401613848565b505b8a604051631101335b60e11b81526004016106549181526040602082018190526012908201527110504d4c081c1bdcdd13dc081c995d995c9d60721b606082015260800190565b5a85038701965081870295508589604001511015611de0578a604051631101335b60e11b815260040161065491815260406020808301829052908201527f414135312070726566756e642062656c6f772061637475616c476173436f7374606082015260800190565b6040890151869003611df28582611abb565b6000808c6002811115611e0757611e07613d9c565b1490508460a001516001600160a01b031685600001516001600160a01b03168c602001517f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f8860200151858d8f604051611e7a949392919093845291151560208401526040830152606082015260800190565b60405180910390a45050505050505095945050505050565b6002805403611ee35760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610654565b60028055565b60008060005a8451909150611efe86826126ce565b611f0786611441565b6020860152604081015160608201516080830151171760e087013517610100870135176effffffffffffffffffffffffffffff811115611f895760405162461bcd60e51b815260206004820152601860248201527f41413934206761732076616c756573206f766572666c6f7700000000000000006044820152606401610654565b600080611f95846127c7565b9050611fa38a8a8a84612814565b85516020870151919950919350611fba9190612a4c565b6120105789604051631101335b60e11b8152600401610654918152604060208201819052601a908201527f4141323520696e76616c6964206163636f756e74206e6f6e6365000000000000606082015260800190565b61201943600052565b60a08401516060906001600160a01b0316156120415761203c8b8b8b8587612a99565b975090505b60005a87039050808b60a0013510156120a6578b604051631101335b60e11b8152600401610654918152604060208201819052601e908201527f41413430206f76657220766572696669636174696f6e4761734c696d69740000606082015260800190565b60408a018390528160608b015260c08b01355a8803018a608001818152505050505050505050935093915050565b6000806120e085612cbc565b91509150816001600160a01b0316836001600160a01b0316146121465785604051631101335b60e11b81526004016106549181526040602082018190526014908201527320a0991a1039b4b3b730ba3ab9329032b93937b960611b606082015260800190565b801561219e5785604051631101335b60e11b81526004016106549181526040602082018190526017908201527f414132322065787069726564206f72206e6f7420647565000000000000000000606082015260800190565b60006121a985612cbc565b925090506001600160a01b038116156122055786604051631101335b60e11b81526004016106549181526040602082018190526014908201527320a0999a1039b4b3b730ba3ab9329032b93937b960611b606082015260800190565b81156122675786604051631101335b60e11b81526004016106549181526040602082018190526021908201527f41413332207061796d61737465722065787069726564206f72206e6f742064756060820152606560f81b608082015260a00190565b50505050505050565b6000805a90506000612283846060015190565b905030631d732756612298606088018861390a565b87856040518563ffffffff1660e01b81526004016122b99493929190613ecc565b6020604051808303816000875af19250505080156122f4575060408051601f3d908101601f191682019092526122f191810190613f7f565b60015b61238b57600060206000803e50600051632152215360e01b81016123565786604051631101335b60e11b8152600401610654918152604060208201819052600f908201526e41413935206f7574206f662067617360881b606082015260800190565b600085608001515a6123689086613898565b61237291906137cc565b9050612382886002888685611b9b565b9450505061238e565b92505b50509392505050565b6001600160a01b0382166123ed5760405162461bcd60e51b815260206004820152601860248201527f4141393020696e76616c69642062656e656669636961727900000000000000006044820152606401610654565b6000826001600160a01b03168260405160006040518083038185875af1925050503d806000811461243a576040519150601f19603f3d011682016040523d82523d6000602084013e61243f565b606091505b5050905080610ba65760405162461bcd60e51b815260206004820152601f60248201527f41413931206661696c65642073656e6420746f2062656e6566696369617279006044820152606401610654565b600061249b82612d0f565b805190602001209050919050565b3063957122ab6124bc604084018461390a565b6124c960208601866135cb565b6124d761012087018761390a565b6040518663ffffffff1660e01b81526004016124f7959493929190613f98565b60006040518083038186803b15801561250f57600080fd5b505afa925050508015612520575060015b6125805761252c613de9565b806308c379a0036125745750612540613e05565b8061254b5750612576565b80511561257057600081604051631101335b60e11b8152600401610654929190613848565b5050565b505b3d6000803e3d6000fd5b50565b60408051606081018252600080825260208201819052918101829052906125a984612de2565b905060006125b684612de2565b82519091506001600160a01b0381166125cd575080515b602080840151604080860151928501519085015191929165ffffffffffff80831690851610156125fb578193505b8065ffffffffffff168365ffffffffffff161115612617578092505b5050604080516060810182526001600160a01b03909416845265ffffffffffff92831660208501529116908201529250505092915050565b604080518082018252600080825260208083018281526001600160a01b03959095168252819052919091208054600160781b90046001600160701b031682526001015463ffffffff1690915290565b60c081015160e0820151600091908082036126ba575092915050565b6126c682488301612e53565b949350505050565b6126db60208301836135cb565b6001600160a01b0316815260208083013590820152608080830135604083015260a0830135606083015260c0808401359183019190915260e080840135918301919091526101008301359082015236600061273a61012085018561390a565b909250905080156127ba5760148110156127965760405162461bcd60e51b815260206004820152601d60248201527f4141393320696e76616c6964207061796d6173746572416e64446174610000006044820152606401610654565b6127a4601460008385613b6d565b6127ad91613b97565b60601c60a0840152610d21565b600060a084015250505050565b60a081015160009081906001600160a01b03166127e55760016127e8565b60035b60ff16905060008360800151828560600151028560400151010190508360c00151810292505050919050565b60008060005a8551805191925090612839898861283460408c018c61390a565b612e6b565b60a082015161284743600052565b60006001600160a01b03821661288f576001600160a01b0383166000908152602081905260409020546001600160701b03168881116128885780890361288b565b60005b9150505b606084015160208a0151604051633a871cdd60e01b81526001600160a01b03861692633a871cdd9290916128c9918f918790600401613fce565b60206040518083038160008887f193505050508015612905575060408051601f3d908101601f1916820190925261290291810190613f7f565b60015b61298f57612911613de9565b806308c379a0036129425750612925613e05565b806129305750612944565b8b81604051602001611d069190613ff3565b505b8a604051631101335b60e11b8152600401610654918152604060208201819052601690820152754141323320726576657274656420286f72204f4f472960501b606082015260800190565b95506001600160a01b038216612a39576001600160a01b038316600090815260208190526040902080546001600160701b0316808a1115612a1c578c604051631101335b60e11b81526004016106549181526040602082018190526017908201527f41413231206469646e2774207061792070726566756e64000000000000000000606082015260800190565b81546001600160701b031916908a90036001600160701b03161790555b5a85039650505050505094509492505050565b6001600160a01b038216600090815260016020908152604080832084821c80855292528220805484916001600160401b038316919085612a8b836137df565b909155501495945050505050565b82516060818101519091600091848111612af55760405162461bcd60e51b815260206004820152601f60248201527f4141343120746f6f206c6974746c6520766572696669636174696f6e476173006044820152606401610654565b60a08201516001600160a01b038116600090815260208190526040902080548784039291906001600160701b031689811015612b7d578c604051631101335b60e11b8152600401610654918152604060208201819052601e908201527f41413331207061796d6173746572206465706f73697420746f6f206c6f770000606082015260800190565b8981038260000160006101000a8154816001600160701b0302191690836001600160701b03160217905550826001600160a01b031663f465c77e858e8e602001518e6040518563ffffffff1660e01b8152600401612bdd93929190613fce565b60006040518083038160008887f193505050508015612c1e57506040513d6000823e601f3d908101601f19168201604052612c1b919081019061402a565b60015b612ca857612c2a613de9565b806308c379a003612c5b5750612c3e613e05565b80612c495750612c5d565b8d81604051602001611d0691906140b5565b505b8c604051631101335b60e11b8152600401610654918152604060208201819052601690820152754141333320726576657274656420286f72204f4f472960501b606082015260800190565b909e909d509b505050505050505050505050565b60008082600003612cd257506000928392509050565b6000612cdd84612de2565b9050806040015165ffffffffffff16421180612d045750806020015165ffffffffffff1642105b905194909350915050565b6060813560208301356000612d2f612d2a604087018761390a565b61312c565b90506000612d43612d2a606088018861390a565b9050608086013560a087013560c088013560e08901356101008a01356000612d72612d2a6101208e018e61390a565b604080516001600160a01b039c909c1660208d01528b81019a909a5260608b019890985250608089019590955260a088019390935260c087019190915260e08601526101008501526101208401526101408084019190915281518084039091018152610160909201905292915050565b60408051606081018252600080825260208201819052918101919091528160a081901c65ffffffffffff8116600003612e1e575065ffffffffffff5b604080516060810182526001600160a01b03909316835260d09490941c602083015265ffffffffffff16928101929092525090565b6000818310612e625781612e64565b825b9392505050565b8015610d21578251516001600160a01b0381163b15612ed65784604051631101335b60e11b8152600401610654918152604060208201819052601f908201527f414131302073656e64657220616c726561647920636f6e737472756374656400606082015260800190565b835160600151604051632b870d1b60e11b81526000916001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169163570e1a369190612f2e9088908890600401613bcc565b60206040518083038160008887f1158015612f4d573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190612f729190613be0565b90506001600160a01b038116612fd45785604051631101335b60e11b8152600401610654918152604060208201819052601b908201527f4141313320696e6974436f6465206661696c6564206f72204f4f470000000000606082015260800190565b816001600160a01b0316816001600160a01b03161461303e5785604051631101335b60e11b815260040161065491815260406020808301829052908201527f4141313420696e6974436f6465206d7573742072657475726e2073656e646572606082015260800190565b806001600160a01b03163b6000036130a15785604051631101335b60e11b815260040161065491815260406020808301829052908201527f4141313520696e6974436f6465206d757374206372656174652073656e646572606082015260800190565b60006130b06014828688613b6d565b6130b991613b97565b60601c9050826001600160a01b031686602001517fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d83896000015160a0015160405161311b9291906001600160a01b0392831681529116602082015260400190565b60405180910390a350505050505050565b6000604051828085833790209392505050565b6040518060a001604052806131a460405180610100016040528060006001600160a01b031681526020016000815260200160008152602001600081526020016000815260200160006001600160a01b0316815260200160008152602001600081525090565b8152602001600080191681526020016000815260200160008152602001600081525090565b6000602082840312156131db57600080fd5b813563ffffffff81168114612e6457600080fd5b80356001600160c01b038116811461320657600080fd5b919050565b60006020828403121561321d57600080fd5b612e64826131ef565b6001600160a01b038116811461258057600080fd5b803561320681613226565b6000806040838503121561325957600080fd5b823561326481613226565b9150613272602084016131ef565b90509250929050565b634e487b7160e01b600052604160045260246000fd5b60a081018181106001600160401b03821117156132b0576132b061327b565b60405250565b61010081018181106001600160401b03821117156132b0576132b061327b565b601f8201601f191681016001600160401b03811182821017156132fb576132fb61327b565b6040525050565b60006001600160401b0382111561331b5761331b61327b565b50601f01601f191660200190565b600081830361018081121561333d57600080fd5b60405161334981613291565b8092506101008083121561335c57600080fd5b604051925061336a836132b6565b6133738561323b565b8352602085013560208401526040850135604084015260608501356060840152608085013560808401526133a960a0860161323b565b60a084015260c085013560c084015260e085013560e084015282825280850135602083015250610120840135604082015261014084013560608201526101608401356080820152505092915050565b60008083601f84011261340a57600080fd5b5081356001600160401b0381111561342157600080fd5b60208301915083602082850101111561343957600080fd5b9250929050565b6000806000806101c0858703121561345757600080fd5b84356001600160401b038082111561346e57600080fd5b818701915087601f83011261348257600080fd5b813561348d81613302565b60405161349a82826132d6565b8281528a60208487010111156134af57600080fd5b826020860160208301376000602084830101528098505050506134d58860208901613329565b94506101a08701359150808211156134ec57600080fd5b506134f9878288016133f8565b95989497509550505050565b60008083601f84011261351757600080fd5b5081356001600160401b0381111561352e57600080fd5b6020830191508360208260051b850101111561343957600080fd5b60008060006040848603121561355e57600080fd5b83356001600160401b0381111561357457600080fd5b61358086828701613505565b909450925050602084013561359481613226565b809150509250925092565b600080604083850312156135b257600080fd5b82356135bd81613226565b946020939093013593505050565b6000602082840312156135dd57600080fd5b8135612e6481613226565b600080600080606085870312156135fe57600080fd5b843561360981613226565b935060208501356001600160401b0381111561362457600080fd5b613630878288016133f8565b9598909750949560400135949350505050565b60008060008060006060868803121561365b57600080fd5b85356001600160401b038082111561367257600080fd5b61367e89838a016133f8565b90975095506020880135915061369382613226565b909350604087013590808211156136a957600080fd5b506136b6888289016133f8565b969995985093965092949392505050565b600080602083850312156136da57600080fd5b82356001600160401b038111156136f057600080fd5b6136fc858286016133f8565b90969095509350505050565b6000610160828403121561371b57600080fd5b50919050565b60006020828403121561373357600080fd5b81356001600160401b0381111561374957600080fd5b6126c684828501613708565b6000806000806060858703121561376b57600080fd5b84356001600160401b038082111561378257600080fd5b61378e88838901613708565b9550602087013591506137a082613226565b909350604086013590808211156134ec57600080fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115610d6757610d676137b6565b6000600182016137f1576137f16137b6565b5060010190565b60005b838110156138135781810151838201526020016137fb565b50506000910152565b600081518084526138348160208601602086016137f8565b601f01601f19169290920160200192915050565b8281526040602082015260006126c6604083018461381c565b634e487b7160e01b600052603260045260246000fd5b6000823561015e1983360301811261388e57600080fd5b9190910192915050565b81810381811115610d6757610d676137b6565b60008235605e1983360301811261388e57600080fd5b6000808335601e198436030181126138d857600080fd5b8301803591506001600160401b038211156138f257600080fd5b6020019150600581901b360382131561343957600080fd5b6000808335601e1984360301811261392157600080fd5b8301803591506001600160401b0382111561393b57600080fd5b60200191503681900382131561343957600080fd5b6000808335601e1984360301811261396757600080fd5b83016020810192503590506001600160401b0381111561398657600080fd5b80360382131561343957600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60006101606139dd846139d08561323b565b6001600160a01b03169052565b602083013560208501526139f46040840184613950565b826040870152613a078387018284613995565b92505050613a186060840184613950565b8583036060870152613a2b838284613995565b925050506080830135608085015260a083013560a085015260c083013560c085015260e083013560e0850152610100808401358186015250610120613a7281850185613950565b86840383880152613a84848284613995565b9350505050610140613a9881850185613950565b86840383880152613aaa848284613995565b979650505050505050565b6040808252810184905260006060600586901b830181019083018783805b89811015613b1b57868503605f190184528235368c900361015e19018112613af9578283fd5b613b05868d83016139be565b9550506020938401939290920191600101613ad3565b505050508281036020840152613aaa818587613995565b8183823760009101908152919050565b8315158152606060208201526000613b5d606083018561381c565b9050826040830152949350505050565b60008085851115613b7d57600080fd5b83861115613b8a57600080fd5b5050820193919092039150565b6bffffffffffffffffffffffff198135818116916014851015613bc45780818660140360031b1b83161692505b505092915050565b6020815260006126c6602083018486613995565b600060208284031215613bf257600080fd5b8151612e6481613226565b65ffffffffffff818116838216019080821115613c1c57613c1c6137b6565b5092915050565b868152856020820152600065ffffffffffff8087166040840152808616606084015250831515608083015260c060a0830152613c6260c083018461381c565b98975050505050505050565b80518252602081015160208301526040810151151560408301526000606082015165ffffffffffff8082166060860152806080850151166080860152505060a082015160c060a08501526126c660c085018261381c565b6000610140808352613cd981840189613c6e565b915050613cf3602083018780518252602090810151910152565b845160608301526020948501516080830152835160a08301529284015160c082015281516001600160a01b031660e0820152908301518051610100830152909201516101209092019190915292915050565b60e081526000613d5860e0830187613c6e565b9050613d71602083018680518252602090810151910152565b8351606083015260208401516080830152825160a0830152602083015160c083015295945050505050565b634e487b7160e01b600052602160045260246000fd5b600060038510613dd257634e487b7160e01b600052602160045260246000fd5b84825260606020830152613b5d606083018561381c565b600060033d1115613e025760046000803e5060005160e01c5b90565b600060443d1015613e135790565b6040516003193d81016004833e81513d6001600160401b038160248401118184111715613e4257505050505090565b8285019150815181811115613e5a5750505050505090565b843d8701016020828501011115613e745750505050505090565b613e83602082860101876132d6565b509095945050505050565b75020a09a98103837b9ba27b8103932bb32b93a32b21d160551b815260008251613ebf8160168501602087016137f8565b9190910160160192915050565b60006101c0808352613ee18184018789613995565b9050845160018060a01b03808251166020860152602082015160408601526040820151606086015260608201516080860152608082015160a08601528060a08301511660c08601525060c081015160e085015260e08101516101008501525060208501516101208401526040850151610140840152606085015161016084015260808501516101808401528281036101a0840152613aaa818561381c565b600060208284031215613f9157600080fd5b5051919050565b606081526000613fac606083018789613995565b6001600160a01b03861660208401528281036040840152613c62818587613995565b606081526000613fe160608301866139be565b60208301949094525060400152919050565b6e020a09919903932bb32b93a32b21d1608d1b81526000825161401d81600f8501602087016137f8565b91909101600f0192915050565b6000806040838503121561403d57600080fd5b82516001600160401b0381111561405357600080fd5b8301601f8101851361406457600080fd5b805161406f81613302565b60405161407c82826132d6565b82815287602084860101111561409157600080fd5b6140a28360208301602087016137f8565b6020969096015195979596505050505050565b6e020a09999903932bb32b93a32b21d1608d1b81526000825161401d81600f8501602087016137f856fea26469706673582212201892e38d1eac5b99b119bf1333f8e39f72ad5274c5da6bb916f97bef4e7e0afc64736f6c63430008140033" @@ -46,35 +47,10 @@ export class GasEstimationHandler { gasEstimatorV06: GasEstimatorV06 gasEstimatorV07: GasEstimatorV07 - constructor( - binarySearchToleranceDelta: bigint, - binarySearchGasAllowance: bigint, - publicClient: PublicClient, - chainId: number, - blockTagSupport: boolean, - utilityWalletAddress: Address, - chainType: ChainType, - entryPointSimulationsAddress?: Address, - fixedGasLimitForEstimation?: bigint - ) { - this.gasEstimatorV06 = new GasEstimatorV06( - publicClient, - blockTagSupport, - utilityWalletAddress, - fixedGasLimitForEstimation - ) + constructor(config: AltoConfig) { + this.gasEstimatorV06 = new GasEstimatorV06(config) - this.gasEstimatorV07 = new GasEstimatorV07( - binarySearchToleranceDelta, - binarySearchGasAllowance, - chainId, - publicClient, - entryPointSimulationsAddress, - blockTagSupport, - utilityWalletAddress, - chainType, - fixedGasLimitForEstimation - ) + this.gasEstimatorV07 = new GasEstimatorV07(config) } simulateHandleOp({ diff --git a/src/rpc/estimation/gasEstimationsV06.ts b/src/rpc/estimation/gasEstimationsV06.ts index e4323005..6b25ae7a 100644 --- a/src/rpc/estimation/gasEstimationsV06.ts +++ b/src/rpc/estimation/gasEstimationsV06.ts @@ -10,30 +10,19 @@ import type { StateOverrides, UserOperationV06 } from "@alto/types" import type { Hex, RpcRequestErrorType } from "viem" import { type Address, - type PublicClient, decodeErrorResult, encodeFunctionData, toHex } from "viem" import { z } from "zod" import type { SimulateHandleOpResult } from "./types" +import type { AltoConfig } from "../../createConfig" export class GasEstimatorV06 { - publicClient: PublicClient - blockTagSupport: boolean - utilityWalletAddress: Address - fixedGasLimitForEstimation?: bigint + private config: AltoConfig - constructor( - publicClient: PublicClient, - blockTagSupport: boolean, - utilityWalletAddress: Address, - fixedGasLimitForEstimation?: bigint - ) { - this.publicClient = publicClient - this.blockTagSupport = blockTagSupport - this.utilityWalletAddress = utilityWalletAddress - this.fixedGasLimitForEstimation = fixedGasLimitForEstimation + constructor(config: AltoConfig) { + this.config = config } async simulateHandleOpV06({ @@ -49,12 +38,13 @@ export class GasEstimatorV06 { entryPoint: Address stateOverrides?: StateOverrides | undefined }): Promise { - const { - publicClient, - blockTagSupport, - utilityWalletAddress, - fixedGasLimitForEstimation - } = this + const publicClient = this.config.publicClient + const blockTagSupport = this.config.blockTagSupport + const utilityWalletAddress = + this.config.utilityPrivateKey?.address ?? + "0x4337000c2828F5260d8921fD25829F606b9E8680" + const fixedGasLimitForEstimation = + this.config.fixedGasLimitForEstimation try { await publicClient.request({ diff --git a/src/rpc/estimation/gasEstimationsV07.ts b/src/rpc/estimation/gasEstimationsV07.ts index ac70a7ec..bf07fab8 100644 --- a/src/rpc/estimation/gasEstimationsV07.ts +++ b/src/rpc/estimation/gasEstimationsV07.ts @@ -1,5 +1,4 @@ import { - type ChainType, EntryPointV07Abi, EntryPointV07SimulationsAbi, ExecutionErrors, @@ -17,7 +16,6 @@ import { getUserOperationHash, toPackedUserOperation } from "@alto/utils" import type { Hex } from "viem" import { type Address, - type PublicClient, decodeAbiParameters, decodeErrorResult, decodeFunctionResult, @@ -31,38 +29,13 @@ import { type SimulateHandleOpResult, simulationValidationResultStruct } from "./types" +import type { AltoConfig } from "../../createConfig" export class GasEstimatorV07 { - binarySearchToleranceDelta: bigint - binarySearchGasAllowance: bigint - chainId: number - publicClient: PublicClient - entryPointSimulationsAddress: Address | undefined - blockTagSupport: boolean - utilityWalletAddress: Address - fixedGasLimitForEstimation?: bigint - chainType: ChainType - - constructor( - binarySearchToleranceDelta: bigint, - binarySearchGasAllowance: bigint, - chainId: number, - publicClient: PublicClient, - entryPointSimulationsAddress: Address | undefined, - blockTagSupport: boolean, - utilityWalletAddress: Address, - chainType: ChainType, - fixedGasLimitForEstimation?: bigint - ) { - this.binarySearchToleranceDelta = binarySearchToleranceDelta - this.binarySearchGasAllowance = binarySearchGasAllowance - this.chainId = chainId - this.publicClient = publicClient - this.entryPointSimulationsAddress = entryPointSimulationsAddress - this.blockTagSupport = blockTagSupport - this.utilityWalletAddress = utilityWalletAddress - this.chainType = chainType - this.fixedGasLimitForEstimation = fixedGasLimitForEstimation + private config: AltoConfig + + constructor(config: AltoConfig) { + this.config = config } async simulateValidation({ @@ -115,7 +88,11 @@ export class GasEstimatorV07 { functionName: "executeUserOp", args: [ packedOp, - getUserOperationHash(op, entryPoint, this.chainId) + getUserOperationHash( + op, + entryPoint, + this.config.publicClient.chain.id + ) ] }) } @@ -139,7 +116,7 @@ export class GasEstimatorV07 { userOperationHash: getUserOperationHash( uop, entryPoint, - this.chainId + this.config.publicClient.chain.id ) })) @@ -156,7 +133,7 @@ export class GasEstimatorV07 { userOperation, queuedUserOperations, entryPoint, - gasAllowance = this.binarySearchGasAllowance, + gasAllowance = this.config.binarySearchGasAllowance, initialMinGas = 0n }: { userOperation: UserOperationV07 @@ -191,7 +168,7 @@ export class GasEstimatorV07 { targetOp, entryPoint, initialMinGas, - this.binarySearchToleranceDelta, + this.config.binarySearchToleranceDelta, gasAllowance ] }) @@ -308,7 +285,7 @@ export class GasEstimatorV07 { let cause - if (this.chainType === "hedera") { + if (this.config.chainType === "hedera") { // due to Hedera specific restrictions, we can't combine these two calls. const [simulateHandleOpLastCause, simulateCallDataCause] = await Promise.all([ @@ -409,13 +386,15 @@ export class GasEstimatorV07 { entryPointSimulationsCallData: Hex[] stateOverrides?: StateOverrides }) { - const { - publicClient, - blockTagSupport, - utilityWalletAddress, - entryPointSimulationsAddress, - fixedGasLimitForEstimation - } = this + const publicClient = this.config.publicClient + const blockTagSupport = this.config.blockTagSupport + const utilityWalletAddress = + this.config.utilityPrivateKey?.address ?? + "0x4337000c2828F5260d8921fD25829F606b9E8680" + const entryPointSimulationsAddress = + this.config.entrypointSimulationContract + const fixedGasLimitForEstimation = + this.config.fixedGasLimitForEstimation if (!entryPointSimulationsAddress) { throw new RpcError( diff --git a/src/rpc/nonceQueuer.ts b/src/rpc/nonceQueuer.ts index 6490f11e..527d5bfb 100644 --- a/src/rpc/nonceQueuer.ts +++ b/src/rpc/nonceQueuer.ts @@ -14,13 +14,12 @@ import { } from "@alto/utils" import { type Address, - type Chain, type Hash, type MulticallReturnType, type PublicClient, - type Transport, getContract } from "viem" +import type { AltoConfig } from "../createConfig" type QueuedUserOperation = { entryPoint: Address @@ -34,23 +33,28 @@ type QueuedUserOperation = { export class NonceQueuer { queuedUserOperations: QueuedUserOperation[] = [] + config: AltoConfig mempool: MemoryMempool - publicClient: PublicClient logger: Logger - blockTagSupport: boolean eventManager: EventManager - constructor( - mempool: MemoryMempool, - publicClient: PublicClient, - logger: Logger, - blockTagSupport: boolean, + constructor({ + config, + mempool, + eventManager + }: { + config: AltoConfig + mempool: MemoryMempool eventManager: EventManager - ) { + }) { + this.config = config this.mempool = mempool - this.publicClient = publicClient - this.logger = logger - this.blockTagSupport = blockTagSupport + this.logger = config.getLogger( + { module: "nonce_queuer" }, + { + level: config.nonceQueuerLogLevel || config.logLevel + } + ) this.eventManager = eventManager setInterval(() => { @@ -69,7 +73,7 @@ export class NonceQueuer { } const availableOps = await this.getAvailableUserOperations( - this.publicClient + this.config.publicClient ) if (availableOps.length === 0) { @@ -99,7 +103,7 @@ export class NonceQueuer { const hash = getUserOperationHash( deriveUserOperation(mempoolUserOperation), entryPoint, - this.publicClient.chain.id + this.config.publicClient.chain.id ) this.queuedUserOperations.push({ entryPoint, @@ -154,7 +158,7 @@ export class NonceQueuer { args: [userOperation.sender, qop.nonceKey] } }), - blockTag: this.blockTagSupport ? "latest" : undefined + blockTag: this.config.blockTagSupport ? "latest" : undefined }) } catch (error) { this.logger.error( diff --git a/src/rpc/rpcHandler.ts b/src/rpc/rpcHandler.ts index 0d1b7b5d..7d3f9277 100644 --- a/src/rpc/rpcHandler.ts +++ b/src/rpc/rpcHandler.ts @@ -11,8 +11,6 @@ import type { } from "@alto/mempool" import type { ApiVersion, - ChainType, - GasPriceMultipliers, PackedUserOperation, StateOverrides, TransactionInfo, @@ -70,12 +68,9 @@ import { toUnpackedUserOperation } from "@alto/utils" import { - type Chain, type Hex, - type PublicClient, type Transaction, TransactionNotFoundError, - type Transport, decodeFunctionData, getAbiItem, getAddress, @@ -85,6 +80,7 @@ import { } from "viem" import { base, baseSepolia, optimism } from "viem/chains" import type { NonceQueuer } from "./nonceQueuer" +import type { AltoConfig } from "../createConfig" export interface IRpcEndpoint { handleMethod( @@ -113,80 +109,65 @@ export interface IRpcEndpoint { } export class RpcHandler implements IRpcEndpoint { - entryPoints: Address[] - publicClient: PublicClient + config: AltoConfig validator: InterfaceValidator mempool: MemoryMempool executor: Executor monitor: Monitor nonceQueuer: NonceQueuer - usingTenderly: boolean rpcMaxBlockRange: number | undefined logger: Logger metrics: Metrics - chainId: number - chainType: ChainType - enableDebugEndpoints: boolean executorManager: ExecutorManager reputationManager: InterfaceReputationManager compressionHandler: CompressionHandler | null - legacyTransactions: boolean - dangerousSkipUserOperationValidation: boolean gasPriceManager: GasPriceManager - gasPriceMultipliers: GasPriceMultipliers - paymasterGasLimitMultiplier: bigint eventManager: EventManager - enableInstantBundlingEndpoint: boolean - - constructor( - entryPoints: Address[], - publicClient: PublicClient, - validator: InterfaceValidator, - mempool: MemoryMempool, - executor: Executor, - monitor: Monitor, - nonceQueuer: NonceQueuer, - executorManager: ExecutorManager, - reputationManager: InterfaceReputationManager, - usingTenderly: boolean, - rpcMaxBlockRange: number | undefined, - logger: Logger, - metrics: Metrics, - enableDebugEndpoints: boolean, - compressionHandler: CompressionHandler | null, - legacyTransactions: boolean, - gasPriceManager: GasPriceManager, - gasPriceMultipliers: GasPriceMultipliers, - chainType: ChainType, - paymasterGasLimitMultiplier: bigint, - eventManager: EventManager, - enableInstantBundlingEndpoint: boolean, - dangerousSkipUserOperationValidation = false - ) { - this.entryPoints = entryPoints - this.publicClient = publicClient + + constructor({ + config, + validator, + mempool, + executor, + monitor, + nonceQueuer, + executorManager, + reputationManager, + metrics, + compressionHandler, + gasPriceManager, + eventManager + }: { + config: AltoConfig + validator: InterfaceValidator + mempool: MemoryMempool + executor: Executor + monitor: Monitor + nonceQueuer: NonceQueuer + executorManager: ExecutorManager + reputationManager: InterfaceReputationManager + metrics: Metrics + compressionHandler: CompressionHandler | null + eventManager: EventManager + gasPriceManager: GasPriceManager + }) { + this.config = config this.validator = validator this.mempool = mempool this.executor = executor this.monitor = monitor this.nonceQueuer = nonceQueuer - this.usingTenderly = usingTenderly - this.rpcMaxBlockRange = rpcMaxBlockRange - this.logger = logger + this.logger = config.getLogger( + { module: "rpc" }, + { + level: config.rpcLogLevel || config.logLevel + } + ) this.metrics = metrics - this.enableDebugEndpoints = enableDebugEndpoints - this.chainId = publicClient.chain.id this.executorManager = executorManager this.reputationManager = reputationManager this.compressionHandler = compressionHandler - this.legacyTransactions = legacyTransactions - this.dangerousSkipUserOperationValidation = - dangerousSkipUserOperationValidation - this.gasPriceMultipliers = gasPriceMultipliers - this.chainType = chainType this.gasPriceManager = gasPriceManager - this.paymasterGasLimitMultiplier = paymasterGasLimitMultiplier - this.enableInstantBundlingEndpoint = enableInstantBundlingEndpoint this.eventManager = eventManager } @@ -321,9 +302,9 @@ export class RpcHandler implements IRpcEndpoint { } ensureEntryPointIsSupported(entryPoint: Address) { - if (!this.entryPoints.includes(entryPoint)) { + if (!this.config.entrypoints.includes(entryPoint)) { throw new Error( - `EntryPoint ${entryPoint} not supported, supported EntryPoints: ${this.entryPoints.join( + `EntryPoint ${entryPoint} not supported, supported EntryPoints: ${this.config.entrypoints.join( ", " )}` ) @@ -331,7 +312,7 @@ export class RpcHandler implements IRpcEndpoint { } ensureDebugEndpointsAreEnabled(methodName: string) { - if (!this.enableDebugEndpoints) { + if (!this.config.enableDebugEndpoints) { throw new RpcError( `${methodName} is only available in development environment` ) @@ -345,7 +326,7 @@ export class RpcHandler implements IRpcEndpoint { entryPoint: Address ) { if ( - this.legacyTransactions && + this.config.legacyTransactions && userOperation.maxFeePerGas !== userOperation.maxPriorityFeePerGas ) { const reason = @@ -380,11 +361,11 @@ export class RpcHandler implements IRpcEndpoint { } eth_chainId(): ChainIdResponseResult { - return BigInt(this.chainId) + return BigInt(this.config.publicClient.chain.id) } eth_supportedEntryPoints(): SupportedEntryPointsResponseResult { - return this.entryPoints + return this.config.entrypoints } async eth_estimateUserOperationGas( @@ -401,15 +382,13 @@ export class RpcHandler implements IRpcEndpoint { ) } - let preVerificationGas = await calcPreVerificationGas( - this.publicClient, + let preVerificationGas = await calcPreVerificationGas({ + config: this.config, userOperation, entryPoint, - this.chainId, - this.chainType, - this.gasPriceManager, - false - ) + gasPriceManager: this.gasPriceManager, + validate: false + }) preVerificationGas = scaleBigIntByPercent(preVerificationGas, 110) // biome-ignore lint/style/noParameterAssign: prepare userOperaiton for simulation @@ -420,11 +399,11 @@ export class RpcHandler implements IRpcEndpoint { callGasLimit: 10_000_000n } - if (this.chainId === base.id) { + if (this.config.publicClient.chain.id === base.id) { userOperation.verificationGasLimit = 5_000_000n } - if (this.chainType === "hedera") { + if (this.config.chainType === "hedera") { // The eth_call gasLimit is set to 12_500_000 on Hedera. userOperation.verificationGasLimit = 5_000_000n userOperation.callGasLimit = 4_500_000n @@ -497,7 +476,7 @@ export class RpcHandler implements IRpcEndpoint { calcVerificationGasAndCallGasLimit( userOperation, executionResult.data.executionResult, - this.chainId, + this.config.publicClient.chain.id, executionResult.data.callDataResult ) @@ -518,7 +497,7 @@ export class RpcHandler implements IRpcEndpoint { executionResult.data.executionResult.paymasterPostOpGasLimit || 1n - const multiplier = Number(this.paymasterGasLimitMultiplier) + const multiplier = Number(this.config.paymasterGasLimitMultiplier) paymasterVerificationGasLimit = scaleBigIntByPercent( paymasterVerificationGasLimit, @@ -531,11 +510,17 @@ export class RpcHandler implements IRpcEndpoint { ) } - if (this.chainId === base.id || this.chainId === baseSepolia.id) { + if ( + this.config.publicClient.chain.id === base.id || + this.config.publicClient.chain.id === baseSepolia.id + ) { callGasLimit += 10_000n } - if (this.chainId === base.id || this.chainId === optimism.id) { + if ( + this.config.publicClient.chain.id === base.id || + this.config.publicClient.chain.id === optimism.id + ) { callGasLimit = maxBigInt(callGasLimit, 120_000n) } @@ -596,7 +581,7 @@ export class RpcHandler implements IRpcEndpoint { const hash = getUserOperationHash( userOperation, entryPoint, - this.chainId + this.config.publicClient.chain.id ) this.eventManager.emitReceived(hash) @@ -633,7 +618,7 @@ export class RpcHandler implements IRpcEndpoint { let fromBlock: bigint | undefined let toBlock: "latest" | undefined if (this.rpcMaxBlockRange !== undefined) { - const latestBlock = await this.publicClient.getBlockNumber() + const latestBlock = await this.config.publicClient.getBlockNumber() fromBlock = latestBlock - BigInt(this.rpcMaxBlockRange) if (fromBlock < 0n) { fromBlock = 0n @@ -641,8 +626,8 @@ export class RpcHandler implements IRpcEndpoint { toBlock = "latest" } - const filterResult = await this.publicClient.getLogs({ - address: this.entryPoints, + const filterResult = await this.config.publicClient.getLogs({ + address: this.config.entrypoints, event: userOperationEventAbiItem, fromBlock, toBlock, @@ -666,7 +651,9 @@ export class RpcHandler implements IRpcEndpoint { txHash: HexData32 ): Promise => { try { - return await this.publicClient.getTransaction({ hash: txHash }) + return await this.config.publicClient.getTransaction({ + hash: txHash + }) } catch (e) { if (e instanceof TransactionNotFoundError) { return getTransaction(txHash) @@ -825,12 +812,12 @@ export class RpcHandler implements IRpcEndpoint { let { maxFeePerGas, maxPriorityFeePerGas } = await this.gasPriceManager.getGasPrice() - if (this.chainType === "hedera") { + if (this.config.chainType === "hedera") { maxFeePerGas /= 10n ** 9n maxPriorityFeePerGas /= 10n ** 9n } - const { slow, standard, fast } = this.gasPriceMultipliers + const { slow, standard, fast } = this.config.gasPriceMultipliers return { slow: { @@ -860,7 +847,7 @@ export class RpcHandler implements IRpcEndpoint { const opHash = getUserOperationHash( userOperation, entryPoint, - this.chainId + this.config.publicClient.chain.id ) await this.preMempoolChecks( @@ -907,7 +894,7 @@ export class RpcHandler implements IRpcEndpoint { userOperationNonceValue === currentNonceValue + BigInt(queuedUserOperations.length) ) { - if (this.dangerousSkipUserOperationValidation) { + if (this.config.dangerousSkipUserOperationValidation) { const [success, errorReason] = this.mempool.add(op, entryPoint) if (!success) { this.eventManager.emitFailedValidation( @@ -976,7 +963,7 @@ export class RpcHandler implements IRpcEndpoint { userOperation: UserOperation, entryPoint: Address ) { - if (!this.enableInstantBundlingEndpoint) { + if (!this.config.enableInstantBundlingEndpoint) { throw new RpcError( "pimlico_sendUserOperationNow endpoint is not enabled", ValidationErrors.InvalidFields @@ -988,7 +975,7 @@ export class RpcHandler implements IRpcEndpoint { const opHash = getUserOperationHash( userOperation, entryPoint, - this.chainId + this.config.publicClient.chain.id ) await this.preMempoolChecks( @@ -1040,10 +1027,11 @@ export class RpcHandler implements IRpcEndpoint { this.executor.markWalletProcessed(res.value.transactionInfo.executor) // wait for receipt - const receipt = await this.publicClient.waitForTransactionReceipt({ - hash: res.value.transactionInfo.transactionHash, - pollingInterval: 100 - }) + const receipt = + await this.config.publicClient.waitForTransactionReceipt({ + hash: res.value.transactionInfo.transactionHash, + pollingInterval: 100 + }) const userOperationReceipt = parseUserOperationReceipt(opHash, receipt) @@ -1068,7 +1056,7 @@ export class RpcHandler implements IRpcEndpoint { const hash = getUserOperationHash( inflatedOp, entryPoint, - this.chainId + this.config.publicClient.chain.id ) this.eventManager.emitReceived(hash, receivedTimestamp) @@ -1113,7 +1101,7 @@ export class RpcHandler implements IRpcEndpoint { const inflatorId = await this.compressionHandler.getInflatorRegisteredId( inflatorAddress, - this.publicClient + this.config.publicClient ) if (inflatorId === 0) { @@ -1128,7 +1116,7 @@ export class RpcHandler implements IRpcEndpoint { address: inflatorAddress, abi: IOpInflatorAbi, client: { - public: this.publicClient + public: this.config.publicClient } }) @@ -1163,7 +1151,7 @@ export class RpcHandler implements IRpcEndpoint { ? EntryPointV06Abi : EntryPointV07Abi, client: { - public: this.publicClient + public: this.config.publicClient } }) diff --git a/src/rpc/server.ts b/src/rpc/server.ts index bf329c64..9cc79383 100644 --- a/src/rpc/server.ts +++ b/src/rpc/server.ts @@ -7,7 +7,7 @@ import { bundlerRequestSchema, jsonRpcSchema } from "@alto/types" -import type { Logger, Metrics } from "@alto/utils" +import type { Metrics } from "@alto/utils" import cors from "@fastify/cors" import websocket from "@fastify/websocket" import * as sentry from "@sentry/node" @@ -23,6 +23,7 @@ import type * as WebSocket from "ws" import { fromZodError } from "zod-validation-error" import RpcReply from "../utils/rpc-reply" import type { IRpcEndpoint } from "./rpcHandler" +import type { AltoConfig } from "../createConfig" // jsonBigIntOverride.ts const originalJsonStringify = JSON.stringify @@ -69,37 +70,40 @@ declare module "fastify" { } export class Server { + private config: AltoConfig private fastify: FastifyInstance private rpcEndpoint: IRpcEndpoint - private port: number private registry: Registry private metrics: Metrics - private apiVersions: ApiVersion[] - private defaultApiVersion: ApiVersion - private supportedRpcMethods: string[] | null - - constructor( - rpcEndpoint: IRpcEndpoint, - apiVersions: ApiVersion[], - defaultApiVersion: ApiVersion, - port: number, - requestTimeout: number | undefined, - websocketMaxPayloadSize: number, - websocketEnabled: boolean, - logger: Logger, - registry: Registry, - metrics: Metrics, - supportedRpcMethods: string[] | null - ) { + + constructor({ + config, + rpcEndpoint, + registry, + metrics + }: { + config: AltoConfig + rpcEndpoint: IRpcEndpoint + registry: Registry + metrics: Metrics + }) { + this.config = config + const logger = config.getLogger( + { module: "rpc" }, + { + level: config.rpcLogLevel || config.logLevel + } + ) + this.fastify = Fastify({ logger: logger as FastifyBaseLogger, // workaround for https://github.com/fastify/fastify/issues/4960 - requestTimeout: requestTimeout, + requestTimeout: config.timeout, disableRequestLogging: true }) this.fastify.register(websocket, { options: { - maxPayload: websocketMaxPayloadSize + maxPayload: config.websocketMaxPayloadSize } }) @@ -138,9 +142,8 @@ export class Server { this.fastify.post("/:version/rpc", this.rpcHttp.bind(this)) this.fastify.post("/", this.rpcHttp.bind(this)) - if (websocketEnabled) { - // biome-ignore lint/suspicious/useAwait: adhere to interface - this.fastify.register(async (fastify) => { + if (config.websocket) { + this.fastify.register((fastify) => { fastify.route({ method: "GET", url: "/:version/rpc", @@ -153,8 +156,7 @@ export class Server { `GET request to /${version}/rpc is not supported, use POST isntead` ) }, - // biome-ignore lint/suspicious/useAwait: adhere to interface - wsHandler: async (socket: WebSocket.WebSocket, request) => { + wsHandler: (socket: WebSocket.WebSocket, request) => { socket.on("message", async (msgBuffer: Buffer) => this.rpcSocket(request, msgBuffer, socket) ) @@ -167,16 +169,12 @@ export class Server { this.fastify.get("/metrics", this.serveMetrics.bind(this)) this.rpcEndpoint = rpcEndpoint - this.port = port this.registry = registry this.metrics = metrics - this.apiVersions = apiVersions - this.defaultApiVersion = defaultApiVersion - this.supportedRpcMethods = supportedRpcMethods } public start(): void { - this.fastify.listen({ port: this.port, host: "0.0.0.0" }) + this.fastify.listen({ port: this.config.port, host: "0.0.0.0" }) } public async stop(): Promise { @@ -227,7 +225,7 @@ export class Server { let requestId: number | null = null const versionParsingResult = altoVersions.safeParse( - (request.params as any)?.version ?? this.defaultApiVersion + (request.params as any)?.version ?? this.config.defaultApiVersion ) if (!versionParsingResult.success) { @@ -240,7 +238,7 @@ export class Server { const apiVersion: ApiVersion = versionParsingResult.data - if (this.apiVersions.indexOf(apiVersion) === -1) { + if (this.config.apiVersion.indexOf(apiVersion) === -1) { throw new RpcError( `unsupported version ${apiVersion}`, ValidationErrors.InvalidFields @@ -307,8 +305,8 @@ export class Server { request.rpcMethod = bundlerRequest.method if ( - this.supportedRpcMethods !== null && - !this.supportedRpcMethods.includes(bundlerRequest.method) + this.config.rpcMethods !== null && + !this.config.rpcMethods.includes(bundlerRequest.method) ) { throw new RpcError( `Method not supported: ${bundlerRequest.method}`, diff --git a/src/rpc/validation/SafeValidator.ts b/src/rpc/validation/SafeValidator.ts index d64c7b68..ce1a2283 100644 --- a/src/rpc/validation/SafeValidator.ts +++ b/src/rpc/validation/SafeValidator.ts @@ -1,7 +1,6 @@ import type { SenderManager } from "@alto/executor" import type { GasPriceManager } from "@alto/handlers" import type { - ChainType, InterfaceValidator, UserOperationV06, UserOperationV07, @@ -26,7 +25,7 @@ import { ValidationErrors, type ValidationResultWithAggregation } from "@alto/types" -import type { Logger, Metrics } from "@alto/utils" +import type { Metrics } from "@alto/utils" import { calcVerificationGasAndCallGasLimit, getAddressFromInitCodeOrPaymasterAndData, @@ -35,11 +34,8 @@ import { toPackedUserOperation } from "@alto/utils" import { - type Chain, type ExecutionRevertedError, type Hex, - type PublicClient, - type Transport, decodeErrorResult, encodeDeployData, encodeFunctionData, @@ -55,6 +51,7 @@ import { tracerResultParserV06 } from "./TracerResultParserV06" import { tracerResultParserV07 } from "./TracerResultParserV07" import { UnsafeValidator } from "./UnsafeValidator" import { debug_traceCall } from "./tracer" +import type { AltoConfig } from "../../createConfig" export class SafeValidator extends UnsafeValidator @@ -62,37 +59,22 @@ export class SafeValidator { private senderManager: SenderManager - constructor( - publicClient: PublicClient, - senderManager: SenderManager, - logger: Logger, - metrics: Metrics, - gasPriceManager: GasPriceManager, - chainType: ChainType, - blockTagSupport: boolean, - utilityWalletAddress: Address, - binarySearchToleranceDelta: bigint, - binarySearchGasAllowance: bigint, - entryPointSimulationsAddress?: Address, - fixedGasLimitForEstimation?: bigint, - usingTenderly = false, - balanceOverrideEnabled = false - ) { - super( - publicClient, - logger, + constructor({ + config, + senderManager, + metrics, + gasPriceManager + }: { + config: AltoConfig + senderManager: SenderManager + metrics: Metrics + gasPriceManager: GasPriceManager + }) { + super({ + config, metrics, - gasPriceManager, - chainType, - blockTagSupport, - utilityWalletAddress, - binarySearchToleranceDelta, - binarySearchGasAllowance, - entryPointSimulationsAddress, - fixedGasLimitForEstimation, - usingTenderly, - balanceOverrideEnabled - ) + gasPriceManager + }) this.senderManager = senderManager } @@ -126,7 +108,7 @@ export class SafeValidator preOpGas: validationResult.returnInfo.preOpGas, paid: validationResult.returnInfo.prefund }, - this.chainId + this.config.publicClient.chain.id ) let mul = 1n @@ -181,7 +163,7 @@ export class SafeValidator let hash = "" try { - await this.publicClient.call({ + await this.config.publicClient.call({ account: wallet, data: deployData }) @@ -210,7 +192,7 @@ export class SafeValidator referencedContracts?: ReferencedCodeHashes } > { - if (this.usingTenderly) { + if (this.config.tenderly) { return super.getValidationResultV07( userOperation, queuedUserOperations, @@ -281,7 +263,7 @@ export class SafeValidator storageMap: StorageMap } > { - if (this.usingTenderly) { + if (this.config.tenderly) { return super.getValidationResultV06(userOperation, entryPoint) } @@ -359,7 +341,7 @@ export class SafeValidator entryPoint: Address ): Promise<[ValidationResultV06, BundlerTracerResult]> { const tracerResult = await debug_traceCall( - this.publicClient, + this.config.publicClient, { from: zeroAddress, to: entryPoint, @@ -518,11 +500,10 @@ export class SafeValidator }) const entryPointSimulationsAddress = - this.gasEstimationHandler.gasEstimatorV07 - .entryPointSimulationsAddress + this.config.entrypointSimulationContract const tracerResult = await debug_traceCall( - this.publicClient, + this.config.publicClient, { from: zeroAddress, to: entryPointSimulationsAddress, diff --git a/src/rpc/validation/UnsafeValidator.ts b/src/rpc/validation/UnsafeValidator.ts index 7493d62f..ab62bf2e 100644 --- a/src/rpc/validation/UnsafeValidator.ts +++ b/src/rpc/validation/UnsafeValidator.ts @@ -1,6 +1,5 @@ import type { GasPriceManager } from "@alto/handlers" import type { - ChainType, InterfaceValidator, StateOverrides, UserOperationV06, @@ -36,10 +35,7 @@ import { import * as sentry from "@sentry/node" import { BaseError, - type Chain, ContractFunctionExecutionError, - type PublicClient, - type Transport, getContract, pad, slice, @@ -49,57 +45,34 @@ import { import { fromZodError } from "zod-validation-error" import { GasEstimationHandler } from "../estimation/gasEstimationHandler" import type { SimulateHandleOpResult } from "../estimation/types" +import type { AltoConfig } from "../../createConfig" export class UnsafeValidator implements InterfaceValidator { - publicClient: PublicClient - logger: Logger + config: AltoConfig metrics: Metrics - usingTenderly: boolean - balanceOverrideEnabled: boolean - expirationCheck: boolean - chainId: number gasPriceManager: GasPriceManager - chainType: ChainType - + logger: Logger gasEstimationHandler: GasEstimationHandler - constructor( - publicClient: PublicClient, - logger: Logger, - metrics: Metrics, - gasPriceManager: GasPriceManager, - chainType: ChainType, - blockTagSupport: boolean, - utilityWalletAddress: Address, - binarySearchToleranceDelta: bigint, - binarySearchGasAllowance: bigint, - entryPointSimulationsAddress?: Address, - fixedGasLimitForEstimation?: bigint, - usingTenderly = false, - balanceOverrideEnabled = false, - expirationCheck = true - ) { - this.publicClient = publicClient - this.logger = logger + constructor({ + config, + metrics, + gasPriceManager + }: { + config: AltoConfig + metrics: Metrics + gasPriceManager: GasPriceManager + }) { + this.config = config this.metrics = metrics - this.usingTenderly = usingTenderly - this.balanceOverrideEnabled = balanceOverrideEnabled - this.expirationCheck = expirationCheck - this.chainId = publicClient.chain.id this.gasPriceManager = gasPriceManager - this.chainType = chainType - - this.gasEstimationHandler = new GasEstimationHandler( - binarySearchToleranceDelta, - binarySearchGasAllowance, - publicClient, - publicClient.chain.id, - blockTagSupport, - utilityWalletAddress, - chainType, - entryPointSimulationsAddress, - fixedGasLimitForEstimation + this.logger = config.getLogger( + { module: "validator" }, + { + level: config.logLevel + } ) + this.gasEstimationHandler = new GasEstimationHandler(config) } async getSimulationResult( @@ -192,7 +165,7 @@ export class UnsafeValidator implements InterfaceValidator { userOperation, queuedUserOperations, addSenderBalanceOverride, - balanceOverrideEnabled: this.balanceOverrideEnabled, + balanceOverrideEnabled: this.config.balanceOverride, entryPoint, replacedEntryPoint: false, targetAddress: zeroAddress, @@ -224,7 +197,7 @@ export class UnsafeValidator implements InterfaceValidator { address: entryPoint, abi: EntryPointV06Abi, client: { - public: this.publicClient + public: this.config.publicClient } }) @@ -255,7 +228,7 @@ export class UnsafeValidator implements InterfaceValidator { simulateValidationResult, this.logger, "validation", - this.usingTenderly + this.config.tenderly )) as ValidationResultV06 | ValidationResultWithAggregationV06), storageMap: {} } @@ -277,7 +250,7 @@ export class UnsafeValidator implements InterfaceValidator { if ( validationResult.returnInfo.validAfter > now - 5 && - this.expirationCheck + this.config.expirationCheck ) { throw new RpcError( "User operation is not valid yet", @@ -287,7 +260,7 @@ export class UnsafeValidator implements InterfaceValidator { if ( validationResult.returnInfo.validUntil < now + 30 && - this.expirationCheck + this.config.expirationCheck ) { throw new RpcError( "expires too soon", @@ -509,15 +482,13 @@ export class UnsafeValidator implements InterfaceValidator { userOperation: UserOperation, entryPoint: Address ) { - const preVerificationGas = await calcPreVerificationGas( - this.publicClient, + const preVerificationGas = await calcPreVerificationGas({ + config: this.config, userOperation, entryPoint, - this.chainId, - this.chainType, - this.gasPriceManager, - true - ) + gasPriceManager: this.gasPriceManager, + validate: true + }) if (preVerificationGas > userOperation.preVerificationGas) { throw new RpcError( @@ -556,7 +527,7 @@ export class UnsafeValidator implements InterfaceValidator { preOpGas: validationResult.returnInfo.preOpGas, paid: validationResult.returnInfo.prefund }, - this.chainId + this.config.publicClient.chain.id ) let mul = 1n diff --git a/src/utils/validation.ts b/src/utils/validation.ts index f5533f99..a943d472 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,7 +1,6 @@ import type { GasPriceManager } from "@alto/handlers" import { type Address, - type ChainType, EntryPointV06Abi, EntryPointV07Abi, type PackedUserOperation, @@ -34,6 +33,7 @@ import { import { base, baseGoerli, baseSepolia } from "viem/chains" import { maxBigInt, minBigInt, scaleBigIntByPercent } from "./bigInt" import { isVersion06, toPackedUserOperation } from "./userop" +import type { AltoConfig } from "../createConfig" export interface GasOverheads { /** @@ -299,36 +299,41 @@ export function packUserOpV07(op: PackedUserOperation): `0x${string}` { ) } -export async function calcPreVerificationGas( - publicClient: PublicClient, - userOperation: UserOperation, - entryPoint: Address, - chainId: number, - chainType: ChainType, - gasPriceManager: GasPriceManager, - validate: boolean, // when calculating preVerificationGas for validation +export async function calcPreVerificationGas({ + config, + userOperation, + entryPoint, + gasPriceManager, + validate, + overheads +}: { + config: AltoConfig + userOperation: UserOperation + entryPoint: Address + gasPriceManager: GasPriceManager + validate: boolean // when calculating preVerificationGas for validation overheads?: GasOverheads -): Promise { +}): Promise { let preVerificationGas = calcDefaultPreVerificationGas( userOperation, overheads ) - if (chainId === 59140) { + if (config.publicClient.chain.id === 59140) { // linea sepolia preVerificationGas *= 2n - } else if (chainType === "op-stack") { + } else if (config.chainType === "op-stack") { preVerificationGas = await calcOptimismPreVerificationGas( - publicClient, + config.publicClient, userOperation, entryPoint, preVerificationGas, gasPriceManager, validate ) - } else if (chainType === "arbitrum") { + } else if (config.chainType === "arbitrum") { preVerificationGas = await calcArbitrumPreVerificationGas( - publicClient, + config.publicClient, userOperation, entryPoint, preVerificationGas,