Отметим: когда команда DeFi-стартапа решила перенести свой протокол liquidity на Near, первый контракт съел 15 NEAR на storage из-за неправильной структуры данных — 15 000 транзакций на пользовательских балансах хранились в Vector, хотя нужен был LookupMap. Так мы столкнулись с главным отличием Near от EVM: здесь за хранение платит разработчик. Наш опыт более 5 лет в экосистеме Near позволяет избежать таких ошибок и сэкономить до 40% затрат на storage при правильном проектировании. Разберём, как всё сделать правильно, чтобы не терять депозиты.
Как устроена аккаунт-модель Near?
В Near каждый смарт-контракт живёт на именованном аккаунте (myapp.near, myapp.testnet). Аккаунт хранит код контракта и его состояние. Стоимость хранения — 1 NEAR за 100 КБ состояния, и эти средства блокируются на балансе аккаунта. Это называется storage staking.
Практическое следствие: если контракт хранит данные пользователей, нужна логика storage_deposit — пользователь вносит NEAR-депозит перед регистрацией, эти средства покрывают его storage. Стандарт NEP-145 определяет этот паттерн для 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); // минимум 0.00125 NEAR за запись аккаунта assert!(amount >= STORAGE_PER_ACCOUNT, "Insufficient deposit"); // ... } Sub-аккаунты — стандартный паттерн для factory-контрактов: token.myapp.near, pool.myapp.near. Каждый sub-аккаунт — полноценный независимый аккаунт.
Почему storage staking критичен для вашего бюджета?
Выбор структуры данных напрямую влияет на затраты. Вот сравнение популярных коллекций:
| Коллекция | Итерация | Storage cost на 1000 записей | Gas на запись |
|---|---|---|---|
LookupMap |
Нет | ~0.01 NEAR | 2 TGas |
UnorderedMap |
Да | ~0.015 NEAR | 3 TGas |
Vector |
Да | ~0.02 NEAR | 4 TGas |
Rust-контракты на Near в 2-3 раза производительнее JavaScript при одинаковой логике — это подтверждают наши тесты на аналогичных операциях (1000 записей, ~3 TGas против ~8 TGas).
Среда выполнения и языки
Near VM исполняет WASM-байткод. Официально поддерживаются два языка.
Rust + near-sdk-rs — основной выбор для production. SDK предоставляет макросы #[near_bindgen], #[init], BorshSerialize/BorshDeserialize для сериализации состояния. Borsh (Binary Object Representation Serializer for Hashing) — детерминированный бинарный формат, обязательный для хранения состояния контрактов Near.
JavaScript/TypeScript + near-sdk-js — для прототипов и несложной логики. Компилируется в WASM через QuickJS, производительность заметно ниже, лимиты по gas жёстче.
Пример минимального Rust-контракта:
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 } } Кросс-контрактные вызовы и async-модель
Near — асинхронный шардированный блокчейн. Кросс-контрактный вызов не выполняется синхронно внутри транзакции — он создаёт отдельный receipt, который обрабатывается в следующем блоке. Результат приходит в 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) => { /* обработка */ } Err(_) => { env::panic_str("External call failed") } } } } #[private] — макрос, добавляющий проверку assert_eq!(env::current_account_id(), env::predecessor_account_id()). Без него любой может вызвать callback напрямую.
Сборка и деплой
Инструментарий: cargo-near — официальный CLI для сборки, near-cli-rs (Rust-переписанная версия) для деплоя.
# Установка cargo install cargo-near cargo install near-cli-rs # Сборка оптимизированного WASM cargo near build # Деплой на testnet near contract deploy myapp.testnet \ use-file ./target/near/myapp.wasm \ without-init-call \ network-config testnet \ sign-with-keychain \ send WASM-файл после сборки с cargo-near проходит через wasm-opt (Binaryen) автоматически — это уменьшает размер до 30% и экономит на storage.
Для mainnet деплой через multisig-аккаунт или через near-cli с hardware wallet (--useLedgerKey). Деплой без инициализации — контракт в неинициализированном состоянии, первый вызов new() задаёт начальное состояние.
Тестирование
Unit-тесты — стандартные Rust unit tests с VMContextBuilder для мока окружения (predecessor, attached_deposit, block_timestamp).
Workspaces-rs — интеграционные тесты, запускают локальный sandbox Near и деплоят реальный WASM. Это де-факто стандарт для тестирования кросс-контрактных взаимодействий:
#[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(()) } Типичные ошибки при переносе с EVM
- Нет
msg.senderкак адреса. В Nearenv::predecessor_account_id()— строка, не 20 байт. Сравнение через==дляAccountId. - Паника вместо revert.
env::panic_str("message")илиassert!()— аналогrequire()в Solidity. Стоимость газа за панику не возвращается. - Итерация по
LookupMap.LookupMapне итерируемый. Для итерируемых коллекций —UnorderedMap,Vector. Выбор структуры данных влияет на gas. - Gas единицы. 1 TGas = 10¹² gas. Простой вызов ~2-3 TGas, кросс-контрактный +5 TGas минимум. Лимит транзакции — 300 TGas.
Подробнее о sub-аккаунтах
Sub-аккаунты создаются через деплой контракта на имя вида `subdomain.main.near`. Они наследуют права от родительского аккаунта, но имеют собственное хранилище. Например, `token.mybank.near` — независимый контракт с балансом, не связанный с `mybank.near`. Это удобно для мультитокеновых платформ.Что входит в разработку под ключ
- Аудит требований и выбор языка (Rust/JS).
- Проектирование storage-модели — критично для cost (правильные типы коллекций снижают storage на 30-50%).
- Разработка с unit-тестами.
- Интеграционное тестирование через workspaces.
- Деплой на testnet, верификация через Near Explorer.
- Деплой на mainnet с multisig.
- Передача доступа к контракту и документация.
- Пост-деплой поддержка — 1 месяц включено.
Срок деплоя готового кода — 4 часа. Разработка контракта с нуля с тестированием: 1-5 дней в зависимости от сложности.
Мы гарантируем соблюдение стандартов Near (NEP-145, NEP-141) и предоставляем полную документацию. Наши инженеры имеют сертификацию Near Certified Developer.
Закажите разработку под ключ — мы оценим ваш проект за 1 рабочий день. Свяжитесь с нами для оценки проекта — проанализируем требования, предложим оптимальный подход и уложимся в сроки без сюрпризов.







