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.

A read method and a write method
Solidity
// 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;
    }
}
Rust
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;
    }
}
Coming from EVM

&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({}).

Calling each one
Shell
# ethers.js
await counter.get();           // read — no tx, no gas
await counter.increment();     // write — sends a transaction
Shell
# NEAR CLI
near view counter.testnet get '{}'
# read: free, no signer

near call counter.testnet increment '{}' \
  --accountId you.testnet
# write: signed transaction, costs gas
Coming from EVM

In 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.