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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/index.ts +361 -0
  4. package/lib/mcp-servers.ts +14 -0
  5. package/lib/x402-fetch.ts +213 -0
  6. package/memory/algorand-plugin.md +82 -0
  7. package/openclaw.plugin.json +30 -0
  8. package/package.json +38 -0
  9. package/setup.ts +80 -0
  10. package/skills/algorand-development/SKILL.md +90 -0
  11. package/skills/algorand-development/references/build-smart-contracts-reference.md +79 -0
  12. package/skills/algorand-development/references/build-smart-contracts.md +52 -0
  13. package/skills/algorand-development/references/create-project-reference.md +86 -0
  14. package/skills/algorand-development/references/create-project.md +89 -0
  15. package/skills/algorand-development/references/implement-arc-standards-arc32-arc56.md +396 -0
  16. package/skills/algorand-development/references/implement-arc-standards-arc4.md +265 -0
  17. package/skills/algorand-development/references/implement-arc-standards.md +92 -0
  18. package/skills/algorand-development/references/search-algorand-examples-reference.md +119 -0
  19. package/skills/algorand-development/references/search-algorand-examples.md +89 -0
  20. package/skills/algorand-development/references/troubleshoot-errors-contract.md +373 -0
  21. package/skills/algorand-development/references/troubleshoot-errors-transaction.md +599 -0
  22. package/skills/algorand-development/references/troubleshoot-errors.md +105 -0
  23. package/skills/algorand-development/references/use-algokit-cli-reference.md +228 -0
  24. package/skills/algorand-development/references/use-algokit-cli.md +64 -0
  25. package/skills/algorand-interaction/SKILL.md +223 -0
  26. package/skills/algorand-interaction/references/algorand-mcp.md +743 -0
  27. package/skills/algorand-interaction/references/examples-algorand-mcp.md +647 -0
  28. package/skills/algorand-python/SKILL.md +95 -0
  29. package/skills/algorand-python/references/build-smart-contracts-decorators.md +413 -0
  30. package/skills/algorand-python/references/build-smart-contracts-reference.md +55 -0
  31. package/skills/algorand-python/references/build-smart-contracts-storage.md +452 -0
  32. package/skills/algorand-python/references/build-smart-contracts-transactions.md +445 -0
  33. package/skills/algorand-python/references/build-smart-contracts-types.md +438 -0
  34. package/skills/algorand-python/references/build-smart-contracts.md +82 -0
  35. package/skills/algorand-python/references/create-project-reference.md +55 -0
  36. package/skills/algorand-python/references/create-project.md +75 -0
  37. package/skills/algorand-python/references/implement-arc-standards-arc32-arc56.md +101 -0
  38. package/skills/algorand-python/references/implement-arc-standards-arc4.md +154 -0
  39. package/skills/algorand-python/references/implement-arc-standards.md +39 -0
  40. package/skills/algorand-python/references/troubleshoot-errors-contract.md +355 -0
  41. package/skills/algorand-python/references/troubleshoot-errors-transaction.md +430 -0
  42. package/skills/algorand-python/references/troubleshoot-errors.md +46 -0
  43. package/skills/algorand-python/references/use-algokit-utils-reference.md +350 -0
  44. package/skills/algorand-python/references/use-algokit-utils.md +76 -0
  45. package/skills/algorand-typescript/SKILL.md +131 -0
  46. package/skills/algorand-typescript/references/algorand-ts-migration-from-beta.md +448 -0
  47. package/skills/algorand-typescript/references/algorand-ts-migration-from-tealscript.md +487 -0
  48. package/skills/algorand-typescript/references/algorand-ts-migration.md +102 -0
  49. package/skills/algorand-typescript/references/algorand-typescript-syntax-methods-and-abi.md +134 -0
  50. package/skills/algorand-typescript/references/algorand-typescript-syntax-reference.md +58 -0
  51. package/skills/algorand-typescript/references/algorand-typescript-syntax-storage.md +154 -0
  52. package/skills/algorand-typescript/references/algorand-typescript-syntax-transactions.md +187 -0
  53. package/skills/algorand-typescript/references/algorand-typescript-syntax-types-and-values.md +150 -0
  54. package/skills/algorand-typescript/references/algorand-typescript-syntax.md +84 -0
  55. package/skills/algorand-typescript/references/build-smart-contracts-reference.md +52 -0
  56. package/skills/algorand-typescript/references/build-smart-contracts.md +74 -0
  57. package/skills/algorand-typescript/references/call-smart-contracts-reference.md +237 -0
  58. package/skills/algorand-typescript/references/call-smart-contracts.md +183 -0
  59. package/skills/algorand-typescript/references/create-project-reference.md +53 -0
  60. package/skills/algorand-typescript/references/create-project.md +86 -0
  61. package/skills/algorand-typescript/references/deploy-react-frontend-examples.md +527 -0
  62. package/skills/algorand-typescript/references/deploy-react-frontend-reference.md +412 -0
  63. package/skills/algorand-typescript/references/deploy-react-frontend.md +239 -0
  64. package/skills/algorand-typescript/references/implement-arc-standards-arc32-arc56.md +73 -0
  65. package/skills/algorand-typescript/references/implement-arc-standards-arc4.md +126 -0
  66. package/skills/algorand-typescript/references/implement-arc-standards.md +44 -0
  67. package/skills/algorand-typescript/references/test-smart-contracts-examples.md +245 -0
  68. package/skills/algorand-typescript/references/test-smart-contracts-unit-tests.md +147 -0
  69. package/skills/algorand-typescript/references/test-smart-contracts.md +127 -0
  70. package/skills/algorand-typescript/references/troubleshoot-errors-contract.md +296 -0
  71. package/skills/algorand-typescript/references/troubleshoot-errors-transaction.md +438 -0
  72. package/skills/algorand-typescript/references/troubleshoot-errors.md +56 -0
  73. package/skills/algorand-typescript/references/use-algokit-utils-reference.md +342 -0
  74. package/skills/algorand-typescript/references/use-algokit-utils.md +74 -0
  75. package/skills/algorand-x402-python/SKILL.md +113 -0
  76. package/skills/algorand-x402-python/references/create-python-x402-client-examples.md +469 -0
  77. package/skills/algorand-x402-python/references/create-python-x402-client-reference.md +313 -0
  78. package/skills/algorand-x402-python/references/create-python-x402-client.md +207 -0
  79. package/skills/algorand-x402-python/references/create-python-x402-facilitator-examples.md +924 -0
  80. package/skills/algorand-x402-python/references/create-python-x402-facilitator-reference.md +629 -0
  81. package/skills/algorand-x402-python/references/create-python-x402-facilitator.md +408 -0
  82. package/skills/algorand-x402-python/references/create-python-x402-server-examples.md +703 -0
  83. package/skills/algorand-x402-python/references/create-python-x402-server-reference.md +303 -0
  84. package/skills/algorand-x402-python/references/create-python-x402-server.md +221 -0
  85. package/skills/algorand-x402-python/references/explain-algorand-x402-python-examples.md +605 -0
  86. package/skills/algorand-x402-python/references/explain-algorand-x402-python-reference.md +315 -0
  87. package/skills/algorand-x402-python/references/explain-algorand-x402-python.md +167 -0
  88. package/skills/algorand-x402-python/references/use-python-x402-core-avm-examples.md +554 -0
  89. package/skills/algorand-x402-python/references/use-python-x402-core-avm-reference.md +278 -0
  90. package/skills/algorand-x402-python/references/use-python-x402-core-avm.md +166 -0
  91. package/skills/algorand-x402-typescript/SKILL.md +129 -0
  92. package/skills/algorand-x402-typescript/references/create-typescript-x402-client-examples.md +879 -0
  93. package/skills/algorand-x402-typescript/references/create-typescript-x402-client-reference.md +371 -0
  94. package/skills/algorand-x402-typescript/references/create-typescript-x402-client.md +236 -0
  95. package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-examples.md +875 -0
  96. package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-reference.md +461 -0
  97. package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator.md +270 -0
  98. package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-examples.md +1181 -0
  99. package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-reference.md +360 -0
  100. package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs.md +251 -0
  101. package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-examples.md +870 -0
  102. package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-reference.md +323 -0
  103. package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall.md +281 -0
  104. package/skills/algorand-x402-typescript/references/create-typescript-x402-server-examples.md +1135 -0
  105. package/skills/algorand-x402-typescript/references/create-typescript-x402-server-reference.md +382 -0
  106. package/skills/algorand-x402-typescript/references/create-typescript-x402-server.md +216 -0
  107. package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-examples.md +616 -0
  108. package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-reference.md +323 -0
  109. package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript.md +232 -0
  110. package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-examples.md +1417 -0
  111. package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-reference.md +504 -0
  112. package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm.md +158 -0
