openclaw-algorand-plugin 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +112 -0
- package/index.ts +361 -0
- package/lib/mcp-servers.ts +14 -0
- package/lib/x402-fetch.ts +213 -0
- package/memory/algorand-plugin.md +82 -0
- package/openclaw.plugin.json +30 -0
- package/package.json +38 -0
- package/setup.ts +80 -0
- package/skills/algorand-development/SKILL.md +90 -0
- package/skills/algorand-development/references/build-smart-contracts-reference.md +79 -0
- package/skills/algorand-development/references/build-smart-contracts.md +52 -0
- package/skills/algorand-development/references/create-project-reference.md +86 -0
- package/skills/algorand-development/references/create-project.md +89 -0
- package/skills/algorand-development/references/implement-arc-standards-arc32-arc56.md +396 -0
- package/skills/algorand-development/references/implement-arc-standards-arc4.md +265 -0
- package/skills/algorand-development/references/implement-arc-standards.md +92 -0
- package/skills/algorand-development/references/search-algorand-examples-reference.md +119 -0
- package/skills/algorand-development/references/search-algorand-examples.md +89 -0
- package/skills/algorand-development/references/troubleshoot-errors-contract.md +373 -0
- package/skills/algorand-development/references/troubleshoot-errors-transaction.md +599 -0
- package/skills/algorand-development/references/troubleshoot-errors.md +105 -0
- package/skills/algorand-development/references/use-algokit-cli-reference.md +228 -0
- package/skills/algorand-development/references/use-algokit-cli.md +64 -0
- package/skills/algorand-interaction/SKILL.md +223 -0
- package/skills/algorand-interaction/references/algorand-mcp.md +743 -0
- package/skills/algorand-interaction/references/examples-algorand-mcp.md +647 -0
- package/skills/algorand-python/SKILL.md +95 -0
- package/skills/algorand-python/references/build-smart-contracts-decorators.md +413 -0
- package/skills/algorand-python/references/build-smart-contracts-reference.md +55 -0
- package/skills/algorand-python/references/build-smart-contracts-storage.md +452 -0
- package/skills/algorand-python/references/build-smart-contracts-transactions.md +445 -0
- package/skills/algorand-python/references/build-smart-contracts-types.md +438 -0
- package/skills/algorand-python/references/build-smart-contracts.md +82 -0
- package/skills/algorand-python/references/create-project-reference.md +55 -0
- package/skills/algorand-python/references/create-project.md +75 -0
- package/skills/algorand-python/references/implement-arc-standards-arc32-arc56.md +101 -0
- package/skills/algorand-python/references/implement-arc-standards-arc4.md +154 -0
- package/skills/algorand-python/references/implement-arc-standards.md +39 -0
- package/skills/algorand-python/references/troubleshoot-errors-contract.md +355 -0
- package/skills/algorand-python/references/troubleshoot-errors-transaction.md +430 -0
- package/skills/algorand-python/references/troubleshoot-errors.md +46 -0
- package/skills/algorand-python/references/use-algokit-utils-reference.md +350 -0
- package/skills/algorand-python/references/use-algokit-utils.md +76 -0
- package/skills/algorand-typescript/SKILL.md +131 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-beta.md +448 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-tealscript.md +487 -0
- package/skills/algorand-typescript/references/algorand-ts-migration.md +102 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-methods-and-abi.md +134 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-reference.md +58 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-storage.md +154 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-transactions.md +187 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-types-and-values.md +150 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax.md +84 -0
- package/skills/algorand-typescript/references/build-smart-contracts-reference.md +52 -0
- package/skills/algorand-typescript/references/build-smart-contracts.md +74 -0
- package/skills/algorand-typescript/references/call-smart-contracts-reference.md +237 -0
- package/skills/algorand-typescript/references/call-smart-contracts.md +183 -0
- package/skills/algorand-typescript/references/create-project-reference.md +53 -0
- package/skills/algorand-typescript/references/create-project.md +86 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-examples.md +527 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-reference.md +412 -0
- package/skills/algorand-typescript/references/deploy-react-frontend.md +239 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc32-arc56.md +73 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc4.md +126 -0
- package/skills/algorand-typescript/references/implement-arc-standards.md +44 -0
- package/skills/algorand-typescript/references/test-smart-contracts-examples.md +245 -0
- package/skills/algorand-typescript/references/test-smart-contracts-unit-tests.md +147 -0
- package/skills/algorand-typescript/references/test-smart-contracts.md +127 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-contract.md +296 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-transaction.md +438 -0
- package/skills/algorand-typescript/references/troubleshoot-errors.md +56 -0
- package/skills/algorand-typescript/references/use-algokit-utils-reference.md +342 -0
- package/skills/algorand-typescript/references/use-algokit-utils.md +74 -0
- package/skills/algorand-x402-python/SKILL.md +113 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-examples.md +469 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-reference.md +313 -0
- package/skills/algorand-x402-python/references/create-python-x402-client.md +207 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-examples.md +924 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-reference.md +629 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator.md +408 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-examples.md +703 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-reference.md +303 -0
- package/skills/algorand-x402-python/references/create-python-x402-server.md +221 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-examples.md +605 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-reference.md +315 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python.md +167 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-examples.md +554 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-reference.md +278 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm.md +166 -0
- package/skills/algorand-x402-typescript/SKILL.md +129 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-examples.md +879 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-reference.md +371 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client.md +236 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-examples.md +875 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-reference.md +461 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator.md +270 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-examples.md +1181 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-reference.md +360 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs.md +251 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-examples.md +870 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall.md +281 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-examples.md +1135 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-reference.md +382 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server.md +216 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-examples.md +616 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript.md +232 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-examples.md +1417 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-reference.md +504 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm.md +158 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# x402 Python HTTP Client Reference
|
|
2
|
+
|
|
3
|
+
Detailed API reference for httpx (async) and requests (sync) client integrations in the `x402-avm` Python package.
|
|
4
|
+
|
|
5
|
+
## httpx APIs
|
|
6
|
+
|
|
7
|
+
All httpx APIs are in `x402.http.clients.httpx`. They use `x402Client` (async).
|
|
8
|
+
|
|
9
|
+
### `x402HttpxClient(x402_client, **kwargs)`
|
|
10
|
+
|
|
11
|
+
Convenience class extending `httpx.AsyncClient` with built-in payment transport.
|
|
12
|
+
|
|
13
|
+
| Parameter | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `x402_client` | `x402Client \| x402HTTPClient` | The async x402 client instance |
|
|
16
|
+
| `**kwargs` | `Any` | Additional arguments forwarded to `httpx.AsyncClient` |
|
|
17
|
+
|
|
18
|
+
**Returns**: `httpx.AsyncClient` subclass with payment handling.
|
|
19
|
+
|
|
20
|
+
**Usage**:
|
|
21
|
+
```python
|
|
22
|
+
async with x402HttpxClient(x402, timeout=30.0) as client:
|
|
23
|
+
response = await client.get(url)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### `wrapHttpxWithPayment(x402_client, **httpx_kwargs)`
|
|
27
|
+
|
|
28
|
+
Factory function that creates a new `httpx.AsyncClient` with payment transport.
|
|
29
|
+
|
|
30
|
+
| Parameter | Type | Description |
|
|
31
|
+
|-----------|------|-------------|
|
|
32
|
+
| `x402_client` | `x402Client \| x402HTTPClient` | The async x402 client instance |
|
|
33
|
+
| `**httpx_kwargs` | `Any` | Additional arguments forwarded to `httpx.AsyncClient` |
|
|
34
|
+
|
|
35
|
+
**Returns**: `httpx.AsyncClient` (standard instance, not subclass).
|
|
36
|
+
|
|
37
|
+
### `wrapHttpxWithPaymentFromConfig(config, **httpx_kwargs)`
|
|
38
|
+
|
|
39
|
+
Creates an `httpx.AsyncClient` from a `x402ClientConfig` (builds `x402Client` internally).
|
|
40
|
+
|
|
41
|
+
| Parameter | Type | Description |
|
|
42
|
+
|-----------|------|-------------|
|
|
43
|
+
| `config` | `x402ClientConfig` | Configuration with scheme registrations |
|
|
44
|
+
| `**httpx_kwargs` | `Any` | Additional arguments forwarded to `httpx.AsyncClient` |
|
|
45
|
+
|
|
46
|
+
**Returns**: `httpx.AsyncClient` with payment transport.
|
|
47
|
+
|
|
48
|
+
### `x402_httpx_transport(client, transport=None)`
|
|
49
|
+
|
|
50
|
+
Low-level function that returns an `x402AsyncTransport` for manual `httpx.AsyncClient` construction.
|
|
51
|
+
|
|
52
|
+
| Parameter | Type | Default | Description |
|
|
53
|
+
|-----------|------|---------|-------------|
|
|
54
|
+
| `client` | `x402Client \| x402HTTPClient` | required | The async x402 client |
|
|
55
|
+
| `transport` | `AsyncBaseTransport \| None` | `None` | Custom underlying transport |
|
|
56
|
+
|
|
57
|
+
**Returns**: `x402AsyncTransport` (an `httpx.AsyncBaseTransport`).
|
|
58
|
+
|
|
59
|
+
### `x402AsyncTransport`
|
|
60
|
+
|
|
61
|
+
The transport implementation that intercepts 402 responses and handles payments.
|
|
62
|
+
|
|
63
|
+
| Parameter | Type | Default | Description |
|
|
64
|
+
|-----------|------|---------|-------------|
|
|
65
|
+
| `client` | `x402Client \| x402HTTPClient` | required | The async x402 client |
|
|
66
|
+
| `transport` | `AsyncBaseTransport \| None` | `None` | Custom underlying transport |
|
|
67
|
+
|
|
68
|
+
**Behavior**:
|
|
69
|
+
1. Sends the original request via the underlying transport
|
|
70
|
+
2. If response is 402, parses PaymentRequirements from headers/body
|
|
71
|
+
3. Creates payment payload via the registered scheme
|
|
72
|
+
4. Retries request with `PAYMENT-SIGNATURE` header
|
|
73
|
+
5. If retry also returns 402, passes through as-is (no infinite loop)
|
|
74
|
+
|
|
75
|
+
## requests APIs
|
|
76
|
+
|
|
77
|
+
All requests APIs are in `x402.http.clients.requests`. They use `x402ClientSync` (sync).
|
|
78
|
+
|
|
79
|
+
### `x402_requests(client, **adapter_kwargs)`
|
|
80
|
+
|
|
81
|
+
Creates a new `requests.Session` with payment adapter pre-configured.
|
|
82
|
+
|
|
83
|
+
| Parameter | Type | Description |
|
|
84
|
+
|-----------|------|-------------|
|
|
85
|
+
| `client` | `x402ClientSync \| x402HTTPClientSync` | The sync x402 client instance |
|
|
86
|
+
| `**adapter_kwargs` | `Any` | Additional arguments forwarded to `x402HTTPAdapter` |
|
|
87
|
+
|
|
88
|
+
**Returns**: `requests.Session` with payment handling for `https://` and `http://`.
|
|
89
|
+
|
|
90
|
+
### `wrapRequestsWithPayment(session, client, **adapter_kwargs)`
|
|
91
|
+
|
|
92
|
+
Wraps an existing `requests.Session` with payment adapter.
|
|
93
|
+
|
|
94
|
+
| Parameter | Type | Description |
|
|
95
|
+
|-----------|------|-------------|
|
|
96
|
+
| `session` | `requests.Session` | Existing session to wrap |
|
|
97
|
+
| `client` | `x402ClientSync \| x402HTTPClientSync` | The sync x402 client instance |
|
|
98
|
+
| `**adapter_kwargs` | `Any` | Additional arguments forwarded to `x402HTTPAdapter` |
|
|
99
|
+
|
|
100
|
+
**Returns**: Same `requests.Session` (mutated in place). Mounts adapter for `https://` and `http://`.
|
|
101
|
+
|
|
102
|
+
### `wrapRequestsWithPaymentFromConfig(session, config, **adapter_kwargs)`
|
|
103
|
+
|
|
104
|
+
Wraps a session from a `x402ClientConfig` (builds `x402ClientSync` internally).
|
|
105
|
+
|
|
106
|
+
| Parameter | Type | Description |
|
|
107
|
+
|-----------|------|-------------|
|
|
108
|
+
| `session` | `requests.Session` | Existing session to wrap |
|
|
109
|
+
| `config` | `x402ClientConfig` | Configuration with scheme registrations |
|
|
110
|
+
| `**adapter_kwargs` | `Any` | Additional arguments forwarded to `x402HTTPAdapter` |
|
|
111
|
+
|
|
112
|
+
**Returns**: Same `requests.Session` with payment handling.
|
|
113
|
+
|
|
114
|
+
### `x402_http_adapter(client, **kwargs)`
|
|
115
|
+
|
|
116
|
+
Low-level function that returns an `x402HTTPAdapter` for manual `session.mount()`.
|
|
117
|
+
|
|
118
|
+
| Parameter | Type | Description |
|
|
119
|
+
|-----------|------|-------------|
|
|
120
|
+
| `client` | `x402ClientSync \| x402HTTPClientSync` | The sync x402 client |
|
|
121
|
+
| `**kwargs` | `Any` | Additional arguments forwarded to `x402HTTPAdapter` |
|
|
122
|
+
|
|
123
|
+
**Returns**: `x402HTTPAdapter` (a `requests.adapters.HTTPAdapter`).
|
|
124
|
+
|
|
125
|
+
### `x402HTTPAdapter`
|
|
126
|
+
|
|
127
|
+
The adapter implementation that intercepts 402 responses and handles payments.
|
|
128
|
+
|
|
129
|
+
| Parameter | Type | Description |
|
|
130
|
+
|-----------|------|-------------|
|
|
131
|
+
| `client` | `x402ClientSync \| x402HTTPClientSync` | The sync x402 client |
|
|
132
|
+
| `**kwargs` | `Any` | Forwarded to `HTTPAdapter.__init__` (e.g., `max_retries`, `pool_connections`) |
|
|
133
|
+
|
|
134
|
+
**Runtime check**: Raises `TypeError` if passed an async `x402Client` instead of `x402ClientSync`.
|
|
135
|
+
|
|
136
|
+
**Behavior**:
|
|
137
|
+
1. Sends the original request via the standard HTTPAdapter
|
|
138
|
+
2. If response is 402, parses PaymentRequirements from headers/body
|
|
139
|
+
3. Creates payment payload synchronously via the registered scheme
|
|
140
|
+
4. Clones request with `PAYMENT-SIGNATURE` header and `Payment-Retry: 1` marker
|
|
141
|
+
5. Retries request; if retry returns 402, passes through as-is
|
|
142
|
+
|
|
143
|
+
## ClientAvmSigner Implementation
|
|
144
|
+
|
|
145
|
+
The signer protocol that both httpx and requests clients depend on:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
class ClientAvmSigner(Protocol):
|
|
149
|
+
@property
|
|
150
|
+
def address(self) -> str:
|
|
151
|
+
"""58-character Algorand address."""
|
|
152
|
+
...
|
|
153
|
+
|
|
154
|
+
def sign_transactions(
|
|
155
|
+
self,
|
|
156
|
+
unsigned_txns: list[bytes],
|
|
157
|
+
indexes_to_sign: list[int],
|
|
158
|
+
) -> list[bytes | None]:
|
|
159
|
+
"""Sign specified transactions in a group."""
|
|
160
|
+
...
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Encoding Boundaries
|
|
164
|
+
|
|
165
|
+
The x402 SDK passes raw msgpack bytes. Python algosdk (v2.11.1) uses base64 strings. The conversion happens inside `sign_transactions()`:
|
|
166
|
+
|
|
167
|
+
| Direction | Code |
|
|
168
|
+
|-----------|------|
|
|
169
|
+
| Raw bytes to algosdk | `encoding.msgpack_decode(base64.b64encode(raw).decode())` |
|
|
170
|
+
| algosdk to raw bytes | `base64.b64decode(encoding.msgpack_encode(obj))` |
|
|
171
|
+
| Private key for signing | `base64.b64encode(secret_key).decode()` |
|
|
172
|
+
|
|
173
|
+
### Complete Implementation
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
import base64
|
|
177
|
+
from algosdk import encoding
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class AlgorandSigner:
|
|
181
|
+
def __init__(self, secret_key: bytes, address: str):
|
|
182
|
+
self._secret_key = secret_key
|
|
183
|
+
self._address = address
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def address(self) -> str:
|
|
187
|
+
return self._address
|
|
188
|
+
|
|
189
|
+
def sign_transactions(
|
|
190
|
+
self,
|
|
191
|
+
unsigned_txns: list[bytes],
|
|
192
|
+
indexes_to_sign: list[int],
|
|
193
|
+
) -> list[bytes | None]:
|
|
194
|
+
sk_b64 = base64.b64encode(self._secret_key).decode()
|
|
195
|
+
result: list[bytes | None] = []
|
|
196
|
+
for i, txn_bytes in enumerate(unsigned_txns):
|
|
197
|
+
if i in indexes_to_sign:
|
|
198
|
+
txn = encoding.msgpack_decode(
|
|
199
|
+
base64.b64encode(txn_bytes).decode()
|
|
200
|
+
)
|
|
201
|
+
signed = txn.sign(sk_b64)
|
|
202
|
+
result.append(
|
|
203
|
+
base64.b64decode(encoding.msgpack_encode(signed))
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
result.append(None)
|
|
207
|
+
return result
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Registration Functions
|
|
211
|
+
|
|
212
|
+
### `register_exact_avm_client(client, signer, networks=None, algod_url=None)`
|
|
213
|
+
|
|
214
|
+
Registers AVM payment scheme on an x402 client (works with both async and sync).
|
|
215
|
+
|
|
216
|
+
| Parameter | Type | Default | Description |
|
|
217
|
+
|-----------|------|---------|-------------|
|
|
218
|
+
| `client` | `x402Client \| x402ClientSync` | required | The x402 client instance |
|
|
219
|
+
| `signer` | `ClientAvmSigner` | required | Signer implementation |
|
|
220
|
+
| `networks` | `str \| list[str] \| None` | `None` | Default: registers `"algorand:*"` wildcard + V1 names |
|
|
221
|
+
| `algod_url` | `str \| None` | `None` | Custom algod endpoint |
|
|
222
|
+
|
|
223
|
+
### Network Registration Behavior
|
|
224
|
+
|
|
225
|
+
By default, `register_exact_avm_client` registers:
|
|
226
|
+
- V2: `"algorand:*"` wildcard (matches any Algorand CAIP-2 network)
|
|
227
|
+
- V1: All legacy network names (`"algorand-mainnet"`, `"algorand-testnet"`)
|
|
228
|
+
|
|
229
|
+
For specific networks:
|
|
230
|
+
```python
|
|
231
|
+
register_exact_avm_client(x402, signer, networks="algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=")
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Error Classes
|
|
235
|
+
|
|
236
|
+
### httpx Errors
|
|
237
|
+
|
|
238
|
+
| Error | Description |
|
|
239
|
+
|-------|-------------|
|
|
240
|
+
| `PaymentError` | Base class for all payment errors |
|
|
241
|
+
| `PaymentAlreadyAttemptedError` | Payment retry already attempted (prevents infinite loop) |
|
|
242
|
+
| `MissingRequestConfigError` | Missing request configuration |
|
|
243
|
+
|
|
244
|
+
### requests Errors
|
|
245
|
+
|
|
246
|
+
| Error | Description |
|
|
247
|
+
|-------|-------------|
|
|
248
|
+
| `PaymentError` | Payment handling failed |
|
|
249
|
+
| `TypeError` | Passed async `x402Client` instead of `x402ClientSync` |
|
|
250
|
+
|
|
251
|
+
## Environment Variables
|
|
252
|
+
|
|
253
|
+
| Variable | Description | Example |
|
|
254
|
+
|----------|-------------|---------|
|
|
255
|
+
| `AVM_PRIVATE_KEY` | Base64-encoded 64-byte key (32-byte seed + 32-byte pubkey) | `base64(seed \|\| pubkey)` |
|
|
256
|
+
| `RESOURCE_URL` | The paid endpoint URL | `https://api.example.com/paid` |
|
|
257
|
+
| `ALGOD_TESTNET_URL` | Custom algod testnet endpoint (optional) | `https://testnet-api.algonode.cloud` |
|
|
258
|
+
| `ALGOD_MAINNET_URL` | Custom algod mainnet endpoint (optional) | `https://mainnet-api.algonode.cloud` |
|
|
259
|
+
|
|
260
|
+
### Private Key Format
|
|
261
|
+
|
|
262
|
+
The `AVM_PRIVATE_KEY` is a Base64-encoded 64-byte key:
|
|
263
|
+
- First 32 bytes: Ed25519 seed (private key)
|
|
264
|
+
- Last 32 bytes: Ed25519 public key
|
|
265
|
+
- Address derivation: `encoding.encode_address(secret_key[32:])`
|
|
266
|
+
|
|
267
|
+
## Payment Flow
|
|
268
|
+
|
|
269
|
+
1. The HTTP client sends a GET request to the resource URL
|
|
270
|
+
2. If the server returns HTTP 402, the transport/adapter intercepts the response
|
|
271
|
+
3. It parses the `PaymentRequired` data from response headers (V2) or body (V1)
|
|
272
|
+
4. The registered `ExactAvmScheme` creates an atomic transaction group (ASA transfer) and signs it via the signer
|
|
273
|
+
5. Payment headers are added to a retry request
|
|
274
|
+
6. The server validates payment and returns the resource
|
|
275
|
+
|
|
276
|
+
## Sync vs Async Comparison
|
|
277
|
+
|
|
278
|
+
| Feature | httpx (async) | requests (sync) |
|
|
279
|
+
|---------|---------------|-----------------|
|
|
280
|
+
| x402 Client | `x402Client` | `x402ClientSync` |
|
|
281
|
+
| Convenience | `x402HttpxClient(x402)` | `x402_requests(x402)` |
|
|
282
|
+
| Wrap existing | `wrapHttpxWithPayment(x402)` | `wrapRequestsWithPayment(session, x402)` |
|
|
283
|
+
| From config | `wrapHttpxWithPaymentFromConfig(config)` | `wrapRequestsWithPaymentFromConfig(session, config)` |
|
|
284
|
+
| Low-level | `x402_httpx_transport(x402)` | `x402_http_adapter(x402)` |
|
|
285
|
+
| Transport class | `x402AsyncTransport` | `x402HTTPAdapter` |
|
|
286
|
+
| Context manager | `async with ... as client:` | `with ... as session:` |
|
|
287
|
+
| Payment creation | `await client.create_payment_payload(...)` | `client.create_payment_payload(...)` |
|
|
288
|
+
|
|
289
|
+
## pip Install Commands
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# httpx (async) + Algorand
|
|
293
|
+
pip install "x402-avm[httpx,avm]"
|
|
294
|
+
|
|
295
|
+
# requests (sync) + Algorand
|
|
296
|
+
pip install "x402-avm[requests,avm]"
|
|
297
|
+
|
|
298
|
+
# Both clients + Algorand
|
|
299
|
+
pip install "x402-avm[httpx,requests,avm]"
|
|
300
|
+
|
|
301
|
+
# Everything
|
|
302
|
+
pip install "x402-avm[all]"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## External Resources
|
|
306
|
+
|
|
307
|
+
- [x402-avm on PyPI](https://pypi.org/project/x402-avm/)
|
|
308
|
+
- [x402-avm Examples Repository](https://github.com/GoPlausible/x402-avm/tree/branch-v2-algorand-publish/examples/)
|
|
309
|
+
- [x402 Algorand Documentation](https://github.com/GoPlausible/.github/blob/main/profile/algorand-x402-documentation/)
|
|
310
|
+
- [httpx Documentation](https://www.python-httpx.org/)
|
|
311
|
+
- [requests Documentation](https://docs.python-requests.org/)
|
|
312
|
+
- [py-algorand-sdk Documentation](https://py-algorand-sdk.readthedocs.io/)
|
|
313
|
+
- [Coinbase x402 Specification](https://github.com/coinbase/x402)
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Creating x402 HTTP Clients in Python
|
|
2
|
+
|
|
3
|
+
Build Python HTTP clients with httpx (async) or requests (sync) that automatically detect 402 Payment Required responses, create Algorand USDC payment transactions, and retry with payment headers.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Before using this skill, ensure:
|
|
8
|
+
|
|
9
|
+
1. **Python 3.10+** is installed
|
|
10
|
+
2. **An Algorand private key** -- Base64-encoded 64-byte key (set as `AVM_PRIVATE_KEY` env var)
|
|
11
|
+
3. **USDC opt-in** -- The paying address must have opted into the USDC ASA on the target network
|
|
12
|
+
4. **A payment-protected endpoint** -- A server running x402 middleware to test against
|
|
13
|
+
|
|
14
|
+
## Core Workflow: Automatic 402 Handling
|
|
15
|
+
|
|
16
|
+
The x402 HTTP client wraps your HTTP library's transport layer. When a 402 response is received, it automatically creates a payment and retries.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
client.get("https://api.example.com/paid")
|
|
20
|
+
|
|
|
21
|
+
v
|
|
22
|
+
HTTP Transport sends request
|
|
23
|
+
|
|
|
24
|
+
v
|
|
25
|
+
Server returns 402 + PaymentRequirements
|
|
26
|
+
|
|
|
27
|
+
v
|
|
28
|
+
x402 Transport intercepts 402
|
|
29
|
+
|
|
|
30
|
+
v
|
|
31
|
+
ExactAvmScheme creates atomic transaction group
|
|
32
|
+
|
|
|
33
|
+
v
|
|
34
|
+
ClientAvmSigner signs payment transaction
|
|
35
|
+
|
|
|
36
|
+
v
|
|
37
|
+
Transport retries with PAYMENT-SIGNATURE header
|
|
38
|
+
|
|
|
39
|
+
v
|
|
40
|
+
Server verifies, settles, returns 200 + data
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How to Proceed
|
|
44
|
+
|
|
45
|
+
### Step 1: Choose Async or Sync
|
|
46
|
+
|
|
47
|
+
| Library | Variant | x402 Client | Install |
|
|
48
|
+
|---------|---------|-------------|---------|
|
|
49
|
+
| httpx (async) | `x402HttpxClient` | `x402Client` | `pip install "x402-avm[httpx,avm]"` |
|
|
50
|
+
| requests (sync) | `x402_requests` | `x402ClientSync` | `pip install "x402-avm[requests,avm]"` |
|
|
51
|
+
|
|
52
|
+
### Step 2: Implement ClientAvmSigner
|
|
53
|
+
|
|
54
|
+
Both async and sync clients need the same signer implementation:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import base64
|
|
58
|
+
from algosdk import encoding
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class AlgorandSigner:
|
|
62
|
+
def __init__(self, secret_key: bytes, address: str):
|
|
63
|
+
self._secret_key = secret_key
|
|
64
|
+
self._address = address
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def address(self) -> str:
|
|
68
|
+
return self._address
|
|
69
|
+
|
|
70
|
+
def sign_transactions(
|
|
71
|
+
self,
|
|
72
|
+
unsigned_txns: list[bytes],
|
|
73
|
+
indexes_to_sign: list[int],
|
|
74
|
+
) -> list[bytes | None]:
|
|
75
|
+
sk_b64 = base64.b64encode(self._secret_key).decode()
|
|
76
|
+
result: list[bytes | None] = []
|
|
77
|
+
for i, txn_bytes in enumerate(unsigned_txns):
|
|
78
|
+
if i in indexes_to_sign:
|
|
79
|
+
txn = encoding.msgpack_decode(
|
|
80
|
+
base64.b64encode(txn_bytes).decode()
|
|
81
|
+
)
|
|
82
|
+
signed = txn.sign(sk_b64)
|
|
83
|
+
result.append(
|
|
84
|
+
base64.b64decode(encoding.msgpack_encode(signed))
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
result.append(None)
|
|
88
|
+
return result
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Step 3: Create Signer from Environment
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import os
|
|
95
|
+
import base64
|
|
96
|
+
from algosdk import encoding
|
|
97
|
+
|
|
98
|
+
avm_private_key = os.environ["AVM_PRIVATE_KEY"]
|
|
99
|
+
secret_key = base64.b64decode(avm_private_key)
|
|
100
|
+
if len(secret_key) != 64:
|
|
101
|
+
raise ValueError("AVM_PRIVATE_KEY must be a Base64-encoded 64-byte key")
|
|
102
|
+
|
|
103
|
+
avm_address = encoding.encode_address(secret_key[32:])
|
|
104
|
+
signer = AlgorandSigner(secret_key, avm_address)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Step 4: Register and Make Requests
|
|
108
|
+
|
|
109
|
+
**With httpx (async):**
|
|
110
|
+
```python
|
|
111
|
+
import asyncio
|
|
112
|
+
from x402 import x402Client
|
|
113
|
+
from x402.http.clients.httpx import x402HttpxClient
|
|
114
|
+
from x402.mechanisms.avm.exact.register import register_exact_avm_client
|
|
115
|
+
|
|
116
|
+
async def main():
|
|
117
|
+
x402 = x402Client()
|
|
118
|
+
register_exact_avm_client(x402, signer)
|
|
119
|
+
|
|
120
|
+
async with x402HttpxClient(x402) as client:
|
|
121
|
+
response = await client.get("https://api.example.com/paid-resource")
|
|
122
|
+
print(response.status_code)
|
|
123
|
+
print(response.text)
|
|
124
|
+
|
|
125
|
+
asyncio.run(main())
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**With requests (sync):**
|
|
129
|
+
```python
|
|
130
|
+
from x402 import x402ClientSync
|
|
131
|
+
from x402.http.clients.requests import x402_requests
|
|
132
|
+
from x402.mechanisms.avm.exact.register import register_exact_avm_client
|
|
133
|
+
|
|
134
|
+
x402 = x402ClientSync()
|
|
135
|
+
register_exact_avm_client(x402, signer)
|
|
136
|
+
|
|
137
|
+
with x402_requests(x402) as session:
|
|
138
|
+
response = session.get("https://api.example.com/paid-resource")
|
|
139
|
+
print(response.status_code)
|
|
140
|
+
print(response.text)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Important Rules / Guidelines
|
|
144
|
+
|
|
145
|
+
1. **httpx uses `x402Client` (async), requests uses `x402ClientSync` (sync)** -- mixing causes `TypeError`
|
|
146
|
+
2. **Private key format** -- `AVM_PRIVATE_KEY` must be Base64-encoded 64 bytes (32-byte seed + 32-byte pubkey)
|
|
147
|
+
3. **algosdk encoding boundaries** -- `msgpack_decode` expects base64 string, `msgpack_encode` returns base64 string, `sign()` expects base64 key
|
|
148
|
+
4. **No infinite retry** -- The transport retries at most once; if the retry still returns 402, it passes through
|
|
149
|
+
5. **Register before creating HTTP client** -- Call `register_exact_avm_client` before wrapping with httpx/requests
|
|
150
|
+
6. **USDC opt-in required** -- The paying address must have opted into USDC on the target network
|
|
151
|
+
|
|
152
|
+
## Alternative Client Creation Methods
|
|
153
|
+
|
|
154
|
+
### `wrapHttpxWithPayment` (returns standard httpx.AsyncClient)
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from x402.http.clients.httpx import wrapHttpxWithPayment
|
|
158
|
+
|
|
159
|
+
async with wrapHttpxWithPayment(x402, timeout=30.0) as client:
|
|
160
|
+
response = await client.get(url)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### `wrapRequestsWithPayment` (wraps existing session)
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from x402.http.clients.requests import wrapRequestsWithPayment
|
|
167
|
+
|
|
168
|
+
session = requests.Session()
|
|
169
|
+
session.headers.update({"Authorization": "Bearer token"})
|
|
170
|
+
wrapRequestsWithPayment(session, x402)
|
|
171
|
+
response = session.get(url)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Config-based setup
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from x402 import x402ClientConfig, SchemeRegistration
|
|
178
|
+
from x402.mechanisms.avm.exact import ExactAvmClientScheme
|
|
179
|
+
|
|
180
|
+
config = x402ClientConfig(
|
|
181
|
+
schemes=[
|
|
182
|
+
SchemeRegistration(
|
|
183
|
+
network="algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
|
|
184
|
+
client=ExactAvmClientScheme(signer=my_signer),
|
|
185
|
+
),
|
|
186
|
+
],
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Common Errors / Troubleshooting
|
|
191
|
+
|
|
192
|
+
| Error | Cause | Solution |
|
|
193
|
+
|-------|-------|----------|
|
|
194
|
+
| `TypeError: x402HTTPAdapter requires sync client` | Used `x402Client` with requests | Use `x402ClientSync` for requests |
|
|
195
|
+
| `PaymentError: Failed to handle payment` | Payment creation or encoding failed | Check signer setup, private key, network registration |
|
|
196
|
+
| `ValueError: AVM_PRIVATE_KEY must be...` | Key is wrong length | Ensure key is Base64-encoded 64-byte key |
|
|
197
|
+
| `ImportError: AVM mechanism requires...` | `py-algorand-sdk` not installed | `pip install "x402-avm[avm]"` |
|
|
198
|
+
| `ImportError: requests client requires...` | `requests` not installed | `pip install "x402-avm[requests]"` |
|
|
199
|
+
| `ImportError: httpx client requires...` | `httpx` not installed | `pip install "x402-avm[httpx]"` |
|
|
200
|
+
| 402 returned after retry | Payment invalid or facilitator rejected | Check USDC balance, opt-in status, correct network |
|
|
201
|
+
|
|
202
|
+
## References / Further Reading
|
|
203
|
+
|
|
204
|
+
- [create-python-x402-client-reference.md](./create-python-x402-client-reference.md) - Detailed API reference for httpx and requests
|
|
205
|
+
- [create-python-x402-client-examples.md](./create-python-x402-client-examples.md) - Complete code examples
|
|
206
|
+
- [x402-avm Examples Repository](https://github.com/GoPlausible/x402-avm/tree/branch-v2-algorand-publish/examples/)
|
|
207
|
+
- [x402 Algorand Documentation](https://github.com/GoPlausible/.github/blob/main/profile/algorand-x402-documentation/)
|