All articles
Web3 FoundationsSeptember 14, 20268 min read

msg.sender in Solidity: Who Is Calling, Right Now

msg.sender is the single most used value in Solidity. What it means, why it changes when a contract calls another contract, and how tx.origin differs from it.

By Carlos (Bloqarl)

TL;DR

  • msg.sender is the address of whoever is calling the current function, at this exact moment. Not the owner, not the user, not the account that started everything. The immediate caller.
  • It is available in every function without being passed in, because the chain supplies it with the call.
  • When a contract calls another contract, msg.sender becomes the calling contract, not the person who set the whole thing in motion. This is the detail that trips up almost everyone.
  • Inside a constructor, msg.sender is the deployer, which is why so many contracts learn their owner that way.
  • tx.origin is the account that originally signed the transaction, and it is a different value with different behaviour.

What is msg.sender in Solidity?

msg.sender is a built-in value holding the address of the account that made the call currently executing, whether that account is a person's wallet or another contract.

It appears twice in our worked contract, doing two different jobs:

    constructor(uint256 _goal) {
        owner = msg.sender;
    }

    function smash() external {
        require(msg.sender == owner, "only the owner");

In the constructor it records who deployed the contract. In smash it identifies who is calling right now so the contract can compare that against the stored owner.

Same value, same rule, two moments. Whoever is calling, right now.

It is also the value that makes the third pass of how to read a smart contract work: once you know who a function believes it is talking to, most of what it does stops being mysterious.

Where does msg.sender come from?

It arrives with the call. You never declare it, never pass it, and never set it.

Every call to a contract carries a small envelope of information about the call itself, and Solidity exposes the contents as msg and tx. The ones you meet most:

ValueWhat it holds
msg.senderThe address making this call
msg.valueHow much ETH came with this call, in wei
msg.dataThe raw calldata of this call
tx.originThe account that signed the original transaction

Because the chain supplies these, a contract cannot be lied to about msg.sender by the caller choosing what to put in an argument. That property is the reason it is the backbone of nearly every ownership and permission pattern in Solidity: it is one of the few things a contract knows rather than is told. A caller can always lie about an address they pass in as a parameter. They cannot lie about the address the call came from.

If the idea of a "call" is still hazy, how crypto transactions work is the primer.

Why does msg.sender change mid transaction?

This is the part worth slowing down for, because it is where almost all confusion about msg.sender lives.

msg.sender is not "the user". It is the immediate caller of the current call, and a single transaction can contain several calls, nested inside each other.

Picture Ana clicking a button that calls a router contract, and the router calling a pool contract:

  1. Ana's wallet sends a transaction to Router.
  2. Inside Router's function, msg.sender is Ana.
  3. Router calls Pool.
  4. Inside Pool's function, msg.sender is Router, not Ana.

Pool has no idea Ana exists. From where it stands, a contract called it. If Pool checks msg.sender == owner and Ana is the owner, that check fails, because Ana is not the one calling Pool. The router is.

This is not a quirk to work around, it is the model. Each call knows only its direct caller. Once you hold that, a large amount of otherwise baffling contract architecture makes sense: why protocols pass an explicit recipient or to parameter through their routers, why approvals are granted to contracts rather than people, and why "the user" is often an argument rather than an assumption.

What about tx.origin?

tx.origin is the account that signed the original transaction: in the example above, Ana, at every level of the nesting. It does not change as calls nest.

That sounds more convenient than msg.sender, and it is precisely why it is rarely the right tool. A value that stays the same no matter how many contracts the call has passed through cannot tell you anything about who is actually calling you now.

For reading purposes, the distinction is enough:

  • msg.sender answers "who is calling me?"
  • tx.origin answers "who signed the transaction that eventually led here?"

If you see tx.origin used where the code seems to mean "the user", that is worth a second look, and the security reasoning behind that instinct is the kind of thing covered in why crypto gets hacked. This article stops at what the words mean.

How is msg.sender used in practice?

Three patterns account for most sightings, and all three are readable at a glance once you know the shape.

Recording an owner at deployment. Seen above: owner = msg.sender in a constructor.

Gating a function. The comparison pattern, either written inline or wrapped in a modifier:

    require(msg.sender == owner, "only the owner");

A msg.sender comparison like this is the single most common line in all of Solidity. It says: if the caller is not the stored owner, stop everything. What "stop everything" actually means is covered in require and revert.

Keying a balance. In token and vault contracts, msg.sender is used as the key into a mapping, so the caller's own record is read or written without them having to say who they are:

    balances[msg.sender] += msg.value;

That single line is most of what a deposit function does, and it is why token contracts do not need to trust an address you pass them.

Why can a contract not simply ask who the user is?

Because there is nobody to ask. A contract has no session, no login, and no notion of a user beyond the address that called it right now.

This is the mental shift that takes longest coming from web development. In a web application, "the current user" is a stable idea that persists across requests, backed by a session or a token. A contract has none of that. Each call arrives, carries a sender, executes, and ends. The next call may come from an entirely different address a second later, and the contract has no memory of the previous one unless it deliberately wrote something down.

So every permission decision has to be made from what is in front of it: the caller's address, plus whatever the contract stored earlier. That is the entire basis. It is why so much of Solidity reads as comparisons against stored addresses, and why "who am I talking to" is a question answered fresh on every single call.

It also explains a pattern that looks redundant until you see it this way. When a contract stores owner = msg.sender at deployment and then checks msg.sender == owner later, it is not being cautious about a value it already has. It is comparing a fact recorded at one moment against a fact observed at a completely different one, with no continuity between them except the stored variable.

Delete the word and ask what breaks

  • Replace owner = msg.sender with owner = someAddressParameter and the deployer can now name anyone as owner, including by mistake. The contract is no longer guaranteed to be owned by whoever created it.
  • Delete require(msg.sender == owner, ...) from smash and anyone at all can empty the pig. One line is the entire difference between a private savings jar and a public one.
  • Swap msg.sender for tx.origin in that same check and the contract now accepts a call from any contract, as long as the person who signed the transaction was the owner.
  • Use balances[someAddress] instead of balances[msg.sender] in a deposit and callers can credit deposits to accounts that are not theirs.

Related questions

What is the difference between msg.sender and tx.origin? msg.sender is the immediate caller of the current function and changes as calls nest between contracts. tx.origin is the externally owned account that signed the original transaction and stays the same throughout.

Can msg.sender be faked? No. It is supplied by the chain as part of the call, not by the caller as data. A caller can pass any address they like as a function argument, but they cannot change the address the call appears to come from.

Is msg.sender always a wallet? No. It is whatever account made the call, which is frequently another contract. Any contract that assumes msg.sender is a person's wallet is making an assumption the value does not support.

What is msg.sender inside a constructor? The account deploying the contract. Since the constructor runs as part of the deployment transaction, the caller at that moment is the deployer, which is how most contracts record their initial owner.

Why do contracts take a recipient address instead of using msg.sender? Because when a contract is called by another contract, msg.sender is that intermediary rather than the end user. Protocols that expect to be called through routers pass the real recipient explicitly.

Does msg.sender cost gas to read? It is one of the cheapest reads available, since the value is already present in the call context and does not require touching storage.

Where to go next

msg.sender is the value contracts use to know who they are talking to, and its one surprising property, that it changes as calls nest, explains a surprising amount of how real systems are shaped.

The next word in our contract is the one that decides whether ETH is allowed through the door at all: payable, receive, and fallback.

Tagged

SoliditySmart ContractsLearn to Code