Loading prices…

How to Read a Smart-Contract Wallet Account and Its Sessions

A smart-account transaction is not one transaction. It is a UserOperation that a shared EntryPoint routes, a bundler submits, and a paymaster may sponsor. Learn how to read each field before approving.

How to Read a Smart-Contract Wallet Account and Its Sessions

Why a smart account is a different mental model

If you have ever copied an address, checked it on a block explorer, and tried to decode what a transaction did, you are starting from the assumption that one key controls one account. Smart-contract wallets break that assumption. The address you see on Etherscan or a similar explorer is not a key. It is a contract. The contract owns assets, enforces rules, and decides which instructions to accept. The user, more accurately the set of keys or sessions currently allowed to act, sends instructions to that contract through a separate pipeline.

This is why a single on-chain action often looks noisy on a block explorer. What appears as one transaction is, internally, several layers: the user's signed intent, a relayer that submits it, an EntryPoint contract that validates it, optional paymaster logic that pays gas, and the callData that runs the actual action inside the smart account. The shape on chain is unfamiliar at first, and that unfamiliarity is exactly what attackers rely on.

Transaction versus UserOperation: the core distinction

A normal Ethereum transaction has six readable fields: from, to, value, data, gas limit, and signature. A UserOperation, defined under the ERC-4337 standard, adds fields designed for delegated and sponsored execution. The ones that matter most for reading are: sender, which is the smart-account address; nonce, which is per-account and not tied to the global transaction count the way an EOA nonce is; initCode, which deploys the account on first use; callData, which is what the smart account will run if approved; and signature, which may be an ECDSA signature, an aggregated BLS signature from a bundler, or a validator hook defined by the account itself.

There is also a gas-payments overlay absent in plain transactions. paymasterAndData points to a paymaster contract that will reimburse the bundler. The paymaster is allowed to inspect your UserOperation before agreeing to sponsor it, and in some implementations it can alter the postOp step, which runs after your call completes. That post-op callback is a real attack surface: a malicious paymaster can read the return data of your call and use it in ways you did not authorize. Treat paymasters the way you treat any counterparty, because in a strict sense that is what they are.

Reading a UserOperation therefore means checking at minimum four things: that sender points to a contract you actually own or expect, that callData targets addresses you authorized (a session or a guardian-approved whitelist), that the gas parameters are not absurdly high, and that the paymaster is one you trust to behave.

The EntryPoint contract: shared infrastructure risk

Every ERC-4337 smart account on Ethereum mainnet shares a single EntryPoint address at the time of writing. The standard explicitly permits multiple EntryPoint versions, but in practice one canonical deployment carries most activity. This centralization is a deliberate design choice: a shared EntryPoint lets bundlers, paymasters, and account implementations interoperate without per-pair integrations. It also concentrates risk.

If the EntryPoint is upgraded, taken over by a governance vote, or found to contain a vulnerability, every smart account routed through it is exposed at once. That is not a hypothetical. In late 2023, an audit-by-auditor incident around an EntryPoint-related library was disclosed and patched across integrations. Nothing was lost at scale, but the lesson held: shared infrastructure is a single point of failure, and your account's safety is only as strong as the weakest component in that stack.

Reading on-chain data should therefore include a quick check that the EntryPoint your account routes through is the version you expect. Mainnet and most L2s publish canonical EntryPoint addresses, and tools such as Etherscan label the audited deployments. If you see a UserOperation being sent to a non-canonical EntryPoint, treat that as a strong warning sign: it could be a phishing relayer or a fork designed to capture signatures.

Session keys: scoped, time-limited sub-approvals

Sessions are the feature most worth understanding, because they are also the part most often abused. A session key is a secondary signer you grant to a specific application for a defined window of time, with a defined set of permissions. In practice this looks like a dApp asking for permission to trade on Uniswap on your behalf for the next hour, up to a spending cap, against an allowlist of token contracts.

On-chain, a session lives as state inside your smart account. The relevant fields, in the order they tend to appear in storage, are: an authorized session key address; an expiry timestamp or block number; a spend limit per period (often daily or per-epoch); a target allowlist or selector allowlist; and a policy hook that may apply additional rules. Implementations vary. Safe Modules, Zodiac Heads, Biconomy sessions, and Kernel session validators all encode these slightly differently, and the exact slot layout matters when you read storage directly.

The point to remember is that sessions are bounded but not safe by default. A session scoped to Uniswap's router is only as good as Uniswap's router. A session that grants an allowlist of selectors can still hit dangerous ones if the application knows which selectors to call. And sessions that do not enforce a period-reset spend limit can drain funds over time once the user stops paying attention.

Two practical habits help. First, read the exact storage slot or decoded view that the wallet UI shows before you sign the session grant, and treat it as the source of truth, not the dApp's marketing copy. Second, prefer sessions with both a per-period cap and a per-call cap, so a single runaway call cannot exceed a sane maximum.

Reading a single UserOperation in detail

Imagine you see a hash 0xabc... in your wallet and want to know what it actually did. Open the transaction on a block explorer, then click through to the EntryPoint transaction whose callData starts with handleOps. The internal transactions tab becomes the real story.

Step 1: validateUserOp

