Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion examples/typescript/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion examples/typescript/servers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ This directory contains TypeScript server examples demonstrating how to protect
| Directory | Description |
| --- | --- |
| [`express/`](./express/) | Using `@x402/express` middleware |
| [`self-facilitation/`](./self-facilitation/) | Express middleware with in-process SDK facilitator |
| [`hono/`](./hono/) | Using `@x402/hono` middleware |
| [`advanced/`](./advanced/) | Advanced patterns: hooks, dynamic pricing, custom tokens |
| [`custom/`](./custom/) | Manual implementation using only `@x402/core` |

## Framework Examples

The **express** and **hono** directories showcase the minimal approach to adding x402 paywalls to your API. These use our middleware packages that automatically handle:
The **express**, **self-facilitation**, and **hono** directories showcase the minimal approach to adding x402 paywalls to your API. These use our middleware packages that automatically handle:

1. Checking for payment headers on protected routes
2. Returning 402 with payment requirements if no payment
Expand Down
1 change: 1 addition & 0 deletions examples/typescript/servers/self-facilitation/.env-local
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EVM_PRIVATE_KEY=
8 changes: 8 additions & 0 deletions examples/typescript/servers/self-facilitation/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
docs/
dist/
node_modules/
coverage/
.github/
src/client
**/**/*.json
*.md
11 changes: 11 additions & 0 deletions examples/typescript/servers/self-facilitation/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 100,
"proseWrap": "never"
}
62 changes: 62 additions & 0 deletions examples/typescript/servers/self-facilitation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# @x402/self-facilitation Example Server

Express.js server demonstrating the same paywalled route shape as the `express` example, but using an in-process facilitator created with the SDK (`x402Facilitator`) instead of calling an external facilitator URL.

## What is different from `servers/express`

- `servers/express` uses `HTTPFacilitatorClient` and `FACILITATOR_URL`
- `servers/self-facilitation` creates an SDK facilitator in the same process and passes it as a `FacilitatorClient`
- The route/payment middleware setup stays essentially the same

## Prerequisites

