Opcode Summary

PropertyValue
Opcode0x32
MnemonicORIGIN
Gas2
Stack Input(none)
Stack Outputtx.origin (address)
BehaviorPushes the 160-bit address of the externally owned account (EOA) that originally signed and submitted the transaction. This value is constant for the entire transaction, regardless of how many contract-to-contract calls occur in the call chain.

Threat Surface

ORIGIN is one of the most dangerous opcodes for access control in the EVM. Unlike CALLER (0x33), which returns the immediate caller and changes at every call boundary, ORIGIN always returns the original EOA that signed the transaction. This property makes it inherently unsuitable for authorization.

The core threat: any contract in the call chain can read tx.origin and it will always resolve to the same EOA. If a contract uses tx.origin == owner as an access control check, then any intermediary contract that the owner interacts with can call that contract and pass the check. This enables a devastating class of phishing attacks where an attacker deploys a malicious contract, tricks the victim into calling it (e.g., by disguising it as a legitimate DApp, airdrop claim, or token approval), and the malicious contract then calls the target contract with the victim’s tx.origin.

The vulnerability is classified as SWC-115 (Authorization through tx.origin) in the Smart Contract Weakness Classification registry and SCWE-018 in the OWASP Smart Contract Security framework. The Solidity documentation itself explicitly warns against using tx.origin for authorization.

Furthermore, the ongoing evolution of Ethereum’s account model — particularly EIP-4337 (Account Abstraction) and ERC-2771 (Meta-Transactions) — fundamentally alters the semantics of tx.origin, breaking contracts that rely on assumptions about its value.


Smart Contract Threats

T1: tx.origin Phishing Attack (Critical)

The canonical ORIGIN vulnerability. When a contract protects a privileged function with require(tx.origin == owner), any contract that the owner interacts with can invoke that function on the owner’s behalf.

Attack flow:

  1. Victim deploys a contract (e.g., a wallet) that uses tx.origin == owner for access control
  2. Attacker deploys a phishing contract disguised as a legitimate service (DEX router, airdrop claim, NFT mint)
  3. Attacker tricks the victim into sending a transaction to the phishing contract (social engineering, malicious link, fake UI)
  4. The phishing contract’s receive(), fallback(), or a bait function calls the victim’s contract, invoking a privileged operation (e.g., transferTo(attacker, address(this).balance))
  5. The victim’s contract checks tx.origin == owner — this passes because the victim’s EOA initiated the transaction
  6. Funds are transferred to the attacker

The attack succeeds because tx.origin pierces through the entire call chain. The victim never directly called their own contract, but tx.origin makes it look like they did.

T2: tx.origin != msg.sender in Multi-Contract Architectures (High)

In any architecture where contracts call other contracts (routers, proxies, aggregators, DAOs), tx.origin and msg.sender diverge. Contracts that use tx.origin for authorization will accept calls from any contract in a transaction initiated by an authorized EOA, even if the intermediate contract is malicious or compromised.

Common vulnerable patterns:

  • DeFi routers/aggregators: A user interacts with a DEX aggregator that routes through multiple pools. Any pool contract could call back to a tx.origin-protected contract.
  • Callback-based protocols: Flash loan receivers, Uniswap V3 swap callbacks, and similar patterns involve the protocol calling back into user-supplied code. If the callback contract calls a tx.origin-protected function, the check passes.
  • Governance/DAO execution: When a multisig or DAO executes a proposal, tx.origin is the EOA that submitted the execution transaction, not the DAO itself.

T3: Account Abstraction (EIP-4337) Breaking tx.origin Assumptions (High)

EIP-4337 introduces smart contract wallets as first-class accounts. Under this model:

  • Users submit UserOperation objects to a separate mempool
  • Bundlers (EOAs) package multiple UserOperations into a single transaction and submit it to the EntryPoint contract
  • tx.origin resolves to the bundler’s EOA, not the user’s account

This means:

  • tx.origin no longer identifies the user who authorized the operation
  • require(tx.origin == owner) will always fail for EIP-4337 wallet users because the bundler is the transaction originator
  • The common guard require(tx.origin == msg.sender) (used to ensure the caller is an EOA, not a contract) will fail for all smart contract wallet users, effectively locking them out
  • Contracts that use tx.origin for any purpose will be incompatible with the account abstraction ecosystem

As EIP-4337 adoption grows, tx.origin-dependent contracts become progressively more broken.

T4: tx.origin-Based Access Control Bypass via Legitimate Interactions (Medium)

