pnpm monorepo with three packages:
shared-config/—@mento-protocol/monitoring-config: shared deployment config (chain ID → treb namespace mapping)indexer-envio/— Envio HyperIndex indexer for Celo v3 FPMM poolsui-dashboard/— Next.js 16 + Plotly.js monitoring dashboard
# Install all deps
pnpm install
# Indexer
pnpm indexer:codegen # Generate types from schema (devnet)
pnpm indexer:dev # Start indexer (devnet)
pnpm indexer:celo-sepolia:codegen # Generate types (Celo Sepolia config)
pnpm indexer:celo-sepolia:dev # Start indexer (Celo Sepolia)
pnpm indexer:celo-mainnet:codegen # Generate types (Celo mainnet)
pnpm indexer:celo-mainnet:dev # Start indexer (Celo mainnet)
pnpm indexer:monad-mainnet:codegen # Generate types (Monad mainnet)
pnpm indexer:monad-mainnet:dev # Start indexer (Monad mainnet)
pnpm indexer:monad-testnet:codegen # Generate types (Monad testnet)
pnpm indexer:monad-testnet:dev # Start indexer (Monad testnet)
# Dashboard
pnpm dashboard:dev # Dev server
pnpm dashboard:build # Production build
# Infrastructure (Terraform)
pnpm infra:init # Init providers (first time or after changes)
pnpm infra:plan # Preview infrastructure changes
pnpm infra:apply # Apply infrastructure changes- Package:
@mento-protocol/monitoring-config(private, no build step) - Purpose: Single source of truth for chain ID → active treb namespace. Edit
deployment-namespaces.jsonwhen promoting a new deployment. - Consumed by: both
indexer-envioandui-dashboardviaworkspace:*dependency
- Runtime: Envio HyperIndex (envio@2.32.3)
- Schema:
schema.graphqldefines indexed entities (FPMM, Swap, Mint, Burn, etc.) - Configs:
config.celo.devnet.yaml,config.celo.mainnet.yaml,config.celo.sepolia.yaml,config.monad.mainnet.yaml,config.monad.testnet.yaml - Handlers:
src/EventHandlers.tsis the Envio entry point (allconfig.*.yamlfiles reference it). It imports handler modules fromsrc/handlers/and re-exports test utilities. Handler logic lives insrc/handlers/fpmm.ts,src/handlers/sortedOracles.ts,src/handlers/virtualPool.ts,src/handlers/feeToken.ts. Shared logic:src/rpc.ts(RPC + caches),src/pool.ts(upsert),src/priceDifference.ts,src/tradingLimits.ts,src/feeToken.ts,src/abis.ts,src/helpers.ts. - Contract addresses:
src/contractAddresses.ts— resolves addresses from@mento-protocol/contractsusing the namespace map fromshared-config - ABIs:
abis/— FPMMFactory, FPMM, VirtualPoolFactory (indexer-specific); SortedOracles + token ABIs come from@mento-protocol/contracts - Scripts:
scripts/run-envio-with-env.mjs— loads .env and runs envio CLI - Tests:
test/— mocha + chai - Docker: Envio dev mode spins up Postgres + Hasura automatically
- Framework: Next.js 16 (App Router, React 19)
- Charts: Plotly.js via react-plotly.js
- Data: GraphQL queries to Hasura (via graphql-request + SWR)
- Styling: Tailwind CSS 4
- Multi-chain: Network selector switches between celo-mainnet, celo-sepolia, monad-mainnet, monad-testnet Hasura endpoints; all networks defined in
src/lib/networks.ts - Contract labels:
src/lib/networks.tsderives token symbols and address labels from@mento-protocol/contracts(no vendored JSON); the active namespace per chain comes fromshared-config - Address book:
/address-bookpage + inline editing; custom labels stored in Upstash Redis, backed up daily to Vercel Blob; custom labels override/extend the package-derived ones - Deployment: Vercel (
monitoring-dashboardproject); infra managed by Terraform interraform/
- Current expected scale is roughly 30–50 total pools.
- At this size, client-side aggregation for the 24h volume tiles/table is acceptable with the current polling setup.
- Do not flag the current snapshot-query aggregation path as a scalability issue in PR reviews unless assumptions change materially (e.g. significantly more pools, much higher polling frequency, or observed latency/cost regressions in production).
monitoring-monorepo/
├── package.json # Root workspace config
├── pnpm-workspace.yaml # Workspace package list
├── terraform/ # Terraform — Vercel project + Upstash Redis + env vars
│ ├── main.tf # All resources
│ ├── variables.tf # Input variables
│ ├── outputs.tf # Outputs (project ID, Redis URL, etc.)
│ ├── terraform.tfvars.example # Template (copy to terraform.tfvars)
│ └── .gitignore # Ignores tfstate, tfvars, .terraform/
├── shared-config/ # @mento-protocol/monitoring-config (private)
│ ├── package.json
│ └── deployment-namespaces.json # ← edit this when promoting a new deployment
├── indexer-envio/
│ ├── config.celo.devnet.yaml # Devnet indexer config
│ ├── config.celo.mainnet.yaml # Celo Mainnet config
│ ├── config.celo.sepolia.yaml # Celo Sepolia config
│ ├── config.monad.mainnet.yaml # Monad Mainnet config
│ ├── config.monad.testnet.yaml # Monad Testnet config
│ ├── schema.graphql # Entity definitions
│ ├── src/
│ │ ├── EventHandlers.ts # Envio entry point (imports handlers, re-exports for tests)
│ │ ├── handlers/ # Event handler registrations
│ │ │ ├── fpmm.ts # FPMMFactory + FPMM handlers
│ │ │ ├── sortedOracles.ts # SortedOracles handlers
│ │ │ ├── virtualPool.ts # VirtualPool handlers
│ │ │ └── feeToken.ts # ERC20FeeToken.Transfer handler
│ │ ├── rpc.ts # RPC client, fetch functions, caches, test mocks
│ │ ├── pool.ts # Pool/PoolSnapshot upsert, health status
│ │ ├── priceDifference.ts # Price math (computePriceDifference, normalizeTo18)
│ │ ├── tradingLimits.ts # Trading limit types and computation
│ │ ├── feeToken.ts # Fee token metadata, backfill, YIELD_SPLIT_ADDRESS
│ │ ├── abis.ts # ABI definitions
│ │ ├── helpers.ts # Pure utilities (eventId, asAddress, etc.)
│ │ └── contractAddresses.ts # Contract address resolution from @mento-protocol/contracts
│ ├── abis/ # Contract ABIs (FPMMFactory, FPMM, VirtualPoolFactory)
│ ├── scripts/ # Helper scripts
│ └── test/ # Tests
└── ui-dashboard/
├── src/
│ ├── app/
│ │ ├── address-book/ # Address book page
│ │ └── api/address-labels/ # CRUD + export/import/backup routes
│ ├── components/
│ │ ├── address-label-editor.tsx # Inline edit dialog
│ │ └── address-labels-provider.tsx # Context: merges package + custom labels
│ └── lib/
│ ├── address-labels.ts # Upstash Redis data access (server-side)
│ └── networks.ts # Network defs; derives labels from @mento-protocol/contracts
├── public/ # Static assets
├── vercel.json # Vercel config + daily backup cron
└── next.config.ts # Next.js config
- Indexer needs Docker for local dev (Postgres + Hasura containers)
- Dashboard needs
NEXT_PUBLIC_HASURA_URL_*env vars for local dev; runvercel env pull ui-dashboard/.env.localto pull from the linked project - Production env vars (including Upstash Redis + Blob credentials) are managed by Terraform — see
terraform/terraform.tfvars.example - See root README.md for full env var documentation
The envio binary hardcodes http://localhost:8080/hasura/healthz?strict=true for its startup liveness check. This port is not configurable via env vars. Never set HASURA_EXTERNAL_PORT to anything other than 8080 (or omit it entirely) — the binary will silently fail its health check and retry with exponential backoff, stalling startup for 5+ minutes per attempt.
All envio configs share the same Docker project name (generated, derived from the generated/ directory name) and the same Hasura port (8080). Running two local indexers simultaneously will cause container name conflicts. Start one, stop it, then start the other.
The envio-generated generated/docker-compose.yaml does not include a healthcheck for the postgres service. Without one, Docker reports Health:"" and the envio binary waits indefinitely. scripts/run-envio-with-env.mjs automatically patches the file to add a pg_isready healthcheck after every pnpm codegen run. If you regenerate the compose file manually, re-run codegen via the script (not directly via envio codegen) to re-apply the patch.
After creating a new worktree or cloning the repo, run:
./scripts/setup.shThis installs deps and runs Envio codegen (required for indexer-envio TypeScript to compile — the generated/ dir is gitignored).
⚠️ Git hooks don't run on the server. Trunk's pre-push hooks live in the Mac's common.git/hooks/dir. When pushing from the server, they are silently skipped. CI is the first place checks run — and CI failures are far more expensive than local checks. Always run these manually before pushing:
./tools/trunk fmt --all
./tools/trunk check --all
pnpm --filter @mento-protocol/ui-dashboard typecheck
pnpm --filter @mento-protocol/indexer-envio typecheck
pnpm --filter @mento-protocol/indexer-envio test
pnpm indexer:celo-mainnet:codegen # Validates Envio can parse handler entry point + module imports
pnpm --filter @mento-protocol/ui-dashboard test:coverageCommon traps:
codespellflags short variable names that match common abbreviations (e.g. a two-letter loop var that looks like a misspelling). Use descriptive names likenetDatato avoid this.trunk check <file>only checks the specified files — always use--allto match what CI runs- If
indexer-envio typecheckfails with "Cannot find module 'generated'", run./scripts/setup.shfirst
Every config.*.yaml specifies handler: src/EventHandlers.ts. Envio expects all handler registrations (e.g. FPMM.Swap.handler(...)) to be reachable from this file at module load time. The actual logic lives in src/handlers/*.ts — these are imported as side effects from EventHandlers.ts. If you add a new handler file, you must add a corresponding import "./handlers/yourFile" in EventHandlers.ts and then re-run pnpm indexer:celo-mainnet:codegen to verify Envio picks it up.
When a new set of contracts has been deployed and a new @mento-protocol/contracts version is published:
- Update the
@mento-protocol/contractsversion inindexer-envio/package.jsonandui-dashboard/package.json - Update namespace string(s) in
shared-config/deployment-namespaces.json(e.g."42220": "mainnet-v2") - Run
pnpm install - Typecheck:
pnpm --filter @mento-protocol/ui-dashboard typecheckandpnpm --filter @mento-protocol/indexer-envio typecheck
- Add ABI to
indexer-envio/abis/ - Add contract entry in the relevant config(s):
config.celo.mainnet.yaml,config.celo.sepolia.yaml,config.monad.mainnet.yaml,config.monad.testnet.yaml - Add entity to
schema.graphql - Add handler in the appropriate
src/handlers/*.tsfile (or create a new one and import it fromsrc/EventHandlers.ts) - Run
pnpm indexer:codegento regenerate types
- Create component in
ui-dashboard/src/ - Add GraphQL query for the data
- Wire up with SWR for real-time updates
- Edit
terraform/main.tforterraform/variables.tf - Run
pnpm infra:planto preview - Run
pnpm infra:applyto apply - Commit the updated
terraform/main.tfandterraform/.terraform.lock.hcl(state file is gitignored)