Skip to main content
Learn how to update NEAR smart contracts, both through tools like NEAR CLI and programmatically. Understand the implications of state migration when changing contract logic. NEAR accounts separate their logic (contract’s code) from their state (storage), allowing the code to be changed. Contract’s can be updated in two ways:
  1. Through tools such as NEAR CLI or the NEAR API (if you hold the account’s full access key).
  2. Programmatically, by implementing a method that takes the new code and deploys it.

Updating Through Tools

Simply re-deploy another contract using your preferred tool, for example, using NEAR CLI:

Programmatic Update

A smart contract can also update itself by implementing a method that:
  1. Takes the new wasm contract as input
  2. Creates a Promise to deploy it on itself

How to Invoke Such Method?

DAO FactoriesThis is how DAO factories update their contracts

Migrating the State

Since the account’s logic (smart contract) is separated from the account’s state (storage), the account’s state persists when re-deploying a contract. Because of this, adding methods or modifying existing ones will yield no problems. However, deploying a contract that modifies or removes structures stored in the state will raise an error: Cannot deserialize the contract state, in which case you can choose to:
  1. Use a different account
  2. Rollback to the previous contract code
  3. Add a method to migrate the contract’s state

The Migration Method

If you have no option but to migrate the state, then you need to implement a method that:
  1. Reads the current state of the contract
  2. Applies different functions to transform it into the new state
  3. Returns the new state
DAO UpdateThis is how DAOs update themselves

Example: Guest Book Migration

Imagine you have a Guest Book where you store messages, and the users can pay for such messages to be “premium”. You keep track of the messages and payments using the following state:

Update Contract

At some point you realize that you could keep track of the payments inside of the PostedMessage itself, so you change the contract to:

Incompatible States

If you deploy the update into an initialized account the contract will fail to deserialize the account’s state, because:
  1. There is an extra payments vector saved in the state (from the previous contract)
  2. The stored PostedMessages are missing the payment field (as in the previous contract)

Migrating the State

To fix the problem, you need to implement a method that goes through the old state, removes the payments vector and adds the information to the PostedMessages: Notice that migrate is actually an initialization method that ignores the existing state (#[init(ignore_state)]), thus being able to execute and rewrite the state.
To understand why we should remove old structures from the state let’s take a look to how the data is stored.For example, if the old version of the contract stores two messages with payments according methods get_messages and get_payments will return the following results:
But if we take a look at the storage as text using following command, we will see that each payment is stored under its own key started with p\ prefix.
That means that while migrating the state to a new version we need not only change the messages structure, but also remove all payments related keys from the state. Otherwise, the old keys will simply stay behind being orphan, still occupying space.To remove them in migrate method, we call clear() method on payments vector in mutable old_state struct. This method removes all elements from the collection.
You can follow a migration step by step in the official migration example

State versioning

State versioning lets old and new representations coexist. Instead of rewriting every stored value during one migration, store an enum whose variants represent each supported version:
When the contract reads an older variant, it can convert it to the latest representation before applying new logic. This makes future changes easier, but it also means the contract must continue understanding every version that can remain in storage. See the state-versioning example for a complete implementation.

Locking a contract account

You can prevent external actors from upgrading a contract by removing every full-access key from its account. Once the keys are removed, nobody can sign transactions in the account’s name to deploy new code or transfer its balance. First, list the account’s keys and identify every full-access key:
Then remove each full-access key:
Removing all full-access keys cannot be undone through an externally signed transaction. Confirm that you have the intended upgrade and recovery design before locking the account.
A locked contract can still upgrade itself if its current code exposes an authorized programmatic update method. If the contract must be permanently immutable, do not include such a method.