Even without a dedicated phishing contract, tx.origin checks can be bypassed through legitimate protocol interactions. If an authorized user interacts with any contract that has a callback mechanism, re-entrancy path, or hook system, that contract can call back to the tx.origin-protected contract.

Examples:

  • ERC-777 token hooks: Receiving ERC-777 tokens triggers a tokensReceived hook on the recipient. If the recipient contract calls a tx.origin-protected function during this hook, the check passes with the sender’s tx.origin.
  • ERC-1155 onERC1155Received: Similar callback-based attack surface.
  • Flash loan callbacks: The borrower’s callback executes in the context of the original transaction, inheriting tx.origin.

Protocol-Level Threats

P1: Account Abstraction Paradigm Shift (High)

EIP-4337 fundamentally redefines the relationship between transaction signers and tx.origin. At the protocol level:

  • The EntryPoint contract (a singleton deployed at a canonical address) executes all account-abstracted transactions. tx.origin is the bundler, a third party with no relationship to the user.
  • Future EIPs (e.g., EIP-3074 AUTH/AUTHCALL, EIP-7702) further decouple authorization from tx.origin by allowing EOAs to delegate execution to invoker contracts.
  • Any protocol that reads tx.origin for analytics, access control, or fee logic will produce incorrect results for an increasing fraction of users.

The Ethereum roadmap explicitly moves toward a world where tx.origin is meaningless for identifying the authorizing party.

P2: Meta-Transaction and Relayer Implications (Medium)

ERC-2771 (Secure Protocol for Native Meta Transactions) enables gasless transactions by having a trusted forwarder relay user-signed messages. In this model:

  • tx.origin is the relayer’s EOA, not the user
  • The user’s identity is extracted from appended calldata via _msgSender(), not from tx.origin
  • Contracts using tx.origin will identify the relayer (e.g., OpenZeppelin Defender, Gelato, Biconomy) as the transaction originator

In December 2023, a critical vulnerability was discovered in contracts combining ERC-2771 with Multicall. The delegatecall in Multicall allowed attackers to manipulate the appended sender data, enabling arbitrary address spoofing. This affected over 9,800 contracts across 37 chains and resulted in documented losses including 84.59 ETH and 17,394 USDC. While this vulnerability was in the _msgSender() extraction logic rather than tx.origin directly, it illustrates the dangers of transaction-origin abstractions.

P3: Cross-Chain and L2 Implications (Low)

On L2 rollups with sequencers or cross-chain bridges:

  • tx.origin on an L2 may refer to the sequencer or bridge contract, not the original user
  • Cross-chain message passing (e.g., L1 → L2 via canonical bridges) sets tx.origin to the bridge executor
  • Contracts deployed across multiple chains that use tx.origin will behave inconsistently

Edge Cases

Edge CaseBehaviorSecurity Implication
tx.origin == msg.senderTrue only when the caller is the EOA that signed the transaction (no intermediate contracts)Commonly used as an “is EOA” check; breaks under EIP-4337 account abstraction
tx.origin in delegatecallReturns the same value as in the calling context — the original EOACannot be used to distinguish between direct and delegated calls
tx.origin with contract wallets (Gnosis Safe, etc.)Returns the EOA that submitted the Safe transaction to the chain, not the Safe itselfMulti-sig owners rotating submission duty means tx.origin changes unpredictably
tx.origin in a staticcallSame as in the calling context; staticcall does not alter tx.originRead-only calls still expose the original EOA to called contracts
tx.origin in constructorReturns the deployer EOAStoring tx.origin as owner during construction works but has the same phishing risk post-deployment
tx.origin with EIP-4337Returns the bundler’s EOA, not the userAll tx.origin-based authorization fails for smart wallet users
tx.origin with meta-transactions (ERC-2771)Returns the relayer’s EOA, not the usertx.origin is useless for identifying the user in gasless transaction flows
Multiple contracts reading tx.origin in one txAll see the same valueAny contract in the call chain can impersonate the originator to tx.origin-protected contracts

Real-World Exploits

Exploit 1: ERC-2771 + Multicall Address Spoofing — 9,800+ Contracts Affected (December 2023)

Root cause: Interaction between ERC-2771 meta-transaction context and Multicall’s delegatecall pattern allowed arbitrary sender spoofing.

Details: ERC-2771 appends the original sender’s address as the last 20 bytes of calldata, and _msgSender() extracts it using assembly. When combined with Multicall (which uses delegatecall(data) for each sub-call), an attacker could craft calldata where each sub-call’s last 20 bytes contained an arbitrary address, spoofing _msgSender() to impersonate any user.

