Inputs
solana-m0-orderbook
Package ref
solana-m0-orderbook@v0.2.0Run package
CLI
Run map_svm_events from the command line.
substreams run solana-m0-orderbook@v0.2.0 map_svm_events -e solana-mainnet --start-block -1substreams auth or directly on thegraph.market (see docs).README
Solana Substreams — M0 OrderBook
A Substreams module that indexes the M0 OrderBook
program on Solana and sinks its events into the shared Postgres database (the
solana schema). The EVM chains are indexed separately by Envio (see ../evm);
the unified cross-chain views live in ../sql/views.sql.
Program ID (devnet + mainnet): MzLoYnJ6sF6eeejs4vV95TNmXqS3W4cAtLGKkjT4ZrK
What it indexes
map_svm_events (in src/lib.rs) scans each block's transactions for the
program's Anchor CPI events, decodes them, and emits one row per event. The proto
schema (proto/program.proto) maps each event to a table:
| Event | Table |
|---|---|
OrderOpened | events_order_opened |
OrderFilled | events_order_filled |
OrderCompleted | events_order_completed |
OrderCancelled | events_order_cancelled |
FillReported | events_fill_reported |
CancelReported | events_cancel_reported |
RefundClaimed | events_refund_claimed |
OrderOpened has two on-chain layouts that share an Anchor discriminator: the
legacy (V1, 188-byte) event and the current (V2, 228-byte) event that adds
funder and fill_deadline. The handler dispatches on payload length — V1 events
synthesize funder = sender and derive fill_deadline from the amount, V2 events
read both fields directly. This mirrors the EVM indexer's V1/V2 handling.
Networks
Each network has its own manifest. They share the same Rust/proto code and differ
only in package name, start block, network, and the chain_id written to rows.
| Network | Manifest | Package | initialBlock | chain_id |
|---|---|---|---|---|
| Solana mainnet | substreams-solana.yaml | solana-m0-orderbook | 399833693 | 1399811149 |
| Solana devnet | substreams-solana-devnet.yaml | solana-devnet-m0-orderbook | 436247835 | 1399811150 |
Build & test locally
# Compile the WASM module (output: target/wasm32-unknown-unknown/release/substreams.wasm)
just cargo-build
# Stream 10 blocks from a start block through the GUI to inspect decoded events
just debug-gui solana-devnet <start_block>
just debug-gui solana <start_block>
Pick a <start_block> at or just before a known order to see events in the output.
The GUI requires a Substreams API token (see below).
Packages
Published packages live in the Substreams registry under
their package names (solana-m0-orderbook, solana-devnet-m0-orderbook). The
deployed sink references a package by its .spkg (via the PACKAGE env var), e.g.
solana-devnet-m0-orderbook-v0.2.0.spkg.
Publishing a new version
-
Authenticate to the registry (sets
SUBSTREAMS_API_TOKEN):substreams auth -
Bump
package.versionin the target manifest and keepversionin thejustfilein sync —substreams packnames the file from the manifest while the publish step uses thejustfilevar, so they must match. (Or override per run:just version=v0.3.0 build-and-publish solana-devnet.) -
Build, pack, and publish:
just build-and-publish solana-devnet # or: solana (mainnet)
Deployment
The indexer runs as a containerized substreams-sink-sql
sink. The image entrypoint is substreams-sink-sql from-proto "$DSN" "$PACKAGE",
which creates the tables from the proto schema on first run and streams events into
Postgres.
# Build & push the runtime image (ghcr.io/m0-foundation/liquidity-delivery:orderbook-substreams)
just build-docker-image
Run the container (in our AWS/ECS infra) with:
| Env | Value |
|---|---|
DSN | Postgres connection string (writes to the solana schema) |
SUBSTREAMS_API_TOKEN | registry token from substreams auth |
PACKAGE | published spkg, e.g. solana-m0-orderbook-v0.2.0.spkg |
After the sink creates/updates the tables, (re)apply ../sql/views.sql so the
unified public.* views expose any new columns.
Schema changes & reindexing
substreams-sink-sql resumes from a stored cursor and does not auto-migrate
existing tables when the proto changes. To pick up new columns across history,
either drop the solana schema and let the sink recreate + backfill from
initialBlock, or ALTER TABLE to add the columns and accept NULLs on rows that
predate the change (they are not reprocessed).
Modules
Execution graph
Show 3 dependencies
Output
sf.solana.type.v1.BlockInputs
Output
sf.substreams.index.v1.Keys
`blocks_without_votes` allows you to consume a full Solana Block without Vote instructions (`Vote111111111111111111111111111111111111111`). If you consume it on HISTORICAL data (+1000 blocks from HEAD), you will be reading from the StreamingFast cache, thus saving costs on the amount of TB read.
Output
sf.solana.type.v1.BlockInputs