pi-402
Pi Agent 402 extension: pay-per-request access to OpenAI-compatible inference providers that support 402 payment protocols (L402, x402, MPP), settled over a Nostr Wallet Connect (NWC) Lightning wallet.
Package details
Install pi-402 from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-402- Package
pi-402- Version
0.2.1- Published
- Jun 20, 2026
- Downloads
- not available
- Author
- rolznz
- License
- unknown
- Types
- extension
- Size
- 281.5 KB
- Dependencies
- 0 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"dist/index.js"
],
"image": "https://github.com/rolznz/pi-402/raw/master/media/screenshot.png"
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-402
A pi extension for pay-per-request AI inference against OpenAI-compatible providers that support a 402 payment protocol (L402, x402, MPP), settled over a Nostr Wallet Connect (NWC) Lightning wallet — no account, no API key, no prepaid balance. Each request pays its own invoice.
You choose the provider in setup (default blockrun.ai). pi-402 probes it and picks the cheapest route automatically:
- L402-native provider (issues its own Lightning invoices) → paid directly.
- Anything else (x402/USDC, MPP, …) → routed through the l402.space gateway, which bridges the provider's rail to a Lightning/L402 invoice we can pay.
So you can pay any 402-capable OpenAI-compatible provider with Lightning — even one that doesn't support L402 itself — without paying the gateway's cut when you don't have to.
/402-setup → paste an NWC connection secret, pick a model
/model → switch between the upstream's models (native pi UI, provider "l402")
… chat … → every request pays its own Lightning invoice via your wallet
How it works
Pi's provider machinery makes a plain OpenAI-style HTTP call; it can't run a 402
402 → pay → retry loop on its own. So pi-402 starts a tiny loopback proxy and
registers an openai-completions provider that points at it. The proxy rewrites
each request to the gateway and pays:
pi ──/v1/chat/completions──▶ 127.0.0.1:<port>
rewrite to: https://l402.space/<url-encoded <upstream>/chat/completions >
│ 402 Payment Required (macaroon + invoice)
│ pay invoice via NWC ──▶ preimage
▼ retry Authorization: L402 <macaroon>:<preimage>
l402.space ──(L402 / x402 / MPP)──▶ upstream provider
pi ◀──────── 200 (streamed) ─────────┘
GET /v1/modelsis fetched directly from the upstream (free) to populate/model; it never goes through the gateway or pays.POST /v1/chat/completionsis wrapped for the gateway and triggers the pay-and-retry; streaming (SSE) works.- The proxy is a pure pass-through for the request/response body — pi already sends OpenAI-compatible requests, so nothing is rewritten except the URL.
- Payment is retried up to 3 times with a fresh invoice if a Lightning payment
flakes (e.g.
RouteNotFound); a failed payment never settles, so retrying is safe.
Why a loopback proxy and not pi's hooks?
There is no extension hook that can catch a 402 and resubmit with payment:
before_provider_request fires before the call, and after_provider_response is
observe-only ({status, headers}, no body, returns void). Pi dispatches via the
official openai SDK using the model's baseUrl, and ProviderConfig exposes no
custom-fetch/retry seam — so pointing baseUrl at a local paying proxy is the
clean, version-robust way to insert payment. (A streamSimple custom provider
handler could also do it in-process, but it requires reimplementing the OpenAI⇄pi
translation against pi-ai internals; not used here.)
Install
The extension ships as a single self-contained bundle (dist/index.js) with
all runtime deps inlined — no npm install needed by the end user. pi loads it via
the pi.extensions manifest in package.json.
pi install npm:pi-402 # once published to npm
pi install /path/to/pi-402 # from a local checkout (after `npm run build`)
pi install git:github.com/you/pi-402 # from git
Restart pi (or /reload). You'll get /402-setup, /402-provider, and /402-status.
The published npm tarball contains only
dist/index.js,package.json, and this README — the bundle is fully self-contained, so it works on a fresh machine with nothing else installed.
Usage
/402-setup— paste your NWC connection string (nostr+walletconnect://…), then pick a provider from the list (blockrun.ai, tx402.ai, or Custom). For Custom you must enter the full base URL — includinghttps://and the API path, e.g.https://host/v1. It validates the wallet, probes the provider to pick direct-vs-gateway routing, loads the model catalog, and lets you pick a default model.- Chat normally. Every request pays its own invoice (you'll see
L402: paying N sats …), and pi's footer cost reflects what you actually paid: the sats spent on each request are converted to USD (BTC/USD rate fetched once at session start) and written into that request's cost. Switch models anytime with/model(providerl402). /402-provider— switch to a different provider without redoing setup. Keeps your connected wallet; just re-pick from the list (or Custom URL), then it reloads the model catalog and re-probes direct-vs-gateway routing. Switch models afterward with/model./402-status— show provider, routing (direct/gateway), wallet balance, sats spent this session, current model, proxy URL — plus the NWC connection's spending budget (remaining / total / period) when it has one.
Spending limits
pi-402 auto-pays every invoice and adds no caps of its own. Put the budget on the NWC connection instead: when you create the connection in your wallet (Alby Hub, etc.), set a per-period spending limit and a max-amount. That's enforced by the wallet, survives across apps, and can't be bypassed by a buggy endpoint.
Configuration
Written to ~/.pi/agent/pi-402.json (mode 0600):
{ "nwcUrl": "nostr+walletconnect://…", "upstream": "https://blockrun.ai/api/v1", "useGateway": true, "defaultModel": "openai/gpt-4o-mini" }
upstream— any OpenAI-compatible API root that exposes/modelsand/chat/completions. Defaults to blockrun.ai.useGateway— set by/402-setupfrom a probe:falsewhen the provider is L402-native (paid directly),trueotherwise (routed via the hardcodedhttps://l402.spacegateway, which bridges x402/MPP/L402 rails to Lightning).nwcUrl— a spending secret; the file is owner-only and never logged. Note: the/402-setupprompt is not masked.
Provider compatibility
The proxy is a pure pass-through — it inserts payment at the HTTP layer and never
rewrites request or response bodies. A provider must be genuinely OpenAI-compatible
(accept the OpenAI-standard request pi sends, including content-part arrays and the
tools parameter). Providers that reject spec-compliant requests are not supported;
the fix belongs on the provider.
Development / testing
npm install # dev deps (also runs the build via `prepare`)
npm run build # bundle index.ts + deps -> dist/index.js (esbuild)
npm run typecheck
A test wallet lives in .env.test (gitignored). The harnesses make real
Lightning payments through l402.space using the cheapest models:
npm run models # list cheapest chat models (no payment)
npm run e2e # proxy → l402.space → upstream: paid completion + streaming
npx tsx --env-file=.env.test scripts/blockrun.ts # array vs string content through l402.space
npx tsx --env-file=.env.test scripts/smoke.ts # drive the extension via a mock pi API
To test the bundled extension exactly as users get it (no node_modules):
npm run build
pi --print -e ./dist/index.js --provider l402 --model openai/gpt-4o-mini "say pong"
Verified end-to-end inside real pi (headless):
export PI_402_NWC_URL="$(grep -oP '(?<=PI_402_NWC_URL=).*' .env.test)"
pi --print --no-extensions -e ./index.ts --no-tools --no-session \
--provider l402 --model "openai/gpt-4o-mini" "say pong"
Set PI_402_DEBUG=1 to log upstream status / error bodies to stderr.
Override the upstream for a run with PI_402_UPSTREAM=....
Layout
index.ts extension entry: /402-setup, /402-provider, /402-status, provider registration
scripts/build.mjs esbuild bundle → dist/index.js (self-contained, deps inlined)
src/config.ts load/save config; GATEWAY constant + gatewayUrl() URL wrapping
src/proxy.ts loopback proxy: rewrite to gateway, 402 → pay → retry, stream
src/l402.ts parse the L402 challenge, build the Authorization credential
src/nwc.ts NWC client: payInvoice, balance, validate
src/models.ts fetch upstream /models and map to pi's provider-model shape
scripts/e2e.ts end-to-end proxy test (→ l402.space → upstream)
scripts/blockrun.ts array-vs-string content probe through l402.space
scripts/smoke.ts drives the extension via a mocked ExtensionAPI
