t402 1.9.0__py3-none-any.whl → 1.9.1__py3-none-any.whl
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.
- t402/__init__.py +2 -1
- t402/bridge/client.py +13 -5
- t402/bridge/constants.py +3 -1
- t402/bridge/router.py +1 -1
- t402/bridge/scan.py +3 -1
- t402/chains.py +268 -1
- t402/cli.py +31 -9
- t402/common.py +2 -0
- t402/cosmos_paywall_template.py +2 -0
- t402/encoding.py +9 -3
- t402/erc4337/accounts.py +56 -51
- t402/erc4337/bundlers.py +105 -99
- t402/erc4337/paymasters.py +100 -109
- t402/erc4337/types.py +39 -26
- t402/evm_paywall_template.py +1 -1
- t402/fastapi/middleware.py +1 -3
- t402/mcp/server.py +79 -46
- t402/near_paywall_template.py +2 -0
- t402/networks.py +34 -1
- t402/paywall.py +1 -3
- t402/schemes/__init__.py +124 -0
- t402/schemes/aptos/__init__.py +70 -0
- t402/schemes/aptos/constants.py +349 -0
- t402/schemes/aptos/exact_direct/__init__.py +44 -0
- t402/schemes/aptos/exact_direct/client.py +202 -0
- t402/schemes/aptos/exact_direct/facilitator.py +426 -0
- t402/schemes/aptos/exact_direct/server.py +272 -0
- t402/schemes/aptos/types.py +237 -0
- t402/schemes/evm/__init__.py +46 -1
- t402/schemes/evm/exact/__init__.py +11 -0
- t402/schemes/evm/exact/client.py +3 -1
- t402/schemes/evm/exact/facilitator.py +894 -0
- t402/schemes/evm/exact/server.py +1 -1
- t402/schemes/evm/exact_legacy/__init__.py +38 -0
- t402/schemes/evm/exact_legacy/client.py +291 -0
- t402/schemes/evm/exact_legacy/facilitator.py +777 -0
- t402/schemes/evm/exact_legacy/server.py +231 -0
- t402/schemes/evm/upto/__init__.py +12 -0
- t402/schemes/evm/upto/client.py +6 -2
- t402/schemes/evm/upto/facilitator.py +625 -0
- t402/schemes/evm/upto/server.py +243 -0
- t402/schemes/evm/upto/types.py +3 -1
- t402/schemes/interfaces.py +6 -2
- t402/schemes/near/__init__.py +112 -0
- t402/schemes/near/constants.py +189 -0
- t402/schemes/near/exact_direct/__init__.py +21 -0
- t402/schemes/near/exact_direct/client.py +204 -0
- t402/schemes/near/exact_direct/facilitator.py +455 -0
- t402/schemes/near/exact_direct/server.py +303 -0
- t402/schemes/near/types.py +419 -0
- t402/schemes/polkadot/__init__.py +72 -0
- t402/schemes/polkadot/constants.py +155 -0
- t402/schemes/polkadot/exact_direct/__init__.py +43 -0
- t402/schemes/polkadot/exact_direct/client.py +235 -0
- t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
- t402/schemes/polkadot/exact_direct/server.py +292 -0
- t402/schemes/polkadot/types.py +385 -0
- t402/schemes/registry.py +6 -2
- t402/schemes/stacks/__init__.py +68 -0
- t402/schemes/stacks/constants.py +122 -0
- t402/schemes/stacks/exact_direct/__init__.py +43 -0
- t402/schemes/stacks/exact_direct/client.py +222 -0
- t402/schemes/stacks/exact_direct/facilitator.py +424 -0
- t402/schemes/stacks/exact_direct/server.py +292 -0
- t402/schemes/stacks/types.py +380 -0
- t402/schemes/svm/__init__.py +29 -0
- t402/schemes/svm/exact/__init__.py +35 -0
- t402/schemes/svm/exact/client.py +23 -0
- t402/schemes/svm/exact/facilitator.py +24 -0
- t402/schemes/svm/exact/server.py +20 -0
- t402/schemes/tezos/__init__.py +84 -0
- t402/schemes/tezos/constants.py +372 -0
- t402/schemes/tezos/exact_direct/__init__.py +22 -0
- t402/schemes/tezos/exact_direct/client.py +226 -0
- t402/schemes/tezos/exact_direct/facilitator.py +491 -0
- t402/schemes/tezos/exact_direct/server.py +277 -0
- t402/schemes/tezos/types.py +220 -0
- t402/schemes/ton/__init__.py +9 -2
- t402/schemes/ton/exact/__init__.py +7 -0
- t402/schemes/ton/exact/facilitator.py +730 -0
- t402/schemes/ton/exact/server.py +1 -1
- t402/schemes/tron/__init__.py +11 -2
- t402/schemes/tron/exact/__init__.py +9 -0
- t402/schemes/tron/exact/facilitator.py +673 -0
- t402/schemes/tron/exact/server.py +1 -1
- t402/stacks_paywall_template.py +2 -0
- t402/svm.py +45 -11
- t402/svm_paywall_template.py +1 -1
- t402/ton.py +5 -1
- t402/ton_paywall_template.py +1 -192
- t402/tron.py +2 -0
- t402/tron_paywall_template.py +2 -0
- t402/types.py +3 -1
- t402/wdk/errors.py +15 -5
- t402/wdk/signer.py +11 -2
- {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
- t402-1.9.1.dist-info/RECORD +125 -0
- t402-1.9.0.dist-info/RECORD +0 -72
- {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
- {t402-1.9.0.dist-info → t402-1.9.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Aptos Exact-Direct Scheme - Server Implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the server-side implementation of the exact-direct payment
|
|
4
|
+
scheme for Aptos. It handles parsing user-friendly prices into atomic units
|
|
5
|
+
and enhancing payment requirements with Aptos-specific metadata.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Dict, List, Optional, Union
|
|
12
|
+
|
|
13
|
+
from t402.types import (
|
|
14
|
+
PaymentRequirementsV2,
|
|
15
|
+
Network,
|
|
16
|
+
)
|
|
17
|
+
from t402.schemes.interfaces import AssetAmount, SupportedKindDict
|
|
18
|
+
from t402.schemes.aptos.constants import (
|
|
19
|
+
SCHEME_EXACT_DIRECT,
|
|
20
|
+
CAIP_FAMILY,
|
|
21
|
+
DEFAULT_DECIMALS,
|
|
22
|
+
get_network_config,
|
|
23
|
+
get_token_by_address,
|
|
24
|
+
get_token_info,
|
|
25
|
+
parse_amount,
|
|
26
|
+
TokenInfo,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ExactDirectAptosServerScheme:
|
|
34
|
+
"""Server scheme for Aptos exact-direct payments.
|
|
35
|
+
|
|
36
|
+
Handles parsing user-friendly prices (e.g., "$1.50") into atomic FA amounts
|
|
37
|
+
and enhancing payment requirements with Aptos-specific metadata such as
|
|
38
|
+
token symbol, name, and decimals.
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
```python
|
|
42
|
+
scheme = ExactDirectAptosServerScheme()
|
|
43
|
+
|
|
44
|
+
# Parse a price
|
|
45
|
+
asset_amount = await scheme.parse_price("$0.10", "aptos:1")
|
|
46
|
+
# Returns: {"amount": "100000", "asset": "0xf73e...", "extra": {...}}
|
|
47
|
+
|
|
48
|
+
# Enhance requirements
|
|
49
|
+
enhanced = await scheme.enhance_requirements(
|
|
50
|
+
requirements,
|
|
51
|
+
supported_kind,
|
|
52
|
+
facilitator_extensions,
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
scheme: The scheme identifier ("exact-direct").
|
|
58
|
+
caip_family: The CAIP-2 family pattern ("aptos:*").
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
scheme = SCHEME_EXACT_DIRECT
|
|
62
|
+
caip_family = CAIP_FAMILY
|
|
63
|
+
|
|
64
|
+
def __init__(self, preferred_token: Optional[str] = None) -> None:
|
|
65
|
+
"""Initialize the Aptos exact-direct server scheme.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
preferred_token: Preferred token symbol (e.g., "USDT").
|
|
69
|
+
Defaults to the network's default token if not specified.
|
|
70
|
+
"""
|
|
71
|
+
self._preferred_token = preferred_token
|
|
72
|
+
|
|
73
|
+
async def parse_price(
|
|
74
|
+
self,
|
|
75
|
+
price: Union[str, int, float, Dict[str, Any]],
|
|
76
|
+
network: Network,
|
|
77
|
+
) -> AssetAmount:
|
|
78
|
+
"""Parse a user-friendly price to atomic amount and asset.
|
|
79
|
+
|
|
80
|
+
Supports multiple input formats:
|
|
81
|
+
- String with $ prefix: "$0.10" -> 100000 (6 decimals)
|
|
82
|
+
- String without prefix: "0.10" -> 100000
|
|
83
|
+
- Integer/float: 0.10 -> 100000
|
|
84
|
+
- Dict (pre-parsed TokenAmount): {"amount": "100000", "asset": "0x..."}
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
price: User-friendly price in any supported format.
|
|
88
|
+
network: CAIP-2 network identifier (e.g., "aptos:1").
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
AssetAmount dict with:
|
|
92
|
+
- amount: Atomic amount as string
|
|
93
|
+
- asset: FA metadata address
|
|
94
|
+
- extra: Token metadata (symbol, name, decimals)
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
ValueError: If price format is invalid or network is unsupported.
|
|
98
|
+
"""
|
|
99
|
+
network_str = str(network)
|
|
100
|
+
|
|
101
|
+
# Validate network
|
|
102
|
+
config = get_network_config(network_str)
|
|
103
|
+
if not config:
|
|
104
|
+
raise ValueError(f"Unsupported Aptos network: {network}")
|
|
105
|
+
|
|
106
|
+
# Handle dict (already in TokenAmount format)
|
|
107
|
+
if isinstance(price, dict):
|
|
108
|
+
amount_val = price.get("amount", "0")
|
|
109
|
+
asset = price.get("asset", config.default_token.metadata_address)
|
|
110
|
+
extra = price.get("extra", {})
|
|
111
|
+
|
|
112
|
+
if not asset:
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"Asset address must be specified for AssetAmount on network {network}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
"amount": str(amount_val),
|
|
119
|
+
"asset": asset,
|
|
120
|
+
"extra": extra,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Parse money to decimal number
|
|
124
|
+
decimal_amount = self._parse_money_to_decimal(price)
|
|
125
|
+
|
|
126
|
+
# Get the appropriate token
|
|
127
|
+
token = self._get_default_token(network_str, config)
|
|
128
|
+
|
|
129
|
+
# Convert decimal to atomic units
|
|
130
|
+
amount_str = f"{decimal_amount:.{token.decimals}f}"
|
|
131
|
+
atomic_amount = parse_amount(amount_str, token.decimals)
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
"amount": str(atomic_amount),
|
|
135
|
+
"asset": token.metadata_address,
|
|
136
|
+
"extra": {
|
|
137
|
+
"symbol": token.symbol,
|
|
138
|
+
"name": token.name,
|
|
139
|
+
"decimals": token.decimals,
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async def enhance_requirements(
|
|
144
|
+
self,
|
|
145
|
+
requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
|
|
146
|
+
supported_kind: SupportedKindDict,
|
|
147
|
+
facilitator_extensions: List[str],
|
|
148
|
+
) -> Union[PaymentRequirementsV2, Dict[str, Any]]:
|
|
149
|
+
"""Enhance payment requirements with Aptos-specific metadata.
|
|
150
|
+
|
|
151
|
+
Adds FA token metadata (symbol, name, decimals) to the extra field
|
|
152
|
+
and converts decimal amounts to atomic units if needed.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
requirements: Base payment requirements with amount/asset set.
|
|
156
|
+
supported_kind: The matched SupportedKind from the facilitator.
|
|
157
|
+
facilitator_extensions: Extensions supported by the facilitator.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Enhanced requirements with Aptos metadata in the extra field.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
ValueError: If the network is unsupported or amount parsing fails.
|
|
164
|
+
"""
|
|
165
|
+
# Convert to dict for modification
|
|
166
|
+
if hasattr(requirements, "model_dump"):
|
|
167
|
+
req = requirements.model_dump(by_alias=True)
|
|
168
|
+
else:
|
|
169
|
+
req = dict(requirements)
|
|
170
|
+
|
|
171
|
+
network = req.get("network", "")
|
|
172
|
+
asset = req.get("asset", "")
|
|
173
|
+
|
|
174
|
+
# Validate network
|
|
175
|
+
config = get_network_config(network)
|
|
176
|
+
if not config:
|
|
177
|
+
raise ValueError(f"Unsupported Aptos network: {network}")
|
|
178
|
+
|
|
179
|
+
# Determine token info
|
|
180
|
+
token_info: Optional[TokenInfo] = None
|
|
181
|
+
if asset:
|
|
182
|
+
# Try to find by address
|
|
183
|
+
token_info = get_token_by_address(network, asset)
|
|
184
|
+
if not token_info:
|
|
185
|
+
# Use generic token with default decimals
|
|
186
|
+
token_info = TokenInfo(
|
|
187
|
+
metadata_address=asset,
|
|
188
|
+
symbol="UNKNOWN",
|
|
189
|
+
name="Unknown Token",
|
|
190
|
+
decimals=DEFAULT_DECIMALS,
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
# Use default token
|
|
194
|
+
token_info = self._get_default_token(network, config)
|
|
195
|
+
req["asset"] = token_info.metadata_address
|
|
196
|
+
|
|
197
|
+
# Convert decimal amount to atomic units if needed
|
|
198
|
+
amount = req.get("amount", "")
|
|
199
|
+
if amount and "." in amount:
|
|
200
|
+
atomic = parse_amount(amount, token_info.decimals)
|
|
201
|
+
req["amount"] = str(atomic)
|
|
202
|
+
|
|
203
|
+
# Initialize extra map if needed
|
|
204
|
+
if "extra" not in req or req["extra"] is None:
|
|
205
|
+
req["extra"] = {}
|
|
206
|
+
|
|
207
|
+
# Add asset metadata to extra
|
|
208
|
+
req["extra"]["symbol"] = token_info.symbol
|
|
209
|
+
req["extra"]["name"] = token_info.name
|
|
210
|
+
req["extra"]["decimals"] = token_info.decimals
|
|
211
|
+
|
|
212
|
+
# Copy facilitator-provided extra fields
|
|
213
|
+
kind_extra = supported_kind.get("extra") if isinstance(supported_kind, dict) else None
|
|
214
|
+
if kind_extra:
|
|
215
|
+
if "assetSymbol" in kind_extra:
|
|
216
|
+
req["extra"]["assetSymbol"] = kind_extra["assetSymbol"]
|
|
217
|
+
if "assetDecimals" in kind_extra:
|
|
218
|
+
req["extra"]["assetDecimals"] = kind_extra["assetDecimals"]
|
|
219
|
+
|
|
220
|
+
# Copy specific extension keys
|
|
221
|
+
for key in facilitator_extensions:
|
|
222
|
+
if key in kind_extra:
|
|
223
|
+
req["extra"][key] = kind_extra[key]
|
|
224
|
+
|
|
225
|
+
return req
|
|
226
|
+
|
|
227
|
+
def _get_default_token(self, network: str, config: Any) -> TokenInfo:
|
|
228
|
+
"""Get the default token for a network, considering preferred token config.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
network: CAIP-2 network identifier.
|
|
232
|
+
config: NetworkConfig for the network.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
TokenInfo for the default or preferred token.
|
|
236
|
+
"""
|
|
237
|
+
if self._preferred_token:
|
|
238
|
+
token = get_token_info(network, self._preferred_token)
|
|
239
|
+
if token:
|
|
240
|
+
return token
|
|
241
|
+
return config.default_token
|
|
242
|
+
|
|
243
|
+
def _parse_money_to_decimal(self, price: Union[str, int, float]) -> float:
|
|
244
|
+
"""Convert a price value to a decimal amount.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
price: Price as string (e.g., "$1.50"), int, or float.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Decimal amount as float.
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
ValueError: If the price format cannot be parsed.
|
|
254
|
+
"""
|
|
255
|
+
if isinstance(price, str):
|
|
256
|
+
clean = price.strip()
|
|
257
|
+
# Remove $ prefix
|
|
258
|
+
if clean.startswith("$"):
|
|
259
|
+
clean = clean[1:].strip()
|
|
260
|
+
# Take the first token (handle "1.50 USDT" format)
|
|
261
|
+
parts = clean.split()
|
|
262
|
+
if parts:
|
|
263
|
+
try:
|
|
264
|
+
return float(parts[0])
|
|
265
|
+
except ValueError:
|
|
266
|
+
raise ValueError(f"Failed to parse price string: '{price}'")
|
|
267
|
+
raise ValueError(f"Empty price string: '{price}'")
|
|
268
|
+
|
|
269
|
+
if isinstance(price, (int, float)):
|
|
270
|
+
return float(price)
|
|
271
|
+
|
|
272
|
+
raise ValueError(f"Invalid price format: {price}")
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Aptos Scheme Types.
|
|
2
|
+
|
|
3
|
+
This module defines the data types used by the Aptos exact-direct payment scheme,
|
|
4
|
+
including the payload structure, transaction result, and signer protocols.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class ClientAptosSigner(Protocol):
|
|
14
|
+
"""Protocol for Aptos client-side signing operations.
|
|
15
|
+
|
|
16
|
+
Implementations must provide the signer's address and the ability
|
|
17
|
+
to sign and submit transactions to the Aptos network.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
```python
|
|
21
|
+
class MyAptosSigner:
|
|
22
|
+
def __init__(self, account, client):
|
|
23
|
+
self._account = account
|
|
24
|
+
self._client = client
|
|
25
|
+
|
|
26
|
+
def address(self) -> str:
|
|
27
|
+
return str(self._account.address())
|
|
28
|
+
|
|
29
|
+
async def sign_and_submit(self, payload: Dict, network: str) -> str:
|
|
30
|
+
txn = await self._client.submit_transaction(
|
|
31
|
+
self._account, payload
|
|
32
|
+
)
|
|
33
|
+
return txn["hash"]
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def address(self) -> str:
|
|
38
|
+
"""Return the signer's Aptos address (0x-prefixed hex).
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The account address as a hex string.
|
|
42
|
+
"""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
async def sign_and_submit(self, payload: Dict[str, Any], network: str) -> str:
|
|
46
|
+
"""Sign and submit a transaction to the Aptos network.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
payload: Transaction payload dict with function, type_arguments, and arguments.
|
|
50
|
+
network: CAIP-2 network identifier (e.g., "aptos:1").
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Transaction hash as a 0x-prefixed hex string.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
Exception: If signing or submission fails.
|
|
57
|
+
"""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@runtime_checkable
|
|
62
|
+
class FacilitatorAptosSigner(Protocol):
|
|
63
|
+
"""Protocol for Aptos facilitator-side operations.
|
|
64
|
+
|
|
65
|
+
Implementations must provide the ability to query transactions
|
|
66
|
+
from the Aptos network for verification.
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
```python
|
|
70
|
+
import httpx
|
|
71
|
+
|
|
72
|
+
class MyAptosQuerier:
|
|
73
|
+
def __init__(self, rpc_url: str):
|
|
74
|
+
self._rpc_url = rpc_url
|
|
75
|
+
self._client = httpx.AsyncClient()
|
|
76
|
+
|
|
77
|
+
def get_addresses(self, network: str) -> List[str]:
|
|
78
|
+
return [] # No on-chain addresses needed for exact-direct
|
|
79
|
+
|
|
80
|
+
async def get_transaction(self, tx_hash: str, network: str) -> Dict:
|
|
81
|
+
resp = await self._client.get(
|
|
82
|
+
f"{self._rpc_url}/transactions/by_hash/{tx_hash}"
|
|
83
|
+
)
|
|
84
|
+
return resp.json()
|
|
85
|
+
```
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def get_addresses(self, network: str) -> List[str]:
|
|
89
|
+
"""Return the facilitator's Aptos addresses for a network.
|
|
90
|
+
|
|
91
|
+
For exact-direct, the facilitator typically does not hold funds
|
|
92
|
+
and may return an empty list.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
network: CAIP-2 network identifier.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List of facilitator addresses.
|
|
99
|
+
"""
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
async def get_transaction(self, tx_hash: str, network: str) -> Dict[str, Any]:
|
|
103
|
+
"""Query a transaction by hash from the Aptos network.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
tx_hash: The transaction hash to query (0x-prefixed).
|
|
107
|
+
network: CAIP-2 network identifier.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Transaction result dict from the Aptos REST API containing fields:
|
|
111
|
+
- hash: Transaction hash
|
|
112
|
+
- version: Ledger version
|
|
113
|
+
- success: Whether the transaction succeeded
|
|
114
|
+
- vm_status: VM execution status
|
|
115
|
+
- sender: Transaction sender address
|
|
116
|
+
- timestamp: Block timestamp in microseconds
|
|
117
|
+
- payload: Transaction payload
|
|
118
|
+
- events: Transaction events
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
Exception: If the transaction is not found or the query fails.
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ExactDirectPayload:
|
|
127
|
+
"""Represents the payment payload for the exact-direct scheme on Aptos.
|
|
128
|
+
|
|
129
|
+
This payload contains proof that the client has already executed a
|
|
130
|
+
fungible asset transfer on-chain.
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
tx_hash: The transaction hash of the completed transfer.
|
|
134
|
+
from_address: The sender's Aptos address.
|
|
135
|
+
to_address: The recipient's Aptos address.
|
|
136
|
+
amount: The transfer amount in atomic units.
|
|
137
|
+
metadata_address: The FA metadata object address.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
tx_hash: str,
|
|
143
|
+
from_address: str,
|
|
144
|
+
to_address: str,
|
|
145
|
+
amount: str,
|
|
146
|
+
metadata_address: str,
|
|
147
|
+
) -> None:
|
|
148
|
+
self.tx_hash = tx_hash
|
|
149
|
+
self.from_address = from_address
|
|
150
|
+
self.to_address = to_address
|
|
151
|
+
self.amount = amount
|
|
152
|
+
self.metadata_address = metadata_address
|
|
153
|
+
|
|
154
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
155
|
+
"""Convert to dictionary for serialization.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Dict with camelCase keys matching the protocol format.
|
|
159
|
+
"""
|
|
160
|
+
return {
|
|
161
|
+
"txHash": self.tx_hash,
|
|
162
|
+
"from": self.from_address,
|
|
163
|
+
"to": self.to_address,
|
|
164
|
+
"amount": self.amount,
|
|
165
|
+
"metadataAddress": self.metadata_address,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ExactDirectPayload":
|
|
170
|
+
"""Create an ExactDirectPayload from a dictionary.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
data: Dict with payload fields (supports both camelCase and snake_case).
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
ExactDirectPayload instance.
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
ValueError: If required fields are missing.
|
|
180
|
+
"""
|
|
181
|
+
tx_hash = data.get("txHash") or data.get("tx_hash", "")
|
|
182
|
+
from_address = data.get("from") or data.get("from_address", "")
|
|
183
|
+
to_address = data.get("to") or data.get("to_address", "")
|
|
184
|
+
amount = data.get("amount", "")
|
|
185
|
+
metadata_address = (
|
|
186
|
+
data.get("metadataAddress") or data.get("metadata_address", "")
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return cls(
|
|
190
|
+
tx_hash=tx_hash,
|
|
191
|
+
from_address=from_address,
|
|
192
|
+
to_address=to_address,
|
|
193
|
+
amount=amount,
|
|
194
|
+
metadata_address=metadata_address,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def extract_transfer_details(tx: Dict[str, Any]) -> Optional[Dict[str, str]]:
|
|
199
|
+
"""Extract fungible asset transfer details from a transaction result.
|
|
200
|
+
|
|
201
|
+
Parses the transaction payload to extract sender, recipient, amount,
|
|
202
|
+
and metadata address from a primary_fungible_store::transfer call.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
tx: Transaction result dict from the Aptos REST API.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Dict with 'from', 'to', 'amount', 'metadata_address' keys,
|
|
209
|
+
or None if the transaction is not a valid FA transfer.
|
|
210
|
+
"""
|
|
211
|
+
if not tx or not tx.get("success"):
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
payload = tx.get("payload")
|
|
215
|
+
if not payload or payload.get("type") != "entry_function_payload":
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
function = payload.get("function", "")
|
|
219
|
+
if "primary_fungible_store::transfer" not in function:
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
arguments = payload.get("arguments", [])
|
|
223
|
+
if len(arguments) < 3:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
metadata_address = str(arguments[0])
|
|
227
|
+
to_address = str(arguments[1])
|
|
228
|
+
amount = str(arguments[2])
|
|
229
|
+
|
|
230
|
+
sender = tx.get("sender", "")
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
"from": sender,
|
|
234
|
+
"to": to_address,
|
|
235
|
+
"amount": amount,
|
|
236
|
+
"metadata_address": metadata_address,
|
|
237
|
+
}
|
t402/schemes/evm/__init__.py
CHANGED
|
@@ -4,20 +4,55 @@ This package provides payment scheme implementations for EVM-compatible
|
|
|
4
4
|
blockchains (Ethereum, Base, Avalanche, etc.).
|
|
5
5
|
|
|
6
6
|
Supported schemes:
|
|
7
|
-
- exact: EIP-3009 TransferWithAuthorization
|
|
7
|
+
- exact: EIP-3009 TransferWithAuthorization (recommended)
|
|
8
|
+
- exact-legacy: approve + transferFrom (DEPRECATED - see deprecation notice below)
|
|
8
9
|
- upto: EIP-2612 Permit (usage-based billing)
|
|
10
|
+
|
|
11
|
+
.. deprecated:: 2.3.0
|
|
12
|
+
The **exact-legacy** scheme is deprecated and will be removed in v3.0.0.
|
|
13
|
+
|
|
14
|
+
The exact-legacy scheme uses the traditional approve + transferFrom pattern,
|
|
15
|
+
which has several drawbacks compared to the "exact" scheme with USDT0:
|
|
16
|
+
|
|
17
|
+
1. **Two transactions required**: Users must approve then transfer
|
|
18
|
+
2. **Gas costs**: Users pay gas for both transactions
|
|
19
|
+
3. **Limited availability**: Legacy USDT only on specific chains
|
|
20
|
+
|
|
21
|
+
**Migration Guide:**
|
|
22
|
+
1. Replace `ExactLegacyEvmClientScheme` with `ExactEvmClientScheme`
|
|
23
|
+
2. Replace `ExactLegacyEvmServerScheme` with `ExactEvmServerScheme`
|
|
24
|
+
3. Use USDT0 token addresses instead of legacy USDT
|
|
25
|
+
4. See https://docs.t402.io/migration/exact-legacy for details
|
|
26
|
+
|
|
27
|
+
**USDT0 Advantages:**
|
|
28
|
+
- Single signature (no approve transaction)
|
|
29
|
+
- Gasless via EIP-3009
|
|
30
|
+
- Available on 19+ chains via LayerZero
|
|
31
|
+
- Cross-chain bridging support
|
|
9
32
|
"""
|
|
10
33
|
|
|
11
34
|
from t402.schemes.evm.exact import (
|
|
12
35
|
ExactEvmClientScheme,
|
|
13
36
|
ExactEvmServerScheme,
|
|
37
|
+
ExactEvmFacilitatorScheme,
|
|
38
|
+
FacilitatorEvmSigner,
|
|
39
|
+
EvmVerifyResult,
|
|
40
|
+
EvmTransactionConfirmation,
|
|
14
41
|
EvmSigner,
|
|
15
42
|
create_nonce,
|
|
16
43
|
SCHEME_EXACT,
|
|
17
44
|
)
|
|
18
45
|
|
|
46
|
+
from t402.schemes.evm.exact_legacy import (
|
|
47
|
+
ExactLegacyEvmClientScheme,
|
|
48
|
+
ExactLegacyEvmServerScheme,
|
|
49
|
+
SCHEME_EXACT_LEGACY,
|
|
50
|
+
)
|
|
51
|
+
|
|
19
52
|
from t402.schemes.evm.upto import (
|
|
20
53
|
UptoEvmClientScheme,
|
|
54
|
+
UptoEvmServerScheme,
|
|
55
|
+
UptoEvmFacilitatorScheme,
|
|
21
56
|
create_payment_nonce,
|
|
22
57
|
SCHEME_UPTO,
|
|
23
58
|
PermitSignature,
|
|
@@ -31,11 +66,21 @@ __all__ = [
|
|
|
31
66
|
# Exact scheme
|
|
32
67
|
"ExactEvmClientScheme",
|
|
33
68
|
"ExactEvmServerScheme",
|
|
69
|
+
"ExactEvmFacilitatorScheme",
|
|
70
|
+
"FacilitatorEvmSigner",
|
|
71
|
+
"EvmVerifyResult",
|
|
72
|
+
"EvmTransactionConfirmation",
|
|
34
73
|
"EvmSigner",
|
|
35
74
|
"create_nonce",
|
|
36
75
|
"SCHEME_EXACT",
|
|
76
|
+
# Exact-Legacy scheme
|
|
77
|
+
"ExactLegacyEvmClientScheme",
|
|
78
|
+
"ExactLegacyEvmServerScheme",
|
|
79
|
+
"SCHEME_EXACT_LEGACY",
|
|
37
80
|
# Upto scheme
|
|
38
81
|
"UptoEvmClientScheme",
|
|
82
|
+
"UptoEvmServerScheme",
|
|
83
|
+
"UptoEvmFacilitatorScheme",
|
|
39
84
|
"create_payment_nonce",
|
|
40
85
|
"SCHEME_UPTO",
|
|
41
86
|
"PermitSignature",
|
|
@@ -16,6 +16,12 @@ from t402.schemes.evm.exact.client import (
|
|
|
16
16
|
from t402.schemes.evm.exact.server import (
|
|
17
17
|
ExactEvmServerScheme,
|
|
18
18
|
)
|
|
19
|
+
from t402.schemes.evm.exact.facilitator import (
|
|
20
|
+
ExactEvmFacilitatorScheme,
|
|
21
|
+
FacilitatorEvmSigner,
|
|
22
|
+
EvmVerifyResult,
|
|
23
|
+
EvmTransactionConfirmation,
|
|
24
|
+
)
|
|
19
25
|
|
|
20
26
|
__all__ = [
|
|
21
27
|
# Client
|
|
@@ -24,6 +30,11 @@ __all__ = [
|
|
|
24
30
|
"create_nonce",
|
|
25
31
|
# Server
|
|
26
32
|
"ExactEvmServerScheme",
|
|
33
|
+
# Facilitator
|
|
34
|
+
"ExactEvmFacilitatorScheme",
|
|
35
|
+
"FacilitatorEvmSigner",
|
|
36
|
+
"EvmVerifyResult",
|
|
37
|
+
"EvmTransactionConfirmation",
|
|
27
38
|
# Constants
|
|
28
39
|
"SCHEME_EXACT",
|
|
29
40
|
]
|
t402/schemes/evm/exact/client.py
CHANGED
|
@@ -123,7 +123,9 @@ class ExactEvmClientScheme:
|
|
|
123
123
|
asset = req.get("asset", "")
|
|
124
124
|
|
|
125
125
|
# Get timeout
|
|
126
|
-
max_timeout = req.get("maxTimeoutSeconds") or req.get(
|
|
126
|
+
max_timeout = req.get("maxTimeoutSeconds") or req.get(
|
|
127
|
+
"max_timeout_seconds", 300
|
|
128
|
+
)
|
|
127
129
|
|
|
128
130
|
# Get EIP-712 domain info from extra
|
|
129
131
|
extra = req.get("extra", {})
|