Skip to content

Commit 11d91cb

Browse files
committed
feat: add x402 payment-gated proxy template
A transparent proxy template with payment-gated routes using the x402 protocol and stateless JWT cookie authentication. Includes built-in test endpoints for health checks and payment flow demonstration.
1 parent c0a40a3 commit 11d91cb

22 files changed

+16947
-514
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+
});

0 commit comments

Comments
 (0)