Developers · quickstart
From a clone to a settled payment.
Everything runs on your machine first: a local chain, the contracts, a facilitator and a demo provider, with published test keys and no network. The same code then points at Elysium Testnet by changing one option.
What you need
- 1
Node 22 or later and pnpm 10
The repository is a pnpm workspace; corepack enables the pinned pnpm. - 2
Foundry, for the local chain
anvil runs the local chain and forge deploys the contracts. Install from getfoundry.sh. Paying against the testnet needs neither. - 3
The repository
The packages are workspace packages; they are not yet published to npm. Clone the repository, runpnpm install, and build the packages once.
Status: Elysium Testnet (chain 99801), pre-audit. Test assets have no value. Mainnet is not deployed.
Run the whole protocol locally
One command starts the chain, deploys the contracts, mints the test asset to the demo agent, and serves a provider with two paid services. A second command plays the agent.
# Everything local: anvil, the contracts, a facilitator and a demo provider. No funds, no network.
pnpm install
pnpm -r --filter './packages/*' build
pnpm --filter @epp402/cli dev -- dev # terminal 1
pnpm --filter @epp402/cli dev -- simulate # terminal 2: discover, quote, session, pay, verify, complete
pnpm --filter @epp402/cli dev -- inspect http://127.0.0.1:4021simulate prints the discovery check, the session it opened, the signed quote, the 402 and the paid response, the transaction hash, the receipt and its verification against the chain, and the completed session with its history. Add --json for the objects themselves, and --registry http://127.0.0.1:4402 when the local API from pnpm dev is running, to see the session appear in the explorer.
Ports: anvil 8402, the stack's facilitator 4020, the demo provider 4021. pnpm dev at the repository root also starts embedded Postgres on 54402, the API on 4402 and this website on 3402.
Make a paid call
The agent SDK does the whole lifecycle: discover, quote, session, intent, x402 handshake, receipt verification, publication.
import { privateKeyToAccount } from "viem/accounts";
import { Epp402Agent } from "@epp402/client";
const agent = new Epp402Agent({
signer: privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`),
network: "elysium-testnet",
registryUrl: "https://epp402-core.fly.dev", // optional: publishes sessions, quotes, intents and receipts
});
const { document } = await agent.discover("https://inference.example.com");
const asset = document.assets[0]!.address;
const session = await agent.openSession({ asset, spendingLimit: "25000000" }); // 25 EPPUSD
const quote = await agent.requestQuote(document, "inference.standard", { text: "Summarise Q3." }, { session });
const paid = await agent.pay(quote, { session, body: { text: "Summarise Q3." } });
console.log(paid.settlement.txHash, paid.receipt?.receiptId, paid.verification?.ok);
await agent.completeSession(session);- 1
discover
Fetches /.well-known/epp402.json, validates it, checks the provider's signature and that it settles on your network. - 2
openSession
A buyer-side budget in the payment asset's minor units. Everything the agent buys is recorded under it. - 3
requestQuote
A provider-signed price for your exact parameters, bound to your address and the session. - 4
pay
Checks the policy, signs the intent, drives the x402 402 → PAYMENT-SIGNATURE exchange with the signed intent in EPP-INTENT, verifies the receipt, records the settlement. - 5
completeSession
Closes the budget. With a registry URL the session, quote, intent and receipt were published along the way.
The full option tables are on the Agent SDK page.
Sell something
A provider is one configuration object. The SDK serves the discovery document, signs quotes and receipts, and mounts the x402 gate in front of your handler.
import { serve } from "@hono/node-server";
import { privateKeyToAccount } from "viem/accounts";
import { createProvider } from "@epp402/server";
const provider = createProvider({
providerId: "inference.example.com",
name: "Example Inference",
url: "https://inference.example.com",
network: "elysium-testnet",
signer: privateKeyToAccount(process.env.PROVIDER_PRIVATE_KEY as `0x${string}`),
facilitatorUrl: "https://epp402-core.fly.dev/facilitator",
registryUrl: "https://epp402-core.fly.dev",
services: [
{
id: "inference.standard",
name: "Standard inference",
category: "ai-inference",
endpoint: "/v1/inference",
method: "POST",
price: "0.01", // list price in EPPUSD; the quote below prices each request
quote: {
price: (req) => 10_000n + 5n * BigInt(String((req.params as { text?: string }).text ?? "").length / 100),
},
},
],
});
// Discovery, quotes, receipts and the x402 gate are already mounted; add your handler.
const app = provider.app();
app.post("/v1/inference", async (c) => c.json(await infer(await c.req.json())));
serve({ fetch: app.fetch, port: 4031 }, () => provider.publish());Options, the service shape and what the gate does are on the Provider SDK page. The CLI scaffolds this file with epp402 init.
Without an SDK
The wire format is x402 v2. Any client that can sign an EIP-3009 authorization can pay; the EPP-402 objects arrive in headers it may ignore.
# 1. Ask without paying: the gate answers 402 with its terms.
curl -i https://inference.example.com/v1/inference -X POST -d '{"text":"hi"}'
# HTTP/1.1 402 Payment Required
# PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwicmVzb3VyY2Ui... (base64 JSON)
# 2. Sign the EIP-3009 authorization the terms describe, then retry with it.
curl -i https://inference.example.com/v1/inference -X POST -d '{"text":"hi"}' \
-H "PAYMENT-SIGNATURE: eyJ4NDAyVmVyc2lvbiI6MiwiYWNjZXB0ZWQi..."
# HTTP/1.1 200 OK
# PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4...
# EPP-RECEIPT: eyJwcm90b2NvbCI6ImVwcC00MDIiLCJyZWNlaXB0SWQi...What a plain x402 client gets, and what an EPP-402 client adds, is on the x402 compatibility page.
Hosted endpoints
The facilitator and the registry run as one service. Point the SDKs at them to leave the local stack.
Service roothttps://epp402-core.fly.dev/
Network, contract addresses and assets as the service sees them.Facilitatorhttps://epp402-core.fly.dev/facilitator
x402 v2: /supported, /verify, /settle; EPP-402 adds /status/:transactionId and /health.Registryhttps://epp402-core.fly.dev/v1
Providers, services, sessions, quotes, intents, settlements, receipts, stats, network, events.Healthhttps://epp402-core.fly.dev/health
Database, RPC and the facilitator's gas balance.This sitehttps://epp402.vercel.app
Explorer, receipt verification, documentation.
Contract addresses are read from contracts/deployments/99801.json. Until that manifest exists the facilitator settles directly on the configured token and the indexer stays idle; the network page shows the current state.
Packages
What each workspace package is for. Import from the specific package; @epp402/sdk re-exports the protocol, network, client and server surfaces for convenience.
@epp402/protocolschemas, signatures
The normative objects as zod schemas, EIP-712 typed data, canonical hashing, ids, error codes, header codecs.@epp402/elysiumnetwork adapter
Verified network parameters, the viem chain, explorer links, deployment manifests.@epp402/authorizationbuyer policy
The spending policy engine and Payment Intent construction and binding.@epp402/sessionbuyer state
The session state machine with in-memory and Postgres stores.@epp402/receiptsproof
Issue a receipt as a provider; verify one against signature and chain as anyone.@epp402/clientagent SDK
Epp402Agent, discovery and quote helpers, PaymentFailed.@epp402/serverprovider SDK
createProvider: discovery document, quotes, the x402 gate, receipts, on-chain registration.@epp402/facilitatorsettlement
An x402 facilitator with replay protection and the EPP402Settlement relay.@epp402/registryindex
Crawler, chain indexer, the /v1 API and the worker.@epp402/clitooling
epp402: init, dev, inspect, simulate, publish, registry, receipts, session, doctor.@epp402/sdkumbrella
Re-exports protocol, elysium, authorization, session, receipts, client and server.