Reading Query Results

How to read query results from Axiom.

The areResponsesValid view function in the AxiomV1Query contract allows users to read block, account, and storage data from verified query results. This function has the following signature:

function areResponsesValid(
    bytes32 keccakBlockResponse,
    bytes32 keccakAccountResponse,
    bytes32 keccakStorageResponse,
    BlockResponse[] calldata blockResponses,
    AccountResponse[] calldata accountResponses,
    StorageResponse[] calldata storageResponses
) external view returns (bool);

Using the Axiom SDK to read query results

The Axiom SDK provides an interface to help you read verified results from Axiom. To start reading query results, first use the getResponseTreeForKeccakQueryResponse function to look up the a responseTree for the query which contains information about the result. This will require looking up the query by the keccakQueryResponse from the build function in QueryBuilder:

import { Axiom, AxiomConfig } from "@axiom-crypto/core";

const config: AxiomConfig = {
    providerUri: <your provider uri (such as from Alchemy, Infura, etc)>,
    version: "v1",
    chainId: 5, // Goerli; defaults to 1 (Ethereum Mainnet)
    mock: true, // builds proofs without utilizing actual Prover resources
}
const ax = new Axiom(config);
const responseTree = await ax.query.getResponseTreeForKeccakQueryResponse(
    <keccakQueryResponse>
);

The responseTree contains blockTree, accountTree, and storageTree, which encapsulate all information about blocks, accounts, and storage, respectively. The next step is to use getHexRoot() on each of the trees to generate keccakBlockResponse, keccakAccountResponse, and keccakStorageResponse, which encode the verified query result on-chain:

const keccakBlockResponse = responseTree.blockTree.getHexRoot();
const keccakAccountResponse = responseTree.accountTree.getHexRoot();
const keccakStorageResponse = responseTree.storageTree.getHexRoot();

Finally, format the block, account, and storage information you wish to verify by calling getValidationWitness which will return a ValidationWitnessResponse object with blockReponse, accountResponse, and storageResponse fields. Depending on the type of data you are interested in querying (see table below), you can use the specific appropriate getValidationWitness function call.

Block dataAccount dataStorage data
  • block number

  • address

  • nonce

  • balance

  • storage root

  • code hash

  • block number

  • address

  • slot number

  • slot value

In all cases, you will be passing in the data that you originally built the Query with.

Block data

To get the ValidationWitness for any of the data in the Block data column of the table above (block number or block hash), add the blockNumber for the QueryRow that you added to the QueryBuilder.

const blockWitness: ValidationWitnessResponse = ax.query.getValidationWitness(
    responseTree, <blockNumber>
);

Account data

To get the ValidationWitness for any of the data in the Account data column of the table above (block number, address, nonce, balance, storage root, or code hash), add the blockNumber and address for the QueryRow that you added to the QueryBuilder.

const accountWitness: ValidationWitnessResponse = ax.query.getValidationWitness(
    responseTree, <blockNumber>, <address>
);

Storage data

To get the ValidationWitness for any of the data in the Account data column of the table above (block number, address, slot number, slot value), add the blockNumber, address, and storage for the QueryRow that you added to the QueryBuilder.

const storageWitness: ValidationWitnessResponse = ax.query.getValidationWitness(
    responseTree, <blockNumber>, <address>, <storage slot>
);

Verifying the data on-chain

The ValidationWitnessResponse contains a blockResponse, an optional accountResponse object, and an optional storageResponse object. The type of data that you created the QueryRow for is the same type that you will push to either the BlockResponse[], AccountResponse[], or StorageResponse[] arrays that you will pass in to the areResponsesValid function.

You are now ready to verify this data against the on-chain result by calling the areResponsesValid view function:

const axiomV1Query = new ethers.Contract(
    ax.getAxiomQueryAddress() as string, 
    ax.getAxiomQueryAbi(), 
    wallet
);
const isValid = await axiomV1Query.areResponsesValid(
    keccakBlockResponse,
    keccakAccountResponse,
    keccakStorageResponse,
    [blockWitness.blockResponse],
    [accountWitness.accountResponse],
    [storageWitness.storageResponse]
);

Information is available here on how to use the data in your own smart contract.

Advanced on-chain reads

For more advanced users, we offer access to the raw Merkle-ized query results via isKeccakResultValid and isPoseidonResultValid. These allow validation of Keccak and Poseidon encoded block, account, and storage data in the Axiom Query Format. The Poseidon format may be especially useful for ZK developers.

function isKeccakResultValid(
    bytes32 keccakBlockResponse, 
    bytes32 keccakAccountResponse, 
    bytes32 keccakStorageResponse
) external view returns (bool);

function isPoseidonResultValid(
    bytes32 poseidonBlockResponse, 
    bytes32 poseidonAccountResponse, 
    bytes32 poseidonStorageResponse
) external view returns (bool);

Last updated