# ENVIRONMENT.md — Local Dev Stack

How to start, stop, and verify the harb development environment.

## Stack Overview

Docker Compose services (in startup order):

| Service | Purpose | Port | Health Check |
|---------|---------|------|-------------|
| **anvil** | Local Ethereum fork (Base Sepolia by default; override with `FORK_URL`) | 8545 | JSON-RPC response |
| **postgres** | Ponder database | 5432 | pg_isready |
| **bootstrap** | Deploys contracts to anvil | — | One-shot, exits 0 |
| **ponder** | On-chain indexer + GraphQL API | 42069 | HTTP /ready or GraphQL |
| **landing** | Landing page (Vue 3 + Vite) | 5174 | HTTP response |
| **webapp** | Staking app (Vue 3) | 5173 | HTTP response |
| **txn-bot** | Automated `recenter()` and `payTax()` upkeep ([services/txnBot/](../services/txnBot/)) | 43069 | Process alive |
| **caddy** | Reverse proxy / TLS | 80/443 | — |
| **umami** | Self-hosted analytics (Umami) | 3000 | HTTP /api/heartbeat |
| **otterscan** | Block explorer | 5100 | — |

## txnBot Service

`services/txnBot/` is the automation service responsible for keeping the protocol healthy:

- **`recenter()` monitoring** — polls Ponder GraphQL metrics and submits `recenter()` transactions to the LiquidityManager when price drift requires repositioning.
- **`payTax()` tracking** — monitors staking positions for overdue taxes and calls `payTax()` when it is profitable to do so.
- **Status endpoint** — exposes `GET /status` (port 43069) for operational health checks.

txnBot starts in the third phase of the dev stack (after ponder) alongside webapp and landing. See [services/txnBot/AGENTS.md](../services/txnBot/AGENTS.md) for configuration, safety checklist, and debugging guidance.

## Network Contexts

Two network contexts are relevant: the dev-stack Anvil (docker-compose) and the backtesting tools that require Base mainnet.

### Dev stack Anvil (docker-compose)

The `anvil` service in `docker-compose.yml` runs `containers/anvil-entrypoint.sh`, which forks:

```
${FORK_URL:-https://sepolia.base.org}
```

**Default: Base Sepolia.** The `bootstrap` service deploys all KRAIKEN protocol contracts (Kraiken, Stake, Optimizer, LiquidityManager) and creates a new KRK/WETH pool using the existing Uniswap V3 Factory already present on the forked network. Addresses are written to `tmp/containers/contracts.env`.

To fork Base mainnet instead (required for red-team / backtesting — see below):

```bash
FORK_URL=https://mainnet.base.org docker compose up -d
```

### Backtesting / red-team (`scripts/harb-evaluator/red-team.sh`)

`red-team.sh` boots the docker-compose stack and then calls protocol operations using **Base mainnet** addresses for the Uniswap V3 periphery (V3_FACTORY, SwapRouter02, NonfungiblePositionManager). These addresses are only valid on a mainnet fork.

`red-team.sh` calls `sudo docker compose up -d` internally. The script uses `sudo -E` so that `FORK_URL` is preserved across the sudo boundary:

```bash
FORK_URL=https://mainnet.base.org bash scripts/harb-evaluator/red-team.sh
```

### FitnessEvaluator (`onchain/test/FitnessEvaluator.t.sol`)

`FitnessEvaluator.t.sol` does **not** use Anvil. It uses Foundry's native revm backend (`vm.createSelectFork`) to fork Base mainnet in-process — no docker-compose dependency:

```bash
BASE_RPC_URL=https://mainnet.base.org \
FITNESS_MANIFEST_DIR=/tmp/manifest \
forge test --match-contract FitnessEvaluator --match-test testBatchEvaluate -vv
```

## Quick Start

```bash
cd /home/debian/harb

# Start everything
docker compose up -d

# Wait for bootstrap (deploys contracts, ~60-90s)
docker compose logs -f bootstrap

# Check all healthy
docker compose ps
```

## Verify Stack Health

```bash
# Anvil (local chain)
curl -s http://localhost:8545 -X POST -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' | jq .result

# Ponder (indexer + GraphQL)
curl -s http://localhost:42069/graphql -X POST \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ stats { id } }"}' | jq .

# Landing page
curl -sf http://localhost:5174 | head -5

# Staking app
curl -sf http://localhost:5173 | head -5
```

## Container Network

Services communicate on `harb-network` Docker bridge.
Internal hostnames match service names (e.g., `ponder:42069`).

Landing page container IP (for Playwright testing): check with
```bash
docker inspect landing --format '{{.NetworkSettings.Networks.harb_harb-network.IPAddress}}'
```

## Common URLs (for testing/review)

- **Landing:** `http://172.18.0.6:5174` (container IP) or `http://localhost:5174`
- **Staking app:** `http://localhost:5173/app/`
- **Ponder GraphQL:** `http://localhost:42069/graphql`
- **Anvil RPC:** `http://localhost:8545`
- **txnBot status:** `http://localhost:43069/status`
- **Umami analytics:** `http://localhost:3000` (default login: `admin` / `umami`)

## Resource Notes

