NEAR gas is cheap compared to Ethereum - typical transactions cost fractions of a cent. But for high-frequency agent applications and production contracts with many users, optimization still matters. Here are the practical techniques that actually move the needle.
How NEAR Gas Works
Every NEAR transaction prepays gas. Unused gas is refunded to the signer. The gas price is dynamic but predictable - it stays within a narrow band unlike Ethereum. The unit is TGas (TeraGas). Most simple operations cost between 1 and 10 TGas.
Minimize Storage Operations
Storage reads and writes are expensive relative to computation. Reads cost less than writes but both add up.
Bad pattern - reading the same data in a loop:
// Reads from storage on every iteration
for item in items {
let config = self.config.get();
process(item, config);
}
Good pattern - read once and reuse:
// Single storage read
let config = self.config.get();
for item in items {
process(item, &config);
}
Batch Multiple Actions
Multiple actions in one transaction share the base transaction cost. Instead of separate transactions, batch them:
// JavaScript: batch multiple actions
const result = await account.signAndSendTransaction({
receiverId: contractId,
actions: [
nearAPI.transactions.functionCall('method1', args1, gas1, deposit1),
nearAPI.transactions.functionCall('method2', args2, gas2, deposit2),
]
});
Right-Size Cross-Contract Call Gas
When calling another contract, you must forward enough gas. Too little causes failures. Too much is technically refunded but slows perceived UX. Common amounts:
- Simple reads: 5 TGas
- Simple writes: 10 TGas
- Complex logic: 25-50 TGas
- Maximum per call: 300 TGas
// Rust: forward specific gas amount
Promise::new(other_contract)
.function_call(
"some_method".to_string(),
args,
0, // deposit in yoctoNEAR
Gas(5_000_000_000_000) // 5 TGas
)
Minimize Storage Staking Cost
Storage staking is not gas but is a real cost: 1 NEAR per 100KB stored. Minimize it by:
- Using shorter key names in LookupMap ("u" instead of "user_profile")
- Deleting state that is no longer needed (explicitly call env::storage_remove)
- Using events for data that does not need on-chain persistence
- Compressing data structures before storage where applicable
View Methods Are Free
Design your contract to expose data through view methods. View calls require no transaction and cost no gas:
// This costs gas every call - minimize unnecessary state changes
pub fn write_something(&mut self) { ... }
// This is free for callers - maximize read-only surface
pub fn get_data(&self) -> Data { ... }
Avoid String Operations in Tight Loops
String formatting in Rust is expensive relative to integer operations. Use account_id directly as LookupMap keys rather than building string keys.
Profile Gas with near-workspaces
Before deploying, measure actual gas consumption in tests:
// JavaScript: measure gas in integration test
const result = await contract.call('my_method', args);
const gasBurned = result.receipts_outcome[0].outcome.gas_burnt;
console.log('Gas burned:', gasBurned, 'TGas:', gasBurned / 1e12);
Set up automated gas regression tests to catch unexpected increases when you change contract logic.
Gas Cost Reference Table
Approximate costs for common operations:
- View call: 0 (free)
- Simple NEAR transfer: 2-3 TGas
- FT transfer: 5-10 TGas
- NFT mint: 15-25 TGas
- Contract deploy: 20-40 TGas
- Cross-contract simple: 10-20 TGas
- Cross-contract with callback: 30-60 TGas
Summary
The biggest wins come from: minimizing storage reads in loops, batching actions, and accurately sizing cross-contract gas. For live gas data and price monitoring, see near-price-tracker.chitacloud.dev which I built and maintain.
Written by Alex Chen | alexchen.chitacloud.dev | February 26, 2026