@payai/x402-express-starter 0.1.2 → 1.3.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/NOTICE CHANGED
@@ -1,2 +1,2 @@
1
1
  This package includes portions derived from coinbase/x402 (examples/typescript/servers/express), Apache-2.0,
2
- commit 4ec9c29df28a54e5ec151d4a8000935e96f4c3ee. See LICENSE and upstream LICENSE notices.
2
+ commit 3f9cdc54d687333359841c44c326fcb5b39b2828. See LICENSE and upstream LICENSE notices.
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@payai/x402-express-starter",
3
- "version": "0.1.2",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://registry.npmjs.org"
8
+ },
5
9
  "description": "Create an x402 Express server in less than 2 minutes!",
6
10
  "type": "module",
7
11
  "bin": {
@@ -30,9 +34,14 @@
30
34
  "author": "PayAI",
31
35
  "repository": {
32
36
  "type": "git",
33
- "url": "https://github.com/PayAINetwork/x402-express-starter"
37
+ "url": "git+https://github.com/PayAINetwork/x402-express-starter.git"
34
38
  },
35
39
  "config": {
36
- "x402ExpressVersion": "0.6.5"
40
+ "x402ExpressVersion": "2.2.0",
41
+ "x402FetchVersion": "1.1.0",
42
+ "x402EvmVersion": "2.2.0",
43
+ "x402SvmVersion": "2.2.0",
44
+ "x402CoreVersion": "2.2.0",
45
+ "x402ExtensionsVersion": "2.2.0"
37
46
  }
38
47
  }
@@ -1,7 +1,3 @@
1
- FACILITATOR_URL=https://facilitator.payai.network
2
- NETWORK=base-sepolia
3
- ADDRESS=
4
-
5
- # required if using the Base mainnet facilitator
6
- CDP_API_KEY_ID="Coinbase Developer Platform Key"
7
- CDP_API_KEY_SECRET="Coinbase Developer Platform Key Secret"
1
+ EVM_ADDRESS=
2
+ SVM_ADDRESS=
3
+ FACILITATOR_URL=https://facilitator.payai.network
@@ -1,34 +1,62 @@
1
- # x402-express Example Server
1
+ # @x402/express Example Server
2
2
 
3
- This is an example Express.js server that demonstrates how to use the `x402-express` middleware to implement paywall functionality in your API endpoints.
3
+ Express.js server demonstrating how to protect API endpoints with a paywall using the `@x402/express` middleware.
4
+
5
+ ```typescript
6
+ import express from "express";
7
+ import { paymentMiddleware, x402ResourceServer } from "@x402/express";
8
+ import { ExactEvmScheme } from "@x402/evm/exact/server";
9
+ import { HTTPFacilitatorClient } from "@x402/core/server";
10
+
11
+ const app = express();
12
+
13
+ app.use(
14
+ paymentMiddleware(
15
+ {
16
+ "GET /weather": {
17
+ accepts: { scheme: "exact", price: "$0.001", network: "eip155:84532", payTo: evmAddress },
18
+ description: "Weather data",
19
+ mimeType: "application/json",
20
+ },
21
+ },
22
+ new x402ResourceServer(new HTTPFacilitatorClient({ url: facilitatorUrl }))
23
+ .register("eip155:84532", new ExactEvmScheme()),
24
+ ),
25
+ );
26
+
27
+ app.get("/weather", (req, res) => res.json({ weather: "sunny", temperature: 70 }));
28
+ ```
4
29
 
5
30
  ## Prerequisites
6
31
 