- 8GB VPS — running full stack uses ~4-5GB RAM
- npm install inside containers can OOM with all services running
- Landing container takes ~2min to restart (npm install + vite startup)
- 4GB swap is essential for CI + stack concurrency

## Staking App Passwords

For testing login: `lobsterDao`, `test123`, `lobster-x010syqe?412!`
(defined in `web-app/src/views/LoginView.vue`)

## Webapp Environment Variables

| Variable | Default | Set in docker-compose | Purpose |
|---|---|---|---|
| `VITE_ENABLE_LOCAL_SWAP` | `false` (unset) | `true` | Show inline ETH→$KRK swap widget on Get KRK page instead of the Uniswap link. Enable for local dev; leave unset for production builds. |
| `VITE_KRAIKEN_ADDRESS` | from `deployments-local.json` | via `contracts.env` + entrypoint | Override KRK token address. |
| `VITE_STAKE_ADDRESS` | from `deployments-local.json` | via `contracts.env` + entrypoint | Override Stake contract address. |
| `VITE_DEFAULT_CHAIN_ID` | auto-detected (31337 on localhost) | — | Force the default chain. |
| `VITE_UMAMI_URL` | unset | via env | Full URL to Umami `script.js` (e.g. `https://analytics.kraiken.org/script.js`). Omit to disable analytics. |
| `VITE_UMAMI_WEBSITE_ID` | unset | via env | Umami website ID (UUID). Required alongside `VITE_UMAMI_URL`. |

## Analytics (Umami)

Self-hosted [Umami](https://umami.is/) provides privacy-respecting funnel analytics with no third-party tracking. The `umami` Docker service shares the `postgres` instance (separate `umami` database created by `containers/init-umami-db.sh`).

### Setup

1. Start the stack — Umami comes up automatically.
2. Open `http://localhost:3000` and log in (default: `admin` / `umami`). Change the password on first login.
3. Add a website in Umami and copy the **Website ID** (UUID).
4. Set the env vars before starting landing/webapp:
   ```bash
   export VITE_UMAMI_URL=http://localhost:3000/script.js
   export VITE_UMAMI_WEBSITE_ID=<your-website-id>
   ```
   For staging/production behind Caddy, use the `/analytics/script.js` path instead.

### Tracked funnel events

| Event | App | Trigger |
|-------|-----|---------|
| `cta_click` | landing | User clicks a CTA button (label in event data) |
| `wallet_connect` | web-app | Wallet connected for the first time |
| `swap_initiated` | web-app | User submits a buy or sell swap (direction in event data) |
| `stake_created` | web-app | Stake position successfully created |

Page views are tracked automatically by the Umami script on every route change.

### Production deployment

On `harb-staging`, set `VITE_UMAMI_URL` and `VITE_UMAMI_WEBSITE_ID` in the environment and configure `UMAMI_APP_SECRET` to a strong random value. The Caddy route `/analytics*` proxies to the Umami container.

## Contract Addresses

After bootstrap, addresses are written to `/home/debian/harb/tmp/containers/contracts.env` with the following variable names (no `VITE_` prefix):

```
LIQUIDITY_MANAGER=0x...
KRAIKEN=0x...
STAKE=0x...
```

The entrypoint scripts read this file and re-export the addresses with `VITE_` prefixes for Vite builds:
- `containers/landing-entrypoint.sh` exports `VITE_KRAIKEN_ADDRESS` and `VITE_STAKE_ADDRESS`
- `containers/webapp-entrypoint.sh` exports `VITE_KRAIKEN_ADDRESS` and `VITE_STAKE_ADDRESS`

## E2E Test Environment Variables

The Playwright test setup (`tests/setup/stack.ts`) reads stack coordinates from env vars, falling back to `onchain/deployments-local.json` when they are absent.

| Variable | Purpose |
|---|---|
| `STACK_RPC_URL` | RPC endpoint (default: `http://localhost:8081/api/rpc`) |
| `STACK_WEBAPP_URL` | Web app base URL (default: `http://localhost:8081`) |
| `STACK_GRAPHQL_URL` | GraphQL endpoint (default: `http://localhost:8081/api/graphql`) |
| `STACK_KRAIKEN_ADDRESS` | Kraiken contract address (overrides deployments-local.json) |
| `STACK_STAKE_ADDRESS` | Stake contract address (overrides deployments-local.json) |
| `STACK_LM_ADDRESS` | LiquidityManager contract address (overrides deployments-local.json) |
| `STACK_OPTIMIZER_PROXY_ADDRESS` | OptimizerProxy address (optional; enables optimizer integration tests) |

When all three of `STACK_KRAIKEN_ADDRESS`, `STACK_STAKE_ADDRESS`, and `STACK_LM_ADDRESS` are set, the deployments file is not read at all, which allows tests to run in containerised environments that have no local checkout.

## Playwright Testing

```bash
# Chromium path
/home/debian/.cache/ms-playwright/chromium-1209/chrome-linux64/chrome

# Run against landing (block fonts for speed)
NODE_PATH=$(npm root -g) node test-script.cjs
```

See `tmp/user-test-r4.cjs` for the most recent test script pattern.
