Accounts & keys

Accounts: who is calling

address becomes AccountId — a human-readable name. predecessor, signer and current account replace msg.sender, tx.origin and address(this).

On the EVM an account is a 20-byte hash. On NEAR it is a named account — alice.near, app.near — and there are three context accounts to keep straight where Solidity has msg.sender, tx.origin and address(this).

The three context accounts
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Who {
    function caller() external view returns (address) {
        return msg.sender;
    }

    function origin() external view returns (address) {
        return tx.origin;
    }

    function self_() external view returns (address) {
        return address(this);
    }
}
Rust
use near_sdk::{near, env, AccountId};

#[near(contract_state)]
#[derive(Default)]
pub struct Who {}

#[near]
impl Who {
    // the immediate caller (a user, or a calling contract)
    pub fn caller(&self) -> AccountId {
        env::predecessor_account_id()
    }

    // the account that signed the transaction
    pub fn origin(&self) -> AccountId {
        env::signer_account_id()
    }

    // this contract's own account
    pub fn this(&self) -> AccountId {
        env::current_account_id()
    }
}
Coming from EVM

address ↔ AccountId: a validated name like alice.near, not a hash. You compare and clone it; there is no address arithmetic.

msg.sender ↔ env::predecessor_account_id() — the immediate caller. In a cross-contract call the predecessor is the *calling contract*, exactly like msg.sender.

tx.origin ↔ env::signer_account_id() (the account that signed). address(this) ↔ env::current_account_id(). Accounts are hierarchical: app.near can create sub.app.near, which matters for keys and storage.