Building indexers
Indexers are created using the defineIndexer
higher-order function. This function takes a stream definition and returns a function to define the indexer.
The job of an indexer is to stream and process historical data (backfilling) and then switch to real-time mode. Indexers built using our SDK are designed to handle chain-reorganizations automatically.
If, for any reason, you need to receive notifications about reorgs, you can define a custom message:invalidate
hook to handle them.
By default, the indexer is stateless (restarts from the beginning on restart) and does not provide any storage. You can add persistence and storage by using one of the provided storage plugins.
Examples
The following examples show how to create indexers for the Beacon Chain, EVM (Ethereum), and Starknet.
Beacon Chain indexer
import { BeaconChainStream } from "@apibara/beaconchain";
import { defineIndexer } from "@apibara/indexer";
export default defineIndexer(BeaconChainStream)({ /* ... */ });
EVM (Ethereum) indexer
import { EvmStream } from "@apibara/evm";
import { defineIndexer } from "@apibara/indexer";
export default defineIndexer(EvmStream)({ /* ... */ });
Starknet indexer
import { StarknetStream } from "@apibara/starknet";
import { defineIndexer } from "@apibara/indexer";
export default defineIndexer(StarknetStream)({ /* ... */ });
Indexer configuration
All indexers take the same configuration options.
streamUrl
string
The URL of the DNA stream to connect to.filter
TFilter
The filter to apply to the DNA stream. This argument is specific to the stream definition. You should refer to the chain's filter reference for the available options (see Beacon Chain, EVM (Ethereum), Starknet).finality
"finalized" | "accepted" | "pending"
Receive data with the specified finality. Defaults toaccepted
.startingCursor
{ orderKey: bigint, uniqueKey?: string }
The cursor to start the indexer from. Defaults to the genesis block. TheorderKey
represents the block number, and theuniqueKey
represents the block hash (optional).debug
boolean
Enable debug mode. This will print debug information to the console.transform
({ block, cursor, endCursor, finality, context }) => Promise<void>
The transform function called for each block received from the DNA stream.factory
({ block, context }) => Promise<{ filter?: TFilter }>
The factory function used to add data filters at runtime. Useful for creating indexers for smart contracts like Uniswap V2.hooks
object
The hooks to register with the indexer. Refer to the plugins & hooks page for more information.plugins
array
The plugins to register with the indexer. Refer to the plugins & hooks page for more information.
The transform function
The transform
function is invoked for each block received from the DNA stream. This function is where you should implement your business logic.
Arguments
block
TBlock
The block received from the DNA stream. This is chain-specific (see Beacon Chain, EVM (Ethereum), Starknet).cursor
{ orderKey: bigint, uniqueKey?: string }
The cursor of the block before the received block.endCursor
{ orderKey: bigint, uniqueKey?: string }
The cursor of the current block.finality
"finalized" | "accepted" | "pending"
The finality of the block.context
object
The context shared between the indexer and the plugins.
The following example shows a minimal indexer that streams block headers and prints them to the console.
import { EvmStream } from "@apibara/evm";
import { defineIndexer } from "@apibara/indexer";
export default defineIndexer(EvmStream)({
streamUrl: "https://ethereum.preview.apibara.org",
filter: {
header: "always",
},
async transform({ block }) {
const { header } = block;
console.log(header);
},
});
The factory function
The factory
function is used to add data filters at runtime. This is useful for creating indexers for smart contracts that deploy other smart contracts like Uniswap V2 and its forks.
Arguments
block
TBlock
The block received from the DNA stream. This is chain-specific (see Beacon Chain, EVM (Ethereum), Starknet).context
object
The context shared between the indexer and the plugins.
The following example shows a minimal indexer that streams PairCreated
events from Uniswap V2 to detect new pools, and then streams the pool's events.
import { EvmStream } from "@apibara/evm";
import { defineIndexer } from "@apibara/indexer";
export default defineIndexer(EvmStream)({
streamUrl: "https://ethereum.preview.apibara.org",
filter: {
logs: [{ /* ... */ }],
},
async factory({ block }) {
const { logs } = block;
return { /* ... */ };
},
async transform({ block }) {
const { header, logs } = block;
console.log(header);
console.log(logs);
},
});