Docs

view_functions

Gas-free read operations

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:

move
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:

Bash
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:

JSON
{
  "result": ["1000000"]
}

TypeScript SDK

TypeScript
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

Python
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

move
#[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

move
#[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

  1. Wallet Displays: Fetch token balances, NFT collections, and transaction history to display in user wallets without gas costs.

  2. DeFi Dashboards: Query liquidity pool reserves, staking rewards, lending positions, and APY calculations for real-time financial data.

  3. NFT Marketplaces: Check NFT ownership, metadata, listing prices, and royalty information to populate marketplace listings efficiently.

  4. Analytics Platforms: Aggregate protocol metrics like total value locked, trading volumes, user counts, and historical statistics.

  5. Gaming Applications: Retrieve player stats, inventory items, leaderboard positions, and game state without requiring transactions.

  6. 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

move
// 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

move
#[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_global calls
  • 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