Deploying Smart Contracts on Near: Rust, Accounts, and Storage

Deploying Smart Contracts on Near: Rust, Accounts, and Storage When a DeFi startup team decided to migrate its liquidity protocol to Near, the first contract consumed 15 NEAR on storage due to incorrect data structures — 15,000 user balance entries were stored in a `Vector` when they needed a `Lo

Blockchain Development Services

Frequently Asked Questions

העבודות האחרונות

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1441
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    998
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1267
  • image_logo-advance_0.webp
    B2B Advance company logo design
    713
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1003

Deploying Smart Contracts on Near: Rust, Accounts, and Storage

When a DeFi startup team decided to migrate its liquidity protocol to Near, the first contract consumed 15 NEAR on storage due to incorrect data structures — 15,000 user balance entries were stored in a Vector when they needed a LookupMap. That's when we faced the core difference between Near and EVM: here, the developer pays for storage. Our 5+ years of experience in the Near ecosystem helps avoid such mistakes and save up to 40% on storage costs with proper design. Let’s break down how to do it right so you don’t waste deposits.

How Near’s Account Model Works

In Near, each smart contract lives on a named account (myapp.near, myapp.testnet). The account stores the contract code and its state. The storage cost is 1 NEAR per 100 KB of state, and these funds are locked on the account balance. This is called storage staking.

Practical consequence: if the contract stores user data, you need storage_deposit logic — the user deposits NEAR before registration, and those funds cover their storage. The NEP-145 standard defines this pattern for fungible tokens.

#[payable] pub fn storage_deposit(&mut self, account_id: Option<AccountId>) -> StorageBalance { let amount = env::attached_deposit(); let account_id = account_id.unwrap_or_else(env::predecessor_account_id); // minimum 0.00125 NEAR per account record assert!(amount >= STORAGE_PER_ACCOUNT, "Insufficient deposit"); // ... } 

Sub-accounts are a standard pattern for factory contracts: token.myapp.near, pool.myapp.near. Each sub-account is a fully independent account.

Why Storage Staking Is Critical for Your Budget

The choice of data structure directly impacts costs. Here’s a comparison of popular collections:

Collection Iteration Storage cost per 1000 records Gas per write
LookupMap No ~0.01 NEAR 2 TGas
UnorderedMap Yes ~0.015 NEAR 3 TGas
Vector Yes ~0.02 NEAR 4 TGas

Rust contracts on Near are 2-3 times more performance-efficient than JavaScript for the same logic — our tests on identical operations (1000 records, ~3 TGas vs ~8 TGas) confirm this.

Runtime and Languages

The Near VM executes WASM bytecode. Two languages are officially supported.

Rust + near-sdk-rs — the primary choice for production. The SDK provides macros like #[near_bindgen], #[init], BorshSerialize/BorshDeserialize for state serialization. Borsh (Binary Object Representation Serializer for Hashing) is a deterministic binary format mandatory for storing Near contract state.

JavaScript/TypeScript + near-sdk-js — for prototypes and simple logic. It compiles to WASM via QuickJS, with noticeably lower performance and tighter gas limits.

Example of a minimal Rust contract:

use near_sdk::{near_bindgen, env, AccountId}; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize)] pub struct Counter { value: i64, } #[near_bindgen] impl Counter { #[init] pub fn new() -> Self { Self { value: 0 } } pub fn increment(&mut self) { self.value += 1; } pub fn get(&self) -> i64 { self.value } } 

Cross-Contract Calls and Async Model

Near is an asynchronous sharded blockchain. A cross-contract call is not executed synchronously within the transaction — it creates a separate receipt that is processed in the next block. The result comes back in a callback. This model complicates design but allows error handling without full state rollback.

#[near_bindgen] impl MyContract { pub fn call_external(&mut self, contract_id: AccountId) -> Promise { ext_other_contract::ext(contract_id) .with_static_gas(Gas(5 * TGAS)) .get_value() .then( Self::ext(env::current_account_id()) .with_static_gas(Gas(5 * TGAS)) .on_value_received() ) } #[private] pub fn on_value_received(&mut self, #[callback_result] result: Result<u64, PromiseError>) { match result { Ok(value) => { /* processing */ } Err(_) => { env::panic_str("External call failed") } } } } 

#[private] — a macro that adds the check assert_eq!(env::current_account_id(), env::predecessor_account_id()). Without it, anyone can call the callback directly.

Build and Deploy

Tooling: cargo-near — official CLI for building, near-cli-rs (Rust-rewritten version) for deployment.

# Installation cargo install cargo-near cargo install near-cli-rs # Build optimized WASM cargo near build # Deploy to testnet near contract deploy myapp.testnet \ use-file ./target/near/myapp.wasm \ without-init-call \ network-config testnet \ sign-with-keychain \ send 

The WASM file after building with cargo-near automatically passes through wasm-opt (Binaryen) — this reduces size by up to 30% and saves on storage.

For mainnet deployment, use a multisig account or near-cli with a hardware wallet (--useLedgerKey). Deploying without initialization leaves the contract uninitialized; the first call to new() sets the initial state.

Testing

Unit tests — standard Rust unit tests with VMContextBuilder to mock the environment (predecessor, attached_deposit, block_timestamp).

Workspaces-rs — integration tests that run a local Near sandbox and deploy real WASM. This is the de facto standard for testing cross-contract interactions:

#[tokio::test] async fn test_full_flow() -> anyhow::Result<()> { let worker = near_workspaces::sandbox().await?; let wasm = std::fs::read("./target/near/myapp.wasm")?; let contract = worker.dev_deploy(&wasm).await?; let result = contract.call("increment") .transact().await?; assert!(result.is_success()); Ok(()) } 

Typical Mistakes When Migrating from EVM

  • No msg.sender as an address. In Near, env::predecessor_account_id() is a string, not 20 bytes. Comparison uses == for AccountId.
  • Panic instead of revert. env::panic_str("message") or assert!() — similar to require() in Solidity. Gas for panic is not refunded.
  • Iteration over LookupMap. LookupMap is not iterable. For iterable collections, use UnorderedMap or Vector. Choosing the right data structure affects gas.
  • Gas units. 1 TGas = 10¹² gas. A simple call ~2-3 TGas, a cross-contract call +5 TGas minimum. Transaction limit is 300 TGas.
More about sub-accounts Sub-accounts are created by deploying a contract to a name like `subdomain.main.near`. They inherit permissions from the parent account but have their own storage. For example, `token.mybank.near` is an independent contract with its own balance, not linked to `mybank.near`. This is convenient for multi-token platforms.

What's Included in Turnkey Development

  1. Requirements audit and language selection (Rust/JS).
  2. Storage model design — critical for cost (correct collection types reduce storage by 30-50%).
  3. Development with unit tests.
  4. Integration testing via workspaces.
  5. Deployment to testnet, verification via Near Explorer.
  6. Deployment to mainnet with multisig.
  7. Handover of contract access and documentation.
  8. Post-deployment support — 1 month included.

Deployment time for ready-to-go code — 4 hours. Contract development from scratch with testing: 1-5 days depending on complexity.

We guarantee compliance with Near standards (NEP-145, NEP-141) and provide full documentation. Our engineers are Near Certified Developers.

Order turnkey development — we will evaluate your project within 1 business day. Contact us for a project assessment — we will analyze requirements, propose the optimal approach, and meet deadlines without surprises.