Architecture

This page is for developers who want to understand how Plumb is built under the hood. If you're just trying to use Plumb, Quickstart is a better starting point.

In one paragraph

Plumb is made up of a handful of small, focused programs that talk to each other: a web API (the "gateway") that answers your AI requests, a background worker that handles timed jobs like blockchain settlement, a few small websites (console, explorer, marketing), and a set of smart contracts living on the Base blockchain. Everything runs on a single Linux server — no Docker, no Kubernetes, no cloud platform — and the code for all of it lives in one repository so you can read any part of the system without hunting across services.

The nine workspaces

Plumb is a pnpm monorepo. Every app or package is a workspace, each with its own package.json, tests, and typecheck target. There is no global build — pnpm -r build runs in topological order and nothing is hidden.

apps/
  gateway       Hono API on port 3000/3010 -- the main HTTP surface
  worker        Long-lived Node process -- watchers + batcher + pipe fulfiller + memsync
  console       Next.js 15 operator UI on port 3002/3012
  explorer      Next.js 15 public read-only UI on port 3003/3013, reads a dedicated pg role
  web           Next.js 15 marketing site on port 3001, deployed to Vercel
  sdk-python    Python package (httpx wrapper + ed25519 verify + canonical + merkle)

packages/
  auth          SIWE nonce issuance + consume, session token create/verify
  billing       Atomic debit + credit + ledger entries, cost calculation
  contracts     Foundry project -- Solidity + forge tests + deploy scripts + ABI exports
  db            Drizzle schema + migrations (0000..0005)
  hub-storage   StorageBackend interface (fs + optional s3), ONNX session cache
  memsync       Memory extraction prompt + pgvector writer + profile composer
  onchain       Event watcher + Merkle batcher + settlement wallet + reorg check + tx queue
  pipe          JobWatcher (pipe oracle events) + fulfill pipeline
  pricing       Per-model rate catalog + cost calculator
  providers     OpenAI, Anthropic, Google, xAI, mock adapters
  ui            Caliper design tokens + React primitives (Plumb, TickLabel, etc.)
  verification  ed25519 signer + key registry + canonical payload + merkle helpers

Request flow — chat completion

 ┌───────────┐   POST /v1/chat/completions           ┌────────────────────┐
 │  caller   │ ────────────────────────────────────▶│   apps/gateway      │
 │  (SDK)    │                                       │                    │
 └───────────┘                                       │  session middleware│
                                                    │  rate limit        │
                                                    │  providers router  │
                                                    └──────────┬─────────┘
                                                               │
                                          ┌────────────────────┼────────────────────┐
                                          │                    │                    │
                                          ▼                    ▼                    ▼
                                 upstream provider      BEGIN TX                 Redis
                                 (OpenAI / Anthropic)   ──────────               (BullMQ)
                                          │             receipts.insert              │
                                          ▼             ledger.debit                 ▼
                                   response bytes       ──────────               memsync job
                                          │             COMMIT                  (if opted in)
                                          ▼             ─────────
                                   canonical hash       X-Plumb-Receipt
                                          │             back to caller
                                          ▼
                                   ed25519.sign
                                          │
                                          ▼
                                   JSON response + header

Request flow — receipt → settlement

 ┌──────────────┐   every 2 min OR                 ┌───────────────────┐
 │ apps/worker  │ ─ unsettled > 1000 ────────────▶│ checkpoint batcher │
 │              │                                  │                   │
 └──────────────┘                                  │  SELECT unsettled │
                                                   │  build Merkle     │
                                                   │  sign root + meta │
                                                   └─────────┬─────────┘
                                                             │ runExclusive(signer)
                                                             ▼
                                                   Settlement.checkpointReceipts
                                                             │
                                                             ▼
                                                       Base Sepolia
                                                             │
                                                             ▼
                                                   UPDATE receipts SET
                                                     settlement_tx_hash = ...

Request flow — pipe oracle

 ┌──────────────┐   1. oracle.request(modelHash, inputHash, callback)
 │ consumer     │ ──────────────────────────────▶ PipeOracle on Base
 │ contract     │                                       │
 └──────────────┘                                       │ emits JobRequested
                                                        ▼
                                            ┌────────────────────┐
                                            │  pipe-fulfiller    │
                                            │  loop (worker)     │
                                            └─────────┬──────────┘
                                                      │ 2. fetch inputBytes via hash
                                                      │ 3. onnxruntime.run (30s timeout)
                                                      │ 4. sign EIP-191 over result
                                                      │ 5. runExclusive(signer)
                                                      ▼
                                            PipeOracle.fulfill(id, result, sig)
                                                      │
                                                      │ verify signer, mark fulfilled
                                                      │ try { consumer.pipeCallback{gas: 100_000} }
                                                      ▼
                                                consumer state updated

Data model

  • receipts — every signed completion. Indexed by (addr, created_at) + settled_onchain_at.
  • ledger_entries — debits, credits, refunds. Per-addr balance is derived via SUM(credit) - SUM(debit) - SUM(refund).
  • hub_models(hash, uploader_addr, framework, size_bytes, storage_url, registered_onchain_at). Unique on hash.
  • pipe_jobs(id, requester_addr, oracle_call_tx, model_hash, input_hash, status, result_hash, receipt_id).
  • memories(id, addr, kind, content, embedding vector(1536), confidence, created_at). HNSW index on embedding.
  • onchain_cursors — one row per watcher. Tracks last_block_scanned + last_block_hash (reorg detection).
  • sessions — SIWE-issued tokens, sha256-hashed at rest. TTL 30 days.

Deployment surface

Prod runs on a single Hostinger VPS:

  • Caddy terminates TLS for api.plumbtech.xyz, console.plumbtech.xyz, explorer.plumbtech.xyz. Security headers (HSTS, X-Frame-Options DENY, CSP) are set at the reverse proxy.
  • PM2 runs four Node services: plumb-gateway, plumb-worker, plumb-console, plumb-explorer. Restart on crash, persist via pm2 save.
  • Postgres 16 + pgvector 0.6, one database (plumb), two roles (plumb for app, plumb_explorer_ro for read-only explorer).
  • Redis 7 for BullMQ queues (plumb-checkpoints, plumb-memsync) and rate-limit sliding windows.
  • Marketing site (apps/web) auto-deploys to Vercel on push to main. Everything else is git pull && pm2 restart.

Contracts (Base Sepolia)

All verified on Blockscout. See /docs/contracts for live addresses.

  • PLMB — ERC-20 credit token with claim faucet (dev) and OZ ERC20Permit.
  • Settlement — receives PLMB deposits, checkpoints receipt Merkle roots, enforces signer rotation via Ownable.
  • HubRegistryregister(hash, uploader, metadataURI, prevVersion). Owner-gated (Ownable). Append-only version chain.
  • PipeOracle — Ownable2Step. request / fulfill with signer cosigning + 100k callback stipend + CallbackFailed event on consumer revert.
  • PipeConsumer — example IPipeCallback implementer for e2e tests.