Indexing
Status
IN PROGRESSThe indexing pipeline is designed and blocked on a prerequisite that has not been met: no Vessel contract is published or verified on a Monad explorer, so no public indexer can be built from it. As of 2026-08-29 there is no indexer endpoint to query.
What Vessel uses it for
Vessel is a set of contracts that emit events. An indexer turns those events into queryable tables so that a dashboard, an integrator, or a reader with a browser can answer questions like "what did the last epoch settle at" without replaying the chain themselves.
The design uses Envio HyperIndex indexers deployed on Envio Cloud via the envio-cloud CLI, deploying from GitHub. Each indexer produces a GraphQL API URL that serves the indexed data.
Two things are meant to read that API:
- The transparency dashboard. Its numbers are meant to be derived from indexed settlement and waterfall events, not from an off-chain database Vessel controls. The dashboard itself is not published; its data model is described here as designed, not as shipped. UNVERIFIED — PENDING GATE-0
- Anyone else. The GraphQL endpoint is public by design. A query that reproduces a dashboard number is the verification path.
What gets indexed is the event surface described on Events: the per-epoch settlement (gross funding G, the protocol fee, the Hull accrual A_H = r_H × H × Δt, the Ballast residual) and the waterfall on shortfall (Ballast NAV absorbs first, then Reserve, then Hull principal). The conservation invariant ΔNAV_Hull + ΔNAV_Ballast + ΔReserve + fees = G is checkable per epoch from those fields alone, which is the point of indexing them.
Event names and field layouts are contract-level artifacts. The public repository contains only a README and a LICENSE as of 2026-08-29, so every event signature on this site is design-stage UNVERIFIED — PENDING GATE-0 until the contracts are published.
What is actually wired today
Nothing public.
The dependency chain is strict and has not started:
- Vessel contracts are deployed to Monad testnet. (Deployed per vessel.wtf; addresses unpublished.)
- The contracts are verified on a Monad explorer. Not done, because the source is unpublished.
envio contract-importpulls the ABI from the explorer and scaffolds the indexer. Blocked on step 2.- The indexer deploys to Envio Cloud and exposes a GraphQL endpoint. Blocked on step 3.
- The transparency dashboard reads that endpoint. Blocked on step 4.
Step 2 is the hard prerequisite: contract-import needs a verified contract on the explorer because that is where it gets the ABI. There is no way to skip it and still have a public, reproducible indexer. Whether a private indexer exists inside the team is not something this page can confirm; it is not a public artifact and is not counted.
The explorer set used for verification is the one docs.monad.xyz publishes: testnet.monadscan.com for testnet, monadscan.com for mainnet.
Interface
The indexer is defined by three files plus one opt-in.
config.yaml names the network and the contracts to watch. The initialisation is pinned to envio@3.0.0-alpha.21 with -b monad or -b monad-testnet. Vessel is testnet-only today, so the network is testnet (chain ID 10143).
# config.yaml (design-stage; addresses and event names pending publication)
name: vessel-indexer
networks:
- id: 10143
start_block: 0
contracts:
- name: VesselEngine
address: "0x..." # unpublished
handler: src/EventHandlers.ts
events:
- event: "EpochSettled(...)" # signature pending /developers/events
- event: "ShortfallAbsorbed(...)" # signature pending /developers/events
field_selection:
transaction_fields:
- hash
The field_selection.transaction_fields block must opt in hash. Without it the indexed rows carry no transaction hash, and the dashboard cannot link a number back to the transaction that produced it. That link is the whole reason to index rather than summarise.
schema.graphql declares the entities. A minimal shape for the settlement record:
# schema.graphql (design-stage)
type EpochSettlement {
id: ID! # epoch number
blockNumber: BigInt!
txHash: String!
grossFunding: BigInt! # G
protocolFee: BigInt!
hullAccrual: BigInt! # A_H
ballastResidual: BigInt!
reserveDelta: BigInt!
}
TypeScript handlers (src/EventHandlers.ts) map each event to an entity write. The handler for a settlement event stores the emitted fields verbatim and does no arithmetic; the invariant is checked by the reader, not by the indexer.
Until the ABI is published the exact event names above are placeholders, and this page will be updated when they are not.
Addresses
| Contract (testnet) | Address | Actions |
|---|---|---|
| Vessel Engine (event source) | UNVERIFIED — PENDING GATE-0 | |
| Indexer GraphQL endpoint | UNVERIFIED — PENDING GATE-0 |
Neither exists as a public artifact as of 2026-08-29. The engine address is unpublished; the endpoint cannot be created until the engine is verified on the explorer.
Verify it yourself
Once the endpoint publishes, this query reproduces the most recent settlement record. It is the template the dashboard's per-epoch row is meant to be built from; if the dashboard and this query disagree, the query wins.
# PENDING: endpoint not yet published. Field names follow the design-stage
# schema above and will be corrected when the ABI is public.
query LatestSettlement {
EpochSettlement(order_by: {blockNumber: desc}, limit: 1) {
id
blockNumber
txHash
grossFunding
protocolFee
hullAccrual
ballastResidual
reserveDelta
}
}
Endpoint: not published UNVERIFIED — PENDING GATE-0.
The check to run on the result: hullAccrual + ballastResidual + reserveDelta + protocolFee should equal grossFunding for that epoch. That is the conservation invariant, and it is the one number on the dashboard that has no interpretation, only a truth value.
Then take txHash to testnet.monadscan.com and confirm the emitted log matches. That step does not need the indexer at all, which is the subject of the next section.
Failure mode
If the indexer goes down, the transparency dashboard goes stale. It does not go wrong in a way that touches funds: the indexer is read-only, and no Vessel contract reads from it. Keepers, rebalancing, settlement, and the waterfall all run against chain state and do not depend on Envio, Envio Cloud, or the GraphQL endpoint.
The chain remains the source of truth. Every number the dashboard shows is meant to be a projection of an on-chain event, so a stale dashboard is recoverable by re-indexing from the start block, and a suspicious dashboard is checkable by reading the log directly on the explorer or over RPC at testnet-rpc.monad.xyz.
The verify-the-hedge path in particular does not route through the indexer. Checking that the short leg exists and is sized against the spot leg is a matter of reading venue and engine state on chain. SIMVENUE — SIMULATED The hedge venue today is SimVenue, a simulated venue with an owner-settable funding rate behind the IVenue interface; that is a separate and larger caveat than indexer availability, and it is documented on the Perpl page.
Two residual risks are worth naming. First, a stale dashboard that does not say it is stale will mislead; the dashboard should display the indexed head block against the chain head, and a reader should treat any gap as a reason to go to the explorer. Second, a wrong handler can index a correct event incorrectly. That is why the invariant check above is done by the reader against raw emitted fields rather than trusted from a computed column.
Links
- Envio HyperIndex documentation
- Monad developer docs, including the contract verification guide the indexer depends on
- Monad testnet explorer
- Vessel repository (README and LICENSE only as of 2026-08-29)
- Events, the surface this indexer consumes