Bonded-escrow marketplace for prompt → response between buyers and
local-LLM suppliers, settling on Vector (UTxO) via Aiken Plutus V3
validators. Lifecycle PostEscrow → Claim → Submit → Accept proven on
Vector testnet 2026-05-06; AcceptEscrow tx 125e3cfe…7dc100d distributes
payment + bonds per the validator's distribution rule.
Workspace layout (pnpm + TypeScript):
contracts/marketplace/ Aiken validators (advert + escrow) + plutus.json
packages/shared/ Tx-builders (lucid-evolution), CBOR codecs,
chain providers (mock / read-only / live Ogmios)
supplier/ OpenAI-compatible HTTP node (Ollama by default;
vLLM / llama.cpp / OpenRouter / OpenClaw all
swap-in via OLLAMA_URL); 202+poll chat handler
buyer/ Express + React SPA; server-side runs the full
chain lifecycle via /v1/submit-prompt and
/v1/accept; SPA never holds the buyer key
indexer/ Chain-follower (extends apex-dashboard pattern),
SSE event stream + REST queries
indexer-ui/ Operator dashboard (events + suppliers + escrow
lookup)
tests/ 1098 vitest unit + integration tests (10 skipped
under M1-F-D-defer for UPLC test plumbing)
deploy/testnet/ Per-service docker-compose for the Hetzner stack
docs/EXECUTIVE_SUMMARY.* Marp deck (PDF + PPTX) — 11 slides, mermaid
diagrams pre-rendered for portable export
docs/CLOUDFLARE_WAF_BYPASS.md CF Skip-rule runbook for /v1/* paths
docs/ARCHITECTURE.md canonical spec
Live demo (testnet, 2026-05-06):
https://mp-buyers.vector.testnet.apexfusion.org (buyer SPA)
https://mp-suppliers.vector.testnet.apexfusion.org (supplier API)
https://mp-indexer.vector.testnet.apexfusion.org (indexer + UI)
87 lines
4.0 KiB
Docker
87 lines
4.0 KiB
Docker
# syntax=docker/dockerfile:1.7
|
|
#
|
|
# indexer/Dockerfile — multi-stage image for the chain-follower indexer.
|
|
#
|
|
# Notes:
|
|
# - better-sqlite3 has native components and needs python3 + make + g++ to
|
|
# build during `pnpm install`. We install them in the builder stage only
|
|
# and ship the prebuilt binding to the runtime image (no toolchain in
|
|
# prod).
|
|
# - We tried node:20-alpine first; better-sqlite3 11.x ships prebuilds for
|
|
# musl on linux/x64 so it works without a fallback. If a future version
|
|
# drops musl prebuilds, switch BUILDER_IMAGE to node:20-bookworm-slim and
|
|
# replace `apk add` with `apt-get install -y python3 make g++`.
|
|
# - Same tsx-based runtime as supplier (see supplier/Dockerfile for the
|
|
# rationale: shared package exports .ts directly).
|
|
#
|
|
# Build:
|
|
# docker build -f indexer/Dockerfile -t marketplace-indexer .
|
|
|
|
ARG NODE_VERSION=20.19.0
|
|
|
|
# ─── builder ──────────────────────────────────────────────────────────────
|
|
FROM node:${NODE_VERSION}-alpine AS builder
|
|
WORKDIR /repo
|
|
|
|
# Toolchain for native modules (better-sqlite3). These do NOT land in runtime.
|
|
RUN apk add --no-cache python3 make g++
|
|
|
|
RUN corepack enable && corepack prepare pnpm@10.33.2 --activate
|
|
|
|
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json tsconfig.base.json .npmrc ./
|
|
COPY packages/shared/package.json ./packages/shared/package.json
|
|
COPY indexer/package.json ./indexer/package.json
|
|
COPY indexer-ui/package.json ./indexer-ui/package.json
|
|
COPY buyer/package.json ./buyer/package.json
|
|
COPY supplier/package.json ./supplier/package.json
|
|
|
|
# Install both the indexer (chain follower / API) AND the indexer-ui (Vite/React
|
|
# bundle) so the multi-stage build can produce a self-hosted dashboard image.
|
|
RUN pnpm install --frozen-lockfile --filter '@marketplace/indexer...' --filter '@marketplace/indexer-ui...'
|
|
|
|
# better-sqlite3 has native bindings. pnpm 10's "approve-builds" gate skips
|
|
# postinstall on native modules by default. Explicitly fetch the prebuilt
|
|
# binary into build/Release/. Match Catherine's M1-D-green diagnosis (the
|
|
# .node file lands at .pnpm/better-sqlite3@*/.../build/Release/).
|
|
RUN BSQ_DIR=$(ls -d node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3 2>/dev/null | head -1) \
|
|
&& [ -n "$BSQ_DIR" ] || (echo "FATAL: better-sqlite3 not in pnpm store" && exit 1) \
|
|
&& echo "rebuilding native binding at: $BSQ_DIR" \
|
|
&& (cd "$BSQ_DIR" && (npx --yes prebuild-install --runtime=node || npm install --build-from-source --no-save)) \
|
|
&& ls -la "$BSQ_DIR/build/Release/better_sqlite3.node"
|
|
|
|
COPY packages/shared ./packages/shared
|
|
COPY indexer ./indexer
|
|
COPY indexer-ui ./indexer-ui
|
|
|
|
# Build the indexer-ui dashboard. Output lands at indexer-ui/dist/ which the
|
|
# runtime image will serve via express.static when INDEXER_UI_DIST is set.
|
|
RUN pnpm --filter @marketplace/indexer-ui build
|
|
|
|
# ─── runtime ──────────────────────────────────────────────────────────────
|
|
FROM node:${NODE_VERSION}-alpine AS runtime
|
|
WORKDIR /repo
|
|
|
|
RUN apk add --no-cache tini
|
|
|
|
COPY --from=builder /repo /repo
|
|
|
|
# SQLite data dir — mounted as a named volume in compose.
|
|
RUN mkdir -p /repo/indexer/data && chown -R node:node /repo
|
|
USER node
|
|
|
|
ENV NODE_ENV=production
|
|
ENV INDEXER_PORT=8090
|
|
ENV DB_PATH=/repo/indexer/data/indexer.db
|
|
# Auto-mount the bundled dashboard in the runtime image. Override with empty
|
|
# string ("") to run API-only.
|
|
ENV INDEXER_UI_DIST=/repo/indexer-ui/dist
|
|
EXPOSE 8090
|
|
|
|
# Indexer health endpoint is /healthz (standardized 2026-04-27 across all
|
|
# services — see indexer/src/routes/healthz.ts and ARCHITECTURE.md §5).
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
CMD wget -qO- http://127.0.0.1:${INDEXER_PORT}/healthz >/dev/null || exit 1
|
|
|
|
ENTRYPOINT ["/sbin/tini", "--"]
|
|
CMD ["node_modules/.bin/tsx", "indexer/src/index.ts"]
|