ap2-algorand 0.1.0__tar.gz
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.
- ap2_algorand-0.1.0/PKG-INFO +102 -0
- ap2_algorand-0.1.0/README.md +88 -0
- ap2_algorand-0.1.0/pyproject.toml +26 -0
- ap2_algorand-0.1.0/setup.cfg +4 -0
- ap2_algorand-0.1.0/src/ap2_algorand/__init__.py +63 -0
- ap2_algorand-0.1.0/src/ap2_algorand/cart_adapter.py +89 -0
- ap2_algorand-0.1.0/src/ap2_algorand/payment_method.py +71 -0
- ap2_algorand-0.1.0/src/ap2_algorand/settle.py +154 -0
- ap2_algorand-0.1.0/src/ap2_algorand.egg-info/PKG-INFO +102 -0
- ap2_algorand-0.1.0/src/ap2_algorand.egg-info/SOURCES.txt +11 -0
- ap2_algorand-0.1.0/src/ap2_algorand.egg-info/dependency_links.txt +1 -0
- ap2_algorand-0.1.0/src/ap2_algorand.egg-info/requires.txt +7 -0
- ap2_algorand-0.1.0/src/ap2_algorand.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ap2-algorand
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Google AP2 × Algorand adapter — settle x402 payments from AP2 CartMandate/PaymentMandate
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: algorand,x402,usdc,ap2,agent-payments,ai-agent
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: py-algorand-sdk<3.0,>=2.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0
|
|
11
|
+
Provides-Extra: ap2
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# ap2-algorand
|
|
16
|
+
|
|
17
|
+
**Google AP2 × Algorand x402 adapter** — settle AI agent payments on Algorand from an AP2 `PaymentMandate`.
|
|
18
|
+
|
|
19
|
+
Bridges the [Google Agent Payments Protocol (AP2)](https://github.com/google-agentic-commerce/AP2) with non-custodial USDC payments on Algorand, enforced by AVM MandateContracts.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install ap2-algorand
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
### Buyer: advertise x402 Algorand in a CartMandate
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from ap2_algorand import AlgorandUsdcMethodData, X402_METHOD_NAME
|
|
33
|
+
from ap2.types.payment_request import PaymentMethodData
|
|
34
|
+
|
|
35
|
+
method = PaymentMethodData(
|
|
36
|
+
supported_methods=X402_METHOD_NAME, # "https://www.x402.org/"
|
|
37
|
+
data=AlgorandUsdcMethodData(
|
|
38
|
+
mandate_app_id=3_498_113_854, # agent's MandateContract app ID
|
|
39
|
+
asset_id=31_566_704, # mainnet USDC ASA
|
|
40
|
+
network="mainnet",
|
|
41
|
+
x402_endpoint="https://api.example.com/weather",
|
|
42
|
+
).model_dump(),
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Buyer: parse payment args from a CartMandate
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from ap2_algorand import cart_to_payment_args
|
|
50
|
+
|
|
51
|
+
args = cart_to_payment_args(cart_mandate)
|
|
52
|
+
# args.mandate_app_id → int
|
|
53
|
+
# args.amount_micro_usdc → int (e.g. 10_000 for $0.01 USDC)
|
|
54
|
+
# args.asset_id → int
|
|
55
|
+
# args.x402_endpoint → str
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Merchant: submit payment and get a PaymentReceipt
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import os
|
|
62
|
+
from ap2_algorand import settle
|
|
63
|
+
|
|
64
|
+
receipt = settle(
|
|
65
|
+
payment_mandate=payment_mandate,
|
|
66
|
+
algod_url="https://mainnet-api.algonode.cloud",
|
|
67
|
+
)
|
|
68
|
+
txid = receipt.payment_status.network_confirmation_id
|
|
69
|
+
# txid == Algorand transaction ID (network IS the ledger)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Lower-level: submit without AP2 types
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from ap2_algorand import settle_raw
|
|
76
|
+
|
|
77
|
+
txid = settle_raw(
|
|
78
|
+
x_payment="<base64-msgpack-signed-tx>",
|
|
79
|
+
algod_url="https://mainnet-api.algonode.cloud",
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## How it maps to AP2
|
|
84
|
+
|
|
85
|
+
| AP2 field | Algorand value |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `payment_response.method_name` | `https://www.x402.org/` |
|
|
88
|
+
| `payment_response.details["value"]` | `base64(msgpack(SignedTransaction))` |
|
|
89
|
+
| `merchant_confirmation_id` | Algorand txid |
|
|
90
|
+
| `psp_confirmation_id` | Algorand txid (AVM is the PSP) |
|
|
91
|
+
| `network_confirmation_id` | Algorand txid (Algorand is the network) |
|
|
92
|
+
| OTP challenge | skipped — AVM MandateContract enforces limits |
|
|
93
|
+
|
|
94
|
+
## Dependencies
|
|
95
|
+
|
|
96
|
+
- `py-algorand-sdk >= 2.0`
|
|
97
|
+
- `pydantic >= 2.0`
|
|
98
|
+
- `ap2` — optional, only needed for `settle()` return type (`PaymentReceipt`)
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT © Algo Wallet
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# ap2-algorand
|
|
2
|
+
|
|
3
|
+
**Google AP2 × Algorand x402 adapter** — settle AI agent payments on Algorand from an AP2 `PaymentMandate`.
|
|
4
|
+
|
|
5
|
+
Bridges the [Google Agent Payments Protocol (AP2)](https://github.com/google-agentic-commerce/AP2) with non-custodial USDC payments on Algorand, enforced by AVM MandateContracts.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install ap2-algorand
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
### Buyer: advertise x402 Algorand in a CartMandate
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from ap2_algorand import AlgorandUsdcMethodData, X402_METHOD_NAME
|
|
19
|
+
from ap2.types.payment_request import PaymentMethodData
|
|
20
|
+
|
|
21
|
+
method = PaymentMethodData(
|
|
22
|
+
supported_methods=X402_METHOD_NAME, # "https://www.x402.org/"
|
|
23
|
+
data=AlgorandUsdcMethodData(
|
|
24
|
+
mandate_app_id=3_498_113_854, # agent's MandateContract app ID
|
|
25
|
+
asset_id=31_566_704, # mainnet USDC ASA
|
|
26
|
+
network="mainnet",
|
|
27
|
+
x402_endpoint="https://api.example.com/weather",
|
|
28
|
+
).model_dump(),
|
|
29
|
+
)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Buyer: parse payment args from a CartMandate
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from ap2_algorand import cart_to_payment_args
|
|
36
|
+
|
|
37
|
+
args = cart_to_payment_args(cart_mandate)
|
|
38
|
+
# args.mandate_app_id → int
|
|
39
|
+
# args.amount_micro_usdc → int (e.g. 10_000 for $0.01 USDC)
|
|
40
|
+
# args.asset_id → int
|
|
41
|
+
# args.x402_endpoint → str
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Merchant: submit payment and get a PaymentReceipt
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import os
|
|
48
|
+
from ap2_algorand import settle
|
|
49
|
+
|
|
50
|
+
receipt = settle(
|
|
51
|
+
payment_mandate=payment_mandate,
|
|
52
|
+
algod_url="https://mainnet-api.algonode.cloud",
|
|
53
|
+
)
|
|
54
|
+
txid = receipt.payment_status.network_confirmation_id
|
|
55
|
+
# txid == Algorand transaction ID (network IS the ledger)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Lower-level: submit without AP2 types
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from ap2_algorand import settle_raw
|
|
62
|
+
|
|
63
|
+
txid = settle_raw(
|
|
64
|
+
x_payment="<base64-msgpack-signed-tx>",
|
|
65
|
+
algod_url="https://mainnet-api.algonode.cloud",
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## How it maps to AP2
|
|
70
|
+
|
|
71
|
+
| AP2 field | Algorand value |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `payment_response.method_name` | `https://www.x402.org/` |
|
|
74
|
+
| `payment_response.details["value"]` | `base64(msgpack(SignedTransaction))` |
|
|
75
|
+
| `merchant_confirmation_id` | Algorand txid |
|
|
76
|
+
| `psp_confirmation_id` | Algorand txid (AVM is the PSP) |
|
|
77
|
+
| `network_confirmation_id` | Algorand txid (Algorand is the network) |
|
|
78
|
+
| OTP challenge | skipped — AVM MandateContract enforces limits |
|
|
79
|
+
|
|
80
|
+
## Dependencies
|
|
81
|
+
|
|
82
|
+
- `py-algorand-sdk >= 2.0`
|
|
83
|
+
- `pydantic >= 2.0`
|
|
84
|
+
- `ap2` — optional, only needed for `settle()` return type (`PaymentReceipt`)
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT © Algo Wallet
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ap2-algorand"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Google AP2 × Algorand adapter — settle x402 payments from AP2 CartMandate/PaymentMandate"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
keywords = ["algorand", "x402", "usdc", "ap2", "agent-payments", "ai-agent"]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"py-algorand-sdk>=2.0,<3.0",
|
|
15
|
+
"pydantic>=2.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
ap2 = [] # pip install ap2 (from google-agentic-commerce/AP2 when published)
|
|
20
|
+
dev = ["pytest>=8.0"]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["src"]
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.package-dir]
|
|
26
|
+
"" = "src"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""ap2-algorand — Google AP2 × Algorand x402 adapter.
|
|
2
|
+
|
|
3
|
+
Bridges the `Google Agent Payments Protocol (AP2)
|
|
4
|
+
<https://github.com/google-agentic-commerce/AP2>`_ with Algorand x402
|
|
5
|
+
payments.
|
|
6
|
+
|
|
7
|
+
**Buyer side** — build a ``PaymentMethodData`` for your ``CartMandate``::
|
|
8
|
+
|
|
9
|
+
from ap2_algorand import AlgorandUsdcMethodData, X402_METHOD_NAME
|
|
10
|
+
from ap2.types.payment_request import PaymentMethodData
|
|
11
|
+
|
|
12
|
+
method = PaymentMethodData(
|
|
13
|
+
supported_methods=X402_METHOD_NAME,
|
|
14
|
+
data=AlgorandUsdcMethodData(
|
|
15
|
+
mandate_app_id=3_498_113_854,
|
|
16
|
+
asset_id=31_566_704, # mainnet USDC
|
|
17
|
+
network="mainnet",
|
|
18
|
+
x402_endpoint="https://api.example.com/premium",
|
|
19
|
+
).model_dump(),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
**Buyer side** — extract payment args from a ``CartMandate``::
|
|
23
|
+
|
|
24
|
+
from ap2_algorand import cart_to_payment_args
|
|
25
|
+
|
|
26
|
+
args = cart_to_payment_args(cart_mandate)
|
|
27
|
+
# args.mandate_app_id, args.amount_micro_usdc, args.asset_id, args.x402_endpoint
|
|
28
|
+
|
|
29
|
+
**Merchant side** — submit payment and get ``PaymentReceipt``::
|
|
30
|
+
|
|
31
|
+
from ap2_algorand import settle
|
|
32
|
+
|
|
33
|
+
receipt = settle(payment_mandate, algod_url="https://mainnet-api.algonode.cloud")
|
|
34
|
+
# receipt.payment_status.network_confirmation_id == Algorand txid
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
__version__ = "0.1.0"
|
|
38
|
+
|
|
39
|
+
from ap2_algorand.payment_method import (
|
|
40
|
+
AlgorandUsdcMethodData,
|
|
41
|
+
X402_METHOD_NAME,
|
|
42
|
+
USDC_MAINNET_ASA_ID,
|
|
43
|
+
USDC_TESTNET_ASA_ID,
|
|
44
|
+
)
|
|
45
|
+
from ap2_algorand.cart_adapter import (
|
|
46
|
+
AlgorandPaymentArgs,
|
|
47
|
+
cart_to_payment_args,
|
|
48
|
+
)
|
|
49
|
+
from ap2_algorand.settle import settle, settle_raw
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
# Payment method
|
|
53
|
+
"AlgorandUsdcMethodData",
|
|
54
|
+
"X402_METHOD_NAME",
|
|
55
|
+
"USDC_MAINNET_ASA_ID",
|
|
56
|
+
"USDC_TESTNET_ASA_ID",
|
|
57
|
+
# Cart adapter
|
|
58
|
+
"AlgorandPaymentArgs",
|
|
59
|
+
"cart_to_payment_args",
|
|
60
|
+
# Settlement
|
|
61
|
+
"settle",
|
|
62
|
+
"settle_raw",
|
|
63
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""cart_to_payment_args — extract Algorand payment parameters from an AP2 CartMandate.
|
|
2
|
+
|
|
3
|
+
Converts the merchant-signed AP2 ``CartMandate`` into the concrete arguments
|
|
4
|
+
needed to build and sign an Algorand MandateContract.pay() transaction.
|
|
5
|
+
|
|
6
|
+
Usage::
|
|
7
|
+
|
|
8
|
+
from ap2_algorand import cart_to_payment_args
|
|
9
|
+
|
|
10
|
+
args = cart_to_payment_args(cart_mandate)
|
|
11
|
+
# args.mandate_app_id, args.amount_micro_usdc, args.asset_id, args.x402_endpoint
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from ap2_algorand.payment_method import AlgorandUsdcMethodData, X402_METHOD_NAME
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class AlgorandPaymentArgs:
|
|
24
|
+
"""Parameters extracted from an AP2 CartMandate for Algorand payment."""
|
|
25
|
+
|
|
26
|
+
mandate_app_id: int
|
|
27
|
+
"""Algorand application ID of the buyer's MandateContract."""
|
|
28
|
+
|
|
29
|
+
asset_id: int
|
|
30
|
+
"""Algorand ASA ID for the payment token (USDC)."""
|
|
31
|
+
|
|
32
|
+
amount_micro_usdc: int
|
|
33
|
+
"""Payment amount in micro-USDC (value × 1_000_000)."""
|
|
34
|
+
|
|
35
|
+
x402_endpoint: str
|
|
36
|
+
"""The x402-gated endpoint being paid for."""
|
|
37
|
+
|
|
38
|
+
network: str
|
|
39
|
+
"""Algorand network ('mainnet' or 'testnet')."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def cart_to_payment_args(cart_mandate: Any) -> AlgorandPaymentArgs:
|
|
43
|
+
"""Extract Algorand payment parameters from an AP2 CartMandate.
|
|
44
|
+
|
|
45
|
+
Locates the ``https://www.x402.org/`` entry in the cart's
|
|
46
|
+
``payment_request.method_data``, parses it as
|
|
47
|
+
:class:`~ap2_algorand.AlgorandUsdcMethodData`, and converts the cart
|
|
48
|
+
total to micro-USDC.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
cart_mandate: An AP2 ``CartMandate`` instance (or any object with a
|
|
52
|
+
compatible ``.contents.payment_request`` attribute).
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
:class:`AlgorandPaymentArgs` ready to pass to the x402 buyer client.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: If no x402 Algorand payment method is found in the cart.
|
|
59
|
+
"""
|
|
60
|
+
payment_request = cart_mandate.contents.payment_request
|
|
61
|
+
|
|
62
|
+
# Locate the x402 payment method entry
|
|
63
|
+
x402_method_data: AlgorandUsdcMethodData | None = None
|
|
64
|
+
for method in payment_request.method_data:
|
|
65
|
+
if method.supported_methods == X402_METHOD_NAME:
|
|
66
|
+
data = method.data or {}
|
|
67
|
+
x402_method_data = AlgorandUsdcMethodData(**data)
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
if x402_method_data is None:
|
|
71
|
+
available = [m.supported_methods for m in payment_request.method_data]
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"No x402 Algorand payment method found in CartMandate. "
|
|
74
|
+
f"Available methods: {available}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Convert total to micro-USDC.
|
|
78
|
+
# PaymentCurrencyAmount.value is a float USD amount; 1 USDC = 1_000_000 µUSDC.
|
|
79
|
+
total = payment_request.details.total
|
|
80
|
+
amount_float: float = float(total.amount.value)
|
|
81
|
+
amount_micro_usdc = round(amount_float * 1_000_000)
|
|
82
|
+
|
|
83
|
+
return AlgorandPaymentArgs(
|
|
84
|
+
mandate_app_id=x402_method_data.mandate_app_id,
|
|
85
|
+
asset_id=x402_method_data.asset_id,
|
|
86
|
+
amount_micro_usdc=amount_micro_usdc,
|
|
87
|
+
x402_endpoint=x402_method_data.x402_endpoint,
|
|
88
|
+
network=x402_method_data.network,
|
|
89
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""AlgorandUsdcMethodData — PaymentMethodData.data for x402 on Algorand.
|
|
2
|
+
|
|
3
|
+
Use this as the ``data`` dict inside a W3C ``PaymentMethodData`` object when
|
|
4
|
+
constructing an AP2 ``CartMandate`` or ``PaymentRequest`` that accepts x402
|
|
5
|
+
payments settled on Algorand.
|
|
6
|
+
|
|
7
|
+
Example::
|
|
8
|
+
|
|
9
|
+
from ap2.types.payment_request import PaymentMethodData
|
|
10
|
+
from ap2_algorand import AlgorandUsdcMethodData
|
|
11
|
+
|
|
12
|
+
method = PaymentMethodData(
|
|
13
|
+
supported_methods="https://www.x402.org/",
|
|
14
|
+
data=AlgorandUsdcMethodData(
|
|
15
|
+
mandate_app_id=3498113854,
|
|
16
|
+
asset_id=31_566_704,
|
|
17
|
+
network="mainnet",
|
|
18
|
+
x402_endpoint="https://api.example.com/premium",
|
|
19
|
+
).model_dump(),
|
|
20
|
+
)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from typing import Literal
|
|
24
|
+
|
|
25
|
+
from pydantic import BaseModel, Field
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
X402_METHOD_NAME = "https://www.x402.org/"
|
|
29
|
+
|
|
30
|
+
USDC_MAINNET_ASA_ID = 31_566_704
|
|
31
|
+
USDC_TESTNET_ASA_ID = 10_458_941
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AlgorandUsdcMethodData(BaseModel):
|
|
35
|
+
"""Configuration data for an Algorand / x402 payment method.
|
|
36
|
+
|
|
37
|
+
Carried in ``PaymentMethodData.data`` with
|
|
38
|
+
``supported_methods="https://www.x402.org/"``.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
mandate_app_id: int = Field(
|
|
42
|
+
...,
|
|
43
|
+
description=(
|
|
44
|
+
"Algorand application ID of the agent's MandateContract. "
|
|
45
|
+
"The AVM enforces per-tx and velocity caps for this agent."
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
asset_id: int = Field(
|
|
49
|
+
...,
|
|
50
|
+
description=(
|
|
51
|
+
"Algorand ASA ID for the payment token. "
|
|
52
|
+
f"Mainnet USDC = {USDC_MAINNET_ASA_ID}, "
|
|
53
|
+
f"testnet USDC = {USDC_TESTNET_ASA_ID}."
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
network: Literal["mainnet", "testnet"] = Field(
|
|
57
|
+
"mainnet",
|
|
58
|
+
description="Algorand network.",
|
|
59
|
+
)
|
|
60
|
+
x402_endpoint: str = Field(
|
|
61
|
+
...,
|
|
62
|
+
description=(
|
|
63
|
+
"The x402-gated API endpoint URL that will receive the payment. "
|
|
64
|
+
"The merchant verifies the signed transaction references this endpoint."
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def usdc_asset_id(self) -> int:
|
|
70
|
+
"""Convenience: returns the canonical USDC ASA ID for the network."""
|
|
71
|
+
return USDC_MAINNET_ASA_ID if self.network == "mainnet" else USDC_TESTNET_ASA_ID
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""settle — submit an x402 Algorand payment and return an AP2 PaymentReceipt.
|
|
2
|
+
|
|
3
|
+
This is the merchant-side counterpart: given an AP2 ``PaymentMandate`` whose
|
|
4
|
+
``payment_response.details["value"]`` carries a base64-encoded msgpack
|
|
5
|
+
``SignedTransaction`` (the x402 payment header), submit it to Algorand and
|
|
6
|
+
return a fully-formed AP2 ``PaymentReceipt``.
|
|
7
|
+
|
|
8
|
+
The Algorand transaction ID serves as all three confirmation IDs:
|
|
9
|
+
|
|
10
|
+
* ``merchant_confirmation_id`` — the merchant records the txid
|
|
11
|
+
* ``psp_confirmation_id`` — Algorand IS the PSP (self-custodial mandate)
|
|
12
|
+
* ``network_confirmation_id`` — Algorand IS the network
|
|
13
|
+
|
|
14
|
+
Usage (merchant payment processor agent)::
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from ap2_algorand import settle
|
|
18
|
+
|
|
19
|
+
receipt = settle(
|
|
20
|
+
payment_mandate=payment_mandate,
|
|
21
|
+
algod_url=os.environ["ALGOD_URL"], # e.g. https://mainnet-api.algonode.cloud
|
|
22
|
+
algod_token=os.environ.get("ALGOD_TOKEN", ""),
|
|
23
|
+
wait_rounds=4,
|
|
24
|
+
)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import base64
|
|
30
|
+
import uuid
|
|
31
|
+
from datetime import datetime, timezone
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
import algosdk
|
|
35
|
+
from algosdk.v2client import algod
|
|
36
|
+
|
|
37
|
+
from ap2_algorand.payment_method import X402_METHOD_NAME
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def settle(
|
|
41
|
+
payment_mandate: Any,
|
|
42
|
+
algod_url: str,
|
|
43
|
+
algod_token: str = "",
|
|
44
|
+
wait_rounds: int = 4,
|
|
45
|
+
) -> Any:
|
|
46
|
+
"""Submit an x402 Algorand payment and return an AP2 PaymentReceipt.
|
|
47
|
+
|
|
48
|
+
Reads the signed transaction from
|
|
49
|
+
``payment_mandate.payment_mandate_contents.payment_response.details["value"]``,
|
|
50
|
+
submits it to the Algorand network, waits for on-chain confirmation, and
|
|
51
|
+
returns a ``PaymentReceipt`` with the transaction ID as the confirmation ID.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
payment_mandate: An AP2 ``PaymentMandate`` instance.
|
|
55
|
+
algod_url: Algod REST endpoint (e.g. ``https://mainnet-api.algonode.cloud``).
|
|
56
|
+
algod_token: Algod API token. Empty string for public nodes.
|
|
57
|
+
wait_rounds: Maximum rounds to wait for confirmation (default 4).
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
An AP2 ``PaymentReceipt`` (``payment_status`` is ``Success``).
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: If the payment_response is missing or has no x402 value.
|
|
64
|
+
algosdk.error.AlgodHTTPError: On algod submission failure.
|
|
65
|
+
Exception: If confirmation times out.
|
|
66
|
+
"""
|
|
67
|
+
# ── 1. Extract signed transaction bytes ───────────────────────────────────
|
|
68
|
+
contents = payment_mandate.payment_mandate_contents
|
|
69
|
+
response = contents.payment_response
|
|
70
|
+
|
|
71
|
+
if response.method_name != X402_METHOD_NAME:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Expected payment method '{X402_METHOD_NAME}', "
|
|
74
|
+
f"got '{response.method_name}'"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
x_payment: str | None = (response.details or {}).get("value")
|
|
78
|
+
if not x_payment:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
"PaymentMandate.payment_response.details['value'] is missing. "
|
|
81
|
+
"The buyer agent must set this to the base64-encoded x402 SignedTransaction."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
stxn_bytes = base64.b64decode(x_payment)
|
|
85
|
+
|
|
86
|
+
# ── 2. Submit to Algorand ─────────────────────────────────────────────────
|
|
87
|
+
algod_client = algod.AlgodClient(algod_token, algod_url)
|
|
88
|
+
txid: str = algod_client.send_raw_transaction(stxn_bytes)
|
|
89
|
+
|
|
90
|
+
# ── 3. Wait for on-chain confirmation ─────────────────────────────────────
|
|
91
|
+
algosdk.util.wait_for_confirmation(algod_client, txid, wait_rounds)
|
|
92
|
+
|
|
93
|
+
# ── 4. Build PaymentReceipt ───────────────────────────────────────────────
|
|
94
|
+
# Import AP2 types at call time so the package works without ap2 installed
|
|
95
|
+
# (e.g. for testing or non-AP2 use of the settle logic alone).
|
|
96
|
+
try:
|
|
97
|
+
from ap2.types.payment_receipt import PaymentReceipt, Success # type: ignore[import]
|
|
98
|
+
from ap2.types.payment_request import PaymentCurrencyAmount # type: ignore[import]
|
|
99
|
+
except ImportError as exc:
|
|
100
|
+
raise ImportError(
|
|
101
|
+
"ap2 package is required to build a PaymentReceipt. "
|
|
102
|
+
"Install it with: pip install ap2"
|
|
103
|
+
) from exc
|
|
104
|
+
|
|
105
|
+
payment_id = uuid.uuid4().hex
|
|
106
|
+
payment_mandate_id = contents.payment_mandate_id
|
|
107
|
+
total_item = contents.payment_details_total
|
|
108
|
+
|
|
109
|
+
return PaymentReceipt(
|
|
110
|
+
payment_mandate_id=payment_mandate_id,
|
|
111
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
112
|
+
payment_id=payment_id,
|
|
113
|
+
amount=PaymentCurrencyAmount(
|
|
114
|
+
currency=total_item.amount.currency,
|
|
115
|
+
value=total_item.amount.value,
|
|
116
|
+
),
|
|
117
|
+
payment_status=Success(
|
|
118
|
+
merchant_confirmation_id=txid,
|
|
119
|
+
psp_confirmation_id=txid,
|
|
120
|
+
network_confirmation_id=txid,
|
|
121
|
+
),
|
|
122
|
+
payment_method_details={
|
|
123
|
+
"method_name": X402_METHOD_NAME,
|
|
124
|
+
"network": "algorand",
|
|
125
|
+
"txid": txid,
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def settle_raw(
|
|
131
|
+
x_payment: str,
|
|
132
|
+
algod_url: str,
|
|
133
|
+
algod_token: str = "",
|
|
134
|
+
wait_rounds: int = 4,
|
|
135
|
+
) -> str:
|
|
136
|
+
"""Submit a raw x402 payment header to Algorand and return the txid.
|
|
137
|
+
|
|
138
|
+
Lower-level alternative to :func:`settle` that works without AP2 types.
|
|
139
|
+
Useful for testing or non-AP2 contexts.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
x_payment: Base64-encoded msgpack SignedTransaction (the X-PAYMENT header value).
|
|
143
|
+
algod_url: Algod REST endpoint.
|
|
144
|
+
algod_token: Algod API token. Empty string for public nodes.
|
|
145
|
+
wait_rounds: Maximum rounds to wait for confirmation.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Algorand transaction ID string.
|
|
149
|
+
"""
|
|
150
|
+
stxn_bytes = base64.b64decode(x_payment)
|
|
151
|
+
algod_client = algod.AlgodClient(algod_token, algod_url)
|
|
152
|
+
txid: str = algod_client.send_raw_transaction(stxn_bytes)
|
|
153
|
+
algosdk.util.wait_for_confirmation(algod_client, txid, wait_rounds)
|
|
154
|
+
return txid
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ap2-algorand
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Google AP2 × Algorand adapter — settle x402 payments from AP2 CartMandate/PaymentMandate
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: algorand,x402,usdc,ap2,agent-payments,ai-agent
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: py-algorand-sdk<3.0,>=2.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0
|
|
11
|
+
Provides-Extra: ap2
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# ap2-algorand
|
|
16
|
+
|
|
17
|
+
**Google AP2 × Algorand x402 adapter** — settle AI agent payments on Algorand from an AP2 `PaymentMandate`.
|
|
18
|
+
|
|
19
|
+
Bridges the [Google Agent Payments Protocol (AP2)](https://github.com/google-agentic-commerce/AP2) with non-custodial USDC payments on Algorand, enforced by AVM MandateContracts.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install ap2-algorand
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
### Buyer: advertise x402 Algorand in a CartMandate
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from ap2_algorand import AlgorandUsdcMethodData, X402_METHOD_NAME
|
|
33
|
+
from ap2.types.payment_request import PaymentMethodData
|
|
34
|
+
|
|
35
|
+
method = PaymentMethodData(
|
|
36
|
+
supported_methods=X402_METHOD_NAME, # "https://www.x402.org/"
|
|
37
|
+
data=AlgorandUsdcMethodData(
|
|
38
|
+
mandate_app_id=3_498_113_854, # agent's MandateContract app ID
|
|
39
|
+
asset_id=31_566_704, # mainnet USDC ASA
|
|
40
|
+
network="mainnet",
|
|
41
|
+
x402_endpoint="https://api.example.com/weather",
|
|
42
|
+
).model_dump(),
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Buyer: parse payment args from a CartMandate
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from ap2_algorand import cart_to_payment_args
|
|
50
|
+
|
|
51
|
+
args = cart_to_payment_args(cart_mandate)
|
|
52
|
+
# args.mandate_app_id → int
|
|
53
|
+
# args.amount_micro_usdc → int (e.g. 10_000 for $0.01 USDC)
|
|
54
|
+
# args.asset_id → int
|
|
55
|
+
# args.x402_endpoint → str
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Merchant: submit payment and get a PaymentReceipt
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import os
|
|
62
|
+
from ap2_algorand import settle
|
|
63
|
+
|
|
64
|
+
receipt = settle(
|
|
65
|
+
payment_mandate=payment_mandate,
|
|
66
|
+
algod_url="https://mainnet-api.algonode.cloud",
|
|
67
|
+
)
|
|
68
|
+
txid = receipt.payment_status.network_confirmation_id
|
|
69
|
+
# txid == Algorand transaction ID (network IS the ledger)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Lower-level: submit without AP2 types
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from ap2_algorand import settle_raw
|
|
76
|
+
|
|
77
|
+
txid = settle_raw(
|
|
78
|
+
x_payment="<base64-msgpack-signed-tx>",
|
|
79
|
+
algod_url="https://mainnet-api.algonode.cloud",
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## How it maps to AP2
|
|
84
|
+
|
|
85
|
+
| AP2 field | Algorand value |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `payment_response.method_name` | `https://www.x402.org/` |
|
|
88
|
+
| `payment_response.details["value"]` | `base64(msgpack(SignedTransaction))` |
|
|
89
|
+
| `merchant_confirmation_id` | Algorand txid |
|
|
90
|
+
| `psp_confirmation_id` | Algorand txid (AVM is the PSP) |
|
|
91
|
+
| `network_confirmation_id` | Algorand txid (Algorand is the network) |
|
|
92
|
+
| OTP challenge | skipped — AVM MandateContract enforces limits |
|
|
93
|
+
|
|
94
|
+
## Dependencies
|
|
95
|
+
|
|
96
|
+
- `py-algorand-sdk >= 2.0`
|
|
97
|
+
- `pydantic >= 2.0`
|
|
98
|
+
- `ap2` — optional, only needed for `settle()` return type (`PaymentReceipt`)
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT © Algo Wallet
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/ap2_algorand/__init__.py
|
|
4
|
+
src/ap2_algorand/cart_adapter.py
|
|
5
|
+
src/ap2_algorand/payment_method.py
|
|
6
|
+
src/ap2_algorand/settle.py
|
|
7
|
+
src/ap2_algorand.egg-info/PKG-INFO
|
|
8
|
+
src/ap2_algorand.egg-info/SOURCES.txt
|
|
9
|
+
src/ap2_algorand.egg-info/dependency_links.txt
|
|
10
|
+
src/ap2_algorand.egg-info/requires.txt
|
|
11
|
+
src/ap2_algorand.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ap2_algorand
|