This guide covers everything needed to clone, configure, run, and deploy the Orcfax On-Demand web application.
- Node.js 20+
- pnpm (enabled via corepack:
corepack enable && corepack prepare pnpm@latest --activate) - Git (with submodule support)
- Blockfrost account — sign up at blockfrost.io and create a project for Preview or Mainnet
- A CIP-30 Cardano wallet browser extension for using the app (Eternl, Nami, Lace, etc.)
The repository uses two git submodules under services/. You must initialize them when cloning:
git clone --recurse-submodules https://github.com/orcfax/on-demand.git
cd on-demandIf you already cloned without --recurse-submodules:
git submodule update --init --recursiveVerify the submodules are populated:
ls services/subbit-man-js/package.json # should exist
ls services/subbit-xyz/js/ # should exist-
services/subbit-man-js— a Fastify-based Node.js service that manages Subbit payment channels on the provider side. It handles L2 accounting (tracking IOUs, balances, costs) in LevelDB and L1 blockchain interactions (building transactions, syncing chain state, auto-settling channels) via Lucid Evolution. Runs on port 7822. -
services/subbit-xyz— the Subbit protocol implementation. Contains the Aiken smart contract source (aik/) and JavaScript packages (js/) including@subbit-tx/tx(validator contract bindings and transaction builders) and@subbit-tx/kio. The web app and SubbitMan both depend on@subbit-tx/txvia pnpm workspacelink:references.
pnpm installThis also runs a postinstall script that:
- Syncs SvelteKit types (
svelte-kit sync) - Fixes a pnpm strict isolation issue with
libsodium-wrappers-sumoby creating a symlink
Copy the example environment file and fill in the values:
cp .env.example .env| Variable | Example | Description |
|---|---|---|
PUBLIC_NODE_ENV |
development |
development, test, or production |
PUBLIC_BLOCKFROST_NETWORK |
Preview |
Preview or Mainnet — must match your Blockfrost API key |
PRIVATE_BLOCKFROST_API_KEY |
previewAbCdEf... |
Your Blockfrost project API key |
PUBLIC_MAINNET_ENABLED |
false |
Whether the Mainnet option appears in the network selector |
| Variable | Example | Description |
|---|---|---|
PRIVATE_ODAPI_VALIDATOR_URL |
https://validator.orcfax.io |
Orcfax validator node endpoint (the data source) |
PRIVATE_SUBBIT_MAN_URL |
http://localhost:7822 |
SubbitMan service URL. Default works for local pnpm dev |
| Variable | Example | Description |
|---|---|---|
PUBLIC_CHANNEL_INIT_COST |
1000 |
Channel initialization cost in lovelace (deducted from deposit by provider) |
Provider identity keys and the Subbit reference script UTxO are configured in SubbitMan's own environment (see the Provider Guide).
PUBLIC_*variables are exposed to the browser (available in client-side code)PRIVATE_*variables are server-only (available in*.remote.tsfiles,+server.tsroutes, and hooks)
pnpm devThis uses concurrently to start both:
- SvelteKit dev server — the web application (default:
http://localhost:5173) - SubbitMan — the Subbit channel manager backend (
http://localhost:7822)
Both services run side by side. The terminal labels output with [web] and [subbit] prefixes.
If SubbitMan is running separately (e.g. deployed remotely or in another terminal):
pnpm dev:only:webSet PRIVATE_SUBBIT_MAN_URL to point at the remote SubbitMan instance.
- Both services start without errors
- Open
http://localhost:5173in a browser - Navigate to
/appto see the main application - The app will prompt you to connect a wallet, generate keys, and open a channel
# One-off check
pnpm check
# Watch mode (re-checks on file changes)
pnpm check:watchThe project uses Prettier with tabs, single quotes, no trailing commas, and a 100-character print width. Plugins for Svelte and Tailwind class sorting are included.
# Format all files
pnpm prettier --write .
# Lint
pnpm eslint .pnpm buildThis produces a Node.js build in the build/ directory using @sveltejs/adapter-node.
To run the production build locally:
node buildThe server starts on port 3000 (configurable via the PORT environment variable).
The Dockerfile uses a multi-stage build:
- Build stage — installs dependencies and runs
pnpm buildwith a 4 GB heap (NODE_OPTIONS=--max-old-space-size=4096) to handle the Mesh SDK bundling - Run stage — copies only the production artifacts into a clean
node:20-alpineimage
# Build the image
docker build -t orcfax-express .
# Run (pass environment variables)
docker run -p 3000:3000 --env-file .env orcfax-expressThe container runs node build and listens on port 3000.
The build requires ~4 GB of memory due to the Mesh SDK's dependency tree (@cardano-sdk/*). The NODE_OPTIONS setting in the Dockerfile handles this. If building locally with pnpm build and hitting memory issues, set the same flag:
NODE_OPTIONS="--max-old-space-size=4096" pnpm buildThe project includes Dockerfiles for both services. Build and deploy using any Docker-compatible platform:
# Build and run the web app
docker build -t odapi-web .
docker run -p 3000:3000 --env-file .env odapi-web
# Build and run SubbitMan
docker build -t subbit-man -f services/subbit-man-js/Dockerfile services/subbit-man-js
docker run -p 7822:7822 --env-file .env.subbitman subbit-manSet the required environment variables (see Environment Variables) via your deployment platform's configuration.
To switch between Cardano networks:
- Update
PUBLIC_BLOCKFROST_NETWORKtoPrevieworMainnet - Use a
PRIVATE_BLOCKFROST_API_KEYthat matches the network - Update SubbitMan's
SUBBIT_MAN_SUBBIT_REFERENCE_UTXOto the reference script UTxO on that network - Update SubbitMan's provider keys (
PROVIDER_KEY_HASH,PROVIDER_SIGNING_KEY) if they differ per network - Set
PUBLIC_MAINNET_ENABLED=trueif you want users to see the Mainnet option in the UI
Users must also have their wallet browser extension set to the matching network — the app detects mismatches and shows a warning.
Error: services/subbit-man-js appears empty
Run git submodule update --init --recursive from the repo root.
The postinstall script handles this automatically. If you see errors about libsodium-sumo.mjs not being found, run:
node scripts/fix-libsodium.mjsThis creates a symlink to work around pnpm's strict package isolation.
The Mesh SDK dependency tree is large. Increase the Node.js heap:
NODE_OPTIONS="--max-old-space-size=4096" pnpm build- Make sure you have a CIP-30 compatible wallet extension installed (Eternl, Nami, Lace)
- Some wallets require a page refresh after installation
- Check that the wallet is set to the same network (Preview/Mainnet) as the app
If the web app can't reach SubbitMan:
- Verify SubbitMan is running (
pnpm devstarts it automatically) - Check
PRIVATE_SUBBIT_MAN_URLin.env(default:http://localhost:7822) - For deployed environments, make sure the SubbitMan URL is reachable from the web server
For a deeper understanding of the system, here's how the pieces fit together:
┌─────────────────────┐
│ Cardano (L1) │
│ Subbit smart contract│
│ (escrowed ADA) │
└──────────┬───────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌─────▼──────┐ ┌─────▼──────┐ ┌─────▼──────┐
│ Blockfrost │ │ SubbitMan │ │ Orcfax │
│ (indexer) │ │ (L2 state) │ │ Validator │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
└───────┬───────┴────────┬────────┘
│ │
┌──────▼────────────────▼──────┐
│ SvelteKit Server │
│ Remote functions + REST API │
└──────────────┬────────────────┘
│
┌──────────────▼────────────────┐
│ Browser (Svelte 5) │
│ Wallet + Keys + Channel + UI │
└────────────────────────────────┘
- Browser: Svelte 5 reactive UI. Manages wallet connection (Mesh SDK), Ed25519 keypairs, channel state, and price history. Stores keys in IndexedDB, preferences in localStorage.
- SvelteKit Server: Middleware layer using experimental remote functions. Validates credentials against SubbitMan, fetches data from the Orcfax validator, and charges costs. Also serves REST API endpoints for programmatic access.
- SubbitMan: Fastify service that tracks L2 channel accounting in LevelDB. Builds Cardano transactions via Lucid Evolution. Runs an automated liaison loop that syncs chain state, settles closed channels, and claims owed funds.
- Orcfax Validator: The upstream data source providing real-time price feeds and on-chain publication capabilities.
- Cardano / Blockfrost: The L1 blockchain where Subbit channels are opened, settled, and closed. Blockfrost provides chain indexing.
Subbit — an L2 payment channel protocol on Cardano. ADA is locked in a smart contract (escrow), and the consumer signs IOUs off-chain to authorize incremental payments without requiring a blockchain transaction per request.
IOU — a signed payment authorization. Each IOU specifies a cumulative total the provider may claim. IOUs are monotonically increasing — a new IOU for 500,000 lovelace replaces (not adds to) a previous IOU for 490,000 lovelace.
Stamp — a signed proof of channel ownership used for free informational queries (e.g., checking channel state). Contains a timestamp instead of a payment amount.
Channel lifecycle — Open (lock ADA) -> Use (sign IOUs per request) -> Close (start settlement window) -> Settle (provider claims authorized amount) -> End (consumer reclaims remainder). If the provider doesn't settle within the window, the consumer can Expire the channel and reclaim everything.
Remote functions — a SvelteKit experimental feature that lets client code call server-side functions directly, without manually defining API routes. Files named *.remote.ts contain these functions.
See examples/README.md for the full API reference and credential format documentation.