Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2025bf6
feat: vibenet stack — faucet, explorer, landing page (Next.js port)
chunter-cb May 14, 2026
fdf1529
fix: indexer wipeAll — use db.exec() for multi-statement reset
chunter-cb May 14, 2026
ea17917
fix: Dockerfile native deps, .dockerignore, proxy subdomain routing
chunter-cb May 14, 2026
fc39eb0
fix: drop standalone output, copy full node_modules for indexer
chunter-cb May 14, 2026
f392def
feat: NFV faucet button, explorer search, landing page bug fixes
chunter-cb May 15, 2026
a195c40
fix: correct ERC-20/721 Transfer topic constant in indexer
chunter-cb May 15, 2026
5aadff5
style: port the original vibenet visual design (Base Blue, dark theme)
chunter-cb May 15, 2026
332e50b
style(faucet): rewrite to match original 2-pill design
chunter-cb May 15, 2026
0260b26
style(explorer): balance the two lists, drop # from block numbers,
chunter-cb May 15, 2026
21896a1
style(explorer): align with original tx + address detail templates
chunter-cb May 15, 2026
66f8c85
feat(explorer): tx Type indicator, full input/log data, live-row anim…
chunter-cb May 15, 2026
52f8b15
style(explorer): shared header with search bar on every page
chunter-cb May 15, 2026
432c0e9
docs: add README explaining how to run base/ui locally
chunter-cb May 15, 2026
1149b76
fix(ci): regenerate bun.lock, fix lint failures
chunter-cb May 15, 2026
4bdc0c4
ci: switch from bun to npm for install + scripts
chunter-cb May 15, 2026
0a6c61f
ci: revert to bun (with the now-current bun.lock)
chunter-cb May 15, 2026
ff2351d
ci: cache bun install dir between runs
chunter-cb May 15, 2026
347bf6d
ci: run jobs inside oven/bun:1.3.14 container
chunter-cb May 17, 2026
fd60c41
ci: revert workflow to original team config
chunter-cb May 17, 2026
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
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.git
.next
node_modules
npm-debug.log*
.env*.local
/tmp
*.db
30 changes: 19 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
FROM oven/bun:1 AS deps
FROM node:20-alpine AS deps
WORKDIR /app
COPY ui/package.json ui/bun.lock ./
RUN bun install --frozen-lockfile
# better-sqlite3 requires native compilation
RUN apk add --no-cache python3 make g++
COPY package.json package-lock.json ./
RUN npm ci

FROM oven/bun:1 AS builder
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY ./ui .
COPY . .

ENV NEXT_TELEMETRY_DISABLED=1

RUN bun run build
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
Expand All @@ -21,12 +23,18 @@ ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

RUN mkdir .next
RUN chown nextjs:nodejs .next
RUN mkdir -p .next /data
RUN chown nextjs:nodejs .next /data

# Full node_modules (not standalone) so indexer.mjs can resolve viem + better-sqlite3
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/scripts ./scripts
COPY --from=builder /app/docker/migrations ./docker/migrations

RUN chown -R nextjs:nodejs node_modules && chmod +x scripts/start.sh

USER nextjs

Expand All @@ -35,4 +43,4 @@ EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]
CMD ["sh", "scripts/start.sh"]
179 changes: 179 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# base/ui

Next.js app that powers the vibenet UI surface plus the internal TIPS
block-metering tool.

