Functions & calls
View vs change calls
Reads are free RPC queries with no signer and no gas; writes are signed transactions. Calling a read as a transaction costs gas and changes nothing — a classic first surprise.
NEAR has two distinct ways to invoke a method. A view call is a free, read-only RPC query — no wallet, no signer, no gas — exactly like an EVM eth_call. A change call is a signed transaction: it costs gas, has a predecessor, and is the only way to modify state.
Which one a method supports is declared in its signature: &self (Rust) or @view (JS) is a view; &mut self or @call is a change method.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Counter {
uint256 public count;
// read-only: free via eth_call, no state change
function get() external view returns (uint256) {
return count;
}
// state-changing: must be sent as a transaction (costs gas)
function increment() external {
count += 1;
}
}use near_sdk::near;
#[near(contract_state)]
#[derive(Default)]
pub struct Counter {
count: u64,
}
#[near]
impl Counter {
// &self -> view method: callable for free, cannot write state
pub fn get(&self) -> u64 {
self.count
}
// &mut self -> change method: needs a signed transaction (costs gas)
pub fn increment(&mut self) {
self.count += 1;
}
}&self is a view method — read it with a free RPC call_function query: no signer, no gas, and the runtime forbids writing state inside it. &mut self is a change method — it only runs inside a signed transaction and costs gas.
The surprise: you *can* also invoke a view method inside a transaction. It runs, costs gas, and changes nothing — so sending get as a transaction looks like it "did" something while count never moves. (If a tool sends every call as a transaction, this bites immediately.) Read methods should go through the free view path.
near-sdk-js mirrors this with decorators: @view({}) vs @call({}).
# ethers.js
await counter.get(); // read — no tx, no gas
await counter.increment(); // write — sends a transaction# NEAR CLI
near view counter.testnet get '{}'
# read: free, no signer
near call counter.testnet increment '{}' \
--accountId you.testnet
# write: signed transaction, costs gasIn the IDE, Read runs the free view query and Write sends a signed transaction — the method picker only offers the one that applies, so you never accidentally send a read as a transaction.