Upsert DatabaseChanges for markets, market_states, positions, vaults, vault_positions, vault_states and blue_config. Pair with schema.sql.
Output
sf.substreams.sink.database.v1.DatabaseChanges

Package ref
morpho-blue-paulie@v0.1.0Run package
CLI
Run db_out from the command line.
substreams run morpho-blue-paulie@v0.1.0 db_out -e mainnetsubstreams auth or directly on thegraph.market (see docs).README
Event-sourced market state, user positions and MetaMorpho vault balances for Morpho Blue on Ethereum mainnet, emitted as upsert SQL you can sink into your own database.
StreamingFast already ships a Morpho Blue event decoder. This package does not fork it — it imports it and spends its modules on the layer that decoder does not have:
StreamingFast morpho-blue-substreams | this package | |
|---|---|---|
| Blue event tape | ✅ map_events | imported, not reimplemented |
Admin events (SetFee, SetFeeRecipient, EnableIrm, …) | ❌ | ✅ map_blue_admin + store_blue_config |
| Market params / totals | ❌ | ✅ stores |
| Per-user positions | ❌ | ✅ store_positions |
| MetaMorpho vaults | ❌ | ✅ factory + vault share tracking |
| SQL output | append-only event tables | upsert current-state tables |
┌─ store_market_params ─┐
morpho_sf:map_events ───┼─ store_market_totals ─┤
└─ store_positions ─────┤
▲ │
map_blue_admin ─→ store_fee_recipient ├─→ db_out ─→ DatabaseChanges
├─→ store_market_fee ─────────────┤
└─→ store_blue_config ────────────┤
│
map_metamorpho_factory ─→ store_vaults ─┐ │
▼ │
map_metamorpho_events ──┤
├─→ store_vault_positions
└─→ store_vault_totals
Three things make event-sourced Morpho accounting easy to get wrong. All are
handled here, and all are documented in
docs/MORPHO_BLUE_ACCOUNTING.md.
Events must be replayed in log order. The upstream Events message groups
events by type. Iterating field-by-field can apply an AccrueInterest after
a Supply that actually came later in the same transaction — on-chain,
interest always accrues first. Every store here merge-sorts on log_index
before applying anything.
Fee shares are minted silently. AccrueInterest credits feeShares
straight into the fee recipient's supply position and emits no Supply
event (EventsLib says so explicitly). The upstream decoder does not decode
SetFeeRecipient, so this package adds map_blue_admin and reads the
recipient at the accrual's own ordinal — a SetFeeRecipient later in the
same block must not be applied retroactively.
Bad debt is socialized. On Liquidate with bad debt, the loss comes off
totalBorrowAssets and totalSupplyAssets, and the borrower's remaining
borrow shares are zeroed. Suppliers eat it.
Values are raw on-chain integers. Shares are not assets — convert with
Morpho's virtual-share math (VIRTUAL_SHARES = 1e6, VIRTUAL_ASSETS = 1)
against the matching market_states row.
The upstream decoder is not published to the Substreams registry (it only
exists as source in streamingfast/substreams-chain-modules), so a build of it
is vendored at vendor/ to keep this package self-contained.
make vendor regenerates it from source.
make build # cargo build --target wasm32-unknown-unknown --release
make pack # -> morpho-blue-paulie-v0.1.0.spkg
make stale # guard: fails if the .wasm is older than src/
substreams run morpho-blue-paulie-v0.1.0.spkg db_out \
-e mainnet.eth.streamingfast.io:443 --start-block 18883124 --stop-block +1000
substreams packdoes not compile. It packages whatever.wasmsits at the manifest path, so alwaysmake buildfirst —make stalecatches it.
Sink the output with
substreams-sink-sql
against schema.sql. Numeric columns default to 0 because
db_out emits only the columns that changed in a block.
| table | key | holds |
|---|---|---|
markets | market_id | the 5-tuple: loan/collateral token, oracle, IRM, LLTV |
market_states | market_id | supply/borrow assets and shares, collateral, fee |
positions | {market_id}:{user} | supply_shares, borrow_shares, collateral |
vaults | vault address | MetaMorpho name, symbol, asset, factory |
vault_positions | {vault}:{user} | vault share balance |
vault_states | vault address | total_shares (exact), net_deposited_assets |
blue_config | owner / irm:… / lltv:… | protocol owner and enabled IRM/LLTV sets |
In: Ethereum mainnet, Morpho Blue from block 18883124, MetaMorpho factories
V1.1 (0x1897A899…) and V1 (0xA9c3D3a3…).
Not yet: USD prices, APYs and rewards (use the
Morpho API — it already does
these well); live health factors, which need an oracle price() read at 1e36
scale — positions + market_states + lltv + the oracle address are emitted
so a consumer can join prices themselves; Vault V2; other chains (see below).
Note vault_states.net_deposited_assets is deposit principal, not AUM — a
vault also earns interest in the underlying Blue markets, which emits no
vault-level event. For true AUM, join the vault's own rows in positions.
Morpho Blue is deployed at the same address on Base and elsewhere, but a
Substreams package is pinned to one network:, and the vendored upstream
decoder is mainnet-only. Base support means a second manifest plus a Base build
of the decoder — same Rust crate, no handler changes. Tracked as follow-up.
Validated against mainnet at block 21,000,000 and at real vault-creation blocks:
markets, positions and vault events decode and accumulate, and vault metadata
matches the Morpho API exactly (e.g. MC.wM / "MEV Capital M^0 Vault" at block
20,873,628). Absolute totals across a full backfill are not yet verified —
that needs a paid endpoint, since store backfill from block 18883124 exceeds the
free tier's 10,000-block limit.
MIT
Modules
Upsert DatabaseChanges for markets, market_states, positions, vaults, vault_positions, vault_states and blue_config. Pair with schema.sql.
Output
sf.substreams.sink.database.v1.DatabaseChanges
Decodes the Morpho Blue admin events the upstream decoder omits: SetFeeRecipient, SetFee, EnableIrm, EnableLltv and SetOwner.
Deposit, Withdraw and Transfer events restricted to addresses the factory actually created — these are generic ERC-4626/ERC-20 signatures, so the vault store is what makes them Morpho-specific.
Protocol owner and the enabled IRM / LLTV sets (`owner`, `irm:{address}`, `lltv:{value}`). Append-only — Morpho has no disable.
Store value
string
Update policy
set
Inputs
Current protocol fee recipient, keyed `fee_recipient`. Written at the event's log index so consumers can resolve it at a precise ordinal.
Store value
string
Update policy
set
Inputs
Store value
string
Update policy
set
Inputs
The immutable 5-tuple per market (loan token, collateral token, oracle, IRM, LLTV), keyed `mkt:{market_id}:params`.
Store value
proto:morpho_paulie.v1.MarketParamsUpdate policy
set
Inputs
Running supply/borrow assets and shares plus total collateral per market. Applies interest accrual and socialized bad debt in true log order.
Store value
bigint
Update policy
add
Inputs
Per-user supply shares, borrow shares and collateral, keyed `pos:{market_id}:{user}:{field}`. Credits the silent AccrueInterest fee-share mint to the fee recipient in effect at that ordinal.
Store value
bigint
Update policy
add
Per-user vault share balances from mint/burn/transfer, keyed `vpos:{vault}:{user}`.
Store value
bigint
Update policy
add
Inputs
Vault share supply (exact) and net deposited assets (principal only — excludes interest earned in the underlying Blue markets).
Store value
bigint
Update policy
add
Inputs
Vault metadata (asset, name, symbol, factory, initial owner) keyed `vault:{address}`.
Store value
proto:morpho_paulie.v1.VaultMetaUpdate policy
set
Inputs