Skip to main content
Version: FireSquid

Query the contract state

In order to make on-chain calls, one must set up a Websocket JSON-RPC endpoint using setDataSource(). We recommend using a private endpoint from e.g. BlastAPI, and set it via an environment variable:

//... 
.setDataSource({
chain: process.env.ETHEREUM_MAINNET_RPC,
archive: lookupArchive('eth-mainnet'),
})
//...

For local runs, simply update the local .env file and when the squid deployed to Aquarium define at as a secret on your Aquairum account.

Contract class

The EVM contract state is accessed using the Contract class generated by squid-evm-typegen(1). It takes a handler context and a contract address as constructor arguments. The state is always accessed at the context block height unless explicitly defined in the constructor.

For example, assume that we index an ERC721 contract. Typescript ABI module generated with squid-evm-typegen will contain the following class:

export class Contract extends ContractBase {
//...
balanceOf(owner: string): Promise<ethers.BigNumber> {
return this.call("balanceOf", [owner])
}
//...
}

Now suppose we want to query our contract from the batch handler. To create a Contract pass the context object and the current Block to its constructor, then query the contract state at that block:

// ...
const CONTRACT_ADDRESS= '0xb654611f84a8dc429ba3cb4fda9fad236c505a1a'

processor.run(new TypeormDatabase(), async ctx => {
for (const block of ctx.blocks) {
for (const item of block.items) {
const contract = new abi.Contract(ctx, block.header, CONTRACT_ADDRESS);
// query the contract state at the current block
const uri = await contract.balanceOf('0xd8da6bf26964af9d7eed9e03e53415d37aa96045')
}
}
})

For more information on EVM Typegen, see this dedicated page.

Batch state queries

The MakerDAO Multicall contract was designed to batch multiple state queries into a single contract call. In the context of indexing, it normally significantly improves the indexing speed since JSON RPC calls are typically the bottleneck.

Multicall contracts are deployed in many EVM chains, see the contract repo for addresses. You can use any of them with a multicall Typescript module that is generated when running squid-evm-typegen with --multicall option. The module exports a Multicall class with this method:

tryAggregate<Args extends any[], R>(
func: Func<Args, {}, R>,
calls: [address: string, args: Args][],
paging?: number
): Promise<MulticallResult<R>[]>

The arguments are as follows:

  • func: the contract function to be called
  • calls: an array of tuples [contractAddress: string, args]. Each specified contract will be called with the specified arguments.
  • paging an (optional) argument for the maximal number of calls to be batched into a single JSON PRC request. Note that large page sizes may cause timeouts.

A typical usage is as follows:

// generated by evm-typegen
import { functions } from "./abi/mycontract";
import { Multicall } from "./abi/multicall";

const MY_CONTRACT='0xac5c7493036de60e63eb81c5e9a440b42f47ebf5'

processor.run(new TypeormDatabase(), async (ctx) => {
for (let c of ctx.blocks) {
for (let i of c.items) {
// some logic
}
}
const lastBlock = ctx.blocks[ctx.blocks.length - 1];
// Multicall address for Ethereum is 0x5ba1e12693dc8f9c48aad8770482f4739beed696
const multicall = new Multicall(ctx, lastBlock, '0x5ba1e12693dc8f9c48aad8770482f4739beed696')
// call MY_CONTRACT.myContractMethod("foo") and MY_CONTRACT.myContractMethod("bar")
const args = ["foo", "bar"]
const results = await multicall.tryAggregate(functions.myContractMethod, args.map(a => [MY_CONTRACT, a]) as [string, any[]], 100);

results.forEach((res, i) => {
if (res.success) {
ctx.log.info(`Result for argument ${args[i]} is ${res.value}`);
}
})
});