tokenos-p2p
v0.4.2
Published
TokenOS P2P AI node — sell or buy AI inference directly between peers. Solana identity, DHT discovery, encrypted direct transport over TCP or WebSocket (protocol v2.1), browser buyer SDK, credits-funded sessions, USDC payouts.
Maintainers
Readme
tokenos-p2p
Peer-to-peer AI inference for TokenOS. Sellers run a node in front of any OpenAI-compatible, Anthropic or Google endpoint (or a local Ollama / vLLM) and set their own price. Buyers run a node that exposes a local OpenAI-compatible endpoint and routes every request directly to the cheapest trusted seller — no relay in the path.
- Identity — a Solana ed25519 keypair; the peer id is the base58 public key and the default USDC payout wallet.
- Discovery — public BitTorrent DHT (topics
tokenos:*,tokenos:subnet:<n>,tokenos:peer:<id>) plus the TokenOS indexer snapshot (GET https://tokenos.ai/api/p2p/stats). The indexer drops loopback / private endpoints. - Transport — direct TCP, X25519 handshake signed by both identities (schema-validated), ChaCha20-Poly1305 frames with the frame header as associated data (protocol v2; v1 nodes are refused with a clear error — upgrade both sides).
- Payment — buyers fund sessions from TokenOS credits (bought with USDC or $TOS). TokenOS signs a voucher capping the session; the buyer signs cumulative SpendingAuths after every response; the seller settles them and is paid in USDC on Solana. Credits, holds and refunds are fractional (6 decimals) — the unspent hold comes back exactly. Platform fee 4 %. No token rewards.
npm i -g tokenos-p2p # Node ≥ 20Sell
Each command on its own line. seller start keeps running — use a second terminal for the rest.
tokenos-p2p identity create
tokenos-p2p seller init
export OPENAI_API_KEY=sk-…
tokenos-p2p seller register --name "my-node" --region eu --payout-wallet <your-usdc-wallet> --email [email protected]
tokenos-p2p seller start --public-host <your-public-ip>tokenos-p2p seller status
tokenos-p2p seller payout
tokenos-p2p seller unregisteridentity import ~/.config/solana/id.jsonreuses a Solana CLI keypair.seller initwrites~/.tokenos-p2p/seller.json— edit services + pricing. Upstream keys are read from env ("env:OPENAI_API_KEY").--payout-walletis where every payout (manual and automatic) lands; without it, the node's own peer-id wallet is used.seller payoutprints the destination before it transfers;--to-walletoverrides once. Minimum 1 USDC; payouts are sent on request — there is no scheduled sweep.--emailis optional: a one-time uptime checklist and the Monday digest. Unknown flags are rejected.seller startneeds tcp/7882 (+ udp/7881 for the DHT) reachable, plus tcp/7883 for the WebSocket listener (protocol v2.1;--ws-port 0or"wsPort": 0turns it off).--public-hostmust be a public address — loopback, RFC 1918, link-local and CGNAT hosts are warned about and the indexer will not list them. Without the flag the node uses the address the indexer observed.- Session evidence (latest signed SpendingAuth + receipt) is persisted in
~/.tokenos-p2p/sessions/and settled after a restart or crash, so metered work is never forfeited.
seller.json:
{
"displayName": "my-node", "region": "eu", "port": 7882, "publicHost": null,
"providers": [
{ "provider": "openai", "upstream": { "kind": "openai", "apiKey": "env:OPENAI_API_KEY" },
"services": ["gpt-5.5"], "defaultPricing": { "inputUsdPerMillion": 1.0, "outputUsdPerMillion": 8.0 }, "maxConcurrency": 4 },
{ "provider": "ollama", "upstream": { "kind": "ollama", "baseUrl": "http://127.0.0.1:11434/v1" },
"services": ["llama-4-maverick"], "serviceModels": { "llama-4-maverick": "llama4:maverick" },
"defaultPricing": { "inputUsdPerMillion": 0.1, "outputUsdPerMillion": 0.3 }, "maxConcurrency": 2 }
],
"limits": { "maxConnections": 256, "maxConnectionsPerIp": 16, "handshakeTimeoutMs": 8000, "idleTimeoutMs": 600000 },
"verifications": { "domains": [{ "domain": "example.com", "methods": ["dns-txt"] }], "github": [{ "username": "me", "repository": "me" }] }
}Upstream kinds: openai, anthropic, google, ollama, vllm (any baseUrl that speaks the OpenAI HTTP shape — DeepInfra, Novita, DeepSeek, …). upstream.extraBody pins request fields on every forwarded call (e.g. { "service_tier": "flex" }); presets for common upstreams: https://tokenos.ai/docs/p2p-sell#upstream-presets.
The price sheet a buyer signed against is pinned per session — changing prices applies to new sessions only. A request the buyer cancels mid-stream aborts the upstream call and bills only the bytes already delivered (partial receipt).
Buy
export TOKENOS_API_KEY=tos_…
tokenos-p2p buyer startcurl http://127.0.0.1:8377/v1/models
curl http://127.0.0.1:8377/v1/chat/completions -H 'content-type: application/json' -d '{"model":"gpt-5.5","messages":[{"role":"user","content":"hi"}]}'Create the key at https://tokenos.ai/p2p/buy (needs credits). Point any OpenAI SDK at base_url=http://127.0.0.1:8377/v1
(any api_key). Headers: x-tokenos-pin-peer: <peerId> pins a seller; responses carry x-tokenos-peer and
x-tokenos-session. Flags: --max-credits 10 (per-session budget; the first session with a new seller is capped at 10
credits), --min-trust 40, --pin-peer, --no-dht (indexer only).
Routing. Offers are matched on model and API family (/v1/messages only reaches peers advertising
anthropic-messages, /v1/responses needs openai-responses; otherwise 404 protocol_not_offered), ranked by
input + output USD per 1M tokens (cheapest first, trust score as the tie-breaker). --min-trust defaults to 0, so
unrated new sellers are eligible — /v1/peers shows trustLabel: unrated. Up to 3 candidates are tried before any
byte or charge is committed: unreachable peers, 429 busy, exhausted sessions and rejected sessions fail over; when
none works the node answers 503 no_reachable_peer with the attempts listed.
Disputes. Both sides parse the same response bytes (usage when the upstream returns it, bytes-per-token estimate
otherwise). A receipt claiming more than the buyer's own figure × 1.15 + 0.01 credits is a dispute: the buyer authorises
only up to that ceiling and skips the peer after two disputes. /v1/sessions reports meteredCredits, estimatedCredits,
authorized and unsettledCredits separately; settlement is confirmed by TokenOS (tokenos-p2p buyer sessions).
tokenos-p2p buyer sessions
tokenos-p2p buyer close-stale --dry-run
tokenos-p2p buyer close-stale --idle-minutes 30close-stale only targets sessions idle past the threshold (default 30 min) — healthy sessions are left alone
(--session <id> for one, --all to force). It requests closure, then withdraws the unspent hold after the seller's
15-minute grace period. Stopping the buyer just disconnects: sellers close and refund idle sessions themselves.
Explore
tokenos-p2p network browse --model claude
tokenos-p2p network browse --dht
tokenos-p2p provider verify <peerId>Browser SDK (0.4+)
Buy from a web page without Node: tokenos-p2p/browser (also served as a single ES module at
https://tokenos.ai/sdk/tokenos-p2p-browser.js) runs the full buyer in the page — ed25519 identity generated in the browser,
X25519 + ChaCha20-Poly1305 frames on a WebSocket, SpendingAuths signed locally, receipts verified against the seller key.
Browsers can only dial wss:// sellers (the TokenOS reference seller and TLS-fronted independent sellers).
import { TokenOSBrowserBuyer } from 'tokenos-p2p/browser'; // or the tokenos.ai/sdk URL
const buyer = new TokenOSBrowserBuyer({ apiKey: 'tos_…', apiUrl: 'https://tokenos.ai/api', maxCredits: 5, onEvent: console.log });
const res = await buyer.chat.completions.create({ model: 'gpt-oss-120b', messages: [{ role: 'user', content: 'hi' }] });
for await (const chunk of await buyer.chat.completions.create({ model: 'gpt-oss-120b', stream: true, messages: [...] })) …
buyer.close(); // Disconnect → the seller settles, TokenOS refunds the unspent hold at onceres._tokenos / stream.meta carry peerId, sessionId, transport, metered credits. Pass { signal } to abort (bills only the
bytes delivered). The tos_ key is a bearer secret: use it in front-ends you control. Live demo: tokenos.ai/p2p/playground.
Protocol (v2.1)
Everything signed is canonical JSON (sorted keys, no whitespace) prefixed with a domain tag and signed with ed25519:
tokenos-p2p-meta-v1: metadata · tokenos-p2p-msg-v1: handshakes and backend calls · tokenos-p2p-voucher-v1: TokenOS
voucher · tokenos-p2p-spend-v1: buyer SpendingAuth · tokenos-p2p-receipt-v1: seller receipt. Live parameters:
GET https://tokenos.ai/api/p2p/bootstrap. Frame header: type u8 · messageId u32 · length u32 (max 64 MiB); after the
handshake the header is the AEAD associated data of the ChaCha20-Poly1305 payload. HttpCancel (0x27) aborts an
in-flight request. Handshakes are validated (protocol version, base58 peer id, 32-byte X25519 key, clock skew, signature)
before any key derivation; a bad handshake closes only that connection.
Carriers (v2.1). The same frames ride on raw TCP ({host, port} / transport: "tcp") or on WebSocket binary messages
(transport: "ws" sellers, transport: "wss" for the TokenOS reference seller at wss://tokenos.ai/api/p2p/ws). Buyers
dial every announced endpoint, TCP first; x-tokenos-transport on each response tells you which carrier served it. The
reference provider (first-party supply, vendor list price) is always the fallback — independent sellers rank first.
Upgrading. Protocol v2 nodes (0.2.x and 0.3.x) refuse v1 handshakes; 0.2.x and 0.3.x interoperate over TCP. The indexer probes announced endpoints and flags stale
nodes in seller status (upgrade.required) and on tokenos.ai/p2p/provide; since 0.2.1 the node also declares
nodeVersion / protocolVersion in its signed metadata. Upgrade: npm i -g tokenos-p2p@latest, then restart seller start.
MIT.
