Slot Handlers
indexer.onSlot runs logic on every slot or at an interval, the Solana
equivalent of EVM block handlers. Use it for
time-series snapshots, periodic aggregations, or pulling extra data from RPC on a
schedule with the Effect API. For indexing program
activity, reach for instruction handlers
instead.
import { indexer } from "envio";
indexer.onSlot({ name: "MySlotHandler" }, async ({ slot, context }) => {
context.log.info(`Processing slot ${slot}`);
});
Slot handlers self-register: they need no entry in config.yaml beyond the
chain itself. With no where, the handler runs on every slot.
Options
indexer.onSlot(options, handler):
name(required): unique name, used for logging, metrics, and progress tracking.where(optional):({ chain }) => false | true | { slot: { _gte?, _lte?, _every? } }. Evaluated once per chain at registration to decide which chains the handler runs on and over which slot range/interval.
indexer.onSlot(
{
name: "SlotSampler",
where: ({ chain }) =>
chain.id === 7565164
? {
slot: {
_gte: 385_453_000, // start slot (inclusive)
_lte: 385_500_000, // end slot (inclusive)
_every: 100, // every 100th slot
},
}
: false,
},
async ({ slot, context }) => {
context.SlotPing.set({ id: slot.toString(), slot });
},
);
onBlock- The handler argument is
{ slot: number, context }: a plain slot number, not ablockobject. - The filter key is
slot(with_gte/_lte/_every), notblock.number. - There's no
intervaloption; express intervals with_every._everyaligns to_gte(or the chain start): it fires when(slot − _gte) % _every === 0.
The handler
The handler receives { slot, context }:
slot: the slot number being processed (a plainnumber).context: entity operations (one object perschema.graphqlentity, withget/getOrThrow/getWhere/getOrCreate/set/deleteUnsafe), pluscontext.log,context.effect,context.chain(idis7565164on Solana mainnet), andcontext.isPreload. This is the same context as EVM handlers; see the Event Handlers context.
Enriching with RPC data via Effects
A slot number alone is rarely enough, so pair onSlot with an
Effect to fetch block/transaction/account data from
RPC. Effects are deduplicated and cached, and can be rate-limited so you don't
exhaust your RPC provider. S (from envio) builds the input/output schemas
(it's the Sury library).
import { indexer, createEffect, S } from "envio";
const blockSchema = S.schema({
blockhash: S.string,
blockHeight: S.nullable(S.number),
blockTime: S.nullable(S.number),
});
const getBlock = createEffect(
{
name: "getBlock",
input: { slot: S.number },
output: S.nullable(blockSchema),
rateLimit: { calls: 3, per: "second" },
},
async ({ input }) => {
const res = await fetch(process.env.ENVIO_MAINNET_RPC_URL!, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "getBlock",
params: [input.slot, { maxSupportedTransactionVersion: 1, transactionDetails: "none" }],
}),
});
const { result } = await res.json();
return result ?? null;
},
);
indexer.onSlot({ name: "BlockTracker" }, async ({ slot, context }) => {
const block = await context.effect(getBlock, { slot });
if (!block) {
context.log.info(`Slot ${slot} has no block (skipped leader)`);
return; // some slots produce no block
}
context.BlockInfo.set({
id: slot.toString(),
hash: block.blockhash,
height: block.blockHeight ?? undefined,
time: block.blockTime ? new Date(block.blockTime * 1000) : undefined,
});
});
On Solana a slot may be skipped (the leader produced no block). Handle the empty
result rather than assuming getBlock always returns data.
Preload double-run
Like all V3 handlers, slot handlers run twice (a parallel preload pass to warm the
cache, then the ordered pass). Effects are cached across both runs, so reads are
cheap, but guard non-idempotent side effects with if (context.isPreload) return;.
See Preload Optimization.
Slot handlers vs instruction handlers
Reach for instruction handlers to index what programs did, and slot handlers to do something on a cadence. They compose: an instruction handler records activity, and a slot handler rolls it into periodic snapshots. The overview compares the two side by side. For raw, low-level data over large ranges, query HyperSync for Solana directly.
Related
- Instruction Handlers: the main Solana handler type.
- Effect API: external/RPC calls, caching, rate limiting.
- Configuration: chains, RPC, programs, and start slot.