EntryPoint IntelligenceERC-4337 on Avalanche C-Chain Data to 2026-10-25

Four EntryPoint singletons are deployed on C-Chain (chain 43114). They are separate contracts with separate deposits, nonces and event streams. An account or paymaster built for one version does not automatically work with another. When you read a transaction, the first question is always: which EntryPoint emitted the UserOperationEvent? Everything else (struct layout, hash formula, paymaster encoding) follows from that.

Sources used on this page:

All four pages were fetched on 2026-10-19 and the statements below were checked against them.

What the archive shows, per version

Per-version totals, 2023-04 to 2026-10-25 (from the population series)
VersionFirst UserOpLast UserOpOuter txsUserOpsOp successPaymaster-paidReceipt fees (AVAX)Distinct accountsBusiest day
v0.62023-04-172026-10-251,632,6761,653,22299.44%92.3%7,478.95162,4092026-04-24 (16,693 ops)
v0.72024-04-202026-10-254,758,3424,867,81999.98%99.1%1,506.25785,3192026-05-03 (73,641 ops)
v0.82025-04-152026-10-2227392799.24%100.0%7.75792026-08-30 (440 ops)
v0.92026-04-282026-10-259,3369,336100.00%45.8%1.63742026-09-10 (80 ops)

Each small chart has its own y-scale: v0.7 is roughly a thousand times larger than v0.9. The numbers page has the combined chart and data tables.

EntryPoint v0.6: UserOperations per month

EntryPoint v0.6: UserOperations per monthMonthly UserOperation count for EntryPoint v0.6, own y-scale.050K100K150K200K2023202420252026157,637 (2023-09)

EntryPoint v0.7: UserOperations per month

EntryPoint v0.7: UserOperations per monthMonthly UserOperation count for EntryPoint v0.7, own y-scale.0500K1M1.5M20232024202520261,065,029 (2026-04)

EntryPoint v0.8: UserOperations per month

EntryPoint v0.8: UserOperations per monthMonthly UserOperation count for EntryPoint v0.8, own y-scale.02004006002023202420252026440 (2026-08)

EntryPoint v0.9: UserOperations per month

EntryPoint v0.9: UserOperations per monthMonthly UserOperation count for EntryPoint v0.9, own y-scale.05001K1.5K2K20232024202520261,823 (2026-06)

1. Addresses and selectors

Version EntryPoint address (from epi/constants.py) Released (tag date) Struct on chain handleOps selector
v0.6 0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789 evidence level: primary source 24 Apr 2023 UserOperation, 11 fields 0x1fad948c
v0.7 0x0000000071727de22e5e9d8baf0edac6f37da032 evidence level: primary source 22 Feb 2024 PackedUserOperation, 9 fields 0x765e827f
v0.8 0x4337084d9e255ff0702461cf8895ce9e3b5ff108 evidence level: primary source 26 Mar 2025 PackedUserOperation, 9 fields 0x765e827f
v0.9 0x433709009b8330fda32311df1c2afa402ed8d009 evidence level: primary source 16 Nov 2025 PackedUserOperation, 9 fields 0x765e827f

The v0.7, v0.8 and v0.9 addresses match the addresses printed in their release notes. The v0.6 address matches the earlier identity record.

Other selectors (computed in epi/constants.py SEL):

Function v0.6 v0.7 / v0.8 / v0.9
handleOps(ops[], beneficiary) 0x1fad948c 0x765e827f
handleAggregatedOps(...) 0x4b1d7cf5 0xdbed18e0
depositTo(address) 0xb760faf9 0xb760faf9

v0.7, v0.8 and v0.9 share a handleOps selector because the struct is the same. The selector alone cannot tell you the version; the to address (or, for wrapped calls, the address that emits UserOperationEvent) can.

Event topics are the same across all four versions (epi/constants.py TOPIC). For example, UserOperationEvent is 0x49628fd1…419f everywhere. Again, the emitter address tells you the version.

2. The struct: 11 fields (v0.6) vs 9 packed fields (v0.7+)

v0.6 UserOperation

