diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 4fadaf53..6f2dfe0d 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -58,7 +58,7 @@ actions: Runs codegen for indexer-envio (to produce the generated/ types) then typechecks both workspaces. Combined into one action so the generated module is guaranteed to exist before tsc runs. - run: pnpm indexer:celo-mainnet:codegen && pnpm --filter @mento-protocol/ui-dashboard --filter @mento-protocol/indexer-envio typecheck + run: pnpm indexer:codegen && pnpm --filter @mento-protocol/ui-dashboard --filter @mento-protocol/indexer-envio typecheck triggers: - git_hooks: [pre-push] - id: test-pre-push diff --git a/README.md b/README.md index 63cf95ca..0b85d418 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,44 @@ # Mento Monitoring Monorepo -Real-time monitoring infrastructure for Mento v3 on-chain pools — an [Envio HyperIndex](https://docs.envio.dev/) indexer paired with a Next.js 16 + Plotly.js dashboard. +Real-time monitoring infrastructure for Mento v3 on-chain pools — a multichain [Envio HyperIndex](https://docs.envio.dev/) indexer paired with a Next.js 16 + Plotly.js dashboard. **Live dashboard:** [monitoring.mento.org](https://monitoring.mento.org) ## Packages -| Package | Description | -| ----------------------------------- | ------------------------------------------------------------------------------ | -| [`indexer-envio`](./indexer-envio/) | Envio HyperIndex indexer for Celo + Monad (FPMM pools + VirtualPools) | -| [`ui-dashboard`](./ui-dashboard/) | Next.js 16 + Plotly.js monitoring dashboard with multi-chain network switching | -| [`shared-config`](./shared-config/) | Shared deployment config (chain ID → treb namespace mappings) | +| Package | Description | +| ----------------------------------- | ------------------------------------------------------------------- | +| [`indexer-envio`](./indexer-envio/) | Envio HyperIndex indexer — Celo + Monad multichain | +| [`ui-dashboard`](./ui-dashboard/) | Next.js 16 + Plotly.js dashboard with multi-chain network switching | +| [`shared-config`](./shared-config/) | Shared deployment config (chain ID → treb namespace mappings) | ## Architecture ```text -┌─────────────────┐ ┌──────────────────┐ ┌────────────────┐ -│ Celo Chain │────▶│ Envio HyperIndex │────▶│ Hasura │ -│ (RPC / GRPC) │ │ (Hosted) │ │ (GraphQL API) │ -└─────────────────┘ └──────────────────┘ └───────┬────────┘ - │ - ┌──────▼──────┐ - │ Next.js │ - │ Dashboard │ - │ (Vercel) │ - └─────────────┘ +┌──────────────────────┐ ┌──────────────────┐ ┌────────────────┐ +│ Celo + Monad Chains │────▶│ Envio HyperIndex │────▶│ Hasura │ +│ (HyperSync / RPC) │ │ (Hosted, mento) │ │ (GraphQL API) │ +└──────────────────────┘ └──────────────────┘ └───────┬────────┘ + │ + ┌──────▼──────┐ + │ Next.js │ + │ Dashboard │ + │ (Vercel) │ + └─────────────┘ ``` -The indexer runs on Envio's hosted free tier. Each deploy produces a new GraphQL endpoint hash. The dashboard reads from this endpoint via Hasura's GraphQL API. +Both Celo Mainnet (42220) and Monad Mainnet (143) are served from a single Envio project (`mento`) using `config.multichain.mainnet.yaml`. Pool IDs are namespaced as `{chainId}-{address}` to prevent cross-chain collisions. + +**Static production endpoint:** `https://indexer.hyperindex.xyz/2f3dd15/v1/graphql` ## Networks -| Network | Chain ID | Status | -| ------------- | -------- | ----------------------------- | -| Celo Mainnet | 42220 | ✅ Live | -| Celo Sepolia | 44787 | ✅ Live | -| Monad Mainnet | — | ⏳ Blocked on contract deploy | +| Network | Chain ID | Status | +| ------------- | -------- | ------- | +| Celo Mainnet | 42220 | ✅ Live | +| Monad Mainnet | 143 | ✅ Live | +| Celo Sepolia | 11142220 | ✅ Live | +| Monad Testnet | 10143 | ✅ Live | ## Getting Started @@ -54,15 +57,14 @@ pnpm install ### Run the Indexer (local) ```bash -# Celo Sepolia -pnpm indexer:celo-sepolia:codegen && pnpm indexer:celo-sepolia:dev +# Multichain (Celo + Monad mainnet) — default +pnpm indexer:codegen && pnpm indexer:dev -# Celo Mainnet -pnpm indexer:celo-mainnet:codegen && pnpm indexer:celo-mainnet:dev +# Celo Sepolia (testnet) +pnpm indexer:celo-sepolia:codegen && pnpm indexer:celo-sepolia:dev -# Monad Testnet / Mainnet +# Monad Testnet pnpm indexer:monad-testnet:codegen && pnpm indexer:monad-testnet:dev -pnpm indexer:monad-mainnet:codegen && pnpm indexer:monad-mainnet:dev ``` ### Run the Dashboard @@ -71,132 +73,74 @@ pnpm indexer:monad-mainnet:codegen && pnpm indexer:monad-mainnet:dev pnpm dashboard:dev ``` -The dashboard connects to Hasura (local or hosted) to display real-time pool data. - ## Environment Variables ### Indexer Create `indexer-envio/.env` from `indexer-envio/.env.example`: -| Variable | Description | Default | -| ------------------- | ---------------------------- | ---------- | -| `ENVIO_API_TOKEN` | Envio platform API token | — | -| `ENVIO_RPC_URL` | Celo RPC endpoint | — | -| `ENVIO_START_BLOCK` | Block to start indexing from | `60664513` | +| Variable | Description | +| ------------------------- | ------------------------------------- | +| `ENVIO_RPC_URL_42220` | Celo Mainnet RPC endpoint | +| `ENVIO_RPC_URL_143` | Monad Mainnet RPC endpoint | +| `ENVIO_START_BLOCK_CELO` | Celo start block (default: 60664500) | +| `ENVIO_START_BLOCK_MONAD` | Monad start block (default: 60730000) | ### Dashboard -The dashboard supports multiple network targets via `_` suffix env vars: - -| Variable | Description | -| --------------------------------------------- | ------------------------------------------------ | -| `NEXT_PUBLIC_HASURA_URL_CELO_MAINNET_HOSTED` | Hasura/GraphQL endpoint — Celo Mainnet (hosted) | -| `NEXT_PUBLIC_HASURA_URL_MONAD_MAINNET_HOSTED` | Hasura/GraphQL endpoint — Monad Mainnet (hosted) | -| `NEXT_PUBLIC_HASURA_URL_MONAD_TESTNET_HOSTED` | Hasura/GraphQL endpoint — Monad Testnet (hosted) | -| `NEXT_PUBLIC_HASURA_URL_CELO_SEPOLIA_HOSTED` | Hasura/GraphQL endpoint — Celo Sepolia (hosted) | -| `NEXT_PUBLIC_HASURA_URL_CELO_MAINNET` | Hasura endpoint — Celo Mainnet (local) | -| `NEXT_PUBLIC_HASURA_URL_CELO_SEPOLIA` | Hasura endpoint — Celo Sepolia (local) | -| `NEXT_PUBLIC_EXPLORER_URL_CELO_MAINNET` | Block explorer — Celo Mainnet | -| `NEXT_PUBLIC_EXPLORER_URL_CELO_SEPOLIA` | Block explorer — Celo Sepolia | -| `UPSTASH_REDIS_REST_URL` | Address labels storage (Upstash Redis) | -| `UPSTASH_REDIS_REST_TOKEN` | Address labels Redis auth token | -| `BLOB_READ_WRITE_TOKEN` | Vercel Blob token for daily label backups | +| Variable | Description | +| -------------------------------------------- | ------------------------------------------------- | +| `NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED` | Shared multichain GraphQL endpoint (Celo + Monad) | +| `NEXT_PUBLIC_HASURA_URL_CELO_SEPOLIA_HOSTED` | Celo Sepolia hosted endpoint | +| `UPSTASH_REDIS_REST_URL` | Address labels storage (Upstash Redis) | +| `UPSTASH_REDIS_REST_TOKEN` | Address labels Redis auth token | +| `BLOB_READ_WRITE_TOKEN` | Vercel Blob token for daily label backups | -Production env vars are managed by Terraform — do not edit them in the Vercel dashboard. See [`terraform/`](./terraform/) and [`docs/deployment.md`](./docs/deployment.md). - -## Contract Addresses - -Contract addresses and ABIs are sourced from the published [`@mento-protocol/contracts`](https://www.npmjs.com/package/@mento-protocol/contracts) npm package — no vendored JSON files. The active treb deployment namespace per chain is declared in [`shared-config/deployment-namespaces.json`](./shared-config/deployment-namespaces.json): - -```json -{ - "42220": "mainnet", - "11142220": "testnet-v2-rc5" -} -``` - -**To promote a new treb deployment** (e.g. after a new `mento-deployments-v2` release): - -1. Publish a new `@mento-protocol/contracts` version from `mento-deployments-v2` -2. Update the package version in `indexer-envio/package.json` and `ui-dashboard/package.json` -3. Update the namespace string(s) in `shared-config/deployment-namespaces.json` -4. Run `pnpm install` +Production env vars are managed by Terraform. See [`terraform/`](./terraform/). ## Deployment ### Indexer → Envio Hosted -Each network has a dedicated deploy branch Envio watches: - -| Network | Deploy Branch | -| ------------- | ---------------------- | -| Celo Mainnet | `deploy/celo-mainnet` | -| Celo Sepolia | `deploy/celo-sepolia` | -| Monad Mainnet | `deploy/monad-mainnet` | -| Monad Testnet | `deploy/monad-testnet` | - -Push to trigger a redeploy: +Push to the `envio` branch to trigger a hosted reindex: ```bash -pnpm deploy:indexer celo-mainnet -# or run without args to be prompted: pnpm deploy:indexer -# or push directly: -git push origin main:deploy/celo-mainnet ``` -> ⚠️ **Celo Sepolia endpoint changes on each Envio redeploy.** After redeploying the Celo Sepolia indexer, update `hasura_url_celo_sepolia_hosted` in `terraform/terraform.tfvars` and run `pnpm infra:apply`. +The `mento` project on [Envio Cloud](https://envio.dev/app/mento-protocol/mento) watches this branch. ### Dashboard → Vercel -Vercel's native Git integration watches `main` — every push that touches `ui-dashboard/` auto-deploys the dashboard to [monitoring.mento.org](https://monitoring.mento.org). +Every push to `main` that touches `ui-dashboard/` auto-deploys to [monitoring.mento.org](https://monitoring.mento.org). -All infrastructure (Vercel project, env vars, Upstash Redis, custom domain) is managed by Terraform: +Infrastructure (Vercel project, env vars, Upstash Redis) is managed by Terraform: ```bash pnpm infra:plan # preview changes pnpm infra:apply # apply changes ``` -See [`docs/deployment.md`](./docs/deployment.md) for the full setup guide and troubleshooting. - -## CI - -GitHub Actions runs on every PR: +## Contract Addresses -- ESLint 10 (no `eslint-config-next` — uses `@eslint/js` + `typescript-eslint` + `@eslint-react`) -- Vitest (105 tests) -- TypeScript typecheck -- Codecov coverage reporting +Sourced from the published [`@mento-protocol/contracts`](https://www.npmjs.com/package/@mento-protocol/contracts) npm package. The active treb deployment namespace per chain is declared in [`shared-config/deployment-namespaces.json`](./shared-config/deployment-namespaces.json). ## Key Files -| What | Where | -| ------------------------------ | -------------------------------------------- | -| **Deployment namespace map** | `shared-config/deployment-namespaces.json` | -| Indexer schema | `indexer-envio/schema.graphql` | -| Event handlers | `indexer-envio/src/EventHandlers.ts` | -| Contract address resolution | `indexer-envio/src/contractAddresses.ts` | -| Celo mainnet config | `indexer-envio/config.celo.mainnet.yaml` | -| Celo Sepolia config | `indexer-envio/config.celo.sepolia.yaml` | -| Monad mainnet/testnet configs | `indexer-envio/config.monad.*.yaml` | -| Dashboard app | `ui-dashboard/src/app/` | -| Address book page | `ui-dashboard/src/app/address-book/page.tsx` | -| Address labels API | `ui-dashboard/src/app/api/address-labels/` | -| Address labels storage | `ui-dashboard/src/lib/address-labels.ts` | -| Network defs + contract labels | `ui-dashboard/src/lib/networks.ts` | -| GraphQL queries | `ui-dashboard/src/lib/queries.ts` | -| Pool type helper | `ui-dashboard/src/lib/tokens.ts` | -| Terraform infrastructure | `terraform/` | -| Deployment guide | `docs/deployment.md` | -| Technical spec | `SPEC.md` | -| Roadmap | `docs/ROADMAP.md` | +| What | Where | +| ------------------------- | ---------------------------------------------- | +| Indexer schema | `indexer-envio/schema.graphql` | +| Event handlers | `indexer-envio/src/EventHandlers.ts` | +| Pool ID helpers | `indexer-envio/src/helpers.ts` | +| Multichain config | `indexer-envio/config.multichain.mainnet.yaml` | +| Indexer status + endpoint | `indexer-envio/STATUS.md` | +| Dashboard app | `ui-dashboard/src/app/` | +| Network defs | `ui-dashboard/src/lib/networks.ts` | +| GraphQL queries | `ui-dashboard/src/lib/queries.ts` | +| Terraform infrastructure | `terraform/` | ## Documentation -- [`SPEC.md`](./SPEC.md) — Full technical specification -- [`docs/ROADMAP.md`](./docs/ROADMAP.md) — Current state + upcoming work -- [`docs/BACKLOG.md`](./docs/BACKLOG.md) — Detailed task backlog -- [`docs/deployment.md`](./docs/deployment.md) — Deployment guide - [`indexer-envio/README.md`](./indexer-envio/README.md) — Indexer reference +- [`indexer-envio/STATUS.md`](./indexer-envio/STATUS.md) — Current sync state + endpoint +- [`docs/deployment.md`](./docs/deployment.md) — Full deployment guide diff --git a/indexer-envio/.env.example b/indexer-envio/.env.example index 3a6531f8..8772d0b6 100644 --- a/indexer-envio/.env.example +++ b/indexer-envio/.env.example @@ -1,4 +1,12 @@ # To create or update a token visit https://envio.dev/app/api-tokens ENVIO_API_TOKEN="" -ENVIO_RPC_URL="http://34.32.123.41:8545" -ENVIO_START_BLOCK="60548751" + +# Multichain mode — set per-chain RPC endpoints. +# Do NOT set the generic ENVIO_RPC_URL in multichain mode; it would route all +# chains to the same endpoint and produce incorrect RPC reads. +ENVIO_RPC_URL_42220="https://forno.celo.org" +ENVIO_RPC_URL_143="https://rpc2.monad.xyz" + +# Optional start block overrides (defaults are set in EventHandlers.ts) +# ENVIO_START_BLOCK_CELO="60664500" +# ENVIO_START_BLOCK_MONAD="60730000" diff --git a/indexer-envio/AGENTS.md b/indexer-envio/AGENTS.md index 6cfe1fd4..f829c329 100644 --- a/indexer-envio/AGENTS.md +++ b/indexer-envio/AGENTS.md @@ -52,8 +52,11 @@ pnpm test # Run tests (mocha + chai) Copy `.env.example` → `.env` and set: -- `ENVIO_API_TOKEN` — Get from -- `ENVIO_RPC_URL` — Celo RPC endpoint -- `ENVIO_START_BLOCK` — Block number to start indexing from +- `ENVIO_RPC_URL_42220` — Celo Mainnet RPC endpoint (e.g. `https://forno.celo.org`) +- `ENVIO_RPC_URL_143` — Monad Mainnet RPC endpoint (e.g. `https://rpc2.monad.xyz`) +- `ENVIO_START_BLOCK_CELO` — (optional) Celo start block, defaults to 60664500 +- `ENVIO_START_BLOCK_MONAD` — (optional) Monad start block, defaults to 60730000 -For Celo Sepolia: use root `pnpm indexer:celo-sepolia:dev`. For Celo mainnet: `pnpm indexer:celo-mainnet:dev`. For Monad: `pnpm indexer:monad-mainnet:dev` or `pnpm indexer:monad-testnet:dev`. +Do **not** set the generic `ENVIO_RPC_URL` in multichain mode — it would route all chains to the same endpoint and produce incorrect RPC reads for chain-specific calls. + +Default (multichain Celo + Monad mainnet): `pnpm indexer:codegen && pnpm indexer:dev`. For Celo Sepolia testnet: `pnpm indexer:celo-sepolia:dev`. For Monad Testnet: `pnpm indexer:monad-testnet:dev`. diff --git a/indexer-envio/README.md b/indexer-envio/README.md index f1f03cbc..efc20f3e 100644 --- a/indexer-envio/README.md +++ b/indexer-envio/README.md @@ -1,80 +1,56 @@ # Mento v3 Envio HyperIndex Indexer -Envio HyperIndex indexer for Mento v3 on Celo Mainnet and Celo Sepolia. Tracks FPMM pool activity, oracle health, trading limits, and rebalancer liveness. +Multichain Envio HyperIndex indexer for Mento v3 — Celo Mainnet (42220) and Monad Mainnet (143). +Tracks FPMM pool activity, oracle health, trading limits, and rebalancer liveness. ## What It Does -The indexer listens to on-chain events from Mento v3 contracts and writes structured entities to Postgres, exposed via Hasura GraphQL. +Listens to on-chain events from Mento v3 contracts and writes structured entities to Postgres, exposed via Hasura GraphQL. ### Events Indexed -| Contract | Events | -| ------------------ | ----------------------------------------------------------- | -| FPMMFactory | `FPMMDeployed` | -| FPMM (pool) | `Swap`, `Mint`, `Burn`, `UpdateReserves`, `Rebalanced` | -| VirtualPoolFactory | `VirtualPoolDeployed`, `PoolDeprecated` | -| SortedOracles | `OracleReportRemoved`, `OracleReportUpdated` (mainnet only) | +| Contract | Events | +| --------------------- | ------------------------------------------------------------------------------------------------------------ | +| FPMMFactory | `FPMMDeployed` | +| FPMM (pool) | `Swap`, `Mint`, `Burn`, `UpdateReserves`, `Rebalanced`, `TradingLimitConfigured`, `LiquidityStrategyUpdated` | +| VirtualPool | `Swap`, `Mint`, `Burn`, `UpdateReserves`, `Rebalanced` | +| VirtualPoolFactory | `VirtualPoolDeployed`, `PoolDeprecated` | +| SortedOracles | `OracleReported`, `MedianUpdated`, `ReportExpirySet`, `TokenReportExpirySet` | +| OpenLiquidityStrategy | `PoolAdded`, `PoolRemoved`, `LiquidityMoved`, `RebalanceCooldownSet` | +| ERC20FeeToken | `Transfer` (dynamically registered from FPMMDeployed events) | ### Entities Written -| Entity | Description | -| ---------------------- | ------------------------------------------------------------------------------------------ | -| `Pool` | Per-pool state: reserves, health status, oracle state, trading limits, rebalancer liveness | -| `PoolSnapshot` | Hourly pre-aggregated: volume, TVL, swap count, rebalance count | -| `OracleSnapshot` | Per-oracle-event: price, deviation, health status | -| `TradingLimit` | Per-pool per-token: limit state, netflow, pressure ratio | -| `SwapEvent` | Individual swap: amounts in/out, txHash, timestamp | -| `LiquidityEvent` | Mint/burn events: amounts, txHash | -| `ReserveUpdate` | Reserve snapshots on every `UpdateReserves` | -| `RebalanceEvent` | Per-rebalance: improvement, effectiveness ratio | -| `FactoryDeployment` | Pool creation events from factory | -| `VirtualPoolLifecycle` | VirtualPool deploy/deprecate events | - -### Pool Type Logic - -FPMM pools (4 on mainnet) have oracle health, trading limits, and rebalancer liveness. -VirtualPools get `"N/A"` for these fields. - -The single source of truth is `isFpmm()` in `ui-dashboard/src/lib/tokens.ts`, which checks the pool's source field. - -## Mainnet Contracts - -| Contract | Address | -| ------------- | -------------------------------------------- | -| FPMMFactory | `0xa849b475FE5a4B5C9C3280152c7a1945b907613b` | -| Router | `0x4861840C2EfB2b98312B0aE34d86fD73E8f9B6f6` | -| OracleAdapter | `0xa472fBBF4b890A54381977ac392BdF82EeC4383a` | -| SortedOracles | `0xefB84935239dAcdecF7c5bA76d8dE40b077B7b33` | - -Start block: `60664513` - -## Mainnet FPMM Pools - -| Pool Address | Pair | -| -------------------------------------------- | -------------- | -| `0x8c0014afe032e4574481d8934504100bf23fcb56` | USDm / GBPm | -| `0xb285d4c7133d6f27bfb29224fb0d22e7ec3ddd2d` | USDm / axlUSDC | -| `0x462fe04b4fd719cbd04c0310365d421d02aaa19e` | USDm / USDC | -| `0x0feba760d93423d127de1b6abecdb60e5253228d` | USDT / USDm | - -## Configuration Files - -| File | Network | -| -------------------------- | ------------------- | -| `config.celo.mainnet.yaml` | Celo Mainnet | -| `config.celo.sepolia.yaml` | Celo Sepolia | -| `config.celo.devnet.yaml` | Celo DevNet (local) | - -## Schema - -See [`schema.graphql`](./schema.graphql) for the full entity model. - -Key design decisions: - -- `PoolSnapshot` uses hourly buckets (industry standard, Uniswap/Balancer pattern) -- Gap-filling for charts is done in the dashboard layer (forward-fill) — Envio `onBlock` handlers lack timestamps -- `TradingLimit` has a composite ID: `{poolId}-{tokenAddress}` -- All BigInt fields use string representation in GraphQL responses +| Entity | Description | +| ---------------------- | ----------------------------------------------------------------------------- | +| `Pool` | Per-pool state: reserves, health, oracle, trading limits, rebalancer liveness | +| `PoolSnapshot` | Hourly aggregates: volume, TVL, swap count | +| `OracleSnapshot` | Per-oracle-event: price, deviation, health status | +| `TradingLimit` | Per-pool per-token: limit state, netflow, pressure ratio | +| `SwapEvent` | Individual swap: amounts in/out, txHash, timestamp | +| `LiquidityEvent` | Mint/burn events | +| `ReserveUpdate` | Reserve snapshots on every `UpdateReserves` | +| `RebalanceEvent` | Per-rebalance: improvement, effectiveness ratio | +| `FactoryDeployment` | Pool creation events from factory | +| `VirtualPoolLifecycle` | VirtualPool deploy/deprecate events | +| `OlsPool` | Open Liquidity Strategy pool registrations | +| `OlsLiquidityEvent` | OLS liquidity movements | +| `ProtocolFeeTransfer` | ERC20 fee token transfers | + +### Pool ID Format + +All entity IDs are namespaced by chain: `{chainId}-{address}` (e.g. `42220-0x02fa...`, `143-0xd0e9...`). +This prevents collisions when the same contract address is deployed on multiple chains. + +## Configuration + +| File | Networks | +| -------------------------------- | ------------------------------ | +| `config.multichain.mainnet.yaml` | Celo Mainnet + Monad (default) | +| `config.celo.sepolia.yaml` | Celo Sepolia (testnet) | +| `config.monad.testnet.yaml` | Monad Testnet | + +Deploy branch: `envio` → triggers hosted reindex on push. ## Local Development @@ -87,15 +63,11 @@ Key design decisions: ### Setup ```bash -# From repo root cp indexer-envio/.env.example indexer-envio/.env -# Edit .env: set ENVIO_RPC_URL and ENVIO_START_BLOCK +# Set ENVIO_RPC_URL_42220 and ENVIO_RPC_URL_143 -# Generate types from schema + config -pnpm indexer:celo-sepolia:codegen - -# Start indexer stack (Docker + indexer) -pnpm indexer:celo-sepolia:dev +# Generate types + start multichain indexer +pnpm indexer:codegen && pnpm indexer:dev ``` Hasura console: `http://localhost:8080` (admin secret: `testing`) @@ -104,36 +76,33 @@ GraphQL endpoint: `http://localhost:8080/v1/graphql` ### Available Commands (from repo root) ```bash -pnpm indexer:celo-sepolia:codegen # Generate types for Celo Sepolia +pnpm indexer:codegen # Generate types (multichain — Celo + Monad mainnet) +pnpm indexer:dev # Start local multichain indexer +pnpm indexer:celo-sepolia:codegen # Generate types for Celo Sepolia testnet pnpm indexer:celo-sepolia:dev # Start local Celo Sepolia indexer -pnpm indexer:celo-mainnet:codegen # Generate types for Celo Mainnet -pnpm indexer:celo-mainnet:dev # Start local Celo Mainnet indexer -pnpm deploy:indexer celo-mainnet # Push to deploy/celo-mainnet branch -pnpm deploy:indexer celo-sepolia # Push to deploy/celo-sepolia branch -pnpm deploy:indexer # Prompts for network +pnpm indexer:monad-testnet:codegen # Generate types for Monad Testnet +pnpm indexer:monad-testnet:dev # Start local Monad Testnet indexer +pnpm deploy:indexer # Push to envio branch → triggers hosted reindex ``` ### From `indexer-envio/` directory ```bash -pnpm codegen # Generate types (loads .env automatically) +pnpm codegen # Generate types pnpm dev # Start indexer stack pnpm start # Start without codegen pnpm stop # Stop Docker containers ``` -## Key Files +## Schema + +See [`schema.graphql`](./schema.graphql) for the full entity model. + +Key design decisions: -| File | Purpose | -| ----------------------------------- | ---------------------------------------------------- | -| `schema.graphql` | Entity model (Hasura schema) | -| `src/EventHandlers.ts` | Event → entity mapping logic | -| `src/contractAddresses.ts` | Contract/package address resolution | -| `config/deployment-namespaces.json` | Vendored namespace map for Envio hosted builds | -| `config.celo.mainnet.yaml` | Mainnet chain config, contracts, events, start block | -| `config.celo.sepolia.yaml` | Sepolia chain config | -| `abis/` | Contract ABIs | -| `.env.example` | Environment variable template | +- `PoolSnapshot` uses hourly buckets (forward-fill for charts is done in the dashboard) +- `TradingLimit` has composite ID: `{poolId}-{tokenAddress}` +- All BigInt fields use string representation in GraphQL responses ## Example Queries @@ -143,6 +112,7 @@ pnpm stop # Stop Docker containers query RecentSwaps { SwapEvent(limit: 20, order_by: { blockTimestamp: desc }) { id + chainId poolId amount0In amount1In @@ -160,6 +130,7 @@ query RecentSwaps { query PoolHealth { Pool { id + chainId token0 token1 healthStatus @@ -174,53 +145,30 @@ query PoolHealth { } ``` -**Trading limits:** - -```graphql -query TradingLimits { - TradingLimit { - poolId - token - limitPressure0 - limitPressure1 - limitStatus - netflow0 - netflow1 - updatedAtTimestamp - } -} -``` +## Deployment -**Oracle snapshots for a pool:** +Push to the `envio` branch to trigger a hosted reindex: -```graphql -query OracleHistory($poolId: String!) { - OracleSnapshot( - where: { poolId: { _eq: $poolId } } - order_by: { timestamp: desc } - limit: 100 - ) { - timestamp - oraclePrice - oraclePriceDenom - oracleOk - priceDifference - rebalanceThreshold - numReporters - } -} +```bash +pnpm deploy:indexer ``` -## Deployment - -The indexer deploys via Git push to a deploy branch. Envio watches the branch and auto-redeploys. +The `mento` project on Envio Cloud watches this branch. The static production endpoint never changes on redeploy: -Because Envio hosted may build `indexer-envio/` outside the pnpm workspace, the package keeps a committed copy of `deployment-namespaces.json` in `indexer-envio/config/`. The repo test suite verifies that file stays in sync with `shared-config/deployment-namespaces.json`. +``` +https://indexer.hyperindex.xyz/2f3dd15/v1/graphql +``` -> ⚠️ **Each deployment generates a new endpoint URL hash.** Update the Vercel env var after every redeploy. See [`docs/deployment.md`](../docs/deployment.md). +See [`STATUS.md`](./STATUS.md) for current sync state and endpoint details. -## Known Limitations +## Key Files -- Only one indexer can run locally at a time (port 9898 hardcoded in Envio) -- SortedOracles events are only indexed on mainnet (Sepolia returns zero address for oracle contracts) -- Envio free tier: 100k event soft limit, 30-day expiry — monitor and redeploy before expiry +| File | Purpose | +| ----------------------------------- | ----------------------------------------- | +| `schema.graphql` | Entity model | +| `src/EventHandlers.ts` | Event → entity mapping | +| `src/helpers.ts` | `makePoolId`, `poolIdToAddress` utilities | +| `src/rpc.ts` | RPC read helpers (per-chain clients) | +| `config.multichain.mainnet.yaml` | Production config (Celo + Monad) | +| `config/deployment-namespaces.json` | Vendored namespace map for hosted builds | +| `abis/` | Contract ABIs | diff --git a/indexer-envio/STATUS.md b/indexer-envio/STATUS.md index af23bfb7..603c5e0a 100644 --- a/indexer-envio/STATUS.md +++ b/indexer-envio/STATUS.md @@ -55,7 +55,7 @@ All entities have `chainId: Int! @index` and namespaced IDs since PR #95 (2026-0 See [`README.md`](./README.md#local-development) for setup instructions. ```bash -pnpm indexer:multichain:codegen -pnpm indexer:multichain:dev +pnpm indexer:codegen +pnpm indexer:dev # Hasura: http://localhost:8080 (secret: testing) ``` diff --git a/package.json b/package.json index 6c47d5b1..fcc14d83 100644 --- a/package.json +++ b/package.json @@ -7,14 +7,10 @@ "dashboard:dev": "pnpm --filter @mento-protocol/ui-dashboard dev", "deploy:dashboard": "./scripts/deploy-dashboard.sh", "deploy:indexer": "./scripts/deploy-indexer.sh", - "indexer:celo-mainnet:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.celo.mainnet.yaml", - "indexer:celo-mainnet:dev": "ENVIO_PG_PORT=5435 ENVIO_PG_DATABASE=envio-celo-mainnet ENVIO_START_BLOCK=60664500 pnpm --filter @mento-protocol/indexer-envio dev --config config.celo.mainnet.yaml", + "indexer:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.mainnet.yaml", + "indexer:dev": "pnpm --filter @mento-protocol/indexer-envio dev --config config.multichain.mainnet.yaml", "indexer:celo-sepolia:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.celo.sepolia.yaml", "indexer:celo-sepolia:dev": "ENVIO_PG_PORT=5434 ENVIO_PG_DATABASE=envio-celo-sepolia ENVIO_RPC_URL=https://forno.celo-sepolia.celo-testnet.org ENVIO_START_BLOCK=18901381 pnpm --filter @mento-protocol/indexer-envio dev --config config.celo.sepolia.yaml", - "indexer:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.celo.devnet.yaml", - "indexer:dev": "pnpm --filter @mento-protocol/indexer-envio dev --config config.celo.devnet.yaml", - "indexer:monad-mainnet:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.monad.mainnet.yaml", - "indexer:monad-mainnet:dev": "ENVIO_PG_PORT=5436 ENVIO_PG_DATABASE=envio-monad-mainnet ENVIO_START_BLOCK=60730000 pnpm --filter @mento-protocol/indexer-envio dev --config config.monad.mainnet.yaml", "indexer:monad-testnet:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.monad.testnet.yaml", "indexer:monad-testnet:dev": "ENVIO_PG_PORT=5437 ENVIO_PG_DATABASE=envio-monad-testnet ENVIO_START_BLOCK=17932300 pnpm --filter @mento-protocol/indexer-envio dev --config config.monad.testnet.yaml", "infra:apply": "terraform -chdir=terraform apply", diff --git a/scripts/setup.sh b/scripts/setup.sh index 52869d40..8e63eab6 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -23,8 +23,8 @@ echo " core.hooksPath → .trunk/hooks" echo "▶ Installing dependencies..." pnpm install --frozen-lockfile -echo "▶ Running Envio codegen (celo-mainnet config)..." -pnpm indexer:celo-mainnet:codegen +echo "▶ Running Envio codegen (multichain config)..." +pnpm indexer:codegen echo "" echo "✅ Setup complete. You're ready to work and push." diff --git a/terraform/main.tf b/terraform/main.tf index cb58ee7d..e1c867dc 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -94,23 +94,6 @@ resource "vercel_project_environment_variable" "hasura_url_celo_sepolia" { target = ["production", "preview"] } -resource "vercel_project_environment_variable" "hasura_url_celo_mainnet" { - project_id = vercel_project.dashboard.id - team_id = var.vercel_team_id - key = "NEXT_PUBLIC_HASURA_URL_CELO_MAINNET_HOSTED" - value = var.hasura_url_celo_mainnet_hosted - target = ["production", "preview"] -} - -resource "vercel_project_environment_variable" "hasura_url_monad_mainnet" { - count = var.hasura_url_monad_mainnet_hosted != "" ? 1 : 0 - project_id = vercel_project.dashboard.id - team_id = var.vercel_team_id - key = "NEXT_PUBLIC_HASURA_URL_MONAD_MAINNET_HOSTED" - value = var.hasura_url_monad_mainnet_hosted - target = ["production", "preview"] -} - resource "vercel_project_environment_variable" "hasura_url_monad_testnet" { count = var.hasura_url_monad_testnet_hosted != "" ? 1 : 0 project_id = vercel_project.dashboard.id diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example index bc2c32eb..9f8f084b 100644 --- a/terraform/terraform.tfvars.example +++ b/terraform/terraform.tfvars.example @@ -19,10 +19,9 @@ upstash_api_key = "..." # ── Hasura / Envio ──────────────────────────────────────────────────────────── # Defaults in variables.tf point to the current Envio-hosted endpoints. # Override here only if the deployment IDs change. -# hasura_url_celo_sepolia_hosted = "https://indexer.hyperindex.xyz/fc3170d/v1/graphql" -# hasura_url_celo_mainnet_hosted = "https://indexer.hyperindex.xyz/60ff18c/v1/graphql" -# hasura_url_monad_mainnet_hosted = "https://indexer.hyperindex.xyz/cfeda9e/v1/graphql" -# hasura_url_monad_testnet_hosted = "https://indexer.dev.hyperindex.xyz/7231884/v1/graphql" +# hasura_url_multichain_hosted = "https://indexer.hyperindex.xyz/2f3dd15/v1/graphql" +# hasura_url_celo_sepolia_hosted = "https://indexer.hyperindex.xyz/fc3170d/v1/graphql" +# hasura_url_monad_testnet_hosted = "https://indexer.dev.hyperindex.xyz//v1/graphql" # ── Vercel Blob ─────────────────────────────────────────────────────────────── # Provision the store once (see README), then paste the token here. diff --git a/terraform/variables.tf b/terraform/variables.tf index 3878b9ba..7723c406 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -45,7 +45,7 @@ variable "upstash_region" { # ── Hasura / Envio ──────────────────────────────────────────────────────────── variable "hasura_url_multichain_hosted" { - description = "GraphQL endpoint for the shared multichain Envio indexer (Celo + Monad). When set, both celo-mainnet-hosted and monad-mainnet-hosted networks will query this endpoint filtered by chainId. Takes precedence over per-chain URLs." + description = "GraphQL endpoint for the shared multichain Envio indexer (Celo + Monad). Both celo-mainnet-hosted and monad-mainnet-hosted networks query this single endpoint, filtered by chainId." type = string default = "https://indexer.hyperindex.xyz/2f3dd15/v1/graphql" } @@ -56,18 +56,6 @@ variable "hasura_url_celo_sepolia_hosted" { default = "https://indexer.hyperindex.xyz/fc3170d/v1/graphql" } -variable "hasura_url_celo_mainnet_hosted" { - description = "GraphQL endpoint for the hosted Celo Mainnet Envio indexer." - type = string - default = "https://indexer.hyperindex.xyz/60ff18c/v1/graphql" -} - -variable "hasura_url_monad_mainnet_hosted" { - description = "GraphQL endpoint for the hosted Monad Mainnet Envio indexer. Leave empty until indexer is deployed." - type = string - default = "https://indexer.hyperindex.xyz/cfeda9e/v1/graphql" -} - variable "hasura_url_monad_testnet_hosted" { description = "GraphQL endpoint for the hosted Monad Testnet Envio indexer. Leave empty until indexer is deployed." type = string diff --git a/ui-dashboard/.env.production.local.example b/ui-dashboard/.env.production.local.example index 07346cf0..14bcd759 100644 --- a/ui-dashboard/.env.production.local.example +++ b/ui-dashboard/.env.production.local.example @@ -1,35 +1,20 @@ # Production env vars for Vercel — copy to .env.production.local and fill in values. # .env.production.local is gitignored; this example file is committed. # -# PREFERRED: set NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED to the shared multichain -# Envio deployment (Celo + Monad in one). Both celo-mainnet-hosted and -# monad-mainnet-hosted networks will use this endpoint, filtered by chainId. -# -# FALLBACK: set the per-chain vars below if you need to point each chain at a -# dedicated single-chain deployment. The multichain var takes precedence when set. +# Both celo-mainnet-hosted and monad-mainnet-hosted networks use +# NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED, filtered by chainId. # # Local networks (devnet, celo-sepolia-local, celo-mainnet-local) are hidden by # default on deployed environments. Set this to true in .env.local to show them. # NEXT_PUBLIC_SHOW_LOCAL_NETWORKS=true -# ── Multichain Envio indexer (Celo + Monad, preferred) ─────────────────────── -NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED=https://indexer.hyperindex.xyz/41980c8/v1/graphql +# ── Multichain Envio indexer (Celo + Monad) ─────────────────────────────────── +NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED=https://indexer.hyperindex.xyz/2f3dd15/v1/graphql # ── Celo Sepolia (hosted Envio indexer) ────────────────────────────────────── - -# GraphQL endpoint from your Envio hosted deployment dashboard. NEXT_PUBLIC_HASURA_URL_CELO_SEPOLIA_HOSTED=https://indexer.hyperindex.xyz/fc3170d/v1/graphql - -# Block explorer base URL for address/tx links. Default is already correct. # NEXT_PUBLIC_EXPLORER_URL_CELO_SEPOLIA_HOSTED=https://celo-sepolia.blockscout.com -# ── Celo Mainnet (hosted Envio indexer — fallback, prefer MULTICHAIN above) ── -# NEXT_PUBLIC_HASURA_URL_CELO_MAINNET_HOSTED=https://indexer.hyperindex.xyz/60ff18c/v1/graphql - -# ── Monad Mainnet (hosted Envio indexer — fallback, prefer MULTICHAIN above) ─ -# NEXT_PUBLIC_HASURA_URL_MONAD_MAINNET_HOSTED=https://indexer.hyperindex.xyz/cfeda9e/v1/graphql -# NEXT_PUBLIC_EXPLORER_URL_MONAD_MAINNET_HOSTED=https://monadscan.com - -# ── Monad Testnet (hosted Envio indexer) — deploy this first to verify ─────── +# ── Monad Testnet (hosted Envio indexer) ───────────────────────────────────── # NEXT_PUBLIC_HASURA_URL_MONAD_TESTNET_HOSTED=https://indexer.hyperindex.xyz//v1/graphql # NEXT_PUBLIC_EXPLORER_URL_MONAD_TESTNET_HOSTED=https://testnet.monadscan.com diff --git a/ui-dashboard/src/lib/__tests__/networks.test.ts b/ui-dashboard/src/lib/__tests__/networks.test.ts index 3200cfa0..164b12ce 100644 --- a/ui-dashboard/src/lib/__tests__/networks.test.ts +++ b/ui-dashboard/src/lib/__tests__/networks.test.ts @@ -157,30 +157,31 @@ describe("NETWORKS — Monad networks", () => { const USDM_MONAD_MAINNET = "0xbc69212b8e4d445b2307c9d32dd68e2a4df00115"; const USDM_MONAD_TESTNET = "0x5ecc03111ad2a78f981a108759bc73bae2ab31bc"; - it("does not mark monad-mainnet-hosted configured when only the Celo hosted URL is set", async () => { + it("does not mark monad-mainnet-hosted configured when multichain URL is not set", async () => { vi.stubEnv("NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED", ""); - vi.stubEnv("NEXT_PUBLIC_HASURA_URL_MONAD_MAINNET_HOSTED", ""); - vi.stubEnv( - "NEXT_PUBLIC_HASURA_URL_CELO_MAINNET_HOSTED", - "https://celo.example/v1/graphql", - ); const networks = await import("../networks"); expect(networks.NETWORKS["monad-mainnet-hosted"].hasuraUrl).toBe(""); expect(networks.isConfiguredNetworkId("monad-mainnet-hosted")).toBe(false); }); - it("falls back to chain-specific hosted URL when multichain env var is empty", async () => { - vi.stubEnv("NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED", ""); + it("wires the multichain URL into both celo-mainnet-hosted and monad-mainnet-hosted, trimming whitespace", async () => { + // Positive-path: non-empty multichain URL → both hosted networks visible. + // Also verifies that leading/trailing whitespace is stripped (env var may + // contain spaces in some CI setups). vi.stubEnv( - "NEXT_PUBLIC_HASURA_URL_MONAD_MAINNET_HOSTED", - "https://monad.example/v1/graphql", + "NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED", + " https://indexer.hyperindex.xyz/2f3dd15/v1/graphql ", ); const networks = await import("../networks"); + expect(networks.NETWORKS["celo-mainnet-hosted"].hasuraUrl).toBe( + "https://indexer.hyperindex.xyz/2f3dd15/v1/graphql", + ); expect(networks.NETWORKS["monad-mainnet-hosted"].hasuraUrl).toBe( - "https://monad.example/v1/graphql", + "https://indexer.hyperindex.xyz/2f3dd15/v1/graphql", ); + expect(networks.isConfiguredNetworkId("celo-mainnet-hosted")).toBe(true); expect(networks.isConfiguredNetworkId("monad-mainnet-hosted")).toBe(true); }); diff --git a/ui-dashboard/src/lib/networks.ts b/ui-dashboard/src/lib/networks.ts index 618caf95..002e1faf 100644 --- a/ui-dashboard/src/lib/networks.ts +++ b/ui-dashboard/src/lib/networks.ts @@ -191,12 +191,8 @@ export const NETWORKS: Record = { chainId: 42220, contractsNamespace: NS["celo-mainnet"], rpcUrl: process.env.NEXT_PUBLIC_RPC_URL_CELO ?? "https://forno.celo.org", - // Prefer the shared multichain endpoint, then the chain-specific override. - // Empty strings should not block fallback. hasuraUrl: - process.env.NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED?.trim() || - process.env.NEXT_PUBLIC_HASURA_URL_CELO_MAINNET_HOSTED?.trim() || - "", + process.env.NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED?.trim() ?? "", hasuraSecret: "", explorerBaseUrl: process.env.NEXT_PUBLIC_EXPLORER_URL_CELO_MAINNET_HOSTED ?? @@ -212,15 +208,8 @@ export const NETWORKS: Record = { contractsNamespace: NS["monad-mainnet"], rpcUrl: process.env.NEXT_PUBLIC_RPC_URL_MONAD_MAINNET ?? "https://rpc2.monad.xyz", - // Prefer the shared multichain endpoint, then a Monad-specific override. - // No Celo fallback — silently pointing Monad at the Celo backend masks bad - // rollouts and causes isConfiguredNetworkId() to return true for the wrong - // backend. Monad is hidden unless explicitly configured. - // Empty strings should not block fallback. hasuraUrl: - process.env.NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED?.trim() || - process.env.NEXT_PUBLIC_HASURA_URL_MONAD_MAINNET_HOSTED?.trim() || - "", + process.env.NEXT_PUBLIC_HASURA_URL_MULTICHAIN_HOSTED?.trim() ?? "", hasuraSecret: "", explorerBaseUrl: process.env.NEXT_PUBLIC_EXPLORER_URL_MONAD_MAINNET_HOSTED ??