When deployed against the [vibenet](https://github.com/base/base/tree/main/etc/vibenet)
devnet stack, a single instance of this app serves three subdomains via
host-based routing:

| Subdomain | Pages served |
| -------------------------- | ------------------------------------------------ |
| `vibes.base.org` | Landing page (chain ID, contracts, "Add to wallet") |
| `faucet.vibes.base.org` | Faucet UI + drip API (ETH / USDV / NFV) |
| `explorer.vibes.base.org` | Block explorer (vibescan) |

Subdomain routing happens in [`src/proxy.ts`](./src/proxy.ts), which reads
the `Host` header and rewrites internally to the matching `/faucet` or
`/explorer/*` path. The TIPS tool (separate product) is served at `/tips`.

---

## Running locally

The recommended path is the full devnet stack via the sibling `base/base`
repo. That brings up L1 + L2 + proxyd + the contract deployer along with
this Next.js app, all wired together by `docker compose`.

```bash
# Clone both repos as siblings
git clone git@github.com:base/base.git ../base
git clone git@github.com:base/ui.git # this repo

# One-time: copy the vibenet env template (defaults work for local dev)
cp ../base/etc/vibenet/vibenet-env.example ../base/etc/vibenet/vibenet-env

# Bring up the full stack
just -f ../base/etc/docker/Justfile vibe
```

Once it's up:

| URL | Service |
| ---------------------------------- | ------------------------------------------ |
| `http://localhost:18080/` | Landing page |
| `http://localhost:18080/faucet` | Faucet UI + drip API |
| `http://localhost:18080/explorer` | Block explorer |
| `http://localhost:18080/tips` | TIPS metering tool |
| `http://localhost:18080/api/*` | Faucet, explorer, config, contracts APIs |
| `ws://localhost:18081/` | WebSocket RPC (base-client direct) |
| `http://localhost:18082/` | HTTP RPC (proxyd) |

To rebuild only this app without resetting the chain (block history,
deployed contracts, and the explorer index all survive):

```bash
just -f ../base/etc/docker/Justfile vibe-ui
```

To use a pre-built UI image instead of building from source:

```bash
VIBENET_UI_IMAGE=ghcr.io/base/ui:main just -f ../base/etc/docker/Justfile vibe
```

---

## UI-only iteration (no Docker)

When you're working on the React side and a devnet is already running
(either via `just vibe` above or some other source of an L2 RPC), you
can skip the container rebuild loop by running Next.js on your host:

```bash
# Point at the running devnet's RPC
cat > .env.local <<EOF
VIBESCAN_DB_PATH=/tmp/vibescan-dev.db
VIBESCAN_RPC_HTTP_URL=http://localhost:8545
VIBESCAN_RPC_WS_URL=ws://localhost:8546
VIBESCAN_CHAIN_ID=84538453
VIBENET_FAUCET_RPC_URL=http://localhost:8545
VIBENET_FAUCET_CHAIN_ID=84538453
VIBENET_FAUCET_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
VIBENET_FAUCET_IP_COOLDOWN_SECS=10
VIBENET_FAUCET_ADDR_COOLDOWN_SECS=10
EOF

# Start the explorer indexer (background process)
node scripts/indexer.mjs &

# Start Next.js with hot reload
npm run dev # http://localhost:3000
```

The faucet here uses anvil account #1 (10 K ETH on L2 in the devnet
genesis) since account #0 is the deployer and gets drained.

---

## Testing subdomain routing locally

The `proxy.ts` rewrites kick in when the `Host` header matches
`*.vibes.base.org`. Two ways to test:

```bash
# 1. Spoof the Host header
curl -H 'Host: faucet.vibes.base.org' http://localhost:18080/

# 2. Add /etc/hosts entries (for browser testing)
echo "127.0.0.1 vibes.base.org faucet.vibes.base.org explorer.vibes.base.org" \
| sudo tee -a /etc/hosts
# Then open http://vibes.base.org:18080
```

In production, Cloudflare → Caddy → next-app preserves the `Host`
header end-to-end. The Caddyfile lives in
[`base/base/etc/vibenet/caddy/Caddyfile`](https://github.com/base/base/blob/main/etc/vibenet/caddy/Caddyfile).

---

## Architecture

```
src/
proxy.ts subdomain rewrites + CORS
app/
layout.tsx root html/body shell
(vibenet)/ route group (does not affect URLs)
layout.tsx .vibenet-app wrapper, imports vibenet.css
page.tsx vibes.base.org landing
faucet/page.tsx faucet.vibes.base.org
explorer/
layout.tsx shared header (brand + search + nav)
_header.tsx client component for the search bar
page.tsx explorer.vibes.base.org home
block/[hash]/page.tsx
tx/[hash]/page.tsx
address/[addr]/page.tsx
api/
vibenet/ Next.js API routes
config/ reads /config/config.json (rendered from vibenet.yaml)
contracts/ reads /shared/contracts.json (written by vibenet-setup)
faucet/{status,drip,drip-usdv,drip-nfv}/
explorer/{stats,blocks,block,tx,address}/
# TIPS APIs (existing)
blocks/ block/ bundle/ health/ rejected/ txn/
tips/ TIPS metering tool pages

lib/vibenet/
db.ts better-sqlite3 singleton + schema
faucet.ts rate limiter, viem wallet client, contracts.json reader

styles/
vibenet.css scoped to .vibenet-app, doesn't bleed into /tips

scripts/
indexer.mjs background block indexer (newHeads via WS + backfill)
start.sh container entrypoint (indexer + npm start)

docker/
migrations/0001_init.sql vibescan SQLite schema
vibenet-env.example UI-specific env vars
```

---

## Environment variables

The full list and defaults are in [`docker/vibenet-env.example`](./docker/vibenet-env.example).
The required ones at runtime:

- **Faucet**: `VIBENET_FAUCET_PRIVATE_KEY`, `VIBENET_FAUCET_RPC_URL`,
`VIBENET_FAUCET_CHAIN_ID`
- **Explorer indexer**: `VIBESCAN_RPC_HTTP_URL`, `VIBESCAN_RPC_WS_URL`,
`VIBESCAN_CHAIN_ID`, `VIBESCAN_DB_PATH`
- **Optional volume mounts**: `VIBENET_CONFIG_PATH`,
`VIBENET_CONTRACTS_PATH` (the landing page reads these to render
features list and deployed contracts; defaults to
`/config/config.json` and `/shared/contracts.json` which are mounted
from sibling containers in the docker-compose)
Loading
Loading