# tx.origin vs msg.sender — Why It Matters for Security
One of the most common security mistakes in Solidity is using tx.origin for authentication. This seemingly innocent choice can expose your contract to phishing attacks that drain user funds.
In this article, we'll clarify the difference between tx.origin and msg.sender, demonstrate the vulnerability, and establish when to use each.
The Difference
msg.sender
The immediate caller of the current function.
- If a user calls your contract directly:
msg.sender= user's address
- If contract A calls contract B:
msg.sender= contract A's address
- Changes at each call in the call chain
tx.origin
The original external account that initiated the transaction.
- Always the EOA (Externally Owned Account) that signed the transaction
- Never a contract address
- Remains constant throughout the entire call chain
Visual Example
User (0xUser) → Contract A → Contract B → Contract C
| | |
msg.sender: 0xUser A B
tx.origin: 0xUser 0xUser 0xUser
At Contract C:
msg.sender= Contract B's address
tx.origin= 0xUser (the original EOA)
The Vulnerability: Phishing Attack
Vulnerable Contract
contract VulnerableWallet {
address public owner;
constructor() {
owner = msg.sender;
}
// VULNERABLE: Using tx.origin for authentication
function transfer(address payable to, uint256 amount) public {
require(tx.origin == owner, "Not owner");
to.transfer(amount);
}
receive() external payable {}
}
The Attack
An attacker creates a malicious contract:
contract MaliciousContract {
address payable public attacker;
VulnerableWallet public wallet;
constructor(address _wallet) {
attacker = payable(msg.sender);
wallet = VulnerableWallet(payable(_wallet));
}
// Innocent-looking function
function claimAirdrop() public {
// Calls victim's wallet
wallet.transfer(attacker, address(wallet).balance);
}
}
Attack Flow
MaliciousContractclaimAirdrop() (via phishing)MaliciousContract.claimAirdrop()VulnerableWallet.transfer() - tx.origin = victim's address ✅ (check passes!)
- msg.sender = MaliciousContract's address
Why it works: The victim initiated the transaction, so tx.origin equals the owner throughout the entire call chain.
Secure Implementation
Using msg.sender
contract SecureWallet {
address public owner;
constructor() {
owner = msg.sender;
}
// SECURE: Using msg.sender
function transfer(address payable to, uint256 amount) public {
require(msg.sender == owner, "Not owner");
to.transfer(amount);
}
receive() external payable {}
}
Now the attack fails:
msg.sender= MaliciousContract's address ❌ (check fails)
- Transaction reverts before funds are stolen
When to Use Each
Use msg.sender for:
✅ Authentication/Authorization
modifier onlyOwner() {
require(msg.sender == owner, "Not authorized");
_;
}
✅ Access Control
mapping(address => bool) public whitelist;
function restrictedAction() external {
require(whitelist[msg.sender], "Not whitelisted");
}
✅ Sender-Specific Logic
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
Use tx.origin for:
⚠️ Rare legitimate cases:
function expensiveOperation() external {
// Complex computation
// Refund gas to original user (not intermediate contract)
payable(tx.origin).transfer(gasRefund);
}
event Action(address indexed user, address indexed caller);
function logAction() external {
emit Action(tx.origin, msg.sender);
}
function onlyEOA() external {
require(tx.origin == msg.sender, "No contract calls");
// Ensures direct user interaction
}
Warning: Blocking contracts prevents account abstraction and future composability.
Real-World Impact
THORChain Vulnerability (2021)
An early version had tx.origin checks that could be exploited via router contracts. Fortunately, it was caught in audit before launch.
OpenZeppelin Recommendation
The OpenZeppelin team explicitly warns against tx.origin:
> "Using tx.origin for authorization may lead to phishing attacks where a malicious contract tricks users into approving transactions."
EIP-3074 Implications
Future Ethereum improvements like EIP-3074 (transaction sponsorship) may change tx.origin behavior, making it even riskier to rely on.
Detection and Prevention
Slither Detection
Slither automatically flags tx.origin usage:
slither . --detect tx-origin
Output:
VulnerableWallet.transfer() (contracts/Wallet.sol#8-11) uses tx.origin for authorization:
- require(bool)(tx.origin == owner) (contracts/Wallet.sol#9)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin
Code Review Checklist
✅ All tx.origin occurrences are justified
✅ No tx.origin in require() for authorization
✅ No tx.origin in access control modifiers
✅ msg.sender used for all authentication
Testing
Write tests that simulate malicious intermediaries:
// Foundry test
function testPhishingAttack() public {
// Deploy wallet owned by user
vm.prank(user);
VulnerableWallet wallet = new VulnerableWallet();
vm.deal(address(wallet), 10 ether);
// Deploy malicious contract
MaliciousContract attacker = new MaliciousContract(address(wallet));
// User falls for phishing, calls malicious contract
vm.prank(user);
attacker.claimAirdrop();
// Attacker now has the funds
assertEq(address(attacker).balance, 10 ether);
}
Summary Table
| Aspect | msg.sender | tx.origin |
|--------|---------------|--------------|
| Value | Immediate caller | Original EOA |
| Can be contract | Yes | No |
| Changes in call chain | Yes | No |
| For authentication | ✅ Safe | ❌ Dangerous |
| For access control | ✅ Recommended | ❌ Vulnerable |
| For logging | ✅ Use both | ✅ Acceptable |
| Phishing risk | ❌ No | ✅ High |
Best Practices
msg.sender — it's almost always what you needtx.origin for authentication — no exceptionstx.origin occurrence — justify its necessityConclusion
The choice between tx.origin and msg.sender isn't just a technical detail — it's a critical security decision.
The rule is simple: Use msg.sender for authentication and authorization. Reserve tx.origin for rare edge cases where you genuinely need the original transaction signer, and never for security-critical checks.
Master secure authentication patterns on Solingo — our exercises include phishing attack simulations and real-time feedback to help you avoid these dangerous pitfalls.