tx.origin Explained in Detail
tx.origin returns the original wallet that started a transaction. msg.sender returns the immediate caller of the current function.
That difference matters when contracts call other contracts. A privileged wallet may start a transaction through a malicious contract, and the target contract may still see the privileged wallet as tx.origin.
Smart contract example
The check below is unsafe:
function withdrawAll() external {
require(tx.origin == owner, "not owner");
payable(msg.sender).transfer(address(this).balance);
}
If the owner calls a malicious contract, that contract can call withdrawAll(). The tx.origin check still passes because the original wallet is the owner.
tx.origin in Auditing
Using tx.origin for authorization can turn a normal wallet interaction into an access control bypass. It is especially risky around external calls, wallet flows, phishing, and meta-transaction assumptions.
Red flags in code
-
require(tx.origin == owner)or similar authorization checks. -
Privileged functions that compare
tx.originto an admin, multisig, or role holder. -
Security logic that assumes
tx.origin == msg.sender. -
Calls from routers, relayers, smart wallets, or account-abstraction flows.
-
Comments that describe
tx.originas the user or caller without explaining the trust boundary.
How to test or review it
-
Replace
tx.originauthorization withmsg.senderor explicit signature-based authorization. -
Build a malicious intermediary contract and call the target through it.
-
Check every privileged path for indirect calls through delegatecall, routers, or callbacks.
-
Review whether the flow should support smart contract wallets.
-
Treat any
tx.originauthorization as audit-critical until proven harmless.