- Node.js v20+ (install via [nvm](https://github.com/nvm-sh/nvm))
- pnpm v10 (install via [pnpm.io/installation](https://pnpm.io/installation))
- EVM private key with Base Sepolia ETH for settlement gas

## Setup

1. Copy `.env-local` to `.env`:

```bash
cp .env-local .env
```

Then fill required environment variables:

- `EVM_PRIVATE_KEY` - Ethereum private key used by the embedded facilitator

2. Install and build all packages from the TypeScript examples root:

```bash
cd ../../
pnpm install && pnpm build
cd servers/self-facilitation
```

3. Run the server:

```bash
pnpm dev
```

## Testing the server

You can test with the existing client examples:

```bash
cd ../clients/fetch
pnpm dev
```

or

```bash
cd ../clients/axios
pnpm dev
```

Both clients follow the usual two-step x402 flow:
1. First request gets `402 Payment Required`
2. Client signs and retries with `PAYMENT` headers
3. Server verifies/settles via the embedded facilitator and returns `200`
74 changes: 74 additions & 0 deletions examples/typescript/servers/self-facilitation/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import js from "@eslint/js";
import ts from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import prettier from "eslint-plugin-prettier";
import jsdoc from "eslint-plugin-jsdoc";
import importPlugin from "eslint-plugin-import";

export default [
{
ignores: ["dist/**", "node_modules/**"],
},
{
files: ["**/*.ts"],
languageOptions: {
parser: tsParser,
sourceType: "module",
ecmaVersion: 2020,
globals: {
process: "readonly",
__dirname: "readonly",
module: "readonly",
require: "readonly",
Buffer: "readonly",
console: "readonly",
exports: "readonly",
setTimeout: "readonly",
clearTimeout: "readonly",
setInterval: "readonly",
clearInterval: "readonly",
},
},
plugins: {
"@typescript-eslint": ts,
prettier: prettier,
jsdoc: jsdoc,
import: importPlugin,
},
rules: {
...js.configs.recommended.rules,
...ts.configs.recommended.rules,
"import/first": "error",
"prettier/prettier": "error",
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_$" }],
"jsdoc/tag-lines": ["error", "any", { startLines: 1 }],
"jsdoc/check-alignment": "error",
"jsdoc/no-undefined-types": "off",
"jsdoc/check-param-names": "error",
"jsdoc/check-tag-names": "error",
"jsdoc/check-types": "error",
"jsdoc/implements-on-classes": "error",
"jsdoc/require-description": "error",
"jsdoc/require-jsdoc": [
"error",
{
require: {
FunctionDeclaration: true,
MethodDefinition: true,
ClassDeclaration: true,
ArrowFunctionExpression: false,
FunctionExpression: false,
},
},
],
"jsdoc/require-param": "error",
"jsdoc/require-param-description": "error",
"jsdoc/require-param-type": "off",
"jsdoc/require-returns": "error",
"jsdoc/require-returns-description": "error",
"jsdoc/require-returns-type": "off",
"jsdoc/require-hyphen-before-param-description": ["error", "always"],
},
},
];
83 changes: 83 additions & 0 deletions examples/typescript/servers/self-facilitation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { x402Facilitator } from "@x402/core/facilitator";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { toFacilitatorEvmSigner } from "@x402/evm";
import { registerExactEvmScheme } from "@x402/evm/exact/facilitator";
import { ExactEvmScheme as ExactEvmServerScheme } from "@x402/evm/exact/server";
import { config } from "dotenv";
import express from "express";
import { createWalletClient, http, publicActions } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";

config();

if (!process.env.EVM_PRIVATE_KEY) {
console.error("Missing required environment variables");
process.exit(1);
}

const evmAccount = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);

// 1) Build facilitator signer from an on-chain client.
const viemClient = createWalletClient({
account: evmAccount,
chain: baseSepolia,
transport: http(),
}).extend(publicActions);

const evmSigner = toFacilitatorEvmSigner({
address: evmAccount.address,
getCode: viemClient.getCode,
readContract: viemClient.readContract,
verifyTypedData: viemClient.verifyTypedData,
writeContract: viemClient.writeContract,
sendTransaction: viemClient.sendTransaction,
waitForTransactionReceipt: viemClient.waitForTransactionReceipt,
});

// 2) Build an in-process facilitator and register supported scheme/network.
const facilitator = new x402Facilitator();
registerExactEvmScheme(facilitator, {
signer: evmSigner,
networks: "eip155:84532", // Base Sepolia
});

// 3) Use standard express middleware wired to the local facilitator.
const app = express();

app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: [
{
scheme: "exact",
price: "$0.001",
network: "eip155:84532",
payTo: evmAccount.address,
},
],
description: "Weather data",
mimeType: "application/json",
},
},
new x402ResourceServer({
verify: facilitator.verify.bind(facilitator),
settle: facilitator.settle.bind(facilitator),
getSupported: async () => facilitator.getSupported(),
}).register("eip155:84532", new ExactEvmServerScheme()),
),
);

app.get("/weather", (_req, res) => {
res.send({
report: {
weather: "sunny",
temperature: 70,
},
});
});

app.listen(4021, () => {
console.log(`Server listening at http://localhost:${4021}`);
});
35 changes: 35 additions & 0 deletions examples/typescript/servers/self-facilitation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@x402/self-facilitation-server-example",
"private": true,
"type": "module",
"scripts": {
"dev": "tsx index.ts",
"format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"",
"format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"",
"lint": "eslint . --ext .ts --fix",
"lint:check": "eslint . --ext .ts"
},
"dependencies": {
"@x402/core": "workspace:*",
"@x402/evm": "workspace:*",
"@x402/express": "workspace:*",
"dotenv": "^16.4.7",
"express": "^4.18.2",
"viem": "^2.21.54"
},
"devDependencies": {
"@eslint/js": "^9.24.0",
"@types/express": "^5.0.1",
"@types/node": "^22.13.4",
"@typescript-eslint/eslint-plugin": "^8.29.1",
"@typescript-eslint/parser": "^8.29.1",
"eslint": "^9.24.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsdoc": "^50.6.9",
"eslint-plugin-prettier": "^5.2.6",
"prettier": "3.5.2",
"tsup": "^7.2.0",
"tsx": "^4.7.0",
"typescript": "^5.3.0"
}
}
15 changes: 15 additions & 0 deletions examples/typescript/servers/self-facilitation/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"strict": true,
"resolveJsonModule": true,
"baseUrl": ".",
"types": ["node"]
},
"include": ["index.ts"]
}
Loading