@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
package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-examples.md
ADDED
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
# x402 Facilitator Examples
|
|
2
|
+
|
|
3
|
+
## FacilitatorAvmSigner Implementation
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import algosdk from "algosdk";
|
|
7
|
+
import type { FacilitatorAvmSigner } from "@x402-avm/avm";
|
|
8
|
+
|
|
9
|
+
const secretKey = Buffer.from(process.env.AVM_PRIVATE_KEY!, "base64");
|
|
10
|
+
const address = algosdk.encodeAddress(secretKey.slice(32));
|
|
11
|
+
const algodClient = new algosdk.Algodv2("", "https://testnet-api.algonode.cloud", "");
|
|
12
|
+
|
|
13
|
+
const facilitatorSigner: FacilitatorAvmSigner = {
|
|
14
|
+
getAddresses: () => [address],
|
|
15
|
+
|
|
16
|
+
signTransaction: async (txn: Uint8Array, senderAddress: string) => {
|
|
17
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
18
|
+
const signed = algosdk.signTransaction(decoded, secretKey);
|
|
19
|
+
return signed.blob;
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
getAlgodClient: (network: string) => algodClient,
|
|
23
|
+
|
|
24
|
+
simulateTransactions: async (txns: Uint8Array[], network: string) => {
|
|
25
|
+
const stxns = txns.map((txnBytes) => {
|
|
26
|
+
try {
|
|
27
|
+
return algosdk.decodeSignedTransaction(txnBytes);
|
|
28
|
+
} catch {
|
|
29
|
+
const txn = algosdk.decodeUnsignedTransaction(txnBytes);
|
|
30
|
+
return new algosdk.SignedTransaction({ txn });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const request = new algosdk.modelsv2.SimulateRequest({
|
|
34
|
+
txnGroups: [
|
|
35
|
+
new algosdk.modelsv2.SimulateRequestTransactionGroup({ txns: stxns }),
|
|
36
|
+
],
|
|
37
|
+
allowEmptySignatures: true,
|
|
38
|
+
});
|
|
39
|
+
return algodClient.simulateTransactions(request).do();
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
sendTransactions: async (signedTxns: Uint8Array[], network: string) => {
|
|
43
|
+
const combined = Buffer.concat(signedTxns.map((t) => Buffer.from(t)));
|
|
44
|
+
const { txId } = await algodClient.sendRawTransaction(combined).do();
|
|
45
|
+
return txId;
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
waitForConfirmation: async (txId: string, network: string, waitRounds = 4) => {
|
|
49
|
+
return algosdk.waitForConfirmation(algodClient, txId, waitRounds);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Facilitator Setup and Scheme Registration
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { x402Facilitator } from "@x402-avm/core/facilitator";
|
|
60
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/facilitator";
|
|
61
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
62
|
+
|
|
63
|
+
const facilitator = new x402Facilitator();
|
|
64
|
+
|
|
65
|
+
registerExactAvmScheme(facilitator, {
|
|
66
|
+
signer: facilitatorSigner,
|
|
67
|
+
networks: ALGORAND_TESTNET_CAIP2,
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Basic Express.js Facilitator Server
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import express from "express";
|
|
77
|
+
import { x402Facilitator } from "@x402-avm/core/facilitator";
|
|
78
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/facilitator";
|
|
79
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
80
|
+
import algosdk from "algosdk";
|
|
81
|
+
|
|
82
|
+
const secretKey = Buffer.from(process.env.AVM_PRIVATE_KEY!, "base64");
|
|
83
|
+
const address = algosdk.encodeAddress(secretKey.slice(32));
|
|
84
|
+
const algodClient = new algosdk.Algodv2("", "https://testnet-api.algonode.cloud", "");
|
|
85
|
+
|
|
86
|
+
const signer = {
|
|
87
|
+
getAddresses: () => [address],
|
|
88
|
+
signTransaction: async (txn: Uint8Array, _addr: string) => {
|
|
89
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
90
|
+
return algosdk.signTransaction(decoded, secretKey).blob;
|
|
91
|
+
},
|
|
92
|
+
getAlgodClient: () => algodClient,
|
|
93
|
+
simulateTransactions: async (txns: Uint8Array[]) => {
|
|
94
|
+
const stxns = txns.map((t) => {
|
|
95
|
+
try { return algosdk.decodeSignedTransaction(t); }
|
|
96
|
+
catch { return new algosdk.SignedTransaction({ txn: algosdk.decodeUnsignedTransaction(t) }); }
|
|
97
|
+
});
|
|
98
|
+
const req = new algosdk.modelsv2.SimulateRequest({
|
|
99
|
+
txnGroups: [new algosdk.modelsv2.SimulateRequestTransactionGroup({ txns: stxns })],
|
|
100
|
+
allowEmptySignatures: true,
|
|
101
|
+
});
|
|
102
|
+
return algodClient.simulateTransactions(req).do();
|
|
103
|
+
},
|
|
104
|
+
sendTransactions: async (signedTxns: Uint8Array[]) => {
|
|
105
|
+
const combined = Buffer.concat(signedTxns.map((t) => Buffer.from(t)));
|
|
106
|
+
const { txId } = await algodClient.sendRawTransaction(combined).do();
|
|
107
|
+
return txId;
|
|
108
|
+
},
|
|
109
|
+
waitForConfirmation: async (txId: string, _net: string, rounds = 4) => {
|
|
110
|
+
return algosdk.waitForConfirmation(algodClient, txId, rounds);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const facilitator = new x402Facilitator();
|
|
115
|
+
registerExactAvmScheme(facilitator, { signer, networks: ALGORAND_TESTNET_CAIP2 });
|
|
116
|
+
|
|
117
|
+
const app = express();
|
|
118
|
+
app.use(express.json());
|
|
119
|
+
|
|
120
|
+
app.get("/supported", async (_req, res) => {
|
|
121
|
+
const supported = facilitator.getSupportedNetworks();
|
|
122
|
+
res.json(supported);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
app.post("/verify", async (req, res) => {
|
|
126
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
127
|
+
const result = await facilitator.verify(paymentPayload, paymentRequirements);
|
|
128
|
+
res.json(result);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
app.post("/settle", async (req, res) => {
|
|
132
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
133
|
+
const result = await facilitator.settle(paymentPayload, paymentRequirements);
|
|
134
|
+
res.json(result);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
app.listen(4000, () => console.log("Facilitator running on :4000"));
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Facilitator with Lifecycle Hooks
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const facilitator = new x402Facilitator();
|
|
146
|
+
registerExactAvmScheme(facilitator, { signer, networks: ALGORAND_TESTNET_CAIP2 });
|
|
147
|
+
|
|
148
|
+
facilitator.onBeforeVerify(async (context) => {
|
|
149
|
+
console.log(`Verifying payment for ${context.requirements.resource}`);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
facilitator.onAfterSettle(async (context) => {
|
|
153
|
+
if (context.result.success) {
|
|
154
|
+
console.log(`Settled: ${context.result.txId}`);
|
|
155
|
+
} else {
|
|
156
|
+
console.error(`Settlement failed: ${context.result.error}`);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Bazaar: Declaring Discovery Extension (Resource Server)
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import {
|
|
167
|
+
declareDiscoveryExtension,
|
|
168
|
+
BAZAAR,
|
|
169
|
+
} from "@x402-avm/extensions";
|
|
170
|
+
|
|
171
|
+
// GET endpoint with query parameters
|
|
172
|
+
const weatherExtension = declareDiscoveryExtension({
|
|
173
|
+
input: { city: "San Francisco", units: "metric" },
|
|
174
|
+
inputSchema: {
|
|
175
|
+
properties: {
|
|
176
|
+
city: { type: "string", description: "City name" },
|
|
177
|
+
units: {
|
|
178
|
+
type: "string",
|
|
179
|
+
enum: ["metric", "imperial"],
|
|
180
|
+
description: "Temperature units",
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
required: ["city"],
|
|
184
|
+
},
|
|
185
|
+
output: {
|
|
186
|
+
example: {
|
|
187
|
+
temperature: 18.5,
|
|
188
|
+
condition: "Partly Cloudy",
|
|
189
|
+
humidity: 65,
|
|
190
|
+
},
|
|
191
|
+
schema: {
|
|
192
|
+
properties: {
|
|
193
|
+
temperature: { type: "number" },
|
|
194
|
+
condition: { type: "string" },
|
|
195
|
+
humidity: { type: "number" },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Bazaar: GET Endpoint with No Input
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const priceExtension = declareDiscoveryExtension({
|
|
208
|
+
output: {
|
|
209
|
+
example: {
|
|
210
|
+
price: 42000.50,
|
|
211
|
+
currency: "USD",
|
|
212
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Bazaar: POST Endpoint with JSON Body
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const analysisExtension = declareDiscoveryExtension({
|
|
224
|
+
bodyType: "json",
|
|
225
|
+
input: {
|
|
226
|
+
text: "Analyze this text for sentiment",
|
|
227
|
+
language: "en",
|
|
228
|
+
},
|
|
229
|
+
inputSchema: {
|
|
230
|
+
properties: {
|
|
231
|
+
text: { type: "string", maxLength: 10000 },
|
|
232
|
+
language: { type: "string", enum: ["en", "es", "fr", "de"] },
|
|
233
|
+
},
|
|
234
|
+
required: ["text"],
|
|
235
|
+
},
|
|
236
|
+
output: {
|
|
237
|
+
example: {
|
|
238
|
+
sentiment: "positive",
|
|
239
|
+
confidence: 0.92,
|
|
240
|
+
keywords: ["analyze", "sentiment"],
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Bazaar: POST Endpoint with Form-Data Body
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
const uploadExtension = declareDiscoveryExtension({
|
|
252
|
+
bodyType: "form-data",
|
|
253
|
+
input: {
|
|
254
|
+
file: "(binary)",
|
|
255
|
+
description: "A photo to process",
|
|
256
|
+
},
|
|
257
|
+
inputSchema: {
|
|
258
|
+
properties: {
|
|
259
|
+
file: { type: "string", format: "binary" },
|
|
260
|
+
description: { type: "string" },
|
|
261
|
+
},
|
|
262
|
+
required: ["file"],
|
|
263
|
+
},
|
|
264
|
+
output: {
|
|
265
|
+
example: {
|
|
266
|
+
processedUrl: "https://cdn.example.com/processed/abc123.jpg",
|
|
267
|
+
width: 1920,
|
|
268
|
+
height: 1080,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Bazaar: Resource Server Extension Registration
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import {
|
|
280
|
+
x402HTTPResourceServer,
|
|
281
|
+
HTTPFacilitatorClient,
|
|
282
|
+
} from "@x402-avm/core/server";
|
|
283
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
284
|
+
import {
|
|
285
|
+
bazaarResourceServerExtension,
|
|
286
|
+
declareDiscoveryExtension,
|
|
287
|
+
} from "@x402-avm/extensions";
|
|
288
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
289
|
+
|
|
290
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
291
|
+
url: "https://facilitator.example.com",
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const httpServer = new x402HTTPResourceServer(facilitatorClient, {
|
|
295
|
+
routes: [
|
|
296
|
+
{
|
|
297
|
+
path: "/api/weather",
|
|
298
|
+
config: {
|
|
299
|
+
scheme: "exact",
|
|
300
|
+
payTo: "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA",
|
|
301
|
+
price: {
|
|
302
|
+
asset: USDC_TESTNET_ASA_ID,
|
|
303
|
+
amount: "10000",
|
|
304
|
+
extra: { name: "USDC", decimals: 6 },
|
|
305
|
+
},
|
|
306
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
307
|
+
maxTimeoutSeconds: 60,
|
|
308
|
+
},
|
|
309
|
+
description: "Weather data API",
|
|
310
|
+
mimeType: "application/json",
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
registerExactAvmScheme(httpServer.resourceServer);
|
|
316
|
+
httpServer.resourceServer.registerExtension(bazaarResourceServerExtension);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Bazaar: Extracting Discovery Info (Facilitator)
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import {
|
|
325
|
+
extractDiscoveryInfo,
|
|
326
|
+
validateDiscoveryExtension,
|
|
327
|
+
validateAndExtract,
|
|
328
|
+
extractDiscoveryInfoFromExtension,
|
|
329
|
+
type DiscoveryInfo,
|
|
330
|
+
type DiscoveredResource,
|
|
331
|
+
type ValidationResult,
|
|
332
|
+
} from "@x402-avm/extensions";
|
|
333
|
+
import type { PaymentPayload, PaymentRequirements } from "@x402-avm/core/types";
|
|
334
|
+
|
|
335
|
+
// Method 1: Full extraction from payload + requirements
|
|
336
|
+
async function processPaymentWithDiscovery(
|
|
337
|
+
paymentPayload: PaymentPayload,
|
|
338
|
+
paymentRequirements: PaymentRequirements,
|
|
339
|
+
) {
|
|
340
|
+
const discovered: DiscoveredResource | null = extractDiscoveryInfo(
|
|
341
|
+
paymentPayload,
|
|
342
|
+
paymentRequirements,
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
if (discovered) {
|
|
346
|
+
console.log("Resource URL:", discovered.resourceUrl);
|
|
347
|
+
console.log("HTTP Method:", discovered.method);
|
|
348
|
+
console.log("x402 Version:", discovered.x402Version);
|
|
349
|
+
console.log("Description:", discovered.description);
|
|
350
|
+
console.log("MIME Type:", discovered.mimeType);
|
|
351
|
+
console.log("Discovery Info:", discovered.discoveryInfo);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Method 2: Validate and extract in one step
|
|
356
|
+
function processExtension(extension: unknown) {
|
|
357
|
+
const { valid, info, errors } = validateAndExtract(extension as any);
|
|
358
|
+
|
|
359
|
+
if (valid && info) {
|
|
360
|
+
console.log("Method:", info.input.method);
|
|
361
|
+
console.log("Type:", info.input.type);
|
|
362
|
+
|
|
363
|
+
if ("queryParams" in info.input) {
|
|
364
|
+
console.log("Query params:", info.input.queryParams);
|
|
365
|
+
}
|
|
366
|
+
if ("body" in info.input) {
|
|
367
|
+
console.log("Body type:", (info.input as any).bodyType);
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
console.error("Invalid:", errors);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Method 3: Direct validation
|
|
375
|
+
function validateExtension(extension: unknown) {
|
|
376
|
+
const result: ValidationResult = validateDiscoveryExtension(extension as any);
|
|
377
|
+
|
|
378
|
+
if (result.valid) {
|
|
379
|
+
console.log("Extension is valid");
|
|
380
|
+
} else {
|
|
381
|
+
console.error("Validation errors:", result.errors);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Method 4: Skip validation (trusted source)
|
|
386
|
+
function extractWithoutValidation(
|
|
387
|
+
paymentPayload: PaymentPayload,
|
|
388
|
+
paymentRequirements: PaymentRequirements,
|
|
389
|
+
) {
|
|
390
|
+
const discovered = extractDiscoveryInfo(
|
|
391
|
+
paymentPayload,
|
|
392
|
+
paymentRequirements,
|
|
393
|
+
false,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
if (discovered) {
|
|
397
|
+
console.log("Resource:", discovered.resourceUrl);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Bazaar: Facilitator Client Querying the Bazaar
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
408
|
+
import {
|
|
409
|
+
withBazaar,
|
|
410
|
+
type DiscoveryResourcesResponse,
|
|
411
|
+
type DiscoveryResource,
|
|
412
|
+
} from "@x402-avm/extensions";
|
|
413
|
+
|
|
414
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
415
|
+
url: "https://facilitator.example.com",
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const client = withBazaar(facilitatorClient);
|
|
419
|
+
|
|
420
|
+
// List all discovered resources
|
|
421
|
+
const allResources: DiscoveryResourcesResponse =
|
|
422
|
+
await client.extensions.discovery.listResources();
|
|
423
|
+
|
|
424
|
+
console.log("Total resources:", allResources.pagination.total);
|
|
425
|
+
for (const resource of allResources.items) {
|
|
426
|
+
console.log(`- ${resource.resource} (${resource.type})`);
|
|
427
|
+
console.log(` Payment: ${resource.accepts.length} method(s)`);
|
|
428
|
+
console.log(` Updated: ${resource.lastUpdated}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Filtered query
|
|
432
|
+
const httpResources = await client.extensions.discovery.listResources({
|
|
433
|
+
type: "http",
|
|
434
|
+
limit: 10,
|
|
435
|
+
offset: 0,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Pagination
|
|
439
|
+
async function getAllResources() {
|
|
440
|
+
const allItems: DiscoveryResource[] = [];
|
|
441
|
+
let offset = 0;
|
|
442
|
+
const limit = 50;
|
|
443
|
+
|
|
444
|
+
while (true) {
|
|
445
|
+
const page = await client.extensions.discovery.listResources({
|
|
446
|
+
limit,
|
|
447
|
+
offset,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
allItems.push(...page.items);
|
|
451
|
+
|
|
452
|
+
if (allItems.length >= page.pagination.total) {
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
offset += limit;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return allItems;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Finding Algorand-compatible resources
|
|
462
|
+
async function findAlgorandResources() {
|
|
463
|
+
const resources = await client.extensions.discovery.listResources({
|
|
464
|
+
type: "http",
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return resources.items.filter((resource) =>
|
|
468
|
+
resource.accepts.some((req) =>
|
|
469
|
+
req.network.startsWith("algorand:"),
|
|
470
|
+
),
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Bazaar: WithExtensions Type Utility
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import { WithExtensions } from "@x402-avm/extensions";
|
|
481
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
482
|
+
|
|
483
|
+
type ClientWithBazaar = WithExtensions<
|
|
484
|
+
HTTPFacilitatorClient,
|
|
485
|
+
{ discovery: { listResources(): Promise<any> } }
|
|
486
|
+
>;
|
|
487
|
+
|
|
488
|
+
type ClientWithBazaarAndAuth = WithExtensions<
|
|
489
|
+
ClientWithBazaar,
|
|
490
|
+
{ auth: { login(): Promise<void> } }
|
|
491
|
+
>;
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Bazaar: Chaining Multiple Extensions
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
500
|
+
import { withBazaar } from "@x402-avm/extensions";
|
|
501
|
+
|
|
502
|
+
interface MyCustomExtension {
|
|
503
|
+
custom: {
|
|
504
|
+
doSomething(): Promise<string>;
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function withCustom<T extends HTTPFacilitatorClient>(
|
|
509
|
+
client: T,
|
|
510
|
+
): T & { extensions: MyCustomExtension } {
|
|
511
|
+
const extended = client as T & { extensions: MyCustomExtension };
|
|
512
|
+
const existingExtensions = (client as any).extensions ?? {};
|
|
513
|
+
|
|
514
|
+
extended.extensions = {
|
|
515
|
+
...existingExtensions,
|
|
516
|
+
custom: {
|
|
517
|
+
async doSomething() {
|
|
518
|
+
return "done";
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
} as any;
|
|
522
|
+
|
|
523
|
+
return extended;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const client = withBazaar(withCustom(new HTTPFacilitatorClient({
|
|
527
|
+
url: "https://facilitator.example.com",
|
|
528
|
+
})));
|
|
529
|
+
|
|
530
|
+
const resources = await client.extensions.discovery.listResources();
|
|
531
|
+
const result = await client.extensions.custom.doSomething();
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Complete Resource Server with Bazaar Discovery
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
import express from "express";
|
|
540
|
+
import {
|
|
541
|
+
x402HTTPResourceServer,
|
|
542
|
+
HTTPFacilitatorClient,
|
|
543
|
+
} from "@x402-avm/core/server";
|
|
544
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
545
|
+
import {
|
|
546
|
+
bazaarResourceServerExtension,
|
|
547
|
+
declareDiscoveryExtension,
|
|
548
|
+
} from "@x402-avm/extensions";
|
|
549
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
550
|
+
|
|
551
|
+
const app = express();
|
|
552
|
+
|
|
553
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
554
|
+
url: process.env.FACILITATOR_URL || "https://facilitator.example.com",
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
const httpServer = new x402HTTPResourceServer(facilitatorClient, {
|
|
558
|
+
routes: [
|
|
559
|
+
{
|
|
560
|
+
path: "/api/weather",
|
|
561
|
+
config: {
|
|
562
|
+
scheme: "exact",
|
|
563
|
+
payTo: process.env.RECEIVER_ADDRESS!,
|
|
564
|
+
price: {
|
|
565
|
+
asset: USDC_TESTNET_ASA_ID,
|
|
566
|
+
amount: "10000",
|
|
567
|
+
extra: { name: "USDC", decimals: 6 },
|
|
568
|
+
},
|
|
569
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
570
|
+
maxTimeoutSeconds: 60,
|
|
571
|
+
},
|
|
572
|
+
description: "Real-time weather data",
|
|
573
|
+
mimeType: "application/json",
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
path: "/api/analyze",
|
|
577
|
+
config: {
|
|
578
|
+
scheme: "exact",
|
|
579
|
+
payTo: process.env.RECEIVER_ADDRESS!,
|
|
580
|
+
price: {
|
|
581
|
+
asset: USDC_TESTNET_ASA_ID,
|
|
582
|
+
amount: "500000",
|
|
583
|
+
extra: { name: "USDC", decimals: 6 },
|
|
584
|
+
},
|
|
585
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
586
|
+
maxTimeoutSeconds: 120,
|
|
587
|
+
},
|
|
588
|
+
description: "AI text analysis",
|
|
589
|
+
mimeType: "application/json",
|
|
590
|
+
},
|
|
591
|
+
],
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
registerExactAvmScheme(httpServer.resourceServer);
|
|
595
|
+
httpServer.resourceServer.registerExtension(bazaarResourceServerExtension);
|
|
596
|
+
|
|
597
|
+
const weatherDiscovery = declareDiscoveryExtension({
|
|
598
|
+
input: { city: "San Francisco", units: "metric" },
|
|
599
|
+
inputSchema: {
|
|
600
|
+
properties: {
|
|
601
|
+
city: { type: "string" },
|
|
602
|
+
units: { type: "string", enum: ["metric", "imperial"] },
|
|
603
|
+
},
|
|
604
|
+
required: ["city"],
|
|
605
|
+
},
|
|
606
|
+
output: {
|
|
607
|
+
example: {
|
|
608
|
+
temperature: 18.5,
|
|
609
|
+
condition: "Partly Cloudy",
|
|
610
|
+
humidity: 65,
|
|
611
|
+
windSpeed: 12.3,
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const analysisDiscovery = declareDiscoveryExtension({
|
|
617
|
+
bodyType: "json",
|
|
618
|
+
input: { text: "Sample text for analysis", language: "en" },
|
|
619
|
+
inputSchema: {
|
|
620
|
+
properties: {
|
|
621
|
+
text: { type: "string", maxLength: 50000 },
|
|
622
|
+
language: { type: "string" },
|
|
623
|
+
},
|
|
624
|
+
required: ["text"],
|
|
625
|
+
},
|
|
626
|
+
output: {
|
|
627
|
+
example: {
|
|
628
|
+
sentiment: "neutral",
|
|
629
|
+
confidence: 0.85,
|
|
630
|
+
entities: ["person", "location"],
|
|
631
|
+
summary: "A brief summary of the analyzed text.",
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
app.get("/api/weather", async (req, res) => {
|
|
637
|
+
const result = await httpServer.processRequest({
|
|
638
|
+
url: req.url,
|
|
639
|
+
method: req.method,
|
|
640
|
+
headers: req.headers,
|
|
641
|
+
adapter: {
|
|
642
|
+
getHeader: (name: string) => req.headers[name.toLowerCase()] as string,
|
|
643
|
+
},
|
|
644
|
+
extensions: weatherDiscovery,
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
if (result.status === 402) {
|
|
648
|
+
return res.status(402).json(result.body);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const city = (req.query.city as string) || "San Francisco";
|
|
652
|
+
res.json({
|
|
653
|
+
temperature: 18.5,
|
|
654
|
+
condition: "Partly Cloudy",
|
|
655
|
+
humidity: 65,
|
|
656
|
+
windSpeed: 12.3,
|
|
657
|
+
city,
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
app.post("/api/analyze", express.json(), async (req, res) => {
|
|
662
|
+
const result = await httpServer.processRequest({
|
|
663
|
+
url: req.url,
|
|
664
|
+
method: req.method,
|
|
665
|
+
headers: req.headers,
|
|
666
|
+
adapter: {
|
|
667
|
+
getHeader: (name: string) => req.headers[name.toLowerCase()] as string,
|
|
668
|
+
},
|
|
669
|
+
extensions: analysisDiscovery,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
if (result.status === 402) {
|
|
673
|
+
return res.status(402).json(result.body);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const { text, language } = req.body;
|
|
677
|
+
res.json({
|
|
678
|
+
sentiment: "neutral",
|
|
679
|
+
confidence: 0.85,
|
|
680
|
+
entities: ["person", "location"],
|
|
681
|
+
summary: `Analysis of ${text.length} characters in ${language || "en"}.`,
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
const PORT = parseInt(process.env.PORT || "3000", 10);
|
|
686
|
+
app.listen(PORT, () => {
|
|
687
|
+
console.log(`Resource server with Bazaar discovery on port ${PORT}`);
|
|
688
|
+
});
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
## Complete Facilitator with Bazaar Cataloging
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
import express from "express";
|
|
697
|
+
import { x402Facilitator } from "@x402-avm/core/facilitator";
|
|
698
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/facilitator";
|
|
699
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
700
|
+
import {
|
|
701
|
+
extractDiscoveryInfo,
|
|
702
|
+
type DiscoveredResource,
|
|
703
|
+
} from "@x402-avm/extensions";
|
|
704
|
+
import algosdk from "algosdk";
|
|
705
|
+
|
|
706
|
+
// In-memory catalog (use a database in production)
|
|
707
|
+
const catalog: Map<string, DiscoveredResource & { settledCount: number }> = new Map();
|
|
708
|
+
|
|
709
|
+
const secretKey = Buffer.from(process.env.AVM_PRIVATE_KEY!, "base64");
|
|
710
|
+
const address = algosdk.encodeAddress(secretKey.slice(32));
|
|
711
|
+
const algodClient = new algosdk.Algodv2("", "https://testnet-api.algonode.cloud", "");
|
|
712
|
+
|
|
713
|
+
const signer = {
|
|
714
|
+
getAddresses: () => [address],
|
|
715
|
+
signTransaction: async (txn: Uint8Array, _addr: string) => {
|
|
716
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
717
|
+
return algosdk.signTransaction(decoded, secretKey).blob;
|
|
718
|
+
},
|
|
719
|
+
getAlgodClient: () => algodClient,
|
|
720
|
+
simulateTransactions: async (txns: Uint8Array[]) => {
|
|
721
|
+
const stxns = txns.map((t) => {
|
|
722
|
+
try { return algosdk.decodeSignedTransaction(t); }
|
|
723
|
+
catch { return new algosdk.SignedTransaction({ txn: algosdk.decodeUnsignedTransaction(t) }); }
|
|
724
|
+
});
|
|
725
|
+
const req = new algosdk.modelsv2.SimulateRequest({
|
|
726
|
+
txnGroups: [new algosdk.modelsv2.SimulateRequestTransactionGroup({ txns: stxns })],
|
|
727
|
+
allowEmptySignatures: true,
|
|
728
|
+
});
|
|
729
|
+
return algodClient.simulateTransactions(req).do();
|
|
730
|
+
},
|
|
731
|
+
sendTransactions: async (signedTxns: Uint8Array[]) => {
|
|
732
|
+
const combined = Buffer.concat(signedTxns.map((t) => Buffer.from(t)));
|
|
733
|
+
const { txId } = await algodClient.sendRawTransaction(combined).do();
|
|
734
|
+
return txId;
|
|
735
|
+
},
|
|
736
|
+
waitForConfirmation: async (txId: string, _net: string, rounds = 4) => {
|
|
737
|
+
return algosdk.waitForConfirmation(algodClient, txId, rounds);
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const facilitator = new x402Facilitator();
|
|
742
|
+
registerExactAvmScheme(facilitator, {
|
|
743
|
+
signer,
|
|
744
|
+
networks: ALGORAND_TESTNET_CAIP2,
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
// Register after-settle hook to catalog discovered resources
|
|
748
|
+
facilitator.onAfterSettle(async (context) => {
|
|
749
|
+
if (context.result.success) {
|
|
750
|
+
const discovered = extractDiscoveryInfo(
|
|
751
|
+
context.paymentPayload,
|
|
752
|
+
context.requirements,
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
if (discovered) {
|
|
756
|
+
const key = `${discovered.method}:${discovered.resourceUrl}`;
|
|
757
|
+
const existing = catalog.get(key);
|
|
758
|
+
|
|
759
|
+
if (existing) {
|
|
760
|
+
existing.settledCount += 1;
|
|
761
|
+
} else {
|
|
762
|
+
catalog.set(key, { ...discovered, settledCount: 1 });
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
console.log(`Cataloged: ${key} (${catalog.get(key)!.settledCount} settlements)`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
const app = express();
|
|
771
|
+
app.use(express.json());
|
|
772
|
+
|
|
773
|
+
app.post("/verify", async (req, res) => {
|
|
774
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
775
|
+
const result = await facilitator.verify(paymentPayload, paymentRequirements);
|
|
776
|
+
res.json(result);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
app.post("/settle", async (req, res) => {
|
|
780
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
781
|
+
const result = await facilitator.settle(paymentPayload, paymentRequirements);
|
|
782
|
+
res.json(result);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
app.get("/discovery/resources", (req, res) => {
|
|
786
|
+
const type = req.query.type as string | undefined;
|
|
787
|
+
const limit = parseInt(req.query.limit as string || "50", 10);
|
|
788
|
+
const offset = parseInt(req.query.offset as string || "0", 10);
|
|
789
|
+
|
|
790
|
+
let items = Array.from(catalog.values());
|
|
791
|
+
|
|
792
|
+
if (type) {
|
|
793
|
+
items = items.filter(
|
|
794
|
+
(r) => r.discoveryInfo.input.type === type,
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const total = items.length;
|
|
799
|
+
const paged = items.slice(offset, offset + limit);
|
|
800
|
+
|
|
801
|
+
res.json({
|
|
802
|
+
x402Version: 2,
|
|
803
|
+
items: paged.map((r) => ({
|
|
804
|
+
resource: r.resourceUrl,
|
|
805
|
+
type: r.discoveryInfo.input.type,
|
|
806
|
+
x402Version: r.x402Version,
|
|
807
|
+
accepts: [],
|
|
808
|
+
lastUpdated: new Date().toISOString(),
|
|
809
|
+
metadata: {
|
|
810
|
+
method: r.method,
|
|
811
|
+
description: r.description,
|
|
812
|
+
mimeType: r.mimeType,
|
|
813
|
+
settledCount: r.settledCount,
|
|
814
|
+
},
|
|
815
|
+
})),
|
|
816
|
+
pagination: { limit, offset, total },
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
app.listen(4000, () => {
|
|
821
|
+
console.log("Facilitator with Bazaar discovery on port 4000");
|
|
822
|
+
});
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## Full Stack: Shared Config
|
|
828
|
+
|
|
829
|
+
```typescript
|
|
830
|
+
// shared/config.ts
|
|
831
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
832
|
+
|
|
833
|
+
export const NETWORK = ALGORAND_TESTNET_CAIP2;
|
|
834
|
+
export const USDC_ASA = USDC_TESTNET_ASA_ID;
|
|
835
|
+
export const RESOURCE_WALLET = "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA";
|
|
836
|
+
export const FACILITATOR_URL = "http://localhost:4000";
|
|
837
|
+
export const RESOURCE_SERVER_URL = "http://localhost:3000";
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
## HTTPFacilitatorClient
|
|
843
|
+
|
|
844
|
+
```typescript
|
|
845
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
846
|
+
|
|
847
|
+
// Basic configuration
|
|
848
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
849
|
+
url: "https://facilitator.example.com",
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// With authentication
|
|
853
|
+
const authenticatedClient = new HTTPFacilitatorClient({
|
|
854
|
+
url: "https://facilitator.example.com",
|
|
855
|
+
headers: {
|
|
856
|
+
Authorization: `Bearer ${process.env.FACILITATOR_API_KEY}`,
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
// Check supported networks
|
|
861
|
+
const supported = await facilitatorClient.supported();
|
|
862
|
+
console.log("Supported networks:", supported.networks);
|
|
863
|
+
|
|
864
|
+
// Verify a payment directly
|
|
865
|
+
const verifyResult = await facilitatorClient.verify({
|
|
866
|
+
paymentPayload,
|
|
867
|
+
paymentRequirements,
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// Settle a payment directly
|
|
871
|
+
const settleResult = await facilitatorClient.settle({
|
|
872
|
+
paymentPayload,
|
|
873
|
+
paymentRequirements,
|
|
874
|
+
});
|
|
875
|
+
```
|