Signers
Ethereum passes msg.sender implicitly with every external call. Solana requires every signer to be a typed argument on the instruction, checked before the handler runs.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// `msg.sender` is implicit - every external function call carries it
/// automatically, set by the EVM from the transaction signer.
contract Guarded {
address public owner;
constructor() {
owner = msg.sender;
}
The EVM sets msg.sender automatically from the transaction signer; the contract just reads it. There's no parameter, no declaration - the constructor uses it to remember whoever deployed.
function setOwner(address newOwner) external {
require(msg.sender == owner, "not owner");
owner = newOwner;
}
}
require(msg.sender == owner) is the canonical access guard. The signer information arrives behind the function call; the contract trusts the EVM to set it correctly. Forget to add the check on a new function and you ship a bug.
// `Signer<'info>` makes the caller explicit. There is no `msg.sender` to
// reach for - the signing account is one of the typed arguments declared on
// the instruction struct, and Anchor verifies its `is_signer` flag before
// the handler runs.
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
pub mod guarded {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.config.owner = ctx.accounts.payer.key();
Ok(())
}
pub fn set_owner(ctx: Context<SetOwner>, new_owner: Pubkey) -> Result<()> {
ctx.accounts.config.owner = new_owner;
Ok(())
}
}
The handler body has no ambient identity - it reads ctx.accounts.payer.key() (in initialize) or ctx.accounts.owner.key() (in set_owner) from the typed account list. Every signer is a real account on the instruction.
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + 32,
seeds = [b"config"],
bump,
)]
pub config: Account<'info, Config>,
pub system_program: Program<'info, System>,
}
payer: Signer<'info> declares "this account must have signed the transaction". Anchor verifies the is_signer flag before the handler runs - the body never gets a chance to act on an unsigned account.
#[derive(Accounts)]
pub struct SetOwner<'info> {
// `has_one = owner` plus a `Signer<'info>` field named `owner` is the
// declarative form of `require(msg.sender == owner)` - Anchor checks
// both the signature and that it matches the stored `Config.owner`
// before the handler executes.
#[account(mut, seeds = [b"config"], bump, has_one = owner)]
pub config: Account<'info, Config>,
pub owner: Signer<'info>,
}
has_one = owner plus a Signer<'info> field named owner is the declarative form of require(msg.sender == owner). Anchor checks both the signature and that it matches Config.owner before any handler code executes. The check lives next to the account, not buried in the function body - scan the struct and you see exactly who can call what.
#[account]
pub struct Config {
pub owner: Pubkey,
}