Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 144 additions & 35 deletions docs/ONBOARDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ Install the following before cloning:
|------|---------|---------|
| Node.js | v22+ | [nodejs.org](https://nodejs.org) or `nvm install 22` |
| pnpm | v10+ | `npm install -g pnpm@10` |
| Rust + Cargo | stable | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh` |
| Rust + Cargo | stable (1.78+) | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh` |
| wasm32 target | — | `rustup target add wasm32-unknown-unknown` |
| Stellar CLI | latest | `cargo install --locked stellar-cli --features opt` |
| Docker + Compose | v24+ | [docs.docker.com](https://docs.docker.com/get-docker/) *(optional, recommended)* |

Verify:

Expand All @@ -27,6 +28,44 @@ stellar --version # stellar x.x.x

---

## Architecture overview

```
Smart Meter (Ed25519 keypair)
│ POST /api/readings { kwh, timestamp, signature_hex }
│ Header: Idempotency-Key: <uuid>
SolarProof API (Next.js 15 — apps/web)
│ 1. Check Idempotency-Key in Redis → return cached response if hit
│ 2. Verify Ed25519 signature against meter public key
│ 3. Anchor reading hash → audit_registry contract (Stellar)
│ 4. Mint energy_token (1 token = 1 kWh) to cooperative admin
│ 5. Store certificate in Supabase
Stellar Testnet (Soroban — apps/contracts)
├── energy_token SEP-41 certificate token (minter-gated)
├── audit_registry immutable signed-reading anchors
└── community_governance cooperative proposals + voting
Public Verifier (https://solarproof.vercel.app/verify)
Input: certificate ID or tx hash
Output: meter reading → Ed25519 proof → ledger anchor → certificate
```

### Key data flows

| Flow | Entry point | Key files |
|------|-------------|-----------|
| Meter reading submission | `POST /api/readings` | `src/app/api/readings/route.ts` |
| Signature verification | `src/lib/crypto.ts` | `@noble/ed25519` |
| Stellar interactions | `src/lib/stellar.ts` | `@stellar/stellar-sdk` |
| Idempotency | `src/lib/idempotency.ts` | Upstash Redis |
| Certificate cache | `src/lib/cache.ts` | Upstash Redis |
| Structured logging | `src/lib/logger.ts` | Logtail / stdout |
| APM traces | `src/instrumentation.ts` | OpenTelemetry OTLP |

---

## 1. Clone and install

```bash
Expand All @@ -46,14 +85,26 @@ cp apps/web/.env.example apps/web/.env.local
Edit `apps/web/.env.local` and fill in:

```env
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

# Stellar
NEXT_PUBLIC_STELLAR_NETWORK=testnet
NEXT_PUBLIC_ENERGY_TOKEN_ID= # from contract deployment
NEXT_PUBLIC_AUDIT_REGISTRY_ID= # from contract deployment
NEXT_PUBLIC_COMMUNITY_GOVERNANCE_ID= # from contract deployment
MINTER_SECRET_KEY= # from contract deployment
MINTER_SECRET_KEY= # Stellar secret key for the minter account

# Redis (Upstash) — optional for local dev, required for idempotency + caching
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token

# Observability — optional
LOGTAIL_SOURCE_TOKEN= # Better Stack structured logs
OTEL_EXPORTER_OTLP_ENDPOINT= # OpenTelemetry collector URL
OTEL_EXPORTER_OTLP_HEADERS= # e.g. Authorization=Basic <base64>
```

For contract IDs, see [Deploying contracts locally](#4-deploying-contracts-locally) below, or ask a teammate for the shared testnet IDs.
Expand All @@ -62,17 +113,29 @@ For contract IDs, see [Deploying contracts locally](#4-deploying-contracts-local

## 3. Run the web app

### Option A — Docker (recommended)

Spins up Next.js, Supabase, and Redis together:

```bash
docker compose up
```

App available at http://localhost:3000. Data persists in named volumes across restarts.

### Option B — Local dev server

```bash
pnpm dev
```

Opens at [http://localhost:3000](http://localhost:3000).
Opens at http://localhost:3000. Requires Supabase and Redis to be running separately (or set env vars to cloud instances).

---

## 4. Deploying contracts locally

Build all three contracts:
Build all contracts:

```bash
cd apps/contracts
Expand All @@ -98,10 +161,6 @@ REGISTRY_ID=$(stellar contract deploy \
GOV_ID=$(stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/community_governance.wasm \
--source deployer --network testnet)

echo "TOKEN_ID=$TOKEN_ID"
echo "REGISTRY_ID=$REGISTRY_ID"
echo "GOV_ID=$GOV_ID"
```

Initialize each contract:
Expand All @@ -123,31 +182,58 @@ Copy the IDs into `apps/web/.env.local`.

## 5. Running tests

### Web (lint + type-check + build)
### Web — unit tests

```bash
# From repo root (Turborepo runs all packages)
pnpm test

# From apps/web only
cd apps/web
pnpm test # vitest run (single pass)
pnpm test:watch # vitest watch mode
```

### Web — lint and type-check

```bash
pnpm lint # ESLint
pnpm type-check # TypeScript
pnpm build # Next.js production build
pnpm lint # ESLint
pnpm type-check # TypeScript (tsc --noEmit)
pnpm build # Next.js production build
```

Run from `apps/web/` or from the repo root (Turborepo runs all).
### Web — end-to-end tests

```bash
cd apps/web
pnpm e2e # Playwright (requires running dev server)
```

### Contracts (fmt + clippy + tests)
### Contracts — unit tests

```bash
cd apps/contracts
cargo fmt --all -- --check # formatting
cargo clippy --all-targets # lints
cargo test --all # unit tests
cargo test --all # unit tests (all contracts)

# Single contract
cargo test --package energy-token
cargo test --package audit-registry
cargo test --package community-governance
```

### Shared packages

```bash
cd packages/stellar
pnpm test
```

---

## 6. Simulate a meter reading end-to-end

This sends a signed reading through the full stack: script → API → Stellar.

**Step 1 — Generate a meter keypair:**

```bash
Expand All @@ -157,13 +243,12 @@ node scripts/gen-meter-key.mjs

**Step 2 — Register the meter in Supabase:**

Insert a row into the `meters` table with the `public_key_hex` from step 1. You can do this via the Supabase dashboard or SQL:

```sql
INSERT INTO meters (id, public_key_hex, name) VALUES (gen_random_uuid(), '<public_key_hex>', 'dev-meter');
INSERT INTO meters (id, public_key_hex, name, cooperative_id, active)
VALUES (gen_random_uuid(), '<public_key_hex>', 'dev-meter', '<cooperative_id>', true);
```

**Step 3 — Start the dev server** (if not already running):
**Step 3 — Start the dev server:**

```bash
pnpm dev
Expand All @@ -189,45 +274,69 @@ Reading hash: <hex>

**Step 5 — Verify on-chain:**

Open [http://localhost:3000/verify](http://localhost:3000/verify) and enter the reading hash or token transaction ID.
Open http://localhost:3000/verify and enter the reading hash or token transaction ID.

---

## 7. Troubleshooting

**`pnpm install` fails with peer dependency errors**
→ Ensure Node.js v22+ is active: `node --version`
→ Ensure Node.js v22+ is active: `node --version`. Switch with `nvm use 22`.

**`stellar contract build` fails with `wasm32-unknown-unknown` not found**
→ Run: `rustup target add wasm32-unknown-unknown`

**`stellar keys fund` returns an error**
→ Testnet friendbot may be rate-limited. Try: `curl "https://friendbot.stellar.org?addr=$(stellar keys address deployer)"`
→ Testnet friendbot may be rate-limited. Try directly:
```bash
curl "https://friendbot.stellar.org?addr=$(stellar keys address deployer)"
```

**API returns `invalid signature`**
**API returns `Invalid meter signature`**
→ The `meter_id` passed to `send-reading.mjs` must exactly match the UUID registered in Supabase. UUIDs are case-sensitive.

**API returns `Meter not found or inactive`**
→ Ensure the meter row has `active = true` and the `cooperative_id` references a valid cooperative with an `admin_address`.

**`cargo test` fails with `ed25519` import errors**
→ Ensure you're using the `soroban-sdk` version pinned in `apps/contracts/Cargo.toml`. Run `cargo update` only if explicitly needed.

**Next.js build fails with missing env vars**
→ All `NEXT_PUBLIC_*` vars must be set at build time. Check `apps/web/.env.local` exists and is populated.
→ All `NEXT_PUBLIC_*` vars must be set at build time. Check `apps/web/.env.local` exists and is populated. See `apps/web/src/env.ts` for the full list of required vars.

**Idempotency-Key not working (duplicate requests re-process)**
→ `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` must be set. Without Redis, idempotency is a no-op (by design, for local dev).

**OpenTelemetry traces not appearing**
→ Set `OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_EXPORTER_OTLP_HEADERS`. Verify the collector is reachable. Check stdout for SDK startup errors.

**Docker compose fails to start Supabase**
→ Ensure ports 5432 and 54321 are free: `lsof -i :5432`. Stop any local Postgres instances first.

---

## Project structure recap
## Project structure

```
solarproof/
├── apps/
│ ├── contracts/ # Soroban smart contracts (Rust)
│ │ ├── energy_token/
│ │ ├── audit_registry/
│ │ └── community_governance/
│ └── web/ # Next.js app + API routes
├── packages/stellar/ # Shared Stellar client utilities
├── scripts/ # Meter simulation scripts
└── docs/ # Deployment guide, ADRs, database schema
│ ├── contracts/ # Soroban smart contracts (Rust)
│ │ ├── energy_token/ # SEP-41 certificate token (minter-gated)
│ │ ├── audit_registry/ # Immutable reading anchors
│ │ └── community_governance/ # Cooperative proposals + voting
│ └── web/ # Next.js 15 app + API routes
│ ├── src/
│ │ ├── app/ # Next.js App Router pages + API routes
│ │ │ └── api/ # REST API (readings, certificates, meters…)
│ │ ├── lib/ # Shared utilities (stellar, crypto, cache…)
│ │ ├── components/ # React UI components
│ │ └── instrumentation.ts # OpenTelemetry hook
│ └── e2e/ # Playwright end-to-end tests
├── packages/
│ └── stellar/ # Shared Stellar client utilities
├── scripts/ # Meter simulation scripts
├── docs/ # ADRs, deployment guide, API docs
└── supabase/ # Migrations and seed data
```

See [CONTRIBUTING.md](../CONTRIBUTING.md) for commit conventions and PR workflow.
Loading