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:

  1. The entire User Operation (minus the signature field).
  2. The EntryPoint address.
  3. 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}`);