Attack flow:

  1. Attacker constructs a forwarded request to a contract implementing both ERC-2771 and Multicall
  2. The outer request is properly signed by the attacker’s address
  3. Inside the multicall array, each sub-call is padded so its last 20 bytes are the victim’s address
  4. Each delegatecall within Multicall resolves _msgSender() to the victim
  5. Attacker executes privileged functions as the victim

Impact: Over 9,800 contracts across 37 chains were affected. Documented on-chain losses: 84.59 ETH, 17,394 USDC, and additional smaller amounts on Ethereum and Polygon. Thirdweb issued an emergency advisory. OpenZeppelin published patches and mitigation guidance.

Relevance to ORIGIN: While this exploit targeted _msgSender() rather than tx.origin directly, it demonstrates the fundamental fragility of transaction-origin-based identity. The ERC-2771 pattern exists precisely because tx.origin is inadequate, yet the replacement mechanism introduced its own spoofing vulnerability. Any system that tries to trace “who really initiated this” through layers of abstraction is inherently fragile.

References:


Exploit 2: Ethernaut Level 4 “Telephone” — Canonical tx.origin CTF Challenge

Root cause: Direct use of tx.origin for ownership transfer authorization.

Details: The Ethernaut “Telephone” challenge presents a contract where the changeOwner() function checks if (tx.origin != msg.sender) and, if true, sets the caller as the new owner. This is a teaching exercise, but it precisely models real vulnerable contracts.

function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
        owner = _owner;
    }
}

Attack: Deploy an intermediary contract that calls changeOwner(attacker). When the attacker calls the intermediary, tx.origin (the attacker’s EOA) differs from msg.sender (the intermediary contract), so the condition passes and ownership is transferred.

Relevance: This pattern has been found in production contracts during audits. The Ethernaut challenge has been completed by hundreds of thousands of developers, demonstrating the accessibility of the attack.

References:


Exploit 3: Audit Findings in Production DeFi Protocols

Root cause: Persistent use of tx.origin for authorization in production DeFi code.

Details: Security audit firms (Trail of Bits, OpenZeppelin, Consensys Diligence) have repeatedly flagged tx.origin usage in production contracts:

  • Lending protocol liquidation managers using tx.origin to authorize liquidator bots, allowing any contract the bot interacts with to trigger unauthorized liquidations
  • Token contracts using tx.origin == deployer for mint/pause functions, enabling phishing-based token supply manipulation
  • Governance contracts using tx.origin to identify voters, misattributing votes when proposals are executed through intermediate contracts

The OWASP Smart Contract Security Verification Standard (SCSVS) classifies tx.origin authorization as SCWE-018 and SWC Registry classifies it as SWC-115, both marking it as a critical vulnerability.

References:


Attack Scenarios

Scenario A: Classic Wallet Drain via Phishing Contract

// Vulnerable wallet contract
contract VulnerableWallet {
    address public owner;
 
    constructor() {
        owner = msg.sender;
    }
 
    function transferTo(address payable to, uint256 amount) public {
        require(tx.origin == owner, "Not owner");
        to.transfer(amount);
    }
 
    receive() external payable {}
}
 
// Attacker's phishing contract
contract PhishingAttack {
    VulnerableWallet immutable target;
    address payable immutable attacker;
 
    constructor(VulnerableWallet _target) {
        target = _target;
        attacker = payable(msg.sender);
    }
 
    // Disguised as an airdrop claim, NFT mint, or token approval.
    // When the wallet owner calls this (or sends ETH), funds are drained.
    receive() external payable {
        target.transferTo(attacker, address(target).balance);
    }
}

Attack: Attacker sends the wallet owner a link to “claim free tokens” that triggers a transaction to the PhishingAttack contract. The receive() function calls transferTo() on the victim’s wallet. Since tx.origin is the owner’s EOA, the require passes and all funds are sent to the attacker.

Scenario B: Flash Loan Callback Exploitation

contract VulnerableLendingPool {
    mapping(address => bool) public authorizedLiquidators;
 
    function liquidate(address borrower) external {
        require(tx.origin == msg.sender, "No contracts");
        require(authorizedLiquidators[tx.origin], "Not authorized");
        // ... perform liquidation ...
    }
}
 