address sender
uint256 nonce
bytes   initCode
bytes   callData
uint256 callGasLimit
uint256 verificationGasLimit
uint256 preVerificationGas
uint256 maxFeePerGas
uint256 maxPriorityFeePerGas
bytes   paymasterAndData      // paymaster address (20 bytes) + paymaster-specific data
bytes   signature

In v0.6 one verificationGasLimit covers both account validation and paymaster validation. The paymaster's postOp also runs under that limit. Because postOp could be called twice in v0.6, the required prefund with a paymaster is (callGasLimit + 3 × verificationGasLimit + preVerificationGas) × maxFeePerGas (v0.6 _getRequiredPrefund).

v0.7+ PackedUserOperation

address sender
uint256 nonce
bytes   initCode
bytes   callData
bytes32 accountGasLimits      // verificationGasLimit (high 16 bytes) | callGasLimit (low 16 bytes)
uint256 preVerificationGas
bytes32 gasFees               // maxPriorityFeePerGas (high 16 bytes) | maxFeePerGas (low 16 bytes)
bytes   paymasterAndData
bytes   signature

The v0.7 release notes explain the change: the off-chain UserOperation (what wallets send to bundlers) and the on-chain struct became separate. Four gas numbers were packed into two bytes32 words to save calldata. The paymaster got its own two gas limits, stored inside paymasterAndData.

paymasterAndData layout

Version Layout
v0.6 [0:20] paymaster address, [20:] paymaster-specific data
v0.7, v0.8 [0:20] paymaster, [20:36] paymasterVerificationGasLimit (uint128), [36:52] paymasterPostOpGasLimit (uint128), [52:] paymasterData
v0.9 same as v0.7, plus an optional suffix: paymasterSignature ‖ uint16(len) ‖ 0x22e325a297439656

The offsets 20/36/52 are the constants PAYMASTER_VALIDATION_GAS_OFFSET, PAYMASTER_POSTOP_GAS_OFFSET and PAYMASTER_DATA_OFFSET in UserOperationLib.sol. The v0.9 magic is PAYMASTER_SIG_MAGIC = 0x22e325a297439656, the first 8 bytes of keccak("PaymasterSignature"). (I recomputed this locally and it matches.)

What sits after the paymaster address or the gas limits is defined by each paymaster, not by the EntryPoint. Pimlico's singleton paymaster is an example. Its source is kept as a snapshot. Its data starts with one byte, mode << 1 | allowAllBundlers, where mode 0 is verifying and mode 1 is ERC-20. In verifying mode, that byte is followed by validUntil (6 bytes), validAfter (6 bytes) and a signature. So a Pimlico v0.6 verifying-mode paymasterAndData is 20 + 1 + 12 + 65 = 98 bytes, and the v0.7 one is 52 + 1 + 12 + 65 = 130 bytes. Both lengths appear in the Oct 18 data.

3. The userOpHash formula

The userOpHash is the operation's identity. It is topic1 of UserOperationEvent and AccountDeployed, and it is usually what the account signs. Use the wrong formula and nothing matches.

v0.6 (EntryPoint.getUserOpHash, UserOperationLib.pack):

inner = keccak(abi.encode(sender, nonce, keccak(initCode), keccak(callData),
                          callGasLimit, verificationGasLimit, preVerificationGas,
                          maxFeePerGas, maxPriorityFeePerGas, keccak(paymasterAndData)))
userOpHash = keccak(abi.encode(inner, entryPoint, chainId))

v0.7: same shape, with the packed fields:

inner = keccak(abi.encode(sender, nonce, keccak(initCode), keccak(callData),
                          accountGasLimits, preVerificationGas, gasFees, keccak(paymasterAndData)))
userOpHash = keccak(abi.encode(inner, entryPoint, chainId))

v0.8 and v0.9: EIP-712 typed data with domain name="ERC4337", version="1", chainId, verifyingContract=EntryPoint:

PACKED_USEROP_TYPEHASH = keccak("PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData)")
structHash = keccak(abi.encode(TYPEHASH, sender, nonce, keccak(initCode*), keccak(callData),
                               accountGasLimits, preVerificationGas, gasFees, keccak(paymasterAndData**)))
