Skip to content

Commit

Permalink
feat: eip4337 rpc function
Browse files Browse the repository at this point in the history
  • Loading branch information
sanyu1225 committed Jul 22, 2023
1 parent d12dc7e commit e858fc8
Show file tree
Hide file tree
Showing 8 changed files with 432 additions and 1 deletion.
88 changes: 88 additions & 0 deletions packages/web3-eth/src/rpc_method_wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import {
TransactionWithFromAndToLocalWalletIndex,
TransactionForAccessList,
AccessListResult,
UserOperation,
UserOperationOptionalFees,
} from 'web3-types';
import { Web3Context, Web3PromiEvent } from 'web3-core';
import { format, hexToBytes, bytesToUint8Array, numberToHex } from 'web3-utils';
Expand Down Expand Up @@ -1125,3 +1127,89 @@ export async function createAccessList<ReturnFormat extends DataFormat>(

return format(accessListResultSchema, response, returnFormat);
}

/**
* View additional documentations here: {@link Web3Eth.sendUserOperation}
* @param web3Context ({@link Web3Context}) Web3 configuration object that contains things such as the provider, request manager, wallet, etc.
*/
export async function sendUserOperation<ReturnFormat extends DataFormat>(
web3Context: Web3Context<EthExecutionAPI>,
userOperation: UserOperation,
entryPoint: Address,
returnFormat: ReturnFormat,
) {
const response = await ethRpcMethods.sendUserOperation(
web3Context.requestManager,
userOperation,
entryPoint,
);
return format({ format: 'uint' }, response as Numbers, returnFormat);
}

/**
* View additional documentations here: {@link Web3Eth.estimateUserOperationGas}
* @param web3Context ({@link Web3Context}) Web3 configuration object that contains things such as the provider, request manager, wallet, etc.
*/
export async function estimateUserOperationGas<ReturnFormat extends DataFormat>(
web3Context: Web3Context<EthExecutionAPI>,
userOperation: UserOperationOptionalFees,
entryPoint: Address,
returnFormat: ReturnFormat,
) {
// If maxFeePerGas or maxPriorityFeePerGas is not provided, set them to '0'
if (userOperation?.maxFeePerGas === undefined) {
userOperation.maxFeePerGas = '0';
}
if (userOperation?.maxPriorityFeePerGas === undefined) {
userOperation.maxPriorityFeePerGas = '0';
}
const response = await ethRpcMethods.sendUserOperation(
web3Context.requestManager,
userOperation as UserOperation,
entryPoint,
);
return format({ format: 'uint' }, response as Numbers, returnFormat);
}
/**
* View additional documentations here: {@link Web3Eth.getUserOperationByHash}
* @param web3Context ({@link Web3Context}) Web3 configuration object that contains things such as the provider, request manager, wallet, etc.
*/
export async function getUserOperationByHash<ReturnFormat extends DataFormat>(
web3Context: Web3Context<EthExecutionAPI>,
hash: HexStringBytes,
returnFormat: ReturnFormat,
) {
const response = await ethRpcMethods.getUserOperationByHash(
web3Context.requestManager,
hash,
);
return format({ format: 'uint' }, response as Numbers, returnFormat);
}
/**
* View additional documentations here: {@link Web3Eth.getUserOperationReceipt}
* @param web3Context ({@link Web3Context}) Web3 configuration object that contains things such as the provider, request manager, wallet, etc.
*/
export async function getUserOperationReceipt<ReturnFormat extends DataFormat>(
web3Context: Web3Context<EthExecutionAPI>,
hash: HexStringBytes,
returnFormat: ReturnFormat,
) {
const response = await ethRpcMethods.getUserOperationReceipt(
web3Context.requestManager,
hash,
);
return format({ format: 'uint' }, response as Numbers, returnFormat);
}
/**
* View additional documentations here: {@link Web3Eth.supportedEntryPoints}
* @param web3Context ({@link Web3Context}) Web3 configuration object that contains things such as the provider, request manager, wallet, etc.
*/
export async function supportedEntryPoints<ReturnFormat extends DataFormat>(
web3Context: Web3Context<EthExecutionAPI>,
returnFormat: ReturnFormat,
) {
const response = await ethRpcMethods.supportedEntryPoints(
web3Context.requestManager,
);
return format({ format: 'uint' }, response as Numbers, returnFormat);
}
69 changes: 69 additions & 0 deletions packages/web3-eth/src/utils/generate_useroperation_hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import { encodeParameters } from 'web3-eth-abi';
import { AbiInput, UserOperation } from 'web3-types';
import { sha3 } from 'web3-utils';

const sha3Checked = (data: string): string => {
const result = sha3(data);
if (result === undefined) {
throw new Error(`sha3 returned undefined for data: ${data}`);
}
return result;
};

