x402-engineer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +102 -0
- package/README.md +43 -0
- package/dist/cli.cjs +137 -0
- package/package.json +51 -0
- package/skills/stellar-dev/SKILL.md +146 -0
- package/skills/stellar-dev/advanced-patterns.md +188 -0
- package/skills/stellar-dev/api-rpc-horizon.md +521 -0
- package/skills/stellar-dev/common-pitfalls.md +510 -0
- package/skills/stellar-dev/contracts-soroban.md +565 -0
- package/skills/stellar-dev/ecosystem.md +430 -0
- package/skills/stellar-dev/frontend-stellar-sdk.md +651 -0
- package/skills/stellar-dev/resources.md +306 -0
- package/skills/stellar-dev/security.md +491 -0
- package/skills/stellar-dev/standards-reference.md +94 -0
- package/skills/stellar-dev/stellar-assets.md +419 -0
- package/skills/stellar-dev/testing.md +786 -0
- package/skills/stellar-dev/zk-proofs.md +136 -0
- package/skills/x402-add-paywall/SKILL.md +208 -0
- package/skills/x402-add-paywall/references/patterns.md +132 -0
- package/skills/x402-debug/SKILL.md +92 -0
- package/skills/x402-debug/references/checklist.md +146 -0
- package/skills/x402-explain/SKILL.md +136 -0
- package/skills/x402-init/SKILL.md +129 -0
- package/skills/x402-init/templates/env-example.md +17 -0
- package/skills/x402-init/templates/express/config.ts.md +29 -0
- package/skills/x402-init/templates/express/server.ts.md +30 -0
- package/skills/x402-init/templates/fastify/adapter.ts.md +66 -0
- package/skills/x402-init/templates/fastify/config.ts.md +29 -0
- package/skills/x402-init/templates/fastify/server.ts.md +90 -0
- package/skills/x402-init/templates/hono/config.ts.md +29 -0
- package/skills/x402-init/templates/hono/server.ts.md +31 -0
- package/skills/x402-init/templates/next-app-router/config.ts.md +29 -0
- package/skills/x402-init/templates/next-app-router/server.ts.md +31 -0
- package/skills/x402-stellar/SKILL.md +139 -0
- package/skills/x402-stellar/references/api.md +237 -0
- package/skills/x402-stellar/references/patterns.md +276 -0
- package/skills/x402-stellar/references/setup.md +138 -0
- package/skills/x402-stellar/scripts/check-deps.js +218 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# x402 Debug Checklist
|
|
2
|
+
|
|
3
|
+
Comprehensive diagnostic checklist for `/x402:debug`. Run every check in order. Report results using `[PASS]`, `[FAIL]`, or `[WARN]` prefixes.
|
|
4
|
+
|
|
5
|
+
## Category 1: Environment Variables
|
|
6
|
+
|
|
7
|
+
### Check: SERVER_STELLAR_ADDRESS
|
|
8
|
+
- **How:** Read `.env.local` (or `.env`) and check for `SERVER_STELLAR_ADDRESS`
|
|
9
|
+
- **PASS:** Variable is set, value starts with `G`, length is 56 characters
|
|
10
|
+
- Output: `[PASS] SERVER_STELLAR_ADDRESS`
|
|
11
|
+
- **FAIL:** Variable not set or empty
|
|
12
|
+
- Output: `[FAIL] Environment variable SERVER_STELLAR_ADDRESS: Not set`
|
|
13
|
+
- Fix: `Add SERVER_STELLAR_ADDRESS to .env.local (see .env.example)`
|
|
14
|
+
- **WARN:** Variable is set but doesn't start with `G` or length is not 56
|
|
15
|
+
- Output: `[WARN] SERVER_STELLAR_ADDRESS: Value doesn't look like a Stellar public key (expected G... with 56 chars)`
|
|
16
|
+
- Fix: `Verify the key at https://laboratory.stellar.org`
|
|
17
|
+
|
|
18
|
+
### Check: FACILITATOR_URL
|
|
19
|
+
- **How:** Read `.env.local` (or `.env`) and check for `FACILITATOR_URL`
|
|
20
|
+
- **PASS:** Variable is set and starts with `https://`
|
|
21
|
+
- Output: `[PASS] FACILITATOR_URL`
|
|
22
|
+
- **FAIL:** Variable not set or empty
|
|
23
|
+
- Output: `[FAIL] Environment variable FACILITATOR_URL: Not set`
|
|
24
|
+
- Fix: `Add FACILITATOR_URL to .env.local. Testnet: https://channels.openzeppelin.com/x402/testnet`
|
|
25
|
+
- **WARN:** Variable is set but doesn't start with `https://`
|
|
26
|
+
- Output: `[WARN] FACILITATOR_URL: Value doesn't look like a URL`
|
|
27
|
+
- Fix: `Expected format: https://channels.openzeppelin.com/x402/testnet`
|
|
28
|
+
|
|
29
|
+
### Check: FACILITATOR_API_KEY
|
|
30
|
+
- **How:** Read `.env.local` (or `.env`) and check for `FACILITATOR_API_KEY`
|
|
31
|
+
- **PASS:** Variable is set and non-empty
|
|
32
|
+
- Output: `[PASS] FACILITATOR_API_KEY`
|
|
33
|
+
- **FAIL:** Variable not set or empty
|
|
34
|
+
- Output: `[FAIL] Environment variable FACILITATOR_API_KEY: Not set`
|
|
35
|
+
- Fix: `Add FACILITATOR_API_KEY to .env.local (see .env.example). Get a key at: https://channels.openzeppelin.com/testnet/gen`
|
|
36
|
+
|
|
37
|
+
## Category 2: Dependencies
|
|
38
|
+
|
|
39
|
+
### Check: @x402/core installed
|
|
40
|
+
- **How:** Read `package.json` and check `dependencies` for `@x402/core`
|
|
41
|
+
- **PASS:** Package found in dependencies
|
|
42
|
+
- Output: `[PASS] @x402/core: Installed`
|
|
43
|
+
- **FAIL:** Package not found
|
|
44
|
+
- Output: `[FAIL] Dependency @x402/core: Not installed`
|
|
45
|
+
- Fix: `Run npm install @x402/core`
|
|
46
|
+
|
|
47
|
+
### Check: @x402/stellar installed
|
|
48
|
+
- **How:** Read `package.json` and check `dependencies` for `@x402/stellar`
|
|
49
|
+
- **PASS:** Package found in dependencies
|
|
50
|
+
- Output: `[PASS] @x402/stellar: Installed`
|
|
51
|
+
- **FAIL:** Package not found
|
|
52
|
+
- Output: `[FAIL] Dependency @x402/stellar: Not installed`
|
|
53
|
+
- Fix: `Run npm install @x402/stellar`
|
|
54
|
+
|
|
55
|
+
### Check: Framework-specific x402 package
|
|
56
|
+
- **How:** Based on detected framework, check for the correct adapter package
|
|
57
|
+
- Next.js: check for `@x402/next` in dependencies
|
|
58
|
+
- Express: check for `@x402/express` in dependencies
|
|
59
|
+
- Fastify: skip (no official package -- uses @x402/core directly)
|
|
60
|
+
- Hono: check for `@x402/hono` in dependencies
|
|
61
|
+
- **PASS:** Framework adapter package installed (or Fastify which doesn't need one)
|
|
62
|
+
- Output: `[PASS] Framework adapter: {package} installed` or `[PASS] Framework adapter: Fastify uses @x402/core directly (no dedicated package needed)`
|
|
63
|
+
- **FAIL:** Framework adapter package missing
|
|
64
|
+
- Output: `[FAIL] Dependency {package}: Not installed`
|
|
65
|
+
- Fix: `Run npm install {package}`
|
|
66
|
+
|
|
67
|
+
### Check: Dependency versions
|
|
68
|
+
- **How:** Read `node_modules/@x402/core/package.json` for version
|
|
69
|
+
- **PASS:** Version is 2.x
|
|
70
|
+
- Output: `[PASS] @x402/core version: {version}`
|
|
71
|
+
- **WARN:** Version is below 2.0
|
|
72
|
+
- Output: `[WARN] @x402/core version {version}: Consider upgrading to 2.x`
|
|
73
|
+
- Fix: `Run npm install @x402/core@latest`
|
|
74
|
+
|
|
75
|
+
## Category 3: Code Structure
|
|
76
|
+
|
|
77
|
+
### Check: Route config file exists
|
|
78
|
+
- **How:** Use Grep to find files containing `RoutesConfig` import
|
|
79
|
+
- **PASS:** At least one file found
|
|
80
|
+
- Output: `[PASS] Route config: {file path}`
|
|
81
|
+
- **FAIL:** No file found
|
|
82
|
+
- Output: `[FAIL] Route config: No routesConfig file found`
|
|
83
|
+
- Fix: `Run /x402:init to create route configuration`
|
|
84
|
+
|
|
85
|
+
### Check: Server/middleware file exists
|
|
86
|
+
- **How:** Use Grep to find files importing from `@x402/core/server` or `@x402/express` or `@x402/hono` or `@x402/next`
|
|
87
|
+
- **PASS:** At least one file found
|
|
88
|
+
- Output: `[PASS] Server middleware: {file path}`
|
|
89
|
+
- **FAIL:** No file found
|
|
90
|
+
- Output: `[FAIL] Server middleware: No x402 server file found`
|
|
91
|
+
- Fix: `Run /x402:init to scaffold the server middleware`
|
|
92
|
+
|
|
93
|
+
### Check: ExactStellarScheme import path
|
|
94
|
+
- **How:** Use Grep to find `ExactStellarScheme` imports
|
|
95
|
+
- **PASS:** Import path is `@x402/stellar/exact/server`
|
|
96
|
+
- Output: `[PASS] ExactStellarScheme: Correctly imported from server path`
|
|
97
|
+
- **WARN:** Import path is `@x402/stellar/exact/client` (wrong for server-side)
|
|
98
|
+
- Output: `[WARN] ExactStellarScheme: Imported from client path instead of server path`
|
|
99
|
+
- Fix: `Change import to: import { ExactStellarScheme } from "@x402/stellar/exact/server"`
|
|
100
|
+
|
|
101
|
+
### Check: Protected endpoints in routesConfig
|
|
102
|
+
- **How:** Read routesConfig file, count entries (keys matching `"METHOD /path"` pattern)
|
|
103
|
+
- **PASS:** At least one route entry exists
|
|
104
|
+
- Output: `[PASS] Protected endpoints: {count} route(s) configured`
|
|
105
|
+
- **WARN:** routesConfig is empty (no entries)
|
|
106
|
+
- Output: `[WARN] No protected endpoints in routesConfig`
|
|
107
|
+
- Fix: `Run /x402:add-paywall to protect an endpoint`
|
|
108
|
+
|
|
109
|
+
### Check: Price format
|
|
110
|
+
- **How:** Read routesConfig, check that price values start with `$`
|
|
111
|
+
- **PASS:** All prices start with `$`
|
|
112
|
+
- Output: `[PASS] Price format: All prices use $ prefix`
|
|
113
|
+
- **WARN:** Price without `$` prefix found
|
|
114
|
+
- Output: `[WARN] Price format: Found price without $ prefix`
|
|
115
|
+
- Fix: `Use "$0.001" format (dollar sign required)`
|
|
116
|
+
|
|
117
|
+
## Category 4: Framework Detection
|
|
118
|
+
|
|
119
|
+
### Check: Framework detected
|
|
120
|
+
- **How:** Read `package.json` dependencies for `next`, `express`, `fastify`, `hono`
|
|
121
|
+
- **PASS:** Exactly one supported framework detected
|
|
122
|
+
- Output: `[PASS] Framework detected: {name}`
|
|
123
|
+
- **WARN:** Multiple frameworks detected
|
|
124
|
+
- Output: `[WARN] Multiple frameworks detected: {list}. Using {primary} based on priority.`
|
|
125
|
+
- **FAIL:** No supported framework detected
|
|
126
|
+
- Output: `[FAIL] Framework: Could not detect framework from package.json`
|
|
127
|
+
- Fix: `Supported: Next.js, Express, Fastify, Hono`
|
|
128
|
+
|
|
129
|
+
### Check: Adapter matches framework
|
|
130
|
+
- **How:** Cross-check detected framework with imports in server file
|
|
131
|
+
- Next.js should import from `@x402/next` or use `withPayment`/`withX402`
|
|
132
|
+
- Express should import from `@x402/express`
|
|
133
|
+
- Fastify should import from `@x402/core/server` with custom adapter
|
|
134
|
+
- Hono should import from `@x402/hono`
|
|
135
|
+
- **PASS:** Server file imports match detected framework
|
|
136
|
+
- Output: `[PASS] Adapter matches framework: {framework}`
|
|
137
|
+
- **WARN:** Mismatch between framework and adapter imports
|
|
138
|
+
- Output: `[WARN] Framework mismatch: Detected {framework} but server imports from {package}`
|
|
139
|
+
- Fix: `Re-run /x402:init to regenerate correct adapter`
|
|
140
|
+
|
|
141
|
+
## Summary Format
|
|
142
|
+
|
|
143
|
+
After all checks, output a summary line:
|
|
144
|
+
- If any FAIL: `{N} passed, {N} failed, {N} warnings. Fix the FAIL items above to complete your x402 setup.`
|
|
145
|
+
- If no FAIL but has WARN: `{N} passed, {N} warnings. x402 is configured but review the warnings above.`
|
|
146
|
+
- If all PASS: `All checks passed. x402 is ready.`
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: x402-explain
|
|
3
|
+
description: Generate a human-readable explanation of how x402 payment protection is wired in the current project.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
allowed-tools: [Read, Grep, Glob]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /x402:explain
|
|
10
|
+
|
|
11
|
+
> I'll analyze your codebase and explain exactly how x402 payment protection is wired -- which endpoints are protected, what the payment flow looks like, and where everything is configured.
|
|
12
|
+
|
|
13
|
+
## Instructions
|
|
14
|
+
|
|
15
|
+
Output is generated fresh on every invocation -- no caching. Read the codebase live to reflect its current state.
|
|
16
|
+
|
|
17
|
+
### Step 1 -- Check for x402 setup
|
|
18
|
+
|
|
19
|
+
Use Grep to check if `@x402/core` exists in `package.json` dependencies.
|
|
20
|
+
|
|
21
|
+
If NOT found, output exactly:
|
|
22
|
+
|
|
23
|
+
"No x402 payment protection detected in this project. Run /x402:init to get started."
|
|
24
|
+
|
|
25
|
+
Then STOP. Do not proceed to further steps.
|
|
26
|
+
|
|
27
|
+
### Step 2 -- Detect framework
|
|
28
|
+
|
|
29
|
+
Check `package.json` dependencies for:
|
|
30
|
+
- `"next"` -- Next.js
|
|
31
|
+
- `"express"` -- Express
|
|
32
|
+
- `"fastify"` -- Fastify
|
|
33
|
+
- `"hono"` -- Hono
|
|
34
|
+
|
|
35
|
+
If multiple frameworks detected, prefer: Next.js > Express > Fastify > Hono (by ecosystem size).
|
|
36
|
+
|
|
37
|
+
### Step 3 -- Find configuration files
|
|
38
|
+
|
|
39
|
+
- Use Grep to find files containing `RoutesConfig` import -> this is the routesConfig file path
|
|
40
|
+
- Use Grep to find files containing `x402ResourceServer` or `paymentMiddleware` or `withPayment` or `withX402` -> this is the server/middleware file path
|
|
41
|
+
- Use Grep to find files containing `// x402: payment-protected endpoint` -> these are protected route file paths
|
|
42
|
+
- Read `.env.local` (or `.env` as fallback) for env var values. **Mask sensitive values:** show first 4 chars + `...` only. Never output full env var values.
|
|
43
|
+
|
|
44
|
+
### Step 4 -- Read and analyze
|
|
45
|
+
|
|
46
|
+
- Read the routesConfig file and parse route entries
|
|
47
|
+
- For each route entry, extract: HTTP method, path, price, network, payTo address
|
|
48
|
+
- Mask payTo address: show first 4 chars + `...` + last 4 chars
|
|
49
|
+
- Read the server/middleware file to understand the adapter pattern being used
|
|
50
|
+
- Read protected route files (if any) to understand the wrapping pattern
|
|
51
|
+
|
|
52
|
+
### Step 5 -- Generate output
|
|
53
|
+
|
|
54
|
+
Produce the following markdown output. Use these exact section headings:
|
|
55
|
+
|
|
56
|
+
```markdown
|
|
57
|
+
## x402 Payment Protection Overview
|
|
58
|
+
|
|
59
|
+
Framework: {detected framework}
|
|
60
|
+
Adapter: {package name} ({version if detectable from node_modules})
|
|
61
|
+
Network: {network from config, e.g., "stellar:testnet"}
|
|
62
|
+
Receiving address: {first 4 chars}...{last 4 chars}
|
|
63
|
+
|
|
64
|
+
## Protected Endpoints
|
|
65
|
+
|
|
66
|
+
| Method | Path | Price | Asset | Network |
|
|
67
|
+
|--------|------|-------|-------|---------|
|
|
68
|
+
| GET | /api/content | $0.001 | USDC | stellar:testnet |
|
|
69
|
+
| ... | ... | ... | ... | ... |
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If no endpoints in routesConfig, output instead of the table:
|
|
73
|
+
|
|
74
|
+
"No endpoints currently protected. Run /x402:add-paywall to protect an endpoint."
|
|
75
|
+
|
|
76
|
+
```markdown
|
|
77
|
+
## Payment Flow
|
|
78
|
+
|
|
79
|
+
1. **Client sends request** to a protected endpoint (no payment header)
|
|
80
|
+
2. **Server returns HTTP 402** with payment requirements in response headers
|
|
81
|
+
3. **Client signs a payment** using their Stellar wallet (USDC amount from routesConfig)
|
|
82
|
+
4. **Client retries** the same request with `X-Payment` header containing the signed payment
|
|
83
|
+
5. **Facilitator verifies** the payment signature and settles the transaction on Stellar
|
|
84
|
+
6. **Server returns the resource** after facilitator confirms payment
|
|
85
|
+
|
|
86
|
+
## Framework: {name}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Include framework-specific description based on detected framework:
|
|
90
|
+
|
|
91
|
+
**For Next.js:**
|
|
92
|
+
```
|
|
93
|
+
Adapter pattern: Inline handler wrapping with `withPayment()`/`withX402()`
|
|
94
|
+
Each route handler is individually wrapped in its `route.ts` file.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**For Express:**
|
|
98
|
+
```
|
|
99
|
+
Adapter pattern: Global middleware via `paymentMiddleware()` from `@x402/express`
|
|
100
|
+
All routes in routesConfig are automatically protected.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**For Fastify:**
|
|
104
|
+
```
|
|
105
|
+
Adapter pattern: Fastify plugin via custom `x402PaymentPlugin` using `@x402/core`
|
|
106
|
+
All routes in routesConfig are automatically protected via onRequest hook.
|
|
107
|
+
Note: Uses custom FastifyAdapter because @x402/fastify is not yet published on npm.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**For Hono:**
|
|
111
|
+
```
|
|
112
|
+
Adapter pattern: Global middleware via `paymentMiddleware()` from `@x402/hono`
|
|
113
|
+
All routes in routesConfig are automatically protected.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```markdown
|
|
117
|
+
## Configuration Files
|
|
118
|
+
|
|
119
|
+
| File | Purpose |
|
|
120
|
+
|------|---------|
|
|
121
|
+
| {config path} | Route pricing and environment configuration |
|
|
122
|
+
| {server path} | x402 middleware/adapter setup |
|
|
123
|
+
| .env.local | Environment variables (Stellar address, facilitator URL, API key) |
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If Fastify is detected, add an additional row:
|
|
127
|
+
```
|
|
128
|
+
| {adapter path} | HTTPAdapter implementation (Fastify only) |
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Important Notes
|
|
132
|
+
|
|
133
|
+
- The Payment Flow section is always the same 6-step list (from protocol spec, not derived from codebase analysis).
|
|
134
|
+
- Mask all sensitive values: env vars show first 4 chars + `...`, Stellar addresses show first 4 chars + `...` + last 4 chars.
|
|
135
|
+
- Output sections must follow the exact headings above (`## x402 Payment Protection Overview`, `## Protected Endpoints`, `## Payment Flow`, `## Framework: {name}`, `## Configuration Files`).
|
|
136
|
+
- This command is read-only. Do not modify any files. Use only Read, Grep, and Glob tools.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: x402-init
|
|
3
|
+
description: Bootstrap x402 payment protection in a project. Detects framework, installs dependencies, creates config files, and scaffolds adapter.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
argument-hint: "[framework]"
|
|
7
|
+
allowed-tools: [Read, Write, Edit, Bash, Grep, Glob]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# /x402:init
|
|
11
|
+
|
|
12
|
+
> Bootstrap x402 payment protection in your project. I'll detect your framework, install the right packages, and scaffold all config files.
|
|
13
|
+
|
|
14
|
+
## Important Rules
|
|
15
|
+
|
|
16
|
+
- Do NOT install `@x402/fastify` -- it does not exist on npm. For Fastify, use `@x402/core` directly with a custom adapter.
|
|
17
|
+
- Server-side `ExactStellarScheme` import path is always `@x402/stellar/exact/server` (NOT `@x402/stellar/exact/client`).
|
|
18
|
+
- All templates are in this skill's `templates/` directory. Read the template file and use its content as the basis for generated code.
|
|
19
|
+
- Adapt import paths if needed based on project structure (e.g., relative paths based on file placement).
|
|
20
|
+
|
|
21
|
+
## Step 1 -- Check for Existing x402 Setup (Idempotency)
|
|
22
|
+
|
|
23
|
+
Before creating anything, check if x402 is already configured:
|
|
24
|
+
|
|
25
|
+
1. **Check dependencies:** Use Grep to search for `@x402/core` in `package.json` dependencies
|
|
26
|
+
2. **Check route config:** Use Glob to find files, then Grep to search for files containing a `RoutesConfig` import from `@x402/core/server`
|
|
27
|
+
3. **Check env template:** Use Read to check if `.env.example` already contains `SERVER_STELLAR_ADDRESS`
|
|
28
|
+
|
|
29
|
+
**If all 3 exist:**
|
|
30
|
+
Output: `"x402 is already set up. {N} files exist, {N} skipped. Run /x402:debug to verify configuration."`
|
|
31
|
+
STOP -- do not create or modify any files.
|
|
32
|
+
|
|
33
|
+
**If some exist:**
|
|
34
|
+
Report which pieces are already present. Skip those. Only create the missing pieces. Continue to the relevant steps below, skipping steps for components that already exist.
|
|
35
|
+
|
|
36
|
+
## Step 2 -- Detect Framework
|
|
37
|
+
|
|
38
|
+
**If `$ARGUMENTS` is provided** (e.g., `/x402:init express`), use that framework directly. Skip detection.
|
|
39
|
+
|
|
40
|
+
**Otherwise, detect from `package.json`:**
|
|
41
|
+
|
|
42
|
+
1. Read `package.json` in the current working directory
|
|
43
|
+
2. If no `package.json` in the current directory, walk up parent directories to find the nearest one
|
|
44
|
+
3. Check the `dependencies` object (not `devDependencies`) for framework packages
|
|
45
|
+
4. Detection heuristics -- priority: Next.js > Express > Fastify > Hono (if multiple found, use the first match):
|
|
46
|
+
- `"next"` in dependencies -> **Next.js App Router**
|
|
47
|
+
- `"express"` in dependencies -> **Express**
|
|
48
|
+
- `"fastify"` in dependencies -> **Fastify**
|
|
49
|
+
- `"hono"` in dependencies -> **Hono**
|
|
50
|
+
|
|
51
|
+
**Edge cases:**
|
|
52
|
+
- If `@nestjs/core` is found in dependencies, warn: `"NestJS detected, using underlying Express/Fastify adapter"` and detect which underlying framework NestJS uses (check for `@nestjs/platform-express` or `@nestjs/platform-fastify`)
|
|
53
|
+
- If no supported framework is detected, output: `"Could not detect framework from package.json. Supported: Next.js, Express, Fastify, Hono."` and STOP
|
|
54
|
+
|
|
55
|
+
## Step 3 -- Detect Project Structure
|
|
56
|
+
|
|
57
|
+
Determine where to place x402 files based on existing project conventions:
|
|
58
|
+
|
|
59
|
+
1. Check if the project uses a `src/` directory pattern (e.g., `src/lib/`, `src/app/`, or other code under `src/`)
|
|
60
|
+
2. If `src/` exists and contains code files: place x402 files in `src/lib/x402/`
|
|
61
|
+
3. If no `src/` directory: place x402 files in `lib/x402/`
|
|
62
|
+
4. For Next.js specifically: check for `app/` directory to confirm App Router usage
|
|
63
|
+
|
|
64
|
+
Store the base directory (`src/lib` or `lib`) for use in subsequent steps.
|
|
65
|
+
|
|
66
|
+
## Step 4 -- Install Dependencies
|
|
67
|
+
|
|
68
|
+
Output: `"Installing @x402/core and @x402/stellar..."`
|
|
69
|
+
|
|
70
|
+
Run the framework-specific install command via Bash:
|
|
71
|
+
|
|
72
|
+
| Framework | Install Command |
|
|
73
|
+
|-----------|----------------|
|
|
74
|
+
| Next.js | `npm install @x402/next @x402/core @x402/stellar` |
|
|
75
|
+
| Express | `npm install @x402/express @x402/core @x402/stellar` |
|
|
76
|
+
| Fastify | `npm install @x402/core @x402/stellar` |
|
|
77
|
+
| Hono | `npm install @x402/hono @x402/core @x402/stellar` |
|
|
78
|
+
|
|
79
|
+
**Fastify note:** Do NOT install `@x402/fastify` -- it does not exist on npm. Only `@x402/core` and `@x402/stellar` are needed. The custom adapter code is scaffolded in Step 6.
|
|
80
|
+
|
|
81
|
+
## Step 5 -- Scaffold Config Files
|
|
82
|
+
|
|
83
|
+
1. Read the appropriate config template: `templates/{framework}/config.ts.md`
|
|
84
|
+
- Where `{framework}` is `next-app-router`, `express`, `fastify`, or `hono`
|
|
85
|
+
2. Extract the TypeScript code from the template's fenced code block
|
|
86
|
+
3. Write to `{base_dir}/x402/config.ts` (where `base_dir` was determined in Step 3)
|
|
87
|
+
|
|
88
|
+
4. Read `templates/env-example.md`
|
|
89
|
+
5. Extract the env content from the template's fenced code block
|
|
90
|
+
6. Check if `.env.example` already exists at the project root:
|
|
91
|
+
- If it does NOT exist: create `.env.example` with the template content
|
|
92
|
+
- If it DOES exist: check if it already contains `SERVER_STELLAR_ADDRESS`. If not, append the x402 section to the existing file
|
|
93
|
+
7. Output: `"Created .env.example with required variables. Copy to .env.local and fill in your values."`
|
|
94
|
+
|
|
95
|
+
## Step 6 -- Scaffold Server/Middleware Files
|
|
96
|
+
|
|
97
|
+
1. Read the appropriate server template: `templates/{framework}/server.ts.md`
|
|
98
|
+
2. Extract the TypeScript code from the template's fenced code block
|
|
99
|
+
3. Write to `{base_dir}/x402/server.ts`
|
|
100
|
+
|
|
101
|
+
**For Fastify only -- also scaffold the adapter:**
|
|
102
|
+
4. Read `templates/fastify/adapter.ts.md`
|
|
103
|
+
5. Extract the TypeScript code from the template's fenced code block
|
|
104
|
+
6. Write to `{base_dir}/x402/adapter.ts`
|
|
105
|
+
|
|
106
|
+
## Step 7 -- Summary
|
|
107
|
+
|
|
108
|
+
Count the files created during this run and output:
|
|
109
|
+
|
|
110
|
+
`"x402 initialized. Created {N} files: {file list}"`
|
|
111
|
+
|
|
112
|
+
List each file with its relative path from the project root, for example:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
x402 initialized. Created 3 files:
|
|
116
|
+
- lib/x402/config.ts
|
|
117
|
+
- lib/x402/server.ts
|
|
118
|
+
- .env.example
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For Fastify projects, the adapter file is also listed:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
x402 initialized. Created 4 files:
|
|
125
|
+
- lib/x402/config.ts
|
|
126
|
+
- lib/x402/server.ts
|
|
127
|
+
- lib/x402/adapter.ts
|
|
128
|
+
- .env.example
|
|
129
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# .env.example Template
|
|
2
|
+
|
|
3
|
+
```env
|
|
4
|
+
# x402 Payment Configuration
|
|
5
|
+
# See: https://docs.x402.org/getting-started/quickstart-for-sellers
|
|
6
|
+
|
|
7
|
+
# Stellar public key (G...) that receives USDC payments
|
|
8
|
+
SERVER_STELLAR_ADDRESS=
|
|
9
|
+
|
|
10
|
+
# OpenZeppelin facilitator endpoint
|
|
11
|
+
# Testnet: https://channels.openzeppelin.com/x402/testnet
|
|
12
|
+
# Mainnet: https://channels.openzeppelin.com/x402/mainnet
|
|
13
|
+
FACILITATOR_URL=https://channels.openzeppelin.com/x402/testnet
|
|
14
|
+
|
|
15
|
+
# API key from https://channels.openzeppelin.com/testnet/gen
|
|
16
|
+
FACILITATOR_API_KEY=
|
|
17
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Route Config Template
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import type { RoutesConfig } from "@x402/core/server";
|
|
5
|
+
|
|
6
|
+
export const SERVER_ADDRESS = process.env.SERVER_STELLAR_ADDRESS!;
|
|
7
|
+
export const FACILITATOR_URL =
|
|
8
|
+
process.env.FACILITATOR_URL ||
|
|
9
|
+
"https://channels.openzeppelin.com/x402/testnet";
|
|
10
|
+
export const FACILITATOR_API_KEY = process.env.FACILITATOR_API_KEY!;
|
|
11
|
+
|
|
12
|
+
export const PRICE = "$0.001";
|
|
13
|
+
export const NETWORK = "stellar:testnet";
|
|
14
|
+
|
|
15
|
+
export const routesConfig: RoutesConfig = {
|
|
16
|
+
// "GET /api/example": {
|
|
17
|
+
// accepts: [
|
|
18
|
+
// {
|
|
19
|
+
// scheme: "exact",
|
|
20
|
+
// price: PRICE,
|
|
21
|
+
// network: NETWORK,
|
|
22
|
+
// payTo: SERVER_ADDRESS,
|
|
23
|
+
// },
|
|
24
|
+
// ],
|
|
25
|
+
// description: "Example protected endpoint",
|
|
26
|
+
// mimeType: "application/json",
|
|
27
|
+
// },
|
|
28
|
+
};
|
|
29
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Express Server Template
|
|
2
|
+
|
|
3
|
+
Uses the official `@x402/express` package with `paymentMiddleware`.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
|
|
7
|
+
import { ExactStellarScheme } from "@x402/stellar/exact/server";
|
|
8
|
+
import { HTTPFacilitatorClient } from "@x402/core/server";
|
|
9
|
+
import {
|
|
10
|
+
routesConfig,
|
|
11
|
+
FACILITATOR_URL,
|
|
12
|
+
FACILITATOR_API_KEY,
|
|
13
|
+
NETWORK,
|
|
14
|
+
} from "./config";
|
|
15
|
+
|
|
16
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
17
|
+
url: FACILITATOR_URL,
|
|
18
|
+
createAuthHeaders: async () => {
|
|
19
|
+
const headers = { Authorization: `Bearer ${FACILITATOR_API_KEY}` };
|
|
20
|
+
return { verify: headers, settle: headers, supported: headers };
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const resourceServer = new x402ResourceServer(facilitatorClient).register(
|
|
25
|
+
NETWORK,
|
|
26
|
+
new ExactStellarScheme()
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export const x402Middleware = paymentMiddleware(routesConfig, resourceServer);
|
|
30
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Fastify HTTPAdapter Template
|
|
2
|
+
|
|
3
|
+
Custom adapter implementing `HTTPAdapter` from `@x402/core/server`.
|
|
4
|
+
Required because `@x402/fastify` is not published on npm.
|
|
5
|
+
|
|
6
|
+
```typescript
|
|
7
|
+
import type { HTTPAdapter } from "@x402/core/server";
|
|
8
|
+
import type { FastifyRequest } from "fastify";
|
|
9
|
+
|
|
10
|
+
export class FastifyAdapter implements HTTPAdapter {
|
|
11
|
+
constructor(private req: FastifyRequest) {}
|
|
12
|
+
|
|
13
|
+
getHeader(name: string): string | undefined {
|
|
14
|
+
const value = this.req.headers[name.toLowerCase()];
|
|
15
|
+
return Array.isArray(value) ? value[0] : value ?? undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getMethod(): string {
|
|
19
|
+
return this.req.method;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getPath(): string {
|
|
23
|
+
return this.req.url.split("?")[0];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getUrl(): string {
|
|
27
|
+
return `${this.req.protocol}://${this.req.hostname}${this.req.url}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getAcceptHeader(): string {
|
|
31
|
+
return this.req.headers.accept ?? "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getUserAgent(): string {
|
|
35
|
+
return this.req.headers["user-agent"] ?? "";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getQueryParams(): Record<string, string | string[]> {
|
|
39
|
+
const url = new URL(this.getUrl());
|
|
40
|
+
const params: Record<string, string | string[]> = {};
|
|
41
|
+
url.searchParams.forEach((value, key) => {
|
|
42
|
+
const existing = params[key];
|
|
43
|
+
if (existing) {
|
|
44
|
+
params[key] = Array.isArray(existing)
|
|
45
|
+
? [...existing, value]
|
|
46
|
+
: [existing, value];
|
|
47
|
+
} else {
|
|
48
|
+
params[key] = value;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return params;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getQueryParam(name: string): string | string[] | undefined {
|
|
55
|
+
const url = new URL(this.getUrl());
|
|
56
|
+
const values = url.searchParams.getAll(name);
|
|
57
|
+
if (values.length === 0) return undefined;
|
|
58
|
+
if (values.length === 1) return values[0];
|
|
59
|
+
return values;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getBody(): unknown {
|
|
63
|
+
return this.req.body;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Route Config Template
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import type { RoutesConfig } from "@x402/core/server";
|
|
5
|
+
|
|
6
|
+
export const SERVER_ADDRESS = process.env.SERVER_STELLAR_ADDRESS!;
|
|
7
|
+
export const FACILITATOR_URL =
|
|
8
|
+
process.env.FACILITATOR_URL ||
|
|
9
|
+
"https://channels.openzeppelin.com/x402/testnet";
|
|
10
|
+
export const FACILITATOR_API_KEY = process.env.FACILITATOR_API_KEY!;
|
|
11
|
+
|
|
12
|
+
export const PRICE = "$0.001";
|
|
13
|
+
export const NETWORK = "stellar:testnet";
|
|
14
|
+
|
|
15
|
+
export const routesConfig: RoutesConfig = {
|
|
16
|
+
// "GET /api/example": {
|
|
17
|
+
// accepts: [
|
|
18
|
+
// {
|
|
19
|
+
// scheme: "exact",
|
|
20
|
+
// price: PRICE,
|
|
21
|
+
// network: NETWORK,
|
|
22
|
+
// payTo: SERVER_ADDRESS,
|
|
23
|
+
// },
|
|
24
|
+
// ],
|
|
25
|
+
// description: "Example protected endpoint",
|
|
26
|
+
// mimeType: "application/json",
|
|
27
|
+
// },
|
|
28
|
+
};
|
|
29
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Fastify Server Template
|
|
2
|
+
|
|
3
|
+
Uses `@x402/core` directly because `@x402/fastify` is not published on npm.
|
|
4
|
+
Implements a custom Fastify plugin with the `FastifyAdapter` class.
|
|
5
|
+
|
|
6
|
+
```typescript
|
|
7
|
+
import {
|
|
8
|
+
HTTPFacilitatorClient,
|
|
9
|
+
x402ResourceServer,
|
|
10
|
+
x402HTTPResourceServer,
|
|
11
|
+
} from "@x402/core/server";
|
|
12
|
+
import { ExactStellarScheme } from "@x402/stellar/exact/server";
|
|
13
|
+
import { FastifyAdapter } from "./adapter";
|
|
14
|
+
import {
|
|
15
|
+
routesConfig,
|
|
16
|
+
FACILITATOR_URL,
|
|
17
|
+
FACILITATOR_API_KEY,
|
|
18
|
+
NETWORK,
|
|
19
|
+
} from "./config";
|
|
20
|
+
import type { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
|
|
21
|
+
|
|
22
|
+
let httpServer: x402HTTPResourceServer | null = null;
|
|
23
|
+
let initPromise: Promise<void> | null = null;
|
|
24
|
+
|
|
25
|
+
function getHTTPServer(): x402HTTPResourceServer {
|
|
26
|
+
if (!httpServer) {
|
|
27
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
28
|
+
url: FACILITATOR_URL,
|
|
29
|
+
createAuthHeaders: async () => {
|
|
30
|
+
const headers = { Authorization: `Bearer ${FACILITATOR_API_KEY}` };
|
|
31
|
+
return { verify: headers, settle: headers, supported: headers };
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
const resourceServer = new x402ResourceServer(facilitatorClient).register(
|
|
35
|
+
NETWORK,
|
|
36
|
+
new ExactStellarScheme()
|
|
37
|
+
);
|
|
38
|
+
httpServer = new x402HTTPResourceServer(resourceServer, routesConfig);
|
|
39
|
+
}
|
|
40
|
+
return httpServer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function ensureInitialized(): Promise<x402HTTPResourceServer> {
|
|
44
|
+
const server = getHTTPServer();
|
|
45
|
+
if (!initPromise) {
|
|
46
|
+
initPromise = server.initialize().catch((err) => {
|
|
47
|
+
initPromise = null;
|
|
48
|
+
throw err;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
await initPromise;
|
|
52
|
+
return server;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function x402PaymentPlugin(fastify: FastifyInstance) {
|
|
56
|
+
fastify.addHook(
|
|
57
|
+
"onRequest",
|
|
58
|
+
async (req: FastifyRequest, reply: FastifyReply) => {
|
|
59
|
+
const server = await ensureInitialized();
|
|
60
|
+
const adapter = new FastifyAdapter(req);
|
|
61
|
+
const context = {
|
|
62
|
+
adapter,
|
|
63
|
+
path: adapter.getPath(),
|
|
64
|
+
method: adapter.getMethod(),
|
|
65
|
+
paymentHeader:
|
|
66
|
+
adapter.getHeader("x-payment") ??
|
|
67
|
+
adapter.getHeader("x-payment-signature"),
|
|
68
|
+
};
|
|
69
|
+
const result = await server.processHTTPRequest(context);
|
|
70
|
+
|
|
71
|
+
if (result.type === "payment-error") {
|
|
72
|
+
const { status, headers, body, isHtml } = result.response;
|
|
73
|
+
const responseBody = isHtml
|
|
74
|
+
? (body as string)
|
|
75
|
+
: JSON.stringify(body ?? {});
|
|
76
|
+
const contentType = isHtml ? "text/html" : "application/json";
|
|
77
|
+
reply
|
|
78
|
+
.status(status)
|
|
79
|
+
.headers({ ...headers, "Content-Type": contentType })
|
|
80
|
+
.send(responseBody);
|
|
81
|
+
return reply;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (result.type === "payment-verified") {
|
|
85
|
+
(req as any).__x402Result = result;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|