t402 1.7.1__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 +164 -1
- 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 +67 -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 +70 -0
- t402/schemes/evm/upto/client.py +244 -0
- t402/schemes/evm/upto/facilitator.py +625 -0
- t402/schemes/evm/upto/server.py +243 -0
- t402/schemes/evm/upto/types.py +307 -0
- 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/schemes/upto/__init__.py +80 -0
- t402/schemes/upto/types.py +376 -0
- 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 +4 -2
- t402/wdk/errors.py +15 -5
- t402/wdk/signer.py +11 -2
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
- t402-1.9.1.dist-info/RECORD +125 -0
- t402-1.7.1.dist-info/RECORD +0 -67
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
- {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""Tezos Exact-Direct Scheme - Server Implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the server-side implementation of the exact-direct payment
|
|
4
|
+
scheme for Tezos. It handles price parsing (converting user-friendly prices to
|
|
5
|
+
atomic units) and enhancement of payment requirements with Tezos-specific
|
|
6
|
+
FA2 asset information (CAIP-19 identifiers, token metadata).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from decimal import Decimal
|
|
13
|
+
from typing import Any, Dict, List, Optional, Union
|
|
14
|
+
|
|
15
|
+
from t402.types import (
|
|
16
|
+
PaymentRequirementsV2,
|
|
17
|
+
Network,
|
|
18
|
+
)
|
|
19
|
+
from t402.schemes.interfaces import AssetAmount, SupportedKindDict
|
|
20
|
+
from t402.schemes.tezos.constants import (
|
|
21
|
+
SCHEME_EXACT_DIRECT,
|
|
22
|
+
is_tezos_network,
|
|
23
|
+
get_network_config,
|
|
24
|
+
get_token_info,
|
|
25
|
+
create_asset_identifier,
|
|
26
|
+
parse_decimal_to_atomic,
|
|
27
|
+
TokenInfo,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ExactDirectTezosServer:
|
|
35
|
+
"""Server scheme for Tezos exact-direct payments.
|
|
36
|
+
|
|
37
|
+
Handles parsing user-friendly prices (e.g., "$1.50") into atomic amounts
|
|
38
|
+
and enhancing payment requirements with Tezos-specific metadata including
|
|
39
|
+
CAIP-19 asset identifiers and token information.
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
```python
|
|
43
|
+
from t402.schemes.tezos import ExactDirectTezosServer
|
|
44
|
+
|
|
45
|
+
server = ExactDirectTezosServer()
|
|
46
|
+
|
|
47
|
+
# Parse price to atomic units
|
|
48
|
+
asset_amount = await server.parse_price("$0.10", "tezos:NetXdQprcVkpaWU")
|
|
49
|
+
# Returns: {
|
|
50
|
+
# "amount": "100000",
|
|
51
|
+
# "asset": "tezos:NetXdQprcVkpaWU/fa2:KT1XnTn74.../0",
|
|
52
|
+
# "extra": {"symbol": "USDt", "name": "Tether USD", "decimals": 6}
|
|
53
|
+
# }
|
|
54
|
+
|
|
55
|
+
# Enhance requirements with token metadata
|
|
56
|
+
enhanced = await server.enhance_requirements(
|
|
57
|
+
requirements, supported_kind, []
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
scheme = SCHEME_EXACT_DIRECT
|
|
63
|
+
caip_family = "tezos:*"
|
|
64
|
+
|
|
65
|
+
def __init__(self, preferred_token: Optional[str] = None):
|
|
66
|
+
"""Initialize the Tezos exact-direct server.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
preferred_token: Preferred token symbol (e.g., "USDt").
|
|
70
|
+
If set, this token will be used for price conversions
|
|
71
|
+
when available on the network. Defaults to the network's
|
|
72
|
+
default token.
|
|
73
|
+
"""
|
|
74
|
+
self._preferred_token = preferred_token
|
|
75
|
+
|
|
76
|
+
async def parse_price(
|
|
77
|
+
self,
|
|
78
|
+
price: Union[str, int, float, Dict[str, Any]],
|
|
79
|
+
network: Network,
|
|
80
|
+
) -> AssetAmount:
|
|
81
|
+
"""Parse a user-friendly price to atomic amount and asset.
|
|
82
|
+
|
|
83
|
+
Supports:
|
|
84
|
+
- String with $ prefix: "$0.10" -> 100000 (6 decimals)
|
|
85
|
+
- String without prefix: "0.10" -> 100000
|
|
86
|
+
- Integer/float: 0.10 -> 100000
|
|
87
|
+
- Dict (AssetAmount): {"amount": "100000", "asset": "tezos:.../fa2:KT1.../0"}
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
price: User-friendly price
|
|
91
|
+
network: Network identifier (CAIP-2 format, e.g., "tezos:NetXdQprcVkpaWU")
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
AssetAmount dict with amount (atomic), asset (CAIP-19), and extra metadata
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
ValueError: If network is unsupported or price format is invalid
|
|
98
|
+
"""
|
|
99
|
+
# Validate network
|
|
100
|
+
if not is_tezos_network(network):
|
|
101
|
+
raise ValueError(f"Invalid Tezos network: {network}")
|
|
102
|
+
|
|
103
|
+
# Handle dict (already in AssetAmount format)
|
|
104
|
+
if isinstance(price, dict):
|
|
105
|
+
amount_str = str(price.get("amount", "0"))
|
|
106
|
+
asset = price.get("asset", "")
|
|
107
|
+
if not asset:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"Asset must be specified for AssetAmount on network {network}"
|
|
110
|
+
)
|
|
111
|
+
return {
|
|
112
|
+
"amount": amount_str,
|
|
113
|
+
"asset": asset,
|
|
114
|
+
"extra": price.get("extra", {}),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Get default token for the network
|
|
118
|
+
token = self._get_default_token(network)
|
|
119
|
+
if token is None:
|
|
120
|
+
raise ValueError(f"No token configured for network {network}")
|
|
121
|
+
|
|
122
|
+
# Parse price string/number to decimal
|
|
123
|
+
amount_decimal = self._parse_money_to_decimal(price)
|
|
124
|
+
|
|
125
|
+
# Convert to atomic units
|
|
126
|
+
atomic_amount = int(amount_decimal * Decimal(10**token.decimals))
|
|
127
|
+
|
|
128
|
+
# Build CAIP-19 asset identifier
|
|
129
|
+
asset_id = create_asset_identifier(network, token.contract_address, token.token_id)
|
|
130
|
+
|
|
131
|
+
# Build extra metadata
|
|
132
|
+
extra: Dict[str, Any] = {
|
|
133
|
+
"symbol": token.symbol,
|
|
134
|
+
"name": token.name,
|
|
135
|
+
"decimals": token.decimals,
|
|
136
|
+
"tokenId": token.token_id,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
"amount": str(atomic_amount),
|
|
141
|
+
"asset": asset_id,
|
|
142
|
+
"extra": extra,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async def enhance_requirements(
|
|
146
|
+
self,
|
|
147
|
+
requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
|
|
148
|
+
supported_kind: SupportedKindDict,
|
|
149
|
+
facilitator_extensions: List[str],
|
|
150
|
+
) -> Union[PaymentRequirementsV2, Dict[str, Any]]:
|
|
151
|
+
"""Enhance payment requirements with Tezos-specific metadata.
|
|
152
|
+
|
|
153
|
+
Adds FA2 token information to the extra field, resolves CAIP-19 asset
|
|
154
|
+
identifiers, and converts decimal amounts to atomic units if needed.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
requirements: Base payment requirements with amount/asset set
|
|
158
|
+
supported_kind: Matched SupportedKind from the facilitator
|
|
159
|
+
facilitator_extensions: Extension keys supported by facilitator
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Enhanced requirements dict with Tezos metadata in extra field
|
|
163
|
+
"""
|
|
164
|
+
# Convert to dict for modification
|
|
165
|
+
if hasattr(requirements, "model_dump"):
|
|
166
|
+
req = requirements.model_dump(by_alias=True)
|
|
167
|
+
else:
|
|
168
|
+
req = dict(requirements)
|
|
169
|
+
|
|
170
|
+
network = req.get("network", "")
|
|
171
|
+
|
|
172
|
+
# Validate network
|
|
173
|
+
if not is_tezos_network(network):
|
|
174
|
+
raise ValueError(f"Invalid Tezos network: {network}")
|
|
175
|
+
|
|
176
|
+
# Get network config
|
|
177
|
+
network_config = get_network_config(network)
|
|
178
|
+
if network_config is None:
|
|
179
|
+
raise ValueError(f"Unsupported Tezos network: {network}")
|
|
180
|
+
|
|
181
|
+
# Resolve token info
|
|
182
|
+
token = self._get_default_token(network)
|
|
183
|
+
|
|
184
|
+
# Ensure asset is in CAIP-19 format
|
|
185
|
+
if not req.get("asset") and token is not None:
|
|
186
|
+
req["asset"] = create_asset_identifier(
|
|
187
|
+
network, token.contract_address, token.token_id
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Convert decimal amount to atomic units if needed
|
|
191
|
+
amount = req.get("amount", "")
|
|
192
|
+
if amount and "." in amount and token is not None:
|
|
193
|
+
req["amount"] = parse_decimal_to_atomic(amount, token.decimals)
|
|
194
|
+
|
|
195
|
+
# Ensure extra exists
|
|
196
|
+
if "extra" not in req or req["extra"] is None:
|
|
197
|
+
req["extra"] = {}
|
|
198
|
+
|
|
199
|
+
# Add token metadata to extra
|
|
200
|
+
if token is not None:
|
|
201
|
+
if "assetSymbol" not in req["extra"]:
|
|
202
|
+
req["extra"]["assetSymbol"] = token.symbol
|
|
203
|
+
if "assetDecimals" not in req["extra"]:
|
|
204
|
+
req["extra"]["assetDecimals"] = token.decimals
|
|
205
|
+
if "assetName" not in req["extra"]:
|
|
206
|
+
req["extra"]["assetName"] = token.name
|
|
207
|
+
|
|
208
|
+
# Add network name for convenience
|
|
209
|
+
if "networkName" not in req["extra"]:
|
|
210
|
+
req["extra"]["networkName"] = network_config.name
|
|
211
|
+
|
|
212
|
+
# Copy extensions from supportedKind if provided
|
|
213
|
+
if supported_kind.get("extra"):
|
|
214
|
+
for key, value in supported_kind["extra"].items():
|
|
215
|
+
if key not in req["extra"]:
|
|
216
|
+
req["extra"][key] = value
|
|
217
|
+
|
|
218
|
+
return req
|
|
219
|
+
|
|
220
|
+
def _get_default_token(self, network: str) -> Optional[TokenInfo]:
|
|
221
|
+
"""Get the default token for a given network.
|
|
222
|
+
|
|
223
|
+
If a preferred token is configured, tries to use it first.
|
|
224
|
+
Falls back to the network's default token.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
network: CAIP-2 network identifier
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
TokenInfo if available, None otherwise
|
|
231
|
+
"""
|
|
232
|
+
# If a preferred token is configured, try to use it
|
|
233
|
+
if self._preferred_token:
|
|
234
|
+
token = get_token_info(network, self._preferred_token)
|
|
235
|
+
if token is not None:
|
|
236
|
+
return token
|
|
237
|
+
|
|
238
|
+
# Use the network's default token
|
|
239
|
+
config = get_network_config(network)
|
|
240
|
+
if config is None:
|
|
241
|
+
return None
|
|
242
|
+
return config.default_token
|
|
243
|
+
|
|
244
|
+
def _parse_money_to_decimal(self, price: Union[str, int, float]) -> Decimal:
|
|
245
|
+
"""Parse a money value to a Decimal amount.
|
|
246
|
+
|
|
247
|
+
Handles currency symbols, whitespace, and common suffixes.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
price: Price as string, int, or float
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Decimal amount
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
ValueError: If the price format cannot be parsed
|
|
257
|
+
"""
|
|
258
|
+
if isinstance(price, (int, float)):
|
|
259
|
+
return Decimal(str(price))
|
|
260
|
+
|
|
261
|
+
if isinstance(price, str):
|
|
262
|
+
clean_price = price.strip()
|
|
263
|
+
# Remove currency prefix
|
|
264
|
+
if clean_price.startswith("$"):
|
|
265
|
+
clean_price = clean_price[1:]
|
|
266
|
+
# Remove common suffixes
|
|
267
|
+
for suffix in (" USD", " USDT", " USDt"):
|
|
268
|
+
if clean_price.endswith(suffix):
|
|
269
|
+
clean_price = clean_price[: -len(suffix)]
|
|
270
|
+
clean_price = clean_price.strip()
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
return Decimal(clean_price)
|
|
274
|
+
except Exception:
|
|
275
|
+
raise ValueError(f"Failed to parse price string: {price}")
|
|
276
|
+
|
|
277
|
+
raise ValueError(f"Unsupported price type: {type(price)}")
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Tezos Scheme Type Definitions.
|
|
2
|
+
|
|
3
|
+
This module defines the Pydantic models and Protocol interfaces used by the
|
|
4
|
+
Tezos exact-direct payment scheme.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, List, Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
12
|
+
from pydantic.alias_generators import to_camel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@runtime_checkable
|
|
16
|
+
class ClientTezosSigner(Protocol):
|
|
17
|
+
"""Protocol for Tezos client-side signing operations.
|
|
18
|
+
|
|
19
|
+
Implementations are responsible for managing private keys, constructing
|
|
20
|
+
FA2 transfer operations, signing, and injecting them into the Tezos network.
|
|
21
|
+
|
|
22
|
+
Example implementation:
|
|
23
|
+
```python
|
|
24
|
+
class MyTezosSigner:
|
|
25
|
+
def __init__(self, private_key: str, rpc_url: str):
|
|
26
|
+
self._key = private_key
|
|
27
|
+
self._rpc_url = rpc_url
|
|
28
|
+
|
|
29
|
+
def address(self) -> str:
|
|
30
|
+
return "tz1..."
|
|
31
|
+
|
|
32
|
+
async def transfer_fa2(
|
|
33
|
+
self,
|
|
34
|
+
contract: str,
|
|
35
|
+
token_id: int,
|
|
36
|
+
to: str,
|
|
37
|
+
amount: int,
|
|
38
|
+
network: str,
|
|
39
|
+
) -> str:
|
|
40
|
+
# Build FA2 transfer, sign and inject
|
|
41
|
+
return "o..." # operation hash
|
|
42
|
+
```
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def address(self) -> str:
|
|
46
|
+
"""Return the Tezos address (tz1/tz2/tz3) of the signer."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
async def transfer_fa2(
|
|
50
|
+
self,
|
|
51
|
+
contract: str,
|
|
52
|
+
token_id: int,
|
|
53
|
+
to: str,
|
|
54
|
+
amount: int,
|
|
55
|
+
network: str,
|
|
56
|
+
) -> str:
|
|
57
|
+
"""Execute an FA2 transfer operation on-chain.
|
|
58
|
+
|
|
59
|
+
Constructs the FA2 transfer call parameter, signs the operation,
|
|
60
|
+
and injects it into the Tezos network.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
contract: The FA2 contract address (KT1...)
|
|
64
|
+
token_id: The token ID within the FA2 contract
|
|
65
|
+
to: Recipient Tezos address
|
|
66
|
+
amount: Amount in atomic units (integer)
|
|
67
|
+
network: CAIP-2 network identifier
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The operation hash (starts with 'o', 51 characters)
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
Exception: If the transfer fails
|
|
74
|
+
"""
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@runtime_checkable
|
|
79
|
+
class FacilitatorTezosSigner(Protocol):
|
|
80
|
+
"""Protocol for Tezos facilitator-side operations.
|
|
81
|
+
|
|
82
|
+
Implementations query the Tezos blockchain (via RPC or indexer)
|
|
83
|
+
to verify operation status and details.
|
|
84
|
+
|
|
85
|
+
Example implementation:
|
|
86
|
+
```python
|
|
87
|
+
class MyTezosQuerier:
|
|
88
|
+
def __init__(self, indexer_url: str):
|
|
89
|
+
self._indexer_url = indexer_url
|
|
90
|
+
|
|
91
|
+
async def get_operation(
|
|
92
|
+
self, op_hash: str, network: str
|
|
93
|
+
) -> Dict[str, Any]:
|
|
94
|
+
# Query TzKT indexer for operation details
|
|
95
|
+
response = await httpx.get(
|
|
96
|
+
f"{self._indexer_url}/v1/operations/{op_hash}"
|
|
97
|
+
)
|
|
98
|
+
return response.json()
|
|
99
|
+
```
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
async def get_operation(self, op_hash: str, network: str) -> Dict[str, Any]:
|
|
103
|
+
"""Query an operation by its hash.
|
|
104
|
+
|
|
105
|
+
Returns the operation details from the Tezos blockchain,
|
|
106
|
+
typically via a TzKT indexer API.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
op_hash: The operation hash to query
|
|
110
|
+
network: CAIP-2 network identifier
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Dict containing operation details with at minimum:
|
|
114
|
+
- "status": str ("applied", "failed", "backtracked", "skipped")
|
|
115
|
+
- "sender": Dict with "address" key
|
|
116
|
+
- "target": Dict with "address" key (the FA2 contract)
|
|
117
|
+
- "entrypoint": str (should be "transfer")
|
|
118
|
+
- "parameter": The FA2 transfer parameters
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
Exception: If the query fails or operation not found
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TezosFA2TransferTx(BaseModel):
|
|
127
|
+
"""A single transfer transaction within an FA2 transfer batch.
|
|
128
|
+
|
|
129
|
+
Attributes:
|
|
130
|
+
to: Recipient address
|
|
131
|
+
token_id: Token ID within the FA2 contract
|
|
132
|
+
amount: Amount in atomic units
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
to: str = Field(alias="to_")
|
|
136
|
+
token_id: int = Field(alias="token_id")
|
|
137
|
+
amount: str
|
|
138
|
+
|
|
139
|
+
model_config = ConfigDict(
|
|
140
|
+
populate_by_name=True,
|
|
141
|
+
from_attributes=True,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TezosFA2TransferParam(BaseModel):
|
|
146
|
+
"""FA2 transfer parameter structure.
|
|
147
|
+
|
|
148
|
+
Represents a single sender's batch of transfers in the FA2 standard.
|
|
149
|
+
|
|
150
|
+
Attributes:
|
|
151
|
+
from_: Sender address
|
|
152
|
+
txs: List of transfer transactions
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
from_: str = Field(alias="from_")
|
|
156
|
+
txs: List[TezosFA2TransferTx]
|
|
157
|
+
|
|
158
|
+
model_config = ConfigDict(
|
|
159
|
+
populate_by_name=True,
|
|
160
|
+
from_attributes=True,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class ExactDirectPayload(BaseModel):
|
|
165
|
+
"""Payload for the Tezos exact-direct payment scheme.
|
|
166
|
+
|
|
167
|
+
Contains the operation hash proving on-chain execution and metadata
|
|
168
|
+
about the FA2 transfer for verification.
|
|
169
|
+
|
|
170
|
+
Attributes:
|
|
171
|
+
op_hash: The Tezos operation hash (starts with 'o', 51 chars)
|
|
172
|
+
from_: Sender's Tezos address
|
|
173
|
+
to: Recipient's Tezos address
|
|
174
|
+
amount: Amount transferred in atomic units
|
|
175
|
+
contract_address: FA2 contract address
|
|
176
|
+
token_id: Token ID within the FA2 contract
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
op_hash: str = Field(alias="opHash")
|
|
180
|
+
from_: str = Field(alias="from")
|
|
181
|
+
to: str
|
|
182
|
+
amount: str
|
|
183
|
+
contract_address: str = Field(alias="contractAddress")
|
|
184
|
+
token_id: int = Field(alias="tokenId")
|
|
185
|
+
|
|
186
|
+
model_config = ConfigDict(
|
|
187
|
+
alias_generator=to_camel,
|
|
188
|
+
populate_by_name=True,
|
|
189
|
+
from_attributes=True,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
@field_validator("amount")
|
|
193
|
+
@classmethod
|
|
194
|
+
def validate_amount(cls, v: str) -> str:
|
|
195
|
+
"""Validate that amount is a positive integer string."""
|
|
196
|
+
try:
|
|
197
|
+
val = int(v)
|
|
198
|
+
if val <= 0:
|
|
199
|
+
raise ValueError("amount must be a positive integer")
|
|
200
|
+
except ValueError:
|
|
201
|
+
raise ValueError("amount must be a positive integer encoded as a string")
|
|
202
|
+
return v
|
|
203
|
+
|
|
204
|
+
@field_validator("op_hash")
|
|
205
|
+
@classmethod
|
|
206
|
+
def validate_op_hash(cls, v: str) -> str:
|
|
207
|
+
"""Validate operation hash format."""
|
|
208
|
+
if not v.startswith("o"):
|
|
209
|
+
raise ValueError("Operation hash must start with 'o'")
|
|
210
|
+
if len(v) != 51:
|
|
211
|
+
raise ValueError("Operation hash must be 51 characters")
|
|
212
|
+
return v
|
|
213
|
+
|
|
214
|
+
def to_map(self) -> Dict[str, Any]:
|
|
215
|
+
"""Convert to a dict suitable for payment payload.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Dict with camelCase keys matching the protocol format.
|
|
219
|
+
"""
|
|
220
|
+
return self.model_dump(by_alias=True)
|
t402/schemes/ton/__init__.py
CHANGED
|
@@ -9,14 +9,21 @@ Supported schemes:
|
|
|
9
9
|
from t402.schemes.ton.exact import (
|
|
10
10
|
ExactTonClientScheme,
|
|
11
11
|
ExactTonServerScheme,
|
|
12
|
+
ExactTonFacilitatorScheme,
|
|
12
13
|
TonSigner,
|
|
14
|
+
FacilitatorTonSigner,
|
|
13
15
|
SCHEME_EXACT,
|
|
14
16
|
)
|
|
15
17
|
|
|
16
18
|
__all__ = [
|
|
17
|
-
#
|
|
19
|
+
# Client
|
|
18
20
|
"ExactTonClientScheme",
|
|
19
|
-
"ExactTonServerScheme",
|
|
20
21
|
"TonSigner",
|
|
22
|
+
# Server
|
|
23
|
+
"ExactTonServerScheme",
|
|
24
|
+
# Facilitator
|
|
25
|
+
"ExactTonFacilitatorScheme",
|
|
26
|
+
"FacilitatorTonSigner",
|
|
27
|
+
# Constants
|
|
21
28
|
"SCHEME_EXACT",
|
|
22
29
|
]
|
|
@@ -15,6 +15,10 @@ from t402.schemes.ton.exact.client import (
|
|
|
15
15
|
from t402.schemes.ton.exact.server import (
|
|
16
16
|
ExactTonServerScheme,
|
|
17
17
|
)
|
|
18
|
+
from t402.schemes.ton.exact.facilitator import (
|
|
19
|
+
ExactTonFacilitatorScheme,
|
|
20
|
+
FacilitatorTonSigner,
|
|
21
|
+
)
|
|
18
22
|
|
|
19
23
|
__all__ = [
|
|
20
24
|
# Client
|
|
@@ -22,6 +26,9 @@ __all__ = [
|
|
|
22
26
|
"TonSigner",
|
|
23
27
|
# Server
|
|
24
28
|
"ExactTonServerScheme",
|
|
29
|
+
# Facilitator
|
|
30
|
+
"ExactTonFacilitatorScheme",
|
|
31
|
+
"FacilitatorTonSigner",
|
|
25
32
|
# Constants
|
|
26
33
|
"SCHEME_EXACT",
|
|
27
34
|
]
|