Skip to content

Latest commit

 

History

History
341 lines (251 loc) · 19.1 KB

File metadata and controls

341 lines (251 loc) · 19.1 KB

Infernet Protocol

Infernet Protocol

A decentralized GPU inference marketplace. Rent a GPU anywhere, run one docker command, start earning crypto. The control plane is a Next.js dashboard; GPU nodes authenticate with Nostr-signed HTTP requests — they never hold a database credential, run as an unprivileged user, and can operate outbound-only. Scales horizontally — every new provider on the network is real, additional capacity.

Docker image Release


What you get

  • Next.js 16 + React 19 web dashboard. Also ships as the Electron desktop app (same app, Electron wrapper).
  • Public chat playground at /chat — streams tokens via Server-Sent Events. Uses live P2P providers if any are online; otherwise falls back to NVIDIA NIM (build.nvidia.com) so the demo never breaks.
  • One-click GPU deploy at /deploy — paste a RunPod API key, pick a GPU, the image boots and registers with your control plane automatically.
  • infernet CLI — one binary per GPU server. 14 subcommands: init, login, register, update, remove, start, stop, status, stats, logs, payout, payments, gpu, firewall. Daemon has a local IPC socket for live queries + a public P2P TCP port 46337 (dual-stack IPv4/IPv6) for direct peer communication.
  • Supabase backend on the server — Postgres + Auth + Realtime. Self-hosted or cloud.
  • Nostr-signed node API at /api/v1/node/* — GPU nodes sign every request with their secp256k1 / BIP-340 keypair; the control plane verifies the signature before touching the DB. No service-role keys ever leave the server.
  • Privacy-preserving telemetry — heartbeats carry only coarse GPU capability (vendor + VRAM tier). No hostname, platform, CPU model, or RAM total is stored.
  • Multi-coin payments via CoinPayPortal — BTC, BCH, ETH, SOL, POL, BNB, XRP, ADA, DOGE, plus USDT/USDC on ETH/Polygon/Solana/Base.
  • GPU auto-detect — nvidia-smi, rocm-smi, Apple Silicon; stashed in providers.specs.gpus for control-plane job matching.

Security & anonymity model

Design goals: a GPU node should be able to run as an unprivileged user, leak as little as possible about its host, and never hold a credential that could compromise the rest of the network.

  • No database credential on nodes. The CLI holds a Nostr (secp256k1 / BIP-340 Schnorr) keypair and nothing else. The control plane is the only thing that talks to Supabase; nodes talk to the control plane over /api/v1/node/* with a signed envelope on every request.
  • Signed-request envelope. Each call carries X-Infernet-Auth: base64url({ v, pubkey, created_at, nonce, sig }) where sig is a Schnorr signature over METHOD + path + created_at + nonce + sha256(body). 60-second replay window, per-process nonce cache, pubkey must match public_key on the target row.
  • Minimized telemetry. Heartbeats carry only coarse GPU capability: { vendor, vram_tier, model } per GPU. Hostname, platform, arch, CPU model, RAM total are discarded both client-side and at the server sanitizer.
  • Outbound-only mode. Pass --no-advertise to init / register / start and the node never publishes an IP or port; it only pulls work via signed polls. Pairs naturally with Tor or a NAT.
  • Unprivileged install. Config lives at ~/.config/infernet/config.json (mode 0600). No /etc, no /var, no sudo, no privileged ports (default P2P port 46337, override with --p2p-port).
  • Auto-migration. Legacy configs that still carry supabase.serviceRoleKey are stripped silently on load — the field never gets re-saved to disk.

Deployment modes

The control plane runs one of two ways:

  1. Self-hosted — run Supabase yourself via the Supabase CLI (supabase start). Best for privacy and offline development.
  2. Infernet cloud — point the CLI at the hosted control plane at https://infernet.tech. Rent a GPU anywhere, infernet init, start earning.

Operators can point many GPU nodes at the same control plane — each node has its own Nostr identity and shows up as its own row in the dashboard.


Architecture (one page)

       ┌────────────────────────────────────────────────┐
       │   Next.js dashboard (self-hosted OR cloud)     │
       │   /chat · /deploy · /api/* · SSE streaming     │
       │   /api/v1/node/* — signature-verified endpoints│
       └───────────────────────┬────────────────────────┘
                               │  (server-side only)
                               ▼
       ┌────────────────────────────────────────────────┐
       │                  Supabase                      │
       │  providers · clients · aggregators · jobs      │
       │  users · job_events · platform_wallets         │
       │  provider_payouts · payment_transactions       │
       └────────────────────────────────────────────────┘
                               ▲
        Nostr-signed HTTP      │        anon / SSE
        (BIP-340 Schnorr)      │
   ┌────────────┐   ┌──────────┐   ┌─────────────────┐
   │ infernet   │…  │ Expo app │   │ NVIDIA NIM      │
   │ CLI daemon │   │          │   │ (fallback only) │
   │ P2P:46337  │   └──────────┘   └─────────────────┘
   │ IPC sock   │
   └────────────┘

The GPU node holds a Nostr keypair (generated on infernet init); every call to the control plane carries an X-Infernet-Auth envelope with a Schnorr signature over method + path + timestamp + nonce + sha256(body). The server enforces that the signing pubkey owns the target row. No DB credential ever lives on the node.

Full detail: INFERNET-ARCHITECTURE.md.


Stack

  • Node.js 18+, ESM only, pnpm workspaces monorepo (5 apps, 11 shared packages)
  • Next.js 16.x (App Router), React 19, Tailwind CSS
  • Supabase (@supabase/supabase-js)
  • Vitest for tests
  • Electron for the desktop shell
  • React Native + Expo for mobile
  • Docker + GitHub Actions CI/CD (multi-arch images: linux/amd64 + linux/arm64)

Quick start

1. Control plane (one-time)

Self-hosted:

pnpm install
pnpm supabase:start
pnpm supabase:db:reset   # applies all migrations in supabase/migrations/
pnpm dev                 # dashboard at http://localhost:3000

Or point at Supabase cloud:

pnpm install
pnpm supabase:login
pnpm supabase:link
pnpm supabase:db:push    # pushes migrations to the cloud project
pnpm dev

Copy sample.env.env.local and fill in SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, and the CoinPayPortal keys.

2. Each GPU server (Docker — the supported install path today)

docker run --rm -it \
  --gpus all \
  -p 46337:46337 \
  -e INFERNET_CONTROL_PLANE_URL=https://infernet.tech \
  -e INFERNET_NODE_NAME=edge-01 \
  ghcr.io/profullstack/infernet-provider:latest

Multi-arch (linux/amd64 + linux/arm64). The image calls infernet initinfernet registerinfernet start --foreground internally, so one command boots a provider and starts accepting jobs. No database credentials are passed in — the container generates its own Nostr keypair on first run and uses it to sign every request.

Running the CLI directly

If you cloned the repo:

pnpm install
pnpm --filter @infernetprotocol/cli start -- help     # any subcommand

Full CLI surface: init, login, register, update, remove, start, stop, status, stats, logs, payout, payments, gpu, firewall. Config lives at ~/.config/infernet/config.json.

npm install (npm i -g @infernetprotocol/cli) and Homebrew (brew install infernet) are on the roadmap.

infernet init walks through:

  • Control-plane URL (default https://infernet.tech, or your self-hosted instance)
  • Node role (provider / aggregator / client)
  • Human-readable name
  • Nostr keypair — real secp256k1 / BIP-340 (auto-generated; bring your own with --nostr-privkey)
  • P2P port (default 46337, TCP, dual-stack IPv6/IPv4) — pass --no-advertise to stay outbound-only
  • Auto-detected local address (override with --address)
  • GPU detection via nvidia-smi / rocm-smi / system_profiler
  • Firewall hint per your distro

Config lives at ~/.config/infernet/config.json (mode 0600). It contains the node's Nostr keypair and the control-plane URL — no database credentials, no service-role keys.


CLI surface

Node lifecycle:
  init          First-time setup (generates Nostr keypair)
  login         Re-point at a different control plane
  register      Announce this node (signed POST /api/v1/node/register)
  update        Re-push current state (signed; upsert)
  remove        Deregister and wipe local config (signed)

Daemon:
  start         Start daemon (detached; --foreground for supervisors)
  stop          Stop daemon (graceful via IPC, signal fallback)
  status        Control-plane row + live daemon snapshot
  stats         Live in-memory daemon stats via IPC
  logs          Show / tail the daemon log (-f for follow)

Diagnostics:
  gpu           Inspect local GPUs (list | json)
  firewall      Print firewall commands for the P2P port

Payments:
  payout        Manage payout coin/address (set, list)
  payments      Show recent payment transactions

Daemon ↔ CLI

infernet start detaches into the background (logs at ~/.config/infernet/daemon.log) and exposes a Unix-domain IPC socket at ~/.config/infernet/daemon.sock. The other CLI commands use that socket for live queries:

  • infernet status fetches this node's row from the control plane (signed POST /api/v1/node/me) and merges it with the daemon's in-memory snapshot.
  • infernet stats shows heartbeat counts, poll counts, active jobs, uptime, P2P connections.
  • infernet stop sends a graceful shutdown command; falls back to SIGTERM via the PID file.
  • infernet logs -f tails the log file.

Use infernet start --foreground under systemd / Docker / Kubernetes when a supervisor wants the process in the foreground.

P2P port

Each provider/aggregator node binds TCP 46337 (dual-stack) for peer communication. Change with --p2p-port, or disable with --no-p2p. The node advertises its address:port to the control plane on every signed heartbeat so other nodes can discover it — unless it was started with --no-advertise, in which case nothing is published and the node operates outbound-only.

Need to open the port on your firewall? infernet firewall prints the exact commands for ufw / firewalld / nftables / iptables (Linux), pf (macOS), or netsh (Windows). We never touch firewall state automatically.

GPU support

The CLI auto-detects GPUs on init, register, update, and daemon heartbeat:

  • NVIDIA (CUDA) via nvidia-smi — model, VRAM, driver, CUDA version, utilization, temperature, power.
  • AMD (ROCm) via rocm-smi — same fields where available.
  • Apple Silicon via system_profiler — model, unified memory.
  • Falls back to CPU-only if no GPU tooling is installed.

Detected GPUs feed the coarse capability payload that lands in providers.specs.gpus (jsonb): { vendor, vram_tier, model } per card, plus gpu_count. Enough to route jobs (e.g. "find me a provider in the 24-48gb tier") without broadcasting the machine's full fingerprint.


NVIDIA NIM fallback

When the P2P network has no live providers, the chat playground transparently falls back to build.nvidia.com's OpenAI-compatible endpoint so the UX never breaks while the network is bootstrapping. Controlled by three env vars (all optional; leave blank to disable):

NVIDIA_NIM_API_KEY=             # from https://build.nvidia.com/
NVIDIA_NIM_API_URL=https://integrate.api.nvidia.com/v1
NVIDIA_NIM_DEFAULT_MODEL=meta/llama-3.3-70b-instruct

Same SSE contract as the P2P path — tokens are mirrored into job_events so the audit trail is identical. The chat UI badges fallback responses "NVIDIA NIM (fallback)" for transparency. Adapter: packages/nim-adapter.

Payments

Consumers can pay for jobs and providers can be paid out in any of these coin/network combinations:

BTC, BCH, ETH, SOL, POL, BNB, XRP, ADA, DOGE; plus USDT on ETH/Polygon/Solana; plus USDC on ETH/Polygon/Solana/Base.

Provider earnings flow into the outbound direction of payment_transactions. Configure a payout wallet with infernet payout set <COIN> <ADDRESS>.


Project layout (monorepo)

apps/
  web/                   Next.js dashboard + /chat + /deploy + REST/SSE API
  cli/                   The `infernet` binary (commands + daemon + IPC)
  desktop/               Electron shell wrapping the Next.js app
  mobile/                React Native + Expo app
  daemon/                Placeholder for future standalone service daemons

packages/
  config/                Payment-coin list + canonical deposit addresses
  auth/                  Nostr identity + signed-request envelope (BIP-340)
  db/                    Supabase-backed model layer (server-side only)
  gpu/                   GPU detection (NVIDIA / AMD / Apple Silicon)
  inference/             Distributed inference coordinator / worker
  payments/              CoinPayPortal gateway
  logger/                Structured logger
  sdk-js/                @infernetprotocol/sdk — REST + SSE client
  api-schema/            OpenAPI 3.1 spec for the control-plane API
  deploy-providers/      Cloud-GPU deploy adapters (RunPod today)
  nim-adapter/           NVIDIA NIM fallback inference adapter

tooling/
  docker/provider/       Dockerfile + entrypoint for ghcr.io/.../infernet-provider
  dist/homebrew/         Homebrew formula + release-time updater

supabase/migrations/     SQL schema (applied via supabase CLI)
tests/                   Vitest suite

API routes

GET   /api/overview
GET   /api/nodes
GET   /api/jobs
GET   /api/providers
GET   /api/aggregators
GET   /api/clients
GET   /api/models
POST  /api/chat                            Chat playground — picks P2P provider OR NIM fallback, returns streamUrl
GET   /api/chat/stream/[jobId]             SSE: tokens streamed live (job|meta|token|done|error)
POST  /api/payments/invoice                CoinPayPortal invoice mint
POST  /api/payments/webhook                CoinPayPortal webhook sink (HMAC-verified)
GET   /api/deploy/runpod/gpu-types         RunPod GPU catalog (proxied via user API key; not stored)
POST  /api/deploy/runpod                   One-click launch an Infernet provider pod

Signature-verified node API (Nostr / BIP-340 Schnorr):
POST  /api/v1/node/register                Upsert a provider/aggregator/client row
POST  /api/v1/node/heartbeat               Refresh last_seen / status / address / port
POST  /api/v1/node/jobs/poll               Pull assigned jobs for this provider
POST  /api/v1/node/jobs/[id]/complete      Mark a job completed / failed + record payout row
POST  /api/v1/node/jobs/[id]/events        Batch-emit streaming tokens into job_events
POST  /api/v1/node/remove                  Deregister this node
POST  /api/v1/node/me                      Fetch this node's own row
POST  /api/v1/node/payments/list           List payment_transactions for this node
POST  /api/v1/node/payouts/{list,set}      Manage provider_payouts rows

Web/mobile routes are server-only — the Supabase service-role client is never imported into browser bundles. The /api/v1/node/* routes are the only ones nodes ever touch, and they verify a Nostr signature before any DB write. OpenAPI 3.1 spec ships as @infernetprotocol/api-schema (raw YAML at packages/api-schema/openapi.yaml).

Developer surfaces

  • @infernetprotocol/sdk (packages/sdk-js) — JS/TS SDK with an InfernetClient and an async-iterator chat() helper for the SSE stream.
  • @infernetprotocol/api-schema (packages/api-schema) — OpenAPI 3.1 spec; feed to any generator for Python/Go/Rust clients.
  • @infernetprotocol/deploy-providers (packages/deploy-providers) — cloud-GPU adapters (RunPod today) powering the one-click /deploy page.

Distribution

  • Docker (LIVE) — ghcr.io/profullstack/infernet-provider:0.1.0 and :latest, multi-arch linux/amd64 + linux/arm64. One command boots a provider; see the Quick start above.
  • npm (scaffolded, unblock pending) — 11 publishable @infernetprotocol/* packages (cli, sdk, api-schema, deploy-providers, nim-adapter, auth, config, db, gpu, inference, logger, payments). Release workflow + dry-run publish are ready; waiting on an npm Automation token.
  • Homebrew (scaffolded) — formula + updater script at tooling/dist/homebrew. Tap repo at profullstack/homebrew-infernet. Activates as soon as npm ships.

Tag a v*.*.* and .github/workflows/release.yml builds the Docker image and creates a GitHub Release. See docs/RELEASING.md for the full flow.

One-click GPU deploy

The /deploy page (backed by POST /api/deploy/runpod) spins up an Infernet provider node on RunPod using a user-supplied API key. The server proxies the RunPod API call and immediately drops the key — nothing is persisted. The pod boots the infernet-provider image, generates a Nostr keypair, registers itself with the control plane over a signed request, and starts heartbeating.


Contributing

See docs/CONTRIBUTING.md.

License

MIT. See LICENSE.