GraphQL schema for historical activity, premium accrual, NAV reconciliation. Used by the Activity tab in the reference frontend.
type Vault @entity {
id: ID! # vault address (lowercase)
asset: Bytes!
underlyingVault: Bytes! # the wrapped ERC4626
seniorToken: Bytes!
juniorToken: Bytes!
protectionPremiumBps: BigInt!
seniorNav: BigInt!
juniorNav: BigInt!
seniorTotalSupply: BigInt!
juniorTotalSupply: BigInt!
totalVaultShares: BigInt!
lastPremiumAccrual: BigInt!
depositsPaused: Boolean!
...
}
type Position @entity {
id: ID! # vaultAddress-tranche-account
vault: Vault!
tranche: Tranche! # SENIOR | JUNIOR
account: Bytes!
shares: BigInt! # current lcSEN/lcJUN balance
cumulativeDeposited: BigInt! # lifetime asset deposit
cumulativeWithdrawn: BigInt! # lifetime asset withdrawal
noticeShares: BigInt! # > 0 ⇒ withdrawal notice on file
noticeExecutableAt: BigInt # timestamp the notice unblocks
}
type Snapshot @entity {
id: ID! # vaultAddress-timestamp
vault: Vault!
timestamp: BigInt!
seniorNav: BigInt!
juniorNav: BigInt!
seniorSharePrice: BigInt! # 1e18-scaled
juniorSharePrice: BigInt! # 1e18-scaled
totalAssets: BigInt!
}Position is keyed by (vault, tranche, account), one row per holder per side. Each user has at most one position per tranche per vault.
The frontend's Activity tab queries a single Activity union-style entity that subsumes every event type. Each row carries:
type Activity @entity {
id: ID! # txHash-logIndex
vault: Vault!
kind: ActivityKind!
timestamp: BigInt!
account: Bytes
receiver: Bytes
txHash: Bytes!
assets: BigInt # primary amount, varies by kind
shares: BigInt # secondary amount for share-denominated events
extra: ActivityExtra! # kind-specific JSON-shaped fields
}
enum ActivityKind {
SeniorDeposit
JuniorDeposit
SeniorWithdrawal
JuniorWithdrawal
SeniorWithdrawalNoticeRequested
SeniorWithdrawalNoticeCancelled
JuniorWithdrawalNoticeRequested
JuniorWithdrawalNoticeCancelled
PooledNavSynced
ProtectionPremiumAccrued
ProtectionPremiumRateSet
PerformanceFeeAccrued
PerformanceFeeRateSet
FeeRecipientSet
DepositsPausedSet
}extra carries kind-specific fields like delta (for NAV sync), fromTimestamp / toTimestamp / premiumAssets (for premium accrual), newRateBps (for rate setters), recipient (for fee recipient changes), paused (for pause toggles), and executableAt (for notice requests). The frontend's notesCell and amountCell formatters branch on kind to pull the right field.
query RecentActivity($vault: ID!, $first: Int!) {
activities(
where: { vault: $vault }
orderBy: timestamp
orderDirection: desc
first: $first
) {
id
kind
timestamp
account
receiver
txHash
assets
shares
extra
}
}query UserPositions($user: Bytes!) {
positions(where: { account: $user, shares_gt: 0 }) {
vault {
id
asset
protectionPremiumBps
seniorSharePrice: derivedSeniorSharePrice # see schema notes
juniorSharePrice: derivedJuniorSharePrice
}
tranche
shares
noticeShares
noticeExecutableAt
}
}query NavHistory($vault: ID!, $sinceTs: BigInt!) {
snapshots(
where: { vault: $vault, timestamp_gte: $sinceTs }
orderBy: timestamp
orderDirection: asc
first: 1000
) {
timestamp
seniorSharePrice
juniorSharePrice
seniorNav
juniorNav
}
}The frontend's useTrancheSnapshots hook uses the share-price column to derive realised APY from two consecutive snapshots: (price_t / price_t-1)^(1y / dt) − 1.
For indexing Morpho VaultV2 instances directly, see Morpho's official tooling at docs.morpho.org/tools.