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).
// 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);
}
}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()
}
}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.