msg.sender vs tx.origin in Solidity
Two built-in variables that tell you who's calling your contract, but they work differently and picking the wrong one can create serious security holes.
Published on 09 November, 2025
Categories:
SolidityWhat These Variables Actually Are
Both
msg.sender and tx.origin give you addresses, but they track different things in the call chain.msg.sender is the immediate caller of your function. If a user calls your contract, msg.sender is that user's address. If another contract calls your contract, msg.sender is that contract's address.tx.origin is always the address that started the entire transaction. It traces back to the original externally owned account (EOA) that signed the transaction, no matter how many contracts are in the call chain.How the Call Chain Works
When contracts call other contracts, these variables behave differently:
User (0xUser)
|
| calls
v
Contract A
|
| calls
v
Contract B
Inside Contract B:
msg.sender= Contract A's addresstx.origin= 0xUser (the original caller)
Code Example
Here's a simple demonstration:
contract CallerInfo { address public lastSender; address public lastOrigin; function recordCaller() public { lastSender = msg.sender; lastOrigin = tx.origin; } } contract Intermediary { function callThroughMe(address target) public { CallerInfo(target).recordCaller(); } }
If a user at address
0xUser calls Intermediary.callThroughMe(), which then calls CallerInfo.recordCaller():lastSenderwill be the Intermediary contract addresslastOriginwill be0xUser
The Security Problem
Using
tx.origin for authentication is dangerous. Here's why:// VULNERABLE CONTRACT contract Wallet { address public owner; constructor() { owner = msg.sender; } function transfer(address to, uint amount) public { require(tx.origin == owner, "Not owner"); payable(to).transfer(amount); } }
An attacker can exploit this:
// MALICIOUS CONTRACT contract Attack { Wallet public wallet; address public attacker; constructor(address _wallet) { wallet = Wallet(_wallet); attacker = msg.sender; } function attack() public { wallet.transfer(attacker, address(wallet).balance); } }
The attack works like this:
Owner (tx.origin)
|
| tricked into calling
v
Attack contract
|
| calls
v
Wallet.transfer()
|
| checks: tx.origin == owner?
| YES! (because owner started transaction)
v
Funds stolen
The wallet thinks the owner authorized the transfer because
tx.origin is still the owner's address, even though they never directly called the wallet.The Safe Approach
Always use
msg.sender for access control:contract SafeWallet { address public owner; constructor() { owner = msg.sender; } function transfer(address to, uint amount) public { require(msg.sender == owner, "Not owner"); payable(to).transfer(amount); } }
Now the same attack fails:
Owner
|
| calls
v
Attack contract
|
| calls
v
SafeWallet.transfer()
|
| checks: msg.sender == owner?
| NO! (msg.sender is Attack contract)
v
Transaction reverts
When tx.origin Might Be Acceptable
There are rare cases where
tx.origin is useful, but not for authentication:- Gas sponsorship patterns where you want to know the original signer
- Logging or analytics where you track who initiated a transaction
- Rate limiting based on end users rather than intermediate contracts
Even in these cases, never use it as your primary security check.
Valid Use Case Example
contract GasRelay { mapping(address => uint) public userGasSpent; function relayCall(address target, bytes calldata data) public { // Track gas by original user, not this relay uint gasBefore = gasleft(); (bool success,) = target.call(data); require(success, "Call failed"); userGasSpent[tx.origin] += gasBefore - gasleft(); } }
Here
tx.origin tracks the end user's gas usage, but the actual access control would still use msg.sender in the target contract.Quick Decision Guide
Use
msg.sender when:- Checking permissions or ownership
- Implementing access control
- Transferring funds or tokens
- Any security-critical operation
Use
tx.origin when:- Logging who started a transaction
- Analytics or tracking
- You need to know the EOA in a meta-transaction pattern
- Never as the sole authentication mechanism
Use
msg.sender for authentication and access control because it accurately reflects the immediate caller. Only use tx.origin for non-security purposes like logging where you need to track the original transaction signer, and understand that relying on it for authorization creates exploitable vulnerabilities.