The EntryPoint first calls back into your account's validateUserOp function. This is where the signature is checked, nonces are validated against replay protection, and any session policies are enforced. A failed validation reverts the whole bundle, so a successful run is evidence that at least one valid signer approved the operation.

Step 2: execute

If validation passes, the EntryPoint calls your account's execute function with the callData you provided. This is the user-visible action: a swap, a transfer, a contract interaction. Reading the callData of this internal call gives you the actual target, value, and function selector the user wanted.

Step 3: postOp

After execution, the paymaster's postOp runs, which is used for things like settling gas debts, refunding unused gas, or paying the bundler. Any state change here was authorized by your paymaster choice, not by you directly. Anything weird that happens here, such as extra token transfers or fresh approvals, is a sign to investigate further.

Putting those three layers together tells you who authorized what, what ran, and what side effects occurred after the fact. Most phishing patterns become visible at this level once you know what to look for.

Paymasters and bundlers: visibility and trust

A bundler is the party that takes your UserOperation, packages it with others, and submits the actual on-chain transaction to the EntryPoint. Because the bundler is the one calling EntryPoint.handleOps, the bundler's address is what pays gas at the chain level. You, as the smart-account owner, never need ETH to pay gas if a paymaster is willing to sponsor. That is convenient, and it is also a trust relationship.

A paymaster's commitment is encoded on-chain in two ways: the stake it has deposited with the EntryPoint, which can be slashed for misbehavior under the standard's rules, and the policy of its validatePaymasterUserOp function, which decides whether to sponsor. Slashing exists, but it is a coarse backstop, not a fine-grained guarantee. A paymaster can still observe, censor, or front-run the operations it sees.

Front-running is the most under-discussed risk. A paymaster that sees your swap before it executes can copy the trade to its own address, run it on the same block, and capture the price improvement you expected. This is not theoretical; it is the same dynamic as miner-extractable value, repackaged for the account-abstraction world. Mitigations exist (private mempools, commit-reveal schemes, signed-intent layers), but they require active use. Default public bundlers and paymasters offer no such protection.

Reading on-chain data helps here too. If you consistently see your operations land in the same slot number or just after a specific address, that bundler or paymaster is observing you closely. Switching to a private mempool, or using paymasters that explicitly commit to non-front-running policies and have a verifiable track record, is a real upgrade in safety.

Revoking sessions and permissions on-chain

The most common mistake smart-account users make is treating a session as if revoking it in the dApp's UI is enough. It usually is not. The dApp's UI may simply forget about the session while the on-chain grant remains active until expiry. Until the smart-account contract enforces an inactivity timeout, or until you actively revoke, the session key can still sign operations that the EntryPoint will accept.

True revocation has three practical forms. The first is to call the smart account's revoke function, if one exists, with the session key's identifier. The second is to rotate the account's main signer, which invalidates any session whose authority was tied to the signer. The third is to let the session expire naturally, which works only if you set a short enough window in the first place.

Reading on-chain revocation means checking two things: the session storage slot, which should reflect the revoked or expired state, and the account's nonce sequence, which should no longer be advancing for the revoked key. If you see a session key still signing operations after a supposed revocation, that is the moment to pause and investigate. It could be a stale UI, or it could be a session that was never properly scoped and is now exploited.

How to follow smart-account activity the smart way

Smart-account activity moves fast, and the on-chain trail only makes sense once you understand the layers underneath. Tracking who routes your operations, which paymasters sponsor them, and what sessions are still live is a losing game if you do it by hand. Zippfeed surfaces wallet and protocol headlines with sentiment scoring (bullish, neutral, or bearish) and an importance rating, so you can spot the patterns behind the noise before your next signature.

Frequently asked questions

Is a smart-contract wallet safer than a regular crypto wallet?
It can be, but it depends on the implementation and your habits. A smart account lets you set daily limits, require multiple signatures, rotate keys, and recover access without seed phrases. Those features help. The new risks are shared EntryPoint infrastructure, paymaster trust, and sessions that do not get revoked properly. A misused session key can drain funds just as fast as a leaked seed phrase, so the safety is in how you configure and audit, not in the technology alone. This is education, not financial advice.
What is a UserOperation in simple terms?
A UserOperation is a pseudo-transaction designed for smart accounts under ERC-4337. It carries a sender, nonce, initCode, callData, signature, and paymaster data, and it is submitted to a shared EntryPoint contract rather than executed directly. Think of it as a sealed instruction that the EntryPoint validates and runs on your account's behalf, optionally with gas paid by a paymaster.
Should I approve every session-key request a dApp makes?
Treat session keys the way you treat an API key in your work tooling. Grant the smallest possible scope, the shortest reasonable expiry, and a clear per-call and per-period spend cap. Read the storage that the wallet UI shows you before signing, and revoke sessions as soon as you stop using the dApp. There is no reason to accept broad, long-lived sessions for routine interactions.
How do I revoke a smart-wallet session on-chain?
Call the account's revoke function if it has one, or rotate the primary signer so any dependent sessions become invalid. Letting a session expire naturally is only effective if the timeout is short. Always verify on-chain afterwards by checking the session storage slot in a block explorer; a dApp UI forgetting the session does not mean the on-chain grant is gone.