Get User Operation Hash
Learn the foundational concepts of how a ERC-4337 UserOperation is signed and how a UserOpHash can be generated in Solidity, JS, and Go.
The userOpHash
is a keccak256 hash of:
- The entire User Operation (minus the signature field).
- The EntryPoint address.
- The chain ID.
All signatures should be a signed message of the userOpHash
. To optimize gas, the EntryPoint will generate this hash once and pass it to both validateUserOp
and validatePaymasterUserOp
during the verification phase.
Example Code
const packedData = ethers.utils.defaultAbiCoder.encode(
[
"address",
"uint256",
"bytes32",
"bytes32",
"uint256",
"uint256",
"uint256",
"uint256",
"uint256",
"bytes32",
],
[
sender,
nonce,
ethers.utils.keccak256(initCode),
ethers.utils.keccak256(callData),
callGasLimit,
verificationGasLimit,
preVerificationGas,
maxFeePerGas,
maxPriorityFeePerGas,
ethers.utils.keccak256(paymasterAndData),
]
);
const enc = ethers.utils.defaultAbiCoder.encode(
["bytes32", "address", "uint256"],
[ethers.utils.keccak256(packedData), entryPoint, chainId]
);
const userOpHash = ethers.utils.keccak256(enc);
// See the EntryPoint contract at
// https://github.com/eth-infinitism/account-abstraction/blob/8215b88768d993fb6459c2723d173791a537a2e7/contracts/core/EntryPoint.sol#L298
function getUserOpHash(
UserOperation calldata userOp
) public view returns (bytes32) {
return
keccak256(abi.encode(userOp.hash(), address(this), block.chainid));
}
// See the Stackup bundler code at
// https://github.com/stackup-wallet/stackup-bundler/blob/34cd6b4ebc7a8c0b6a135f947f68911fd475c33a/pkg/userop/object.go#L178-L184
func (op *UserOperation) GetUserOpHash(entryPoint common.Address, chainID *big.Int) common.Hash {
return crypto.Keccak256Hash(
crypto.Keccak256(op.PackForSignature()),
common.LeftPadBytes(entryPoint.Bytes(), 32),
common.LeftPadBytes(chainID.Bytes(), 32),
)
}
User Operation Hash in Userop.js
The user operation hash is automatically returned when a user operation is built.
If you'd like to learn more, read the source code for getUserOpHash.
const res = await client.sendUserOperation(builder.executeBatch(calls), {
onBuild: (op) => console.log("Signed UserOperation:", op),
});
console.log(`UserOpHash: ${res.userOpHash}`);
Updated about 1 year ago