Skip to content

Commit a910838

Browse files
committed
feat: add x402 payment-gated proxy template
1 parent 29bda2e commit a910838

22 files changed

+25420
-7254
lines changed

playwright-tests/utils/template-server.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface Template {
2727
port: number;
2828
devCommand: string;
2929
framework: "vite" | "next" | "astro" | "remix" | "wrangler" | "react-router";
30+
healthCheckPath?: string; // Optional custom path for server readiness check
3031
}
3132

3233
export class TemplateServerManager {
@@ -131,12 +132,16 @@ export class TemplateServerManager {
131132
port = 5173;
132133
}
133134

135+
// Check for custom health check path in cloudflare config
136+
const healthCheckPath = packageJson.cloudflare?.healthCheckPath;
137+
134138
return {
135139
name,
136140
path,
137141
port,
138142
devCommand: scripts.dev,
139143
framework,
144+
healthCheckPath,
140145
};
141146
}
142147

@@ -173,7 +178,10 @@ export class TemplateServerManager {
173178

174179
// Wait for server to be ready
175180
const baseUrl = `http://localhost:${template.port}`;
176-
await this.waitForServer(baseUrl, 30000); // 30 second timeout
181+
const healthCheckUrl = template.healthCheckPath
182+
? `${baseUrl}${template.healthCheckPath}`
183+
: baseUrl;
184+
await this.waitForServer(healthCheckUrl, 30000); // 30 second timeout
177185

178186
console.log(`Server for ${template.name} ready at ${baseUrl}`);
179187
return baseUrl;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { test, expect } from "./fixtures";
2+
3+
test.describe("x402 Payment-Gated Proxy Template", () => {
4+
test("health endpoint is accessible without payment", async ({
5+
page,
6+
templateUrl,
7+
}) => {
8+
const response = await page.goto(`${templateUrl}/__x402/health`);
9+
expect(response?.status()).toBe(200);
10+
11+
const body = await page.textContent("body");
12+
expect(body).toContain("ok");
13+
expect(body).toContain("x402-proxy");
14+
expect(body).toContain("This endpoint is always public");
15+
});
16+
17+
test("health endpoint returns valid JSON", async ({ page, templateUrl }) => {
18+
await page.goto(`${templateUrl}/__x402/health`);
19+
const content = await page.textContent("body");
20+
const json = JSON.parse(content || "{}");
21+
22+
expect(json.status).toBe("ok");
23+
expect(json.proxy).toBe("x402-proxy");
24+
expect(json.message).toBe("This endpoint is always public");
25+
expect(json.timestamp).toBeGreaterThan(0);
26+
});
27+
28+
test("protected endpoint returns 402 payment required", async ({
29+
page,
30+
templateUrl,
31+
}) => {
32+
const response = await page.goto(`${templateUrl}/__x402/protected`);
33+
expect(response?.status()).toBe(402);
34+
});
35+
36+
test("402 response includes payment configuration details", async ({
37+
page,
38+
templateUrl,
39+
}) => {
40+
await page.goto(`${templateUrl}/__x402/protected`);
41+
const body = await page.textContent("body");
42+
43+
// Verify response contains payment-related information
44+
expect(body).toContain("X-PAYMENT");
45+
expect(body).toContain("base-sepolia");
46+
expect(body).toContain("10000"); // Payment amount in smallest unit
47+
});
48+
49+
test("402 response includes proper payment structure", async ({
50+
page,
51+
templateUrl,
52+
}) => {
53+
// Use page.request to get raw JSON response (avoids browser rendering)
54+
const response = await page.request.get(`${templateUrl}/__x402/protected`);
55+
expect(response.status()).toBe(402);
56+
57+
const json = await response.json();
58+
59+
// Verify x402 payment structure
60+
expect(json.error).toBe("X-PAYMENT header is required");
61+
expect(json.accepts).toBeDefined();
62+
expect(Array.isArray(json.accepts)).toBe(true);
63+
expect(json.accepts.length).toBeGreaterThan(0);
64+
expect(json.x402Version).toBe(1);
65+
66+
// Verify payment details
67+
const paymentOption = json.accepts[0];
68+
expect(paymentOption.network).toBe("base-sepolia");
69+
expect(paymentOption.resource).toContain("/__x402/protected");
70+
expect(paymentOption.description).toContain("Access to premium content");
71+
});
72+
});

pnpm-lock.yaml

Lines changed: 12373 additions & 7253 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

templates.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@
7777
},
7878
"react-router-postgres-ssr-template": {
7979
"package_json_hash": "77ba35e81ddae51a6e8a6a7406e0a1f022de4069"
80+
},
81+
"x402-proxy-template": {
82+
"package_json_hash": "5f786630dea32450178ac9c92a0ea350f9b75b14"
8083
}
8184
}
8285
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Environment variables for local development
2+
# Copy this file to .dev.vars and fill in your values
3+
4+
# JWT secret for signing authentication tokens
5+
# Generate a secure random secret with:
6+
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
7+
JWT_SECRET=your-secret-key-here

x402-proxy-template/.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# prod
2+
dist/
3+
4+
# dev
5+
.yarn/
6+
!.yarn/releases
7+
.vscode/*
8+
!.vscode/launch.json
9+
!.vscode/*.code-snippets
10+
.idea/workspace.xml
11+
.idea/usage.statistics.xml
12+
.idea/shelf
13+
14+
# deps
15+
node_modules/
16+
.wrangler
17+
18+
# env
19+
.env
20+
.env.production
21+
.dev.vars
22+
23+
# logs
24+
logs/
25+
*.log
26+
npm-debug.log*
27+
yarn-debug.log*
28+
yarn-error.log*
29+
pnpm-debug.log*
30+
lerna-debug.log*
31+
32+
# misc
33+
.DS_Store
34+
35+
.dev.vars*
36+
!.dev.vars.example
37+
!.env.example

x402-proxy-template/.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
registry=https://registry.npmjs.org/
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build outputs
5+
dist/
6+
.wrangler/
7+
8+
# Generated files
9+
worker-configuration.d.ts
10+
11+
# Logs
12+
*.log
13+
14+
# Environment files
15+
.env
16+
.dev.vars
17+
18+
# Lock files
19+
package-lock.json

x402-proxy-template/.prettierrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"semi": true,
3+
"singleQuote": false,
4+
"tabWidth": 2,
5+
"trailingComma": "es5",
6+
"printWidth": 80,
7+
"arrowParens": "always"
8+
}

0 commit comments

Comments
 (0)