userOpHash = keccak(0x1901 ‖ domainSeparator ‖ structHash)
  • * If initCode starts with the EIP-7702 marker, the initCode hash is replaced by keccak(delegate ‖ initCode[20:]), where delegate is read from the sender's code (v0.8 Eip7702Support._getEip7702InitCodeHashOverride).
  • ** In v0.9 only, if paymasterAndData ends with the magic suffix, the paymaster signature and its 10-byte suffix are excluded from the hash. The 8-byte magic itself is kept. So the user's signature does not cover the paymaster's signature, and the two can sign in parallel (v0.9 release note §2; getPaymasterSignatureLength / paymasterDataKeccak).

The v0.8 domain constants DOMAIN_NAME = "ERC4337" and DOMAIN_VERSION = "1" are in v0.8.0 EntryPoint.sol. The earlier decoder implements all three formulas. All 1,397 Oct 18 UserOperations re-hash to their event topic1 under their own version's formula. Using the v0.7 formula on v0.9 operations fails even though the bytes have the same shape (all-versions report).

Walked example: Reading a transaction recomputes a v0.7 hash byte by byte.

4. Behavior differences that matter when reading data

Topic v0.6 v0.7 v0.8 v0.9
Paymaster gas shares verificationGasLimit; postOp may run twice own paymasterVerificationGasLimit + paymasterPostOpGasLimit; postOp runs once same as v0.7 same as v0.7
Unused-gas penalty none 10% of unused callGasLimit/postOp gas 10%, but only if unused ≥ 40,000 gas same as v0.8
Hash abi-encode + EP + chainId abi-encode + EP + chainId EIP-712 "ERC4337" v1 EIP-712, paymaster-signature suffix excluded
EIP-7702 no no yes: initCode marker 0x7702, delegate in hash yes, plus EIP7702AccountInitialized event
executeUserOp account hook no yes (callData starting with executeUserOp selector 0x8dd7712f) yes yes
initCode when account already exists revert AA10 revert AA10 revert AA10 ignored, emits IgnoredInitCode
Validity range timestamps timestamps timestamps timestamps or block numbers (high bit set)
Zero-address paymaster with data tolerated tolerated rejected rejected
Simulation functions on chain yes (simulateValidation, always reverts) removed removed removed

Sources: v0.7, v0.8 and v0.9 release notes. The 40,000 threshold and the 10% figure are also in v0.8 EntryPoint.sol (PENALTY_GAS_THRESHOLD, UNUSED_GAS_PENALTY_PERCENT). The v0.6 postOp/prefund rule comes from v0.6 EntryPoint.sol.

Two consequences for analysis:

  • v0.9 initCode no longer means "first operation". A nonzero initCode on an already-deployed v0.9 account is silently ignored. Count deployments from AccountDeployed events, never from "initCode was non-empty". (That is good practice in every version.)
  • The user's gas price is min(maxFeePerGas, maxPriorityFeePerGas + basefee). It is computed from the UserOperation's own fields, not from the bundler's transaction gas price. This is why actualGasCost differs from the outer receipt fee (see the primer's money section).

5. EIP-7702 support: capability vs observed use

From v0.8, an EOA that has delegated its code via an EIP-7702 (type-4) transaction can act as a UserOperation sender. Such an operation marks its initCode with 0x7702 padded to 20 bytes. The EntryPoint then puts the delegate address into the hash instead of calling a factory.

What was observed on C-Chain:

  • Oct 18 (earlier all-versions report): no EIP-7702 initCode marker in any v0.9 operation, and no type-4 outer transaction. The v0.9 sender is an ordinary Safe proxy.
  • v0.8 history (a block-archive query, 2025-01-01 → 2026-10-18): all 258 transactions sent directly to v0.8 are type 2. None is type 4. I did not check v0.8 initCode bytes for the 0x7702 marker. Parquet has no calldata, so that check needs full calldata from the transaction database.

Capability is not usage. "Supports 7702" describes the contract version, not what the operations did.

6. What was actually observed on C-Chain

Oct 18, 2026 (earlier, verified population)

Version Outer txs / UserOps Receipt fees (AVAX) Accounts Notes
v0.6 732 / 733 1.532096008949977859 364 collateral withdrawals, small app calls, scheduled probe, Safe
v0.7 600 / 600 0.842293586804707706 141 ERC1155 transfers, signed daily results, probe, Safe, routes
v0.8 0 / 0 0 0 no UserOperations on Oct 18
v0.9 64 / 64 0.055555465949760675 1 one Safe doing self-calls

