Skip to main content
Version: v4.0.0-nightly.20260113

Reading Contract Data

This guide shows you how to read data from Aztec contracts in TypeScript, including simulating function calls, reading raw logs, and retrieving typed events.

Prerequisites

Simulating functions

The simulate method executes a contract function locally and returns its result. It works with private, public, and utility functions. No transaction is created and no gas is spent.

const result = await contract.methods
.myFunction(arg1, arg2)
.simulate({ from: callerAddress });

The from option specifies which address context to use for the simulation. This is required for all simulations, though it only affects private function execution (public functions ignore this value).

Basic simulation

simulate_function
const balance = await contract.methods.balance_of_public(newAccountAddress).simulate({ from: newAccountAddress });
expect(balance).toEqual(1n);
Source code: yarn-project/end-to-end/src/composed/docs_examples.test.ts#L50-L53

Handling return values

For functions returning multiple values, destructure the result:

const [value1, value2] = await contract.methods
.get_multiple_values()
.simulate({ from: callerAddress });

Including metadata

Set includeMetadata: true to get additional information about the simulation:

const result = await contract.methods
.balance_of_public(address)
.simulate({ from: callerAddress, includeMetadata: true });

// Result includes:
// - result: the function return value
// - stats: execution statistics (timing, circuit sizes)
// - offchainEffects: any offchain effects emitted
// - estimatedGas: gas limit estimates (gasLimits and teardownGasLimits)
console.log("Balance:", result.result);
console.log("L2 gas limit:", result.estimatedGas.gasLimits.l2Gas);
console.log("DA gas limit:", result.estimatedGas.gasLimits.daGas);

Private function considerations

When simulating private functions, the caller must have access to any private state being read. The PXE only has visibility into notes belonging to registered accounts.

// This works if callerAddress owns the notes
const balance = await contract.methods
.balance_of_private(callerAddress)
.simulate({ from: callerAddress });

// This fails if callerAddress doesn't have access to otherAddress's notes
const otherBalance = await contract.methods
.balance_of_private(otherAddress)
.simulate({ from: callerAddress }); // Error: cannot access private state
warning

Simulation runs locally without generating proofs. No correctness guarantees are provided on the result. See Call Types for more details.

Reading logs vs events

Contracts emit data in two forms you can read:

AspectLogsEvents
WhatRaw field arrays (untyped)Decoded domain objects with type info
StorageArchiver (node-level)PXE (client-level) for private events
APIaztecNode.getPublicLogs()wallet.getPrivateEvents() or getDecodedPublicEvents()
Type awarenessNone - raw Fr[] dataRequires ABI metadata to decode

Logs are the low-level transport layer, while events are the semantic application layer decoded using ABI metadata from your contract.

Reading raw public logs

Use aztecNode.getPublicLogs() to retrieve raw log data:

// Get logs for a specific transaction
const logs = await aztecNode.getPublicLogs({ txHash: receipt.txHash });
const rawFields = logs.logs[0].log.getEmittedFields(); // Fr[]

// Get logs for a block range
const logFilter = {
fromBlock: startBlock,
toBlock: endBlock,
};
const publicLogs = (await aztecNode.getPublicLogs(logFilter)).logs;

Reading events

Events provide typed access to contract emissions. The event metadata from your contract artifact (Contract.events.EventName) contains the ABI type information needed for decoding.

Reading public events

Use the getDecodedPublicEvents helper to retrieve typed public events:

import { getDecodedPublicEvents } from "@aztec/aztec.js/events";
get_public_events
const collectedEvent0s = await getDecodedPublicEvents<ExampleEvent0>(
aztecNode,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
);

const collectedEvent1s = await getDecodedPublicEvents<ExampleEvent1>(
aztecNode,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
);
Source code: yarn-project/end-to-end/src/e2e_event_logs.test.ts#L137-L151

The function parameters are:

  • aztecNode - The node to query
  • Contract.events.EventName - Event metadata from the contract artifact (contains the event selector)
  • fromBlock - Starting block number
  • limit - Number of blocks to search
note

This function queries all public logs in the block range and filters by event selector, not by contract address. If multiple contracts emit events with the same selector, they will all be returned.

Reading private events

Private events are stored in the PXE with privacy scoping. Use wallet.getPrivateEvents() to retrieve them:

import type { PrivateEventFilter } from "@aztec/aztec.js/wallet";
import { BlockNumber } from "@aztec/foundation/branded-types";

The BlockNumber type is a branded type that wraps raw numbers for type safety. Use it when setting fromBlock and toBlock in filters.

get_private_events
const eventFilter: PrivateEventFilter = {
contractAddress: testLogContract.address,
fromBlock: BlockNumber(firstBlockNumber),
toBlock: BlockNumber(lastBlockNumber + 1),
scopes: [account1Address, account2Address],
};

// Each emit_encrypted_events call emits 2 ExampleEvent0s and 1 ExampleEvent1
// So with 5 calls we expect 10 ExampleEvent0s and 5 ExampleEvent1s
const collectedEvent0s = await wallet.getPrivateEvents<ExampleEvent0>(
TestLogContract.events.ExampleEvent0,
eventFilter,
);

const collectedEvent1s = await wallet.getPrivateEvents<ExampleEvent1>(
TestLogContract.events.ExampleEvent1,
eventFilter,
);
Source code: yarn-project/end-to-end/src/e2e_event_logs.test.ts#L66-L85

The PrivateEventFilter includes:

  • contractAddress - The contract that emitted the events
  • fromBlock / toBlock - Block range to search
  • scopes - Array of account addresses whose private state is being queried
  • txHash (optional) - Filter to a specific transaction

Private events return objects with an event property containing the decoded data:

collectedEvents.forEach((ev) => {
console.log(ev.event.value0); // Access event fields via .event
});

Polling for events

To continuously monitor for new events, poll at regular intervals while tracking the last processed block:

import { BlockNumber } from "@aztec/foundation/branded-types";

let lastProcessedBlock = startBlock; // BlockNumber type

async function pollForEvents() {
const currentBlock = await aztecNode.getBlockNumber();

if (currentBlock > lastProcessedBlock) {
const events = await getDecodedPublicEvents<Transfer>(
aztecNode,
TokenContract.events.Transfer,
lastProcessedBlock + 1,
currentBlock - lastProcessedBlock
);

for (const event of events) {
// Process each event
console.log(`Transfer: ${event.amount} from ${event.from}`);
}

lastProcessedBlock = currentBlock;
}
}

// Poll every 10 seconds
setInterval(pollForEvents, 10000);

For private events, use the same pattern with wallet.getPrivateEvents() and update the fromBlock in your filter accordingly.

Next steps