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 +38 -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,1135 @@
|
|
|
1
|
+
# x402-avm Server Examples
|
|
2
|
+
|
|
3
|
+
## Express Quick Start (paymentMiddlewareFromConfig)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import express from "express";
|
|
7
|
+
import { paymentMiddlewareFromConfig } from "@x402-avm/express";
|
|
8
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
9
|
+
import { x402ResourceServer } from "@x402-avm/core/server";
|
|
10
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
11
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
12
|
+
|
|
13
|
+
const app = express();
|
|
14
|
+
|
|
15
|
+
const routes = {
|
|
16
|
+
"GET /api/weather": {
|
|
17
|
+
accepts: {
|
|
18
|
+
scheme: "exact",
|
|
19
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
20
|
+
payTo: "ALGORAND_ADDRESS_HERE_58_CHARS_AAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
21
|
+
price: "$0.01",
|
|
22
|
+
},
|
|
23
|
+
description: "Current weather data",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
28
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
29
|
+
registerExactAvmScheme(server);
|
|
30
|
+
|
|
31
|
+
app.use(
|
|
32
|
+
paymentMiddlewareFromConfig(
|
|
33
|
+
routes,
|
|
34
|
+
facilitatorClient,
|
|
35
|
+
[{ network: "algorand:*", server: server }],
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
app.get("/api/weather", (req, res) => {
|
|
40
|
+
res.json({ temperature: 72, condition: "sunny", city: "San Francisco" });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
app.get("/api/health", (req, res) => {
|
|
44
|
+
res.json({ status: "ok" });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
app.listen(4021, () => {
|
|
48
|
+
console.log("Resource server running on http://localhost:4021");
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Express with x402ResourceServer (paymentMiddleware)
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import express from "express";
|
|
56
|
+
import { paymentMiddleware, x402ResourceServer } from "@x402-avm/express";
|
|
57
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
58
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
59
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm";
|
|
60
|
+
|
|
61
|
+
const app = express();
|
|
62
|
+
|
|
63
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
64
|
+
url: "https://x402.org/facilitator",
|
|
65
|
+
});
|
|
66
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
67
|
+
registerExactAvmScheme(server);
|
|
68
|
+
|
|
69
|
+
const routes = {
|
|
70
|
+
"GET /api/premium/*": {
|
|
71
|
+
accepts: {
|
|
72
|
+
scheme: "exact",
|
|
73
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
74
|
+
payTo: "ALGORAND_ADDRESS_HERE_58_CHARS_AAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
75
|
+
price: "$0.10",
|
|
76
|
+
},
|
|
77
|
+
description: "Premium API access",
|
|
78
|
+
},
|
|
79
|
+
"POST /api/generate": {
|
|
80
|
+
accepts: {
|
|
81
|
+
scheme: "exact",
|
|
82
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
83
|
+
payTo: "ALGORAND_ADDRESS_HERE_58_CHARS_AAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
84
|
+
price: "$0.50",
|
|
85
|
+
},
|
|
86
|
+
description: "AI content generation",
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
app.use(paymentMiddleware(routes, server));
|
|
91
|
+
|
|
92
|
+
app.get("/api/premium/data", (req, res) => {
|
|
93
|
+
res.json({ data: "premium content" });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
app.post("/api/generate", express.json(), (req, res) => {
|
|
97
|
+
res.json({ generated: "AI-generated content based on your prompt" });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
app.listen(4021);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Express with HTTPServer Hooks (paymentMiddlewareFromHTTPServer)
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import express from "express";
|
|
107
|
+
import {
|
|
108
|
+
paymentMiddlewareFromHTTPServer,
|
|
109
|
+
x402ResourceServer,
|
|
110
|
+
x402HTTPResourceServer,
|
|
111
|
+
} from "@x402-avm/express";
|
|
112
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
113
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
114
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm";
|
|
115
|
+
|
|
116
|
+
const app = express();
|
|
117
|
+
|
|
118
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
119
|
+
const resourceServer = new x402ResourceServer(facilitatorClient);
|
|
120
|
+
registerExactAvmScheme(resourceServer);
|
|
121
|
+
|
|
122
|
+
const routes = {
|
|
123
|
+
"GET /api/data": {
|
|
124
|
+
accepts: {
|
|
125
|
+
scheme: "exact",
|
|
126
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
127
|
+
payTo: "ALGORAND_ADDRESS_HERE_58_CHARS_AAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
128
|
+
price: "$0.05",
|
|
129
|
+
},
|
|
130
|
+
description: "Protected data endpoint",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const httpServer = new x402HTTPResourceServer(resourceServer, routes);
|
|
135
|
+
|
|
136
|
+
httpServer.onProtectedRequest(async (context, routeConfig) => {
|
|
137
|
+
const apiKey = context.adapter.getHeader("x-api-key");
|
|
138
|
+
if (apiKey && apiKey === process.env.API_KEY) {
|
|
139
|
+
return { grantAccess: true };
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
app.use(paymentMiddlewareFromHTTPServer(httpServer));
|
|
145
|
+
|
|
146
|
+
app.get("/api/data", (req, res) => {
|
|
147
|
+
res.json({ data: "protected content" });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
app.listen(4021);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Algorand ALGO Native Token Route
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
157
|
+
|
|
158
|
+
const routes = {
|
|
159
|
+
"GET /api/data": {
|
|
160
|
+
accepts: {
|
|
161
|
+
scheme: "exact",
|
|
162
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
163
|
+
payTo: "ALGORAND_ADDRESS_HERE_58_CHARS_AAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
164
|
+
price: "$0.01",
|
|
165
|
+
},
|
|
166
|
+
description: "Data endpoint paid in ALGO",
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Algorand USDC (ASA) Route
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
175
|
+
|
|
176
|
+
const routes = {
|
|
177
|
+
"GET /api/premium": {
|
|
178
|
+
accepts: {
|
|
179
|
+
scheme: "exact",
|
|
180
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
181
|
+
payTo: "ALGORAND_ADDRESS_HERE_58_CHARS_AAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
182
|
+
price: "$0.50",
|
|
183
|
+
extra: {
|
|
184
|
+
asset: USDC_TESTNET_ASA_ID,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
description: "Premium endpoint paid in USDC",
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Multi-Network Route (Testnet + Mainnet)
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2 } from "@x402-avm/avm";
|
|
196
|
+
|
|
197
|
+
const routes = {
|
|
198
|
+
"GET /api/data": {
|
|
199
|
+
accepts: [
|
|
200
|
+
{
|
|
201
|
+
scheme: "exact",
|
|
202
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
203
|
+
payTo: "YOUR_TESTNET_ADDRESS",
|
|
204
|
+
price: "$0.01",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
scheme: "exact",
|
|
208
|
+
network: ALGORAND_MAINNET_CAIP2,
|
|
209
|
+
payTo: "YOUR_MAINNET_ADDRESS",
|
|
210
|
+
price: "$0.01",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
description: "Accepts payment on testnet or mainnet",
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Cross-Chain Route (Algorand + EVM)
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
222
|
+
|
|
223
|
+
const routes = {
|
|
224
|
+
"GET /api/data": {
|
|
225
|
+
accepts: [
|
|
226
|
+
{
|
|
227
|
+
scheme: "exact",
|
|
228
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
229
|
+
payTo: "YOUR_ALGORAND_ADDRESS",
|
|
230
|
+
price: "$0.01",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
scheme: "exact",
|
|
234
|
+
network: "eip155:84532",
|
|
235
|
+
payTo: "0xYourEvmAddress",
|
|
236
|
+
price: "$0.01",
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
description: "Cross-chain: pay with ALGO or ETH",
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Dynamic Pricing
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import express from "express";
|
|
248
|
+
import { paymentMiddleware, x402ResourceServer } from "@x402-avm/express";
|
|
249
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
250
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
251
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
252
|
+
|
|
253
|
+
const app = express();
|
|
254
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
255
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
256
|
+
registerExactAvmScheme(server);
|
|
257
|
+
|
|
258
|
+
const routes = {
|
|
259
|
+
"GET /api/ai/generate": {
|
|
260
|
+
accepts: {
|
|
261
|
+
scheme: "exact",
|
|
262
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
263
|
+
payTo: "YOUR_ALGORAND_ADDRESS",
|
|
264
|
+
price: (context) => {
|
|
265
|
+
const model = context.adapter.getQueryParam("model") || "basic";
|
|
266
|
+
switch (model) {
|
|
267
|
+
case "gpt4": return "$0.50";
|
|
268
|
+
case "gpt3": return "$0.10";
|
|
269
|
+
default: return "$0.01";
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
description: "AI generation with model-based pricing",
|
|
274
|
+
},
|
|
275
|
+
"POST /api/image/resize": {
|
|
276
|
+
accepts: {
|
|
277
|
+
scheme: "exact",
|
|
278
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
279
|
+
payTo: "YOUR_ALGORAND_ADDRESS",
|
|
280
|
+
price: (context) => {
|
|
281
|
+
const body = context.adapter.getBody();
|
|
282
|
+
const width = body?.width || 256;
|
|
283
|
+
if (width > 2048) return "$0.25";
|
|
284
|
+
if (width > 1024) return "$0.10";
|
|
285
|
+
return "$0.05";
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
description: "Image resize with size-based pricing",
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
app.use(paymentMiddleware(routes, server));
|
|
293
|
+
|
|
294
|
+
app.get("/api/ai/generate", (req, res) => {
|
|
295
|
+
res.json({ model: req.query.model || "basic", result: "Generated content..." });
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
app.post("/api/image/resize", express.json(), (req, res) => {
|
|
299
|
+
res.json({ resized: true, width: req.body.width });
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
app.listen(4021);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Multiple Routes with Different Prices
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import express from "express";
|
|
309
|
+
import { paymentMiddlewareFromConfig } from "@x402-avm/express";
|
|
310
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
311
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
312
|
+
|
|
313
|
+
const app = express();
|
|
314
|
+
const PAY_TO = "YOUR_ALGORAND_ADDRESS";
|
|
315
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
316
|
+
|
|
317
|
+
const routes = {
|
|
318
|
+
"GET /api/lookup/:id": {
|
|
319
|
+
accepts: {
|
|
320
|
+
scheme: "exact",
|
|
321
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
322
|
+
payTo: PAY_TO,
|
|
323
|
+
price: "$0.001",
|
|
324
|
+
},
|
|
325
|
+
description: "Simple data lookup",
|
|
326
|
+
},
|
|
327
|
+
"GET /api/analytics/*": {
|
|
328
|
+
accepts: {
|
|
329
|
+
scheme: "exact",
|
|
330
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
331
|
+
payTo: PAY_TO,
|
|
332
|
+
price: "$0.05",
|
|
333
|
+
},
|
|
334
|
+
description: "Analytics dashboard data",
|
|
335
|
+
},
|
|
336
|
+
"POST /api/generate": {
|
|
337
|
+
accepts: {
|
|
338
|
+
scheme: "exact",
|
|
339
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
340
|
+
payTo: PAY_TO,
|
|
341
|
+
price: "$1.00",
|
|
342
|
+
},
|
|
343
|
+
description: "AI content generation",
|
|
344
|
+
},
|
|
345
|
+
"GET /api/subscription/status": {
|
|
346
|
+
accepts: {
|
|
347
|
+
scheme: "exact",
|
|
348
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
349
|
+
payTo: PAY_TO,
|
|
350
|
+
price: "$5.00",
|
|
351
|
+
extra: { asset: USDC_TESTNET_ASA_ID },
|
|
352
|
+
},
|
|
353
|
+
description: "Monthly subscription verification",
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
app.use(paymentMiddlewareFromConfig(routes, facilitatorClient));
|
|
358
|
+
|
|
359
|
+
app.get("/api/lookup/:id", (req, res) => {
|
|
360
|
+
res.json({ id: req.params.id, data: "lookup result" });
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
app.get("/api/analytics/*", (req, res) => {
|
|
364
|
+
res.json({ analytics: "dashboard data" });
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
app.post("/api/generate", express.json(), (req, res) => {
|
|
368
|
+
res.json({ generated: "content" });
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
app.get("/api/subscription/status", (req, res) => {
|
|
372
|
+
res.json({ active: true, expiresAt: "2026-03-01" });
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
app.get("/", (req, res) => {
|
|
376
|
+
res.json({ message: "Welcome! See /api/* for paid endpoints." });
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
app.listen(4021);
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Fee Abstraction Setup
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
import express from "express";
|
|
386
|
+
import { paymentMiddlewareFromConfig } from "@x402-avm/express";
|
|
387
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
388
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
389
|
+
|
|
390
|
+
const app = express();
|
|
391
|
+
|
|
392
|
+
const routes = {
|
|
393
|
+
"GET /api/premium": {
|
|
394
|
+
accepts: {
|
|
395
|
+
scheme: "exact",
|
|
396
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
397
|
+
payTo: "YOUR_RESOURCE_SERVER_ADDRESS",
|
|
398
|
+
price: "$0.10",
|
|
399
|
+
extra: {
|
|
400
|
+
feePayer: "FACILITATOR_FEE_PAYER_ADDRESS",
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
description: "Fee-abstracted premium endpoint",
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
408
|
+
url: "http://localhost:4020",
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
app.use(paymentMiddlewareFromConfig(routes, facilitatorClient));
|
|
412
|
+
|
|
413
|
+
app.get("/api/premium", (req, res) => {
|
|
414
|
+
res.json({ premium: true, data: "Fee-abstracted content" });
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
app.listen(4021);
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Express Facilitator Server
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
import express from "express";
|
|
424
|
+
import { x402Facilitator } from "@x402-avm/core/facilitator";
|
|
425
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/facilitator";
|
|
426
|
+
import type { FacilitatorAvmSigner } from "@x402-avm/avm";
|
|
427
|
+
import algosdk from "algosdk";
|
|
428
|
+
|
|
429
|
+
const app = express();
|
|
430
|
+
app.use(express.json());
|
|
431
|
+
|
|
432
|
+
const privateKeyBytes = Buffer.from(process.env.AVM_PRIVATE_KEY!, "base64");
|
|
433
|
+
const address = algosdk.encodeAddress(privateKeyBytes.slice(32));
|
|
434
|
+
const algodClient = new algosdk.Algodv2(
|
|
435
|
+
process.env.ALGOD_TOKEN || "",
|
|
436
|
+
process.env.ALGOD_SERVER || "https://testnet-api.algonode.cloud",
|
|
437
|
+
"",
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const signer: FacilitatorAvmSigner = {
|
|
441
|
+
address,
|
|
442
|
+
async getAlgodClient(network: string) { return algodClient; },
|
|
443
|
+
async signGroupTransactions(groupTxnBytes: Uint8Array[], myIndices: number[]) {
|
|
444
|
+
const result = [...groupTxnBytes];
|
|
445
|
+
for (const idx of myIndices) {
|
|
446
|
+
const txn = algosdk.decodeUnsignedTransaction(groupTxnBytes[idx]);
|
|
447
|
+
result[idx] = txn.signTxn(privateKeyBytes);
|
|
448
|
+
}
|
|
449
|
+
return result;
|
|
450
|
+
},
|
|
451
|
+
async sendGroup(signedGroupBytes: Uint8Array[]) {
|
|
452
|
+
const combined = new Uint8Array(
|
|
453
|
+
signedGroupBytes.reduce((acc, b) => acc + b.length, 0),
|
|
454
|
+
);
|
|
455
|
+
let offset = 0;
|
|
456
|
+
for (const bytes of signedGroupBytes) {
|
|
457
|
+
combined.set(bytes, offset);
|
|
458
|
+
offset += bytes.length;
|
|
459
|
+
}
|
|
460
|
+
const { txId } = await algodClient.sendRawTransaction(combined).do();
|
|
461
|
+
return txId;
|
|
462
|
+
},
|
|
463
|
+
async simulateGroup(groupTxnBytes: Uint8Array[]) {
|
|
464
|
+
const txns = groupTxnBytes.map((bytes) => {
|
|
465
|
+
try { return algosdk.decodeSignedTransaction(bytes); }
|
|
466
|
+
catch {
|
|
467
|
+
const unsigned = algosdk.decodeUnsignedTransaction(bytes);
|
|
468
|
+
return new algosdk.SignedTransaction(unsigned);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
const request = new algosdk.modelsv2.SimulateRequest({
|
|
472
|
+
txnGroups: [
|
|
473
|
+
new algosdk.modelsv2.SimulateRequestTransactionGroup({
|
|
474
|
+
txns: txns.map((t) => algosdk.decodeObj(algosdk.encodeMsgpack(t))),
|
|
475
|
+
}),
|
|
476
|
+
],
|
|
477
|
+
allowEmptySignatures: true,
|
|
478
|
+
});
|
|
479
|
+
return algodClient.simulateTransactions(request).do();
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const facilitator = new x402Facilitator();
|
|
484
|
+
registerExactAvmScheme(facilitator, {
|
|
485
|
+
signer,
|
|
486
|
+
networks: "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
app.post("/verify", async (req, res) => {
|
|
490
|
+
try {
|
|
491
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
492
|
+
const result = await facilitator.verify(paymentPayload, paymentRequirements);
|
|
493
|
+
res.json(result);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
res.status(500).json({ error: String(error) });
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
app.post("/settle", async (req, res) => {
|
|
500
|
+
try {
|
|
501
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
502
|
+
const result = await facilitator.settle(paymentPayload, paymentRequirements);
|
|
503
|
+
res.json(result);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
res.status(500).json({ error: String(error) });
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
app.get("/supported", (req, res) => {
|
|
510
|
+
res.json(facilitator.getSupported());
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
app.listen(4020);
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Hono Quick Start
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
import { Hono } from "hono";
|
|
520
|
+
import { paymentMiddlewareFromConfig } from "@x402-avm/hono";
|
|
521
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
522
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
523
|
+
|
|
524
|
+
const app = new Hono();
|
|
525
|
+
|
|
526
|
+
const routes = {
|
|
527
|
+
"GET /api/weather": {
|
|
528
|
+
accepts: {
|
|
529
|
+
scheme: "exact",
|
|
530
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
531
|
+
payTo: "ALGORAND_ADDRESS_HERE_58_CHARS_AAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
532
|
+
price: "$0.01",
|
|
533
|
+
},
|
|
534
|
+
description: "Current weather data",
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
539
|
+
app.use(paymentMiddlewareFromConfig(routes, facilitatorClient));
|
|
540
|
+
|
|
541
|
+
app.get("/api/weather", (c) => {
|
|
542
|
+
return c.json({ temperature: 72, condition: "sunny", city: "San Francisco" });
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
app.get("/api/health", (c) => {
|
|
546
|
+
return c.json({ status: "ok" });
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
export default app;
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## Hono with Node.js Server
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
import { serve } from "@hono/node-server";
|
|
556
|
+
|
|
557
|
+
serve({
|
|
558
|
+
fetch: app.fetch,
|
|
559
|
+
port: 4021,
|
|
560
|
+
}, (info) => {
|
|
561
|
+
console.log(`Server running on http://localhost:${info.port}`);
|
|
562
|
+
});
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
## Hono with Bun
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
export default {
|
|
569
|
+
port: 4021,
|
|
570
|
+
fetch: app.fetch,
|
|
571
|
+
};
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## Hono with Deno
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
Deno.serve({ port: 4021 }, app.fetch);
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## Hono Multiple Protected Routes
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
import { Hono } from "hono";
|
|
584
|
+
import { paymentMiddlewareFromConfig } from "@x402-avm/hono";
|
|
585
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
586
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
587
|
+
|
|
588
|
+
const app = new Hono();
|
|
589
|
+
const PAY_TO = "YOUR_ALGORAND_ADDRESS";
|
|
590
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
591
|
+
|
|
592
|
+
const routes = {
|
|
593
|
+
"GET /api/lookup/:id": {
|
|
594
|
+
accepts: {
|
|
595
|
+
scheme: "exact",
|
|
596
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
597
|
+
payTo: PAY_TO,
|
|
598
|
+
price: "$0.001",
|
|
599
|
+
},
|
|
600
|
+
description: "Simple data lookup",
|
|
601
|
+
},
|
|
602
|
+
"GET /api/analytics/*": {
|
|
603
|
+
accepts: {
|
|
604
|
+
scheme: "exact",
|
|
605
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
606
|
+
payTo: PAY_TO,
|
|
607
|
+
price: "$0.05",
|
|
608
|
+
},
|
|
609
|
+
description: "Analytics data",
|
|
610
|
+
},
|
|
611
|
+
"POST /api/generate": {
|
|
612
|
+
accepts: {
|
|
613
|
+
scheme: "exact",
|
|
614
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
615
|
+
payTo: PAY_TO,
|
|
616
|
+
price: "$1.00",
|
|
617
|
+
},
|
|
618
|
+
description: "AI content generation",
|
|
619
|
+
},
|
|
620
|
+
"GET /api/report/full": {
|
|
621
|
+
accepts: {
|
|
622
|
+
scheme: "exact",
|
|
623
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
624
|
+
payTo: PAY_TO,
|
|
625
|
+
price: "$10.00",
|
|
626
|
+
extra: { asset: USDC_TESTNET_ASA_ID },
|
|
627
|
+
},
|
|
628
|
+
description: "Full analytics report (USDC)",
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
app.use(paymentMiddlewareFromConfig(routes, facilitatorClient));
|
|
633
|
+
|
|
634
|
+
app.get("/api/lookup/:id", (c) => {
|
|
635
|
+
return c.json({ id: c.req.param("id"), data: "lookup result" });
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
app.get("/api/analytics/*", (c) => {
|
|
639
|
+
return c.json({ pageViews: 15420, uniqueVisitors: 8734 });
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
app.post("/api/generate", async (c) => {
|
|
643
|
+
const body = await c.req.json();
|
|
644
|
+
return c.json({ prompt: body.prompt, result: "Generated content" });
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
app.get("/api/report/full", (c) => {
|
|
648
|
+
return c.json({ report: "Comprehensive analytics report..." });
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
app.get("/", (c) => {
|
|
652
|
+
return c.json({
|
|
653
|
+
name: "x402-avm Hono API",
|
|
654
|
+
endpoints: {
|
|
655
|
+
lookup: "GET /api/lookup/:id ($0.001)",
|
|
656
|
+
analytics: "GET /api/analytics/* ($0.05)",
|
|
657
|
+
generate: "POST /api/generate ($1.00)",
|
|
658
|
+
report: "GET /api/report/full ($10.00 USDC)",
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
export default app;
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
## Hono Cloudflare Workers
|
|
667
|
+
|
|
668
|
+
### wrangler.toml
|
|
669
|
+
|
|
670
|
+
```toml
|
|
671
|
+
name = "x402-avm-api"
|
|
672
|
+
main = "src/index.ts"
|
|
673
|
+
compatibility_date = "2025-01-01"
|
|
674
|
+
|
|
675
|
+
[vars]
|
|
676
|
+
PAY_TO = "YOUR_ALGORAND_ADDRESS"
|
|
677
|
+
FACILITATOR_URL = "https://x402.org/facilitator"
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### src/index.ts
|
|
681
|
+
|
|
682
|
+
```typescript
|
|
683
|
+
import { Hono } from "hono";
|
|
684
|
+
import { cors } from "hono/cors";
|
|
685
|
+
import { paymentMiddlewareFromConfig } from "@x402-avm/hono";
|
|
686
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
687
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
688
|
+
|
|
689
|
+
type Bindings = {
|
|
690
|
+
PAY_TO: string;
|
|
691
|
+
FACILITATOR_URL: string;
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
const app = new Hono<{ Bindings: Bindings }>();
|
|
695
|
+
app.use("*", cors());
|
|
696
|
+
|
|
697
|
+
app.use("*", async (c, next) => {
|
|
698
|
+
const routes = {
|
|
699
|
+
"GET /api/data": {
|
|
700
|
+
accepts: {
|
|
701
|
+
scheme: "exact",
|
|
702
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
703
|
+
payTo: c.env.PAY_TO,
|
|
704
|
+
price: "$0.01",
|
|
705
|
+
},
|
|
706
|
+
description: "Edge data endpoint",
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
711
|
+
url: c.env.FACILITATOR_URL,
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
const middleware = paymentMiddlewareFromConfig(
|
|
715
|
+
routes,
|
|
716
|
+
facilitatorClient,
|
|
717
|
+
undefined,
|
|
718
|
+
undefined,
|
|
719
|
+
undefined,
|
|
720
|
+
false,
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
return middleware(c, next);
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
app.get("/api/data", (c) => {
|
|
727
|
+
return c.json({
|
|
728
|
+
data: "Edge-served paid content",
|
|
729
|
+
region: c.req.header("cf-ipcountry") || "unknown",
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
app.get("/", (c) => {
|
|
734
|
+
return c.json({ name: "x402-avm Edge API", runtime: "Cloudflare Workers" });
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
export default app;
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
## Hono Facilitator Server
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
import { Hono } from "hono";
|
|
744
|
+
import { cors } from "hono/cors";
|
|
745
|
+
import { serve } from "@hono/node-server";
|
|
746
|
+
import algosdk from "algosdk";
|
|
747
|
+
import { x402Facilitator } from "@x402-avm/core/facilitator";
|
|
748
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/facilitator";
|
|
749
|
+
import type { FacilitatorAvmSigner } from "@x402-avm/avm";
|
|
750
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
751
|
+
|
|
752
|
+
const app = new Hono();
|
|
753
|
+
app.use("*", cors());
|
|
754
|
+
|
|
755
|
+
const privateKeyBytes = Buffer.from(process.env.AVM_PRIVATE_KEY!, "base64");
|
|
756
|
+
const facilitatorAddress = algosdk.encodeAddress(privateKeyBytes.slice(32));
|
|
757
|
+
const algodClient = new algosdk.Algodv2(
|
|
758
|
+
process.env.ALGOD_TOKEN || "",
|
|
759
|
+
process.env.ALGOD_SERVER || "https://testnet-api.algonode.cloud",
|
|
760
|
+
"",
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
const signer: FacilitatorAvmSigner = {
|
|
764
|
+
address: facilitatorAddress,
|
|
765
|
+
async getAlgodClient(_network: string) { return algodClient; },
|
|
766
|
+
async signGroupTransactions(groupTxnBytes: Uint8Array[], myIndices: number[]) {
|
|
767
|
+
const result = [...groupTxnBytes];
|
|
768
|
+
for (const idx of myIndices) {
|
|
769
|
+
const txn = algosdk.decodeUnsignedTransaction(groupTxnBytes[idx]);
|
|
770
|
+
result[idx] = txn.signTxn(privateKeyBytes);
|
|
771
|
+
}
|
|
772
|
+
return result;
|
|
773
|
+
},
|
|
774
|
+
async sendGroup(signedGroupBytes: Uint8Array[]) {
|
|
775
|
+
const combined = new Uint8Array(
|
|
776
|
+
signedGroupBytes.reduce((acc, b) => acc + b.length, 0),
|
|
777
|
+
);
|
|
778
|
+
let offset = 0;
|
|
779
|
+
for (const bytes of signedGroupBytes) {
|
|
780
|
+
combined.set(bytes, offset);
|
|
781
|
+
offset += bytes.length;
|
|
782
|
+
}
|
|
783
|
+
const { txId } = await algodClient.sendRawTransaction(combined).do();
|
|
784
|
+
return txId;
|
|
785
|
+
},
|
|
786
|
+
async simulateGroup(groupTxnBytes: Uint8Array[]) {
|
|
787
|
+
const txns = groupTxnBytes.map((bytes) => {
|
|
788
|
+
try { return algosdk.decodeSignedTransaction(bytes); }
|
|
789
|
+
catch {
|
|
790
|
+
const unsigned = algosdk.decodeUnsignedTransaction(bytes);
|
|
791
|
+
return new algosdk.SignedTransaction(unsigned);
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
const request = new algosdk.modelsv2.SimulateRequest({
|
|
795
|
+
txnGroups: [
|
|
796
|
+
new algosdk.modelsv2.SimulateRequestTransactionGroup({
|
|
797
|
+
txns: txns.map((t) => algosdk.decodeObj(algosdk.encodeMsgpack(t))),
|
|
798
|
+
}),
|
|
799
|
+
],
|
|
800
|
+
allowEmptySignatures: true,
|
|
801
|
+
});
|
|
802
|
+
return algodClient.simulateTransactions(request).do();
|
|
803
|
+
},
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
const facilitator = new x402Facilitator();
|
|
807
|
+
registerExactAvmScheme(facilitator, { signer, networks: ALGORAND_TESTNET_CAIP2 });
|
|
808
|
+
|
|
809
|
+
app.post("/verify", async (c) => {
|
|
810
|
+
try {
|
|
811
|
+
const { paymentPayload, paymentRequirements } = await c.req.json();
|
|
812
|
+
const result = await facilitator.verify(paymentPayload, paymentRequirements);
|
|
813
|
+
return c.json(result);
|
|
814
|
+
} catch (error: any) {
|
|
815
|
+
return c.json({ error: error.message }, 500);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
app.post("/settle", async (c) => {
|
|
820
|
+
try {
|
|
821
|
+
const { paymentPayload, paymentRequirements } = await c.req.json();
|
|
822
|
+
const result = await facilitator.settle(paymentPayload, paymentRequirements);
|
|
823
|
+
return c.json(result);
|
|
824
|
+
} catch (error: any) {
|
|
825
|
+
return c.json({ error: error.message }, 500);
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
app.get("/supported", (c) => {
|
|
830
|
+
return c.json(facilitator.getSupported());
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
app.get("/", (c) => {
|
|
834
|
+
return c.json({
|
|
835
|
+
name: "x402-avm Facilitator",
|
|
836
|
+
address: facilitatorAddress,
|
|
837
|
+
networks: [ALGORAND_TESTNET_CAIP2],
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
serve({ fetch: app.fetch, port: 4020 }, (info) => {
|
|
842
|
+
console.log(`Facilitator running on http://localhost:${info.port}`);
|
|
843
|
+
console.log(`Address: ${facilitatorAddress}`);
|
|
844
|
+
});
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
## Paywall Integration (Express)
|
|
848
|
+
|
|
849
|
+
```typescript
|
|
850
|
+
import express from "express";
|
|
851
|
+
import { paymentMiddleware, x402ResourceServer } from "@x402-avm/express";
|
|
852
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
853
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
854
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm";
|
|
855
|
+
|
|
856
|
+
const app = express();
|
|
857
|
+
const facilitatorClient = new HTTPFacilitatorClient();
|
|
858
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
859
|
+
registerExactAvmScheme(server);
|
|
860
|
+
|
|
861
|
+
const routes = {
|
|
862
|
+
"GET /premium-article": {
|
|
863
|
+
accepts: {
|
|
864
|
+
scheme: "exact",
|
|
865
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
866
|
+
payTo: "YOUR_ALGORAND_ADDRESS",
|
|
867
|
+
price: "$0.25",
|
|
868
|
+
},
|
|
869
|
+
description: "Premium article access",
|
|
870
|
+
mimeType: "text/html",
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const paywallConfig = {
|
|
875
|
+
title: "Premium Content",
|
|
876
|
+
description: "Pay to access this article",
|
|
877
|
+
logoUrl: "https://example.com/logo.png",
|
|
878
|
+
theme: {
|
|
879
|
+
primaryColor: "#6366f1",
|
|
880
|
+
backgroundColor: "#ffffff",
|
|
881
|
+
},
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
app.use(paymentMiddleware(routes, server, paywallConfig));
|
|
885
|
+
|
|
886
|
+
app.get("/premium-article", (req, res) => {
|
|
887
|
+
res.send("<html><body><h1>Premium Article</h1><p>Full content here.</p></body></html>");
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
app.listen(4021);
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
## Custom Paywall HTML
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
const routes = {
|
|
897
|
+
"GET /premium": {
|
|
898
|
+
accepts: {
|
|
899
|
+
scheme: "exact",
|
|
900
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
901
|
+
payTo: "YOUR_ALGORAND_ADDRESS",
|
|
902
|
+
price: "$0.10",
|
|
903
|
+
},
|
|
904
|
+
description: "Premium content",
|
|
905
|
+
customPaywallHtml: `
|
|
906
|
+
<html>
|
|
907
|
+
<head><title>Payment Required</title></head>
|
|
908
|
+
<body>
|
|
909
|
+
<h1>Payment Required</h1>
|
|
910
|
+
<p>Please pay $0.10 in ALGO to access this content.</p>
|
|
911
|
+
</body>
|
|
912
|
+
</html>
|
|
913
|
+
`,
|
|
914
|
+
},
|
|
915
|
+
};
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
## Unpaid Response Body (API Preview)
|
|
919
|
+
|
|
920
|
+
```typescript
|
|
921
|
+
const routes = {
|
|
922
|
+
"GET /api/article/:id": {
|
|
923
|
+
accepts: {
|
|
924
|
+
scheme: "exact",
|
|
925
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
926
|
+
payTo: "YOUR_ALGORAND_ADDRESS",
|
|
927
|
+
price: "$0.10",
|
|
928
|
+
},
|
|
929
|
+
description: "Full article",
|
|
930
|
+
unpaidResponseBody: (context) => ({
|
|
931
|
+
contentType: "application/json",
|
|
932
|
+
body: {
|
|
933
|
+
title: "Article Title",
|
|
934
|
+
preview: "First paragraph of the article...",
|
|
935
|
+
message: "Pay $0.10 to read the full article",
|
|
936
|
+
},
|
|
937
|
+
}),
|
|
938
|
+
},
|
|
939
|
+
};
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
## Complete Express Application
|
|
943
|
+
|
|
944
|
+
```typescript
|
|
945
|
+
import express from "express";
|
|
946
|
+
import dotenv from "dotenv";
|
|
947
|
+
import { paymentMiddleware, x402ResourceServer } from "@x402-avm/express";
|
|
948
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
949
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
950
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
951
|
+
|
|
952
|
+
dotenv.config();
|
|
953
|
+
|
|
954
|
+
const app = express();
|
|
955
|
+
app.use(express.json());
|
|
956
|
+
|
|
957
|
+
const PAY_TO = process.env.RESOURCE_PAY_TO!;
|
|
958
|
+
const FACILITATOR_URL = process.env.FACILITATOR_URL || "https://facilitator.goplausible.xyz";
|
|
959
|
+
|
|
960
|
+
const facilitatorClient = new HTTPFacilitatorClient({ url: FACILITATOR_URL });
|
|
961
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
962
|
+
registerExactAvmScheme(server);
|
|
963
|
+
|
|
964
|
+
const routes = {
|
|
965
|
+
"GET /api/weather/:city": {
|
|
966
|
+
accepts: {
|
|
967
|
+
scheme: "exact",
|
|
968
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
969
|
+
payTo: PAY_TO,
|
|
970
|
+
price: "$0.01",
|
|
971
|
+
},
|
|
972
|
+
description: "Weather data for a city",
|
|
973
|
+
unpaidResponseBody: () => ({
|
|
974
|
+
contentType: "application/json",
|
|
975
|
+
body: {
|
|
976
|
+
message: "Pay $0.01 to get weather data",
|
|
977
|
+
availableCities: ["san-francisco", "new-york", "london"],
|
|
978
|
+
},
|
|
979
|
+
}),
|
|
980
|
+
},
|
|
981
|
+
"GET /api/analytics/*": {
|
|
982
|
+
accepts: {
|
|
983
|
+
scheme: "exact",
|
|
984
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
985
|
+
payTo: PAY_TO,
|
|
986
|
+
price: "$0.50",
|
|
987
|
+
extra: { asset: USDC_TESTNET_ASA_ID },
|
|
988
|
+
},
|
|
989
|
+
description: "Analytics data (USDC only)",
|
|
990
|
+
},
|
|
991
|
+
"POST /api/ai/generate": {
|
|
992
|
+
accepts: {
|
|
993
|
+
scheme: "exact",
|
|
994
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
995
|
+
payTo: PAY_TO,
|
|
996
|
+
price: "$1.00",
|
|
997
|
+
},
|
|
998
|
+
description: "AI content generation",
|
|
999
|
+
},
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
app.use(paymentMiddleware(routes, server));
|
|
1003
|
+
|
|
1004
|
+
app.get("/api/weather/:city", (req, res) => {
|
|
1005
|
+
res.json({
|
|
1006
|
+
city: req.params.city,
|
|
1007
|
+
temperature: Math.floor(Math.random() * 40) + 50,
|
|
1008
|
+
condition: ["sunny", "cloudy", "rainy"][Math.floor(Math.random() * 3)],
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
app.get("/api/analytics/*", (req, res) => {
|
|
1013
|
+
res.json({ pageViews: 15420, uniqueVisitors: 8734, bounceRate: 0.32 });
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
app.post("/api/ai/generate", (req, res) => {
|
|
1017
|
+
res.json({ prompt: req.body.prompt, generated: "AI-generated response..." });
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
app.get("/", (req, res) => {
|
|
1021
|
+
res.json({
|
|
1022
|
+
name: "x402-avm Demo API",
|
|
1023
|
+
endpoints: {
|
|
1024
|
+
weather: "GET /api/weather/:city ($0.01 ALGO)",
|
|
1025
|
+
analytics: "GET /api/analytics/* ($0.50 USDC)",
|
|
1026
|
+
generate: "POST /api/ai/generate ($1.00 ALGO)",
|
|
1027
|
+
},
|
|
1028
|
+
});
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
app.get("/api/health", (req, res) => {
|
|
1032
|
+
res.json({ status: "healthy", uptime: process.uptime() });
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
const PORT = parseInt(process.env.PORT || "4021");
|
|
1036
|
+
app.listen(PORT, () => {
|
|
1037
|
+
console.log(`Resource server: http://localhost:${PORT}`);
|
|
1038
|
+
console.log(`Facilitator: ${FACILITATOR_URL}`);
|
|
1039
|
+
console.log(`Pay-to address: ${PAY_TO}`);
|
|
1040
|
+
});
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
## Complete Hono Application (Node.js)
|
|
1044
|
+
|
|
1045
|
+
```typescript
|
|
1046
|
+
import { Hono } from "hono";
|
|
1047
|
+
import { cors } from "hono/cors";
|
|
1048
|
+
import { logger } from "hono/logger";
|
|
1049
|
+
import { serve } from "@hono/node-server";
|
|
1050
|
+
import { paymentMiddleware, x402ResourceServer } from "@x402-avm/hono";
|
|
1051
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
1052
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
1053
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
1054
|
+
|
|
1055
|
+
const app = new Hono();
|
|
1056
|
+
app.use("*", cors());
|
|
1057
|
+
app.use("*", logger());
|
|
1058
|
+
|
|
1059
|
+
const PAY_TO = process.env.PAY_TO || "YOUR_ALGORAND_ADDRESS";
|
|
1060
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
1061
|
+
url: process.env.FACILITATOR_URL || "https://facilitator.goplausible.xyz",
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
1065
|
+
registerExactAvmScheme(server);
|
|
1066
|
+
|
|
1067
|
+
const routes = {
|
|
1068
|
+
"GET /api/weather/:city": {
|
|
1069
|
+
accepts: {
|
|
1070
|
+
scheme: "exact",
|
|
1071
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
1072
|
+
payTo: PAY_TO,
|
|
1073
|
+
price: "$0.01",
|
|
1074
|
+
},
|
|
1075
|
+
description: "City weather data",
|
|
1076
|
+
},
|
|
1077
|
+
"GET /api/analytics/*": {
|
|
1078
|
+
accepts: {
|
|
1079
|
+
scheme: "exact",
|
|
1080
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
1081
|
+
payTo: PAY_TO,
|
|
1082
|
+
price: "$0.50",
|
|
1083
|
+
extra: { asset: USDC_TESTNET_ASA_ID },
|
|
1084
|
+
},
|
|
1085
|
+
description: "Analytics (USDC)",
|
|
1086
|
+
},
|
|
1087
|
+
"POST /api/ai/generate": {
|
|
1088
|
+
accepts: {
|
|
1089
|
+
scheme: "exact",
|
|
1090
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
1091
|
+
payTo: PAY_TO,
|
|
1092
|
+
price: "$1.00",
|
|
1093
|
+
},
|
|
1094
|
+
description: "AI generation",
|
|
1095
|
+
},
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
app.use(paymentMiddleware(routes, server));
|
|
1099
|
+
|
|
1100
|
+
app.get("/api/weather/:city", (c) => {
|
|
1101
|
+
return c.json({
|
|
1102
|
+
city: c.req.param("city"),
|
|
1103
|
+
temperature: Math.floor(Math.random() * 40) + 50,
|
|
1104
|
+
condition: ["sunny", "cloudy", "rainy"][Math.floor(Math.random() * 3)],
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
app.get("/api/analytics/*", (c) => {
|
|
1109
|
+
return c.json({ pageViews: 15420, uniqueVisitors: 8734, bounceRate: 0.32 });
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
app.post("/api/ai/generate", async (c) => {
|
|
1113
|
+
const { prompt } = await c.req.json();
|
|
1114
|
+
return c.json({ prompt, generated: "AI-generated response...", tokens: 256 });
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
app.get("/", (c) => {
|
|
1118
|
+
return c.json({
|
|
1119
|
+
name: "x402-avm Hono Demo",
|
|
1120
|
+
version: "2.0.0",
|
|
1121
|
+
endpoints: {
|
|
1122
|
+
weather: "GET /api/weather/:city ($0.01 ALGO)",
|
|
1123
|
+
analytics: "GET /api/analytics/* ($0.50 USDC)",
|
|
1124
|
+
generate: "POST /api/ai/generate ($1.00 ALGO)",
|
|
1125
|
+
},
|
|
1126
|
+
});
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
app.get("/api/health", (c) => c.json({ status: "healthy" }));
|
|
1130
|
+
|
|
1131
|
+
const port = parseInt(process.env.PORT || "4021");
|
|
1132
|
+
serve({ fetch: app.fetch, port }, (info) => {
|
|
1133
|
+
console.log(`x402-avm Hono server: http://localhost:${info.port}`);
|
|
1134
|
+
});
|
|
1135
|
+
```
|