Bird
Raised Fist0
Blockchain / Solidityprogramming~10 mins

Transaction context (tx.origin vs msg.sender) in Blockchain / Solidity - Visual Side-by-Side Comparison

Choose your learning style10 modes available
Concept Flow - Transaction context (tx.origin vs msg.sender)
User initiates transaction
↓
tx.origin = User
↓
Contract A receives call
↓
msg.sender = User (if direct call) OR Contract B (if called by Contract B)
↓
Contract B calls Contract A
↓
msg.sender = Contract B
↓
tx.origin still = User
↓
Contract A uses tx.origin or msg.sender
↓
Action depends on which is used
Shows how tx.origin stays the original user, while msg.sender changes with each call in the call chain.
Execution Sample
Blockchain / Solidity
contract A {
  function check() public view returns (address, address) {
    return (tx.origin, msg.sender);
  }
}
Contract A returns the original transaction sender and the immediate caller.
Execution Table
StepCallertx.originmsg.senderReturned ValuesExplanation
1User calls Contract A directlyUserUser(User, User)Direct call: both tx.origin and msg.sender are User
2User calls Contract BUserUser-User initiates transaction, tx.origin=User, msg.sender=User in Contract B
3Contract B calls Contract AUserContract B-Inside Contract A, msg.sender is Contract B, tx.origin still User
4Contract A returnsUserContract B(User, Contract B)Return from Contract A shows tx.origin=User, msg.sender=Contract B
5End---Execution ends after return
💡 Execution stops after Contract A returns the addresses.
Variable Tracker
VariableStartAfter Step 1After Step 2After Step 3Final
tx.originundefinedUserUserUserUser
msg.senderundefinedUserUserContract BContract B
Key Moments - 3 Insights
Why is tx.origin always the original user even inside multiple contract calls?
Because tx.origin tracks the very first external account that started the transaction, as shown in execution_table rows 2 and 3 where tx.origin remains 'User' despite calls between contracts.
Why does msg.sender change when one contract calls another?
msg.sender is the immediate caller of the current function, so when Contract B calls Contract A, msg.sender inside Contract A is Contract B, as seen in execution_table row 3.
Can using tx.origin for authorization be risky?
Yes, because tx.origin does not change through contract calls, a malicious contract can trick a user into calling it, then call another contract where tx.origin is still the user, potentially bypassing msg.sender checks.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table, what is msg.sender inside Contract A when called by Contract B?
AContract B
BUser
CContract A
Dtx.origin
💡 Hint
Check execution_table row 3 where Contract B calls Contract A.
At which step does tx.origin remain the same despite different msg.sender values?
AStep 1
BStep 3
CStep 2
DStep 5
💡 Hint
Look at execution_table rows 2 and 3 for tx.origin values.
If Contract A used msg.sender for authorization, who would it trust when called by Contract B?
AUser
Btx.origin
CContract B
DItself
💡 Hint
msg.sender is the immediate caller, see execution_table row 3.
Concept Snapshot
tx.origin is the original external user who started the transaction.
msg.sender is the immediate caller of the current function.
In multi-contract calls, tx.origin stays the same, msg.sender changes.
Using tx.origin for security can be dangerous.
Prefer msg.sender for authorization checks.
Full Transcript
This visual trace shows how in blockchain transactions, tx.origin always holds the address of the original user who started the transaction, while msg.sender changes depending on who called the current contract function. When a user calls Contract B, and Contract B calls Contract A, inside Contract A, tx.origin is still the user, but msg.sender is Contract B. This difference is important for security because using tx.origin for authorization can be risky if a malicious contract tricks a user. The execution table steps through these calls, showing variable values and returned results. Key moments clarify why tx.origin remains constant and msg.sender changes, and the quiz tests understanding of these concepts.

Practice

(1/5)
1. What does tx.origin represent in a blockchain transaction?
easy
A. The original external account that started the transaction
B. The immediate caller of the current function
C. The contract address executing the function
D. The miner who included the transaction in a block

Solution

  1. Step 1: Understand the meaning of tx.origin

    tx.origin always points to the external account that initiated the transaction, regardless of how many contracts are called.
  2. Step 2: Differentiate from msg.sender

    msg.sender is the immediate caller, which can be a contract or an account, but tx.origin is always the original external user.
  3. Final Answer:

    The original external account that started the transaction -> Option A
  4. Quick Check:

    tx.origin = original external account [OK]
