Near Learn

NEP-145: Storage management

NEP-145 storage management standard on NEAR: storage_deposit, withdraw, unregister and balance bounds — why you must register before receiving a token.

Intermediate5 min read3-question check

On NEAR, every byte a contract stores must be backed by NEAR locked on the contract’s account (storage staking). A token contract that adds a balance row for every new holder would slowly have its own balance eaten by strangers — anyone could send 1 token to a million fresh accounts.

NEP-145 fixes that with a standard way for users to pay for their own storage. Before an account can hold a token, someone attaches a NEAR deposit to storage_deposit to “register” it. The deposit stays locked as that account’s storage balance and can be reclaimed by unregistering later. NEP-145 is generic — fungible tokens are the main user, but any contract that keeps per-user state can implement it.

Methods#

MethodKindWhat it doesDeposit
storage_deposit(account_id?, registration_only?)changeRegister account_id (default: the caller) and/or top up its storage balance. With registration_only: true, anything above the minimum is refunded.NEAR to cover storage
storage_withdraw(amount?)changeWithdraw unused (available) storage balance; omit amount to withdraw all of it.exactly 1 yoctoNEAR
storage_unregister(force?)changeRemove the caller’s registration and refund the deposit. Fails if the account still holds tokens, unless force: true (which burns them). Returns true if the account was registered.exactly 1 yoctoNEAR
storage_balance_bounds()view{ min, max } — the minimum deposit to register, and the maximum the contract will ever need (or null if unbounded).—
storage_balance_of(account_id)view{ total, available } for a registered account, or null if it is not registered.—
Register a friend, then send them tokens
Shell
# 1. how much does registration cost on this token?
near view usdt.tether-token.near storage_balance_bounds '{}'
# → { "min": "1250000000000000000000", "max": "1250000000000000000000" }  (example values, in yoctoNEAR)

# 2. is bob already registered?
near view usdt.tether-token.near storage_balance_of '{"account_id": "bob.near"}'
# → null  (not registered)

# 3. register bob (anyone may pay for anyone)
near call usdt.tether-token.near storage_deposit \
  '{"account_id": "bob.near", "registration_only": true}' \
  --accountId alice.near --deposit 0.00125

# 4. now the transfer can succeed
near call usdt.tether-token.near ft_transfer \
  '{"receiver_id": "bob.near", "amount": "1000000"}' \
  --accountId alice.near --depositYocto 1

Gotchas#

  • Sending to an unregistered account with ft_transfer fails — the tokens are not lost, the call just panics. Check storage_balance_of first.
  • For fungible tokens min and max are normally equal: each holder needs one fixed-size balance row, so there is nothing to top up and available stays 0.
  • Never hard-code the deposit amount. Read storage_balance_bounds().min from the contract — it differs per token and storage prices can change.
  • Prefer registration_only: true when you only want to register; the contract refunds the excess instead of keeping it as extra storage balance.
  • storage_unregister(force: true) burns any remaining tokens. Wallets should never send force without a very loud warning.

Check yourself

3 questions · progress saved in this browser

  1. 1.Why does NEP-145 exist?
  2. 2.Alice calls ft_transfer to an account that has never been registered via storage_deposit. What happens?
  3. 3.What is the effect of storage_unregister with force: true on an account holding tokens?