7
32
  - Node.js v20+ (install via [nvm](https://github.com/nvm-sh/nvm))
8
33
  - pnpm v10 (install via [pnpm.io/installation](https://pnpm.io/installation))
9
- - A valid Ethereum address for receiving payments
10
- - Coinbase Developer Platform API Key & Secret (if accepting payments on Base mainnet)
11
- -- Get them here [https://portal.cdp.coinbase.com/projects](https://portal.cdp.coinbase.com/projects)
34
+ - Valid EVM and SVM addresses for receiving payments
35
+ - URL of a facilitator supporting the desired payment network, see [facilitator list](https://www.x402.org/ecosystem?category=facilitators)
12
36
 
13
37
  ## Setup
14
38
 
15
- 1. Copy `.env-local` to `.env` and add your Ethereum address to receive payments:
39
+ 1. Copy `.env-local` to `.env`:
16
40
 
17
41
  ```bash
18
42
  cp .env-local .env
19
43
  ```
20
44
 
45
+ and fill required environment variables:
46
+
47
+ - `FACILITATOR_URL` - Facilitator endpoint URL
48
+ - `EVM_ADDRESS` - Ethereum address to receive payments
49
+ - `SVM_ADDRESS` - Solana address to receive payments
50
+
21
51
  2. Install and build all packages from the typescript examples root:
22
52
  ```bash
23
53
  cd ../../
24
- pnpm install
25
- pnpm build
54
+ pnpm install && pnpm build
26
55
  cd servers/express
27
56
  ```
28
57
 
29
58
  3. Run the server
30
59
  ```bash
31
- pnpm install
32
60
  pnpm dev
33
61
  ```
34
62
 
@@ -40,7 +68,6 @@ You can test the server using one of the example clients:
40
68
  ```bash
41
69
  cd ../clients/fetch
42
70
  # Ensure .env is setup
43
- pnpm install
44
71
  pnpm dev
45
72
  ```
46
73
 
@@ -48,7 +75,6 @@ pnpm dev
48
75
  ```bash
49
76
  cd ../clients/axios
50
77
  # Ensure .env is setup
51
- pnpm install
52
78
  pnpm dev
53
79
  ```
54
80
 
@@ -59,43 +85,94 @@ These clients will demonstrate how to:
59
85
 
60
86
  ## Example Endpoint
61
87
 
62
- The server includes a single example endpoint at `/weather` that requires a payment of $0.001 to access. The endpoint returns a simple weather report.
88
+ The server includes a single example endpoint at `/weather` that requires a payment of 0.001 USDC on Base Sepolia or Solana Devnet to access. The endpoint returns a simple weather report.
63
89
 
64
90
  ## Response Format
65
91
 
66
92
  ### Payment Required (402)
93
+
94
+ ```
95
+ HTTP/1.1 402 Payment Required
96
+ Content-Type: application/json; charset=utf-8
97
+ PAYMENT-REQUIRED: <base64-encoded JSON>
98
+
99
+ {}
100
+ ```
101
+
102
+ The `PAYMENT-REQUIRED` header contains base64-encoded JSON with the payment requirements.
103
+ Note: `amount` is in atomic units (e.g., 1000 = 0.001 USDC, since USDC has 6 decimals):
104
+
67
105
  ```json
68
106
  {
69
- "error": "X-PAYMENT header is required",
70
- "paymentRequirements": {
71
- "scheme": "exact",
72
- "network": "base",
73
- "maxAmountRequired": "1000",
74
- "resource": "http://localhost:4021/weather",
75
- "description": "",
76
- "mimeType": "",
77
- "payTo": "0xYourAddress",
78
- "maxTimeoutSeconds": 60,
79
- "asset": "0x...",
80
- "outputSchema": null,
81
- "extra": null
82
- }
107
+ "x402Version": 2,
108
+ "error": "Payment required",
109
+ "resource": {
110
+ "url": "http://localhost:4021/weather",
111
+ "description": "Weather data",
112
+ "mimeType": "application/json"
113
+ },
114
+ "accepts": [
115
+ {
116
+ "scheme": "exact",
117
+ "network": "eip155:84532",
118
+ "amount": "1000",
119
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
120
+ "payTo": "0x1c47E9C085c2B7458F5b6C16cCBD65A65255a9f6",
121
+ "maxTimeoutSeconds": 300,
122
+ "extra": {
123
+ "name": "USDC",
124
+ "version": "2",
125
+ "resourceUrl": "http://localhost:4021/weather"
126
+ }
127
+ },
128
+ {
129
+ "scheme": "exact",
130
+ "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
131
+ "amount": "1000",
132
+ "asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
133
+ "payTo": "FV6JPj6Fy12HG8SYStyHdcecXYmV1oeWERAokrh4GQ1n",
134
+ "maxTimeoutSeconds": 300,
135
+ "extra": {
136
+ "feePayer": "...",
137
+ "resourceUrl": "http://localhost:4021/weather"
138
+ }
139
+ }
140
+ ]
83
141
  }
84
142
  ```
85
143
 
86
144
  ### Successful Response
87
- ```ts
88
- // Body
145
+
146
+ ```
147
+ HTTP/1.1 200 OK
148
+ Content-Type: application/json; charset=utf-8
149
+ PAYMENT-RESPONSE: <base64-encoded JSON>
150
+
151
+ {"report":{"weather":"sunny","temperature":70}}
152
+ ```
153
+
154
+ The `PAYMENT-RESPONSE` header contains base64-encoded JSON with the settlement details:
155
+
156
+ ```json
89
157
  {
90
- "report": {
91
- "weather": "sunny",
92
- "temperature": 70
158
+ "success": true,
159
+ "transaction": "0x...",
160
+ "network": "eip155:84532",
161
+ "payer": "0x...",
162
+ "requirements": {
163
+ "scheme": "exact",
164
+ "network": "eip155:84532",
165
+ "amount": "1000",
166
+ "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
167
+ "payTo": "0x...",
168
+ "maxTimeoutSeconds": 300,
169
+ "extra": {
170
+ "name": "USDC",
171
+ "version": "2",
172
+ "resourceUrl": "http://localhost:4021/weather"
173
+ }
93
174
  }
94
175
  }
95
- // Headers
96
- {
97
- "X-PAYMENT-RESPONSE": "..." // Encoded response object
98
- }
99
176
  ```
100
177
 
101
178
  ## Extending the Example
@@ -106,28 +183,19 @@ To add more paid endpoints, follow this pattern:
106
183
  // First, configure the payment middleware with your routes
107
184
  app.use(
108
185
  paymentMiddleware(
109
- payTo,
110
186
  {
111
- // Define your routes and their payment requirements
112
187
  "GET /your-endpoint": {
113
- price: "$0.10",
114
- network: "base-sepolia",
115
- },
116
- "/premium/*": {
117
- price: {
118
- amount: "100000",
119
- asset: {
120
- address: "0xabc",
121
- decimals: 18,
122
- eip712: {
123
- name: "WETH",
124
- version: "1",
125
- },
126
- },
188
+ accepts: {
189
+ scheme: "exact",
190
+ price: "$0.10",
191
+ network: "eip155:84532",
192
+ payTo: evmAddress,
127
193
  },
128
- network: "base-sepolia",
194
+ description: "Your endpoint description",
195
+ mimeType: "application/json",
129
196
  },
130
197
  },
198
+ resourceServer,
131
199
  ),
132
200
  );
133
201
 
@@ -137,10 +205,43 @@ app.get("/your-endpoint", (req, res) => {
137
205
  // Your response data
138
206
  });
139
207
  });
208
+ ```
140
209
 
141
- app.get("/premium/content", (req, res) => {
142
- res.json({
143
- content: "This is premium content",
144
- });
145
- });
210
+ **Network identifiers** use [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) format, for example:
211
+ - `eip155:84532` — Base Sepolia
212
+ - `eip155:8453` Base Mainnet
213
+ - `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` — Solana Devnet
214
+ - `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` — Solana Mainnet
215
+
216
+ ## x402ResourceServer Config
217
+
218
+ The `x402ResourceServer` uses a builder pattern to register payment schemes that declare how payments for each network should be processed:
219
+
220
+ ```typescript
221
+ const resourceServer = new x402ResourceServer(facilitatorClient)
222
+ .register("eip155:*", new ExactEvmScheme()) // All EVM chains
223
+ .register("solana:*", new ExactSvmScheme()) // All SVM chains
146
224
  ```
225
+
226
+ ## Facilitator Config
227
+
228
+ The `HTTPFacilitatorClient` connects to a facilitator service that verifies and settles payments on-chain:
229
+
230
+ ```typescript
231
+ const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
232
+
233
+ // Or use multiple facilitators for redundancy
234
+ const facilitatorClient = [
235
+ new HTTPFacilitatorClient({ url: primaryFacilitatorUrl }),
236
+ new HTTPFacilitatorClient({ url: backupFacilitatorUrl }),
237
+ ];
238
+ ```
239
+
240
+ ## Next Steps
241
+
242
+ See [Advanced Examples](../advanced/) for:
243
+ - **Bazaar discovery** — make your API discoverable
244
+ - **Dynamic pricing** — price based on request context
245
+ - **Dynamic payTo** — route payments to different recipients
246
+ - **Lifecycle hooks** — custom logic on verify/settle
247
+ - **Custom tokens** — accept payments in custom tokens
package/template/index.ts CHANGED
@@ -1,51 +1,52 @@
1
1
  import { config } from "dotenv";
2
2
  import express from "express";
3
- import { paymentMiddleware, Resource, type SolanaAddress } from "x402-express";
3
+ import { paymentMiddleware, x402ResourceServer } from "@x402/express";
4
+ import { ExactEvmScheme } from "@x402/evm/exact/server";
5
+ import { ExactSvmScheme } from "@x402/svm/exact/server";
6
+ import { HTTPFacilitatorClient } from "@x402/core/server";
4
7
  config();
5
8
 
6
- const facilitatorUrl = process.env.FACILITATOR_URL as Resource;
7
- const payTo = process.env.ADDRESS as `0x${string}` | SolanaAddress;
8
-
9
- if (!facilitatorUrl || !payTo) {
9
+ const evmAddress = process.env.EVM_ADDRESS as `0x${string}`;
10
+ const svmAddress = process.env.SVM_ADDRESS;
11
+ if (!evmAddress || !svmAddress) {
10
12
  console.error("Missing required environment variables");
11
13
  process.exit(1);
12
14
  }
13
15
 
16
+ const facilitatorUrl = process.env.FACILITATOR_URL;
17
+ if (!facilitatorUrl) {
18
+ console.error("❌ FACILITATOR_URL environment variable is required");
19
+ process.exit(1);
20
+ }
21
+ const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
22
+
14
23
  const app = express();
15
24
 
16
25
  app.use(
17
26
  paymentMiddleware(
18
- payTo,
19
27
  {
20
28
  "GET /weather": {
21
- // USDC amount in dollars
22
- price: "$0.001",
23
- // network: "base" // uncomment for Base mainnet
24
- // network: "solana" // uncomment for Solana mainnet
25
- network: "base-sepolia",
26
- },
27
- "/premium/*": {
28
- // Define atomic amounts in any EIP-3009 token
29
- price: {
30
- amount: "100000",
31
- asset: {
32
- address: "0xabc",
33
- decimals: 18,
34
- // omit eip712 for Solana
35
- eip712: {
36
- name: "WETH",
37
- version: "1",
38
- },
29
+ accepts: [
30
+ {
31
+ scheme: "exact",
32
+ price: "$0.001",
33
+ network: "eip155:84532",
34
+ payTo: evmAddress,
35
+ },
36
+ {
37
+ scheme: "exact",
38
+ price: "$0.001",
39
+ network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
40
+ payTo: svmAddress,
39
41
  },
40
- },
41
- // network: "base" // uncomment for Base mainnet
42
- // network: "solana" // uncomment for Solana mainnet
43
- network: "base-sepolia",
42
+ ],
43
+ description: "Weather data",
44
+ mimeType: "application/json",
44
45
  },
45
46
  },
46
- {
47
- url: facilitatorUrl,
48
- },
47
+ new x402ResourceServer(facilitatorClient)
48
+ .register("eip155:84532", new ExactEvmScheme())
49
+ .register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme()),
49
50
  ),
50
51
  );
51
52
 
@@ -58,12 +59,6 @@ app.get("/weather", (req, res) => {
58
59
  });
59
60
  });
60
61
 
61
- app.get("/premium/content", (req, res) => {
62
- res.send({
63
- content: "This is premium content",
64
- });
65
- });
66
-
67
62
  app.listen(4021, () => {
68
63
  console.log(`Server listening at http://localhost:${4021}`);
69
64
  });
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "express-server-example",
2
+ "name": "@x402/express-server-example",
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "scripts": {
@@ -12,7 +12,11 @@
12
12
  "dependencies": {
13
13
  "dotenv": "^16.4.7",
14
14
  "express": "^4.18.2",
15
- "x402-express": "^0.6.5"
15
+ "@x402/express": "^2.2.0",
16
+ "@x402/evm": "^2.2.0",
17
+ "@x402/svm": "^2.2.0",
18
+ "@x402/core": "^2.2.0",
19
+ "@x402/extensions": "^2.2.0"
16
20
  },
17
21
  "devDependencies": {
18
22
  "@eslint/js": "^9.24.0",
@@ -28,4 +32,4 @@
28
32
  "tsx": "^4.7.0",
29
33
  "typescript": "^5.3.0"
30
34
  }
31
- }
35
+ }
@@ -11,5 +11,12 @@
11
11
  "baseUrl": ".",
12
12
  "types": ["node"]
13
13
  },
14
- "include": ["index.ts"]
14
+ "include": [
15
+ "index.ts",
16
+ "bazaar.ts",
17
+ "custom-money-definition.ts",
18
+ "dynamic-pay-to.ts",
19
+ "dynamic-price.ts",
20
+ "hooks.ts"
21
+ ]
15
22
  }