Whole-archive check of v0.8 (correction to an earlier note)

epi/constants.py says v0.8 "only sees deposits". That is wrong over the archive's history. It holds only for the days around Oct 18. I scanned the parquet archive from 2025-01-01 (start of the local archive; v0.8 was not released until 26 Mar 2025) through 2026-10-18:

Measure (v0.8 0x4337084d…f108 evidence level: primary source) Value
Transactions sent to it, handleOps 0x765e827f 254 (251 succeeded, 3 reverted)
Transactions sent to it, depositTo 0xb760faf9 4 (6.65 AVAX total)
UserOperationEvent logs it emitted 921
…of which in transactions not sent directly to it (wrappers) 16 transactions
Distinct senders (UserOperationEvent topic2) 79
Distinct paymasters (topic3, including zero) 6
AccountDeployed events 13
First Deposited event block 59,382,539 (2025-03-28)
First handleOps block 60,327,056 (2025-04-15), tx 0x78287250fe1acd17cb32fb9346fe232b8f32fb4add14d675d3618de435ff4812
Last handleOps block 96,876,734 (2026-10-06), tx 0x9e9f348b617410956dfa19c5dfa04ffed56298744a4b9398e44f02d183f90cba explained →
Busiest months by UserOperations 2026-08 (440 ops in 16 txs), 2025-10 (238 ops in 74 txs)
Latest txs before Oct 18 two depositTo on 2026-10-17 (blocks 97,825,378 and 97,825,384), e.g. 0x8043fbc26c66e397b58835eeecef5c5e786ece6530c638caf5d0063f02924288

So v0.8 is real but small: about 921 operations over 18 months, against millions on v0.7. On Oct 18 it had no operations. The deposits that prompted the "deposit-only" note are two 3-AVAX depositTo calls on Oct 17. A deposit funds a sender's or paymaster's balance inside the EntryPoint. It is not a user action. I verified the three tx hashes in the table above by indexed transaction-database hash lookup: each exists, has the expected to, selector and status, and the handleOps ones contain a v0.8 UserOperationEvent.

Update through 2026-10-25 (from epi history; the table above is the hand-verified snapshot to Oct 18): v0.8 reached 273 successful bundles and 927 UserOperations, the last on 2026-10-22. On that day one bundler, 0x4337000c…8680 (also v0.8's busiest bundler), sent 183 handleOps to v0.8 that reverted within about 30 minutes, alongside 67 more to v0.6 and v0.7. That brings v0.8's reverted bundles from 3 to 187.

For scale, in the archive window to Oct 18: v0.7 emitted 4,842,555 UserOperationEvents, v0.6 946,645, v0.9 8,927 (first at block 84,089,763) and v0.8 921. This is a count of events emitted, not a count of distinct users.

v0.9-specific features actually used on Oct 18 (earlier, verified)

  • All 64 hashes use the EIP-712 formula.
  • 19 operations carry the paymaster-signature suffix. The other sponsored operations do not.
  • The account is Safe 0xa1fdd9daed9004f685b6900d5463f72d13dfccca evidence level: cluster with module 0x22939e839e3c0f479b713eaf95e0df128554aead evidence level: primary source. That module address matches Candide's published multichain-signature module. All 64 signatures use Merkle depth zero (single-chain branch).
  • No EIP-7702 marker, no type-4 transaction.

7. Quick version checklist for a decoder

  1. Find the UserOperationEvent emitter. That is the version. Don't infer the version from the selector.
  2. Decode handleOps with the matching struct: 11 fields for v0.6, 9 packed fields otherwise. If to is not an EntryPoint, the call is wrapped. Find the embedded handleOps bytes and say explicitly that the wrapper's own behavior is unknown.
  3. Recompute the userOpHash with the version's formula. Only then join ops to events by topic1, not by position.
  4. Split paymasterAndData at 20 bytes (v0.6) or 20/36/52 bytes (v0.7+). In v0.9, check for the magic suffix.
  5. Treat 7702, block-number validity and "ignored initCode" as v0.8/v0.9 capabilities to test for, not to assume.