paycall-402
v0.1.1
Published
Umbrella package for Paycall 402 HTTP payment SDK packages.
Readme
Paycall 402
Framework‑agnostic HTTP 402 “payment required” protocol for monetizing API endpoints using EVM ERC‑20 authorizations.
Important: Currently, only EIP-3009 tokens are supported for end-to-end payment and settlement flows.
Packages
@paycall-402/types: shared protocol types@paycall-402/utils: Base64URL(JSON) header helpers + crypto utilities@paycall-402/core:PaymentEnginescheme registry + challenge creation + verification gates@paycall-402/evm: EVM schemes (EIP‑3009, EIP‑2612) + client typed‑data builders@paycall-402/server:createPaywall()+ nonce stores +createPaywallWithEip3009Settlement(verify + relayer settlement)@paycall-402/express:paywallCore()+paywall()@paycall-402/client: fetch wrapper + axios interceptor + wallet abstraction
Examples
examples/servers/express: Express paywall demo serverexamples/clients/node-paid-fetch: NodecreatePaidFetchwith viem, ethers v6, and genericWalletexamples/clients/injected-provider: Vite + MetaMask injected-provider demo
Protocol (headers)
X-PAYMENT-CHALLENGE: Base64URL(JSON PaymentChallenge) (server → client on 402; client must replay it on retry)X-PAYMENT: Base64URL(JSON payment message) (client → server)X-PAYMENT-SIGNATURE:0x…EIP‑712 signature (client → server)
Header names are treated case‑insensitively (Node/Express expose them lowercased).
Install & build
pnpm install
pnpm build
pnpm testAfter a clean checkout, workspace packages must be built before pnpm lint (declaration files live under each package’s dist/). Use pnpm build:packages or full pnpm build; CI builds packages before lint and test.
Maintainer release flow
# 1) add a changeset describing your package changes
pnpm changeset
# 2) apply lockstep version bumps and update internal package ranges
pnpm version-packages
# 3) publish to npm/GitHub Packages
pnpm releaseNotes:
- Packages are versioned in lockstep via Changesets fixed mode.
- In this repo, unpublished
@paycall-402/*packages useworkspace:*sopnpm installand CI resolve local links. Published tarball dependency ranges are updated as part of the release/versioning workflow.
Server usage (Express, EIP-3009 + on-chain settlement)
import express from "express";
import { paywall } from "@paycall-402/express";
const app = express();
const chainId = 1;
const rpcUrl = "https://…";
const token = "0x…" as const; // EIP-3009-capable ERC-20
const recipient = "0x…";
app.get(
"/premium",
paywall({
tokenAddress: token,
evm: { chainId, network: "ethereum", rpcUrl },
recipient,
amountUsd: "1"
}),
(_req, res) => res.json({ ok: true })
);Legacy paywallCore() + createPaywall() (multi-scheme, verify-only) remain available. See docs/executor.md for the settlement flow and status codes.
Client usage (fetch)
import { createPaidFetch, createEvmPaymentSigner, type Wallet } from "@paycall-402/client";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const wallet: Wallet = {
address: account.address,
signTypedData: (td) =>
account.signTypedData({
domain: td.domain,
types: td.types,
primaryType: td.primaryType,
message: td.message
})
};
const paidFetch = createPaidFetch({
wallet,
signer: createEvmPaymentSigner({ rpcUrl: process.env.RPC_URL! })
});
const res = await paidFetch("http://localhost:4020/premium");
console.log(res.status, await res.text());Client usage (axios)
import axios from "axios";
import { installAxiosPaymentInterceptor, createEvmPaymentSigner } from "@paycall-402/client";
const api = axios.create({ baseURL: "http://localhost:4020" });
installAxiosPaymentInterceptor({ axios: api, wallet, signer: createEvmPaymentSigner({ rpcUrl }) });
await api.get("/premium");Security model (what’s enforced)
- Nonce‑based replay protection: verified authorizations are written to a
NonceStore; replays fail. Withpaywall(EIP-3009 settlement), the HTTP nonce is recorded only after a successful on-chain submit (503 + no nonce on settlement failure). - Double‑spend protection:
NonceStore.set()must be atomic (RedisSET NX PXsupported; InMemory throws on reuse). - Expiry validation: challenges are rejected when expired.
- Balance preflight (optional): configure
preVerify(for examplecreateEvmErc20BalancePrecheck) to return402witherror: "insufficient_funds"before signature verification when payer balance is too low. - EIP‑712 verification:
- EIP‑3009:
TransferWithAuthorization(domain useschainId+verifyingContract, andname/versionif readable) - EIP‑2612:
Permit(requires reading the tokennonces(owner)via RPC to ensure the signature matches the current nonce)
- EIP‑3009:
- Chain ID validation: scheme instance enforces a single configured
chainId. - Contract/token address validation: address format is validated; token is used as
verifyingContract. - Amount validation: exact match with the server’s quoted amount.
- Recipient validation: exact match with the server’s quoted
recipient.
Running the example
# Terminal 1
cd examples/servers/express
CHAIN_ID=11155111 RPC_URL=https://... NETWORK=sepolia TOKEN=0x... RECIPIENT=0x... SCHEME=evm:eip3009 AMOUNT=1 RELAYER_PRIVATE_KEY=0x... pnpm dev
# Terminal 2
cd examples/servers/express
API_URL=http://localhost:4020 RPC_URL=https://... PRIVATE_KEY=0x... node ./src/client.tsMetaMask frontend demo
# Terminal 1: start backend paywall server
cd examples/servers/express
CHAIN_ID=11155111 RPC_URL=https://... NETWORK=sepolia TOKEN=0x... RECIPIENT=0x... SCHEME=evm:eip3009 AMOUNT=1 RELAYER_PRIVATE_KEY=0x... pnpm dev
# Terminal 2: start browser demo
cd examples/clients/injected-provider
pnpm devOpen http://localhost:5173, connect MetaMask, then call /premium. The frontend uses the connected wallet provider for reads by default; RPC URL is only an optional override.
On-chain settlement (EIP-3009)
See docs/executor.md. paywall submits the tx from your relayer, logs txHash, returns 503 if submission fails, and tracks receipts in the background (no blocking wait for confirmations in the HTTP handler).
Non‑goals
- No hosted API product—this repo is libraries you import. Optional RabbitMQ/Kafka adapters are for your own workers.