export const generateUserOpHash = (userOp: UserOperation, entryPoint: string, chainId: string): string => {
try {
const types: AbiInput[] = [
'address',
'uint256',
'bytes32',
'bytes32',
'uint256',
'uint256',
'uint256',
'uint256',
'uint256',
'bytes32',
];

const values: (string | number)[] = [
userOp.sender,
userOp.nonce,
sha3Checked(userOp.initCode),
sha3Checked(userOp.callData),
userOp.callGasLimit,
userOp.verificationGasLimit,
userOp.preVerificationGas,
userOp.maxFeePerGas,
userOp.maxPriorityFeePerGas,
sha3Checked(userOp.paymasterAndData),
];

const packed: string = encodeParameters(types, values);

const enctype: AbiInput[] = ['bytes32', 'address', 'uint256'];
const encValues: string[] = [sha3Checked(packed), entryPoint, chainId];
const enc: string = encodeParameters(enctype, encValues);

return sha3Checked(enc);
} catch (error) {
console.error(error);
throw error;
}
};
1 change: 1 addition & 0 deletions packages/web3-eth/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export * from './detect_transaction_type.js';
export * from './format_transaction.js';
export * from './prepare_transaction_for_signing.js';
export * from './transaction_builder.js';
export * from './generate_useroperation_hash.js';
192 changes: 192 additions & 0 deletions packages/web3-eth/src/web3_eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
TransactionForAccessList,
DataFormat,
DEFAULT_RETURN_FORMAT,
UserOperation,
HexStringBytes,
} from 'web3-types';
import { isSupportedProvider, Web3Context, Web3ContextInitOptions } from 'web3-core';
import { TransactionNotFound } from 'web3-errors';
Expand All @@ -51,6 +53,7 @@ import {
NewHeadsSubscription,
SyncingSubscription,
} from './web3_subscriptions.js';
import { generateUserOpHash } from './utils/generate_useroperation_hash.js';

type RegisteredSubscription = {
logs: typeof LogsSubscription;
Expand Down Expand Up @@ -1634,4 +1637,193 @@ export class Web3Eth extends Web3Context<Web3EthExecutionAPI, RegisteredSubscrip
notClearSyncing ? Web3Eth.shouldClearSubscription : undefined,
);
}

