Storage costs are the most common source of unexpected issues for NEAR developers coming from Ethereum or Solana. The storage staking model is unique to NEAR and requires understanding before you deploy contracts that manage user data.

How Storage Staking Works

Every byte of data stored on the NEAR blockchain requires locking a proportional amount of NEAR tokens. This is called storage staking. The locked NEAR is held as a deposit, not burned: if you delete the data, you get the NEAR back. The current rate is approximately 10 NEAR per 1MB of storage, or equivalently 10 yoctoNEAR per byte (1 yoctoNEAR = 10^-24 NEAR).

This model exists to prevent state bloat. On Ethereum, storage costs are paid once as gas and the data remains forever. This creates an incentive for contracts to accumulate state indefinitely, burdening all full nodes. NEAR's storage staking creates an ongoing cost for holding state, incentivizing contracts to clean up data they no longer need.

The locked NEAR earns no staking rewards. It just sits locked as collateral for your storage usage. When you delete data, the formerly-locked NEAR becomes available in your account balance immediately.

Calculating Storage Costs

The formula is straightforward: cost_yoctoNEAR = bytes * 10. To convert to NEAR: cost_NEAR = bytes * 10^-23.

Some reference values:

For most contracts with typical usage, storage costs are negligible. Problems arise when contracts accumulate unbounded state: every new user registration, every new key-value pair in a map, every message in a chat contract adds to the total. At scale, these costs become significant.

In Rust near-sdk, you can calculate storage usage before and after a state change using env::storage_usage(). The typical pattern is:

let initial_storage = env::storage_usage();
// ... modify state ...
let final_storage = env::storage_usage();
let storage_cost = (final_storage - initial_storage) as u128 * env::storage_byte_cost();

The Storage Deposit Pattern

The storage deposit pattern is the standard approach for managing storage costs in multi-user contracts. Here is how it works:

When a user first interacts with a contract (registers, creates a position, sends a message), they attach a storage deposit to their transaction. This deposit must cover the maximum storage that user's data could occupy in the contract. The contract holds this deposit on behalf of the user in a separate storage_deposits map.

When the user's action creates new state, the contract deducts the actual storage cost from their deposit. If the deposit is insufficient, the transaction fails. If the user later removes their data (unregisters, closes their position), the contract returns their remaining deposit.

This pattern keeps storage costs predictable and prevents the contract from bearing storage costs for user data at scale. The NEP-145 standard formalizes this pattern for fungible tokens and is worth reading before implementing your own.

Contract Optimization Strategies

Several techniques significantly reduce storage costs:

Use shorter keys in maps. LookupMap in near-sdk stores keys verbatim. If you use a user's full account ID (up to 64 bytes) as a map key, each entry costs 640 yoctoNEAR just for the key. If you can hash the account ID to a fixed 32-byte representation, you halve the key storage cost across your entire user base.

Prefer fixed-size types. Storing a u128 (16 bytes) is cheaper than storing a String representation of the same number (up to 20 bytes for decimal). For amounts, balances, and other numeric values, use the appropriate numeric type.

Pack small values together. If you have multiple boolean flags per user, pack them into a single u8 using bit operations. Eight booleans in one byte costs far less than eight separate bool fields.

Implement lazy state loading. The near-sdk LazyOption and UnorderedMap types load data only when accessed, not at contract initialization. For contracts with large state, this reduces the cost of simple operations that do not need to access all state.

Clean up state aggressively. When users remove their data, call clear() on their collections and use storage_withdraw to return the freed storage deposit. Do not leave orphaned state in your contract just because the user no longer cares about it.

Common Mistakes to Avoid

Deploying without storage staking consideration is the most common mistake. A contract that stores one record per user and has 100,000 users may need significant NEAR locked for storage. Calculate this before deployment.

Not implementing the storage deposit pattern means your contract bears the storage costs for all user data. At scale this becomes a meaningful balance drain from the contract's account.

Storing large strings or blobs on-chain. NEAR is not a content-addressed storage system. If you need to store large files, use IPFS, Arweave, or a centralized store and put only the hash on-chain.

Assuming storage costs are static. The storage_byte_cost can be modified by governance. Build contracts that read env::storage_byte_cost() dynamically rather than hardcoding the current rate.

Checking Your Contract's Storage Usage

The NEAR CLI and RPC both expose storage usage. Using the CLI:

near view-state CONTRACT_ID --finality final

Using the RPC directly:

curl -X POST https://rpc.mainnet.near.org \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc": "2.0", "id": "1", "method": "query", \
  "params": {"request_type": "view_state", "account_id": "CONTRACT_ID", "prefix_base64": "", "finality": "final"}}'

This returns all key-value pairs in the contract's state with their sizes. For contracts with large state, paginate using the state_part_size parameter.

Summary

Storage staking is a deliberate design choice that keeps NEAR state from growing indefinitely. The cost is real but manageable. Implement the storage deposit pattern for user-facing contracts, choose compact data representations, and clean up state when it is no longer needed. The reference implementations in near-sdk cover the standard patterns and are the best starting point for new contracts.