ETH TO SOL
$npx skills add solana-foundation/eth-to-sol-skill

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.

SolidityGuarded.solETHEREUM
// 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;
    }
`msg.sender` is ambient.

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;
    }
}
The check reads from a hidden context.

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.


port ↓
Anchor 1.0.2programs/guarded/src/lib.rsSOLANA
// `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(())
    }
}
No `msg.sender` to reach for.

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>,
}
Signers are typed and declared up front.

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` is the declarative `require`.

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,
}