@payai/x402-express-starter 0.1.3 → 1.3.1
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 +1 -1
- package/package.json +12 -3
- package/template/.env-local +3 -7
- package/template/README.md +158 -57
- package/template/index.ts +32 -37
- package/template/package.json +7 -3
- package/template/tsconfig.json +8 -1
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
|
|
2
|
+
commit 26d92af44ed49297803816b58ce0d4fdc2c3d599. 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": "
|
|
3
|
+
"version": "1.3.1",
|
|
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": "
|
|
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
|
}
|
package/template/.env-local
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
package/template/README.md
CHANGED
|
@@ -1,34 +1,62 @@
|
|
|
1
|
-
# x402
|
|
1
|
+
# @x402/express Example Server
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
-
|
|
10
|
-
-
|
|
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
|
|
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
|
|
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
|
-
"
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
"
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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,
|
|
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
|
|
7
|
-
const
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
network: "base-sepolia",
|
|
42
|
+
],
|
|
43
|
+
description: "Weather data",
|
|
44
|
+
mimeType: "application/json",
|
|
44
45
|
},
|
|
45
46
|
},
|
|
46
|
-
|
|
47
|
-
|
|
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
|
});
|
package/template/package.json
CHANGED
|
@@ -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
|
|
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
|
+
}
|
package/template/tsconfig.json
CHANGED