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