>_

Getting Started

Set up Open Privacy Suite and run it locally in minutes.

Prerequisites

  • Docker and Docker Compose v2+
  • Make
  • Git

Development mode

In development mode, the proxy uses mock ZK verification. No real Privado wallet needed — use the flask icon on the login page for instant mock authentication.

Evaluating the product?

make quickstart brings up a self-contained demo — two banks and a regulator on one chain, with seeded identities and ready-to-use tokens — and verifies the privacy story live. ONBOARDING.md in the repo root is the guided tour; the steps below bring up the plain development stack instead.

Installation

1

Clone the repository

git clone https://github.com/gateway-fm/open-privacy-suite.git
cd open-privacy-suite
2

Start the stack

make run

This starts PostgreSQL, Anvil (local Ethereum node), the proxy backend, and the admin frontend.

3

Open the admin UI

open http://localhost:5173

Click the flask icon below the QR code for instant mock login. Mock users are automatically granted the admin claim, so the admin dashboard is accessible immediately.

Services

ServicePortDescription
proxy-backend8080API server
proxy-frontend5173Admin UI
postgres5432Database
anvil8545Local Ethereum node

Full-stack dev (privacy mode — proxy + block-explorer + chain-indexer)

make run only brings up the proxy itself. For an end-to-end privacy-mode environment that includes the block-explorer (frontend + BFF), the chain-indexer, and a second postgres for the explorer, use make full-stack-dev:

make full-stack-dev

This calls scripts/privacy-dev-up.sh, which:

  1. Resolves the sibling-repo paths (BLOCK_EXPLORER_PATH, CHAIN_INDEXER_PATH) and verifies they exist.
  2. Generates .env.privacy.dev on first run (random secrets — POSTGRES passwords, JWT secrets, ADMIN_API_TOKEN). Delete the file to rotate (run down -v first to drop encrypted volumes).
  3. Auto-creates rpc-namespaces.json from the example so the EXTRA_RPC_NAMESPACES_FILE mount doesn't fail.
  4. Brings the stack up via docker-compose.privacy.dev.yml, then waits for the proxy-backend healthcheck.

Required layout (sibling clones)

/your/dev/dir/
  ├── open-privacy-suite/  ← you are here
  ├── block-explorer/      ← clone of gateway-fm/ops-explorer
  └── chain-indexer/       ← clone of gateway-fm/ops-indexer (optional;
                             the dev compose pulls the published image
                             by default — see Configuration → INDEXER_VERSION)

Override paths if your layout differs:

BLOCK_EXPLORER_PATH=/path/to/block-explorer \
CHAIN_INDEXER_PATH=/path/to/chain-indexer \
  make full-stack-dev

Services started

ServicePortSourceDescription
proxy-backend8080local buildOpen Privacy Suite API (mock-login + mock-signatures enabled)
proxy-frontend5173local buildOpen Privacy Suite admin UI
privacy-postgresimageOpen Privacy Suite database
anvil8545imageLocal Ethereum node
redisimagePer-user rate limit + session store
chain-indexer50051 (gRPC)published image, tag INDEXER_VERSION (default 0.3.0)Block indexer feeding the BFF
indexer-postgresimageChain-indexer database
block-explorer-apilocal build from BLOCK_EXPLORER_PATH/backendBlock-explorer BFF (privacy-mode build, chain-indexer client compiled out)
block-explorer-frontend3001local build from BLOCK_EXPLORER_PATH/frontendBlock-explorer UI (nginx)
block-explorer-postgresimageBlock-explorer database

URLs after start:

  • Open Privacy Suite backend → http://localhost:8080
  • Open Privacy Suite frontend → http://localhost:5173 (mock-login)
  • Block-explorer frontend → http://localhost:3001

Dev only — mock auth is on

This stack uses ALLOW_MOCK_LOGIN=true and MOCK_SIGNATURES=true. Both are runtime-gated AND require the mockauth build tag (-tags mockauth). The prod compose (docker-compose.privacy.yml) compiles them out entirely. Do not point the dev manifest at any customer-facing environment.

Stop with docker compose -f docker-compose.privacy.dev.yml down. Add -v to wipe all volumes.

Useful Commands

CommandDescription
make runStart all services
make stopStop all services
make restartRestart all services
make logsView live logs
make statusShow service status
make cleanStop and remove volumes

LAN Access

Every port binds to 127.0.0.1 by default, so nothing is reachable from other devices until you opt in. Set HOST_BIND to expose the app and API:

HOST_BIND=0.0.0.0 make run

Then open http://YOUR_LAN_IP:5173 on the other device. Find your IP with ipconfig getifaddr en0 (macOS) or hostname -I (Linux).

That is the only variable you need. In particular:

  • BASE_URL is not required. The browser sends its own origin with the login request, and the backend derives the callback URL from it.
  • CORS_ALLOWED_ORIGINS is not required. The frontend proxies /api to the backend server-side, so browser requests are same-origin.
  • FRONTEND_URL is not required. It is only used by the OAuth server flow.

Postgres and the local chain node stay on loopback regardless of HOST_BIND — only the app and API are exposed. HOST_BIND=0.0.0.0 puts an unauthenticated dev stack on your network; use it on a network you trust.

The RPC endpoint shown in the UI follows the address you opened the app at, so it is already correct on the LAN and can be copied straight into MetaMask. Set VITE_BACKEND_URL only for a split-origin deployment where the API is served from a different host than the dashboard.

Wallet login from a phone

The Privado ID wallet calls back to the API (port 8080), not the app. Which address it is told to call depends on the address you opened the app at, so there are two distinct setups — do not mix them.

Same wifi, plain http. Open the app at http://YOUR_LAN_IP:5173. The backend derives the callback from that origin and points the wallet at http://YOUR_LAN_IP:8080, which HOST_BIND=0.0.0.0 has already exposed. Nothing else to set:

HOST_BIND=0.0.0.0 make run

Wallet needs https — tunnel. Either port works, because the frontend proxies /auth/callback through to the API in every serving mode (dev server and both nginx images).

Tunnelling the app is the simpler of the two — open the tunnel URL in the browser and the backend derives the callback from that origin, so there is nothing else to set. Start the stack first; ngrok holds the terminal:

make run          # detached
ngrok http 5173   # then open the tunnel URL on the phone

Tunnelling the API works too, but the stack needs the tunnel address at startup, so it takes two terminals — and you must then open the app at http://localhost:5173 on the machine running the stack and scan the QR from the phone, because BASE_URL is only consulted for a localhost origin:

# terminal 1 — leave running, copy the https URL it prints
ngrok http 8080

# terminal 2
BASE_URL=https://YOUR-TUNNEL.ngrok-free.app make run

Either way the phone only needs to reach the tunnel, so HOST_BIND is not required for this path.

One tunnel is enough here because the wallet contacts exactly one address: the QR carries the whole authorization request inline, and the only URL inside it is the callback, which answers with JSON rather than a redirect.

That is specific to wallet login. Two other sign-in flows redirect the browser to a second host and need that host reachable in its own right:

  • Block explorer OAuth — on completion the browser is sent to the client's registered redirect URI.
  • Microsoft Entra ID — the browser leaves for Microsoft and returns to <origin>/auth/azure/callback, so that origin must be reachable and registered on the app registration.

Custom Ports

HOST_PORT_PROXY=8081 HOST_PORT_UI=5174 HOST_PORT_DB=5433 HOST_PORT_RPC=8546 make run

Next Steps