View Functions
View functions provide gas-free read access to on-chain state, enabling applications to query smart contract data without submitting transactions or paying fees. They are essential for building responsive user interfaces, analytics dashboards, and efficient off-chain systems that need to read blockchain state frequently.
Overview#
View functions in Aptos are special functions marked with the #[view] attribute that can be called through the REST API without creating a transaction. They are read-only, cannot modify state, and execute immediately without waiting for block confirmation. This makes them perfect for displaying balances, checking permissions, computing derived values, and providing real-time data to applications.
Defining View Functions#
View functions must be public and marked with the #[view] attribute:
module 0x1::token_info {
use std::signer;
use std::string::String;
struct TokenStore has key {
balance: u64,
name: String,
symbol: String,
decimals: u8
}
struct Supply has key {
total: u64,
max: u64
}
// Simple view function
#[view]
public fun balance_of(owner: address): u64 acquires TokenStore {
if (!exists<TokenStore>(owner)) {
return 0
};
borrow_global<TokenStore>(owner).balance
}
// View function with multiple parameters
#[view]
public fun allowance(owner: address, spender: address): u64 acquires Allowances {
// Return approved amount for spender
0
}
// View function returning multiple values
#[view]
public fun token_metadata(addr: address): (String, String, u8) acquires TokenStore {
let store = borrow_global<TokenStore>(addr);
(store.name, store.symbol, store.decimals)
}
// View function with computation
#[view]
public fun circulating_supply(): u64 acquires Supply {
let supply = borrow_global<Supply>(@0x1);
supply.total
}
// View function checking conditions
#[view]
public fun is_paused(): bool acquires Config {
borrow_global<Config>(@0x1).paused
}
// View function with vector return
#[view]
public fun get_holders(start: u64, limit: u64): vector<address> acquires HolderRegistry {
// Return list of token holders
vector::empty()
}
}
Calling View Functions#
REST API#
View functions are called via HTTP POST to the /v1/view endpoint:
curl -X POST https://api-aptos-mainnet.n.dwellir.com/YOUR_API_KEY/v1/view \
-H "Content-Type: application/json" \
-d '{
"function": "0x1::token_info::balance_of",
"type_arguments": [],
"arguments": ["0x123abc..."]
}'
Response:
{
"result": ["1000000"]
}
TypeScript SDK#
import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
const config = new AptosConfig({
network: Network.MAINNET,
fullnode: "https://api-aptos-mainnet.n.dwellir.com/YOUR_API_KEY/v1"
});
const aptos = new Aptos(config);
// Call view function
const balance = await aptos.view({
payload: {
function: "0x1::token_info::balance_of",
typeArguments: [],
functionArguments: ["0x123abc..."]
}
});
console.log(`Balance: ${balance[0]}`);
// Call view function with multiple returns
const [name, symbol, decimals] = await aptos.view({
payload: {
function: "0x1::token_info::token_metadata",
typeArguments: [],
functionArguments: ["0x1"]
}
});
Python SDK#
from aptos_sdk.client import RestClient
from aptos_sdk.account_address import AccountAddress
client = RestClient("https://api-aptos-mainnet.n.dwellir.com/YOUR_API_KEY/v1")
# Call view function
balance = client.view_function(
function="0x1::token_info::balance_of",
type_arguments=[],
arguments=["0x123abc..."]
)
print(f"Balance: {balance[0]}")
Complex View Functions#
Aggregating Data#
#[view]
public fun get_portfolio_value(owner: address): u64 acquires TokenStore, PriceOracle {
let total_value = 0u64;
let store = borrow_global<TokenStore>(owner);
let price = get_token_price(); // Internal helper
total_value = store.balance * price / 1000000; // Adjust for decimals
total_value
}
#[view]
public fun get_top_holders(limit: u64): vector<address> acquires HolderRegistry {
// Query and sort holders by balance
// Return top N addresses
vector::empty()
}
Pagination Support#
#[view]
public fun get_transactions(
account: address,
offset: u64,
limit: u64
): vector<Transaction> acquires TransactionHistory {
let history = borrow_global<TransactionHistory>(account);
// Return paginated results
vector::empty()
}
Real-World Use Cases#
-
Wallet Displays: Fetch token balances, NFT collections, and transaction history to display in user wallets without gas costs.
-
DeFi Dashboards: Query liquidity pool reserves, staking rewards, lending positions, and APY calculations for real-time financial data.
-
NFT Marketplaces: Check NFT ownership, metadata, listing prices, and royalty information to populate marketplace listings efficiently.
-
Analytics Platforms: Aggregate protocol metrics like total value locked, trading volumes, user counts, and historical statistics.
-
Gaming Applications: Retrieve player stats, inventory items, leaderboard positions, and game state without requiring transactions.
-
Permission Checks: Verify user access rights, admin privileges, or whitelist status before displaying UI elements or allowing actions.
Best Practices#
Keep Computations Light: View functions execute during API calls, so avoid expensive computations. Consider pre-computing values in transaction functions and storing them.
Return Meaningful Defaults: Use defensive programming to return sensible defaults (like 0 for balances) when resources don't exist, avoiding errors in calling code.
Use Pagination: For functions returning large datasets, implement offset and limit parameters to prevent timeouts and excessive response sizes.
Cache Results: On the application side, cache view function results when appropriate to reduce API calls and improve performance.
Validate Inputs: Check that addresses and parameters are valid to provide better error messages than internal assertion failures.
Document Return Types: Clearly document what each view function returns, especially when returning multiple values or complex structures.
Consider Gas Limits: While view functions don't charge gas, they still have execution limits. Very complex computations may timeout.
View Functions vs Entry Functions#
// Entry function - Modifies state, requires transaction
public entry fun transfer(from: &signer, to: address, amount: u64) acquires TokenStore {
// Mutates state
let from_store = borrow_global_mut<TokenStore>(signer::address_of(from));
from_store.balance = from_store.balance - amount;
// ...
}
// View function - Read-only, no transaction needed
#[view]
public fun balance_of(owner: address): u64 acquires TokenStore {
// Only reads state
borrow_global<TokenStore>(owner).balance
}
Error Handling#
#[view]
public fun safe_balance_of(owner: address): u64 acquires TokenStore {
// Handle non-existent resources gracefully
if (!exists<TokenStore>(owner)) {
return 0
};
let store = borrow_global<TokenStore>(owner);
store.balance
}
#[view]
public fun get_allowance_or_max(
owner: address,
spender: address
): u64 acquires Allowances {
if (!exists<Allowances>(owner)) {
return 0xFFFFFFFFFFFFFFFF // Return max u64 if no allowance set
};
// Return actual allowance
0
}
Performance Considerations#
View functions execute on fullnodes during API requests, so optimize for speed:
- Minimize the number of
borrow_globalcalls - Avoid nested loops with large iterations
- Use early returns to skip unnecessary computation
- Pre-compute complex values in transaction functions when possible
- Consider implementing summary resources for expensive aggregations
Related Concepts#
- Module Structure - Organize view functions effectively
- Resource Management - Read resources safely
- Testing - Test view function logic
- GraphQL API - Alternative querying approach