Smart Contract Deployment on Near: Rust, Accounts, and Storage

Deploying code to mainnet is the point where timelines, data, and trust in releases are lost. We deploy and maintain smart contracts turnkey: we prepare the environment, migrate state without loss, and hand the team a clear upgrade procedure. The client gets predictable releases and reduced deployment time.

Blockchain Development Services

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1375
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1349
  • B2B Advance company logo design
    B2B Advance company logo design
    794
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1087
  • AIDER company logo development
    AIDER company logo development
    1032
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1129

Deploying smart contracts on Near: bottlenecks and failures

Deploying Near smart contracts is not one operation but two independent ones: uploading code to the account storage and calling an initializer method that creates state. The manual order breaks in predictable ways: the code was updated, but the state schema for it was not.

The contract is published successfully, but the first call fails while parsing stored data, and the account remains in a state with no standard way out.

The second category of failures is the build. If the compiler version, dependency set, and optimization parameters are not pinned, the same source produces different artifacts.

A non-reproducible build means the published code cannot be verified against the repository: you can neither confirm the absence of extraneous changes nor build exactly the same version for rollback.

The third is access. The account keys used for publication often live in command history, in chat, or on a single engineer's machine.

Losing account keys does not stop an already running contract, but it closes off the ability to update: the account is left without an owner, and there is no way to regain control.

Separately, there is the lack of rollback. Publication to mainnet is irreversible, and if the previous code was not saved and tested on testnet, a contract publication error surfaces on live users. Below, we break down how this is addressed.

What managed deployment provides: speed and predictability

Managed deployment starts with a repeatable build: the same source and pinned dependencies produce an identical artifact, so what has been tested on testnet goes to mainnet without discrepancies.

Time to a working launch in a clean environment fits into a single pass — account preparation, publication, and state initialization follow a pre-described order rather than the operator's memory.

The share of failed publications on Near drops first. Before the process was put in order, roughly every third attempt failed: the account was not created, the key was wrong, or the storage did not have enough staking to cover the contract state.

Now these checks pass before the transaction is sent, and only what has already run on a copy goes to mainnet.

Recovery time after a failure shrinks from hours to tens of minutes. It is known which artifact is deployed, which state corresponds to it, and which version can be rolled back to — rollback becomes an operation instead of an investigation.

Along with this, a whole class of failures goes away: incorrect account addresses, incomplete state migrations, manual changes that never made it into history. Deployment stops being a tense event and becomes a routine step that can be assigned to an on-call engineer.

Deployment modes: comparison of approaches and limitations

Deployment modes differ not by the launch tool but by who initiates publication and what happens to the contract data at that moment.

Code is updated in a single operation, while state remains as before — if the record structure has changed, the new code starts reading incompatible data.

Therefore, on Near, the choice of mode is determined by three parameters: reversibility, access requirements, and the cost of an error.

Option When it fits Limitations
Manual publication from a local environment First launch, debugging, one-off changes Everything depends on the engineer's machine: keys and build version are not recorded anywhere, and the repeat is not reproducible
Publication pipeline Regular releases, pre-publication checks, multiple environments Requires secure access in the build environment; an erroneous build goes out without a confirmation pause
Update by state schema The structure of stored data changes Requires a separate state transfer step, a copy before launch, and verification of transfer completeness
Account change The owner changes or a clean address is needed Data between accounts is not transferred automatically; old history and external links become outdated

Modes are more often combined than chosen in isolation: code goes out through the pipeline, state transfer is performed as a separate step with a preliminary copy, and an account change is planned as a separate campaign — with a period when both addresses run in parallel and traffic is migrated gradually.

Engineering stages: from audit to handover

We structure the process so that contract updates go out predictably and state does not have to be rebuilt manually. Skipping an audit of the storage schema or key permissions usually surfaces after deployment to mainnet — when fixing it is most expensive.

  1. Code and storage schema audit. We analyze the state structure: which fields remain unchanged, what will require migration, how much storage the contract account takes up. We identify where an update will affect already written data.
  2. Account and role schema. On Near, the account is the contract, so we separate permissions: subaccounts for contracts, a separate key for deployment, and function keys only for calling specific methods. Code changes and balance withdrawals are not combined in a single key.
  3. Reproducible build. We set compiler parameters and the artifact formation order so that any engineer gets an identical result, and the build hash matches what goes to the network.
  4. Test environments. A local node for quick runs and a separate testnet for integration, where we verify state migrations, key operation, and scenarios with real finality latency.
  5. Pre-release verification. We run tests along critical contract paths, check deposit boundary values, and rehearse rollback to the previous artifact.
  6. Phased deployment. First a limited list of accounts, then all — with monitoring of storage usage and call failures.
  7. Handover and training. We provide documentation, the account and role schema, access credentials, and update release instructions so the team can continue without us.

Accounts and storage on Near: access policies

On Near, access is determined not only by the contract code but also by the account hierarchy: each has its own key, its own set of permitted calls, and its own volume of data in persistent storage.

