@goplausible/openclaw-algorand-plugin 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +112 -0
- package/index.ts +361 -0
- package/lib/mcp-servers.ts +14 -0
- package/lib/x402-fetch.ts +213 -0
- package/memory/algorand-plugin.md +82 -0
- package/openclaw.plugin.json +30 -0
- package/package.json +41 -0
- package/setup.ts +80 -0
- package/skills/algorand-development/SKILL.md +90 -0
- package/skills/algorand-development/references/build-smart-contracts-reference.md +79 -0
- package/skills/algorand-development/references/build-smart-contracts.md +52 -0
- package/skills/algorand-development/references/create-project-reference.md +86 -0
- package/skills/algorand-development/references/create-project.md +89 -0
- package/skills/algorand-development/references/implement-arc-standards-arc32-arc56.md +396 -0
- package/skills/algorand-development/references/implement-arc-standards-arc4.md +265 -0
- package/skills/algorand-development/references/implement-arc-standards.md +92 -0
- package/skills/algorand-development/references/search-algorand-examples-reference.md +119 -0
- package/skills/algorand-development/references/search-algorand-examples.md +89 -0
- package/skills/algorand-development/references/troubleshoot-errors-contract.md +373 -0
- package/skills/algorand-development/references/troubleshoot-errors-transaction.md +599 -0
- package/skills/algorand-development/references/troubleshoot-errors.md +105 -0
- package/skills/algorand-development/references/use-algokit-cli-reference.md +228 -0
- package/skills/algorand-development/references/use-algokit-cli.md +64 -0
- package/skills/algorand-interaction/SKILL.md +223 -0
- package/skills/algorand-interaction/references/algorand-mcp.md +743 -0
- package/skills/algorand-interaction/references/examples-algorand-mcp.md +647 -0
- package/skills/algorand-python/SKILL.md +95 -0
- package/skills/algorand-python/references/build-smart-contracts-decorators.md +413 -0
- package/skills/algorand-python/references/build-smart-contracts-reference.md +55 -0
- package/skills/algorand-python/references/build-smart-contracts-storage.md +452 -0
- package/skills/algorand-python/references/build-smart-contracts-transactions.md +445 -0
- package/skills/algorand-python/references/build-smart-contracts-types.md +438 -0
- package/skills/algorand-python/references/build-smart-contracts.md +82 -0
- package/skills/algorand-python/references/create-project-reference.md +55 -0
- package/skills/algorand-python/references/create-project.md +75 -0
- package/skills/algorand-python/references/implement-arc-standards-arc32-arc56.md +101 -0
- package/skills/algorand-python/references/implement-arc-standards-arc4.md +154 -0
- package/skills/algorand-python/references/implement-arc-standards.md +39 -0
- package/skills/algorand-python/references/troubleshoot-errors-contract.md +355 -0
- package/skills/algorand-python/references/troubleshoot-errors-transaction.md +430 -0
- package/skills/algorand-python/references/troubleshoot-errors.md +46 -0
- package/skills/algorand-python/references/use-algokit-utils-reference.md +350 -0
- package/skills/algorand-python/references/use-algokit-utils.md +76 -0
- package/skills/algorand-typescript/SKILL.md +131 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-beta.md +448 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-tealscript.md +487 -0
- package/skills/algorand-typescript/references/algorand-ts-migration.md +102 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-methods-and-abi.md +134 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-reference.md +58 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-storage.md +154 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-transactions.md +187 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-types-and-values.md +150 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax.md +84 -0
- package/skills/algorand-typescript/references/build-smart-contracts-reference.md +52 -0
- package/skills/algorand-typescript/references/build-smart-contracts.md +74 -0
- package/skills/algorand-typescript/references/call-smart-contracts-reference.md +237 -0
- package/skills/algorand-typescript/references/call-smart-contracts.md +183 -0
- package/skills/algorand-typescript/references/create-project-reference.md +53 -0
- package/skills/algorand-typescript/references/create-project.md +86 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-examples.md +527 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-reference.md +412 -0
- package/skills/algorand-typescript/references/deploy-react-frontend.md +239 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc32-arc56.md +73 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc4.md +126 -0
- package/skills/algorand-typescript/references/implement-arc-standards.md +44 -0
- package/skills/algorand-typescript/references/test-smart-contracts-examples.md +245 -0
- package/skills/algorand-typescript/references/test-smart-contracts-unit-tests.md +147 -0
- package/skills/algorand-typescript/references/test-smart-contracts.md +127 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-contract.md +296 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-transaction.md +438 -0
- package/skills/algorand-typescript/references/troubleshoot-errors.md +56 -0
- package/skills/algorand-typescript/references/use-algokit-utils-reference.md +342 -0
- package/skills/algorand-typescript/references/use-algokit-utils.md +74 -0
- package/skills/algorand-x402-python/SKILL.md +113 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-examples.md +469 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-reference.md +313 -0
- package/skills/algorand-x402-python/references/create-python-x402-client.md +207 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-examples.md +924 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-reference.md +629 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator.md +408 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-examples.md +703 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-reference.md +303 -0
- package/skills/algorand-x402-python/references/create-python-x402-server.md +221 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-examples.md +605 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-reference.md +315 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python.md +167 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-examples.md +554 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-reference.md +278 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm.md +166 -0
- package/skills/algorand-x402-typescript/SKILL.md +129 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-examples.md +879 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-reference.md +371 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client.md +236 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-examples.md +875 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-reference.md +461 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator.md +270 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-examples.md +1181 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-reference.md +360 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs.md +251 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-examples.md +870 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall.md +281 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-examples.md +1135 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-reference.md +382 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server.md +216 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-examples.md +616 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript.md +232 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-examples.md +1417 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-reference.md +504 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm.md +158 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# x402 Python HTTP Client Examples
|
|
2
|
+
|
|
3
|
+
## httpx: Basic Usage with x402HttpxClient
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import asyncio
|
|
7
|
+
from x402 import x402Client
|
|
8
|
+
from x402.http.clients.httpx import x402HttpxClient
|
|
9
|
+
|
|
10
|
+
async def main():
|
|
11
|
+
x402 = x402Client()
|
|
12
|
+
# ... register schemes ...
|
|
13
|
+
|
|
14
|
+
async with x402HttpxClient(x402) as client:
|
|
15
|
+
response = await client.get("https://api.example.com/paid-resource")
|
|
16
|
+
print(response.status_code)
|
|
17
|
+
print(response.text)
|
|
18
|
+
|
|
19
|
+
asyncio.run(main())
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## httpx: x402HttpxClient with Extra Options
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
async with x402HttpxClient(x402, timeout=30.0, follow_redirects=True) as client:
|
|
26
|
+
response = await client.get("https://api.example.com/paid-resource")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## httpx: wrapHttpxWithPayment
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import asyncio
|
|
33
|
+
from x402 import x402Client
|
|
34
|
+
from x402.http.clients.httpx import wrapHttpxWithPayment
|
|
35
|
+
|
|
36
|
+
async def main():
|
|
37
|
+
x402 = x402Client()
|
|
38
|
+
# ... register schemes ...
|
|
39
|
+
|
|
40
|
+
async with wrapHttpxWithPayment(x402, timeout=30.0) as client:
|
|
41
|
+
response = await client.get("https://api.example.com/paid-resource")
|
|
42
|
+
print(response.status_code)
|
|
43
|
+
print(response.text)
|
|
44
|
+
|
|
45
|
+
asyncio.run(main())
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## httpx: Custom Transport Setup
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
import httpx
|
|
53
|
+
from x402 import x402Client
|
|
54
|
+
from x402.http.clients.httpx import x402_httpx_transport
|
|
55
|
+
|
|
56
|
+
async def main():
|
|
57
|
+
x402 = x402Client()
|
|
58
|
+
# ... register schemes ...
|
|
59
|
+
|
|
60
|
+
# Default underlying transport
|
|
61
|
+
transport = x402_httpx_transport(x402)
|
|
62
|
+
|
|
63
|
+
# Custom underlying transport (e.g., with retries)
|
|
64
|
+
custom_transport = httpx.AsyncHTTPTransport(retries=3)
|
|
65
|
+
transport = x402_httpx_transport(x402, transport=custom_transport)
|
|
66
|
+
|
|
67
|
+
async with httpx.AsyncClient(transport=transport) as client:
|
|
68
|
+
response = await client.get("https://api.example.com/paid-resource")
|
|
69
|
+
print(response.status_code)
|
|
70
|
+
|
|
71
|
+
asyncio.run(main())
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## httpx: Config-Based Setup
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import asyncio
|
|
78
|
+
from x402 import x402ClientConfig, SchemeRegistration
|
|
79
|
+
from x402.http.clients.httpx import wrapHttpxWithPaymentFromConfig
|
|
80
|
+
from x402.mechanisms.avm.exact import ExactAvmClientScheme
|
|
81
|
+
|
|
82
|
+
config = x402ClientConfig(
|
|
83
|
+
schemes=[
|
|
84
|
+
SchemeRegistration(
|
|
85
|
+
network="algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
|
|
86
|
+
client=ExactAvmClientScheme(signer=my_avm_signer),
|
|
87
|
+
),
|
|
88
|
+
],
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
async def main():
|
|
92
|
+
async with wrapHttpxWithPaymentFromConfig(config) as client:
|
|
93
|
+
response = await client.get("https://api.example.com/paid-resource")
|
|
94
|
+
print(response.text)
|
|
95
|
+
|
|
96
|
+
asyncio.run(main())
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## httpx: Complete Algorand Example
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
"""Complete x402-avm httpx example with Algorand payment handling."""
|
|
103
|
+
|
|
104
|
+
import asyncio
|
|
105
|
+
import base64
|
|
106
|
+
import os
|
|
107
|
+
|
|
108
|
+
import algosdk
|
|
109
|
+
from x402 import x402Client
|
|
110
|
+
from x402.http import x402HTTPClient
|
|
111
|
+
from x402.http.clients.httpx import x402HttpxClient
|
|
112
|
+
from x402.mechanisms.avm.exact.register import register_exact_avm_client
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class AlgorandSigner:
|
|
116
|
+
"""Implements the ClientAvmSigner protocol using algosdk."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, secret_key: bytes, address: str):
|
|
119
|
+
self._secret_key = secret_key
|
|
120
|
+
self._address = address
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def address(self) -> str:
|
|
124
|
+
return self._address
|
|
125
|
+
|
|
126
|
+
def sign_transactions(
|
|
127
|
+
self,
|
|
128
|
+
unsigned_txns: list[bytes],
|
|
129
|
+
indexes_to_sign: list[int],
|
|
130
|
+
) -> list[bytes | None]:
|
|
131
|
+
sk_b64 = base64.b64encode(self._secret_key).decode()
|
|
132
|
+
result: list[bytes | None] = []
|
|
133
|
+
for i, txn_bytes in enumerate(unsigned_txns):
|
|
134
|
+
if i in indexes_to_sign:
|
|
135
|
+
txn = algosdk.encoding.msgpack_decode(
|
|
136
|
+
base64.b64encode(txn_bytes).decode()
|
|
137
|
+
)
|
|
138
|
+
signed = txn.sign(sk_b64)
|
|
139
|
+
result.append(
|
|
140
|
+
base64.b64decode(algosdk.encoding.msgpack_encode(signed))
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
result.append(None)
|
|
144
|
+
return result
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
async def main():
|
|
148
|
+
avm_private_key = os.environ["AVM_PRIVATE_KEY"]
|
|
149
|
+
secret_key = base64.b64decode(avm_private_key)
|
|
150
|
+
if len(secret_key) != 64:
|
|
151
|
+
raise ValueError("AVM_PRIVATE_KEY must be a Base64-encoded 64-byte key")
|
|
152
|
+
|
|
153
|
+
avm_address = algosdk.encoding.encode_address(secret_key[32:])
|
|
154
|
+
print(f"Algorand address: {avm_address}")
|
|
155
|
+
|
|
156
|
+
signer = AlgorandSigner(secret_key, avm_address)
|
|
157
|
+
x402 = x402Client()
|
|
158
|
+
register_exact_avm_client(x402, signer)
|
|
159
|
+
|
|
160
|
+
resource_url = os.environ.get(
|
|
161
|
+
"RESOURCE_URL", "https://api.example.com/paid-resource"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
async with x402HttpxClient(x402) as client:
|
|
165
|
+
response = await client.get(resource_url)
|
|
166
|
+
await response.aread()
|
|
167
|
+
|
|
168
|
+
print(f"Status: {response.status_code}")
|
|
169
|
+
print(f"Body: {response.text}")
|
|
170
|
+
|
|
171
|
+
if response.is_success:
|
|
172
|
+
http_client = x402HTTPClient(x402)
|
|
173
|
+
try:
|
|
174
|
+
settle = http_client.get_payment_settle_response(
|
|
175
|
+
lambda name: response.headers.get(name)
|
|
176
|
+
)
|
|
177
|
+
print(f"Settlement: {settle.model_dump_json(indent=2)}")
|
|
178
|
+
except ValueError:
|
|
179
|
+
print("No payment response header found")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == "__main__":
|
|
183
|
+
asyncio.run(main())
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## httpx: Error Handling
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from x402.http.clients.httpx import (
|
|
190
|
+
PaymentError,
|
|
191
|
+
PaymentAlreadyAttemptedError,
|
|
192
|
+
MissingRequestConfigError,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
async with x402HttpxClient(x402) as client:
|
|
196
|
+
try:
|
|
197
|
+
response = await client.get("https://api.example.com/paid")
|
|
198
|
+
except PaymentAlreadyAttemptedError:
|
|
199
|
+
print("Payment was already attempted but failed")
|
|
200
|
+
except PaymentError as e:
|
|
201
|
+
print(f"Payment handling failed: {e}")
|
|
202
|
+
except httpx.HTTPError as e:
|
|
203
|
+
print(f"HTTP error: {e}")
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## httpx: Register with Specific Network
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
# Testnet only
|
|
210
|
+
register_exact_avm_client(x402, signer, networks="algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=")
|
|
211
|
+
|
|
212
|
+
# Mainnet only
|
|
213
|
+
register_exact_avm_client(x402, signer, networks="algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=")
|
|
214
|
+
|
|
215
|
+
# Both explicitly
|
|
216
|
+
register_exact_avm_client(x402, signer, networks=[
|
|
217
|
+
"algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
|
|
218
|
+
"algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
|
|
219
|
+
])
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## httpx: Custom Algod Endpoint
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
register_exact_avm_client(
|
|
226
|
+
x402,
|
|
227
|
+
signer,
|
|
228
|
+
algod_url="https://my-private-algod.example.com",
|
|
229
|
+
)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## requests: Basic Usage with x402_requests
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from x402 import x402ClientSync
|
|
238
|
+
from x402.http.clients.requests import x402_requests
|
|
239
|
+
|
|
240
|
+
x402 = x402ClientSync()
|
|
241
|
+
# ... register schemes ...
|
|
242
|
+
|
|
243
|
+
session = x402_requests(x402)
|
|
244
|
+
response = session.get("https://api.example.com/paid-resource")
|
|
245
|
+
print(response.status_code)
|
|
246
|
+
print(response.text)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## requests: Context Manager
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
with x402_requests(x402) as session:
|
|
253
|
+
response = session.get("https://api.example.com/paid-resource")
|
|
254
|
+
print(response.text)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## requests: wrapRequestsWithPayment
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
import requests
|
|
261
|
+
from x402 import x402ClientSync
|
|
262
|
+
from x402.http.clients.requests import wrapRequestsWithPayment
|
|
263
|
+
|
|
264
|
+
x402 = x402ClientSync()
|
|
265
|
+
# ... register schemes ...
|
|
266
|
+
|
|
267
|
+
session = requests.Session()
|
|
268
|
+
session.headers.update({"Authorization": "Bearer my-token"})
|
|
269
|
+
|
|
270
|
+
wrapRequestsWithPayment(session, x402)
|
|
271
|
+
|
|
272
|
+
response = session.get("https://api.example.com/paid-resource")
|
|
273
|
+
print(response.text)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## requests: Manual Adapter Mounting
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
import requests
|
|
280
|
+
from x402 import x402ClientSync
|
|
281
|
+
from x402.http.clients.requests import x402_http_adapter
|
|
282
|
+
|
|
283
|
+
x402 = x402ClientSync()
|
|
284
|
+
# ... register schemes ...
|
|
285
|
+
|
|
286
|
+
session = requests.Session()
|
|
287
|
+
|
|
288
|
+
adapter = x402_http_adapter(x402)
|
|
289
|
+
session.mount("https://api.example.com/", adapter)
|
|
290
|
+
|
|
291
|
+
response = session.get("https://api.example.com/paid-resource")
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## requests: Using x402HTTPAdapter Directly
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from x402.http.clients.requests import x402HTTPAdapter
|
|
298
|
+
|
|
299
|
+
adapter = x402HTTPAdapter(x402, max_retries=3, pool_connections=10)
|
|
300
|
+
session.mount("https://", adapter)
|
|
301
|
+
session.mount("http://", adapter)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## requests: Config-Based Setup
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
import requests
|
|
308
|
+
from x402 import x402ClientConfig, SchemeRegistration
|
|
309
|
+
from x402.http.clients.requests import wrapRequestsWithPaymentFromConfig
|
|
310
|
+
from x402.mechanisms.avm.exact import ExactAvmClientScheme
|
|
311
|
+
|
|
312
|
+
config = x402ClientConfig(
|
|
313
|
+
schemes=[
|
|
314
|
+
SchemeRegistration(
|
|
315
|
+
network="algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
|
|
316
|
+
client=ExactAvmClientScheme(signer=my_avm_signer),
|
|
317
|
+
),
|
|
318
|
+
],
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
session = wrapRequestsWithPaymentFromConfig(requests.Session(), config)
|
|
322
|
+
response = session.get("https://api.example.com/paid-resource")
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## requests: Complete Algorand Example
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
"""Complete x402-avm requests example with Algorand payment handling."""
|
|
329
|
+
|
|
330
|
+
import base64
|
|
331
|
+
import os
|
|
332
|
+
|
|
333
|
+
import algosdk
|
|
334
|
+
from x402 import x402ClientSync
|
|
335
|
+
from x402.http import x402HTTPClientSync
|
|
336
|
+
from x402.http.clients.requests import x402_requests
|
|
337
|
+
from x402.mechanisms.avm.exact.register import register_exact_avm_client
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class AlgorandSigner:
|
|
341
|
+
"""Implements the ClientAvmSigner protocol using algosdk."""
|
|
342
|
+
|
|
343
|
+
def __init__(self, secret_key: bytes, address: str):
|
|
344
|
+
self._secret_key = secret_key
|
|
345
|
+
self._address = address
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def address(self) -> str:
|
|
349
|
+
return self._address
|
|
350
|
+
|
|
351
|
+
def sign_transactions(
|
|
352
|
+
self,
|
|
353
|
+
unsigned_txns: list[bytes],
|
|
354
|
+
indexes_to_sign: list[int],
|
|
355
|
+
) -> list[bytes | None]:
|
|
356
|
+
sk_b64 = base64.b64encode(self._secret_key).decode()
|
|
357
|
+
result: list[bytes | None] = []
|
|
358
|
+
for i, txn_bytes in enumerate(unsigned_txns):
|
|
359
|
+
if i in indexes_to_sign:
|
|
360
|
+
txn = algosdk.encoding.msgpack_decode(
|
|
361
|
+
base64.b64encode(txn_bytes).decode()
|
|
362
|
+
)
|
|
363
|
+
signed = txn.sign(sk_b64)
|
|
364
|
+
result.append(
|
|
365
|
+
base64.b64decode(algosdk.encoding.msgpack_encode(signed))
|
|
366
|
+
)
|
|
367
|
+
else:
|
|
368
|
+
result.append(None)
|
|
369
|
+
return result
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def main():
|
|
373
|
+
avm_private_key = os.environ["AVM_PRIVATE_KEY"]
|
|
374
|
+
secret_key = base64.b64decode(avm_private_key)
|
|
375
|
+
if len(secret_key) != 64:
|
|
376
|
+
raise ValueError("AVM_PRIVATE_KEY must be a Base64-encoded 64-byte key")
|
|
377
|
+
|
|
378
|
+
avm_address = algosdk.encoding.encode_address(secret_key[32:])
|
|
379
|
+
print(f"Algorand address: {avm_address}")
|
|
380
|
+
|
|
381
|
+
signer = AlgorandSigner(secret_key, avm_address)
|
|
382
|
+
x402 = x402ClientSync()
|
|
383
|
+
register_exact_avm_client(x402, signer)
|
|
384
|
+
|
|
385
|
+
resource_url = os.environ.get(
|
|
386
|
+
"RESOURCE_URL", "https://api.example.com/paid-resource"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
with x402_requests(x402) as session:
|
|
390
|
+
response = session.get(resource_url)
|
|
391
|
+
|
|
392
|
+
print(f"Status: {response.status_code}")
|
|
393
|
+
print(f"Body: {response.text}")
|
|
394
|
+
|
|
395
|
+
if response.ok:
|
|
396
|
+
http_client = x402HTTPClientSync(x402)
|
|
397
|
+
try:
|
|
398
|
+
settle = http_client.get_payment_settle_response(
|
|
399
|
+
lambda name: response.headers.get(name)
|
|
400
|
+
)
|
|
401
|
+
print(f"Settlement: {settle.model_dump_json(indent=2)}")
|
|
402
|
+
except ValueError:
|
|
403
|
+
print("No payment response header found")
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
if __name__ == "__main__":
|
|
407
|
+
main()
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## requests: Error Handling
|
|
411
|
+
|
|
412
|
+
```python
|
|
413
|
+
from x402.http.clients.requests import PaymentError
|
|
414
|
+
|
|
415
|
+
with x402_requests(x402) as session:
|
|
416
|
+
try:
|
|
417
|
+
response = session.get("https://api.example.com/paid")
|
|
418
|
+
except PaymentError as e:
|
|
419
|
+
print(f"Payment handling failed: {e}")
|
|
420
|
+
except requests.RequestException as e:
|
|
421
|
+
print(f"HTTP error: {e}")
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## requests: Register with Specific Network
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
# Testnet only
|
|
428
|
+
register_exact_avm_client(x402, signer, networks="algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=")
|
|
429
|
+
|
|
430
|
+
# Mainnet only
|
|
431
|
+
register_exact_avm_client(x402, signer, networks="algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=")
|
|
432
|
+
|
|
433
|
+
# Both explicitly
|
|
434
|
+
register_exact_avm_client(x402, signer, networks=[
|
|
435
|
+
"algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
|
|
436
|
+
"algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
|
|
437
|
+
])
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## requests: Custom Algod Endpoint
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
register_exact_avm_client(
|
|
444
|
+
x402,
|
|
445
|
+
signer,
|
|
446
|
+
algod_url="https://my-private-algod.example.com",
|
|
447
|
+
)
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## ClientAvmSigner: Encoding Boundary Pattern
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
import base64
|
|
454
|
+
from algosdk import encoding
|
|
455
|
+
|
|
456
|
+
# Inside sign_transactions():
|
|
457
|
+
|
|
458
|
+
# 1. Raw bytes -> base64 string for algosdk
|
|
459
|
+
b64_txn = base64.b64encode(txn_bytes).decode("utf-8")
|
|
460
|
+
txn_obj = encoding.msgpack_decode(b64_txn)
|
|
461
|
+
|
|
462
|
+
# 2. Sign with base64 private key
|
|
463
|
+
sk_b64 = base64.b64encode(self._secret_key).decode()
|
|
464
|
+
signed_txn = txn_obj.sign(sk_b64)
|
|
465
|
+
|
|
466
|
+
# 3. Base64 string from algosdk -> raw bytes for SDK
|
|
467
|
+
signed_b64 = encoding.msgpack_encode(signed_txn)
|
|
468
|
+
signed_bytes = base64.b64decode(signed_b64)
|
|
469
|
+
```
|