@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,554 @@
|
|
|
1
|
+
# x402-avm Python Core and AVM Examples
|
|
2
|
+
|
|
3
|
+
## ClientAvmSigner Protocol
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from x402.mechanisms.avm.signer import ClientAvmSigner
|
|
7
|
+
|
|
8
|
+
class ClientAvmSigner(Protocol):
|
|
9
|
+
@property
|
|
10
|
+
def address(self) -> str:
|
|
11
|
+
"""58-character Algorand address."""
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
def sign_transactions(
|
|
15
|
+
self,
|
|
16
|
+
unsigned_txns: list[bytes],
|
|
17
|
+
indexes_to_sign: list[int],
|
|
18
|
+
) -> list[bytes | None]:
|
|
19
|
+
"""Sign specified transactions in a group.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
unsigned_txns: List of unsigned transaction bytes (msgpack encoded).
|
|
23
|
+
indexes_to_sign: Indexes of transactions this signer should sign.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List parallel to unsigned_txns, with signed bytes at
|
|
27
|
+
indexes_to_sign and None elsewhere.
|
|
28
|
+
"""
|
|
29
|
+
...
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Client Signer Implementation
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import os
|
|
38
|
+
import base64
|
|
39
|
+
from x402.mechanisms.avm.signer import ClientAvmSigner
|
|
40
|
+
from algosdk import encoding
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PrivateKeySigner:
|
|
44
|
+
"""
|
|
45
|
+
ClientAvmSigner implementation using a base64-encoded private key.
|
|
46
|
+
|
|
47
|
+
Key format: 64 bytes = [32-byte seed][32-byte public key]
|
|
48
|
+
algosdk expects base64-encoded 64-byte key for signing.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, private_key_b64: str):
|
|
52
|
+
self._secret_key = base64.b64decode(private_key_b64)
|
|
53
|
+
if len(self._secret_key) != 64:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"Invalid key length: expected 64, got {len(self._secret_key)}"
|
|
56
|
+
)
|
|
57
|
+
self._address = encoding.encode_address(self._secret_key[32:])
|
|
58
|
+
# algosdk.Transaction.sign() expects base64 string
|
|
59
|
+
self._signing_key = base64.b64encode(self._secret_key).decode()
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def address(self) -> str:
|
|
63
|
+
return self._address
|
|
64
|
+
|
|
65
|
+
def sign_transactions(
|
|
66
|
+
self,
|
|
67
|
+
unsigned_txns: list[bytes],
|
|
68
|
+
indexes_to_sign: list[int],
|
|
69
|
+
) -> list[bytes | None]:
|
|
70
|
+
result: list[bytes | None] = []
|
|
71
|
+
for i, txn_bytes in enumerate(unsigned_txns):
|
|
72
|
+
if i not in indexes_to_sign:
|
|
73
|
+
result.append(None)
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
# IMPORTANT: algosdk.encoding.msgpack_decode expects base64 string
|
|
77
|
+
b64_txn = base64.b64encode(txn_bytes).decode("utf-8")
|
|
78
|
+
txn_obj = encoding.msgpack_decode(b64_txn)
|
|
79
|
+
|
|
80
|
+
# Sign: Transaction.sign() expects base64 private key string
|
|
81
|
+
signed_txn = txn_obj.sign(self._signing_key)
|
|
82
|
+
|
|
83
|
+
# IMPORTANT: algosdk.encoding.msgpack_encode returns base64 string
|
|
84
|
+
signed_b64 = encoding.msgpack_encode(signed_txn)
|
|
85
|
+
signed_bytes = base64.b64decode(signed_b64)
|
|
86
|
+
|
|
87
|
+
result.append(signed_bytes)
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Usage:
|
|
92
|
+
signer = PrivateKeySigner(os.environ["AVM_PRIVATE_KEY"])
|
|
93
|
+
print(f"Signer address: {signer.address}")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## FacilitatorAvmSigner Protocol
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from x402.mechanisms.avm.signer import FacilitatorAvmSigner
|
|
102
|
+
|
|
103
|
+
class FacilitatorAvmSigner(Protocol):
|
|
104
|
+
def get_addresses(self) -> list[str]:
|
|
105
|
+
"""Get all managed fee payer addresses."""
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
def sign_transaction(
|
|
109
|
+
self, txn_bytes: bytes, fee_payer: str, network: str,
|
|
110
|
+
) -> bytes:
|
|
111
|
+
"""Sign a single transaction with the fee payer's key."""
|
|
112
|
+
...
|
|
113
|
+
|
|
114
|
+
def sign_group(
|
|
115
|
+
self,
|
|
116
|
+
group_bytes: list[bytes],
|
|
117
|
+
fee_payer: str,
|
|
118
|
+
indexes_to_sign: list[int],
|
|
119
|
+
network: str,
|
|
120
|
+
) -> list[bytes]:
|
|
121
|
+
"""Sign specified transactions in a group."""
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
def simulate_group(
|
|
125
|
+
self, group_bytes: list[bytes], network: str,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Simulate a transaction group (raises on failure)."""
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
def send_group(
|
|
131
|
+
self, group_bytes: list[bytes], network: str,
|
|
132
|
+
) -> str:
|
|
133
|
+
"""Send a transaction group, returns txid."""
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
def confirm_transaction(
|
|
137
|
+
self, txid: str, network: str, rounds: int = 4,
|
|
138
|
+
) -> None:
|
|
139
|
+
"""Wait for transaction confirmation."""
|
|
140
|
+
...
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Facilitator Signer Implementation
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
import base64
|
|
149
|
+
from x402.mechanisms.avm.signer import FacilitatorAvmSigner
|
|
150
|
+
from x402.mechanisms.avm.constants import NETWORK_CONFIGS
|
|
151
|
+
from algosdk import encoding, transaction
|
|
152
|
+
from algosdk.v2client import algod
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class AlgorandFacilitatorSigner:
|
|
156
|
+
def __init__(self, private_key_b64: str, algod_url: str = "", algod_token: str = ""):
|
|
157
|
+
self._secret_key = base64.b64decode(private_key_b64)
|
|
158
|
+
self._address = encoding.encode_address(self._secret_key[32:])
|
|
159
|
+
self._signing_key = base64.b64encode(self._secret_key).decode()
|
|
160
|
+
self._clients: dict[str, algod.AlgodClient] = {}
|
|
161
|
+
if algod_url:
|
|
162
|
+
self._default_client = algod.AlgodClient(algod_token, algod_url)
|
|
163
|
+
else:
|
|
164
|
+
self._default_client = None
|
|
165
|
+
|
|
166
|
+
def _get_client(self, network: str) -> algod.AlgodClient:
|
|
167
|
+
if network not in self._clients:
|
|
168
|
+
if self._default_client:
|
|
169
|
+
self._clients[network] = self._default_client
|
|
170
|
+
else:
|
|
171
|
+
config = NETWORK_CONFIGS.get(network, {})
|
|
172
|
+
url = config.get("algod_url", "https://testnet-api.algonode.cloud")
|
|
173
|
+
self._clients[network] = algod.AlgodClient("", url)
|
|
174
|
+
return self._clients[network]
|
|
175
|
+
|
|
176
|
+
def get_addresses(self) -> list[str]:
|
|
177
|
+
return [self._address]
|
|
178
|
+
|
|
179
|
+
def sign_transaction(self, txn_bytes, fee_payer, network):
|
|
180
|
+
b64 = base64.b64encode(txn_bytes).decode("utf-8")
|
|
181
|
+
txn_obj = encoding.msgpack_decode(b64)
|
|
182
|
+
signed = txn_obj.sign(self._signing_key)
|
|
183
|
+
return base64.b64decode(encoding.msgpack_encode(signed))
|
|
184
|
+
|
|
185
|
+
def sign_group(self, group_bytes, fee_payer, indexes_to_sign, network):
|
|
186
|
+
result = list(group_bytes)
|
|
187
|
+
for i in indexes_to_sign:
|
|
188
|
+
result[i] = self.sign_transaction(group_bytes[i], fee_payer, network)
|
|
189
|
+
return result
|
|
190
|
+
|
|
191
|
+
def simulate_group(self, group_bytes, network):
|
|
192
|
+
client = self._get_client(network)
|
|
193
|
+
stxns = []
|
|
194
|
+
for txn_bytes in group_bytes:
|
|
195
|
+
b64 = base64.b64encode(txn_bytes).decode("utf-8")
|
|
196
|
+
obj = encoding.msgpack_decode(b64)
|
|
197
|
+
if isinstance(obj, transaction.SignedTransaction):
|
|
198
|
+
stxns.append(obj)
|
|
199
|
+
else:
|
|
200
|
+
stxns.append(transaction.SignedTransaction(obj, None))
|
|
201
|
+
req = transaction.SimulateRequest(
|
|
202
|
+
txn_groups=[transaction.SimulateRequestTransactionGroup(txns=stxns)],
|
|
203
|
+
allow_empty_signatures=True,
|
|
204
|
+
)
|
|
205
|
+
result = client.simulate_raw_transactions(req)
|
|
206
|
+
for group in result.get("txn-groups", []):
|
|
207
|
+
if group.get("failure-message"):
|
|
208
|
+
raise Exception(f"Simulation failed: {group['failure-message']}")
|
|
209
|
+
|
|
210
|
+
def send_group(self, group_bytes, network):
|
|
211
|
+
client = self._get_client(network)
|
|
212
|
+
return client.send_raw_transaction(base64.b64encode(b"".join(group_bytes)))
|
|
213
|
+
|
|
214
|
+
def confirm_transaction(self, txid, network, rounds=4):
|
|
215
|
+
client = self._get_client(network)
|
|
216
|
+
transaction.wait_for_confirmation(client, txid, rounds)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Constants
|
|
222
|
+
|
|
223
|
+
### Network Identifiers
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from x402.mechanisms.avm.constants import (
|
|
227
|
+
ALGORAND_MAINNET_CAIP2, # "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="
|
|
228
|
+
ALGORAND_TESTNET_CAIP2, # "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
|
|
229
|
+
SUPPORTED_NETWORKS, # [MAINNET_CAIP2, TESTNET_CAIP2]
|
|
230
|
+
MAINNET_GENESIS_HASH,
|
|
231
|
+
TESTNET_GENESIS_HASH,
|
|
232
|
+
V1_NETWORKS,
|
|
233
|
+
V1_TO_V2_NETWORK_MAP,
|
|
234
|
+
V2_TO_V1_NETWORK_MAP,
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### USDC Configuration
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from x402.mechanisms.avm.constants import (
|
|
242
|
+
USDC_MAINNET_ASA_ID, # 31566704
|
|
243
|
+
USDC_TESTNET_ASA_ID, # 10458941
|
|
244
|
+
DEFAULT_DECIMALS, # 6
|
|
245
|
+
NETWORK_CONFIGS,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
testnet_config = NETWORK_CONFIGS[ALGORAND_TESTNET_CAIP2]
|
|
249
|
+
usdc_info = testnet_config["default_asset"]
|
|
250
|
+
# {"asa_id": 10458941, "name": "USDC", "decimals": 6}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Algod Endpoints
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from x402.mechanisms.avm.constants import (
|
|
257
|
+
MAINNET_ALGOD_URL,
|
|
258
|
+
TESTNET_ALGOD_URL,
|
|
259
|
+
FALLBACK_ALGOD_MAINNET, # "https://mainnet-api.algonode.cloud"
|
|
260
|
+
FALLBACK_ALGOD_TESTNET, # "https://testnet-api.algonode.cloud"
|
|
261
|
+
)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Transaction Limits
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from x402.mechanisms.avm.constants import (
|
|
268
|
+
MAX_GROUP_SIZE, # 16
|
|
269
|
+
MIN_TXN_FEE, # 1000
|
|
270
|
+
)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Address Validation
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
from x402.mechanisms.avm.constants import AVM_ADDRESS_REGEX
|
|
277
|
+
import re
|
|
278
|
+
|
|
279
|
+
is_valid = bool(re.match(AVM_ADDRESS_REGEX, some_address))
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Utility Functions
|
|
285
|
+
|
|
286
|
+
### Address Validation
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
from x402.mechanisms.avm.utils import is_valid_address
|
|
290
|
+
|
|
291
|
+
is_valid_address("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
|
292
|
+
# => True
|
|
293
|
+
|
|
294
|
+
is_valid_address("invalid")
|
|
295
|
+
# => False
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Amount Conversion
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
from x402.mechanisms.avm.utils import to_atomic_amount, from_atomic_amount
|
|
302
|
+
|
|
303
|
+
to_atomic_amount(1.50) # => 1500000
|
|
304
|
+
to_atomic_amount(0.10) # => 100000
|
|
305
|
+
to_atomic_amount(0.000001) # => 1
|
|
306
|
+
|
|
307
|
+
from_atomic_amount(1500000) # => 1.5
|
|
308
|
+
from_atomic_amount(100000) # => 0.1
|
|
309
|
+
from_atomic_amount(1) # => 1e-06
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Transaction Encoding/Decoding
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
from x402.mechanisms.avm.utils import (
|
|
316
|
+
decode_transaction_bytes,
|
|
317
|
+
decode_base64_transaction,
|
|
318
|
+
decode_payment_group,
|
|
319
|
+
encode_transaction_group,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Decode raw bytes
|
|
323
|
+
info = decode_transaction_bytes(raw_txn_bytes)
|
|
324
|
+
print(info.type) # "axfer" or "pay"
|
|
325
|
+
print(info.sender) # Algorand address
|
|
326
|
+
print(info.fee) # in microAlgos
|
|
327
|
+
print(info.is_signed) # True/False
|
|
328
|
+
print(info.asset_amount) # for asset transfers
|
|
329
|
+
print(info.receiver) # for payments
|
|
330
|
+
|
|
331
|
+
# Decode from base64 string
|
|
332
|
+
info = decode_base64_transaction(base64_txn_string)
|
|
333
|
+
|
|
334
|
+
# Decode a full payment group
|
|
335
|
+
group_info = decode_payment_group(
|
|
336
|
+
payment_group=["base64txn1...", "base64txn2..."],
|
|
337
|
+
payment_index=0,
|
|
338
|
+
)
|
|
339
|
+
print(group_info.transactions)
|
|
340
|
+
print(group_info.group_id)
|
|
341
|
+
print(group_info.total_fee)
|
|
342
|
+
print(group_info.has_fee_payer)
|
|
343
|
+
|
|
344
|
+
# Encode to base64
|
|
345
|
+
encoded = encode_transaction_group([raw_bytes_1, raw_bytes_2])
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Network Utilities
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
from x402.mechanisms.avm.utils import (
|
|
352
|
+
normalize_network,
|
|
353
|
+
is_valid_network,
|
|
354
|
+
get_network_config,
|
|
355
|
+
get_usdc_asa_id,
|
|
356
|
+
get_genesis_hash,
|
|
357
|
+
network_from_genesis_hash,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
normalize_network("algorand-testnet")
|
|
361
|
+
# => "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
|
|
362
|
+
|
|
363
|
+
is_valid_network("algorand-testnet") # True
|
|
364
|
+
is_valid_network("unknown-network") # False
|
|
365
|
+
|
|
366
|
+
config = get_network_config("algorand-testnet")
|
|
367
|
+
# {"algod_url": "...", "genesis_hash": "...", "default_asset": {...}}
|
|
368
|
+
|
|
369
|
+
get_usdc_asa_id("algorand-testnet") # 10458941
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Security Validation
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
from x402.mechanisms.avm.utils import (
|
|
376
|
+
validate_no_security_risks,
|
|
377
|
+
validate_fee_payer_transaction,
|
|
378
|
+
is_blocked_transaction_type,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
info = decode_transaction_bytes(txn_bytes)
|
|
382
|
+
error_code = validate_no_security_risks(info)
|
|
383
|
+
if error_code:
|
|
384
|
+
raise ValueError(f"Security risk: {error_code}")
|
|
385
|
+
|
|
386
|
+
error_code = validate_fee_payer_transaction(info, expected_fee_payer="ABC...")
|
|
387
|
+
if error_code:
|
|
388
|
+
raise ValueError(f"Invalid fee payer: {error_code}")
|
|
389
|
+
|
|
390
|
+
is_blocked_transaction_type("keyreg") # True
|
|
391
|
+
is_blocked_transaction_type("axfer") # False
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Transaction Group Creation
|
|
397
|
+
|
|
398
|
+
### Simple Payment Group
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
import base64
|
|
402
|
+
from algosdk import transaction, encoding
|
|
403
|
+
from algosdk.v2client import algod
|
|
404
|
+
from x402.mechanisms.avm.constants import (
|
|
405
|
+
ALGORAND_TESTNET_CAIP2,
|
|
406
|
+
USDC_TESTNET_ASA_ID,
|
|
407
|
+
NETWORK_CONFIGS,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def create_simple_payment(sender, receiver, amount):
|
|
412
|
+
config = NETWORK_CONFIGS[ALGORAND_TESTNET_CAIP2]
|
|
413
|
+
client = algod.AlgodClient("", config["algod_url"])
|
|
414
|
+
params = client.suggested_params()
|
|
415
|
+
|
|
416
|
+
txn = transaction.AssetTransferTxn(
|
|
417
|
+
sender=sender, sp=params, receiver=receiver,
|
|
418
|
+
amt=amount, index=USDC_TESTNET_ASA_ID,
|
|
419
|
+
)
|
|
420
|
+
return [base64.b64decode(encoding.msgpack_encode(txn))]
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Fee-Abstracted Payment Group
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
import base64
|
|
427
|
+
from algosdk import transaction, encoding
|
|
428
|
+
from algosdk.v2client import algod
|
|
429
|
+
from x402.mechanisms.avm.constants import (
|
|
430
|
+
ALGORAND_TESTNET_CAIP2,
|
|
431
|
+
USDC_TESTNET_ASA_ID,
|
|
432
|
+
MIN_TXN_FEE,
|
|
433
|
+
NETWORK_CONFIGS,
|
|
434
|
+
)
|
|
435
|
+
from x402.mechanisms.avm.utils import encode_transaction_group
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def create_fee_abstracted_payment(sender, receiver, fee_payer, amount):
|
|
439
|
+
config = NETWORK_CONFIGS[ALGORAND_TESTNET_CAIP2]
|
|
440
|
+
client = algod.AlgodClient("", config["algod_url"])
|
|
441
|
+
params = client.suggested_params()
|
|
442
|
+
|
|
443
|
+
# Transaction 0: USDC transfer (fee = 0)
|
|
444
|
+
payment_params = transaction.SuggestedParams(
|
|
445
|
+
fee=0, first=params.first, last=params.last,
|
|
446
|
+
gh=params.gh, gen=params.gen, flat_fee=True,
|
|
447
|
+
)
|
|
448
|
+
payment_txn = transaction.AssetTransferTxn(
|
|
449
|
+
sender=sender, sp=payment_params, receiver=receiver,
|
|
450
|
+
amt=amount, index=USDC_TESTNET_ASA_ID,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# Transaction 1: Fee payer (self-payment, covers both fees)
|
|
454
|
+
fee_params = transaction.SuggestedParams(
|
|
455
|
+
fee=MIN_TXN_FEE * 2, first=params.first, last=params.last,
|
|
456
|
+
gh=params.gh, gen=params.gen, flat_fee=True,
|
|
457
|
+
)
|
|
458
|
+
fee_payer_txn = transaction.PaymentTxn(
|
|
459
|
+
sender=fee_payer, sp=fee_params,
|
|
460
|
+
receiver=fee_payer, amt=0,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# Assign group ID
|
|
464
|
+
gid = transaction.calculate_group_id([payment_txn, fee_payer_txn])
|
|
465
|
+
payment_txn.group = gid
|
|
466
|
+
fee_payer_txn.group = gid
|
|
467
|
+
|
|
468
|
+
payment_bytes = base64.b64decode(encoding.msgpack_encode(payment_txn))
|
|
469
|
+
fee_payer_bytes = base64.b64decode(encoding.msgpack_encode(fee_payer_txn))
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
"paymentGroup": encode_transaction_group([payment_bytes, fee_payer_bytes]),
|
|
473
|
+
"paymentIndex": 0,
|
|
474
|
+
"rawBytes": [payment_bytes, fee_payer_bytes],
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## ExactAvmScheme Registration
|
|
481
|
+
|
|
482
|
+
### Client Registration
|
|
483
|
+
|
|
484
|
+
```python
|
|
485
|
+
from x402 import x402Client
|
|
486
|
+
from x402.mechanisms.avm.exact import register_exact_avm_client
|
|
487
|
+
|
|
488
|
+
client = x402Client()
|
|
489
|
+
register_exact_avm_client(client, signer)
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Server Registration
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
from x402 import x402ResourceServer
|
|
496
|
+
from x402.mechanisms.avm.exact import register_exact_avm_server
|
|
497
|
+
|
|
498
|
+
server = x402ResourceServer(facilitator_url="https://facilitator.example.com")
|
|
499
|
+
register_exact_avm_server(server)
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Facilitator Registration
|
|
503
|
+
|
|
504
|
+
```python
|
|
505
|
+
from x402 import x402Facilitator
|
|
506
|
+
from x402.mechanisms.avm.exact import register_exact_avm_facilitator
|
|
507
|
+
from x402.mechanisms.avm import ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2
|
|
508
|
+
|
|
509
|
+
facilitator = x402Facilitator()
|
|
510
|
+
|
|
511
|
+
# Single network
|
|
512
|
+
register_exact_avm_facilitator(
|
|
513
|
+
facilitator, signer, networks=[ALGORAND_TESTNET_CAIP2]
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# Multiple networks
|
|
517
|
+
register_exact_avm_facilitator(
|
|
518
|
+
facilitator, signer, networks=[ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2]
|
|
519
|
+
)
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Client-Side Fee Abstraction
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
from x402 import x402Client
|
|
528
|
+
from x402.mechanisms.avm.exact import register_exact_avm_client
|
|
529
|
+
|
|
530
|
+
signer = MyClientSigner(os.environ["AVM_PRIVATE_KEY"])
|
|
531
|
+
client = x402Client()
|
|
532
|
+
register_exact_avm_client(client, signer)
|
|
533
|
+
|
|
534
|
+
# Fee abstraction is automatic when PaymentRequirements include feePayer info
|
|
535
|
+
response = await client.fetch("https://api.example.com/paid-resource")
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## algosdk Encoding Boundary Patterns
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
import base64
|
|
544
|
+
from algosdk import encoding
|
|
545
|
+
|
|
546
|
+
# Raw bytes -> algosdk object (DECODE)
|
|
547
|
+
raw_bytes: bytes = ...
|
|
548
|
+
b64_string = base64.b64encode(raw_bytes).decode("utf-8")
|
|
549
|
+
txn_obj = encoding.msgpack_decode(b64_string)
|
|
550
|
+
|
|
551
|
+
# algosdk object -> raw bytes (ENCODE)
|
|
552
|
+
b64_string = encoding.msgpack_encode(txn_obj)
|
|
553
|
+
raw_bytes = base64.b64decode(b64_string)
|
|
554
|
+
```
|