// Attacker tricks authorized liquidator into calling this
contract MaliciousFlashLoanReceiver {
    VulnerableLendingPool pool;
 
    function executeOperation(uint256 amount, uint256 fee, bytes calldata) external {
        // Called during a flash loan callback -- tx.origin is the
        // authorized liquidator who initiated the flash loan
        pool.liquidate(targetBorrower);
    }
}

Attack: The authorized liquidator bot takes a flash loan for a legitimate purpose. The flash loan callback routes through the attacker’s contract, which calls liquidate(). Both checks pass: tx.origin == msg.sender is false (so this specific example would revert), but if the contract only checked authorizedLiquidators[tx.origin], the call succeeds because tx.origin is the authorized bot.

Scenario C: EIP-4337 Compatibility Failure

contract LegacyVault {
    modifier onlyEOA() {
        require(tx.origin == msg.sender, "No contracts allowed");
        _;
    }
 
    function withdraw(uint256 amount) external onlyEOA {
        // Users with EIP-4337 smart wallets can NEVER call this function.
        // tx.origin = bundler EOA, msg.sender = smart wallet contract.
        // The require always fails for smart wallet users.
        payable(msg.sender).transfer(amount);
    }
}

Impact: As EIP-4337 adoption increases, an increasing fraction of users are permanently locked out of the contract. Their funds are trapped.


Mitigations

ThreatMitigationImplementation
T1: tx.origin phishingNever use tx.origin for authorizationReplace all require(tx.origin == owner) with require(msg.sender == owner)
T1: Phishing attack surfaceUse msg.sender exclusively for access controlOpenZeppelin Ownable, AccessControl contracts use msg.sender by design
T2: Multi-contract interactionsDesign access control for composabilityAssume msg.sender may be a contract; use role-based access (RBAC)
T3: EIP-4337 compatibilityRemove tx.origin == msg.sender EOA checksUse ERC-1271 isValidSignature() for signature validation from smart wallets
T3: Account abstractionAvoid tx.origin entirelyFuture-proof contracts by never reading tx.origin
T4: Callback exploitationImplement reentrancy guards and explicit call-origin checksUse OpenZeppelin ReentrancyGuard; validate msg.sender not tx.origin
P1: Protocol evolutionDesign for a world where tx.origin is meaninglessUse ERC-4337’s UserOperation signer validation or ERC-1271
P2: Meta-transactionsUse _msgSender() from ERC-2771, not tx.originOpenZeppelin ERC2771Context; but audit for Multicall interaction bugs

Static Analysis Detection

All major Solidity analysis tools flag tx.origin usage:

  • Slither: Detector tx-origin flags any use of tx.origin in conditions
  • Mythril: Detects tx.origin-based authorization patterns
  • Semgrep: Rule solidity.security.tx-origin-usage catches tx.origin in require/if statements
  • Solhint: Rule avoid-tx-origin warns on any tx.origin reference

The Only Safe Uses of tx.origin

tx.origin has very few legitimate uses:

  1. Preventing contracts from calling a function (EOA-only gate): require(tx.origin == msg.sender) — but this breaks EIP-4337 compatibility and is increasingly considered an anti-pattern
  2. Gas refund attribution: Some protocols read tx.origin to refund gas to the transaction submitter, which is valid because the submitter is the one who paid gas
  3. Analytics/logging: Emitting tx.origin in events for off-chain analysis (not for on-chain logic)

Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart ContractCriticalHighSWC-115; ubiquitous in audit findings
T2Smart ContractHighHighDeFi composability makes this pervasive
T3Smart ContractHighMedium (growing)EIP-4337 adoption increasing
T4Smart ContractMediumMediumERC-777 hooks, flash loan callbacks
P1ProtocolHighHighEIP-4337, EIP-3074, EIP-7702 roadmap
P2ProtocolMediumMediumERC-2771/Multicall exploit (Dec 2023)
P3ProtocolLowLowL2 sequencer edge cases

OpcodeRelationship
CALLER (0x33)Returns msg.sender — the immediate caller. This is the correct alternative to ORIGIN for access control. Changes at every call boundary.
ADDRESS (0x30)Returns the current contract’s address. Combined with CALLER, enables proper access control without ORIGIN.
DELEGATECALL (0xF4)Executes code in the context of the calling contract. tx.origin is preserved through delegatecall, meaning delegated code sees the same ORIGIN — relevant for proxy patterns.
STATICCALL (0xFA)Read-only external call. tx.origin is still readable in static calls, leaking the originator’s address to untrusted view functions.
CALL (0xF1)Standard external call. Each CALL changes msg.sender but preserves tx.origin, which is the root cause of the ORIGIN phishing vulnerability.