Skip to main content

Reading Chain State

Aleo programs keep two kinds of state: public mappings, which anyone can read, and private records, which only their owner can decrypt. This guide covers the public half. For records, see Working with Records.

Every read in this guide goes through a public client — no account, wallet, or private key is involved. A public client wraps a transport pointed at an Aleo node and exposes read-only actions over it.

import { createPublicClient, http } from '@provablehq/veil-core'

const publicClient = createPublicClient({
transport: http('https://api.provable.com/v2', { network: 'mainnet' }),
})

Mappings

A mapping is a program's on-chain key-value store, maintained by validators and updated each block a transaction writes to it. credits.aleo's account mapping, for example, holds every address's public credits balance — reading it is analogous to reading a contract's storage slot on Ethereum. Fetch a value with readContract (or its Aleo-native alias, readMapping):

import { parseValue } from '@provablehq/veil-core'

const raw = await publicClient.readContract({
programId: 'credits.aleo',
mapping: 'account',
key: 'aleo1q6qstg8q8shwqf5m6q5fcenuwsdqsvp4hhsgfnx5chzjm3secyzqt9mxm8',
})
// '5000000u64', or null when the account holds no public credits

const balance = raw === null ? 0n : parseValue(raw).value

The value comes back as the raw Aleo literal the node stores — a number, boolean, or struct literal with its type suffix still attached (u64, in the example above). parseValue decodes suffixed integers of every width (including field, scalar, and group), booleans, and aleo1... addresses, and throws on anything else — a struct literal needs manual parsing, or ABI-based decoding through a contract instance. Skip parseValue when the raw string is all that is needed.

A key that has never been written resolves to null — a mapping only holds entries a transaction has actually inserted, and absence is a normal answer, not an error. Two caveats. First, the node also answers null for a mapping or program name that does not exist, so a typo is indistinguishable from an absent key on this raw path; a contract instance validates both names against the ABI before requesting. Second, a malformed key literal does throw — a TransportError (HTTP 404 on the Provable API) — because the request itself is invalid, not because the key is absent.

For a typed read bound to a program's ABI instead of a raw mapping name and key, see Contract instances.

Discovering a program's mappings

Before reading a mapping, getMappingNames lists what a program exposes:

const mappings = await publicClient.getMappingNames({ programId: 'credits.aleo' })
// ['account', 'committee', 'bonded', 'unbonding', ...]

Blocks and transactions

getBlockNumber returns the current chain height, widened to bigint to match viem's return type even though the underlying value is a u32 on chain. Convert it with Number() before passing a height into an action such as getBlock:

const height = await publicClient.getBlockNumber()
const block = await publicClient.getBlock({ height: Number(height) })

getBlock returns the full block — header, ratifications, solutions, and confirmed transactions. Look up a specific transaction by its at1... id with getTransaction:

const tx = await publicClient.getTransaction({ id: 'at1...' })

See Types for the full Block and Transaction field reference, and Transaction Lifecycle for tracking a transaction from submission through acceptance.

Programs

getCode fetches a deployed program's Aleo instructions source — Veil's analogue of viem's getCode, which returns bytecode. Inspect the source to see a program's functions, mappings, and record types before calling it:

const source = await publicClient.getCode({ programId: 'credits.aleo' })

getDeploymentTransaction finds the transaction that deployed a program, including the deployer's address and signature:

const deployTx = await publicClient.getDeploymentTransaction({
programId: 'credits.aleo',
})