Hint: Remember: tx.origin is the original user, msg.sender is immediate caller [OK]
Common Mistakes:
  • Confusing tx.origin with msg.sender
  • Thinking tx.origin changes in internal calls
  • Assuming tx.origin is always a contract
2. Which of the following is the correct way to check if the caller is the owner using msg.sender in Solidity?
easy
A. require(owner == block.coinbase);
B. require(tx.origin == owner);
C. require(owner == tx.origin);
D. require(msg.sender == owner);

Solution

  1. Step 1: Identify the correct variable for immediate caller

    To check the caller of the function, msg.sender is used because it represents the immediate caller.
  2. Step 2: Verify the syntax of the require statement

    The correct syntax is require(msg.sender == owner); to ensure only the owner can proceed.
  3. Final Answer:

    require(msg.sender == owner); -> Option D
  4. Quick Check:

    Use msg.sender for permission checks [OK]
Hint: Use msg.sender for permission checks, not tx.origin [OK]
Common Mistakes:
  • Using tx.origin for permission checks
  • Swapping owner and caller in comparison
  • Using block.coinbase which is miner address
3. Consider this Solidity code snippet:
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?
medium
A. false
B. true
C. Compilation error
D. Revert due to permission error

Solution

  1. Step 1: Understand owner assignment in constructor

    The owner is set to msg.sender when the contract is deployed, which is the external user deploying it.
  2. Step 2: Analyze checkOrigin() call by external user

    When the external user calls checkOrigin(), tx.origin is the same external user who deployed the contract (owner), so the comparison returns true.
  3. Final Answer:

    true -> Option B
  4. Quick Check:

    Direct call: tx.origin equals owner [OK]
Hint: Direct calls: tx.origin equals msg.sender equals owner [OK]
Common Mistakes:
  • Assuming tx.origin differs from msg.sender on direct calls
  • Confusing owner initialization
  • Expecting errors from this code
4. Given this Solidity 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?
medium
A. It causes a compilation error due to missing payable keyword
B. It compares tx.origin instead of msg.sender inside checkOrigin
C. It uses low-level call incorrectly and ignores return value unpacking
D. It sets owner incorrectly in Callee constructor

Solution

  1. Step 1: Analyze the use of low-level call in callCheck

    The call returns (bool, bytes), but the code only returns the bool from call without decoding the actual function's return value.
  2. Step 2: Understand the consequence of ignoring return data

    Because the function ignores the returned bytes, it cannot get the actual boolean result from checkOrigin(), leading to incorrect or misleading results.
  3. Final Answer:

    It uses low-level call incorrectly and ignores return value unpacking -> Option C
  4. Quick Check:

    Low-level call needs return data decoding [OK]
Hint: Low-level call returns bytes; decode to get function result [OK]
Common Mistakes:
  • Blaming tx.origin usage instead of call misuse
  • Ignoring that call returns (bool, bytes)
  • Assuming owner is set wrongly
5. You want to design a contract that only allows the original external user who started a transaction to execute a sensitive function, even if called through multiple contracts. Which is the safest way to implement this check?
hard
A. Use require(tx.origin == owner); to check the original external account
B. Use require(msg.sender == owner); to check the immediate caller
C. Use require(tx.origin == msg.sender); to ensure both are equal
D. Use require(owner == address(this)); to check contract address

Solution

  1. Step 1: Understand the difference between msg.sender and tx.origin

    msg.sender is the immediate caller, which can be a contract, while tx.origin is the original external account that started the transaction.
  2. Step 2: Choose the check that ensures original user authorization

    To ensure only the original external user can execute the function, use require(tx.origin == owner); because it tracks the transaction origin through multiple calls.
  3. Final Answer:

    Use require(tx.origin == owner); to check the original external account -> Option A
  4. Quick Check:

    Original user check uses tx.origin [OK]
Hint: Use tx.origin to track original external user through calls [OK]
Common Mistakes:
  • Using msg.sender which can be a contract
  • Comparing tx.origin to msg.sender incorrectly
  • Checking contract address instead of user
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard