Skip to content

Commit da65c96

Browse files
Merge develop into master for v5.17.235
2 parents 2171feb + e2f150d commit da65c96

6 files changed

Lines changed: 231 additions & 1 deletion

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [5.17.235] - 2026-06-28
8+
### Fixed
9+
- blog: publish - Who Called You? EIP-8130's Self-Call Model, TX_CONTEXT, and Phase Atomicity
10+
11+
712
## [5.17.234] - 2026-06-27
813
### Fixed
914
- fix(sync): keep retrying recovery daily instead of giving up at max a… (#1341)
720 KB
Loading
874 KB
Loading

blog/public/llms.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- [One Block Field, Three Hard Problems: Inside EIP-7928's Block-Level Access Lists](https://tryethernal.com/blog/eip-7928-block-level-access-lists): EIP-7928 adds one field to every block header and unlocks parallel execution, deterministic state healing, and executionless sync. Here's how it works.
2020
- [The EVM Has No Subroutines. Here's Why That's a ZK Problem, and the Fix](https://tryethernal.com/blog/eip-7979-evm-subroutines-zk-control-flow): EIP-7979 adds CALLSUB/ENTERSUB/RETURNSUB to the EVM. Three opcodes that cut ZK proof size 50% and solve a quadratic control flow problem from 2015.
2121
- [The Provenance Gap: Why Smart Contracts Can't See NFT Transfer History](https://tryethernal.com/blog/eip-8042-nft-ownership-history): EIP-8042 proposes on-chain ownership history for ERC-721. Here's why event logs break composability and how the three-layer model fixes it.
22+
- [Who Called You? EIP-8130's Self-Call Model, TX_CONTEXT, and Phase Atomicity](https://tryethernal.com/blog/eip-8130-execution-model-self-call-tx-context): EIP-8130 makes msg.sender the actual user, not EntryPoint. Here's what TX_CONTEXT, phase atomicity, and policy gates mean for contract authors.
2223
- [One Signature, Every Chain: EIP-8130's Protocol-Level Account Abstraction](https://tryethernal.com/blog/eip-8130-protocol-level-account-abstraction): EIP-8130 proposes native AA with chainId=0 config sync. No bundlers, no per-chain key rotations. Here's how the mechanism actually works.
2324
- [The Frame Wallet: What EIP-8141 Means for Every EOA](https://tryethernal.com/blog/eip-8141-frame-transactions-eoa-abstraction): EIP-8141 gives unmodified EOAs gas abstraction via default code and a new frame execution model. No bundlers, no migration, no new wallet.
2425
- [The EVM Has No CALL/RET. Here's What It Costs Every L2.](https://tryethernal.com/blog/eip-8173-evm-control-flow-foundations): EIP-8173 makes the historical case for static EVM control flow. Here's what the missing CALL/RET instruction costs ZK provers, L2 clients, and block explorers.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
---
2+
title: "Who Called You? EIP-8130's Self-Call Model, TX_CONTEXT, and Phase Atomicity"
3+
description: "EIP-8130 makes msg.sender the actual user, not EntryPoint. Here's what TX_CONTEXT, phase atomicity, and policy gates mean for contract authors."
4+
date: 2026-06-28
5+
tags:
6+
- Account Abstraction
7+
- EIP-8130
8+
- Ethereum
9+
- Solidity
10+
- Security
11+
keywords: []
12+
image: "/blog/images/eip-8130-execution-model-self-call-tx-context.png"
13+
ogImage: "/blog/images/eip-8130-execution-model-self-call-tx-context-og.png"
14+
status: published
15+
readingTime: 7
16+
---
17+
18+
EIP-8130 restores `msg.sender` to the user's actual address, solving ERC-4337's EntryPoint problem. But contract authors need three new mental models: who pays gas (TX\_CONTEXT), which phase just ran (phase atomicity), and which key signed (actorId). Here's what each one means in practice.
19+
20+
A Solidity developer adds a guard to their vault:
21+
22+
```solidity
23+
require(msg.sender == expectedUser, "unauthorized");
24+
```
25+
26+
On ERC-4337, this check fails for every AA user. The EntryPoint contract is `msg.sender`. The developer wraps it in a bundler-detection branch. The contract ships. Then they deploy on a chain that runs EIP-8130. The check passes: `msg.sender` is the user's actual account address.
27+
28+
Problem solved? Not quite.
29+
30+
In Phase 2 of the same transaction, a DeFi router the user interacted with in Phase 1 calls the vault. `msg.sender` is still the user's account , because EIP-8130 executes all calls as self-calls. The authorization check passes. But the user didn't initiate this call directly, and the vault doesn't know that.
31+
32+
Identity in account abstraction is more complicated than "who is `msg.sender`."
33+
34+
## Why ERC-4337 made msg.sender the wrong address
35+
36+
ERC-4337 had no choice: without a protocol change, every AA transaction routes through a shared EntryPoint contract, making the EntryPoint the `msg.sender` for all AA calls rather than the actual user, breaking every existing identity check.<sup>[4](#fn-4)</sup>
37+
38+
ERC-4337 achieved account abstraction without a protocol change. That constraint had a cost: in every AA transaction, `msg.sender` is the EntryPoint contract (`0x0000000071727De22E5E9d8BAf0edAc6f37da032`), not the user.
39+
40+
Every Solidity pattern that checks `msg.sender == user` silently breaks for AA users. The workaround is explicit and invasive:
41+
42+
```solidity
43+
address constant ENTRY_POINT = 0x0000000071727De22E5E9d8BAf0edAc6f37da032;
44+
45+
function deposit(uint256 amount) external {
46+
address sender;
47+
if (msg.sender == ENTRY_POINT) {
48+
sender = IEntryPoint(ENTRY_POINT).getUserOpSender(); // unwrap
49+
} else {
50+
sender = msg.sender; // direct call
51+
}
52+
balances[sender] += amount;
53+
}
54+
```
55+
56+
Contracts must opt-in to understanding AA. Protocols that skip the unwrapping are silently unusable from AA wallets , the user sees a generic revert with no explanation.
57+
58+
EIP-8130, written with the luxury of a protocol change, takes a different position on what `msg.sender` should be.<sup>[1](#fn-1)</sup>
59+
60+
## The self-call inversion
61+
62+
EIP-8130 fixes the identity problem at the protocol level: all calls execute as self-calls, so `msg.sender` is always the user's account address with no intermediary.<sup>[5](#fn-5)</sup>
63+
64+
As C. Hunter writes in the EIP-8130 specification: "The principal limitation of ERC-4337 is that it cannot modify the `msg.sender` field: the EntryPoint contract becomes the sender for all AA transactions. This proposal resolves the limitation by encoding account identity at the protocol level."<sup>[1](#fn-1)</sup>
65+
66+
In an EIP-8130 AA transaction, all calls execute as self-calls. The identity relationship during execution is:
67+
68+
```
69+
to == msg.sender == tx.origin == sender_address
70+
```
71+
72+
The user's account is the executing context. The protocol sets up the call such that the account is simultaneously the caller and the callee , which is how "the account calls a contract on its own behalf" works at the EVM level.
73+
74+
The consequence for contract authors is direct:
75+
76+
```solidity
77+
// EIP-8130: msg.sender IS the user , no unwrapping needed
78+
function deposit(uint256 amount) external {
79+
balances[msg.sender] += amount;
80+
}
81+
```
82+
83+
No EntryPoint check. No unwrapping. The pattern you'd write for a regular EOA just works for an AA account. Contracts that avoided ERC-4337 because of the `msg.sender` complexity can adopt EIP-8130 chains without auditing every caller check.
84+
85+
This is only possible because EIP-8130 is a protocol change. ERC-4337 had to route through a user-space EntryPoint contract to avoid touching consensus clients. The EntryPoint as `msg.sender` was the price of doing AA without a hard fork. EIP-8130 pays the fork cost and recovers the natural identity model.
86+
87+
The tradeoff: EIP-8130 authentication is bounded. Accounts register verifiers , K1, P256, WebAuthn, delegate , from a defined set.<sup>[2](#fn-2)</sup> There is no arbitrary validation code at the protocol layer (EIP-8141 takes that path instead). But within that constraint, execution is fully programmable.
88+
89+
## But who's paying? The TX_CONTEXT precompile
90+
91+
When sender and gas payer can be different addresses, the `TX_CONTEXT_ADDRESS` precompile gives contracts immutable, per-transaction metadata to tell them apart , per the EIP-8130 spec.
92+
93+
If `msg.sender` is always the user's account, how does a contract distinguish the sender from the gas payer? In EIP-8130, these can be different addresses: a protocol subsidy, a gasless relay, a third-party sponsor.<sup>[3](#fn-3)</sup>
94+
95+
The answer is the `TX_CONTEXT_ADDRESS` precompile. It provides immutable transaction metadata during call execution , not during validation, only during execution:
96+
97+
```solidity
98+
interface ITxContext {
99+
function getTransactionSender() external view returns (address);
100+
function getTransactionPayer() external view returns (address);
101+
function getTransactionSenderActorId() external view returns (bytes32);
102+
}
103+
104+
address constant TX_CONTEXT = /* TX_CONTEXT_ADDRESS from spec */;
105+
```
106+
107+
Three reads, each serving a distinct purpose:
108+
109+
- `getTransactionSender()` , the account that signed the transaction
110+
- `getTransactionPayer()` , the account paying gas (may differ from sender)
111+
- `getTransactionSenderActorId()` , which registered key or device authorized this transaction
112+
113+
Gas cost: 3 gas per 32 bytes of output.<sup>[1](#fn-1)</sup> The protocol holds this data in memory for the transaction lifetime, so population cost is zero.
114+
115+
A vault that only accepts self-funded deposits can now enforce that explicitly:
116+
117+
```solidity
118+
function deposit(uint256 amount) external {
119+
ITxContext ctx = ITxContext(TX_CONTEXT);
120+
address payer = ctx.getTransactionPayer();
121+
bytes32 actorId = ctx.getTransactionSenderActorId();
122+
123+
// Reject sponsored deposits , only sender-funded deposits allowed
124+
require(payer == msg.sender, "no sponsored deposits");
125+
126+
// Record which device signed this deposit for audit
127+
emit DepositWithActor(msg.sender, actorId, amount);
128+
balances[msg.sender] += amount;
129+
}
130+
```
131+
132+
The `actorId` field opens a layer of granularity that ERC-4337 never exposed. An account can register multiple actors , a hardware security key, a browser session key, a scheduled automation bot , each identified by a `bytes32` actorId. `getTransactionSenderActorId()` tells you which one signed the current transaction. This enables per-device audit logs, key-level analytics, and access controls that are meaningful in security postmortems: "the session key signed this withdrawal, not the hardware key."
133+
134+
## Phase atomicity: two levels, different rules
135+
136+
EIP-8130 replaces all-or-nothing revert semantics with per-phase atomicity: each phase commits independently, and a later phase reverting does not undo earlier ones.<sup>[1](#fn-1)</sup>
137+
138+
Standard Ethereum: if anything reverts, everything reverts. The entire state delta rolls back.
139+
140+
EIP-8130 has a different model. Calls are structured as a two-level array:<sup>[1](#fn-1)</sup>
141+
142+
```javascript
143+
// Wallet SDK pseudocode , three-phase transaction
144+
const tx = {
145+
calls: [
146+
// Phase 0: approve tokens (atomic unit)
147+
[{ to: TOKEN, data: approve(BRIDGE, amount) }],
148+
// Phase 1: bridge to L2 (persists even if Phase 2 fails)
149+
[{ to: BRIDGE, data: bridgeTokens(amount, destChain) }],
150+
// Phase 2: stake on destination (can fail independently)
151+
[{ to: YIELD, data: stake(amount) }],
152+
]
153+
};
154+
```
155+
156+
Within a phase, all calls in the inner array succeed or the entire phase reverts, rolling back that phase's state changes. Between phases, completed state changes survive even if a later phase reverts.
157+
158+
Walk through the example: Phase 0 approves. Phase 1 bridges. Phase 2 tries to stake, but the yield protocol is paused , revert. The user's tokens are on L2, not staked. Phase 0 and Phase 1 are permanent. This is not a failed transaction in any traditional sense.
159+
160+
This is a designed feature, not an oversight. Long multi-step operations don't need a single massive revert surface. A partial failure in Phase 3 doesn't undo Phase 0 and Phase 1, which may have required independent coordination to complete.
161+
162+
But it changes the safety assumptions DeFi protocols can make. "If my function is executing, all the setup is complete" is not necessarily true in a multi-phase world. A contract that expects tokens to be approved and bridged atomically may be called with the bridge step complete but a dependent step from a different phase having failed.
163+
164+
For auditors, each phase is its own atomicity domain. Cross-phase invariants require explicit checks, not implicit revert-on-failure assumptions. A contract that reads state written by Phase 0 during Phase 2 execution is implicitly depending on cross-phase ordering , and needs to verify that the expected Phase 1 state is present, not assume it.
165+
166+
The receipt format surfaces this directly. EIP-8130 adds a `phaseStatuses` array to the transaction receipt , one status entry per phase. A three-phase transaction where Phase 2 fails shows `[success, success, reverted]`. A block explorer that renders "reverted" for this transaction is wrong about what happened. Each phase needs its own status badge.
167+
168+
## Policy gates: constraining actors to one target
169+
170+
Policy gates provide protocol-level enforcement that a given actor can only call one specific contract , no smart contract audit required to verify the restriction is in place.<sup>[1](#fn-1)</sup>
171+
172+
Scope bits in `ActorConfig` control what an actor can do: send transactions, pay gas, modify configuration, sign messages. But scope doesn't limit which contracts an actor can interact with.
173+
174+
Policy gates do. When an actor's `policyType != 0x00`, its configuration requires two additional slots: a `policy_commitment` and a `policy_manager` address.<sup>[1](#fn-1)</sup> That actor can only call `to == policy_manager`. Any other target triggers `ActorPolicyViolation(actorId, to)` and the transaction fails at the protocol level.
175+
176+
A session key for a gaming dApp: `scope = SENDER`, `policy_manager = GAME_CONTRACT`. The session key can transact from the account but only with the game contract. It cannot drain tokens to an arbitrary address. The constraint is enforced by the protocol. No smart contract audit required to verify the restriction is in place.
177+
178+
A DCA automation bot: `scope = SENDER`, `policy_manager = DEX_CONTRACT`. The bot can send transactions from the account, but only to the designated DEX. Scope says what it can do. Policy says where.
179+
180+
Combined, they describe a minimal-privilege actor with meaningful protocol-level enforcement. A session key with `scope = SENDER` and a policy locked to one DEX is categorically safer than a session key with unrestricted target access, and that safety does not depend on the called contract implementing any checks.
181+
182+
`ActorPolicyViolation` events matter for block explorers. When an AA transaction fails with a policy violation, the revert trace alone doesn't explain why , the constraint lives in the account configuration, not in the callee contract. The event provides the full picture: which actor, which attempted target, why the protocol rejected it.
183+
184+
## What changes in practice
185+
186+
### For contract authors on EIP-8130 chains
187+
188+
- Use `msg.sender` for identity. It's the real user address , no EntryPoint unwrapping.
189+
- Use `TX_CONTEXT.getTransactionPayer()` for gas attribution. Sender and payer may differ.
190+
- Use `TX_CONTEXT.getTransactionSenderActorId()` when the signing key matters , hardware vs. session key, scheduled automation vs. manual approval.
191+
- Don't assume cross-phase atomicity. If your protocol's invariants depend on earlier steps having completed, check for that state explicitly.
192+
193+
**For block explorers:**
194+
195+
- Parse `phaseStatuses[]` and render per-phase outcomes. A single success/failure flag is wrong for multi-phase transactions.
196+
- Display `payer` and `sender` as distinct fields when they differ , this is the difference between "who submitted" and "who paid" for every sponsored transaction.
197+
- Index `ActorAuthorized`, `ActorRevoked`, and `ActorPolicyViolation` events. These are the accountability trail for AA accounts.
198+
- Show `actorId` in the trace view, mapped to the authenticator type. A withdrawal signed by a session key looks identical to one signed by a hardware key in the call trace , the actorId distinguishes them.
199+
200+
Ethernal's decoded event view and transaction trace are built to surface exactly this kind of execution detail. AA-aware rendering , per-phase status, payer vs. sender separation, actor resolution , is the natural evolution of what's already there.
201+
202+
## The gap between msg.sender and the full picture
203+
204+
The self-call model delivers on `msg.sender == user`. The remaining complexity , who pays, which key signed, which phases committed , is now explicit in TX_CONTEXT and the phase model, where it is inspectable and protocol-enforced.
205+
206+
Return to the opening scenario. On EIP-8130, `require(msg.sender == expectedUser)` passes , the self-call model delivers on that. The ERC-4337 bundler problem is gone.
207+
208+
But `msg.sender` alone doesn't tell you: was this a hardware key or a session key? Is a sponsor paying gas, and does your protocol care? Did earlier phases complete successfully, and does your invariant depend on them?
209+
210+
EIP-8130's execution model is cleaner than ERC-4337's for contract authors who don't want to think about bundlers. The complexity hasn't been eliminated , it's been redistributed into TX_CONTEXT and the phase model, where it's at least explicit, inspectable, and protocol-enforced rather than buried in bundler infrastructure.
211+
212+
---
213+
214+
## References
215+
216+
<span id="fn-1">1.</span> Hunter, C. (@chunter-cb). "EIP-8130: Account Abstraction by Account Configurations." _Ethereum Improvement Proposals_, 2026. [https://eips.ethereum.org/EIPS/eip-8130](https://eips.ethereum.org/EIPS/eip-8130)
217+
218+
<span id="fn-2">2.</span> Hunter, C. (@chunter-cb). "Update EIP-8130: Clear up naming and k1 behaviour." _GitHub EIPs_, March 7, 2026. [https://github.com/ethereum/EIPs/pull/11382](https://github.com/ethereum/EIPs/pull/11382)
219+
220+
<span id="fn-3">3.</span> Hunter, C. (@chunter-cb). "Update EIP-8130: Enable permissionless payer." _GitHub EIPs_, March 9, 2026. [https://github.com/ethereum/EIPs/pull/11388](https://github.com/ethereum/EIPs/pull/11388)
221+
222+
<span id="fn-4">4.</span> Biconomy. "Native Account Abstraction: State of Art and Pending Proposals Q1/26." _blog.biconomy.io_, 2026. [https://blog.biconomy.io/native-account-abstraction-state-of-art-and-pending-proposals-q1-26/](https://blog.biconomy.io/native-account-abstraction-state-of-art-and-pending-proposals-q1-26/)
223+
224+
<span id="fn-5">5.</span> Base. "EIP-8130 Reference Implementation." _GitHub_, 2026. [https://github.com/base/eip-8130](https://github.com/base/eip-8130)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ethernal",
3-
"version": "5.17.234",
3+
"version": "5.17.235",
44
"private": true,
55
"type": "module",
66
"scripts": {

0 commit comments

Comments
 (0)