paycall-402
v0.1.1
Published
Umbrella package for Paycall 402 HTTP payment SDK packages.
Downloads
20
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.
