Near Learn

Reclaim storage and staked NEAR

Reclaim NEAR storage staking: remove entries to unlock balance, clear collections safely, free old state after migrations, and delete test accounts.

Intermediate4 min read3-question check

Storage stake is not spent, only locked, so every byte you delete releases 10¹⁹ yoctoNEAR back into the account’s available balance. That balance belongs to the account that held the data, usually the contract. If a user paid for the bytes, returning their share is your code’s job, not the protocol’s.

Delete entries and refund the payer#

Remove a record, measure what was freed, and refund it
Rust
// the Notes contract from "Make users pay for their storage"
#[near]
impl Notes {
    pub fn delete_note(&mut self) -> Promise {
        let who = env::predecessor_account_id();
        let initial = env::storage_usage();

        require!(self.notes.remove(&who).is_some(), "no note to delete");
        // removals are buffered too: flush before measuring
        self.notes.flush();

        let freed = initial.saturating_sub(env::storage_usage());
        let refund = env::storage_byte_cost().saturating_mul(freed as u128);
        Promise::new(who).transfer(refund)
    }
}

Clearing whole collections#

OperationWork doneNotes
remove(&key)One storage removalThe normal, bounded way to free data
clear() on IterableMap, IterableSet or VectorOne removal per entryFine for small collections; on large ones it runs out of gas, so remove in capped batches instead
LookupMap / LookupSetNo clear()They keep no list of keys. You must know the keys, from your own index or an off-chain indexer, and remove them one by one
Dropping the field in an upgradeNothingThe entries stay in storage under the old prefix, still locking NEAR, until you remove them
What it takes to free a collection’s storage

Migrations: free the old state#

An upgrade changes code, not state. A migrate method marked #[init(ignore_state)] reads the old layout with env::state_read::<OldState>() and returns the new struct. If the new version no longer needs a collection, removing the field is not enough: delete its entries (in batches if there are many) or they keep locking NEAR forever. Migration is also the moment to move a large inline Vec or HashMap into a store collection and shrink the root record.

  • Smaller code releases stake too. Deploying a smaller Wasm unlocks the difference in code size (1 NEAR per 100 KB).
  • Delete test and dev accounts you no longer need. near-cli’s account delete-account command sends the remaining balance, including released storage stake, to a beneficiary account you choose.
  • Clear state before deleting an account. The protocol refuses to delete an account whose state, not counting contract code, exceeds 10,000 bytes (DeleteAccountWithLargeState).
  • Withdraw what is now available. Released stake sits on the contract account as ordinary balance; move it out with a transfer if it is not needed there.

Check yourself

3 questions · progress saved in this browser

  1. 1.A contract removes an entry and frees 500 bytes. What happens to the stake?
  2. 2.Why can you not simply clear a large LookupMap in one call?
  3. 3.An upgrade removes the history: Vector<String> field from the contract struct. What happens to its entries?