Call a View Function
This how-to demonstrates how to call a view function from off-chain and read its return values, without submitting a transaction or paying gas. The examples call the leaderboard module from Define a View Function:
/// Returns the number of recorded scores.
#[view]
public fun total_entries(board: &Leaderboard): u64 {
board.entries.length()
}
/// Returns the leading entry, or `none` if the leaderboard is empty.
///
/// Entries are kept ranked highest-first, so the leader is simply the first.
#[view]
public fun highest_score(board: &Leaderboard): Option<ScoreEntry> {
if (board.entries.is_empty()) option::none() else option::some(board.entries[0])
}
A function can only be called as a view if it is recorded in its module's on-chain view functions metadata, which is written when the package is published. This requires the network to have view function support enabled, which is available on devnet or local network.
Before You Begin
The examples on this page publish the view_functions package and call its views end-to-end, so they need a network that records view metadata — devnet or a local network (testnet and mainnet do not record it yet).
Each example takes a network flag and defaults to devnet:
--localnetmints a throwaway wallet and funds it from the local faucet. Start a local network first, e.g.iota-localnet start --force-regenesis --with-faucet.--devnet/--testnetuse the wallet configured in theiotaCLI (~/.iota) and assume it is already funded — the public faucets have no HTTP API, so fund your address at the faucet website first. The examples error if no wallet is configured or the address holds no coins.
The runnable examples live in the IOTA repository. From a checkout, execute whichever one you want to follow (shown here with --localnet):
- Rust SDK:
cargo run -p iota-sdk --example move_view_function_call -- --localnet - TypeScript SDK:
cd docs/examples/typescript && npm install && npm run call-view-function -- --localnet - Shell script (JSON-RPC with
curl):docs/examples/shell/call-view-function.sh --localnet
Call over JSON-RPC
- Call the
iota_viewmethod with the fully qualified function name<package_id>::<module_name>::<function_name>, the type arguments (empty for a non-generic view), and the function arguments. Objects are passed by their ID.
curl --location --request POST 'https://api.devnet.iota.cafe' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": 1,
"method": "iota_view",
"params": [
"<PACKAGE-ID>::leaderboard::total_entries",
[],
["<LEADERBOARD-ID>"]
]
}'
- Read the return values from the
functionReturnValuesarray.u64andu128values are returned as strings to avoid overflow.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"functionReturnValues": ["3"]
}
}
- Struct return values carry their type and fields. Calling
<PACKAGE-ID>::leaderboard::highest_scoreon the same leaderboard returns the leadingScoreEntry; a view returningOption<T>yields the inner value forsomeandnullfornone.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"functionReturnValues": [
{
"type": "<PACKAGE-ID>::leaderboard::ScoreEntry",
"fields": {
"player": "<PLAYER-ADDRESS>",
"score": "2500"
}
}
]
}
}
For the exact parameter and result schemas, see the iota_view reference. For a runnable version that publishes the package and makes each of these calls with curl against devnet, see the shell script below.
Error Cases
A call that fails validation — the target is not a recorded #[view] function, or the module or function does not exist — is rejected with a JSON-RPC error:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "Invalid Move View Function call: function submit_score in module leaderboard of package <PACKAGE-ID> is not declared as a #[view] function"
}
}
A call that passes validation but fails during execution (for example, a view that aborts) returns an executionError instead of functionReturnValues:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"executionError": "..."
}
}
Call over GraphQL
The GraphQL API exposes the same call as the moveViewCall query, with the same arguments and value encoding.
- Send the query with the fully qualified function name and the function arguments.
curl -i -X POST 'https://graphql.devnet.iota.cafe' \
--header 'Content-Type: application/json' \
--data-raw '{
"query": "{ moveViewCall(functionName: \"<PACKAGE-ID>::leaderboard::total_entries\", arguments: [\"<LEADERBOARD-ID>\"]) { error results } }"
}'
- Read the return values from
results; on execution failureresultsisnullanderrorholds the message.
{
"data": {
"moveViewCall": {
"error": null,
"results": ["3"]
}
}
}
Call a Generic View
The calls above target non-generic views, so their type arguments are empty. A generic view takes one type argument per type parameter, in declaration order. The vault module from Define a View Function is generic over the type T it stores, and its item view returns that value:
#[view]
public fun item<T: store>(vault: &Vault<T>): &T {
&vault.item
}
To read a Vault<Coin<IOTA>>, call item with 0x2::coin::Coin<0x2::iota::IOTA> as its type argument and the vault's object ID as its function argument. Type arguments are fully qualified type names, so a nested generic like Coin<IOTA> is written out in full.
Over JSON-RPC, the type arguments are the second parameter:
curl --location --request POST 'https://api.devnet.iota.cafe' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": 1,
"method": "iota_view",
"params": [
"<PACKAGE-ID>::vault::item",
["0x2::coin::Coin<0x2::iota::IOTA>"],
["<VAULT-ID>"]
]
}'
Over GraphQL, they go in typeArgs:
curl -i -X POST 'https://graphql.devnet.iota.cafe' \
--header 'Content-Type: application/json' \
--data-raw '{
"query": "{ moveViewCall(functionName: \"<PACKAGE-ID>::vault::item\", typeArgs: [\"0x2::coin::Coin<0x2::iota::IOTA>\"], arguments: [\"<VAULT-ID>\"]) { error results } }"
}'
Call from the Rust SDK
The Rust SDK exposes iota_view as view_function_call on the JSON-RPC client. This example publishes a package with #[view] functions and calls the non-generic counter::value:
let view_call_results = client
.http()
.view_function_call(
format!("{package_id}::counter::value"),
None,
vec![IotaJsonValue::new(serde_json::json!(counter_id))?],
)
.await?;
println!("{view_call_results:?}");
The type arguments are the second parameter. This call reads the generic vault::item view, filling in the type argument (Coin<IOTA>) alongside the object argument:
let vault_view_results = client
.http()
.view_function_call(
format!("{package_id}::vault::item"),
Some(vec![IotaTypeTag::new(
"0x2::coin::Coin<0x2::iota::IOTA>".to_string(),
)]),
vec![IotaJsonValue::new(serde_json::json!(vault_id))?],
)
.await?;
println!("{vault_view_results:?}");
Call from the TypeScript SDK
The TypeScript SDK (@iota/iota-sdk) exposes iota_view as client.view(), taking the same functionName, typeArgs, and arguments, and returning the same result shape — a union of functionReturnValues and executionError. This example publishes the view_functions package, creates a leaderboard with one score, and calls its views:
// Call a view returning a primitive. `u64` values arrive as strings.
const totalEntries = await iotaClient.view({
functionName: `${packageId}::leaderboard::total_entries`,
arguments: [leaderboardId],
});
if ('executionError' in totalEntries) {
throw new Error(`View call failed: ${totalEntries.executionError}`);
}
console.log('Total entries:', totalEntries.functionReturnValues[0]);
// Call a view returning `Option<ScoreEntry>`. `some` arrives as the
// struct's type and fields, `none` as null.
const highestScore = await iotaClient.view({
functionName: `${packageId}::leaderboard::highest_score`,
arguments: [leaderboardId],
});
if ('executionError' in highestScore) {
throw new Error(`View call failed: ${highestScore.executionError}`);
}
console.log('Highest score:', JSON.stringify(highestScore.functionReturnValues[0], null, 2));
For a generic view, pass the type arguments in typeArgs. This call reads vault::item on a Vault<Coin<IOTA>>:
// Call the generic `vault::item` view, filling in the type argument
// (`Coin<IOTA>`) and the function argument (the vault's object ID). The
// returned coin arrives as a struct carrying its type and fields.
const storedItem = await iotaClient.view({
functionName: `${packageId}::vault::item`,
typeArgs: ['0x2::coin::Coin<0x2::iota::IOTA>'],
arguments: [vaultId],
});
if ('executionError' in storedItem) {
throw new Error(`View call failed: ${storedItem.executionError}`);
}
console.log('Stored item:', JSON.stringify(storedItem.functionReturnValues[0], null, 2));
Full Example Code
The complete, runnable examples live in the IOTA repository:
- Rust SDK example — publishes the package and calls the views.
- TypeScript SDK example — same flow with
@iota/iota-sdk. - Shell script — publishes with the
iotaCLI and calls each view over JSON-RPC withcurl.