diff --git a/lerna.json b/lerna.json index 4d241454..bed9b857 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.0.42", + "version": "0.0.43", "stream": "true", "command": { "version": { diff --git a/package.json b/package.json index 16b953bf..4541cb80 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "root", "private": true, - "version": "0.0.42", + "version": "0.0.43", "engines": { "node": ">=18.0.0" }, diff --git a/packages/api/package.json b/packages/api/package.json index f788cead..e2bb606c 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "api", - "version": "0.0.42", + "version": "0.0.43", "description": "The API module of Etherspot bundler client", "author": "Etherspot", "homepage": "https://https://github.com/etherspot/skandha#readme", @@ -35,12 +35,12 @@ "class-transformer": "0.5.1", "class-validator": "0.14.0", "ethers": "5.7.2", - "executor": "^0.0.42", + "executor": "^0.0.43", "fastify": "4.14.1", "pino": "8.11.0", "pino-pretty": "10.0.0", "reflect-metadata": "0.1.13", - "types": "^0.0.42" + "types": "^0.0.43" }, "devDependencies": { "@types/connect": "3.4.35" diff --git a/packages/api/src/app.ts b/packages/api/src/app.ts index 76569f24..506e8705 100644 --- a/packages/api/src/app.ts +++ b/packages/api/src/app.ts @@ -190,6 +190,13 @@ export class ApiApp { case CustomRPCMethods.skandha_getGasPrice: result = await skandhaApi.getGasPrice(); break; + case CustomRPCMethods.skandha_feeHistory: + result = await skandhaApi.getFeeHistory({ + entryPoint: params[0], + blockCount: params[1], + newestBlock: params[2], + }); + break; default: throw new RpcError( `Method ${method} is not supported`, diff --git a/packages/api/src/constants.ts b/packages/api/src/constants.ts index 9651022c..2003e3d6 100644 --- a/packages/api/src/constants.ts +++ b/packages/api/src/constants.ts @@ -1,6 +1,7 @@ export const CustomRPCMethods = { skandha_validateUserOperation: "skandha_validateUserOperation", skandha_getGasPrice: "skandha_getGasPrice", + skandha_feeHistory: "skandha_feeHistory", }; export const BundlerRPCMethods = { diff --git a/packages/api/src/dto/FeeHistory.dto.ts b/packages/api/src/dto/FeeHistory.dto.ts new file mode 100644 index 00000000..606f75ea --- /dev/null +++ b/packages/api/src/dto/FeeHistory.dto.ts @@ -0,0 +1,15 @@ +import { IsEthereumAddress, ValidateIf } from "class-validator"; +import { BigNumberish } from "ethers"; +import { IsBigNumber } from "../utils/is-bignumber"; + +export class FeeHistoryArgs { + @IsEthereumAddress() + entryPoint!: string; + + @IsBigNumber() + blockCount!: BigNumberish; + + @ValidateIf((o) => o.newestBlock != "latest") + @IsBigNumber() + newestBlock!: BigNumberish | string; +} diff --git a/packages/api/src/modules/skandha.ts b/packages/api/src/modules/skandha.ts index fec09a54..4f54aa82 100644 --- a/packages/api/src/modules/skandha.ts +++ b/packages/api/src/modules/skandha.ts @@ -1,8 +1,14 @@ import { Eth } from "executor/lib/modules/eth"; -import { GetGasPriceResponse } from "types/lib/api/interfaces"; +import { + GetFeeHistoryResponse, + GetGasPriceResponse, +} from "types/lib/api/interfaces"; import { Skandha } from "executor/lib/modules"; +import RpcError from "types/lib/api/errors/rpc-error"; +import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; import { RpcMethodValidator } from "../utils/RpcMethodValidator"; import { SendUserOperationGasArgs } from "../dto/SendUserOperation.dto"; +import { FeeHistoryArgs } from "../dto/FeeHistory.dto"; export class SkandhaAPI { constructor(private ethModule: Eth, private skandhaModule: Skandha) {} @@ -19,6 +25,24 @@ export class SkandhaAPI { return await this.ethModule.validateUserOp(args); } + /** + * @param entryPoint Entry Point + * @param useropCount Number of blocks in the requested range + * @param newestBlock Highest number block of the requested range, or "latest" + * @returns + */ + @RpcMethodValidator(FeeHistoryArgs) + async getFeeHistory(args: FeeHistoryArgs): Promise { + if (!this.ethModule.validateEntryPoint(args.entryPoint)) { + throw new RpcError("Invalid Entrypoint", RpcErrorCodes.INVALID_REQUEST); + } + return await this.skandhaModule.getFeeHistory( + args.entryPoint, + args.blockCount, + args.newestBlock + ); + } + async getGasPrice(): Promise { return await this.skandhaModule.getGasPrice(); } diff --git a/packages/cli/package.json b/packages/cli/package.json index a50168df..011222ac 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cli", - "version": "0.0.42", + "version": "0.0.43", "description": "> TODO: description", "author": "zincoshine ", "homepage": "https://https://github.com/etherspot/skandha#readme", @@ -31,13 +31,13 @@ "url": "https://https://github.com/etherspot/skandha/issues" }, "dependencies": { - "api": "^0.0.42", - "db": "^0.0.42", - "executor": "^0.0.42", + "api": "^0.0.43", + "db": "^0.0.43", + "executor": "^0.0.43", "find-up": "5.0.0", "got": "12.5.3", "js-yaml": "4.1.0", - "types": "^0.0.42", + "types": "^0.0.43", "yargs": "17.6.2" }, "devDependencies": { diff --git a/packages/db/package.json b/packages/db/package.json index b3dcc526..9217d128 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "db", - "version": "0.0.42", + "version": "0.0.43", "description": "The DB module of Etherspot bundler client", "author": "Etherspot", "homepage": "https://github.com/etherspot/etherspot-bundler#readme", @@ -37,6 +37,6 @@ "devDependencies": { "@types/rocksdb": "3.0.1", "prettier": "^2.8.4", - "types": "^0.0.42" + "types": "^0.0.43" } } diff --git a/packages/executor/package.json b/packages/executor/package.json index 84bed5c1..1c9f483a 100644 --- a/packages/executor/package.json +++ b/packages/executor/package.json @@ -1,6 +1,6 @@ { "name": "executor", - "version": "0.0.42", + "version": "0.0.43", "description": "The Relayer module of Etherspot bundler client", "author": "Etherspot", "homepage": "https://https://github.com/etherspot/skandha#readme", @@ -33,7 +33,7 @@ "dependencies": { "async-mutex": "0.4.0", "ethers": "5.7.2", - "params": "^0.0.42", - "types": "^0.0.42" + "params": "^0.0.43", + "types": "^0.0.43" } } diff --git a/packages/executor/src/modules/eth.ts b/packages/executor/src/modules/eth.ts index ff02147d..52133023 100644 --- a/packages/executor/src/modules/eth.ts +++ b/packages/executor/src/modules/eth.ts @@ -396,7 +396,7 @@ export class Eth { ); } - private validateEntryPoint(entryPoint: string): boolean { + validateEntryPoint(entryPoint: string): boolean { return ( this.config.entryPoints.findIndex( (ep) => ep.toLowerCase() === entryPoint.toLowerCase() diff --git a/packages/executor/src/modules/skandha.ts b/packages/executor/src/modules/skandha.ts index 9459d17b..acea2b68 100644 --- a/packages/executor/src/modules/skandha.ts +++ b/packages/executor/src/modules/skandha.ts @@ -1,10 +1,15 @@ -import { BigNumber, ethers } from "ethers"; +import { BigNumber, BigNumberish, ethers } from "ethers"; import { NetworkName } from "types/lib"; -import { GetGasPriceResponse } from "types/lib/api/interfaces"; +import { + GetFeeHistoryResponse, + GetGasPriceResponse, +} from "types/lib/api/interfaces"; import RpcError from "types/lib/api/errors/rpc-error"; import * as RpcErrorCodes from "types/lib/api/errors/rpc-error-codes"; import { GasPriceMarkupOne } from "params/lib"; import { getGasFee } from "params/lib"; +import { IEntryPoint__factory } from "types/lib/executor/contracts"; +import { UserOperationStruct } from "types/lib/executor/contracts/EntryPoint"; import { Logger, NetworkConfig } from "../interfaces"; // custom features of Skandha @@ -51,4 +56,59 @@ export class Skandha { maxFeePerGas, }; } + + /** + * see eth_feeHistory + * @param entryPoint Entry Point contract + * @param blockCount Number of blocks in the requested range + * @param newestBlock Highest number block of the requested range, or "latest" + */ + async getFeeHistory( + entryPoint: string, + blockCount: BigNumberish, + newestBlock: BigNumberish | string + ): Promise { + const toBlockInfo = await this.provider.getBlock(newestBlock.toString()); + const fromBlockNumber = BigNumber.from(toBlockInfo.number) + .sub(blockCount) + .toNumber(); + const contract = IEntryPoint__factory.connect(entryPoint, this.provider); + const events = await contract.queryFilter( + contract.filters.UserOperationEvent(), + fromBlockNumber, + toBlockInfo.number + ); + const txReceipts = await Promise.all( + events.map((event) => event.getTransaction()) + ); + const txDecoded = txReceipts + .map((receipt) => { + try { + return contract.interface.decodeFunctionData( + "handleOps", + receipt.data + ); + } catch (err) { + this.logger.error(err); + return null; + } + }) + .filter((el) => el !== null); + + const actualGasPrice = events.map((event) => + BigNumber.from(event.args.actualGasCost).div(event.args.actualGasUsed) + ); + const userops = txDecoded + .map((handleOps) => handleOps!.ops as UserOperationStruct[]) + .reduce((p, c) => { + return p.concat(c); + }, []); + return { + actualGasPrice, + maxFeePerGas: userops.map((userop) => userop.maxFeePerGas), + maxPriorityFeePerGas: userops.map( + (userop) => userop.maxPriorityFeePerGas + ), + }; + } } diff --git a/packages/executor/src/services/BundlingService.ts b/packages/executor/src/services/BundlingService.ts index aca5ad5a..18a5fe19 100644 --- a/packages/executor/src/services/BundlingService.ts +++ b/packages/executor/src/services/BundlingService.ts @@ -125,6 +125,16 @@ export class BundlingService { let txHash: string; // geth-dev doesn't support signTransaction if (!this.config.testingMode) { + // check for execution revert + try { + await wallet.estimateGas(tx); + } catch (err) { + this.logger.error(err); + for (const entry of entries) { + await this.mempoolService.remove(entry); + } + return null; + } const signedRawTx = await wallet.signTransaction(tx); const method = !this.networkConfig.conditionalTransactions diff --git a/packages/params/package.json b/packages/params/package.json index f24aaf9f..4080d181 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "params", - "version": "0.0.42", + "version": "0.0.43", "description": "Various bundler parameters", "author": "Etherspot", "homepage": "https://github.com/etherspot/skandha#readme", @@ -25,7 +25,7 @@ "@eth-optimism/sdk": "3.0.0", "@mantleio/sdk": "0.2.1", "ethers": "5.7.2", - "types": "^0.0.42" + "types": "^0.0.43" }, "scripts": { "clean": "rm -rf lib && rm -f *.tsbuildinfo", diff --git a/packages/types/package.json b/packages/types/package.json index 3abbc6e4..7952c6de 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "types", - "version": "0.0.42", + "version": "0.0.43", "description": "The types of Etherspot bundler client", "author": "Etherspot", "homepage": "https://https://github.com/etherspot/skandha#readme", diff --git a/packages/types/src/api/interfaces.ts b/packages/types/src/api/interfaces.ts index d5c745da..cdb698ed 100644 --- a/packages/types/src/api/interfaces.ts +++ b/packages/types/src/api/interfaces.ts @@ -25,6 +25,12 @@ export type GetGasPriceResponse = { maxPriorityFeePerGas: BigNumberish; }; +export type GetFeeHistoryResponse = { + actualGasPrice: BigNumberish[]; + maxFeePerGas: BigNumberish[]; + maxPriorityFeePerGas: BigNumberish[]; +}; + export type UserOperationReceipt = { userOpHash: string; sender: string;