@goplausible/openclaw-algorand-plugin 0.5.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/LICENSE +21 -0
- package/README.md +112 -0
- package/index.ts +361 -0
- package/lib/mcp-servers.ts +14 -0
- package/lib/x402-fetch.ts +213 -0
- package/memory/algorand-plugin.md +82 -0
- package/openclaw.plugin.json +30 -0
- package/package.json +41 -0
- package/setup.ts +80 -0
- package/skills/algorand-development/SKILL.md +90 -0
- package/skills/algorand-development/references/build-smart-contracts-reference.md +79 -0
- package/skills/algorand-development/references/build-smart-contracts.md +52 -0
- package/skills/algorand-development/references/create-project-reference.md +86 -0
- package/skills/algorand-development/references/create-project.md +89 -0
- package/skills/algorand-development/references/implement-arc-standards-arc32-arc56.md +396 -0
- package/skills/algorand-development/references/implement-arc-standards-arc4.md +265 -0
- package/skills/algorand-development/references/implement-arc-standards.md +92 -0
- package/skills/algorand-development/references/search-algorand-examples-reference.md +119 -0
- package/skills/algorand-development/references/search-algorand-examples.md +89 -0
- package/skills/algorand-development/references/troubleshoot-errors-contract.md +373 -0
- package/skills/algorand-development/references/troubleshoot-errors-transaction.md +599 -0
- package/skills/algorand-development/references/troubleshoot-errors.md +105 -0
- package/skills/algorand-development/references/use-algokit-cli-reference.md +228 -0
- package/skills/algorand-development/references/use-algokit-cli.md +64 -0
- package/skills/algorand-interaction/SKILL.md +223 -0
- package/skills/algorand-interaction/references/algorand-mcp.md +743 -0
- package/skills/algorand-interaction/references/examples-algorand-mcp.md +647 -0
- package/skills/algorand-python/SKILL.md +95 -0
- package/skills/algorand-python/references/build-smart-contracts-decorators.md +413 -0
- package/skills/algorand-python/references/build-smart-contracts-reference.md +55 -0
- package/skills/algorand-python/references/build-smart-contracts-storage.md +452 -0
- package/skills/algorand-python/references/build-smart-contracts-transactions.md +445 -0
- package/skills/algorand-python/references/build-smart-contracts-types.md +438 -0
- package/skills/algorand-python/references/build-smart-contracts.md +82 -0
- package/skills/algorand-python/references/create-project-reference.md +55 -0
- package/skills/algorand-python/references/create-project.md +75 -0
- package/skills/algorand-python/references/implement-arc-standards-arc32-arc56.md +101 -0
- package/skills/algorand-python/references/implement-arc-standards-arc4.md +154 -0
- package/skills/algorand-python/references/implement-arc-standards.md +39 -0
- package/skills/algorand-python/references/troubleshoot-errors-contract.md +355 -0
- package/skills/algorand-python/references/troubleshoot-errors-transaction.md +430 -0
- package/skills/algorand-python/references/troubleshoot-errors.md +46 -0
- package/skills/algorand-python/references/use-algokit-utils-reference.md +350 -0
- package/skills/algorand-python/references/use-algokit-utils.md +76 -0
- package/skills/algorand-typescript/SKILL.md +131 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-beta.md +448 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-tealscript.md +487 -0
- package/skills/algorand-typescript/references/algorand-ts-migration.md +102 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-methods-and-abi.md +134 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-reference.md +58 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-storage.md +154 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-transactions.md +187 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-types-and-values.md +150 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax.md +84 -0
- package/skills/algorand-typescript/references/build-smart-contracts-reference.md +52 -0
- package/skills/algorand-typescript/references/build-smart-contracts.md +74 -0
- package/skills/algorand-typescript/references/call-smart-contracts-reference.md +237 -0
- package/skills/algorand-typescript/references/call-smart-contracts.md +183 -0
- package/skills/algorand-typescript/references/create-project-reference.md +53 -0
- package/skills/algorand-typescript/references/create-project.md +86 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-examples.md +527 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-reference.md +412 -0
- package/skills/algorand-typescript/references/deploy-react-frontend.md +239 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc32-arc56.md +73 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc4.md +126 -0
- package/skills/algorand-typescript/references/implement-arc-standards.md +44 -0
- package/skills/algorand-typescript/references/test-smart-contracts-examples.md +245 -0
- package/skills/algorand-typescript/references/test-smart-contracts-unit-tests.md +147 -0
- package/skills/algorand-typescript/references/test-smart-contracts.md +127 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-contract.md +296 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-transaction.md +438 -0
- package/skills/algorand-typescript/references/troubleshoot-errors.md +56 -0
- package/skills/algorand-typescript/references/use-algokit-utils-reference.md +342 -0
- package/skills/algorand-typescript/references/use-algokit-utils.md +74 -0
- package/skills/algorand-x402-python/SKILL.md +113 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-examples.md +469 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-reference.md +313 -0
- package/skills/algorand-x402-python/references/create-python-x402-client.md +207 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-examples.md +924 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-reference.md +629 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator.md +408 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-examples.md +703 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-reference.md +303 -0
- package/skills/algorand-x402-python/references/create-python-x402-server.md +221 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-examples.md +605 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-reference.md +315 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python.md +167 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-examples.md +554 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-reference.md +278 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm.md +166 -0
- package/skills/algorand-x402-typescript/SKILL.md +129 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-examples.md +879 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-reference.md +371 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client.md +236 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-examples.md +875 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-reference.md +461 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator.md +270 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-examples.md +1181 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-reference.md +360 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs.md +251 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-examples.md +870 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall.md +281 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-examples.md +1135 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-reference.md +382 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server.md +216 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-examples.md +616 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript.md +232 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-examples.md +1417 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-reference.md +504 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm.md +158 -0
|
@@ -0,0 +1,1181 @@
|
|
|
1
|
+
# @x402-avm/next Examples
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @x402-avm/next @x402-avm/avm @x402-avm/core
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
For paywall UI support:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @x402-avm/next @x402-avm/avm @x402-avm/core @x402-avm/paywall
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## paymentProxyFromConfig
|
|
18
|
+
|
|
19
|
+
### middleware.ts
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { NextRequest } from "next/server";
|
|
23
|
+
import { paymentProxyFromConfig } from "@x402-avm/next";
|
|
24
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
25
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
26
|
+
|
|
27
|
+
const PAY_TO = process.env.PAY_TO!;
|
|
28
|
+
|
|
29
|
+
const routes = {
|
|
30
|
+
"GET /api/weather": {
|
|
31
|
+
accepts: {
|
|
32
|
+
scheme: "exact",
|
|
33
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
34
|
+
payTo: PAY_TO,
|
|
35
|
+
price: "$0.01",
|
|
36
|
+
},
|
|
37
|
+
description: "Weather data",
|
|
38
|
+
},
|
|
39
|
+
"GET /api/premium/*": {
|
|
40
|
+
accepts: {
|
|
41
|
+
scheme: "exact",
|
|
42
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
43
|
+
payTo: PAY_TO,
|
|
44
|
+
price: "$0.10",
|
|
45
|
+
},
|
|
46
|
+
description: "Premium API endpoints",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
51
|
+
|
|
52
|
+
const proxy = paymentProxyFromConfig(routes, facilitatorClient);
|
|
53
|
+
|
|
54
|
+
export async function middleware(request: NextRequest) {
|
|
55
|
+
return proxy(request);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const config = {
|
|
59
|
+
matcher: "/api/:path*",
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### app/api/weather/route.ts
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { NextResponse } from "next/server";
|
|
67
|
+
|
|
68
|
+
export async function GET() {
|
|
69
|
+
return NextResponse.json({
|
|
70
|
+
temperature: 72,
|
|
71
|
+
condition: "sunny",
|
|
72
|
+
city: "San Francisco",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### app/api/premium/data/route.ts
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { NextResponse } from "next/server";
|
|
81
|
+
|
|
82
|
+
export async function GET() {
|
|
83
|
+
return NextResponse.json({
|
|
84
|
+
data: "premium content",
|
|
85
|
+
tier: "gold",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## paymentProxy
|
|
93
|
+
|
|
94
|
+
### middleware.ts
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { NextRequest } from "next/server";
|
|
98
|
+
import { paymentProxy, x402ResourceServer } from "@x402-avm/next";
|
|
99
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
100
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
101
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
102
|
+
|
|
103
|
+
const PAY_TO = process.env.PAY_TO!;
|
|
104
|
+
|
|
105
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
106
|
+
url: process.env.FACILITATOR_URL || "https://x402.org/facilitator",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
110
|
+
registerExactAvmScheme(server);
|
|
111
|
+
|
|
112
|
+
const routes = {
|
|
113
|
+
"GET /api/weather/:city": {
|
|
114
|
+
accepts: {
|
|
115
|
+
scheme: "exact",
|
|
116
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
117
|
+
payTo: PAY_TO,
|
|
118
|
+
price: "$0.01",
|
|
119
|
+
},
|
|
120
|
+
description: "City weather data",
|
|
121
|
+
},
|
|
122
|
+
"POST /api/generate": {
|
|
123
|
+
accepts: {
|
|
124
|
+
scheme: "exact",
|
|
125
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
126
|
+
payTo: PAY_TO,
|
|
127
|
+
price: "$1.00",
|
|
128
|
+
},
|
|
129
|
+
description: "AI content generation",
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const proxy = paymentProxy(routes, server);
|
|
134
|
+
|
|
135
|
+
export async function middleware(request: NextRequest) {
|
|
136
|
+
return proxy(request);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const config = {
|
|
140
|
+
matcher: "/api/:path*",
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## paymentProxyFromHTTPServer
|
|
147
|
+
|
|
148
|
+
### middleware.ts
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { NextRequest } from "next/server";
|
|
152
|
+
import {
|
|
153
|
+
paymentProxyFromHTTPServer,
|
|
154
|
+
x402ResourceServer,
|
|
155
|
+
x402HTTPResourceServer,
|
|
156
|
+
} from "@x402-avm/next";
|
|
157
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
158
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
159
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
160
|
+
|
|
161
|
+
const PAY_TO = process.env.PAY_TO!;
|
|
162
|
+
|
|
163
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
164
|
+
const resourceServer = new x402ResourceServer(facilitatorClient);
|
|
165
|
+
registerExactAvmScheme(resourceServer);
|
|
166
|
+
|
|
167
|
+
const routes = {
|
|
168
|
+
"GET /api/data": {
|
|
169
|
+
accepts: {
|
|
170
|
+
scheme: "exact",
|
|
171
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
172
|
+
payTo: PAY_TO,
|
|
173
|
+
price: "$0.05",
|
|
174
|
+
},
|
|
175
|
+
description: "Protected data",
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const httpServer = new x402HTTPResourceServer(resourceServer, routes);
|
|
180
|
+
|
|
181
|
+
httpServer.onProtectedRequest(async (context, routeConfig) => {
|
|
182
|
+
const authToken = context.adapter.getHeader("authorization");
|
|
183
|
+
|
|
184
|
+
if (authToken && await verifyToken(authToken)) {
|
|
185
|
+
return { grantAccess: true };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const clientIP = context.adapter.getHeader("x-forwarded-for");
|
|
189
|
+
if (clientIP && isRateLimited(clientIP)) {
|
|
190
|
+
return { abort: true, reason: "Rate limited" };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return undefined;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const proxy = paymentProxyFromHTTPServer(httpServer);
|
|
197
|
+
|
|
198
|
+
export async function middleware(request: NextRequest) {
|
|
199
|
+
return proxy(request);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const config = {
|
|
203
|
+
matcher: "/api/:path*",
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
async function verifyToken(token: string): Promise<boolean> {
|
|
207
|
+
return token.startsWith("Bearer valid-");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isRateLimited(ip: string): boolean {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## withX402
|
|
218
|
+
|
|
219
|
+
### app/api/weather/route.ts
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
223
|
+
import { withX402, x402ResourceServer } from "@x402-avm/next";
|
|
224
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
225
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
226
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
227
|
+
|
|
228
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
229
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
230
|
+
registerExactAvmScheme(server);
|
|
231
|
+
|
|
232
|
+
const routeConfig = {
|
|
233
|
+
accepts: {
|
|
234
|
+
scheme: "exact",
|
|
235
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
236
|
+
payTo: process.env.PAY_TO!,
|
|
237
|
+
price: "$0.01",
|
|
238
|
+
},
|
|
239
|
+
description: "Weather data",
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
async function handler(request: NextRequest) {
|
|
243
|
+
return NextResponse.json({
|
|
244
|
+
temperature: 72,
|
|
245
|
+
condition: "sunny",
|
|
246
|
+
city: "San Francisco",
|
|
247
|
+
timestamp: new Date().toISOString(),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const GET = withX402(handler, routeConfig, server);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Shared Config Module (lib/x402.ts)
|
|
257
|
+
|
|
258
|
+
### lib/x402.ts
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { x402ResourceServer } from "@x402-avm/next";
|
|
262
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
263
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
264
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
265
|
+
|
|
266
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
267
|
+
url: process.env.FACILITATOR_URL || "https://x402.org/facilitator",
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
export const x402Server = new x402ResourceServer(facilitatorClient);
|
|
271
|
+
registerExactAvmScheme(x402Server);
|
|
272
|
+
|
|
273
|
+
export const PAY_TO = process.env.PAY_TO!;
|
|
274
|
+
export const NETWORK = ALGORAND_TESTNET_CAIP2;
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### app/api/weather/route.ts
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
281
|
+
import { withX402 } from "@x402-avm/next";
|
|
282
|
+
import { x402Server, PAY_TO, NETWORK } from "@/lib/x402";
|
|
283
|
+
|
|
284
|
+
const handler = async (request: NextRequest) => {
|
|
285
|
+
return NextResponse.json({
|
|
286
|
+
temperature: 72,
|
|
287
|
+
condition: "sunny",
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export const GET = withX402(handler, {
|
|
292
|
+
accepts: {
|
|
293
|
+
scheme: "exact",
|
|
294
|
+
network: NETWORK,
|
|
295
|
+
payTo: PAY_TO,
|
|
296
|
+
price: "$0.01",
|
|
297
|
+
},
|
|
298
|
+
description: "Weather data",
|
|
299
|
+
}, x402Server);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### app/api/premium/route.ts
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
306
|
+
import { withX402 } from "@x402-avm/next";
|
|
307
|
+
import { x402Server, PAY_TO, NETWORK } from "@/lib/x402";
|
|
308
|
+
|
|
309
|
+
const handler = async (request: NextRequest) => {
|
|
310
|
+
return NextResponse.json({
|
|
311
|
+
data: "premium content",
|
|
312
|
+
tier: "gold",
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export const GET = withX402(handler, {
|
|
317
|
+
accepts: {
|
|
318
|
+
scheme: "exact",
|
|
319
|
+
network: NETWORK,
|
|
320
|
+
payTo: PAY_TO,
|
|
321
|
+
price: "$0.50",
|
|
322
|
+
},
|
|
323
|
+
description: "Premium content",
|
|
324
|
+
}, x402Server);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### app/api/generate/route.ts
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
331
|
+
import { withX402 } from "@x402-avm/next";
|
|
332
|
+
import { x402Server, PAY_TO, NETWORK } from "@/lib/x402";
|
|
333
|
+
|
|
334
|
+
const handler = async (request: NextRequest) => {
|
|
335
|
+
const { prompt } = await request.json();
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const result = await generateContent(prompt);
|
|
339
|
+
return NextResponse.json({ prompt, result });
|
|
340
|
+
} catch (error) {
|
|
341
|
+
return NextResponse.json(
|
|
342
|
+
{ error: "Generation failed" },
|
|
343
|
+
{ status: 500 },
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
export const POST = withX402(handler, {
|
|
349
|
+
accepts: {
|
|
350
|
+
scheme: "exact",
|
|
351
|
+
network: NETWORK,
|
|
352
|
+
payTo: PAY_TO,
|
|
353
|
+
price: "$1.00",
|
|
354
|
+
},
|
|
355
|
+
description: "AI generation",
|
|
356
|
+
}, x402Server);
|
|
357
|
+
|
|
358
|
+
async function generateContent(prompt: string): Promise<string> {
|
|
359
|
+
return "Generated content...";
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## withX402FromHTTPServer
|
|
366
|
+
|
|
367
|
+
### app/api/data/route.ts
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
371
|
+
import {
|
|
372
|
+
withX402FromHTTPServer,
|
|
373
|
+
x402ResourceServer,
|
|
374
|
+
x402HTTPResourceServer,
|
|
375
|
+
} from "@x402-avm/next";
|
|
376
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
377
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
378
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
379
|
+
|
|
380
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
381
|
+
const resourceServer = new x402ResourceServer(facilitatorClient);
|
|
382
|
+
registerExactAvmScheme(resourceServer);
|
|
383
|
+
|
|
384
|
+
const routeConfig = {
|
|
385
|
+
accepts: {
|
|
386
|
+
scheme: "exact",
|
|
387
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
388
|
+
payTo: process.env.PAY_TO!,
|
|
389
|
+
price: "$0.05",
|
|
390
|
+
},
|
|
391
|
+
description: "Protected data",
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const httpServer = new x402HTTPResourceServer(resourceServer, {
|
|
395
|
+
"*": routeConfig,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
httpServer.onProtectedRequest(async (context, config) => {
|
|
399
|
+
console.log(`Payment request: ${context.method} ${context.path}`);
|
|
400
|
+
return undefined;
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const handler = async (request: NextRequest) => {
|
|
404
|
+
return NextResponse.json({ data: "protected content" });
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
export const GET = withX402FromHTTPServer(handler, httpServer);
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Protected Page Route (HTML)
|
|
413
|
+
|
|
414
|
+
### middleware.ts
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import { NextRequest } from "next/server";
|
|
418
|
+
import { paymentProxyFromConfig } from "@x402-avm/next";
|
|
419
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
420
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
421
|
+
|
|
422
|
+
const routes = {
|
|
423
|
+
"GET /premium-article": {
|
|
424
|
+
accepts: {
|
|
425
|
+
scheme: "exact",
|
|
426
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
427
|
+
payTo: process.env.PAY_TO!,
|
|
428
|
+
price: "$0.25",
|
|
429
|
+
},
|
|
430
|
+
description: "Premium article",
|
|
431
|
+
mimeType: "text/html",
|
|
432
|
+
},
|
|
433
|
+
"GET /api/data/*": {
|
|
434
|
+
accepts: {
|
|
435
|
+
scheme: "exact",
|
|
436
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
437
|
+
payTo: process.env.PAY_TO!,
|
|
438
|
+
price: "$0.01",
|
|
439
|
+
},
|
|
440
|
+
description: "Data API",
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const proxy = paymentProxyFromConfig(routes, new HTTPFacilitatorClient());
|
|
445
|
+
|
|
446
|
+
export async function middleware(request: NextRequest) {
|
|
447
|
+
return proxy(request);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export const config = {
|
|
451
|
+
matcher: ["/premium-article", "/api/data/:path*"],
|
|
452
|
+
};
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### app/premium-article/page.tsx
|
|
456
|
+
|
|
457
|
+
```tsx
|
|
458
|
+
export default function PremiumArticlePage() {
|
|
459
|
+
return (
|
|
460
|
+
<article>
|
|
461
|
+
<h1>Premium Article</h1>
|
|
462
|
+
<p>This content is only accessible after payment.</p>
|
|
463
|
+
<p>Full article text here...</p>
|
|
464
|
+
</article>
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Protecting Multiple HTTP Methods
|
|
472
|
+
|
|
473
|
+
### app/api/resource/route.ts
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
477
|
+
import { withX402 } from "@x402-avm/next";
|
|
478
|
+
import { x402Server, PAY_TO, NETWORK } from "@/lib/x402";
|
|
479
|
+
|
|
480
|
+
const routeConfig = {
|
|
481
|
+
accepts: {
|
|
482
|
+
scheme: "exact",
|
|
483
|
+
network: NETWORK,
|
|
484
|
+
payTo: PAY_TO,
|
|
485
|
+
price: "$0.05",
|
|
486
|
+
},
|
|
487
|
+
description: "Resource CRUD",
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const getHandler = async (request: NextRequest) => {
|
|
491
|
+
return NextResponse.json({ resource: "data", method: "GET" });
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const postHandler = async (request: NextRequest) => {
|
|
495
|
+
const body = await request.json();
|
|
496
|
+
return NextResponse.json({ created: true, resource: body });
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const putHandler = async (request: NextRequest) => {
|
|
500
|
+
const body = await request.json();
|
|
501
|
+
return NextResponse.json({ updated: true, resource: body });
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
export const GET = withX402(getHandler, routeConfig, x402Server);
|
|
505
|
+
export const POST = withX402(postHandler, routeConfig, x402Server);
|
|
506
|
+
export const PUT = withX402(putHandler, {
|
|
507
|
+
accepts: {
|
|
508
|
+
scheme: "exact",
|
|
509
|
+
network: NETWORK,
|
|
510
|
+
payTo: PAY_TO,
|
|
511
|
+
price: "$0.10",
|
|
512
|
+
},
|
|
513
|
+
description: "Resource update",
|
|
514
|
+
}, x402Server);
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Conditional Payment (Free Tier + Paid Tier)
|
|
520
|
+
|
|
521
|
+
### app/api/search/route.ts
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
525
|
+
import {
|
|
526
|
+
withX402FromHTTPServer,
|
|
527
|
+
x402ResourceServer,
|
|
528
|
+
x402HTTPResourceServer,
|
|
529
|
+
} from "@x402-avm/next";
|
|
530
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
531
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
532
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
533
|
+
|
|
534
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
535
|
+
const resourceServer = new x402ResourceServer(facilitatorClient);
|
|
536
|
+
registerExactAvmScheme(resourceServer);
|
|
537
|
+
|
|
538
|
+
const httpServer = new x402HTTPResourceServer(resourceServer, {
|
|
539
|
+
"*": {
|
|
540
|
+
accepts: {
|
|
541
|
+
scheme: "exact",
|
|
542
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
543
|
+
payTo: process.env.PAY_TO!,
|
|
544
|
+
price: "$0.05",
|
|
545
|
+
},
|
|
546
|
+
description: "Search API",
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
httpServer.onProtectedRequest(async (context) => {
|
|
551
|
+
const limit = context.adapter.getQueryParam("limit");
|
|
552
|
+
if (limit && parseInt(limit) <= 10) {
|
|
553
|
+
return { grantAccess: true };
|
|
554
|
+
}
|
|
555
|
+
return undefined;
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const handler = async (request: NextRequest) => {
|
|
559
|
+
const { searchParams } = new URL(request.url);
|
|
560
|
+
const query = searchParams.get("q") || "";
|
|
561
|
+
const limit = parseInt(searchParams.get("limit") || "100");
|
|
562
|
+
|
|
563
|
+
const results = await performSearch(query, limit);
|
|
564
|
+
return NextResponse.json({ query, results, count: results.length });
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
export const GET = withX402FromHTTPServer(handler, httpServer);
|
|
568
|
+
|
|
569
|
+
async function performSearch(query: string, limit: number) {
|
|
570
|
+
return Array.from({ length: Math.min(limit, 50) }, (_, i) => ({
|
|
571
|
+
id: i,
|
|
572
|
+
title: `Result ${i + 1} for "${query}"`,
|
|
573
|
+
}));
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Dynamic Routes with Payment
|
|
580
|
+
|
|
581
|
+
### middleware.ts
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
import { NextRequest } from "next/server";
|
|
585
|
+
import { paymentProxyFromConfig } from "@x402-avm/next";
|
|
586
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
587
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
588
|
+
|
|
589
|
+
const PAY_TO = process.env.PAY_TO!;
|
|
590
|
+
|
|
591
|
+
const routes = {
|
|
592
|
+
"GET /api/users/*": {
|
|
593
|
+
accepts: {
|
|
594
|
+
scheme: "exact",
|
|
595
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
596
|
+
payTo: PAY_TO,
|
|
597
|
+
price: "$0.01",
|
|
598
|
+
},
|
|
599
|
+
description: "User profile data",
|
|
600
|
+
},
|
|
601
|
+
"GET /api/reports/*": {
|
|
602
|
+
accepts: {
|
|
603
|
+
scheme: "exact",
|
|
604
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
605
|
+
payTo: PAY_TO,
|
|
606
|
+
price: "$0.25",
|
|
607
|
+
},
|
|
608
|
+
description: "Monthly report",
|
|
609
|
+
},
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const proxy = paymentProxyFromConfig(routes, new HTTPFacilitatorClient());
|
|
613
|
+
|
|
614
|
+
export async function middleware(request: NextRequest) {
|
|
615
|
+
return proxy(request);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export const config = {
|
|
619
|
+
matcher: ["/api/users/:path*", "/api/reports/:path*"],
|
|
620
|
+
};
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### app/api/users/[id]/route.ts
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
import { NextResponse } from "next/server";
|
|
627
|
+
|
|
628
|
+
export async function GET(
|
|
629
|
+
request: Request,
|
|
630
|
+
{ params }: { params: { id: string } },
|
|
631
|
+
) {
|
|
632
|
+
const user = await fetchUser(params.id);
|
|
633
|
+
if (!user) {
|
|
634
|
+
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
|
635
|
+
}
|
|
636
|
+
return NextResponse.json(user);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async function fetchUser(id: string) {
|
|
640
|
+
return { id, name: "Alice", email: "alice@example.com" };
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### app/api/reports/[year]/[month]/route.ts
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
import { NextResponse } from "next/server";
|
|
648
|
+
|
|
649
|
+
export async function GET(
|
|
650
|
+
request: Request,
|
|
651
|
+
{ params }: { params: { year: string; month: string } },
|
|
652
|
+
) {
|
|
653
|
+
return NextResponse.json({
|
|
654
|
+
year: params.year,
|
|
655
|
+
month: params.month,
|
|
656
|
+
data: "Monthly report data...",
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## Paywall with Proxy Pattern
|
|
664
|
+
|
|
665
|
+
### middleware.ts
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
import { NextRequest } from "next/server";
|
|
669
|
+
import { paymentProxy, x402ResourceServer } from "@x402-avm/next";
|
|
670
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
671
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
672
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
673
|
+
|
|
674
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
675
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
676
|
+
registerExactAvmScheme(server);
|
|
677
|
+
|
|
678
|
+
const routes = {
|
|
679
|
+
"GET /premium/*": {
|
|
680
|
+
accepts: {
|
|
681
|
+
scheme: "exact",
|
|
682
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
683
|
+
payTo: process.env.PAY_TO!,
|
|
684
|
+
price: "$0.25",
|
|
685
|
+
},
|
|
686
|
+
description: "Premium content",
|
|
687
|
+
mimeType: "text/html",
|
|
688
|
+
},
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const paywallConfig = {
|
|
692
|
+
title: "Premium Content",
|
|
693
|
+
description: "Pay with Algorand to unlock this content",
|
|
694
|
+
logoUrl: "/logo.png",
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const proxy = paymentProxy(routes, server, paywallConfig);
|
|
698
|
+
|
|
699
|
+
export async function middleware(request: NextRequest) {
|
|
700
|
+
return proxy(request);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
export const config = {
|
|
704
|
+
matcher: "/premium/:path*",
|
|
705
|
+
};
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
## Custom Paywall HTML
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
const routes = {
|
|
714
|
+
"GET /premium/article": {
|
|
715
|
+
accepts: {
|
|
716
|
+
scheme: "exact",
|
|
717
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
718
|
+
payTo: process.env.PAY_TO!,
|
|
719
|
+
price: "$0.25",
|
|
720
|
+
},
|
|
721
|
+
description: "Premium article",
|
|
722
|
+
customPaywallHtml: `
|
|
723
|
+
<!DOCTYPE html>
|
|
724
|
+
<html>
|
|
725
|
+
<head>
|
|
726
|
+
<title>Payment Required</title>
|
|
727
|
+
<style>
|
|
728
|
+
body { font-family: sans-serif; max-width: 600px; margin: 50px auto; }
|
|
729
|
+
.price { font-size: 2em; color: #6366f1; }
|
|
730
|
+
</style>
|
|
731
|
+
</head>
|
|
732
|
+
<body>
|
|
733
|
+
<h1>Premium Article</h1>
|
|
734
|
+
<p class="price">$0.25</p>
|
|
735
|
+
<p>Pay with your Algorand wallet to read this article.</p>
|
|
736
|
+
<p>Supported wallets: Pera, Defly, Kibisis</p>
|
|
737
|
+
</body>
|
|
738
|
+
</html>
|
|
739
|
+
`,
|
|
740
|
+
},
|
|
741
|
+
};
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
## Unpaid Response Body (for API Clients)
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
const routes = {
|
|
750
|
+
"GET /api/article/:id": {
|
|
751
|
+
accepts: {
|
|
752
|
+
scheme: "exact",
|
|
753
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
754
|
+
payTo: process.env.PAY_TO!,
|
|
755
|
+
price: "$0.10",
|
|
756
|
+
},
|
|
757
|
+
description: "Full article",
|
|
758
|
+
unpaidResponseBody: (context) => ({
|
|
759
|
+
contentType: "application/json",
|
|
760
|
+
body: {
|
|
761
|
+
title: "Article Preview",
|
|
762
|
+
preview: "First paragraph of the article...",
|
|
763
|
+
fullArticle: false,
|
|
764
|
+
paymentRequired: {
|
|
765
|
+
price: "$0.10",
|
|
766
|
+
network: "Algorand Testnet",
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
}),
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
## Multiple Networks (Testnet and Mainnet)
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
import {
|
|
780
|
+
ALGORAND_TESTNET_CAIP2,
|
|
781
|
+
ALGORAND_MAINNET_CAIP2,
|
|
782
|
+
} from "@x402-avm/avm";
|
|
783
|
+
|
|
784
|
+
const routes = {
|
|
785
|
+
"GET /api/data": {
|
|
786
|
+
accepts: [
|
|
787
|
+
{
|
|
788
|
+
scheme: "exact",
|
|
789
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
790
|
+
payTo: "YOUR_TESTNET_ADDRESS",
|
|
791
|
+
price: "$0.01",
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
scheme: "exact",
|
|
795
|
+
network: ALGORAND_MAINNET_CAIP2,
|
|
796
|
+
payTo: "YOUR_MAINNET_ADDRESS",
|
|
797
|
+
price: "$0.01",
|
|
798
|
+
},
|
|
799
|
+
],
|
|
800
|
+
description: "Data endpoint (testnet or mainnet)",
|
|
801
|
+
},
|
|
802
|
+
};
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
---
|
|
806
|
+
|
|
807
|
+
## Cross-Chain: Algorand + EVM
|
|
808
|
+
|
|
809
|
+
```typescript
|
|
810
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
811
|
+
|
|
812
|
+
const routes = {
|
|
813
|
+
"GET /api/data": {
|
|
814
|
+
accepts: [
|
|
815
|
+
{
|
|
816
|
+
scheme: "exact",
|
|
817
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
818
|
+
payTo: "YOUR_ALGORAND_ADDRESS",
|
|
819
|
+
price: "$0.01",
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
scheme: "exact",
|
|
823
|
+
network: "eip155:84532",
|
|
824
|
+
payTo: "0xYourEvmAddress",
|
|
825
|
+
price: "$0.01",
|
|
826
|
+
},
|
|
827
|
+
],
|
|
828
|
+
description: "Cross-chain: ALGO or ETH",
|
|
829
|
+
},
|
|
830
|
+
};
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## USDC Payments
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
import {
|
|
839
|
+
ALGORAND_TESTNET_CAIP2,
|
|
840
|
+
USDC_TESTNET_ASA_ID,
|
|
841
|
+
} from "@x402-avm/avm";
|
|
842
|
+
|
|
843
|
+
const routes = {
|
|
844
|
+
"GET /api/premium": {
|
|
845
|
+
accepts: {
|
|
846
|
+
scheme: "exact",
|
|
847
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
848
|
+
payTo: "YOUR_ALGORAND_ADDRESS",
|
|
849
|
+
price: "$5.00",
|
|
850
|
+
extra: {
|
|
851
|
+
asset: USDC_TESTNET_ASA_ID,
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
description: "Premium (USDC only)",
|
|
855
|
+
},
|
|
856
|
+
};
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
861
|
+
## Complete App: Proxy Pattern
|
|
862
|
+
|
|
863
|
+
### Project Structure
|
|
864
|
+
|
|
865
|
+
```
|
|
866
|
+
my-x402-app/
|
|
867
|
+
.env.local
|
|
868
|
+
middleware.ts
|
|
869
|
+
app/
|
|
870
|
+
page.tsx
|
|
871
|
+
api/
|
|
872
|
+
health/route.ts
|
|
873
|
+
weather/[city]/route.ts
|
|
874
|
+
premium/data/route.ts
|
|
875
|
+
generate/route.ts
|
|
876
|
+
lib/
|
|
877
|
+
x402.ts
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### .env.local
|
|
881
|
+
|
|
882
|
+
```bash
|
|
883
|
+
PAY_TO=ALGORAND_ADDRESS_HERE_58_CHARS
|
|
884
|
+
FACILITATOR_URL=https://x402.org/facilitator
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
### lib/x402.ts
|
|
888
|
+
|
|
889
|
+
```typescript
|
|
890
|
+
import { x402ResourceServer } from "@x402-avm/next";
|
|
891
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
892
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
893
|
+
|
|
894
|
+
export const PAY_TO = process.env.PAY_TO!;
|
|
895
|
+
|
|
896
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
897
|
+
url: process.env.FACILITATOR_URL || "https://x402.org/facilitator",
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
export const x402Server = new x402ResourceServer(facilitatorClient);
|
|
901
|
+
registerExactAvmScheme(x402Server);
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### middleware.ts
|
|
905
|
+
|
|
906
|
+
```typescript
|
|
907
|
+
import { NextRequest } from "next/server";
|
|
908
|
+
import { paymentProxy } from "@x402-avm/next";
|
|
909
|
+
import { x402Server, PAY_TO } from "@/lib/x402";
|
|
910
|
+
import {
|
|
911
|
+
ALGORAND_TESTNET_CAIP2,
|
|
912
|
+
USDC_TESTNET_ASA_ID,
|
|
913
|
+
} from "@x402-avm/avm";
|
|
914
|
+
|
|
915
|
+
const routes = {
|
|
916
|
+
"GET /api/weather/*": {
|
|
917
|
+
accepts: {
|
|
918
|
+
scheme: "exact",
|
|
919
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
920
|
+
payTo: PAY_TO,
|
|
921
|
+
price: "$0.01",
|
|
922
|
+
},
|
|
923
|
+
description: "Weather data",
|
|
924
|
+
unpaidResponseBody: () => ({
|
|
925
|
+
contentType: "application/json",
|
|
926
|
+
body: { message: "Pay $0.01 for weather data" },
|
|
927
|
+
}),
|
|
928
|
+
},
|
|
929
|
+
"GET /api/premium/*": {
|
|
930
|
+
accepts: {
|
|
931
|
+
scheme: "exact",
|
|
932
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
933
|
+
payTo: PAY_TO,
|
|
934
|
+
price: "$0.50",
|
|
935
|
+
extra: { asset: USDC_TESTNET_ASA_ID },
|
|
936
|
+
},
|
|
937
|
+
description: "Premium content (USDC)",
|
|
938
|
+
},
|
|
939
|
+
"POST /api/generate": {
|
|
940
|
+
accepts: {
|
|
941
|
+
scheme: "exact",
|
|
942
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
943
|
+
payTo: PAY_TO,
|
|
944
|
+
price: "$1.00",
|
|
945
|
+
},
|
|
946
|
+
description: "AI generation",
|
|
947
|
+
},
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
const paywallConfig = {
|
|
951
|
+
title: "x402-avm Demo",
|
|
952
|
+
description: "Pay with Algorand to access premium features",
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
const proxy = paymentProxy(routes, x402Server, paywallConfig);
|
|
956
|
+
|
|
957
|
+
export async function middleware(request: NextRequest) {
|
|
958
|
+
return proxy(request);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
export const config = {
|
|
962
|
+
matcher: "/api/:path*",
|
|
963
|
+
};
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
### app/api/weather/[city]/route.ts
|
|
967
|
+
|
|
968
|
+
```typescript
|
|
969
|
+
import { NextResponse } from "next/server";
|
|
970
|
+
|
|
971
|
+
export async function GET(
|
|
972
|
+
request: Request,
|
|
973
|
+
{ params }: { params: { city: string } },
|
|
974
|
+
) {
|
|
975
|
+
return NextResponse.json({
|
|
976
|
+
city: params.city,
|
|
977
|
+
temperature: Math.floor(Math.random() * 40) + 50,
|
|
978
|
+
condition: ["sunny", "cloudy", "rainy"][Math.floor(Math.random() * 3)],
|
|
979
|
+
humidity: Math.floor(Math.random() * 60) + 30,
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
### app/api/premium/data/route.ts
|
|
985
|
+
|
|
986
|
+
```typescript
|
|
987
|
+
import { NextResponse } from "next/server";
|
|
988
|
+
|
|
989
|
+
export async function GET() {
|
|
990
|
+
return NextResponse.json({
|
|
991
|
+
analytics: {
|
|
992
|
+
pageViews: 15420,
|
|
993
|
+
uniqueVisitors: 8734,
|
|
994
|
+
bounceRate: 0.32,
|
|
995
|
+
},
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
### app/api/generate/route.ts
|
|
1001
|
+
|
|
1002
|
+
```typescript
|
|
1003
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
1004
|
+
|
|
1005
|
+
export async function POST(request: NextRequest) {
|
|
1006
|
+
const { prompt } = await request.json();
|
|
1007
|
+
return NextResponse.json({
|
|
1008
|
+
prompt,
|
|
1009
|
+
generated: "AI-generated content...",
|
|
1010
|
+
model: "gpt-4",
|
|
1011
|
+
tokens: 256,
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### app/api/health/route.ts
|
|
1017
|
+
|
|
1018
|
+
```typescript
|
|
1019
|
+
import { NextResponse } from "next/server";
|
|
1020
|
+
|
|
1021
|
+
export async function GET() {
|
|
1022
|
+
return NextResponse.json({ status: "healthy" });
|
|
1023
|
+
}
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
### app/page.tsx
|
|
1027
|
+
|
|
1028
|
+
```tsx
|
|
1029
|
+
export default function Home() {
|
|
1030
|
+
return (
|
|
1031
|
+
<main>
|
|
1032
|
+
<h1>x402-avm Next.js Demo</h1>
|
|
1033
|
+
<p>API Endpoints:</p>
|
|
1034
|
+
<ul>
|
|
1035
|
+
<li>GET /api/weather/:city -- $0.01 ALGO</li>
|
|
1036
|
+
<li>GET /api/premium/data -- $0.50 USDC</li>
|
|
1037
|
+
<li>POST /api/generate -- $1.00 ALGO</li>
|
|
1038
|
+
<li>GET /api/health -- Free</li>
|
|
1039
|
+
</ul>
|
|
1040
|
+
</main>
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
---
|
|
1046
|
+
|
|
1047
|
+
## Complete App: Route Handler Pattern
|
|
1048
|
+
|
|
1049
|
+
### Project Structure
|
|
1050
|
+
|
|
1051
|
+
```
|
|
1052
|
+
my-x402-app/
|
|
1053
|
+
.env.local
|
|
1054
|
+
app/
|
|
1055
|
+
page.tsx
|
|
1056
|
+
api/
|
|
1057
|
+
health/route.ts
|
|
1058
|
+
weather/route.ts
|
|
1059
|
+
premium/route.ts
|
|
1060
|
+
generate/route.ts
|
|
1061
|
+
lib/
|
|
1062
|
+
x402.ts
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
### lib/x402.ts
|
|
1066
|
+
|
|
1067
|
+
```typescript
|
|
1068
|
+
import { x402ResourceServer } from "@x402-avm/next";
|
|
1069
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
1070
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
1071
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
1072
|
+
|
|
1073
|
+
export const PAY_TO = process.env.PAY_TO!;
|
|
1074
|
+
export const NETWORK = ALGORAND_TESTNET_CAIP2;
|
|
1075
|
+
|
|
1076
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
1077
|
+
url: process.env.FACILITATOR_URL || "https://x402.org/facilitator",
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
export const x402Server = new x402ResourceServer(facilitatorClient);
|
|
1081
|
+
registerExactAvmScheme(x402Server);
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
### app/api/weather/route.ts
|
|
1085
|
+
|
|
1086
|
+
```typescript
|
|
1087
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
1088
|
+
import { withX402 } from "@x402-avm/next";
|
|
1089
|
+
import { x402Server, PAY_TO, NETWORK } from "@/lib/x402";
|
|
1090
|
+
|
|
1091
|
+
const handler = async (request: NextRequest) => {
|
|
1092
|
+
const city = request.nextUrl.searchParams.get("city") || "san-francisco";
|
|
1093
|
+
return NextResponse.json({
|
|
1094
|
+
city,
|
|
1095
|
+
temperature: 72,
|
|
1096
|
+
condition: "sunny",
|
|
1097
|
+
});
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
export const GET = withX402(handler, {
|
|
1101
|
+
accepts: {
|
|
1102
|
+
scheme: "exact",
|
|
1103
|
+
network: NETWORK,
|
|
1104
|
+
payTo: PAY_TO,
|
|
1105
|
+
price: "$0.01",
|
|
1106
|
+
},
|
|
1107
|
+
description: "Weather data",
|
|
1108
|
+
}, x402Server);
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
### app/api/premium/route.ts
|
|
1112
|
+
|
|
1113
|
+
```typescript
|
|
1114
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
1115
|
+
import { withX402 } from "@x402-avm/next";
|
|
1116
|
+
import { x402Server, PAY_TO, NETWORK } from "@/lib/x402";
|
|
1117
|
+
import { USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
1118
|
+
|
|
1119
|
+
const handler = async (request: NextRequest) => {
|
|
1120
|
+
return NextResponse.json({
|
|
1121
|
+
data: "premium content",
|
|
1122
|
+
analytics: { views: 15000, revenue: "$1234.56" },
|
|
1123
|
+
});
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
export const GET = withX402(handler, {
|
|
1127
|
+
accepts: {
|
|
1128
|
+
scheme: "exact",
|
|
1129
|
+
network: NETWORK,
|
|
1130
|
+
payTo: PAY_TO,
|
|
1131
|
+
price: "$0.50",
|
|
1132
|
+
extra: { asset: USDC_TESTNET_ASA_ID },
|
|
1133
|
+
},
|
|
1134
|
+
description: "Premium analytics (USDC)",
|
|
1135
|
+
}, x402Server);
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
### app/api/generate/route.ts
|
|
1139
|
+
|
|
1140
|
+
```typescript
|
|
1141
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
1142
|
+
import { withX402 } from "@x402-avm/next";
|
|
1143
|
+
import { x402Server, PAY_TO, NETWORK } from "@/lib/x402";
|
|
1144
|
+
|
|
1145
|
+
const handler = async (request: NextRequest) => {
|
|
1146
|
+
try {
|
|
1147
|
+
const { prompt } = await request.json();
|
|
1148
|
+
const result = await generateContent(prompt);
|
|
1149
|
+
return NextResponse.json({ prompt, result });
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
return NextResponse.json(
|
|
1152
|
+
{ error: "Generation failed" },
|
|
1153
|
+
{ status: 500 },
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1158
|
+
export const POST = withX402(handler, {
|
|
1159
|
+
accepts: {
|
|
1160
|
+
scheme: "exact",
|
|
1161
|
+
network: NETWORK,
|
|
1162
|
+
payTo: PAY_TO,
|
|
1163
|
+
price: "$1.00",
|
|
1164
|
+
},
|
|
1165
|
+
description: "AI generation",
|
|
1166
|
+
}, x402Server);
|
|
1167
|
+
|
|
1168
|
+
async function generateContent(prompt: string): Promise<string> {
|
|
1169
|
+
return "Generated content based on: " + prompt;
|
|
1170
|
+
}
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
### app/api/health/route.ts
|
|
1174
|
+
|
|
1175
|
+
```typescript
|
|
1176
|
+
import { NextResponse } from "next/server";
|
|
1177
|
+
|
|
1178
|
+
export async function GET() {
|
|
1179
|
+
return NextResponse.json({ status: "healthy" });
|
|
1180
|
+
}
|
|
1181
|
+
```
|