Ethereum to NEAR Migration Guide
A comprehensive guide for Ethereum developers migrating to NEAR Protocol - covering concepts, code patterns, and practical migration steps.
Why Migrate to NEAR?β
- Human-readable accounts:
alice.nearinstead of0x7a3d... - Predictable gas costs: No fee spikes during congestion
- Built-in account abstraction: Access keys with granular permissions
- Horizontal scaling: Sharded architecture grows with demand
- Developer flexibility: Build in Rust or JavaScript
Conceptual Differencesβ
Account Modelβ
| Ethereum | NEAR |
|---|---|
20-byte address (0x...) | Named accounts (alice.near) |
| EOA vs Contract accounts | All accounts can hold code + data |
| Single private key = full control | Multiple access keys with permissions |
| No sub-accounts | Sub-accounts (app.alice.near) |
Key Insight: NEAR accounts are like domain names. You own alice.near and can create app.alice.near, nft.alice.near, etc.
Access Keysβ
NEAR's killer feature for UX:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Named Account (alice.near) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β FullAccess β β FunctionCallβ β FunctionCallβ β
β β Key (owner) β β Key (dApp) β β Key (game) β β
β β β β Only: swap()β β Only: play()β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- FullAccess keys: Like Ethereum private keys - full control
- FunctionCall keys: Limited to specific contract methods, with allowance
Gas Modelβ
| Ethereum | NEAR |
|---|---|
| Gas auction (higher bid = faster) | Fixed gas price |
| Fees spike during congestion | Predictable costs |
| User always pays | Contract can pay (prepaid gas) |
| ETH for gas | NEAR for gas |
NEAR Gas Costs (approximate):
- Simple transfer: ~0.00045 NEAR
- Contract call: ~0.0005-0.001 NEAR
- Contract deploy: ~0.05-0.5 NEAR
Storage Modelβ
| Ethereum | NEAR |
|---|---|
| Pay gas to write | Stake NEAR to reserve storage |
| Free reads | Paid reads AND writes |
| 256-bit storage slots | Key-value with serialization |
Storage Staking: 1 NEAR β 10KB of storage. When you delete data, you get the stake back.
Code Translationβ
Basic Mappingsβ
| Solidity | NEAR Rust | NEAR JS |
|---|---|---|
msg.sender | env::predecessor_account_id() | near.predecessorAccountId() |
tx.origin | env::signer_account_id() | near.signerAccountId() |
address(this) | env::current_account_id() | near.currentAccountId() |
msg.value | env::attached_deposit() | near.attachedDeposit() |
block.timestamp | env::block_timestamp() | near.blockTimestamp() |
require(cond, msg) | require!(cond, msg) | assert(cond, msg) |
ERC-20 β NEP-141 Tokenβ
Solidity (ERC-20):
function transfer(address to, uint256 amount) public returns (bool) {
require(_balances[msg.sender] >= amount, "Insufficient balance");
_balances[msg.sender] -= amount;
_balances[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
NEAR Rust (NEP-141):
#[payable]
pub fn ft_transfer(&mut self, receiver_id: AccountId, amount: U128, memo: Option<String>) {
require!(env::attached_deposit() >= 1, "Requires 1 yoctoNEAR");
let sender_id = env::predecessor_account_id();
let amount: u128 = amount.into();
self.internal_transfer(&sender_id, &receiver_id, amount, memo);
}
Storage Patternsβ
| Solidity | NEAR Rust |
|---|---|
mapping(address => uint) | LookupMap<AccountId, u128> |
mapping(address => mapping(...)) | Nested LookupMap with prefix |
uint[] array | Vector<T> |
address[] with iteration | UnorderedSet<AccountId> |
Cross-Contract Callsβ
Critical Difference: NEAR cross-contract calls are ASYNCHRONOUS.
Solidity (synchronous):
uint balance = IERC20(token).balanceOf(address(this));
// Balance available immediately
NEAR Rust (async with callback):
// Initiate call - returns Promise, not result
ext_token::ext(token_account)
.ft_balance_of(env::current_account_id())
.then(
Self::ext(env::current_account_id())
.on_balance_received()
)
// Handle result in callback
#[private]
pub fn on_balance_received(&self, #[callback_result] result: Result<U128, PromiseError>) -> U128 {
match result {
Ok(balance) => balance,
Err(_) => U128(0),
}
}
Migration Checklistβ
1. Environment Setupβ
# Install NEAR CLI
npm install -g near-cli-rs
# Install Rust (for contracts)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
# Create testnet account
near account create-account fund-myself myapp.testnet
2. Contract Migration Stepsβ
- Audit Solidity contracts - Identify all state variables, external calls, events
- Map to NEAR patterns - Use the translation tables above
- Handle async calls - Redesign any synchronous external calls
- Implement storage - Choose appropriate collections (LookupMap vs UnorderedMap)
- Add NEP-297 events - Replace Solidity events with standard logging
- Test thoroughly - Use near-workspaces for integration tests
3. Frontend Migrationβ
Before (ethers.js):
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const contract = new ethers.Contract(address, abi, signer);
await contract.transfer(to, amount);
After (NEAR wallet-selector):
import { setupWalletSelector } from '@near-wallet-selector/core';
const selector = await setupWalletSelector({ network: 'mainnet', modules: [...] });
const wallet = await selector.wallet();
await wallet.signAndSendTransaction({
receiverId: 'token.near',
actions: [{
type: 'FunctionCall',
params: { methodName: 'ft_transfer', args: { receiver_id: to, amount }, gas: '30000000000000', deposit: '1' }
}]
});
Common Gotchasβ
1. Async Cross-Contract Callsβ
Your Solidity mental model won't work. Design state machines that handle callbacks.
2. Storage Costsβ
Unlike Ethereum's "pay once" model, NEAR requires ongoing storage staking. Optimize data structures.
3. 1 yoctoNEAR Security Depositβ
NEP-141 transfers require 1 yoctoNEAR attached to prevent exploits. Don't forget it!
4. Access Key Managementβ
Leverage function-call keys for better UX. Users can approve limited permissions without full wallet access.
5. Account Creation Costsβ
Creating accounts costs NEAR (for storage). Budget for this in your dApp economics.
Aurora: The Fast Pathβ
If you need to migrate quickly, Aurora runs a full EVM on NEAR:
- Deploy Solidity contracts unchanged
- Use existing Ethereum tooling (Hardhat, Foundry)
- Bridge assets from Ethereum
- Slightly higher fees than native NEAR
Best for: Quick migrations, complex Solidity codebases, teams without Rust experience.