Therefore, the deliverable includes not only the contract but also documents by which access and storage can be reproduced and handed over to another team — without reconstructing logic from chat.

  • Account and role map: owner, account for rolling out new versions, service accounts for automated calls, and read-only roles — permissions are not mixed in a single key.
  • Access policies: which methods are available to each role, what requires confirmation by a second key, and what is restricted by an address list or time window.
  • State storage schema: which data resides in persistent storage, what is computed on the fly, how record keys are structured, and how the storage balance changes as volume grows.
  • Update procedure: the order for rolling out a new version — preparing state transfer, running on a network copy, switching over — with points where the process can be stopped safely.
  • Rollback procedure: how to restore the previous version together with compatible state, who makes the decision, and how long the operation takes.
  • Environment separation: separate key sets for testnet and mainnet, with a prohibition on transferring keys and data between them.
  • Operations log: which actions are recorded, where records are stored, and how to use them to analyze an incident without rebuilding the contract.

Documentation is not for reporting: six months later, a new engineer joins the project through it. We keep everything listed in the repository next to the code — the Rust contract and access descriptions change in the same changeset.

Case study: migrating logic to a new account while preserving data

Before

Both the logic and the state lived on one account, and releasing a version consisted of manual steps: build the contract, upload the code, catch up the state, and verify it by eye.

The build was not repeatable — two runs from the same source produced different artifacts, and the discrepancy surfaced on the live account. There was no rollback: on error, the account was restored from a copy, and downtime stretched to hours.

After

Migrating logic to a new account on Near was broken down into a reproducible build and a separate state migration step. The script computes keys and checksums on the old account, then on the new one, and stops on a discrepancy — broken state does not go into production.

The old account remains the rollback point: until checks pass, the switch is canceled with a single operation.

Manual steps went from 12 to 3, and the migration takes about half an hour instead of four hours. All migrations proceed without state discrepancies, whereas previously roughly every second attempt failed.

Returning to the previous account takes minutes, not hours of restoring from a copy, and is performed by the same command as the migration.

What to do if the logic is already deployed and stores data?

Contract updates on Near work like this: the code is overwritten entirely, while the state storage remains untouched.

Therefore, redeploying to the same account does not by itself reset data — the risk is elsewhere: the new code starts reading old keys under a changed schema and fails on the very first call or, worse, silently returns incorrect values.

Schema compatibility is maintained through discipline: fields are added to the end of the structure, the order and types of existing fields are not touched unnecessarily, and incompatible changes are moved into a separate migration method.

It reinitializes the contract with explicit disregard for the previous state, reads old keys, lays them out in the new format, and writes them back in a single call — so that state does not remain in an intermediate form if interrupted.

Moving to a new account is needed when the owner or the address itself changes. There is no automatic migration here: state is transferred by reading keys and writing to the new account, or the old one is left as a thin layer that delegates calls forward.

The second path is preferable if wallets, the frontend, and other contracts already reference the address — their calls continue to work without changes.

To keep the application working, the previous method names and signatures are preserved, and migration is moved into a separate call that runs before the switch.

Rehearsal takes place on a copy of the state, with checksums compared before and after — balances, number of records, availability of public methods. The previous code is kept at hand for a rollback deployment if checks on live data do not match.

Technical concerns before starting work

Before starting, almost every team asks the same questions: where keys will be stored, what will happen to the account when code is updated, how to transfer access from staging to production, and whether someone else's contract can be trusted. Below are brief answers to each.

For us, this is not chat but part of the work: decisions are recorded in configuration, not in chat messages — otherwise, six months later no one will remember why a key has a particular permission level.

How do access keys avoid ending up in the repository and logs?

Private keys are stored outside the code: locally in system storage, and in automated builds in protected environment variables with restricted access.

For deployment, a key is issued with permission to call only specific contract methods, not full access to the account: leakage of such a key does not allow funds to be withdrawn. After project handover, permissions are revoked and new ones are issued.

What happens to the account and data when the contract is updated?

Code is updated in place, the account and its storage are preserved, so calls to the previous address continue to work. A break occurs if the data structure or method signatures have changed.

We describe the state migration in advance, add transfer functions, and run the update on an isolated staging environment before touching the production account.

How is access transferred between staging and production?

State cannot be transferred directly — the environments are independent. What is transferred is configuration: the list of accounts, the key set, the permission schema, and initialization data, while state is deployed anew using the same migrations. This way production repeats the tested staging scenario rather than copying it blindly.

Can an already written contract be trusted — your own or someone else's?

Before modifying someone else's code, we verify the published bytecode against the sources and dependency set: if they diverge, changing the logic is dangerous. Next comes checking key permissions, update points, and withdrawal logic. Any discrepancies found are recorded as a condition that must be resolved before work begins.

Let's discuss the task: we'll come back with an estimate and plan

To come back with an estimate and work plan rather than general statements, a minimal set of inputs is enough for us. The more specific the input, the more accurate the timelines and the fewer clarifying rounds before work begins.

What to send to get started:

  • A link to the repository or archive with the Rust contract sources, including the build manifest and the pinned compiler version.
  • A list and purpose of accounts: which contract is deployed where, who the owner is, which access keys are issued, and for which methods.
  • Storage description: which structures are written to state, expected volume, whether there are migrations, and how data transfer is planned.
  • Target network and operating mode: testnet or mainnet, required throughput, write limits.
  • Current deployment scheme: build scripts, manual steps, what is already automated and what is done by hand.
  • Verification requirements: which scenarios must pass before release and who confirms readiness.

We will review the contract, the state model, and key permissions separately — during deployment, these are three independent sources of failure.

We will look at where assumptions about state size are hardcoded, how the logic behaves with partially applied changes, and what happens to the account during a failed deployment.

We will come back with an estimate and work plan: stages, deployment order, rollback points to the previous state, the set of checks, and what will be required from your side at each step. Describe the task — we will send an analysis with specific timelines.