A monorepo for all our frontend apps, designed to simplify sharing of code like components, styles, utils, and configs between different applications.
- Turborepo: For monorepo management and build tooling
- PNPM: Our package manager
- TypeScript: Our main language with shared, extendable config
- NextJS: The framework for all our frontend apps
- Tailwind CSS: For styling
- shadcn/ui: Our UI component base library to extend from
- Trunk CLI: Metalinter and formatter (ESLint, Prettier, Markdown, YAML, Shell, Commitlint)
- Vercel: For deployments and turborepo build remote caching
- GitHub Actions: For CI (with Turborepo caching for builds via Vercel)
frontend-monorepo/
├── apps/ # Frontend applications
│ ├── app.mento.org/ # Mento Exchange UI
│ ├── governance.mento.org/ # Governance UI
│ ├── reserve.mento.org/ # Reserve UI
│ └── ui.mento.org/ # Component Library Showcase
│
├── packages/ # Shared packages
│ ├── eslint-config/ # Shared ESLint configuration
│ ├── typescript-config/ # Shared TypeScript configuration
│ ├── ui/ # Shared UI library with tailwind styles and shadcn/ui components
│ ├── vitest-config/ # Shared Vitest configuration
│ └── web3/ # Shared library with web3-specific components and hooks
│
├── .github/ # GitHub workflows
│ └── workflows/ # CI/CD workflows
├── .trunk/ # Trunk CLI configuration and cache
├── docs/
│ └── adr/ # Architecture decision records and lifecycle
├── turbo.json # Turborepo configuration
└── pnpm-workspace.yaml # PNPM workspace configuration- Node.js (v22 or later)
- PNPM (v10 or later)
- Trunk CLI (automatically installed during development)
-
Clone the repository:
git clone https://github.com/mento-protocol/frontend-monorepo && cd frontend-monorepo
-
Install dependencies:
pnpm install
-
Configure environment variables for each app you plan to build or run:
cd apps/<app-name> cp .env.example .env.local
-
Build all packages:
pnpm build
-
Start the development server for all applications:
pnpm dev
Each app has its own .env.example listing the variables it needs:
apps/app.mento.org/.env.exampleapps/governance.mento.org/.env.exampleapps/reserve.mento.org/.env.exampleapps/ui.mento.org/.env.example
For each app you run locally, copy its example file to .env.local and fill in the values before building or starting dev:
cd apps/<app-name>
cp .env.example .env.localMost values are public config and safe to copy as-is. A few require secrets from a teammate or the Vercel project settings:
app.mento.orgneedsNEXT_PUBLIC_STORAGE_URL,NEXT_PUBLIC_WALLET_CONNECT_ID, andCHAINALYSIS_API_KEYgovernance.mento.orgneedsNEXT_PUBLIC_WALLET_CONNECT_ID,NEXT_PUBLIC_GRAPH_API_KEY, andETHERSCAN_API_KEYreserve.mento.orgneedsNEXT_PUBLIC_STORAGE_URLandNEXT_PUBLIC_ANALYTICS_API_URLui.mento.orgneedsNEXT_PUBLIC_STORAGE_URLfor showcase static assets
We use Trunk CLI as our universal linter and formatter. It combines ESLint, Prettier, Markdown linting, YAML linting, and more into a single, fast tool.
# Lint all files (comprehensive check)
pnpm lint
# Lint with auto-fix
pnpm lint:fix
# Format all files
pnpm format
# Check formatting without making changes
pnpm format:check
# Verify every third-party GitHub Action uses an immutable SHA + version comment
pnpm ci:action-pins
# Run the action-pin scanner and REST materializer fixture suites
pnpm ci:action-pins:test
# Remind on newly added architecture-significant workflows/workspaces
pnpm adr:check
# Test the offline ADR reminder and repository wiring
pnpm adr:check:test
# Test the network-free Vercel planning and prebuilt-build primitives
pnpm vercel:primitives:test
# Test canonical read-only Vercel state and guarded alias-drift evidence
pnpm vercel:deployment-state:test
# Test manual and automatic Vercel workflows, exact-main gating, transactions, and smoke
pnpm vercel:workflow:test
# Test preview state, reusable smoke trust, native-adapter, and Git ownership
pnpm vercel:preview:test
# Test production-shadow state, workflow, and runtime-smoke invariants
pnpm vercel:production-shadow:test
# Run the real two-origin Chromium protection-header isolation regression
pnpm --filter app.mento.org test:production-shadow:routing
# Verify exact Next.js and Vercel CLI custom deployment-ID prerequisites
pnpm vercel:versions:check
# Test and run the redaction-safe Vercel build-minute closeout analyzer
pnpm vercel:cost:test
pnpm vercel:cost:analyze --input .vercel-cost-evidence/manifest.json --format markdownTwo always-run checks protect the policy on every pull request:
GitHub Actions Policy runs the trusted base-branch checker against only the
PR head's Actions YAML, fetched as inert blobs from its exact commit through the
GitHub Git API. It never checks out or executes pull-request files. GitHub Actions Policy Source runs the proposed checker, REST materializer, and fixtures
in a credential-free pull_request workflow. After these workflows merge, branch
protection must require both Action Pin Policy and Action Pin Policy Source
so neither trusted enforcement nor proposed-policy validation can be skipped.
Because the source lane necessarily runs pull-request-controlled policy code,
changes to either policy workflow, checker, or fixture suite must also require
protected human/code-owner review or an organization required-workflow rule;
the two status contexts alone are not a tamper-proof approval boundary.
Canonical structure changes such as pnpm/Node versions, commands, or triggers
intentionally require a protected two-PR transition: first teach the trusted
checker to allow the transition while retaining the old workflow, then change
the workflow and tighten the checker. Immutable action SHA bumps are normalized
by the checker and can remain a single PR. When adding or updating a third-party
action, pin its full 40-character commit SHA and retain the release tag as an
inline comment (for example, uses: org/action@<sha> # v1.2.3).
To lint a specific application:
cd apps/<app-name>
pnpm lint # Lints only this appOr from the root directory:
trunk check apps/<app-name> # Direct Trunk usage- JavaScript/TypeScript: ESLint with your existing rules
- Code Formatting: Prettier (including Tailwind CSS class sorting)
- Markdown: Documentation formatting and best practices
- YAML: Configuration file formatting
- Shell Scripts: shellcheck and shfmt
- Git: Pre-commit and pre-push hooks
The workspace is configured to use Trunk for:
- Auto-formatting on save for JS/TS files
- Lint-on-type feedback
- Code actions for quick fixes
To run a specific application:
cd apps/<app-name>
pnpm devOr from the root directory:
pnpm dev --filter <app-name>
# i.e. pnpm dev --filter ui.mento.orgTo build a specific application:
pnpm build --filter <app-name>For wallet-gated testing against real Mento contracts, run a local anvil fork of Celo mainnet:
pnpm fork:mainnet # anvil --celo --auto-impersonate --fork-url https://forno.celo.org --port 8545
pnpm fork:seed # fund anvil's junk accounts (CELO + cUSD/cEUR/USDC/MENTO) and re-report oracle prices (mainnet forks only)
pnpm fork:testnet # same anvil flags, forking Celo Sepolia instead (fork:seed does not support testnet forks)--celo requires Foundry >= 1.4 — without it, CELO's native/ERC-20 token duality breaks and transfer() silently no-ops. fork:seed is idempotent; re-run it after every evm_revert and whenever Broker quotes start reverting (SortedOracles reports go stale on a wall-clock timescale).
Monad mainnet (chain 143) runs a different Mento stack than Celo (Router + FPMM, no Broker), so it has its own scripts and port:
pnpm fork:monad # anvil --auto-impersonate --fork-url https://rpc.monad.xyz --fork-block-number <finalized> --port 8546 (no --celo)
pnpm fork:seed:monad # fund anvil's junk accounts with MON + Reserve collateral + every Mento stable (via real Router swaps), re-report oraclesfork:seed:monad is idempotent (re-run after every evm_revert / when quotes stall). To point the app at this fork, dev/build with NEXT_PUBLIC_MONAD_RPC_URL=http://localhost:8546 — Monad has no --celo/NEXT_PUBLIC_USE_FORK redirect, so that override is the seam (it redirects both wagmi and the mento-sdk). See docs/wallet-testing.md for the full Monad runbook.
Full runbook with localStorage activation, on-chain verification, safety rules, and troubleshooting: docs/wallet-testing.md
This monorepo uses PNPM's catalog feature to centralize dependency version management. This ensures all packages and apps use consistent versions of shared dependencies, reducing conflicts and simplifying updates.
The catalog is defined in pnpm-workspace.yaml under the catalog section. Instead of specifying version numbers directly in each package.json, we reference the catalog using "catalog:".
Example in package.json:
{
"dependencies": {
"react": "catalog:",
"jotai": "catalog:",
"@tanstack/react-query": "catalog:"
}
}The actual versions are defined once in pnpm-workspace.yaml:
catalog:
"react": ^19.2.5
"jotai": ^2.16.2
"@tanstack/react-query": 5.90.16Root pnpm.overrides are used for security patches and compatibility pins.
@tanstack/react-query and @tanstack/query-core are pinned there to the
verified app-compatible version; newer compatible-range releases caused a
production QueryClient context split in app.mento.org. Remove those overrides
only after a production build and browser verification of the swap and pools
routes. Note the catalog entry above matches the override exactly (5.90.16,
not a caret range) — pnpm overrides rewrite catalog: references too, so the
catalog value must stay truthful about what's actually installed. See
docs/dependency-overrides.md for the reason
and removal condition behind every unconditional override.
When adding a new dependency that should be shared across packages:
-
Add the dependency to the catalog in
pnpm-workspace.yaml:catalog: "new-package": ^1.0.0
-
Reference it in your
package.json:{ "dependencies": { "new-package": "catalog:" } } -
Run
pnpm installto update the lockfile.
To update a dependency version across the entire monorepo:
-
Update the version in
pnpm-workspace.yaml:catalog: "react": ^19.2.0 # Updated from ^19.1.0
-
Run
pnpm installto update all packages using this dependency.
All packages referencing "react": "catalog:" will automatically use the new version.
- Use catalog (
"catalog:"): For dependencies shared across multiple packages/apps (React, TypeScript, common utilities, etc.) - Use direct versions: For app-specific dependencies that aren't shared (e.g., a Next.js plugin only used in one app)
The UI package is located in packages/ui/ and contains reusable components built with shadcn/ui.
shadcn/ui is our component base layer we extend from.
- Install the shadcn/ui component you need:
pnpm dlx shadcn@latest add button - Customize it to your needs by simply editing
./packages/ui/src/components/ui/button.tsx - Export the new component from the main barrel file
./packages/ui/src/index.ts - Build the UI package:
pnpm build --filter @mento-protocol/ui
- Create a new component in
packages/ui/src/components - Export it from
packages/ui/src/index.ts - Build the UI package:
pnpm build --filter @mento-protocol/ui
Import components into an application:
// layout.tsx
import "@mento-protocol/ui/globals.css"; // Import once at the top of the app
import { Button } from "@mento-protocol/ui";Install the public package once it has been manually published:
pnpm add @mento-protocol/uiImport the bundled stylesheet once at the app root, then import components from the package entrypoint:
import "@mento-protocol/ui/globals.css";
import { Button } from "@mento-protocol/ui";globals.css includes the Mento token layer, Tailwind-generated utilities,
component styles, and the bundled unmodified Aspekta font. Consumers that only
need the CSS variables and Tailwind v4 token declarations can import
@mento-protocol/ui/theme.css instead. React and React DOM are peer
dependencies; Tailwind CSS is an optional peer dependency unless the consuming
app compiles or extends the package's Tailwind token layer.
We use Conventional Commits for standardized commit messages. This helps with automated versioning and generating changelogs.
Each commit message should follow this format:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]Types include:
feat: A new featurefix: A bug fixdocs: Documentation changesstyle: Code style changes (formatting, etc.)refactor: Code changes that neither fix bugs nor add featurestest: Adding or fixing testschore: Changes to the build process or auxiliary toolsperf: Performance improvements
Example:
feat(ui): add new button componentGit Hooks: Trunk automatically manages git hooks that will:
- Pre-commit: Format and lint staged files
- Pre-push: Run comprehensive checks and the advisory
pnpm adr:checkreminder before pushing - Commit-msg: Validate commit message format
The repository is set up with GitHub Actions for CI:
-
CI: On every PR, it plans the changed-file scope, fans build, unit tests, and static analysis out in parallel, then reports the existing required
Build and Testsentinel. Markdown- anddocs/**-only PRs skip builds, unit tests, type checking, and Knip, but retain the Trunk static checks for Markdown validation and secret scanning. Scope-planning errors and all other PR paths fail closed into full validation. Everymainpush runs the full suite so a successfulCI/CDworkflow is trustworthy recovery evidence for the failure notifier. -
Quality budgets: The always-reported Quality Budgets check enforces production-source coverage and gzip route limits. Its general CI failure notifier opens or updates one issue for an operational workflow failure and closes the issue after recovery.
-
CD: GitHub Actions automatically builds
app.mento.org,governance.mento.org,reserve.mento.org, andui.mento.orgpreviews for trusted same-repository PRs with exact-SHA aggregateVercel Previewstatuses, independent first-eligible-plus-latest batching per target, credential-free reusable HTTP and browser smoke, one canonical GitHub Deployment per selected target, and one four-target v2 controller-journal comment with a visible outcome table per participating PR. The canonical target definitions, package names, Vercel project variables, configuration paths, and ownership modes live inscripts/vercel-preview-targets.mjs; workflow callers remain literal so GitHub can resolve each target's secrets statically. Dependabot events pass through a read-only metadata intake; trusted default-branch code re-queries the exact PR head before publishing the explicit preview-disabled status. Fork and Dependabot PRs remain credential-free. A dedicated repository-scoped GitHub credential performs only the worker-dispatch POST so terminalworkflow_runcallbacks are created; the normal job token still owns all state and recovery calls. GitHub Actions is the sole automatic branch-preview owner for App, Governance, Reserve, and UI. App completed its final exact-head and fresh post-merge canary gates in PR #609 and PR #610, respectively. Ordinary pull requests therefore have no native Vercel branch preview path. GitHub Actions is also the configured automaticmainowner for all four targets. The automaticVercel Main Deploymentworkflow runs in globalactivemode after the exact successfulCI/CDattempt, with all four per-targetmainOwnershipModevalues set togithub. Governance, Reserve, and UI stage, verify, and promote exact immutable deployments. App builds and deploys its customv3output, then verifies or assigns only its reviewed aliases. The legacy Appv2 -> productionpath remains native and is checked independently. The removed Governance QA environment is not part of this topology.Main planning uses strict
vercel-main-plan:v2evidence.stagedTargetscontains every selected target, while deterministicactiveTargetsandshadowTargetspartitions decide which selected targets may mutate public mappings. Missing, extra, malformed, or contradictory ownership state fails closed. Exact-attempt binding, repeated freshness checks, durable journal transitions, reverse-order recovery, public smoke, and an active duplicate-deployment census protect each release. Before planning, a later attempt reconciles provider mappings and candidates against the stable release manifest. The manifest binds repository, SHA, validated upstream CI run, candidate identities, and captured rollback priors; it is the sole durable cross-attempt authority. Mutation journals remain run-and-attempt scoped. A complete release is reused after fresh provider validation. An interrupted forward prefix is resumed or restored as appropriate through a fresh current-attempt journal. The exact terminal App recovery residual—at least one active non-App target with every such target at its original prior, and every reviewed App alias at one manifest-bound candidate—may restore App before new planning, but may never resume forward. No prior journal is resumed and GitHub artifacts do not authorize cross-attempt work. The compact terminal receipt and evidence are the only final-verdict handoff and support final-only reruns. A completed release emitscurrent-release-verifiedonly after fresh mapping, census/state, raw public-runtime-smoke, legacyv2, and freshness proof; it creates no journal and executes no public mutation. App shadow preparation is build-only terminal evidence, never a provider deployment. Every other non-prefix, ambiguous, conflicting, or incomplete provider state fails closed before production work continues. The version-controlled preview-controller mode isactive; per-target preview ownership and exact expected Vercel configurations are executable invariants. The trusted preview controller reads every selected target's bounded exact-head Vercel configuration and rechecks it before dispatch. Its four-state model combines preview and main ownership independently: GitHub/GitHub, GitHub/native-main, native-preview/GitHub-main, or fully native. The native-preview/GitHub-main state disablesmainwhile leaving unspecified preview branches enabled, so a target-local preview rollback cannot recreate a native main deployment. Unknown or contradictory configuration fails closed. During rollback,Vercel Previewproves owner selection and journal drain only; native Vercel deployment status and browser evidence separately prove that the preview works. The nativedeployment_statussmoke adapter remains solely for bounded App/Governance rollback verification and does not imply that ordinary native branch previews remain enabled. Its removal is deferred to the migration cleanup in issue #523, after the required observation period. A target-local main rollback restores only the target's nativemainpath and changes only its main ownership mode toshadow; previews remain GitHub-owned. A target-local preview rollback changes only preview ownership and preserves GitHub-ownedmain. Full-native restoration is a separate coordinated rollback. See ADR 0001 for the accepted ownership boundary, ADR 0002 for the journal persistence and clean-cutover decision, ADR 0003 for the worker-dispatch authentication boundary, and ADR 0005 for stable release identity and provider-side rerun reconciliation, anddocs/vercel-deployments.mdfor the four-target preview controller, active main transaction, historical shadow canary, and rollback procedures.The manual
Vercel Production Shadowworkflow can build App customv3without deploying it and upload Governance, Reserve, and UI production artifacts without custom production domains. Each staged ordinary deployment exposes its immutable hostname through the deployment URL/state identity. Vercel's provider alias list must contain the reviewed base project/team alias and may also contain one exact creator-scoped alias derived from the canonical deploymentcreator.username. The upload implicitly moves those generated system aliases, while the workflow performs no explicit alias, promote, environment-configuration, ownership, or protected/custom production-domain mutation. Its exact-SHA contract, read-only protected-domain drift checks, guarded manual operator recovery, and direct smoke are documented in the same runbook.Vercel Main Deploymentstarts only after the exact successfulCI/CDmainattempt and literalBuild and Testjob. It plans from each target's currently served SHA, so coalesced pushes cannot omit an affected change. Governance, Reserve, and UI stage immutable production candidates with--prod --skip-domainand run direct browser smoke before exact promotion. App customv3remains build-only until its activation turn because its upload moves attachedv3domains; the controller then deploys the verified output and reconciles every reviewed alias. The three ordinary public custom domains are their only protected runtime and rollback aliases; generated Vercel aliases are candidate evidence only. A durable redacted journal records intent and verified state around every public mutation. The final evidence also proves whether any replaced nativemainpath attempted a duplicate deployment. See the active-main and rollback sections ofdocs/vercel-deployments.mdfor the exact operator evidence, public runtime proof, target-local rollback, and full-native restoration order.
Dependency-installing jobs use .github/actions/pnpm-install, which pins the
Node/pnpm bootstrap, relies on actions/setup-node as the single pnpm-store
cache owner, and enforces pnpm install --frozen-lockfile.
Production-shadow candidate builds instead use the dedicated-UID
.github/actions/vercel-candidate-build boundary documented in the deployment
runbook; fresh browser-smoke jobs return to the trusted pnpm action. Publishing
overrides the trusted composite's Node version and disables its cache;
zero-dependency jobs may set up Node directly.
The docs-only decision is implemented by scripts/ci-change-plan.mjs and
covered by pnpm ci:change-plan:test, which the Unit tests job runs before the
workspace test suite. The always-run sentinel accepts skipped build and unit
test jobs only when that planner explicitly reports a documentation-only diff;
the static-analysis job must always succeed. Its dependency-heavy type-check
and Knip steps follow the planner, while Trunk remains mandatory for every diff.
Failures, cancellations, unexpected skips, and invalid planner outputs remain blocking.
Rename detection is disabled for the planning diff so both the old and new
paths are classified; moving source into docs/** cannot masquerade as a
documentation-only change. Until the target branch contains a trusted planner
(including the workflow's bootstrap PR), CI runs the full quality suite instead
of executing planner code from the pull-request checkout. Default-branch pushes
also bypass changed-file planning and always run the full build, unit-test,
type-check, Knip, and Trunk suite before CI/CD can report a successful
recovery.
The CI pipeline uses the Trunk GitHub Action to:
- Install Trunk CLI in the CI environment
- Run the same linting and formatting checks as local development
- Ensure consistent code quality across all environments
This repo utilizes Turborepo's Remote Caching, to speed up local development and CI/CD runs. It works by storing the outputs (build artifacts, logs) of tasks (like build, test, lint) in a shared remote cache on Vercel. Before running a task, Turborepo calculates a hash based on the input files, environment variables, and dependencies. If that hash exists in the remote cache, Turborepo downloads the stored output and logs instead of executing the task locally, saving a lot of time.
To connect your local machine to the remote cache:
-
Login to Vercel via Turbo CLI:
pnpm dlx turbo login
Follow the prompts to authenticate with your Vercel account.
-
Link the Repository:
pnpm dlx turbo link
This connects the local repository instance to your Vercel account/team's remote cache storage.
Once linked, turbo commands (like pnpm build, pnpm test) will automatically attempt to use the remote cache. You generally don't need to set TURBO_TOKEN or TURBO_TEAM locally after linking, as turbo stores the necessary credentials automatically.
The .github/workflows/ci.yml workflow is configured to automatically leverage remote caching:
- It uses the
TURBO_TOKEN(a Vercel Access Token) andTURBO_TEAM(your Vercel team slug/ID) environment variables. - These variables must be configured in the GitHub repository settings under Settings > Secrets and variables > Actions:
TURBO_TOKEN: As a Repository Secret.TURBO_TEAM: As a Repository Variable.
- With these variables set, the CI runner can authenticate with Vercel to read from and write to the remote cache.
Signed Remote Caching is enabled ("signature": true in turbo.json). Artifacts are HMAC-signed with TURBO_REMOTE_CACHE_SIGNATURE_KEY before upload, and signatures are verified on download. This prevents cache poisoning: an attacker who steals TURBO_TOKEN (read/write API access) but not the signing key cannot inject malicious build artifacts.
The signing key is provisioned in:
- GitHub Actions, as the
TURBO_REMOTE_CACHE_SIGNATURE_KEYRepository Secret. - Each Vercel project (
app.mento.org,governance.mento.org,reserve.mento.org,ui.mento.org) for production, preview, and development environments.
Local development: With signing enabled, both reads and writes require the key — a Turbo client without TURBO_REMOTE_CACHE_SIGNATURE_KEY cannot verify signatures on downloaded artifacts and treats every task as a remote-cache miss (it then runs the task locally and uses local cache normally). To get remote-cache hits locally, export the key in your shell with the same value used in CI:
export TURBO_REMOTE_CACHE_SIGNATURE_KEY="<value from a maintainer>"Never commit the key. If you don't set it, nothing breaks — you just lose the remote-cache speedup.
Rotating the key: generate a new value (openssl rand -hex 64), then update all 13 locations in lockstep (1 GitHub secret + 4 projects × 3 envs). After rotation, the cache is effectively wiped — first build per task repopulates it.
-
Add syncpack for consistent dependency versions across all monorepo packages(Now using PNPM catalog)
- Finetune builds. There's probably ways to make the builds of both packages and apps smaller and/or more performant.
- Make VS Code's "Go To Definition" on a component jump to the actual TypeScript source file instead of the compiled JS file in ./dist
- Enable additional Trunk linters for production CI (security scanning, image optimization)