Functions & calls

Functions: view, mutating & payable

Reads vs writes are the &self vs &mut self receiver. Accepting NEAR needs #[payable]. msg.sender and msg.value become env:: calls.

A tip jar shows the three things every function chooses: does it read or write, does it accept money, and who called it.

In Solidity those are keywords (view, payable) and globals (msg.sender, msg.value). On NEAR the read/write choice is the method receiver, accepting money is an attribute, and the context comes from the env module.

A tip jar
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Tip {
    uint256 internal total;
    address public last;

    // view: reads only, free to call off-chain
    function totalReceived() external view returns (uint256) {
        return total;
    }

    // mutating + accepts ETH
    function tip() external payable {
        total += msg.value;
        last = msg.sender;
    }
}
Rust
use near_sdk::{near, env, AccountId, NearToken};

#[near(contract_state)]
pub struct Tip {
    total: NearToken,
    last: Option<AccountId>,
}

impl Default for Tip {
    fn default() -> Self {
        Self {
            total: NearToken::from_yoctonear(0),
            last: None,
        }
    }
}

#[near]
impl Tip {
    // view: &self, reads only
    pub fn total_received(&self) -> NearToken {
        self.total
    }

    pub fn last_tipper(&self) -> Option<AccountId> {
        self.last.clone()
    }

    // mutating + accepts an attached deposit
    #[payable]
    pub fn tip(&mut self) {
        self.total = self.total.saturating_add(env::attached_deposit());
        self.last = Some(env::predecessor_account_id());
    }
}
Coming from EVM

view ↔ &self, mutating ↔ &mut self. A &self method is a read: anyone can query it over RPC for free, no transaction.

payable ↔ #[payable]. The default is the opposite of Solidity: a NEAR method rejects an attached deposit unless it is marked #[payable], so you can’t accidentally trap funds in a non-payable method.

msg.value ↔ env::attached_deposit(), msg.sender ↔ env::predecessor_account_id(). Amounts are a NearToken in yocto-NEAR (1 NEAR = 10²⁴ yocto), so use saturating_add / checked_add rather than bare +.