Skip to content
Retr0 Academy
NEAR Smart Contracts

NEAR Smart Contracts · 2 min read

Storage and Collections

When to use native Rust collections versus near_sdk::store (IterableMap, LookupMap, Vector), and why every collection needs a unique prefix.

Contract state is a key/value trie. If you put a big HashMap on the contract struct, NEAR loads the entire map on every call. Persistent collections in near_sdk::store split data across many storage keys and load only what you touch.

Every byte you store locks NEAR until you delete it.
Every byte you store locks NEAR until you delete it.

Native vs SDK

Use a native Vec or HashMap when the collection stays small (roughly under 100 entries) and you need all of it at once. Use an SDK collection when the set can grow.

TypeModuleIterableBest for
LookupMapnear_sdk::storeNoPoint lookups, lowest storage
IterableMapnear_sdk::storeYesMaps you need to list or paginate
LookupSet / IterableSetnear_sdk::storeSet only / yesMembership
Vectornear_sdk::storeYesOrdered lists

UnorderedMap in near_sdk::store is deprecated; prefer IterableMap.

Prefixes and lazy writes

Every collection is created with a unique prefix. That prefix namespaces its keys so two maps cannot collide. Most store collections cache writes in memory and flush them when the collection is dropped at the end of the call (you can also call flush()).

rust

use near_sdk::{near, store::{IterableMap, LookupMap, Vector}};
 
#[near(contract_state)]
pub struct Contract {
    scores: IterableMap<String, u32>,
    aliases: LookupMap<String, String>,
    events: Vector<String>,
}
 
impl Default for Contract {
    fn default() -> Self {
        Self {
            scores: IterableMap::new(b"s"),
            aliases: LookupMap::new(b"a"),
            events: Vector::new(b"e"),
        }
    }
}
 
#[near]
impl Contract {
    pub fn set_score(&mut self, account: String, score: u32) {
        self.scores.insert(account, score);
    }
 
    pub fn get_score(&self, account: String) -> Option<u32> {
        self.scores.get(&account).copied()
    }
 
    pub fn list_scores(&self, from_index: u32, limit: u32) -> Vec<(&String, &u32)> {
        self.scores.iter().skip(from_index as usize).take(limit as usize).collect()
    }
}

For a view method, returning Option<&u32> or a collected Vec is fine. Do not return the collection itself.

Storage still costs 1 NEAR per 100 KB (env::storage_byte_cost()). Charge the caller or use NEP-145. The holder-only “Storage Staking Deep Dive” lesson walks through measure-and-refund in more detail.

Exercise

Sketch a contract state with an IterableMap of account → score. Write down the prefix you would use and a view method that returns a page of scores.