@payai/x402-hono-starter 1.0.0 → 1.0.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 +2 -2
- package/template/.env-local +3 -5
- package/template/README.md +163 -59
- package/template/index.ts +36 -16
- package/template/package.json +12 -11
- 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/hono), Apache-2.0,
|
|
2
|
-
commit
|
|
2
|
+
commit 7a83a1d57196b27ff9efe30c994f91eb50f60af5. See LICENSE and upstream LICENSE notices.
|
package/package.json
CHANGED
package/template/.env-local
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
+
EVM_ADDRESS=
|
|
2
|
+
SVM_ADDRESS=
|
|
1
3
|
FACILITATOR_URL=https://facilitator.payai.network
|
|
2
|
-
NETWORK=solana-devnet
|
|
3
|
-
ADDRESS=
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
CDP_API_KEY_ID="Coinbase Developer Platform Key"
|
|
7
|
-
CDP_API_KEY_SECRET="Coinbase Developer Platform Key Secret"
|
|
5
|
+
NETWORK=solana-devnet
|
package/template/README.md
CHANGED
|
@@ -1,36 +1,64 @@
|
|
|
1
|
-
# x402
|
|
1
|
+
# @x402/hono Example Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Hono server demonstrating how to protect API endpoints with a paywall using the `@x402/hono` middleware.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { Hono } from "hono";
|
|
7
|
+
import { paymentMiddleware, x402ResourceServer } from "@x402/hono";
|
|
8
|
+
import { ExactEvmScheme } from "@x402/evm/exact/server";
|
|
9
|
+
import { HTTPFacilitatorClient } from "@x402/core/server";
|
|
10
|
+
|
|
11
|
+
const app = new Hono();
|
|
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", c => c.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
|
|
|
23
53
|
```bash
|
|
24
54
|
cd ../../
|
|
25
|
-
pnpm install
|
|
26
|
-
pnpm build
|
|
55
|
+
pnpm install && pnpm build
|
|
27
56
|
cd servers/hono
|
|
28
57
|
```
|
|
29
58
|
|
|
30
59
|
3. Run the server
|
|
31
60
|
|
|
32
61
|
```bash
|
|
33
|
-
pnpm install
|
|
34
62
|
pnpm dev
|
|
35
63
|
```
|
|
36
64
|
|
|
@@ -43,7 +71,6 @@ You can test the server using one of the example clients:
|
|
|
43
71
|
```bash
|
|
44
72
|
cd ../clients/fetch
|
|
45
73
|
# Ensure .env is setup
|
|
46
|
-
pnpm install
|
|
47
74
|
pnpm dev
|
|
48
75
|
```
|
|
49
76
|
|
|
@@ -52,7 +79,6 @@ pnpm dev
|
|
|
52
79
|
```bash
|
|
53
80
|
cd ../clients/axios
|
|
54
81
|
# Ensure .env is setup
|
|
55
|
-
pnpm install
|
|
56
82
|
pnpm dev
|
|
57
83
|
```
|
|
58
84
|
|
|
@@ -64,45 +90,94 @@ These clients will demonstrate how to:
|
|
|
64
90
|
|
|
65
91
|
## Example Endpoint
|
|
66
92
|
|
|
67
|
-
The server includes a single example endpoint at `/weather` that requires a payment of
|
|
93
|
+
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.
|
|
68
94
|
|
|
69
95
|
## Response Format
|
|
70
96
|
|
|
71
97
|
### Payment Required (402)
|
|
72
98
|
|
|
99
|
+
```
|
|
100
|
+
HTTP/1.1 402 Payment Required
|
|
101
|
+
Content-Type: application/json; charset=utf-8
|
|
102
|
+
PAYMENT-REQUIRED: <base64-encoded JSON>
|
|
103
|
+
|
|
104
|
+
{}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The `PAYMENT-REQUIRED` header contains base64-encoded JSON with the payment requirements.
|
|
108
|
+
Note: `amount` is in atomic units (e.g., 1000 = 0.001 USDC, since USDC has 6 decimals):
|
|
109
|
+
|
|
73
110
|
```json
|
|
74
111
|
{
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
112
|
+
"x402Version": 2,
|
|
113
|
+
"error": "Payment required",
|
|
114
|
+
"resource": {
|
|
115
|
+
"url": "http://localhost:4021/weather",
|
|
116
|
+
"description": "Weather data",
|
|
117
|
+
"mimeType": "application/json"
|
|
118
|
+
},
|
|
119
|
+
"accepts": [
|
|
120
|
+
{
|
|
121
|
+
"scheme": "exact",
|
|
122
|
+
"network": "eip155:84532",
|
|
123
|
+
"amount": "1000",
|
|
124
|
+
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
125
|
+
"payTo": "0x1c47E9C085c2B7458F5b6C16cCBD65A65255a9f6",
|
|
126
|
+
"maxTimeoutSeconds": 300,
|
|
127
|
+
"extra": {
|
|
128
|
+
"name": "USDC",
|
|
129
|
+
"version": "2",
|
|
130
|
+
"resourceUrl": "http://localhost:4021/weather"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"scheme": "exact",
|
|
135
|
+
"network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
136
|
+
"amount": "1000",
|
|
137
|
+
"asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
|
|
138
|
+
"payTo": "FV6JPj6Fy12HG8SYStyHdcecXYmV1oeWERAokrh4GQ1n",
|
|
139
|
+
"maxTimeoutSeconds": 300,
|
|
140
|
+
"extra": {
|
|
141
|
+
"feePayer": "...",
|
|
142
|
+
"resourceUrl": "http://localhost:4021/weather"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
]
|
|
89
146
|
}
|
|
90
147
|
```
|
|
91
148
|
|
|
92
149
|
### Successful Response
|
|
93
150
|
|
|
94
|
-
```
|
|
95
|
-
|
|
151
|
+
```
|
|
152
|
+
HTTP/1.1 200 OK
|
|
153
|
+
Content-Type: application/json; charset=utf-8
|
|
154
|
+
PAYMENT-RESPONSE: <base64-encoded JSON>
|
|
155
|
+
|
|
156
|
+
{"report":{"weather":"sunny","temperature":70}}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The `PAYMENT-RESPONSE` header contains base64-encoded JSON with the settlement details:
|
|
160
|
+
|
|
161
|
+
```json
|
|
96
162
|
{
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
163
|
+
"success": true,
|
|
164
|
+
"transaction": "0x...",
|
|
165
|
+
"network": "eip155:84532",
|
|
166
|
+
"payer": "0x...",
|
|
167
|
+
"requirements": {
|
|
168
|
+
"scheme": "exact",
|
|
169
|
+
"network": "eip155:84532",
|
|
170
|
+
"amount": "1000",
|
|
171
|
+
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
172
|
+
"payTo": "0x...",
|
|
173
|
+
"maxTimeoutSeconds": 300,
|
|
174
|
+
"extra": {
|
|
175
|
+
"name": "USDC",
|
|
176
|
+
"version": "2",
|
|
177
|
+
"resourceUrl": "http://localhost:4021/weather"
|
|
178
|
+
}
|
|
100
179
|
}
|
|
101
180
|
}
|
|
102
|
-
// Headers
|
|
103
|
-
{
|
|
104
|
-
"X-PAYMENT-RESPONSE": "..." // Encoded response object
|
|
105
|
-
}
|
|
106
181
|
```
|
|
107
182
|
|
|
108
183
|
## Extending the Example
|
|
@@ -112,39 +187,68 @@ To add more paid endpoints, follow this pattern:
|
|
|
112
187
|
```typescript
|
|
113
188
|
// First, configure the payment middleware with your routes
|
|
114
189
|
app.use(
|
|
115
|
-
paymentMiddleware(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
amount: "100000",
|
|
124
|
-
asset: {
|
|
125
|
-
address: "0xabc",
|
|
126
|
-
decimals: 18,
|
|
127
|
-
eip712: {
|
|
128
|
-
name: "WETH",
|
|
129
|
-
version: "1",
|
|
130
|
-
},
|
|
190
|
+
paymentMiddleware(
|
|
191
|
+
{
|
|
192
|
+
"GET /your-endpoint": {
|
|
193
|
+
accepts: {
|
|
194
|
+
scheme: "exact",
|
|
195
|
+
price: "$0.10",
|
|
196
|
+
network: "eip155:84532",
|
|
197
|
+
payTo: evmAddress,
|
|
131
198
|
},
|
|
199
|
+
description: "Your endpoint description",
|
|
200
|
+
mimeType: "application/json",
|
|
132
201
|
},
|
|
133
|
-
network,
|
|
134
202
|
},
|
|
135
|
-
|
|
203
|
+
resourceServer,
|
|
204
|
+
),
|
|
136
205
|
);
|
|
137
206
|
|
|
138
207
|
// Then define your routes as normal
|
|
139
|
-
app.get("/your-endpoint", c => {
|
|
208
|
+
app.get("/your-endpoint", (c) => {
|
|
140
209
|
return c.json({
|
|
141
210
|
// Your response data
|
|
142
211
|
});
|
|
143
212
|
});
|
|
213
|
+
```
|
|
144
214
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
215
|
+
**Network identifiers** use [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) format, for example:
|
|
216
|
+
|
|
217
|
+
- `eip155:84532` — Base Sepolia
|
|
218
|
+
- `eip155:8453` — Base Mainnet
|
|
219
|
+
- `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` — Solana Devnet
|
|
220
|
+
- `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` — Solana Mainnet
|
|
221
|
+
|
|
222
|
+
## x402ResourceServer Config
|
|
223
|
+
|
|
224
|
+
The `x402ResourceServer` uses a builder pattern to register payment schemes that declare how payments for each network should be processed:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
const resourceServer = new x402ResourceServer(facilitatorClient)
|
|
228
|
+
.register("eip155:*", new ExactEvmScheme()) // All EVM chains
|
|
229
|
+
.register("solana:*", new ExactSvmScheme()); // All SVM chains
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Facilitator Config
|
|
233
|
+
|
|
234
|
+
The `HTTPFacilitatorClient` connects to a facilitator service that verifies and settles payments on-chain:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
|
|
238
|
+
|
|
239
|
+
// Or use multiple facilitators for redundancy
|
|
240
|
+
const facilitatorClient = [
|
|
241
|
+
new HTTPFacilitatorClient({ url: primaryFacilitatorUrl }),
|
|
242
|
+
new HTTPFacilitatorClient({ url: backupFacilitatorUrl }),
|
|
243
|
+
];
|
|
150
244
|
```
|
|
245
|
+
|
|
246
|
+
## Next Steps
|
|
247
|
+
|
|
248
|
+
See [Advanced Examples](../advanced/) for:
|
|
249
|
+
|
|
250
|
+
- **Bazaar discovery** — make your API discoverable
|
|
251
|
+
- **Dynamic pricing** — price based on request context
|
|
252
|
+
- **Dynamic payTo** — route payments to different recipients
|
|
253
|
+
- **Lifecycle hooks** — custom logic on verify/settle
|
|
254
|
+
- **Custom tokens** — accept payments in custom tokens
|
package/template/index.ts
CHANGED
|
@@ -1,35 +1,53 @@
|
|
|
1
1
|
import { config } from "dotenv";
|
|
2
|
+
import { paymentMiddleware, x402ResourceServer } from "@x402/hono";
|
|
3
|
+
import { ExactEvmScheme } from "@x402/evm/exact/server";
|
|
4
|
+
import { ExactSvmScheme } from "@x402/svm/exact/server";
|
|
5
|
+
import { HTTPFacilitatorClient } from "@x402/core/server";
|
|
2
6
|
import { Hono } from "hono";
|
|
3
7
|
import { serve } from "@hono/node-server";
|
|
4
|
-
import { paymentMiddleware, Network, Resource, SolanaAddress } from "x402-hono";
|
|
5
|
-
|
|
6
8
|
config();
|
|
7
9
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (!facilitatorUrl || !payTo || !network) {
|
|
10
|
+
const evmAddress = process.env.EVM_ADDRESS as `0x${string}`;
|
|
11
|
+
const svmAddress = process.env.SVM_ADDRESS;
|
|
12
|
+
if (!evmAddress || !svmAddress) {
|
|
13
13
|
console.error("Missing required environment variables");
|
|
14
14
|
process.exit(1);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const facilitatorUrl = process.env.FACILITATOR_URL;
|
|
18
|
+
if (!facilitatorUrl) {
|
|
19
|
+
console.error("❌ FACILITATOR_URL environment variable is required");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
const app = new Hono();
|
|
20
25
|
|
|
21
26
|
app.use(
|
|
22
27
|
paymentMiddleware(
|
|
23
|
-
payTo,
|
|
24
28
|
{
|
|
25
|
-
"/weather": {
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
"GET /weather": {
|
|
30
|
+
accepts: [
|
|
31
|
+
{
|
|
32
|
+
scheme: "exact",
|
|
33
|
+
price: "$0.001",
|
|
34
|
+
network: "eip155:84532",
|
|
35
|
+
payTo: evmAddress,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
scheme: "exact",
|
|
39
|
+
price: "$0.001",
|
|
40
|
+
network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
41
|
+
payTo: svmAddress,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
description: "Weather data",
|
|
45
|
+
mimeType: "application/json",
|
|
28
46
|
},
|
|
29
47
|
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
new x402ResourceServer(facilitatorClient)
|
|
49
|
+
.register("eip155:84532", new ExactEvmScheme())
|
|
50
|
+
.register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme()),
|
|
33
51
|
),
|
|
34
52
|
);
|
|
35
53
|
|
|
@@ -46,3 +64,5 @@ serve({
|
|
|
46
64
|
fetch: app.fetch,
|
|
47
65
|
port: 4021,
|
|
48
66
|
});
|
|
67
|
+
|
|
68
|
+
console.log(`Server listening at http://localhost:4021`);
|
package/template/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "hono-server-example",
|
|
2
|
+
"name": "@x402/hono-server-example",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
@@ -10,22 +10,23 @@
|
|
|
10
10
|
"lint:check": "eslint . --ext .ts"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@hono/node-server": "^1.13.8",
|
|
14
|
-
"@x402/hono": "^2.2.0",
|
|
15
13
|
"dotenv": "^16.4.7",
|
|
16
|
-
"hono": "^
|
|
14
|
+
"@hono/node-server": "^1.14.0",
|
|
15
|
+
"hono": "^4.7.1",
|
|
16
|
+
"@x402/hono": "^2.2.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"tsup": "^7.2.0",
|
|
20
|
-
"tsx": "^4.7.0",
|
|
21
|
-
"typescript": "^5.3.0",
|
|
22
19
|
"@eslint/js": "^9.24.0",
|
|
23
|
-
"
|
|
24
|
-
"eslint-plugin-jsdoc": "^50.6.9",
|
|
25
|
-
"eslint-plugin-prettier": "^5.2.6",
|
|
20
|
+
"@types/express": "^5.0.1",
|
|
26
21
|
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
|
27
22
|
"@typescript-eslint/parser": "^8.29.1",
|
|
23
|
+
"eslint": "^9.24.0",
|
|
28
24
|
"eslint-plugin-import": "^2.31.0",
|
|
29
|
-
"
|
|
25
|
+
"eslint-plugin-jsdoc": "^50.6.9",
|
|
26
|
+
"eslint-plugin-prettier": "^5.2.6",
|
|
27
|
+
"prettier": "3.5.2",
|
|
28
|
+
"tsup": "^7.2.0",
|
|
29
|
+
"tsx": "^4.7.0",
|
|
30
|
+
"typescript": "^5.3.0"
|
|
30
31
|
}
|
|
31
32
|
}
|
package/template/tsconfig.json
CHANGED