Understanding tx.origin and msg.sender helps you know who started a transaction and who called a function in smart contracts.
Transaction context (tx.origin vs msg.sender) in Blockchain / Solidity
address txOrigin = tx.origin; address msgSender = msg.sender;
tx.origin is the original external account that started the transaction.
msg.sender is the immediate caller of the current function.
function checkOrigin() public view returns (address) {
return tx.origin;
}function checkSender() public view returns (address) {
return msg.sender;
}tx.origin stays the same across calls, but msg.sender changes to the immediate caller.contract Caller {
function callOther(address callee) public {
OtherContract(callee).calledFunction();
}
}
contract OtherContract {
function calledFunction() public view returns (address origin, address sender) {
return (tx.origin, msg.sender);
}
}This program has two contracts. Caller calls OtherContract. When callOther runs, tx.origin is the original user who started the transaction, and msg.sender inside calledFunction is the Caller contract.
pragma solidity ^0.8.0; contract OtherContract { function calledFunction() public view returns (address origin, address sender) { return (tx.origin, msg.sender); } } contract Caller { OtherContract other; constructor(address otherAddress) { other = OtherContract(otherAddress); } function callOther() public view returns (address origin, address sender) { return other.calledFunction(); } }
Never use tx.origin for authorization checks because it can be tricked by phishing attacks.
Use msg.sender to check who called your function directly for better security.
Remember, tx.origin is always an external account, never a contract.
tx.origin is the original external account that started the transaction.
msg.sender is the immediate caller of the current function.
Use msg.sender for secure access control in smart contracts.
Practice
tx.origin represent in a blockchain transaction?Solution
Step 1: Understand the meaning of
tx.origintx.originalways points to the external account that initiated the transaction, regardless of how many contracts are called.Step 2: Differentiate from
msg.sendermsg.senderis the immediate caller, which can be a contract or an account, buttx.originis always the original external user.Final Answer:
The original external account that started the transaction -> Option AQuick Check:
tx.origin= original external account [OK]
- Confusing tx.origin with msg.sender
- Thinking tx.origin changes in internal calls
- Assuming tx.origin is always a contract
msg.sender in Solidity?Solution
Step 1: Identify the correct variable for immediate caller
To check the caller of the function,msg.senderis used because it represents the immediate caller.Step 2: Verify the syntax of the require statement
The correct syntax isrequire(msg.sender == owner);to ensure only the owner can proceed.Final Answer:
require(msg.sender == owner); -> Option DQuick Check:
Use msg.sender for permission checks [OK]
- Using tx.origin for permission checks
- Swapping owner and caller in comparison
- Using block.coinbase which is miner address
contract Test {
address owner;
constructor() {
owner = msg.sender;
}
function checkOrigin() public view returns (bool) {
return tx.origin == owner;
}
}If an external user calls
checkOrigin() directly, what will be the returned value?Solution
Step 1: Understand owner assignment in constructor
The owner is set tomsg.senderwhen the contract is deployed, which is the external user deploying it.Step 2: Analyze
When the external user callscheckOrigin()call by external usercheckOrigin(),tx.originis the same external user who deployed the contract (owner), so the comparison returns true.Final Answer:
true -> Option BQuick Check:
Direct call: tx.origin equals owner [OK]
- Assuming tx.origin differs from msg.sender on direct calls
- Confusing owner initialization
- Expecting errors from this code
contract Caller {
address owner;
function callCheck(address callee) public view returns (bool) {
(bool success, ) = callee.call(abi.encodeWithSignature("checkOrigin()"));
return success;
}
}
contract Callee {
address owner;
constructor() {
owner = msg.sender;
}
function checkOrigin() public view returns (bool) {
return tx.origin == owner;
}
}What is the main issue with the
callCheck function in Caller?Solution
Step 1: Analyze the use of low-level call in callCheck
Thecallreturns (bool, bytes), but the code only returns the bool from call without decoding the actual function's return value.Step 2: Understand the consequence of ignoring return data
Because the function ignores the returned bytes, it cannot get the actual boolean result fromcheckOrigin(), leading to incorrect or misleading results.Final Answer:
It uses low-level call incorrectly and ignores return value unpacking -> Option CQuick Check:
Low-level call needs return data decoding [OK]
- Blaming tx.origin usage instead of call misuse
- Ignoring that call returns (bool, bytes)
- Assuming owner is set wrongly
Solution
Step 1: Understand the difference between msg.sender and tx.origin
msg.senderis the immediate caller, which can be a contract, whiletx.originis the original external account that started the transaction.Step 2: Choose the check that ensures original user authorization
To ensure only the original external user can execute the function, userequire(tx.origin == owner);because it tracks the transaction origin through multiple calls.Final Answer:
Use require(tx.origin == owner); to check the original external account -> Option AQuick Check:
Original user check uses tx.origin [OK]
- Using msg.sender which can be a contract
- Comparing tx.origin to msg.sender incorrectly
- Checking contract address instead of user
Ready to prove it?
Pass the test and this pattern moves unknown
