Smart Contract Deployment to NEAR Protocol

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Smart Contract Deployment to NEAR Protocol
Medium
from 4 hours to 2 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1217
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1046
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Deploying Smart Contracts to Near Protocol

Developers coming from the EVM ecosystem typically expect something similar to Solidity + Hardhat. Near works differently: contracts are WebAssembly modules, the account model is fundamentally different from Ethereum, and the developer pays for data storage, not the user. These differences must be understood before writing the first line of code, otherwise deployment turns into a series of unpleasant surprises.

Execution Environment and Languages

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], and 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. Compiles to WASM via QuickJS; performance is noticeably lower, and gas limits are stricter.

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
    }
}

Account Model and Storage

Key difference from EVM: in Near, each smart contract lives on a named account (myapp.near, myapp.testnet). The account stores the contract code and its state. Storage cost is 1 NEAR per 100 KB of state, and these funds are locked in 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 these funds cover their storage. 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.

Cross-Contract Calls and Async Model

Near is an asynchronous sharded blockchain. A cross-contract call doesn't execute synchronously within a transaction — it creates a separate receipt, which is processed in the next block. The result comes back via a callback.

#[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) => { /* handle result */ }
            Err(_) => { env::panic_str("External call failed") }
        }
    }
}

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

Building and Deployment

Tools: cargo-near is the 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 and saves on storage.

For mainnet deployment through a multisig account or via near-cli with a hardware wallet (--useLedgerKey). Deployment without initialization — the contract is in an uninitialized state; 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 Errors When Migrating from EVM

  • No msg.sender as an address. In Near env::predecessor_account_id() is a string, not 20 bytes. Comparison via == for AccountId.
  • Panic instead of revert. env::panic_str("message") or assert!() is the analog of require() in Solidity. Gas cost for panic is not refunded.
  • Iterating over LookupMap. LookupMap is not iterable. For iterable collections use UnorderedMap, Vector. Choice of data structure affects gas.
  • Gas units. 1 TGas = 10^12 gas. Simple call ~2-3 TGas, cross-contract +5 TGas minimum. Transaction limit is 300 TGas.

Workflow

Requirements audit → language choice (Rust/JS) → storage model design (critical for cost) → development with unit tests → integration testing via workspaces → testnet deployment → verification via Near Explorer → mainnet deployment.

4 hours timeline is for deploying ready code. Developing a contract from scratch with testing: 1-5 days depending on logic complexity.