Зазначимо: коли команда DeFi-стартапу вирішила перенести свій протокол liquidity на Near, перший контракт з'їв 15 NEAR на storage через неправильну структуру даних — 15 000 транзакцій на балансах користувачів зберігалися в Vector, хоча потрібен був LookupMap. Так ми зіткнулися з головною відмінністю Near від EVM: тут за зберігання платить розробник. Наш досвід понад 5 років в екосистемі Near дозволяє уникнути таких помилок та заощадити до 40% витрат на storage при правильному проектуванні. Розберемо, як все зробити правильно, щоб не втрачати депозити.
Як влаштована акаунт-модель Near?
Near Protocol — це шардованна блокчейн-платформа з акаунт-моделлю на іменних акаунтах (myapp.near, myapp.testnet). Кожен смарт-контракт живе на іменованому акаунті, який зберігає код контракту та його стан. Вартість зберігання — 1 NEAR за 100 КБ стану, і ці кошти блокуються на балансі акаунта як storage staking. Важливо провести верифікацію контракту near на блокчейні.
Практичний наслідок: якщо контракт зберігає дані користувачів, потрібна логіка 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). LookupMap займає вдвічі менше storage, ніж Vector — наприклад, заміна Vector на LookupMap для 10 000 записів заощаджує ~100 NEAR. WASM-оптимізація зменшує розмір контракту до 30% — це майже в 1.5 рази менше, ніж без неї.
Середа виконання та мови
Near VM виконує WASM-байткод. Офіційно підтримуються дві мови.
Rust + near-sdk-rs — основний вибір для production. SDK надає макроси #[near_bindgen], #[init], BorshSerialize/BorshDeserialize для серіалізації стану. Borsh (Binary Object Representation Serializer for Hashing) — детермінований бінарний формат, обов'язковий для зберігання стану контрактів Near. Для розробки використовується Rust Near SDK.
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-переписана версія) для деплою. Для деплою смарт-контракту Near використовується cargo-near.
# Встановлення 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% (майже в 1.5 рази) та економить на 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 робочий день. Зв'яжіться з нами для оцінки проекту — проаналізуємо вимоги, запропонуємо оптимальний підхід і вкладемося у строки без сюрпризів.