@@ -0,0 +1,924 @@
1
+ # Python x402 Facilitator and Bazaar Discovery Examples
2
+
3
+ ## FacilitatorAvmSigner Protocol
4
+
5
+ ```python
6
+ from x402.mechanisms.avm.signer import FacilitatorAvmSigner
7
+
8
+ # Protocol definition:
9
+ class FacilitatorAvmSigner(Protocol):
10
+ def get_addresses(self) -> list[str]:
11
+ """Get all managed fee payer addresses."""
12
+ ...
13
+
14
+ def sign_transaction(
15
+ self, txn_bytes: bytes, fee_payer: str, network: str,
16
+ ) -> bytes:
17
+ """Sign a single transaction with the fee payer's key."""
18
+ ...
19
+
20
+ def sign_group(
21
+ self,
22
+ group_bytes: list[bytes],
23
+ fee_payer: str,
24
+ indexes_to_sign: list[int],
25
+ network: str,
26
+ ) -> list[bytes]:
27
+ """Sign specified transactions in a group."""
28
+ ...
29
+
30
+ def simulate_group(
31
+ self, group_bytes: list[bytes], network: str,
32
+ ) -> None:
33
+ """Simulate a transaction group (raises on failure)."""
34
+ ...
35
+
36
+ def send_group(
37
+ self, group_bytes: list[bytes], network: str,
38
+ ) -> str:
39
+ """Send a transaction group, returns txid."""
40
+ ...
41
+
42
+ def confirm_transaction(
43
+ self, txid: str, network: str, rounds: int = 4,
44
+ ) -> None:
45
+ """Wait for transaction confirmation."""
46
+ ...
47
+ ```
48
+
49
+ ---
50
+
51
+ ## AlgorandFacilitatorSigner Implementation
52
+
53
+ ```python
54
+ import base64
55
+ from x402.mechanisms.avm.signer import FacilitatorAvmSigner
56
+ from x402.mechanisms.avm.constants import (
57
+ ALGORAND_TESTNET_CAIP2,
58
+ ALGORAND_MAINNET_CAIP2,
59
+ NETWORK_CONFIGS,
60
+ )
61
+ from algosdk import encoding, transaction
62
+ from algosdk.v2client import algod
63
+
64
+
65
+ class AlgorandFacilitatorSigner:
66
+ """
67
+ Production FacilitatorAvmSigner implementation.
68
+
69
+ Key encoding notes (algosdk v2.11.1):
70
+ - msgpack_decode(s) expects base64 string, NOT raw bytes
71
+ - msgpack_encode(obj) returns base64 string, NOT raw bytes
72
+ - Transaction.sign(pk) expects base64 string private key
73
+ - SDK protocol passes raw msgpack bytes between methods
74
+ - Boundary: msgpack_decode(base64.b64encode(raw).decode()) for decode
75
+ - Boundary: base64.b64decode(msgpack_encode(obj)) for encode
76
+ """
77
+
78
+ def __init__(self, private_key_b64: str, algod_url: str = "", algod_token: str = ""):
79
+ self._secret_key = base64.b64decode(private_key_b64)
80
+ self._address = encoding.encode_address(self._secret_key[32:])
81
+ self._signing_key = base64.b64encode(self._secret_key).decode()
82
+
83
+ # Create algod clients per network
84
+ self._clients: dict[str, algod.AlgodClient] = {}
85
+ if algod_url:
86
+ self._default_client = algod.AlgodClient(algod_token, algod_url)
87
+ else:
88
+ self._default_client = None
89
+
90
+ def _get_client(self, network: str) -> algod.AlgodClient:
91
+ if network not in self._clients:
92
+ if self._default_client:
93
+ self._clients[network] = self._default_client
94
+ else:
95
+ config = NETWORK_CONFIGS.get(network, {})
96
+ url = config.get("algod_url", "https://testnet-api.algonode.cloud")
97
+ self._clients[network] = algod.AlgodClient("", url)
98
+ return self._clients[network]
99
+
100
+ def get_addresses(self) -> list[str]:
101
+ return [self._address]
102
+
103
+ def sign_transaction(
104
+ self, txn_bytes: bytes, fee_payer: str, network: str,
105
+ ) -> bytes:
106
+ """Sign a single transaction."""
107
+ b64_txn = base64.b64encode(txn_bytes).decode("utf-8")
108
+ txn_obj = encoding.msgpack_decode(b64_txn)
109
+ signed = txn_obj.sign(self._signing_key)
110
+ return base64.b64decode(encoding.msgpack_encode(signed))
111
+
112
+ def sign_group(
113
+ self,
114
+ group_bytes: list[bytes],
115
+ fee_payer: str,
116
+ indexes_to_sign: list[int],
117
+ network: str,
118
+ ) -> list[bytes]:
119
+ """Sign specified transactions in a group."""
120
+ result = list(group_bytes)
121
+ for i in indexes_to_sign:
122
+ result[i] = self.sign_transaction(group_bytes[i], fee_payer, network)
123
+ return result
124
+
125
+ def simulate_group(self, group_bytes: list[bytes], network: str) -> None:
126
+ """Simulate a transaction group.
127
+
128
+ Key pattern: wrap unsigned transactions with SignedTransaction(txn, None)
129
+ and use allow_empty_signatures=True.
130
+ """
131
+ client = self._get_client(network)
132
+ stxns = []
133
+ for txn_bytes in group_bytes:
134
+ b64 = base64.b64encode(txn_bytes).decode("utf-8")
135
+ obj = encoding.msgpack_decode(b64)
136
+ if isinstance(obj, transaction.SignedTransaction):
137
+ stxns.append(obj)
138
+ elif isinstance(obj, transaction.Transaction):
139
+ stxns.append(transaction.SignedTransaction(obj, None))
140
+ else:
141
+ stxns.append(obj)
142
+
143
+ request = transaction.SimulateRequest(
144
+ txn_groups=[
145
+ transaction.SimulateRequestTransactionGroup(txns=stxns)
146
+ ],
147
+ allow_empty_signatures=True,
148
+ )
149
+ result = client.simulate_raw_transactions(request)
150
+
151
+ for group in result.get("txn-groups", []):
152
+ if group.get("failure-message"):
153
+ raise Exception(
154
+ f"Simulation failed: {group['failure-message']}"
155
+ )
156
+
157
+ def send_group(self, group_bytes: list[bytes], network: str) -> str:
158
+ """Send a transaction group.
159
+
160
+ Key pattern: use send_raw_transaction(base64.b64encode(b''.join(group_bytes)))
161
+ to avoid decode/re-encode overhead.
162
+ """
163
+ client = self._get_client(network)
164
+ raw = base64.b64encode(b"".join(group_bytes))
165
+ return client.send_raw_transaction(raw)
166
+
167
+ def confirm_transaction(
168
+ self, txid: str, network: str, rounds: int = 4,
169
+ ) -> None:
170
+ """Wait for transaction confirmation."""
171
+ client = self._get_client(network)
172
+ transaction.wait_for_confirmation(client, txid, rounds)
173
+
174
+
175
+ # Usage:
176
+ import os
177
+
178
+ signer = AlgorandFacilitatorSigner(
179
+ private_key_b64=os.environ["AVM_PRIVATE_KEY"],
180
+ algod_url="https://testnet-api.algonode.cloud",
181
+ )
182
+ print(f"Fee payer addresses: {signer.get_addresses()}")
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Facilitator Registration
188
+
189
+ ```python
190
+ from x402 import x402Facilitator
191
+ from x402.mechanisms.avm.exact import register_exact_avm_facilitator
192
+ from x402.mechanisms.avm import ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2
193
+
194
+ facilitator = x402Facilitator()
195
+
196
+ # Single network
197
+ register_exact_avm_facilitator(
198
+ facilitator, signer, networks=[ALGORAND_TESTNET_CAIP2]
199
+ )
200
+
201
+ # Multiple networks
202
+ register_exact_avm_facilitator(
203
+ facilitator, signer, networks=[ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2]
204
+ )
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Complete FastAPI Facilitator Service
210
+
211
+ ```python
212
+ # facilitator_service.py
213
+ import os
214
+ import base64
215
+ from fastapi import FastAPI, Request
216
+ from fastapi.responses import JSONResponse
217
+ from x402 import x402Facilitator
218
+ from x402.mechanisms.avm.exact import register_exact_avm_facilitator
219
+ from x402.mechanisms.avm import ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2
220
+ from x402.mechanisms.avm.constants import NETWORK_CONFIGS
221
+ from algosdk import encoding, transaction
222
+ from algosdk.v2client import algod
223
+
224
+ app = FastAPI(title="x402-avm Facilitator Service")
225
+
226
+ # Build signer
227
+ SECRET_KEY = base64.b64decode(os.environ["AVM_PRIVATE_KEY"])
228
+ ADDRESS = encoding.encode_address(SECRET_KEY[32:])
229
+ SIGNING_KEY = base64.b64encode(SECRET_KEY).decode()
230
+
231
+
232
+ class FacilitatorSigner:
233
+ def __init__(self):
234
+ self._clients: dict[str, algod.AlgodClient] = {}
235
+
236
+ def _client(self, network: str) -> algod.AlgodClient:
237
+ if network not in self._clients:
238
+ config = NETWORK_CONFIGS.get(network, {})
239
+ url = config.get("algod_url", "https://testnet-api.algonode.cloud")
240
+ self._clients[network] = algod.AlgodClient("", url)
241
+ return self._clients[network]
242
+
243
+ def get_addresses(self) -> list[str]:
244
+ return [ADDRESS]
245
+
246
+ def sign_transaction(self, txn_bytes: bytes, fee_payer: str, network: str) -> bytes:
247
+ b64 = base64.b64encode(txn_bytes).decode()
248
+ txn_obj = encoding.msgpack_decode(b64)
249
+ signed = txn_obj.sign(SIGNING_KEY)
250
+ return base64.b64decode(encoding.msgpack_encode(signed))
251
+
252
+ def sign_group(self, group_bytes, fee_payer, indexes, network):
253
+ result = list(group_bytes)
254
+ for i in indexes:
255
+ result[i] = self.sign_transaction(group_bytes[i], fee_payer, network)
256
+ return result
257
+
258
+ def simulate_group(self, group_bytes, network):
259
+ client = self._client(network)
260
+ stxns = []
261
+ for txn_bytes in group_bytes:
262
+ b64 = base64.b64encode(txn_bytes).decode()
263
+ obj = encoding.msgpack_decode(b64)
264
+ if isinstance(obj, transaction.SignedTransaction):
265
+ stxns.append(obj)
266
+ else:
267
+ stxns.append(transaction.SignedTransaction(obj, None))
268
+ req = transaction.SimulateRequest(
269
+ txn_groups=[transaction.SimulateRequestTransactionGroup(txns=stxns)],
270
+ allow_empty_signatures=True,
271
+ )
272
+ result = client.simulate_raw_transactions(req)
273
+ for group in result.get("txn-groups", []):
274
+ if group.get("failure-message"):
275
+ raise Exception(f"Simulation failed: {group['failure-message']}")
276
+
277
+ def send_group(self, group_bytes, network):
278
+ client = self._client(network)
279
+ return client.send_raw_transaction(
280
+ base64.b64encode(b"".join(group_bytes))
281
+ )
282
+
283
+ def confirm_transaction(self, txid, network, rounds=4):
284
+ client = self._client(network)
285
+ transaction.wait_for_confirmation(client, txid, rounds)
286
+
287
+
288
+ # Initialize facilitator
289
+ signer = FacilitatorSigner()
290
+ facilitator = x402Facilitator()
291
+ register_exact_avm_facilitator(
292
+ facilitator,
293
+ signer,
294
+ networks=[ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2],
295
+ )
296
+
297
+
298
+ @app.get("/supported")
299
+ async def supported():
300
+ return facilitator.get_supported_networks()
301
+
302
+
303
+ @app.post("/verify")
304
+ async def verify(request: Request):
305
+ body = await request.json()
306
+ try:
307
+ result = await facilitator.verify(
308
+ body["paymentPayload"], body["paymentRequirements"]
309
+ )
310
+ return result
311
+ except Exception as e:
312
+ return JSONResponse(
313
+ status_code=400, content={"error": str(e)}
314
+ )
315
+
316
+
317
+ @app.post("/settle")
318
+ async def settle(request: Request):
319
+ body = await request.json()
320
+ try:
321
+ result = await facilitator.settle(
322
+ body["paymentPayload"], body["paymentRequirements"]
323
+ )
324
+ return result
325
+ except Exception as e:
326
+ return JSONResponse(
327
+ status_code=400, content={"error": str(e)}
328
+ )
329
+
330
+
331
+ @app.on_event("startup")
332
+ async def startup():
333
+ print(f"Facilitator service started")
334
+ print(f"Fee payer address: {ADDRESS}")
335
+ print(f"Networks: Testnet + Mainnet")
336
+
337
+
338
+ # Run: uvicorn facilitator_service:app --port 4000
339
+ ```
340
+
341
+ ---
342
+
343
+ ## algosdk Encoding Boundary Patterns
344
+
345
+ ```python
346
+ import base64
347
+ from algosdk import encoding
348
+
349
+ # Raw bytes -> algosdk object (DECODE)
350
+ raw_bytes: bytes = ...
351
+ b64_string = base64.b64encode(raw_bytes).decode("utf-8")
352
+ txn_obj = encoding.msgpack_decode(b64_string)
353
+
354
+ # algosdk object -> raw bytes (ENCODE)
355
+ b64_string = encoding.msgpack_encode(txn_obj)
356
+ raw_bytes = base64.b64decode(b64_string)
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Inline FacilitatorSigner (Minimal)
362
+
363
+ ```python
364
+ import os
365
+ import base64
366
+ from algosdk import encoding, transaction
367
+ from algosdk.v2client import algod
368
+
369
+ SECRET = base64.b64decode(os.environ["AVM_PRIVATE_KEY"])
370
+ ADDR = encoding.encode_address(SECRET[32:])
371
+ KEY = base64.b64encode(SECRET).decode()
372
+ CLIENT = algod.AlgodClient("", "https://testnet-api.algonode.cloud")
373
+
374
+
375
+ class MinimalSigner:
376
+ def get_addresses(self):
377
+ return [ADDR]
378
+
379
+ def sign_transaction(self, txn_bytes, fee_payer, network):
380
+ obj = encoding.msgpack_decode(base64.b64encode(txn_bytes).decode())
381
+ return base64.b64decode(encoding.msgpack_encode(obj.sign(KEY)))
382
+
383
+ def sign_group(self, group_bytes, fee_payer, indexes, network):
384
+ r = list(group_bytes)
385
+ for i in indexes:
386
+ r[i] = self.sign_transaction(group_bytes[i], fee_payer, network)
387
+ return r
388
+
389
+ def simulate_group(self, group_bytes, network):
390
+ stxns = []
391
+ for b in group_bytes:
392
+ obj = encoding.msgpack_decode(base64.b64encode(b).decode())
393
+ stxns.append(
394
+ obj if isinstance(obj, transaction.SignedTransaction)
395
+ else transaction.SignedTransaction(obj, None)
396
+ )
397
+ req = transaction.SimulateRequest(
398
+ txn_groups=[transaction.SimulateRequestTransactionGroup(txns=stxns)],
399
+ allow_empty_signatures=True,
400
+ )
401
+ res = CLIENT.simulate_raw_transactions(req)
402
+ for g in res.get("txn-groups", []):
403
+ if g.get("failure-message"):
404
+ raise Exception(g["failure-message"])
405
+
406
+ def send_group(self, group_bytes, network):
407
+ return CLIENT.send_raw_transaction(base64.b64encode(b"".join(group_bytes)))
408
+
409
+ def confirm_transaction(self, txid, network, rounds=4):
410
+ transaction.wait_for_confirmation(CLIENT, txid, rounds)
411
+ ```
412
+
413
+ ---
414
+
415
+ ## Declaring Discovery Extension (GET with Query Parameters)
416
+
417
+ ```python
418
+ from x402.extensions.bazaar import declare_discovery_extension, OutputConfig
419
+
420
+ discovery = declare_discovery_extension(
421
+ input={"city": "San Francisco"},
422
+ input_schema={
423
+ "properties": {
424
+ "city": {"type": "string", "description": "City name"},
425
+ },
426
+ "required": ["city"],
427
+ },
428
+ output=OutputConfig(
429
+ example={"weather": "sunny", "temperature": 70},
430
+ schema={
431
+ "properties": {
432
+ "weather": {"type": "string"},
433
+ "temperature": {"type": "number"},
434
+ },
435
+ "required": ["weather", "temperature"],
436
+ },
437
+ ),
438
+ )
439
+ # discovery is: {"bazaar": {"info": {...}, "schema": {...}}}
440
+ ```
441
+
442
+ ---
443
+
444
+ ## Declaring Discovery Extension (POST with JSON Body)
445
+
446
+ ```python
447
+ from x402.extensions.bazaar import declare_discovery_extension, OutputConfig
448
+
449
+ discovery = declare_discovery_extension(
450
+ input={"prompt": "Tell me about Algorand", "max_tokens": 100},
451
+ input_schema={
452
+ "properties": {
453
+ "prompt": {"type": "string", "description": "The text prompt"},
454
+ "max_tokens": {"type": "integer", "description": "Maximum tokens"},
455
+ },
456
+ "required": ["prompt"],
457
+ },
458
+ body_type="json",
459
+ output=OutputConfig(
460
+ example={"text": "Algorand is a...", "tokens_used": 42},
461
+ schema={
462
+ "properties": {
463
+ "text": {"type": "string"},
464
+ "tokens_used": {"type": "integer"},
465
+ },
466
+ "required": ["text"],
467
+ },
468
+ ),
469
+ )
470
+ ```
471
+
472
+ ---
473
+
474
+ ## Minimal Declaration (No Output)
475
+
476
+ ```python
477
+ from x402.extensions.bazaar import declare_discovery_extension
478
+
479
+ discovery = declare_discovery_extension(
480
+ input={"query": "example search term"},
481
+ input_schema={
482
+ "properties": {"query": {"type": "string"}},
483
+ "required": ["query"],
484
+ },
485
+ )
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Route Configuration with Bazaar Discovery (FastAPI)
491
+
492
+ ```python
493
+ from x402.extensions.bazaar import declare_discovery_extension, OutputConfig
494
+ from x402.http import PaymentOption
495
+ from x402.http.types import RouteConfig
496
+ from x402.schemas import Network
497
+
498
+ AVM_NETWORK: Network = "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
499
+ AVM_ADDRESS = "YOUR_ALGORAND_ADDRESS..."
500
+
501
+ routes = {
502
+ "GET /weather": RouteConfig(
503
+ accepts=[
504
+ PaymentOption(
505
+ scheme="exact",
506
+ pay_to=AVM_ADDRESS,
507
+ price="$0.001",
508
+ network=AVM_NETWORK,
509
+ ),
510
+ ],
511
+ description="Weather report",
512
+ mime_type="application/json",
513
+ extensions={
514
+ **declare_discovery_extension(
515
+ input={"city": "San Francisco"},
516
+ input_schema={
517
+ "properties": {"city": {"type": "string"}},
518
+ "required": ["city"],
519
+ },
520
+ output=OutputConfig(
521
+ example={"weather": "sunny", "temperature": 70},
522
+ schema={
523
+ "properties": {
524
+ "weather": {"type": "string"},
525
+ "temperature": {"type": "number"},
526
+ },
527
+ "required": ["weather", "temperature"],
528
+ },
529
+ ),
530
+ )
531
+ },
532
+ ),
533
+ }
534
+ ```
535
+
536
+ ---
537
+
538
+ ## Multi-Chain Route (Algorand + EVM)
539
+
540
+ ```python
541
+ routes = {
542
+ "GET /weather": RouteConfig(
543
+ accepts=[
544
+ PaymentOption(
545
+ scheme="exact",
546
+ pay_to=AVM_ADDRESS,
547
+ price="$0.001",
548
+ network="algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
549
+ ),
550
+ PaymentOption(
551
+ scheme="exact",
552
+ pay_to=EVM_ADDRESS,
553
+ price="$0.001",
554
+ network="eip155:84532",
555
+ ),
556
+ ],
557
+ extensions={
558
+ **declare_discovery_extension(
559
+ input={"city": "San Francisco"},
560
+ input_schema={
561
+ "properties": {"city": {"type": "string"}},
562
+ "required": ["city"],
563
+ },
564
+ output=OutputConfig(
565
+ example={"weather": "sunny", "temperature": 70},
566
+ ),
567
+ )
568
+ },
569
+ ),
570
+ }
571
+ ```
572
+
573
+ ---
574
+
575
+ ## Registering Bazaar Extension on Async Server (FastAPI)
576
+
577
+ ```python
578
+ from x402.server import x402ResourceServer
579
+ from x402.http import FacilitatorConfig, HTTPFacilitatorClient
580
+ from x402.extensions.bazaar import bazaar_resource_server_extension
581
+ from x402.mechanisms.avm.exact import ExactAvmServerScheme
582
+ from x402.schemas import Network
583
+
584
+ AVM_NETWORK: Network = "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
585
+
586
+ facilitator = HTTPFacilitatorClient(
587
+ FacilitatorConfig(url="https://x402.org/facilitator")
588
+ )
589
+ server = x402ResourceServer(facilitator)
590
+ server.register(AVM_NETWORK, ExactAvmServerScheme())
591
+ server.register_extension(bazaar_resource_server_extension)
592
+ ```
593
+
594
+ ---
595
+
596
+ ## Registering Bazaar Extension on Sync Server (Flask)
597
+
598
+ ```python
599
+ from x402.server import x402ResourceServerSync
600
+ from x402.http import FacilitatorConfig, HTTPFacilitatorClientSync
601
+ from x402.extensions.bazaar import bazaar_resource_server_extension
602
+ from x402.mechanisms.avm.exact import ExactAvmServerScheme
603
+ from x402.schemas import Network
604
+
605
+ AVM_NETWORK: Network = "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
606
+
607
+ facilitator = HTTPFacilitatorClientSync(
608
+ FacilitatorConfig(url="https://x402.org/facilitator")
609
+ )
610
+ server = x402ResourceServerSync(facilitator)
611
+ server.register(AVM_NETWORK, ExactAvmServerScheme())
612
+ server.register_extension(bazaar_resource_server_extension)
613
+ ```
614
+
615
+ ---
616
+
617
+ ## Extracting Discovery Info (Facilitator Side)
618
+
619
+ ```python
620
+ from x402.extensions.bazaar import extract_discovery_info
621
+
622
+ discovered = extract_discovery_info(
623
+ payment_payload=payment_payload,
624
+ payment_requirements=payment_requirements,
625
+ validate=True,
626
+ )
627
+
628
+ if discovered:
629
+ print(f"Resource URL: {discovered.resource_url}")
630
+ print(f"HTTP Method: {discovered.method}")
631
+ print(f"x402 Version: {discovered.x402_version}")
632
+ print(f"Description: {discovered.description}")
633
+ print(f"MIME Type: {discovered.mime_type}")
634
+
635
+ info = discovered.discovery_info
636
+ if hasattr(info.input, "query_params"):
637
+ print(f"Query params: {info.input.query_params}")
638
+ elif hasattr(info.input, "body"):
639
+ print(f"Body: {info.input.body}")
640
+
641
+ if info.output:
642
+ print(f"Output example: {info.output.example}")
643
+ ```
644
+
645
+ ---
646
+
647
+ ## Validating Discovery Extensions
648
+
649
+ ```python
650
+ from x402.extensions.bazaar import (
651
+ declare_discovery_extension,
652
+ validate_discovery_extension,
653
+ OutputConfig,
654
+ )
655
+ from x402.extensions.bazaar.types import parse_discovery_extension
656
+
657
+ ext_dict = declare_discovery_extension(
658
+ input={"city": "San Francisco"},
659
+ input_schema={
660
+ "properties": {"city": {"type": "string"}},
661
+ "required": ["city"],
662
+ },
663
+ output=OutputConfig(example={"weather": "sunny", "temperature": 70}),
664
+ )
665
+
666
+ extension = parse_discovery_extension(ext_dict["bazaar"])
667
+
668
+ result = validate_discovery_extension(extension)
669
+
670
+ if result.valid:
671
+ print("Extension is valid")
672
+ else:
673
+ print("Validation errors:", result.errors)
674
+ ```
675
+
676
+ ---
677
+
678
+ ## Validate and Extract in One Step
679
+
680
+ ```python
681
+ from x402.extensions.bazaar import validate_and_extract
682
+
683
+ result = validate_and_extract(extension_data)
684
+
685
+ if result.valid and result.info:
686
+ print(f"Method: {result.info.input.method}")
687
+ else:
688
+ print("Validation errors:", result.errors)
689
+ ```
690
+
691
+ ---
692
+
693
+ ## Querying Discovery Resources (Client Side)
694
+
695
+ ```python
696
+ from x402.http import HTTPFacilitatorClient, FacilitatorConfig
697
+ from x402.extensions.bazaar import with_bazaar, ListDiscoveryResourcesParams
698
+
699
+ facilitator = HTTPFacilitatorClient(
700
+ FacilitatorConfig(url="https://x402.org/facilitator")
701
+ )
702
+ client = with_bazaar(facilitator)
703
+
704
+ response = client.extensions.discovery.list_resources()
705
+ for resource in response.resources:
706
+ print(f"URL: {resource.url}")
707
+ print(f"Type: {resource.type}")
708
+ print(f"Metadata: {resource.metadata}")
709
+
710
+ # Filter and paginate
711
+ response = client.extensions.discovery.list_resources(
712
+ ListDiscoveryResourcesParams(type="http", limit=10, offset=0)
713
+ )
714
+ print(f"Total resources: {response.total}")
715
+ ```
716
+
717
+ ---
718
+
719
+ ## Complete FastAPI Server with Bazaar Discovery
720
+
721
+ ```python
722
+ """Algorand-gated weather API with Bazaar discovery."""
723
+
724
+ import os
725
+
726
+ from dotenv import load_dotenv
727
+ from fastapi import FastAPI
728
+ from pydantic import BaseModel
729
+
730
+ from x402.extensions.bazaar import (
731
+ OutputConfig,
732
+ bazaar_resource_server_extension,
733
+ declare_discovery_extension,
734
+ )
735
+ from x402.http import FacilitatorConfig, HTTPFacilitatorClient, PaymentOption
736
+ from x402.http.middleware.fastapi import PaymentMiddlewareASGI
737
+ from x402.http.types import RouteConfig
738
+ from x402.mechanisms.avm.exact import ExactAvmServerScheme
739
+ from x402.schemas import Network
740
+ from x402.server import x402ResourceServer
741
+
742
+ load_dotenv()
743
+
744
+ AVM_ADDRESS = os.environ["AVM_ADDRESS"]
745
+ AVM_NETWORK: Network = "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
746
+ FACILITATOR_URL = os.getenv("FACILITATOR_URL", "https://x402.org/facilitator")
747
+
748
+
749
+ class WeatherReport(BaseModel):
750
+ weather: str
751
+ temperature: int
752
+
753
+
754
+ class WeatherResponse(BaseModel):
755
+ report: WeatherReport
756
+
757
+
758
+ app = FastAPI(title="Weather API (x402 + Algorand + Bazaar)")
759
+
760
+ facilitator = HTTPFacilitatorClient(FacilitatorConfig(url=FACILITATOR_URL))
761
+ server = x402ResourceServer(facilitator)
762
+ server.register(AVM_NETWORK, ExactAvmServerScheme())
763
+ server.register_extension(bazaar_resource_server_extension)
764
+
765
+ routes = {
766
+ "GET /weather": RouteConfig(
767
+ accepts=[
768
+ PaymentOption(
769
+ scheme="exact",
770
+ pay_to=AVM_ADDRESS,
771
+ price="$0.001",
772
+ network=AVM_NETWORK,
773
+ ),
774
+ ],
775
+ description="Get weather data for a city",
776
+ mime_type="application/json",
777
+ extensions={
778
+ **declare_discovery_extension(
779
+ input={"city": "San Francisco"},
780
+ input_schema={
781
+ "properties": {
782
+ "city": {
783
+ "type": "string",
784
+ "description": "City name to get weather for",
785
+ },
786
+ },
787
+ "required": ["city"],
788
+ },
789
+ output=OutputConfig(
790
+ example={"weather": "sunny", "temperature": 70},
791
+ schema={
792
+ "properties": {
793
+ "weather": {"type": "string"},
794
+ "temperature": {"type": "number"},
795
+ },
796
+ "required": ["weather", "temperature"],
797
+ },
798
+ ),
799
+ )
800
+ },
801
+ ),
802
+ }
803
+
804
+ app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server)
805
+
806
+
807
+ @app.get("/weather")
808
+ async def get_weather(city: str = "San Francisco") -> WeatherResponse:
809
+ return WeatherResponse(
810
+ report=WeatherReport(weather="sunny", temperature=70)
811
+ )
812
+
813
+
814
+ @app.get("/health")
815
+ async def health():
816
+ return {"status": "ok"}
817
+
818
+
819
+ if __name__ == "__main__":
820
+ import uvicorn
821
+
822
+ uvicorn.run(app, host="0.0.0.0", port=4021)
823
+ ```
824
+
825
+ ---
826
+
827
+ ## Complete Flask Server with Bazaar Discovery
828
+
829
+ ```python
830
+ """Flask version of the Algorand-gated weather API with Bazaar discovery."""
831
+
832
+ import os
833
+
834
+ from dotenv import load_dotenv
835
+ from flask import Flask, jsonify
836
+
837
+ from x402.extensions.bazaar import (
838
+ OutputConfig,
839
+ bazaar_resource_server_extension,
840
+ declare_discovery_extension,
841
+ )
842
+ from x402.http import FacilitatorConfig, HTTPFacilitatorClientSync, PaymentOption
843
+ from x402.http.middleware.flask import payment_middleware
844
+ from x402.http.types import RouteConfig
845
+ from x402.mechanisms.avm.exact import ExactAvmServerScheme
846
+ from x402.schemas import Network
847
+ from x402.server import x402ResourceServerSync
848
+
849
+ load_dotenv()
850
+
851
+ AVM_ADDRESS = os.environ["AVM_ADDRESS"]
852
+ AVM_NETWORK: Network = "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
853
+
854
+ app = Flask(__name__)
855
+
856
+ facilitator = HTTPFacilitatorClientSync(
857
+ FacilitatorConfig(url=os.getenv("FACILITATOR_URL", "https://x402.org/facilitator"))
858
+ )
859
+ server = x402ResourceServerSync(facilitator)
860
+ server.register(AVM_NETWORK, ExactAvmServerScheme())
861
+ server.register_extension(bazaar_resource_server_extension)
862
+
863
+ routes = {
864
+ "GET /weather": RouteConfig(
865
+ accepts=[
866
+ PaymentOption(
867
+ scheme="exact",
868
+ pay_to=AVM_ADDRESS,
869
+ price="$0.001",
870
+ network=AVM_NETWORK,
871
+ ),
872
+ ],
873
+ extensions={
874
+ **declare_discovery_extension(
875
+ input={"city": "San Francisco"},
876
+ input_schema={
877
+ "properties": {"city": {"type": "string"}},
878
+ "required": ["city"],
879
+ },
880
+ output=OutputConfig(
881
+ example={"weather": "sunny", "temperature": 70},
882
+ ),
883
+ )
884
+ },
885
+ ),
886
+ }
887
+ payment_middleware(app, routes=routes, server=server)
888
+
889
+
890
+ @app.route("/weather")
891
+ def get_weather():
892
+ return jsonify({"report": {"weather": "sunny", "temperature": 70}})
893
+
894
+
895
+ if __name__ == "__main__":
896
+ app.run(host="0.0.0.0", port=4021, debug=False)
897
+ ```
898
+
899
+ ---
900
+
901
+ ## Client Discovering and Calling the API
902
+
903
+ ```python
904
+ """Client that discovers and calls the Algorand-gated weather API."""
905
+
906
+ import httpx
907
+
908
+ from x402.http import FacilitatorConfig, HTTPFacilitatorClient
909
+ from x402.extensions.bazaar import with_bazaar, ListDiscoveryResourcesParams
910
+
911
+ FACILITATOR_URL = "https://x402.org/facilitator"
912
+
913
+ facilitator = HTTPFacilitatorClient(FacilitatorConfig(url=FACILITATOR_URL))
914
+ client = with_bazaar(facilitator)
915
+
916
+ resources = client.extensions.discovery.list_resources(
917
+ ListDiscoveryResourcesParams(type="http", limit=50)
918
+ )
919
+
920
+ for resource in resources.resources:
921
+ print(f"Discovered: {resource.url} ({resource.type})")
922
+ if resource.metadata:
923
+ print(f" Metadata: {resource.metadata}")
924
+ ```