/**
* Sends a UserOperation to the bundler. If accepted, the bundler will add it to the UserOperation mempool and return a userOpHash.
*
* @param UserOperation represents the structure of a transaction initiated by the user. It contains the sender, receiver, call data, maximum fee per unit of Gas, maximum priority fee, signature, nonce, and other specific elements.
* @param entryPoint a singleton contract to execute bundles of UserOperations. Bundlers/Clients whitelist the supported entrypoint.
* @param blockNumber ({@link BlockNumberOrTag} defaults to {@link Web3Eth.defaultBlock}) Specifies what block to use as the current state for the balance query.
* @param returnFormat ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) Specifies how the return data should be formatted.
* @returns The current balance for the given address in `wei`.
*
* ```ts
* web3.eth.sendUserOperation({
sender: "0x9fd042a18e90ce326073fa70f111dc9d798d9a52",
nonce: "123",
initCode: "0x68656c6c6f",
callData: "0x776F726C64",
callGasLimit: "1000",
verificationGasLimit: "2300",
preVerificationGas: "3100",
maxFeePerGas: "8500",
maxPriorityFeePerGas: "1",
paymasterAndData: "0x626c6f63746f",
signature: "0x636c656d656e74"
},"0x636c656d656e74").then(console.log);
* > 0xe554d0701f7fdc734f84927d109537f1ac4ee4ebfa3670c71d224a4fa15dbcd1
* ```
*/
public async sendUserOperation<ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT>(
userOperation: UserOperation,
entryPoint: Address,
returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat,
) {
return rpcMethodsWrappers.sendUserOperation(
this,
userOperation,
entryPoint,
returnFormat,
);
}
/**
* Estimate the gas values for a UserOperation. Given UserOperation optionally without gas limits and gas prices, return the needed gas limits. The signature field is ignored by the wallet, so that the operation will not require user's approval. Still, it might require putting a "semi-valid" signature (e.g. a signature in the right length)
*
* @param UserOperation represents the structure of a transaction initiated by the user. It contains the sender, receiver, call data, maximum fee per unit of Gas, maximum priority fee, signature, nonce, and other specific elements.
* @param entryPoint a singleton contract to execute bundles of UserOperations. Bundlers/Clients whitelist the supported entrypoint.
* @param blockNumber ({@link BlockNumberOrTag} defaults to {@link Web3Eth.defaultBlock}) Specifies what block to use as the current state for the balance query.
* @param returnFormat ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) Specifies how the return data should be formatted.
* @returns The current balance for the given address in `wei`.
*
* ```ts
* {
sender, // address
nonce, // uint256
initCode, // bytes
callData, // bytes
callGasLimit, // uint256
verificationGasLimit, // uint256
preVerificationGas, // uint256
maxFeePerGas, // uint256
maxPriorityFeePerGas, // uint256
paymasterAndData, // bytes
signature // bytes
}
* web3.eth.estimateUserOperationGas({
sender: "0x9fd042a18e90ce326073fa70f111dc9d798d9a52",
nonce: "123",
initCode: "0x68656c6c6f",
callData: "0x776F726C64",
callGasLimit: "1000",
verificationGasLimit: "2300",
preVerificationGas: "3100",
maxFeePerGas: "0",
maxPriorityFeePerGas: "0",
paymasterAndData: "0x626c6f63746f",
signature: "0x636c656d656e74"
},"0x636c656d656e74").then(console.log);
* > 0xe554d0701f7fdc734f84927d109537f1ac4ee4ebfa3670c71d224a4fa15dbcd1
* ```
*/
public async estimateUserOperationGas<ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT>(
userOperation: UserOperation,
entryPoint: Address,
returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat,
) {
return rpcMethodsWrappers.sendUserOperation(
this,
userOperation,
entryPoint,
returnFormat,
);
}
/**
* Return a UserOperation based on a hash (userOpHash) returned by eth_sendUserOperation
*
* @param hash a userOpHash value returned by `eth_sendUserOperation`
* @param returnFormat ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) Specifies how the return data should be formatted.
* @returns null in case the UserOperation is not yet included in a block, or a full UserOperation, with the addition of entryPoint, blockNumber, blockHash and transactionHash
*
* ```ts
* web3.eth.getUserOperationByHash("0xxxxx").then(console.log);
* > 0xxxxx
* ```
*/
public async getUserOperationByHash<ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT>(
hash: HexStringBytes,
returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat,
) {
return rpcMethodsWrappers.getUserOperationByHash(
this,
hash,
returnFormat,
);
}
/**
* Return null in case the UserOperation is not yet included in a block, or
* - userOpHash the request hash
* - entryPoint
* - sender
* - nonce
* - paymaster the paymaster used for this userOp (or empty)
* - actualGasCost - actual amount paid (by account or paymaster) for this UserOperation
* - actualGasUsed - total gas used by this UserOperation (including preVerification, creation, validation and execution)
* - success boolean - did this execution completed without revert
* - reason in case of revert, this is the revert reason
* - logs the logs generated by this UserOperation (not including logs of other UserOperations in the same bundle)
* - receipt the TransactionReceipt object. Note that the returned TransactionReceipt is for the entire bundle, not only for this UserOperation.
*
* @param hash hash a userOpHash value returned by `eth_sendUserOperation`
* @param returnFormat ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) Specifies how the return data should be formatted.
* @returns null in case the UserOperation is not yet included in a block, or UserOperation
*
* ```ts
* web3.eth.getUserOperationReceipt("0xxxxx").then(console.log);
* >
* ```
*/
public async getUserOperationReceipt<ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT>(
hash: HexStringBytes,
returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat,
) {
return rpcMethodsWrappers.getUserOperationReceipt(
this,
hash,
returnFormat,
);
}
/**
* Returns an array of the entryPoint addresses supported by the client. The first element of the array SHOULD be the entryPoint addressed preferred by the client.
* @param returnFormat ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) Specifies how the return data should be formatted.
* @returns an array of the entryPoint addresses supported by the client. The first element of the array SHOULD be the entryPoint addressed preferred by the client.
*
* ```ts
* web3.eth.supportedEntryPoints().then(console.log);
* > ["0xcd01C8aa8995A59eB7B2627E69b40e0524B5ecf8", "0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6"]
* ```
*/
public async supportedEntryPoints<ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT>(
returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat,
) {
return rpcMethodsWrappers.supportedEntryPoints(
this,
returnFormat,
);
}
/**
* calculate UserOperationHash
* @param userOp a structure that describes a transaction to be sent on behalf of a user.
* @param entryPoint a singleton contract to execute bundles of UserOperations. Bundlers/Clients whitelist the supported entrypoint.
* @returns an array of the entryPoint addresses supported by the client. The first element of the array SHOULD be the entryPoint addressed preferred by the client.
*
* ```ts
* web3.eth.generateUserOpHash(sender: "0x9fd042a18e90ce326073fa70f111dc9d798d9a52",
nonce: "123",
initCode: "0x68656c6c6f",
callData: "0x776F726C64",
callGasLimit: "1000",
verificationGasLimit: "2300",
preVerificationGas: "3100",
maxFeePerGas: "0",
maxPriorityFeePerGas: "0",
paymasterAndData: "0x626c6f63746f",
signature: "0x636c656d656e74"
},"0x636c656d656e74", '0x1').then(console.log);
* > 0xxxx
* ```
*/
public async generateUserOpHash(userOp: UserOperation, entryPoint: string) {
const chainId = await this.getChainId();
return generateUserOpHash(userOp, entryPoint, String(chainId));
}
}
Loading

0 comments on commit e858fc8

Please sign in to comment.