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