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#
| Method | Kind | What it does | Deposit |
|---|---|---|---|
storage_deposit(account_id?, registration_only?) | change | Register 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?) | change | Withdraw unused (available) storage balance; omit amount to withdraw all of it. | exactly 1 yoctoNEAR |
storage_unregister(force?) | change | Remove 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. | — |
# 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 1Gotchas#
- Sending to an unregistered account with
ft_transferfails — the tokens are not lost, the call just panics. Checkstorage_balance_offirst. - For fungible tokens
minandmaxare normally equal: each holder needs one fixed-size balance row, so there is nothing to top up andavailablestays0. - Never hard-code the deposit amount. Read
storage_balance_bounds().minfrom the contract — it differs per token and storage prices can change. - Prefer
registration_only: truewhen 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 sendforcewithout a very loud warning.
Check yourself
3 questions · progress saved in this browser